From d2bea4cb8822536b7fee57a1c8d332801775b876 Mon Sep 17 00:00:00 2001 From: Tushar Singh Date: Tue, 3 Oct 2023 20:48:40 +0530 Subject: [PATCH 01/24] initial commit for underline style and color --- src/renderer/atlas/AtlasEngine.api.cpp | 22 ++++- src/renderer/atlas/BackendD3D.cpp | 107 ++++++++++++++++++++++++- src/renderer/atlas/BackendD3D.h | 11 ++- src/renderer/atlas/common.h | 2 + src/renderer/atlas/shader_common.hlsl | 3 + src/renderer/atlas/shader_ps.hlsl | 32 ++++++++ src/renderer/base/RenderSettings.cpp | 41 ++++++++++ src/renderer/base/renderer.cpp | 14 +++- src/renderer/inc/IRenderEngine.hpp | 3 + src/renderer/inc/RenderSettings.hpp | 1 + 10 files changed, 223 insertions(+), 13 deletions(-) diff --git a/src/renderer/atlas/AtlasEngine.api.cpp b/src/renderer/atlas/AtlasEngine.api.cpp index 4459c97a8e5..0c7a5983d51 100644 --- a/src/renderer/atlas/AtlasEngine.api.cpp +++ b/src/renderer/atlas/AtlasEngine.api.cpp @@ -686,12 +686,19 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo adjustedWidth = std::max(1.0f, adjustedWidth); adjustedHeight = std::max(1.0f, adjustedHeight); + // TODO: This helps in testing the underlines at different underline + // widths, particularly curly line, so they don't break if/when we get + // support for user customizable underline width. Remove it before merging. + // I'm using 1.5f as a hardcoded value below. In the future, we would + // get it from the user/config. + const auto underlineWidthScale = std::clamp(1.0f, 1.5f, 2.0f); + const auto baseline = std::roundf(ascent + (lineGap + adjustedHeight - advanceHeight) / 2.0f); const auto underlinePos = std::roundf(baseline + underlinePosition); - const auto underlineWidth = std::max(1.0f, std::roundf(underlineThickness)); + const auto underlineWidth = std::max(1.0f, std::roundf(underlineThickness * underlineWidthScale)); const auto strikethroughPos = std::roundf(baseline + strikethroughPosition); - const auto strikethroughWidth = std::max(1.0f, std::roundf(strikethroughThickness)); - const auto thinLineWidth = std::max(1.0f, std::roundf(underlineThickness / 2.0f)); + const auto strikethroughWidth = std::max(1.0f, std::roundf(strikethroughThickness * underlineWidthScale)); + const auto thinLineWidth = std::max(1.0f, std::roundf(underlineThickness * underlineWidthScale / 2.0f)); // For double underlines we loosely follow what Word does: // 1. The lines are half the width of an underline (= thinLineWidth) @@ -715,6 +722,12 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo // Our cells can't overlap each other so we additionally clamp the bottom line to be inside the cell boundaries. doubleUnderlinePosBottom = std::min(doubleUnderlinePosBottom, adjustedHeight - thinLineWidth); + // For curly-line, we'll make room for the top and bottom curve ("wave"). + // The baseline for curly-line is kept at the baseline of singly underline. + const auto curlyLinePeakHeight = std::roundf(fontMetrics->curlyUnderlineWaviness * fontSizeInPx); + const auto curlyUnderlinePos = underlinePos - (curlyLinePeakHeight + underlineThickness / 2.0f); + const auto curlyUnderlineWidth = underlineWidth + 2.0f * (curlyLinePeakHeight + underlineThickness / 2.0f); + const auto cellWidth = gsl::narrow(lrintf(adjustedWidth)); const auto cellHeight = gsl::narrow(lrintf(adjustedHeight)); @@ -748,6 +761,8 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo const auto underlinePosU16 = gsl::narrow_cast(lrintf(underlinePos)); const auto underlineWidthU16 = gsl::narrow_cast(lrintf(underlineWidth)); + const auto curlyUnderlinePosU16 = gsl::narrow_cast(lrintf(curlyUnderlinePos)); + const auto curlyUnderlineWidthU16 = gsl::narrow_cast(lrintf(curlyUnderlineWidth)); const auto strikethroughPosU16 = gsl::narrow_cast(lrintf(strikethroughPos)); const auto strikethroughWidthU16 = gsl::narrow_cast(lrintf(strikethroughWidth)); const auto doubleUnderlinePosTopU16 = gsl::narrow_cast(lrintf(doubleUnderlinePosTop)); @@ -774,6 +789,7 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo fontMetrics->underline = { underlinePosU16, underlineWidthU16 }; fontMetrics->strikethrough = { strikethroughPosU16, strikethroughWidthU16 }; + fontMetrics->curlyUnderline = { curlyUnderlinePosU16, curlyUnderlineWidthU16 }; fontMetrics->doubleUnderline[0] = { doubleUnderlinePosTopU16, thinLineWidthU16 }; fontMetrics->doubleUnderline[1] = { doubleUnderlinePosBottomU16, thinLineWidthU16 }; fontMetrics->overline = { 0, underlineWidthU16 }; diff --git a/src/renderer/atlas/BackendD3D.cpp b/src/renderer/atlas/BackendD3D.cpp index 846567ca613..d25198c2e16 100644 --- a/src/renderer/atlas/BackendD3D.cpp +++ b/src/renderer/atlas/BackendD3D.cpp @@ -736,6 +736,9 @@ void BackendD3D::_resetGlyphAtlas(const RenderingPayload& p) _d2dBeginDrawing(); _d2dRenderTarget->Clear(); + // (re)draw custom geometries + _drawCurlylineToAtlas(p); + _fontChangedResetGlyphAtlas = false; } @@ -772,9 +775,7 @@ void BackendD3D::_resizeGlyphAtlas(const RenderingPayload& p, const u16 u, const _d2dRenderTarget.try_query_to(_d2dRenderTarget4.addressof()); _d2dRenderTarget->SetUnitMode(D2D1_UNIT_MODE_PIXELS); - // We don't really use D2D for anything except DWrite, but it - // can't hurt to ensure that everything it does is pixel aligned. - _d2dRenderTarget->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED); + _d2dRenderTarget->SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE); // Ensure that D2D uses the exact same gamma as our shader uses. _d2dRenderTarget->SetTextRenderingParams(_textRenderingParams.get()); @@ -1644,6 +1645,7 @@ void BackendD3D::_drawGridlines(const RenderingPayload& p, u16 y) const auto cellSize = p.s->font->cellSize; const auto dottedLineType = horizontalShift ? ShadingType::DottedLineWide : ShadingType::DottedLine; + const auto dashedLineType = horizontalShift ? ShadingType::DashedLineWide : ShadingType::DashedLine; const auto rowTop = static_cast(cellSize.y * y); const auto rowBottom = static_cast(rowTop + cellSize.y); @@ -1695,6 +1697,10 @@ void BackendD3D::_drawGridlines(const RenderingPayload& p, u16 y) .size = { width, static_cast(rb - rt) }, .color = r.color, }; + if (shadingType == ShadingType::CurlyLine) + { + _getLastQuad().texcoord = _curlyLineTexCoord; + } } }; @@ -1724,10 +1730,18 @@ void BackendD3D::_drawGridlines(const RenderingPayload& p, u16 y) { appendHorizontalLine(r, p.s->font->underline, ShadingType::SolidLine); } - if (r.lines.test(GridLines::HyperlinkUnderline)) + if (r.lines.any(GridLines::DottedUnderline, GridLines::HyperlinkUnderline)) { appendHorizontalLine(r, p.s->font->underline, dottedLineType); } + if (r.lines.test(GridLines::DashedUnderline)) + { + appendHorizontalLine(r, p.s->font->underline, dashedLineType); + } + if (r.lines.test(GridLines::CurlyUnderline)) + { + appendHorizontalLine(r, p.s->font->curlyUnderline, ShadingType::CurlyLine); + } if (r.lines.test(GridLines::DoubleUnderline)) { for (const auto pos : p.s->font->doubleUnderline) @@ -1742,6 +1756,91 @@ void BackendD3D::_drawGridlines(const RenderingPayload& p, u16 y) } } +void BackendD3D::_drawCurlylineToAtlas(const RenderingPayload& p) +{ + const auto cellWidth = p.s->font->cellSize.x; + + // draw cell-width worth of curlyline and reuse it as a tiling texture for + // longer lengths. + stbrp_rect rect{ + .w = cellWidth, + .h = p.s->font->curlyUnderline.height, + }; + if (!stbrp_pack_rects(&_rectPacker, &rect, 1)) + { + _drawGlyphPrepareRetry(p); + return; + } + + const auto baselineY = p.s->font->curlyUnderline.height / 2.0f; + const auto ctrlPointOffsetY = std::roundf(p.s->font->curlyUnderlineWaviness * p.s->font->fontSize); + + wil::com_ptr curlyLine; + THROW_IF_FAILED(p.d2dFactory->CreatePathGeometry(curlyLine.addressof())); + + // The ends of the curve drawn by a PathGeometry is always titled at an + // angle of the tangent at the end points. Since we are going to tile them + // one after another, we need ends to be vertically flat. So we'll draw + // three curly lines, and cut the middle one off. This will give us a + // curly line with flat ends. + { + wil::com_ptr sink; + curlyLine->Open(sink.addressof()); + + // first wave (starts at -cellSize.x) + sink->BeginFigure( + D2D1::Point2F(-cellWidth, baselineY), + D2D1_FIGURE_BEGIN_HOLLOW); + + sink->AddBezier( + D2D1::BezierSegment( + D2D1::Point2F(-cellWidth / 2.0f, baselineY - ctrlPointOffsetY), + D2D1::Point2F(-cellWidth / 2.0f, baselineY + ctrlPointOffsetY), + D2D1::Point2F(0.0f, baselineY))); + + // second wave (starts at 0.0f) + sink->AddBezier( + D2D1::BezierSegment( + D2D1::Point2F(cellWidth / 2.0f, baselineY - ctrlPointOffsetY), + D2D1::Point2F(cellWidth / 2.0f, baselineY + ctrlPointOffsetY), + D2D1::Point2F(cellWidth, baselineY))); + + // third wave (starts at 'cellSize.x' px) + sink->AddBezier( + D2D1::BezierSegment( + D2D1::Point2F(cellWidth + cellWidth / 2.0f, baselineY - ctrlPointOffsetY), + D2D1::Point2F(cellWidth + cellWidth / 2.0f, baselineY + ctrlPointOffsetY), + D2D1::Point2F(2.0f * cellWidth, baselineY))); + + sink->EndFigure(D2D1_FIGURE_END_OPEN); + + sink->Close(); + } + + _d2dBeginDrawing(); + + // Clip everything but the middle wave + { + const auto clipRect = D2D1::RectF( + static_cast(rect.x), + static_cast(rect.y), + static_cast(rect.x + rect.w), + static_cast(rect.y + rect.h)); + _d2dRenderTarget->PushAxisAlignedClip(clipRect, D2D1_ANTIALIAS_MODE_ALIASED); + } + + auto const strokeWidth = p.s->font->underline.height; + _d2dRenderTarget->DrawGeometry(curlyLine.get(), _brush.get(), strokeWidth); + + _d2dRenderTarget->PopAxisAlignedClip(); + + _d2dEndDrawing(); + + // store the texture coordinates + _curlyLineTexCoord.x = rect.x; + _curlyLineTexCoord.y = rect.y; +} + void BackendD3D::_drawCursorBackground(const RenderingPayload& p) { _cursorRects.clear(); diff --git a/src/renderer/atlas/BackendD3D.h b/src/renderer/atlas/BackendD3D.h index 14f4ca2943e..0ce08cce479 100644 --- a/src/renderer/atlas/BackendD3D.h +++ b/src/renderer/atlas/BackendD3D.h @@ -67,11 +67,14 @@ namespace Microsoft::Console::Render::Atlas TextPassthrough = 3, DottedLine = 4, DottedLineWide = 5, + DashedLine = 6, + DashedLineWide = 7, + CurlyLine = 8, // All items starting here will be drawing as a solid RGBA color - SolidLine = 6, + SolidLine = 9, - Cursor = 7, - Selection = 8, + Cursor = 10, + Selection = 11, TextDrawingFirst = TextGrayscale, TextDrawingLast = SolidLine, @@ -224,6 +227,7 @@ namespace Microsoft::Console::Render::Atlas void _drawGlyphPrepareRetry(const RenderingPayload& p); void _splitDoubleHeightGlyph(const RenderingPayload& p, const AtlasFontFaceEntryInner& fontFaceEntry, AtlasGlyphEntry& glyphEntry); void _drawGridlines(const RenderingPayload& p, u16 y); + void _drawCurlylineToAtlas(const RenderingPayload& p); void _drawCursorBackground(const RenderingPayload& p); ATLAS_ATTR_COLD void _drawCursorForeground(); ATLAS_ATTR_COLD size_t _drawCursorForegroundSlowPath(const CursorRect& c, size_t offset); @@ -291,6 +295,7 @@ namespace Microsoft::Console::Render::Atlas // The bounding rect of _cursorRects in pixels. til::rect _cursorPosition; + u16x2 _curlyLineTexCoord; bool _requiresContinuousRedraw = false; #if ATLAS_DEBUG_SHOW_DIRTY diff --git a/src/renderer/atlas/common.h b/src/renderer/atlas/common.h index bb46ff61547..2752bbbf757 100644 --- a/src/renderer/atlas/common.h +++ b/src/renderer/atlas/common.h @@ -346,6 +346,7 @@ namespace Microsoft::Console::Render::Atlas u16 baseline = 0; u16 descender = 0; u16 thinLineWidth = 0; + f32 curlyUnderlineWaviness = 0.28f; // Control points' height in `em` units. FontDecorationPosition gridTop; FontDecorationPosition gridBottom; @@ -353,6 +354,7 @@ namespace Microsoft::Console::Render::Atlas FontDecorationPosition gridRight; FontDecorationPosition underline; + FontDecorationPosition curlyUnderline; FontDecorationPosition strikethrough; FontDecorationPosition doubleUnderline[2]; FontDecorationPosition overline; diff --git a/src/renderer/atlas/shader_common.hlsl b/src/renderer/atlas/shader_common.hlsl index 957b17ac6ad..f01e26be881 100644 --- a/src/renderer/atlas/shader_common.hlsl +++ b/src/renderer/atlas/shader_common.hlsl @@ -8,6 +8,9 @@ #define SHADING_TYPE_TEXT_PASSTHROUGH 3 #define SHADING_TYPE_DOTTED_LINE 4 #define SHADING_TYPE_DOTTED_LINE_WIDE 5 +#define SHADING_TYPE_DASHED_LINE 6 +#define SHADING_TYPE_DASHED_LINE_WIDE 7 +#define SHADING_TYPE_CURLY_LINE 8 // clang-format on struct VSData diff --git a/src/renderer/atlas/shader_ps.hlsl b/src/renderer/atlas/shader_ps.hlsl index 2cb92947ce2..4254feb26f0 100644 --- a/src/renderer/atlas/shader_ps.hlsl +++ b/src/renderer/atlas/shader_ps.hlsl @@ -85,6 +85,38 @@ Output main(PSData data) : SV_Target weights = color.aaaa; break; } + case SHADING_TYPE_DASHED_LINE: + { + const bool on = frac(data.position.x / backgroundCellSize.x) < 0.5f; + color = on * premultiplyColor(data.color); + weights = color.aaaa; + break; + } + case SHADING_TYPE_DASHED_LINE_WIDE: + { + const bool on = frac(data.position.x / (2.0f * backgroundCellSize.x)) < 0.5f; + color = on * premultiplyColor(data.color); + weights = color.aaaa; + break; + } + case SHADING_TYPE_CURLY_LINE: + { + const float4 foreground = premultiplyColor(data.color); + const float blendEnhancedContrast = DWrite_ApplyLightOnDarkContrastAdjustment(enhancedContrast, data.color.rgb); + const float intensity = DWrite_CalcColorIntensity(data.color.rgb); + + const float2 texcoord = { + data.texcoord.x % backgroundCellSize.x, + data.texcoord.y % backgroundCellSize.y + }; + const float4 glyph = glyphAtlas[texcoord]; + const float contrasted = DWrite_EnhanceContrast(glyph.a, blendEnhancedContrast); + const float alphaCorrected = DWrite_ApplyAlphaCorrection(contrasted, intensity, gammaRatios); + + color = alphaCorrected * foreground; + weights = color.aaaa; + break; + } default: { color = premultiplyColor(data.color); diff --git a/src/renderer/base/RenderSettings.cpp b/src/renderer/base/RenderSettings.cpp index 03c4795635a..f885957796a 100644 --- a/src/renderer/base/RenderSettings.cpp +++ b/src/renderer/base/RenderSettings.cpp @@ -212,6 +212,47 @@ std::pair RenderSettings::GetAttributeColorsWithAlpha(const return { fg, bg }; } +// Routine Description: +// - Calculates the RGB underline color of a given text attribute, using the +// current color table configuration and active render settings. +// - Returns the current foreground color when the underline color isn't set. +// Arguments: +// - attr - The TextAttribute to retrieve the underline color from. +// Return Value: +// - The color value of the attribute's underline. +COLORREF RenderSettings::GetAttributeUnderlineColor(const TextAttribute& attr) const noexcept +{ + const auto [fg, bg] = GetAttributeColors(attr); + const auto ulTextColor = attr.GetUnderlineColor(); + if (ulTextColor.IsDefault()) + { + return fg; + } + + const auto defaultUlIndex = GetColorAliasIndex(ColorAlias::DefaultForeground); + auto ul = ulTextColor.GetColor(_colorTable, defaultUlIndex, true); + if (attr.IsInvisible()) + { + ul = bg; + } + + // We intentionally aren't _only_ checking for attr.IsInvisible here, because we also want to + // catch the cases where the ul was intentionally set to be the same as the bg. In either case, + // don't adjust the underline color. + if constexpr (Feature_AdjustIndistinguishableText::IsEnabled()) + { + if ( + ul != bg && + (_renderMode.test(Mode::AlwaysDistinguishableColors) || + (_renderMode.test(Mode::IndexedDistinguishableColors) && ulTextColor.IsDefaultOrLegacy() && attr.GetBackground().IsDefaultOrLegacy()))) + { + ul = ColorFix::GetPerceivableColor(ul, bg, 0.5f * 0.5f); + } + } + + return ul; +} + // Routine Description: // - Increments the position in the blink cycle, toggling the blink rendition // state on every second call, potentially triggering a redraw of the given diff --git a/src/renderer/base/renderer.cpp b/src/renderer/base/renderer.cpp index 917c5ba05f9..99cf3f75aa9 100644 --- a/src/renderer/base/renderer.cpp +++ b/src/renderer/base/renderer.cpp @@ -955,13 +955,21 @@ GridLineSet Renderer::s_GetGridlines(const TextAttribute& textAttribute) noexcep { case UnderlineStyle::NoUnderline: break; + case UnderlineStyle::SinglyUnderlined: + lines.set(GridLines::Underline); + break; case UnderlineStyle::DoublyUnderlined: lines.set(GridLines::DoubleUnderline); break; - case UnderlineStyle::SinglyUnderlined: case UnderlineStyle::CurlyUnderlined: + lines.set(GridLines::CurlyUnderline); + break; case UnderlineStyle::DottedUnderlined: + lines.set(GridLines::DottedUnderline); + break; case UnderlineStyle::DashedUnderlined: + lines.set(GridLines::DashedUnderline); + break; default: lines.set(GridLines::Underline); break; @@ -1002,8 +1010,8 @@ void Renderer::_PaintBufferOutputGridLineHelper(_In_ IRenderEngine* const pEngin // Return early if there are no lines to paint. if (lines.any()) { - // Get the current foreground color to render the lines. - const auto rgb = _renderSettings.GetAttributeColors(textAttribute).first; + // Get the current underline color to render the lines. + const auto rgb = _renderSettings.GetAttributeUnderlineColor(textAttribute); // Draw the lines LOG_IF_FAILED(pEngine->PaintBufferGridLines(lines, rgb, cchLine, coordTarget)); } diff --git a/src/renderer/inc/IRenderEngine.hpp b/src/renderer/inc/IRenderEngine.hpp index 46221ae911d..a8e04b6e62d 100644 --- a/src/renderer/inc/IRenderEngine.hpp +++ b/src/renderer/inc/IRenderEngine.hpp @@ -41,6 +41,9 @@ namespace Microsoft::Console::Render Right, Underline, DoubleUnderline, + CurlyUnderline, + DottedUnderline, + DashedUnderline, Strikethrough, HyperlinkUnderline }; diff --git a/src/renderer/inc/RenderSettings.hpp b/src/renderer/inc/RenderSettings.hpp index 4b6e7c3981c..c836bdde848 100644 --- a/src/renderer/inc/RenderSettings.hpp +++ b/src/renderer/inc/RenderSettings.hpp @@ -41,6 +41,7 @@ namespace Microsoft::Console::Render size_t GetColorAliasIndex(const ColorAlias alias) const noexcept; std::pair GetAttributeColors(const TextAttribute& attr) const noexcept; std::pair GetAttributeColorsWithAlpha(const TextAttribute& attr) const noexcept; + COLORREF GetAttributeUnderlineColor(const TextAttribute& attr) const noexcept; void ToggleBlinkRendition(class Renderer& renderer) noexcept; private: From d341402b1dc7cdb0b638e3b8ac4eaef881992a18 Mon Sep 17 00:00:00 2001 From: Tushar Singh Date: Mon, 9 Oct 2023 13:58:43 +0530 Subject: [PATCH 02/24] add new underline styles in UIA --- src/types/UiaTextRangeBase.cpp | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/types/UiaTextRangeBase.cpp b/src/types/UiaTextRangeBase.cpp index af5ebb431ea..419acf1ff0b 100644 --- a/src/types/UiaTextRangeBase.cpp +++ b/src/types/UiaTextRangeBase.cpp @@ -405,16 +405,22 @@ std::optional UiaTextRangeBase::_verifyAttr(TEXTATTRIBUTEID attributeId, V THROW_HR_IF(E_INVALIDARG, val.vt != VT_I4); // The underline style is stored as a TextDecorationLineStyle. - // However, The text buffer doesn't have that many different styles for being underlined. - // Instead, we only have single and double underlined. + // However, The text buffer doesn't have all the different styles for being underlined. + // Instead, we only use a subset of them. switch (val.lVal) { case TextDecorationLineStyle_None: return !attr.IsUnderlined(); + case TextDecorationLineStyle_Single: + return attr.GetUnderlineStyle() == UnderlineStyle::SinglyUnderlined; case TextDecorationLineStyle_Double: return attr.GetUnderlineStyle() == UnderlineStyle::DoublyUnderlined; - case TextDecorationLineStyle_Single: // singly underlined and extended styles are treated the same - return attr.IsUnderlined() && attr.GetUnderlineStyle() != UnderlineStyle::DoublyUnderlined; + case TextDecorationLineStyle_Wavy: + return attr.GetUnderlineStyle() == UnderlineStyle::CurlyUnderlined; + case TextDecorationLineStyle_Dot: + return attr.GetUnderlineStyle() == UnderlineStyle::DottedUnderlined; + case TextDecorationLineStyle_Dash: + return attr.GetUnderlineStyle() == UnderlineStyle::DashedUnderlined; default: return std::nullopt; } @@ -697,18 +703,26 @@ bool UiaTextRangeBase::_initializeAttrQuery(TEXTATTRIBUTEID attributeId, VARIANT const auto style = attr.GetUnderlineStyle(); switch (style) { + case UnderlineStyle::NoUnderline: + pRetVal->lVal = TextDecorationLineStyle_None; + return true; case UnderlineStyle::SinglyUnderlined: pRetVal->lVal = TextDecorationLineStyle_Single; return true; case UnderlineStyle::DoublyUnderlined: pRetVal->lVal = TextDecorationLineStyle_Double; return true; - case UnderlineStyle::NoUnderline: - pRetVal->lVal = TextDecorationLineStyle_None; + case UnderlineStyle::CurlyUnderlined: + pRetVal->lVal = TextDecorationLineStyle_Wavy; + return true; + case UnderlineStyle::DottedUnderlined: + pRetVal->lVal = TextDecorationLineStyle_Dot; + return true; + case UnderlineStyle::DashedUnderlined: + pRetVal->lVal = TextDecorationLineStyle_Dash; return true; + // Out of range styles are treated as singly underlined. default: - // TODO: Handle other underline styles once they're supported in the graphic renderer. - // For now, extended styles are treated (and rendered) as single underline. pRetVal->lVal = TextDecorationLineStyle_Single; return true; } From a042401d674afc1a22e1556680a8e6fcb91f7a3b Mon Sep 17 00:00:00 2001 From: Tushar Singh Date: Thu, 12 Oct 2023 23:33:17 +0530 Subject: [PATCH 03/24] draw curly line from the shader --- src/renderer/atlas/AtlasEngine.api.cpp | 9 --- src/renderer/atlas/BackendD3D.cpp | 107 ++++--------------------- src/renderer/atlas/BackendD3D.h | 14 ++-- src/renderer/atlas/common.h | 2 - src/renderer/atlas/shader_common.hlsl | 7 +- src/renderer/atlas/shader_ps.hlsl | 46 ++++++++--- 6 files changed, 62 insertions(+), 123 deletions(-) diff --git a/src/renderer/atlas/AtlasEngine.api.cpp b/src/renderer/atlas/AtlasEngine.api.cpp index 0c7a5983d51..8e0ec463e53 100644 --- a/src/renderer/atlas/AtlasEngine.api.cpp +++ b/src/renderer/atlas/AtlasEngine.api.cpp @@ -722,12 +722,6 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo // Our cells can't overlap each other so we additionally clamp the bottom line to be inside the cell boundaries. doubleUnderlinePosBottom = std::min(doubleUnderlinePosBottom, adjustedHeight - thinLineWidth); - // For curly-line, we'll make room for the top and bottom curve ("wave"). - // The baseline for curly-line is kept at the baseline of singly underline. - const auto curlyLinePeakHeight = std::roundf(fontMetrics->curlyUnderlineWaviness * fontSizeInPx); - const auto curlyUnderlinePos = underlinePos - (curlyLinePeakHeight + underlineThickness / 2.0f); - const auto curlyUnderlineWidth = underlineWidth + 2.0f * (curlyLinePeakHeight + underlineThickness / 2.0f); - const auto cellWidth = gsl::narrow(lrintf(adjustedWidth)); const auto cellHeight = gsl::narrow(lrintf(adjustedHeight)); @@ -761,8 +755,6 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo const auto underlinePosU16 = gsl::narrow_cast(lrintf(underlinePos)); const auto underlineWidthU16 = gsl::narrow_cast(lrintf(underlineWidth)); - const auto curlyUnderlinePosU16 = gsl::narrow_cast(lrintf(curlyUnderlinePos)); - const auto curlyUnderlineWidthU16 = gsl::narrow_cast(lrintf(curlyUnderlineWidth)); const auto strikethroughPosU16 = gsl::narrow_cast(lrintf(strikethroughPos)); const auto strikethroughWidthU16 = gsl::narrow_cast(lrintf(strikethroughWidth)); const auto doubleUnderlinePosTopU16 = gsl::narrow_cast(lrintf(doubleUnderlinePosTop)); @@ -789,7 +781,6 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo fontMetrics->underline = { underlinePosU16, underlineWidthU16 }; fontMetrics->strikethrough = { strikethroughPosU16, strikethroughWidthU16 }; - fontMetrics->curlyUnderline = { curlyUnderlinePosU16, curlyUnderlineWidthU16 }; fontMetrics->doubleUnderline[0] = { doubleUnderlinePosTopU16, thinLineWidthU16 }; fontMetrics->doubleUnderline[1] = { doubleUnderlinePosBottomU16, thinLineWidthU16 }; fontMetrics->overline = { 0, underlineWidthU16 }; diff --git a/src/renderer/atlas/BackendD3D.cpp b/src/renderer/atlas/BackendD3D.cpp index d25198c2e16..357e3b60a1e 100644 --- a/src/renderer/atlas/BackendD3D.cpp +++ b/src/renderer/atlas/BackendD3D.cpp @@ -305,6 +305,16 @@ void BackendD3D::_updateFontDependents(const RenderingPayload& p) { const auto& font = *p.s->font; + // For curly line, we'll make room for the trough and crest (wave's peaks). + // The baseline for curly-line is kept at the baseline of singly underline. + const auto strokeWidthHalf = font.underline.height / 2.0f; + const auto curlyUnderlinePeakHeight = _curlyLineHeight * font.fontSize; + const auto curlyUnderlinePos = font.underline.position - curlyUnderlinePeakHeight; + const auto curlyUnderlineWidth = 2.0f * (curlyUnderlinePeakHeight + strokeWidthHalf); + const auto curlyUnderlinePosU16 = gsl::narrow_cast(lrintf(curlyUnderlinePos)); + const auto curlyUnderlineWidthU16 = gsl::narrow_cast(lrintf(curlyUnderlineWidth)); + _curlyUnderline = { curlyUnderlinePosU16, curlyUnderlineWidthU16 }; + DWrite_GetRenderParams(p.dwriteFactory.get(), &_gamma, &_cleartypeEnhancedContrast, &_grayscaleEnhancedContrast, _textRenderingParams.put()); // Clearing the atlas requires BeginDraw(), which is expensive. Defer this until we need Direct2D anyways. _fontChangedResetGlyphAtlas = true; @@ -539,6 +549,8 @@ void BackendD3D::_recreateConstBuffer(const RenderingPayload& p) const DWrite_GetGammaRatios(_gamma, data.gammaRatios); data.enhancedContrast = p.s->font->antialiasingMode == AntialiasingMode::ClearType ? _cleartypeEnhancedContrast : _grayscaleEnhancedContrast; data.underlineWidth = p.s->font->underline.height; + data.curlyLineHeight = p.s->font->fontSize * _curlyLineHeight; + data.underlineCellOffset = p.s->font->underline.position; p.deviceContext->UpdateSubresource(_psConstantBuffer.get(), 0, nullptr, &data, 0, 0); } } @@ -736,9 +748,6 @@ void BackendD3D::_resetGlyphAtlas(const RenderingPayload& p) _d2dBeginDrawing(); _d2dRenderTarget->Clear(); - // (re)draw custom geometries - _drawCurlylineToAtlas(p); - _fontChangedResetGlyphAtlas = false; } @@ -1646,6 +1655,7 @@ void BackendD3D::_drawGridlines(const RenderingPayload& p, u16 y) const auto cellSize = p.s->font->cellSize; const auto dottedLineType = horizontalShift ? ShadingType::DottedLineWide : ShadingType::DottedLine; const auto dashedLineType = horizontalShift ? ShadingType::DashedLineWide : ShadingType::DashedLine; + const auto curlyLineType = horizontalShift ? ShadingType::CurlyLineWide : ShadingType::CurlyLine; const auto rowTop = static_cast(cellSize.y * y); const auto rowBottom = static_cast(rowTop + cellSize.y); @@ -1697,10 +1707,6 @@ void BackendD3D::_drawGridlines(const RenderingPayload& p, u16 y) .size = { width, static_cast(rb - rt) }, .color = r.color, }; - if (shadingType == ShadingType::CurlyLine) - { - _getLastQuad().texcoord = _curlyLineTexCoord; - } } }; @@ -1740,7 +1746,7 @@ void BackendD3D::_drawGridlines(const RenderingPayload& p, u16 y) } if (r.lines.test(GridLines::CurlyUnderline)) { - appendHorizontalLine(r, p.s->font->curlyUnderline, ShadingType::CurlyLine); + appendHorizontalLine(r, _curlyUnderline, curlyLineType); } if (r.lines.test(GridLines::DoubleUnderline)) { @@ -1756,91 +1762,6 @@ void BackendD3D::_drawGridlines(const RenderingPayload& p, u16 y) } } -void BackendD3D::_drawCurlylineToAtlas(const RenderingPayload& p) -{ - const auto cellWidth = p.s->font->cellSize.x; - - // draw cell-width worth of curlyline and reuse it as a tiling texture for - // longer lengths. - stbrp_rect rect{ - .w = cellWidth, - .h = p.s->font->curlyUnderline.height, - }; - if (!stbrp_pack_rects(&_rectPacker, &rect, 1)) - { - _drawGlyphPrepareRetry(p); - return; - } - - const auto baselineY = p.s->font->curlyUnderline.height / 2.0f; - const auto ctrlPointOffsetY = std::roundf(p.s->font->curlyUnderlineWaviness * p.s->font->fontSize); - - wil::com_ptr curlyLine; - THROW_IF_FAILED(p.d2dFactory->CreatePathGeometry(curlyLine.addressof())); - - // The ends of the curve drawn by a PathGeometry is always titled at an - // angle of the tangent at the end points. Since we are going to tile them - // one after another, we need ends to be vertically flat. So we'll draw - // three curly lines, and cut the middle one off. This will give us a - // curly line with flat ends. - { - wil::com_ptr sink; - curlyLine->Open(sink.addressof()); - - // first wave (starts at -cellSize.x) - sink->BeginFigure( - D2D1::Point2F(-cellWidth, baselineY), - D2D1_FIGURE_BEGIN_HOLLOW); - - sink->AddBezier( - D2D1::BezierSegment( - D2D1::Point2F(-cellWidth / 2.0f, baselineY - ctrlPointOffsetY), - D2D1::Point2F(-cellWidth / 2.0f, baselineY + ctrlPointOffsetY), - D2D1::Point2F(0.0f, baselineY))); - - // second wave (starts at 0.0f) - sink->AddBezier( - D2D1::BezierSegment( - D2D1::Point2F(cellWidth / 2.0f, baselineY - ctrlPointOffsetY), - D2D1::Point2F(cellWidth / 2.0f, baselineY + ctrlPointOffsetY), - D2D1::Point2F(cellWidth, baselineY))); - - // third wave (starts at 'cellSize.x' px) - sink->AddBezier( - D2D1::BezierSegment( - D2D1::Point2F(cellWidth + cellWidth / 2.0f, baselineY - ctrlPointOffsetY), - D2D1::Point2F(cellWidth + cellWidth / 2.0f, baselineY + ctrlPointOffsetY), - D2D1::Point2F(2.0f * cellWidth, baselineY))); - - sink->EndFigure(D2D1_FIGURE_END_OPEN); - - sink->Close(); - } - - _d2dBeginDrawing(); - - // Clip everything but the middle wave - { - const auto clipRect = D2D1::RectF( - static_cast(rect.x), - static_cast(rect.y), - static_cast(rect.x + rect.w), - static_cast(rect.y + rect.h)); - _d2dRenderTarget->PushAxisAlignedClip(clipRect, D2D1_ANTIALIAS_MODE_ALIASED); - } - - auto const strokeWidth = p.s->font->underline.height; - _d2dRenderTarget->DrawGeometry(curlyLine.get(), _brush.get(), strokeWidth); - - _d2dRenderTarget->PopAxisAlignedClip(); - - _d2dEndDrawing(); - - // store the texture coordinates - _curlyLineTexCoord.x = rect.x; - _curlyLineTexCoord.y = rect.y; -} - void BackendD3D::_drawCursorBackground(const RenderingPayload& p) { _cursorRects.clear(); diff --git a/src/renderer/atlas/BackendD3D.h b/src/renderer/atlas/BackendD3D.h index 0ce08cce479..46b9f6e3967 100644 --- a/src/renderer/atlas/BackendD3D.h +++ b/src/renderer/atlas/BackendD3D.h @@ -42,6 +42,8 @@ namespace Microsoft::Console::Render::Atlas alignas(sizeof(f32x4)) f32 gammaRatios[4]{}; alignas(sizeof(f32)) f32 enhancedContrast = 0; alignas(sizeof(f32)) f32 underlineWidth = 0; + alignas(sizeof(f32)) f32 curlyLineHeight = 0; + alignas(sizeof(f32)) f32 underlineCellOffset = 0; #pragma warning(suppress : 4324) // 'PSConstBuffer': structure was padded due to alignment specifier }; @@ -70,11 +72,12 @@ namespace Microsoft::Console::Render::Atlas DashedLine = 6, DashedLineWide = 7, CurlyLine = 8, + CurlyLineWide = 9, // All items starting here will be drawing as a solid RGBA color - SolidLine = 9, + SolidLine = 10, - Cursor = 10, - Selection = 11, + Cursor = 11, + Selection = 12, TextDrawingFirst = TextGrayscale, TextDrawingLast = SolidLine, @@ -227,7 +230,6 @@ namespace Microsoft::Console::Render::Atlas void _drawGlyphPrepareRetry(const RenderingPayload& p); void _splitDoubleHeightGlyph(const RenderingPayload& p, const AtlasFontFaceEntryInner& fontFaceEntry, AtlasGlyphEntry& glyphEntry); void _drawGridlines(const RenderingPayload& p, u16 y); - void _drawCurlylineToAtlas(const RenderingPayload& p); void _drawCursorBackground(const RenderingPayload& p); ATLAS_ATTR_COLD void _drawCursorForeground(); ATLAS_ATTR_COLD size_t _drawCursorForegroundSlowPath(const CursorRect& c, size_t offset); @@ -295,7 +297,9 @@ namespace Microsoft::Console::Render::Atlas // The bounding rect of _cursorRects in pixels. til::rect _cursorPosition; - u16x2 _curlyLineTexCoord; + const f32 _curlyLineHeight = 0.1f; // in `em` units. + FontDecorationPosition _curlyUnderline; + bool _requiresContinuousRedraw = false; #if ATLAS_DEBUG_SHOW_DIRTY diff --git a/src/renderer/atlas/common.h b/src/renderer/atlas/common.h index 2752bbbf757..bb46ff61547 100644 --- a/src/renderer/atlas/common.h +++ b/src/renderer/atlas/common.h @@ -346,7 +346,6 @@ namespace Microsoft::Console::Render::Atlas u16 baseline = 0; u16 descender = 0; u16 thinLineWidth = 0; - f32 curlyUnderlineWaviness = 0.28f; // Control points' height in `em` units. FontDecorationPosition gridTop; FontDecorationPosition gridBottom; @@ -354,7 +353,6 @@ namespace Microsoft::Console::Render::Atlas FontDecorationPosition gridRight; FontDecorationPosition underline; - FontDecorationPosition curlyUnderline; FontDecorationPosition strikethrough; FontDecorationPosition doubleUnderline[2]; FontDecorationPosition overline; diff --git a/src/renderer/atlas/shader_common.hlsl b/src/renderer/atlas/shader_common.hlsl index f01e26be881..5e384b5e8c8 100644 --- a/src/renderer/atlas/shader_common.hlsl +++ b/src/renderer/atlas/shader_common.hlsl @@ -8,9 +8,10 @@ #define SHADING_TYPE_TEXT_PASSTHROUGH 3 #define SHADING_TYPE_DOTTED_LINE 4 #define SHADING_TYPE_DOTTED_LINE_WIDE 5 -#define SHADING_TYPE_DASHED_LINE 6 -#define SHADING_TYPE_DASHED_LINE_WIDE 7 -#define SHADING_TYPE_CURLY_LINE 8 +#define SHADING_TYPE_DASHED_LINE 6 +#define SHADING_TYPE_DASHED_LINE_WIDE 7 +#define SHADING_TYPE_CURLY_LINE 8 +#define SHADING_TYPE_CURLY_LINE_WIDE 9 // clang-format on struct VSData diff --git a/src/renderer/atlas/shader_ps.hlsl b/src/renderer/atlas/shader_ps.hlsl index 4254feb26f0..cb8cff986c8 100644 --- a/src/renderer/atlas/shader_ps.hlsl +++ b/src/renderer/atlas/shader_ps.hlsl @@ -12,6 +12,8 @@ cbuffer ConstBuffer : register(b0) float4 gammaRatios; float enhancedContrast; float underlineWidth; + float curlyLineHeight; + float underlineCellOffset; } Texture2D background : register(t0); @@ -101,19 +103,41 @@ Output main(PSData data) : SV_Target } case SHADING_TYPE_CURLY_LINE: { - const float4 foreground = premultiplyColor(data.color); - const float blendEnhancedContrast = DWrite_ApplyLightOnDarkContrastAdjustment(enhancedContrast, data.color.rgb); - const float intensity = DWrite_CalcColorIntensity(data.color.rgb); + const float strokeWidthHalf = underlineWidth / 2.0f; + const int cellIdxY = data.position.y / backgroundCellSize.y; + const float cellPosY = cellIdxY * backgroundCellSize.y; + const float centerY = cellPosY + underlineCellOffset + strokeWidthHalf; + const float Pi = radians(180); + const float freq = 2.0f * Pi / backgroundCellSize.x; + const float amp = curlyLineHeight - 1.0f; // -1.0f avoids clipping at the peak - const float2 texcoord = { - data.texcoord.x % backgroundCellSize.x, - data.texcoord.y % backgroundCellSize.y - }; - const float4 glyph = glyphAtlas[texcoord]; - const float contrasted = DWrite_EnhanceContrast(glyph.a, blendEnhancedContrast); - const float alphaCorrected = DWrite_ApplyAlphaCorrection(contrasted, intensity, gammaRatios); + const float s = sin(data.position.x * freq); + const float d = abs(centerY + s * amp - data.position.y); + const float a = 1 - saturate(d - strokeWidthHalf); + color = a * premultiplyColor(data.color); + weights = color.aaaa; + break; + } + case SHADING_TYPE_CURLY_LINE_WIDE: + { + float strokeWidthHalf = underlineWidth / 2.0f; + const int cellIdxY = data.position.y / backgroundCellSize.y; + const float cellPosY = cellIdxY * backgroundCellSize.y; + float centerY = cellPosY + underlineCellOffset + strokeWidthHalf; + const float Pi = radians(180); + float freq = 2.0f * Pi / backgroundCellSize.x; + float amp = curlyLineHeight - 1.0f; // -1.0f avoids clipping at the peak - color = alphaCorrected * foreground; + // In 'Wide' case, we need to draw the same wave on an area twice as big. + strokeWidthHalf *= 2; + centerY -= curlyLineHeight + strokeWidthHalf; + freq /= 2; + amp *= 2; + + const float s = sin(data.position.x * freq); + const float d = abs(centerY + s * amp - data.position.y); + const float a = 1 - saturate(d - strokeWidthHalf); + color = a * premultiplyColor(data.color); weights = color.aaaa; break; } From e16055c78254c3731d4cf467d15177c939df010a Mon Sep 17 00:00:00 2001 From: Tushar Singh Date: Fri, 13 Oct 2023 00:22:27 +0530 Subject: [PATCH 04/24] revert D2D antialias mode --- src/renderer/atlas/BackendD3D.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/renderer/atlas/BackendD3D.cpp b/src/renderer/atlas/BackendD3D.cpp index 357e3b60a1e..a72bc43ae8f 100644 --- a/src/renderer/atlas/BackendD3D.cpp +++ b/src/renderer/atlas/BackendD3D.cpp @@ -784,7 +784,9 @@ void BackendD3D::_resizeGlyphAtlas(const RenderingPayload& p, const u16 u, const _d2dRenderTarget.try_query_to(_d2dRenderTarget4.addressof()); _d2dRenderTarget->SetUnitMode(D2D1_UNIT_MODE_PIXELS); - _d2dRenderTarget->SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE); + // We don't really use D2D for anything except DWrite, but it + // can't hurt to ensure that everything it does is pixel aligned. + _d2dRenderTarget->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED); // Ensure that D2D uses the exact same gamma as our shader uses. _d2dRenderTarget->SetTextRenderingParams(_textRenderingParams.get()); From 1df589435b2a1da3d43df8b3e5070f7da3e43fbe Mon Sep 17 00:00:00 2001 From: Tushar Singh Date: Sat, 14 Oct 2023 22:07:35 +0530 Subject: [PATCH 05/24] fix gridlines being drawn with underline color --- src/interactivity/onecore/BgfxEngine.cpp | 7 ++++--- src/interactivity/onecore/BgfxEngine.hpp | 2 +- src/renderer/atlas/AtlasEngine.cpp | 7 ++++--- src/renderer/atlas/AtlasEngine.h | 2 +- src/renderer/atlas/BackendD2D.cpp | 18 +++++++++--------- src/renderer/atlas/BackendD3D.cpp | 22 +++++++++++----------- src/renderer/atlas/common.h | 3 ++- src/renderer/base/renderer.cpp | 7 ++++--- src/renderer/dx/DxRenderer.cpp | 12 ++++++++---- src/renderer/dx/DxRenderer.hpp | 2 +- src/renderer/gdi/gdirenderer.hpp | 3 ++- src/renderer/gdi/paint.cpp | 14 ++++++++++---- src/renderer/inc/IRenderEngine.hpp | 2 +- src/renderer/uia/UiaRenderer.cpp | 10 ++++++---- src/renderer/uia/UiaRenderer.hpp | 2 +- src/renderer/vt/paint.cpp | 6 ++++-- src/renderer/vt/vtrenderer.hpp | 2 +- src/renderer/wddmcon/WddmConRenderer.cpp | 7 ++++--- src/renderer/wddmcon/WddmConRenderer.hpp | 2 +- 19 files changed, 75 insertions(+), 55 deletions(-) diff --git a/src/interactivity/onecore/BgfxEngine.cpp b/src/interactivity/onecore/BgfxEngine.cpp index ce6b903ffd1..d508603d5f9 100644 --- a/src/interactivity/onecore/BgfxEngine.cpp +++ b/src/interactivity/onecore/BgfxEngine.cpp @@ -147,9 +147,10 @@ CATCH_RETURN() CATCH_RETURN() } -[[nodiscard]] HRESULT BgfxEngine::PaintBufferGridLines(GridLineSet const /*lines*/, - COLORREF const /*color*/, - size_t const /*cchLine*/, +[[nodiscard]] HRESULT BgfxEngine::PaintBufferGridLines(const GridLineSet /*lines*/, + const COLORREF /*gridlineColor*/, + const COLORREF /*underlineColor*/, + const size_t /*cchLine*/, const til::point /*coordTarget*/) noexcept { return S_OK; diff --git a/src/interactivity/onecore/BgfxEngine.hpp b/src/interactivity/onecore/BgfxEngine.hpp index 31fa49c8522..e9759ce0306 100644 --- a/src/interactivity/onecore/BgfxEngine.hpp +++ b/src/interactivity/onecore/BgfxEngine.hpp @@ -51,7 +51,7 @@ namespace Microsoft::Console::Render const til::point coord, const bool trimLeft, const bool lineWrapped) noexcept override; - [[nodiscard]] HRESULT PaintBufferGridLines(GridLineSet const lines, COLORREF const color, size_t const cchLine, til::point const coordTarget) noexcept override; + [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept override; [[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override; [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; diff --git a/src/renderer/atlas/AtlasEngine.cpp b/src/renderer/atlas/AtlasEngine.cpp index e46bb250835..7438b93543e 100644 --- a/src/renderer/atlas/AtlasEngine.cpp +++ b/src/renderer/atlas/AtlasEngine.cpp @@ -375,7 +375,7 @@ try } CATCH_RETURN() -[[nodiscard]] HRESULT AtlasEngine::PaintBufferGridLines(const GridLineSet lines, const COLORREF color, const size_t cchLine, const til::point coordTarget) noexcept +[[nodiscard]] HRESULT AtlasEngine::PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept try { const auto shift = gsl::narrow_cast(_api.lineRendition != LineRendition::SingleWidth); @@ -383,8 +383,9 @@ try const auto y = gsl::narrow_cast(clamp(coordTarget.y, 0, _p.s->viewportCellCount.y)); const auto from = gsl::narrow_cast(clamp(x << shift, 0, _p.s->viewportCellCount.x - 1)); const auto to = gsl::narrow_cast(clamp((x + cchLine) << shift, from, _p.s->viewportCellCount.x)); - const auto fg = gsl::narrow_cast(color) | 0xff000000; - _p.rows[y]->gridLineRanges.emplace_back(lines, fg, from, to); + const auto glColor = gsl::narrow_cast(gridlineColor) | 0xff000000; + const auto ulColor = gsl::narrow_cast(underlineColor) | 0xff000000; + _p.rows[y]->gridLineRanges.emplace_back(lines, glColor, ulColor, from, to); return S_OK; } CATCH_RETURN() diff --git a/src/renderer/atlas/AtlasEngine.h b/src/renderer/atlas/AtlasEngine.h index b135a8989f8..4ec09a9e931 100644 --- a/src/renderer/atlas/AtlasEngine.h +++ b/src/renderer/atlas/AtlasEngine.h @@ -43,7 +43,7 @@ namespace Microsoft::Console::Render::Atlas [[nodiscard]] HRESULT PrepareLineTransform(LineRendition lineRendition, til::CoordType targetRow, til::CoordType viewportLeft) noexcept override; [[nodiscard]] HRESULT PaintBackground() noexcept override; [[nodiscard]] HRESULT PaintBufferLine(std::span clusters, til::point coord, bool fTrimLeft, bool lineWrapped) noexcept override; - [[nodiscard]] HRESULT PaintBufferGridLines(GridLineSet lines, COLORREF color, size_t cchLine, til::point coordTarget) noexcept override; + [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept override; [[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override; [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; [[nodiscard]] HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes, const RenderSettings& renderSettings, gsl::not_null pData, bool usingSoftFont, bool isSettingDefaultBrushes) noexcept override; diff --git a/src/renderer/atlas/BackendD2D.cpp b/src/renderer/atlas/BackendD2D.cpp index 301738954aa..ba1fc9c0731 100644 --- a/src/renderer/atlas/BackendD2D.cpp +++ b/src/renderer/atlas/BackendD2D.cpp @@ -409,7 +409,7 @@ void BackendD2D::_drawGridlineRow(const RenderingPayload& p, const ShapedRow* ro D2D1_POINT_2F point0{ 0, static_cast(textCellCenter) }; D2D1_POINT_2F point1{ 0, static_cast(textCellCenter + cellSize.y) }; - const auto brush = _brushWithColor(r.color); + const auto brush = _brushWithColor(r.gridlineColor); const f32 w = pos.height; const f32 hw = w * 0.5f; @@ -421,11 +421,11 @@ void BackendD2D::_drawGridlineRow(const RenderingPayload& p, const ShapedRow* ro _renderTarget->DrawLine(point0, point1, brush, w, nullptr); } }; - const auto appendHorizontalLine = [&](const GridLineRange& r, FontDecorationPosition pos, ID2D1StrokeStyle* strokeStyle) { + const auto appendHorizontalLine = [&](const GridLineRange& r, FontDecorationPosition pos, ID2D1StrokeStyle* strokeStyle, const u32 color) { const auto from = r.from >> widthShift; const auto to = r.to >> widthShift; - const auto brush = _brushWithColor(r.color); + const auto brush = _brushWithColor(color); const f32 w = pos.height; const f32 centerY = textCellCenter + pos.position + w * 0.5f; const D2D1_POINT_2F point0{ static_cast(from * cellSize.x), centerY }; @@ -448,31 +448,31 @@ void BackendD2D::_drawGridlineRow(const RenderingPayload& p, const ShapedRow* ro } if (r.lines.test(GridLines::Top)) { - appendHorizontalLine(r, p.s->font->gridTop, nullptr); + appendHorizontalLine(r, p.s->font->gridTop, nullptr, r.gridlineColor); } if (r.lines.test(GridLines::Bottom)) { - appendHorizontalLine(r, p.s->font->gridBottom, nullptr); + appendHorizontalLine(r, p.s->font->gridBottom, nullptr, r.gridlineColor); } if (r.lines.test(GridLines::Underline)) { - appendHorizontalLine(r, p.s->font->underline, nullptr); + appendHorizontalLine(r, p.s->font->underline, nullptr, r.underlineColor); } if (r.lines.test(GridLines::HyperlinkUnderline)) { - appendHorizontalLine(r, p.s->font->underline, _dottedStrokeStyle.get()); + appendHorizontalLine(r, p.s->font->underline, _dottedStrokeStyle.get(), r.underlineColor); } if (r.lines.test(GridLines::DoubleUnderline)) { for (const auto pos : p.s->font->doubleUnderline) { - appendHorizontalLine(r, pos, nullptr); + appendHorizontalLine(r, pos, nullptr, r.underlineColor); } } if (r.lines.test(GridLines::Strikethrough)) { - appendHorizontalLine(r, p.s->font->strikethrough, nullptr); + appendHorizontalLine(r, p.s->font->strikethrough, nullptr, r.underlineColor); } } } diff --git a/src/renderer/atlas/BackendD3D.cpp b/src/renderer/atlas/BackendD3D.cpp index a72bc43ae8f..495b3bd3d61 100644 --- a/src/renderer/atlas/BackendD3D.cpp +++ b/src/renderer/atlas/BackendD3D.cpp @@ -1685,11 +1685,11 @@ void BackendD3D::_drawGridlines(const RenderingPayload& p, u16 y) .shadingType = ShadingType::SolidLine, .position = { static_cast(posX), rowTop }, .size = { width, p.s->font->cellSize.y }, - .color = r.color, + .color = r.gridlineColor, }; } }; - const auto appendHorizontalLine = [&](const GridLineRange& r, FontDecorationPosition pos, ShadingType shadingType) { + const auto appendHorizontalLine = [&](const GridLineRange& r, FontDecorationPosition pos, ShadingType shadingType, const u32 color) { const auto offset = pos.position << verticalShift; const auto height = static_cast(pos.height << verticalShift); @@ -1707,7 +1707,7 @@ void BackendD3D::_drawGridlines(const RenderingPayload& p, u16 y) .shadingType = shadingType, .position = { left, static_cast(rt) }, .size = { width, static_cast(rb - rt) }, - .color = r.color, + .color = color, }; } }; @@ -1727,39 +1727,39 @@ void BackendD3D::_drawGridlines(const RenderingPayload& p, u16 y) } if (r.lines.test(GridLines::Top)) { - appendHorizontalLine(r, p.s->font->gridTop, ShadingType::SolidLine); + appendHorizontalLine(r, p.s->font->gridTop, ShadingType::SolidLine, r.gridlineColor); } if (r.lines.test(GridLines::Bottom)) { - appendHorizontalLine(r, p.s->font->gridBottom, ShadingType::SolidLine); + appendHorizontalLine(r, p.s->font->gridBottom, ShadingType::SolidLine, r.gridlineColor); } if (r.lines.test(GridLines::Underline)) { - appendHorizontalLine(r, p.s->font->underline, ShadingType::SolidLine); + appendHorizontalLine(r, p.s->font->underline, ShadingType::SolidLine, r.underlineColor); } if (r.lines.any(GridLines::DottedUnderline, GridLines::HyperlinkUnderline)) { - appendHorizontalLine(r, p.s->font->underline, dottedLineType); + appendHorizontalLine(r, p.s->font->underline, dottedLineType, r.underlineColor); } if (r.lines.test(GridLines::DashedUnderline)) { - appendHorizontalLine(r, p.s->font->underline, dashedLineType); + appendHorizontalLine(r, p.s->font->underline, dashedLineType, r.underlineColor); } if (r.lines.test(GridLines::CurlyUnderline)) { - appendHorizontalLine(r, _curlyUnderline, curlyLineType); + appendHorizontalLine(r, _curlyUnderline, curlyLineType, r.underlineColor); } if (r.lines.test(GridLines::DoubleUnderline)) { for (const auto pos : p.s->font->doubleUnderline) { - appendHorizontalLine(r, pos, ShadingType::SolidLine); + appendHorizontalLine(r, pos, ShadingType::SolidLine, r.underlineColor); } } if (r.lines.test(GridLines::Strikethrough)) { - appendHorizontalLine(r, p.s->font->strikethrough, ShadingType::SolidLine); + appendHorizontalLine(r, p.s->font->strikethrough, ShadingType::SolidLine, r.underlineColor); } } } diff --git a/src/renderer/atlas/common.h b/src/renderer/atlas/common.h index bb46ff61547..0ff0f01524e 100644 --- a/src/renderer/atlas/common.h +++ b/src/renderer/atlas/common.h @@ -426,7 +426,8 @@ namespace Microsoft::Console::Render::Atlas struct GridLineRange { GridLineSet lines; - u32 color = 0; + u32 gridlineColor = 0; + u32 underlineColor = 0; u16 from = 0; u16 to = 0; }; diff --git a/src/renderer/base/renderer.cpp b/src/renderer/base/renderer.cpp index 99cf3f75aa9..2b328ee07f9 100644 --- a/src/renderer/base/renderer.cpp +++ b/src/renderer/base/renderer.cpp @@ -1010,10 +1010,11 @@ void Renderer::_PaintBufferOutputGridLineHelper(_In_ IRenderEngine* const pEngin // Return early if there are no lines to paint. if (lines.any()) { - // Get the current underline color to render the lines. - const auto rgb = _renderSettings.GetAttributeUnderlineColor(textAttribute); + // Get the current foreground and underline colors to render the lines. + const auto fg = _renderSettings.GetAttributeColors(textAttribute).first; + const auto underlineColor = _renderSettings.GetAttributeUnderlineColor(textAttribute); // Draw the lines - LOG_IF_FAILED(pEngine->PaintBufferGridLines(lines, rgb, cchLine, coordTarget)); + LOG_IF_FAILED(pEngine->PaintBufferGridLines(lines, fg, underlineColor, cchLine, coordTarget)); } } diff --git a/src/renderer/dx/DxRenderer.cpp b/src/renderer/dx/DxRenderer.cpp index f74bf528dc1..26c0bcf45b4 100644 --- a/src/renderer/dx/DxRenderer.cpp +++ b/src/renderer/dx/DxRenderer.cpp @@ -1696,14 +1696,16 @@ CATCH_RETURN() // - Paints lines around cells (draws in pieces of the grid) // Arguments: // - lines - Which grid lines (top, left, bottom, right) to draw -// - color - The color to use for drawing the lines +// - gridlineColor - The color to use for drawing the gridlines +// - underlineColor - The color to use for drawing the underlines // - cchLine - Length of the line to draw in character cells // - coordTarget - The X,Y character position in the grid where we should start drawing // - We will draw rightward (+X) from here // Return Value: // - S_OK or relevant DirectX error [[nodiscard]] HRESULT DxEngine::PaintBufferGridLines(const GridLineSet lines, - COLORREF const color, + const COLORREF gridlineColor, + const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept try @@ -1711,8 +1713,6 @@ try const auto existingColor = _d2dBrushForeground->GetColor(); const auto restoreBrushOnExit = wil::scope_exit([&]() noexcept { _d2dBrushForeground->SetColor(existingColor); }); - _d2dBrushForeground->SetColor(_ColorFFromColorRef(color | 0xff000000)); - const auto font = _fontRenderData->GlyphCell().to_d2d_size(); const D2D_POINT_2F target = { coordTarget.x * font.width, coordTarget.y * font.height }; const auto fullRunWidth = font.width * gsl::narrow_cast(cchLine); @@ -1725,6 +1725,8 @@ try _d2dDeviceContext->DrawLine({ x0, y0 }, { x1, y1 }, _d2dBrushForeground.Get(), strokeWidth, _dashStrokeStyle.Get()); }; + _d2dBrushForeground->SetColor(_ColorFFromColorRef(gridlineColor | 0xff000000)); + // NOTE: Line coordinates are centered within the line, so they need to be // offset by half the stroke width. For the start coordinate we add half // the stroke width, and for the end coordinate we subtract half the width. @@ -1773,6 +1775,8 @@ try } } + _d2dBrushForeground->SetColor(_ColorFFromColorRef(underlineColor | 0xff000000)); + // In the case of the underline and strikethrough offsets, the stroke width // is already accounted for, so they don't require further adjustments. diff --git a/src/renderer/dx/DxRenderer.hpp b/src/renderer/dx/DxRenderer.hpp index bfb11205a05..877b3f1adfa 100644 --- a/src/renderer/dx/DxRenderer.hpp +++ b/src/renderer/dx/DxRenderer.hpp @@ -106,7 +106,7 @@ namespace Microsoft::Console::Render const bool fTrimLeft, const bool lineWrapped) noexcept override; - [[nodiscard]] HRESULT PaintBufferGridLines(GridLineSet const lines, COLORREF const color, size_t const cchLine, til::point const coordTarget) noexcept override; + [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept override; [[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override; [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; diff --git a/src/renderer/gdi/gdirenderer.hpp b/src/renderer/gdi/gdirenderer.hpp index a1ab2290a97..76df464eab8 100644 --- a/src/renderer/gdi/gdirenderer.hpp +++ b/src/renderer/gdi/gdirenderer.hpp @@ -52,7 +52,8 @@ namespace Microsoft::Console::Render const bool trimLeft, const bool lineWrapped) noexcept override; [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, - const COLORREF color, + const COLORREF gridlineColor, + const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept override; [[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override; diff --git a/src/renderer/gdi/paint.cpp b/src/renderer/gdi/paint.cpp index 17dc9bddd82..67f0c76632f 100644 --- a/src/renderer/gdi/paint.cpp +++ b/src/renderer/gdi/paint.cpp @@ -509,19 +509,20 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) // - Draws up to one line worth of grid lines on top of characters. // Arguments: // - lines - Enum defining which edges of the rectangle to draw -// - color - The color to use for drawing the edges. +// - gridlineColor - The color to use for drawing the gridlines. +// - underlineColor - The color to use for drawing the underlines. // - cchLine - How many characters we should draw the grid lines along (left to right in a row) // - coordTarget - The starting X/Y position of the first character to draw on. // Return Value: // - S_OK or suitable GDI HRESULT error or E_FAIL for GDI errors in functions that don't reliably return a specific error code. -[[nodiscard]] HRESULT GdiEngine::PaintBufferGridLines(const GridLineSet lines, const COLORREF color, const size_t cchLine, const til::point coordTarget) noexcept +[[nodiscard]] HRESULT GdiEngine::PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept { LOG_IF_FAILED(_FlushBufferLines()); // Convert the target from characters to pixels. const auto ptTarget = coordTarget * _GetFontSize(); - // Set the brush color as requested and save the previous brush to restore at the end. - wil::unique_hbrush hbr(CreateSolidBrush(color)); + // Set the brush color to the gridline color and save the previous brush to restore at the end. + wil::unique_hbrush hbr(CreateSolidBrush(gridlineColor)); RETURN_HR_IF_NULL(E_FAIL, hbr.get()); wil::unique_hbrush hbrPrev(SelectBrush(_hdcMemoryContext, hbr.get())); @@ -574,6 +575,11 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) RETURN_HR_IF(E_FAIL, !DrawLine(ptTarget.x, y, widthOfAllCells, _lineMetrics.gridlineWidth)); } + hbr.reset(CreateSolidBrush(underlineColor)); + wil::unique_hbrush hbrGridline(SelectBrush(_hdcMemoryContext, hbr.get())); + RETURN_HR_IF_NULL(E_FAIL, hbrGridline.get()); + hbr.release(); // If SelectBrush was successful, GDI owns the brush. Release for now. + if (lines.any(GridLines::Underline, GridLines::DoubleUnderline)) { const auto y = ptTarget.y + _lineMetrics.underlineOffset; diff --git a/src/renderer/inc/IRenderEngine.hpp b/src/renderer/inc/IRenderEngine.hpp index a8e04b6e62d..afcaa5aff59 100644 --- a/src/renderer/inc/IRenderEngine.hpp +++ b/src/renderer/inc/IRenderEngine.hpp @@ -76,7 +76,7 @@ namespace Microsoft::Console::Render [[nodiscard]] virtual HRESULT PrepareLineTransform(LineRendition lineRendition, til::CoordType targetRow, til::CoordType viewportLeft) noexcept = 0; [[nodiscard]] virtual HRESULT PaintBackground() noexcept = 0; [[nodiscard]] virtual HRESULT PaintBufferLine(std::span clusters, til::point coord, bool fTrimLeft, bool lineWrapped) noexcept = 0; - [[nodiscard]] virtual HRESULT PaintBufferGridLines(GridLineSet lines, COLORREF color, size_t cchLine, til::point coordTarget) noexcept = 0; + [[nodiscard]] virtual HRESULT PaintBufferGridLines(GridLineSet lines, COLORREF gridlineColor, COLORREF underlineColor, size_t cchLine, til::point coordTarget) noexcept = 0; [[nodiscard]] virtual HRESULT PaintSelection(const til::rect& rect) noexcept = 0; [[nodiscard]] virtual HRESULT PaintCursor(const CursorOptions& options) noexcept = 0; [[nodiscard]] virtual HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes, const RenderSettings& renderSettings, gsl::not_null pData, bool usingSoftFont, bool isSettingDefaultBrushes) noexcept = 0; diff --git a/src/renderer/uia/UiaRenderer.cpp b/src/renderer/uia/UiaRenderer.cpp index e58c227c561..ba09384580b 100644 --- a/src/renderer/uia/UiaRenderer.cpp +++ b/src/renderer/uia/UiaRenderer.cpp @@ -347,14 +347,16 @@ void UiaEngine::WaitUntilCanRender() noexcept // For UIA, this doesn't mean anything. So do nothing. // Arguments: // - lines - -// - color - +// - gridlineColor - +// - underlineColor - // - cchLine - // - coordTarget - // Return Value: // - S_FALSE -[[nodiscard]] HRESULT UiaEngine::PaintBufferGridLines(GridLineSet const /*lines*/, - COLORREF const /*color*/, - size_t const /*cchLine*/, +[[nodiscard]] HRESULT UiaEngine::PaintBufferGridLines(const GridLineSet /*lines*/, + const COLORREF /*gridlineColor*/, + const COLORREF /*underlineColor*/, + const size_t /*cchLine*/, const til::point /*coordTarget*/) noexcept { return S_FALSE; diff --git a/src/renderer/uia/UiaRenderer.hpp b/src/renderer/uia/UiaRenderer.hpp index 5e486b7e0e1..4625ca155cb 100644 --- a/src/renderer/uia/UiaRenderer.hpp +++ b/src/renderer/uia/UiaRenderer.hpp @@ -49,7 +49,7 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT NotifyNewText(const std::wstring_view newText) noexcept override; [[nodiscard]] HRESULT PaintBackground() noexcept override; [[nodiscard]] HRESULT PaintBufferLine(const std::span clusters, const til::point coord, const bool fTrimLeft, const bool lineWrapped) noexcept override; - [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF color, const size_t cchLine, const til::point coordTarget) noexcept override; + [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept override; [[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override; [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; [[nodiscard]] HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes, const RenderSettings& renderSettings, const gsl::not_null pData, const bool usingSoftFont, const bool isSettingDefaultBrushes) noexcept override; diff --git a/src/renderer/vt/paint.cpp b/src/renderer/vt/paint.cpp index 801390085c0..99f8853a98f 100644 --- a/src/renderer/vt/paint.cpp +++ b/src/renderer/vt/paint.cpp @@ -187,13 +187,15 @@ using namespace Microsoft::Console::Types; // - Draws up to one line worth of grid lines on top of characters. // Arguments: // - lines - Enum defining which edges of the rectangle to draw -// - color - The color to use for drawing the edges. +// - gridlineColor - The color to use for drawing the gridlines. +// - underlineColor - The color to use for drawing the underlines. // - cchLine - How many characters we should draw the grid lines along (left to right in a row) // - coordTarget - The starting X/Y position of the first character to draw on. // Return Value: // - S_OK [[nodiscard]] HRESULT VtEngine::PaintBufferGridLines(const GridLineSet /*lines*/, - const COLORREF /*color*/, + const COLORREF /*gridlineColor*/, + const COLORREF /*underlineColor*/, const size_t /*cchLine*/, const til::point /*coordTarget*/) noexcept { diff --git a/src/renderer/vt/vtrenderer.hpp b/src/renderer/vt/vtrenderer.hpp index 15de6e0e760..100e04c549d 100644 --- a/src/renderer/vt/vtrenderer.hpp +++ b/src/renderer/vt/vtrenderer.hpp @@ -62,7 +62,7 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT PrepareLineTransform(const LineRendition lineRendition, const til::CoordType targetRow, const til::CoordType viewportLeft) noexcept override; [[nodiscard]] HRESULT PaintBackground() noexcept override; [[nodiscard]] HRESULT PaintBufferLine(std::span clusters, til::point coord, bool fTrimLeft, bool lineWrapped) noexcept override; - [[nodiscard]] HRESULT PaintBufferGridLines(GridLineSet lines, COLORREF color, size_t cchLine, til::point coordTarget) noexcept override; + [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept override; [[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override; [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; [[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo) noexcept override; diff --git a/src/renderer/wddmcon/WddmConRenderer.cpp b/src/renderer/wddmcon/WddmConRenderer.cpp index 378b1dfd086..fda8ae6c21b 100644 --- a/src/renderer/wddmcon/WddmConRenderer.cpp +++ b/src/renderer/wddmcon/WddmConRenderer.cpp @@ -284,9 +284,10 @@ CATCH_RETURN() CATCH_RETURN(); } -[[nodiscard]] HRESULT WddmConEngine::PaintBufferGridLines(GridLineSet const /*lines*/, - COLORREF const /*color*/, - size_t const /*cchLine*/, +[[nodiscard]] HRESULT WddmConEngine::PaintBufferGridLines(const GridLineSet /*lines*/, + const COLORREF /*gridlineColor*/, + const COLORREF /*underlineColor*/, + const size_t /*cchLine*/, const til::point /*coordTarget*/) noexcept { return S_OK; diff --git a/src/renderer/wddmcon/WddmConRenderer.hpp b/src/renderer/wddmcon/WddmConRenderer.hpp index a658ef51180..954dc226946 100644 --- a/src/renderer/wddmcon/WddmConRenderer.hpp +++ b/src/renderer/wddmcon/WddmConRenderer.hpp @@ -44,7 +44,7 @@ namespace Microsoft::Console::Render const til::point coord, const bool trimLeft, const bool lineWrapped) noexcept override; - [[nodiscard]] HRESULT PaintBufferGridLines(GridLineSet const lines, COLORREF const color, size_t const cchLine, til::point const coordTarget) noexcept override; + [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept override; [[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override; [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; From 0bdeb1a759595c59c4c6cc80e2ab2600e6e15255 Mon Sep 17 00:00:00 2001 From: Tushar Singh Date: Sat, 14 Oct 2023 22:53:20 +0530 Subject: [PATCH 06/24] add dotted underline to BackendD2D and Dx renderers --- src/renderer/atlas/BackendD2D.cpp | 2 +- src/renderer/dx/DxRenderer.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/renderer/atlas/BackendD2D.cpp b/src/renderer/atlas/BackendD2D.cpp index ba1fc9c0731..2f041a5afa2 100644 --- a/src/renderer/atlas/BackendD2D.cpp +++ b/src/renderer/atlas/BackendD2D.cpp @@ -459,7 +459,7 @@ void BackendD2D::_drawGridlineRow(const RenderingPayload& p, const ShapedRow* ro { appendHorizontalLine(r, p.s->font->underline, nullptr, r.underlineColor); } - if (r.lines.test(GridLines::HyperlinkUnderline)) + if (r.lines.any(GridLines::DottedUnderline, GridLines::HyperlinkUnderline)) { appendHorizontalLine(r, p.s->font->underline, _dottedStrokeStyle.get(), r.underlineColor); } diff --git a/src/renderer/dx/DxRenderer.cpp b/src/renderer/dx/DxRenderer.cpp index 26c0bcf45b4..e324f590080 100644 --- a/src/renderer/dx/DxRenderer.cpp +++ b/src/renderer/dx/DxRenderer.cpp @@ -1721,7 +1721,7 @@ try _d2dDeviceContext->DrawLine({ x0, y0 }, { x1, y1 }, _d2dBrushForeground.Get(), strokeWidth, _strokeStyle.Get()); }; - const auto DrawHyperlinkLine = [=](const auto x0, const auto y0, const auto x1, const auto y1, const auto strokeWidth) noexcept { + const auto DrawDottedLine = [=](const auto x0, const auto y0, const auto x1, const auto y1, const auto strokeWidth) noexcept { _d2dDeviceContext->DrawLine({ x0, y0 }, { x1, y1 }, _d2dBrushForeground.Get(), strokeWidth, _dashStrokeStyle.Get()); }; @@ -1780,7 +1780,7 @@ try // In the case of the underline and strikethrough offsets, the stroke width // is already accounted for, so they don't require further adjustments. - if (lines.any(GridLines::Underline, GridLines::DoubleUnderline, GridLines::HyperlinkUnderline)) + if (lines.any(GridLines::Underline, GridLines::DoubleUnderline, GridLines::DottedUnderline, GridLines::HyperlinkUnderline)) { const auto halfUnderlineWidth = lineMetrics.underlineWidth / 2.0f; const auto startX = target.x + halfUnderlineWidth; @@ -1792,9 +1792,9 @@ try DrawLine(startX, y, endX, y, lineMetrics.underlineWidth); } - if (lines.test(GridLines::HyperlinkUnderline)) + if (lines.any(GridLines::DottedUnderline, GridLines::HyperlinkUnderline)) { - DrawHyperlinkLine(startX, y, endX, y, lineMetrics.underlineWidth); + DrawDottedLine(startX, y, endX, y, lineMetrics.underlineWidth); } if (lines.test(GridLines::DoubleUnderline)) From c179041581653cfb381409ceff5a29cfbd5460ac Mon Sep 17 00:00:00 2001 From: Tushar Singh Date: Sat, 14 Oct 2023 23:06:16 +0530 Subject: [PATCH 07/24] fix ScrollTest's MockScrollRenderEngine wasn't updated to receive underline color --- src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp b/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp index 12223e94a63..1637686fd0e 100644 --- a/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp +++ b/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp @@ -57,7 +57,7 @@ namespace HRESULT InvalidateCircling(_Out_ bool* /*pForcePaint*/) noexcept { return S_OK; } HRESULT PaintBackground() noexcept { return S_OK; } HRESULT PaintBufferLine(std::span /*clusters*/, til::point /*coord*/, bool /*fTrimLeft*/, bool /*lineWrapped*/) noexcept { return S_OK; } - HRESULT PaintBufferGridLines(GridLineSet /*lines*/, COLORREF /*color*/, size_t /*cchLine*/, til::point /*coordTarget*/) noexcept { return S_OK; } + HRESULT PaintBufferGridLines(GridLineSet /*lines*/, COLORREF /*gridlineColor*/, COLORREF /*underlineColor*/, size_t /*cchLine*/, til::point /*coordTarget*/) noexcept { return S_OK; } HRESULT PaintSelection(const til::rect& /*rect*/) noexcept { return S_OK; } HRESULT PaintCursor(const CursorOptions& /*options*/) noexcept { return S_OK; } HRESULT UpdateDrawingBrushes(const TextAttribute& /*textAttributes*/, const RenderSettings& /*renderSettings*/, gsl::not_null /*pData*/, bool /*usingSoftFont*/, bool /*isSettingDefaultBrushes*/) noexcept { return S_OK; } From 8684de8d496980edfc7f28cf4b21b8e4a345968c Mon Sep 17 00:00:00 2001 From: Tushar Singh Date: Mon, 16 Oct 2023 12:43:21 +0530 Subject: [PATCH 08/24] make sine wave start with a crest --- src/renderer/atlas/shader_ps.hlsl | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/renderer/atlas/shader_ps.hlsl b/src/renderer/atlas/shader_ps.hlsl index cb8cff986c8..bdf90344cb6 100644 --- a/src/renderer/atlas/shader_ps.hlsl +++ b/src/renderer/atlas/shader_ps.hlsl @@ -109,9 +109,10 @@ Output main(PSData data) : SV_Target const float centerY = cellPosY + underlineCellOffset + strokeWidthHalf; const float Pi = radians(180); const float freq = 2.0f * Pi / backgroundCellSize.x; - const float amp = curlyLineHeight - 1.0f; // -1.0f avoids clipping at the peak + const float amp = curlyLineHeight - 1.0f; // -1.0f avoids clipping at the peaks - const float s = sin(data.position.x * freq); + // The wave starts with a negative-peak(trough). To make it start with a positive-peak(crest), we phase shift it by `Pi`. + const float s = sin(data.position.x * freq + Pi); const float d = abs(centerY + s * amp - data.position.y); const float a = 1 - saturate(d - strokeWidthHalf); color = a * premultiplyColor(data.color); @@ -126,7 +127,7 @@ Output main(PSData data) : SV_Target float centerY = cellPosY + underlineCellOffset + strokeWidthHalf; const float Pi = radians(180); float freq = 2.0f * Pi / backgroundCellSize.x; - float amp = curlyLineHeight - 1.0f; // -1.0f avoids clipping at the peak + float amp = curlyLineHeight - 1.0f; // -1.0f avoids clipping at the peaks // In 'Wide' case, we need to draw the same wave on an area twice as big. strokeWidthHalf *= 2; @@ -134,7 +135,8 @@ Output main(PSData data) : SV_Target freq /= 2; amp *= 2; - const float s = sin(data.position.x * freq); + // The wave starts with a negative-peak(trough). To make it start with a positive-peak(crest), we phase shift it by `Pi`. + const float s = sin(data.position.x * freq + Pi); const float d = abs(centerY + s * amp - data.position.y); const float a = 1 - saturate(d - strokeWidthHalf); color = a * premultiplyColor(data.color); From dc986c1d185bf04c52f225adb6bfc6d5f7353ad0 Mon Sep 17 00:00:00 2001 From: Tushar Singh Date: Mon, 16 Oct 2023 14:29:34 +0530 Subject: [PATCH 09/24] fix strikethrough being drawn with underline color --- src/renderer/atlas/BackendD2D.cpp | 8 ++++---- src/renderer/atlas/BackendD3D.cpp | 8 ++++---- src/renderer/dx/DxRenderer.cpp | 20 ++++++++++---------- src/renderer/gdi/paint.cpp | 12 ++++++------ 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/renderer/atlas/BackendD2D.cpp b/src/renderer/atlas/BackendD2D.cpp index 2f041a5afa2..07713d0fb6f 100644 --- a/src/renderer/atlas/BackendD2D.cpp +++ b/src/renderer/atlas/BackendD2D.cpp @@ -454,6 +454,10 @@ void BackendD2D::_drawGridlineRow(const RenderingPayload& p, const ShapedRow* ro { appendHorizontalLine(r, p.s->font->gridBottom, nullptr, r.gridlineColor); } + if (r.lines.test(GridLines::Strikethrough)) + { + appendHorizontalLine(r, p.s->font->strikethrough, nullptr, r.gridlineColor); + } if (r.lines.test(GridLines::Underline)) { @@ -470,10 +474,6 @@ void BackendD2D::_drawGridlineRow(const RenderingPayload& p, const ShapedRow* ro appendHorizontalLine(r, pos, nullptr, r.underlineColor); } } - if (r.lines.test(GridLines::Strikethrough)) - { - appendHorizontalLine(r, p.s->font->strikethrough, nullptr, r.underlineColor); - } } } diff --git a/src/renderer/atlas/BackendD3D.cpp b/src/renderer/atlas/BackendD3D.cpp index 495b3bd3d61..e090e5f4a98 100644 --- a/src/renderer/atlas/BackendD3D.cpp +++ b/src/renderer/atlas/BackendD3D.cpp @@ -1733,6 +1733,10 @@ void BackendD3D::_drawGridlines(const RenderingPayload& p, u16 y) { appendHorizontalLine(r, p.s->font->gridBottom, ShadingType::SolidLine, r.gridlineColor); } + if (r.lines.test(GridLines::Strikethrough)) + { + appendHorizontalLine(r, p.s->font->strikethrough, ShadingType::SolidLine, r.gridlineColor); + } if (r.lines.test(GridLines::Underline)) { @@ -1757,10 +1761,6 @@ void BackendD3D::_drawGridlines(const RenderingPayload& p, u16 y) appendHorizontalLine(r, pos, ShadingType::SolidLine, r.underlineColor); } } - if (r.lines.test(GridLines::Strikethrough)) - { - appendHorizontalLine(r, p.s->font->strikethrough, ShadingType::SolidLine, r.underlineColor); - } } } diff --git a/src/renderer/dx/DxRenderer.cpp b/src/renderer/dx/DxRenderer.cpp index e324f590080..064d219f72a 100644 --- a/src/renderer/dx/DxRenderer.cpp +++ b/src/renderer/dx/DxRenderer.cpp @@ -1775,6 +1775,16 @@ try } } + if (lines.test(GridLines::Strikethrough)) + { + const auto halfStrikethroughWidth = lineMetrics.strikethroughWidth / 2.0f; + const auto startX = target.x + halfStrikethroughWidth; + const auto endX = target.x + fullRunWidth - halfStrikethroughWidth; + const auto y = target.y + lineMetrics.strikethroughOffset; + + DrawLine(startX, y, endX, y, lineMetrics.strikethroughWidth); + } + _d2dBrushForeground->SetColor(_ColorFFromColorRef(underlineColor | 0xff000000)); // In the case of the underline and strikethrough offsets, the stroke width @@ -1805,16 +1815,6 @@ try } } - if (lines.test(GridLines::Strikethrough)) - { - const auto halfStrikethroughWidth = lineMetrics.strikethroughWidth / 2.0f; - const auto startX = target.x + halfStrikethroughWidth; - const auto endX = target.x + fullRunWidth - halfStrikethroughWidth; - const auto y = target.y + lineMetrics.strikethroughOffset; - - DrawLine(startX, y, endX, y, lineMetrics.strikethroughWidth); - } - return S_OK; } CATCH_RETURN() diff --git a/src/renderer/gdi/paint.cpp b/src/renderer/gdi/paint.cpp index 67f0c76632f..ab074c7ca28 100644 --- a/src/renderer/gdi/paint.cpp +++ b/src/renderer/gdi/paint.cpp @@ -575,6 +575,12 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) RETURN_HR_IF(E_FAIL, !DrawLine(ptTarget.x, y, widthOfAllCells, _lineMetrics.gridlineWidth)); } + if (lines.test(GridLines::Strikethrough)) + { + const auto y = ptTarget.y + _lineMetrics.strikethroughOffset; + RETURN_HR_IF(E_FAIL, !DrawLine(ptTarget.x, y, widthOfAllCells, _lineMetrics.strikethroughWidth)); + } + hbr.reset(CreateSolidBrush(underlineColor)); wil::unique_hbrush hbrGridline(SelectBrush(_hdcMemoryContext, hbr.get())); RETURN_HR_IF_NULL(E_FAIL, hbrGridline.get()); @@ -592,12 +598,6 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) } } - if (lines.test(GridLines::Strikethrough)) - { - const auto y = ptTarget.y + _lineMetrics.strikethroughOffset; - RETURN_HR_IF(E_FAIL, !DrawLine(ptTarget.x, y, widthOfAllCells, _lineMetrics.strikethroughWidth)); - } - return S_OK; } From bea07a5e349689f6a94bd03789593bb0ee3fbb6a Mon Sep 17 00:00:00 2001 From: Tushar Singh Date: Tue, 24 Oct 2023 23:44:51 +0530 Subject: [PATCH 10/24] add support for underline styles in gdi renderer --- src/renderer/gdi/gdirenderer.hpp | 1 + src/renderer/gdi/paint.cpp | 78 ++++++++++++++++++++++++++------ src/renderer/gdi/state.cpp | 14 ++++++ 3 files changed, 80 insertions(+), 13 deletions(-) diff --git a/src/renderer/gdi/gdirenderer.hpp b/src/renderer/gdi/gdirenderer.hpp index 76df464eab8..4fcdc48ad7b 100644 --- a/src/renderer/gdi/gdirenderer.hpp +++ b/src/renderer/gdi/gdirenderer.hpp @@ -121,6 +121,7 @@ namespace Microsoft::Console::Render int underlineWidth; int strikethroughOffset; int strikethroughWidth; + int curlylineHeight; }; LineMetrics _lineMetrics; diff --git a/src/renderer/gdi/paint.cpp b/src/renderer/gdi/paint.cpp index ab074c7ca28..b6a837934ea 100644 --- a/src/renderer/gdi/paint.cpp +++ b/src/renderer/gdi/paint.cpp @@ -532,6 +532,19 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) // On exit, be sure we try to put the brush back how it was originally. auto restoreBrushOnExit = wil::scope_exit([&] { hbr.reset(SelectBrush(_hdcMemoryContext, hbrPrev.get())); }); + // Create a pen matching the underline style. + DWORD underlinePenType = PS_SOLID; + if (lines.test(GridLines::DottedUnderline)) + { + underlinePenType = PS_DOT; + } + else if (lines.test(GridLines::DashedUnderline)) + { + underlinePenType = PS_DASH; + } + const LOGBRUSH brushProp{ .lbStyle = BS_SOLID, .lbColor = underlineColor }; + wil::unique_hpen hpen(ExtCreatePen(underlinePenType | PS_GEOMETRIC | PS_ENDCAP_FLAT, _lineMetrics.underlineWidth, &brushProp, 0, nullptr)); + // Get the font size so we know the size of the rectangle lines we'll be inscribing. const auto fontWidth = _GetFontSize().width; const auto fontHeight = _GetFontSize().height; @@ -540,6 +553,31 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) const auto DrawLine = [=](const auto x, const auto y, const auto w, const auto h) { return PatBlt(_hdcMemoryContext, x, y, w, h, PATCOPY); }; + const auto DrawStrokedLine = [&](const auto x, const auto y, const auto w) { + RETURN_HR_IF(E_FAIL, !MoveToEx(_hdcMemoryContext, x, y, nullptr)); + RETURN_HR_IF(E_FAIL, !LineTo(_hdcMemoryContext, x + w, y)); + return S_OK; + }; + const auto DrawCurlyLine = [&](const auto x, const auto y, const auto cCurlyLines) { + const auto curlyLineWidth = fontWidth; + const auto curlyLineHalfWidth = gsl::narrow_cast(std::floor(curlyLineWidth / 2)); + const auto controlPointHeight = gsl::narrow_cast(std::floor(3.5f * _lineMetrics.curlylineHeight)); + // Each curlyLine requires 3 `POINT`s + const auto cPoints = gsl::narrow(3 * cCurlyLines); + std::vector points; + points.reserve(cPoints); + auto start = x; + for (auto i = 0; i < cCurlyLines; i++) + { + points.emplace_back(start + curlyLineHalfWidth, y - controlPointHeight); + points.emplace_back(start + curlyLineHalfWidth, y + controlPointHeight); + points.emplace_back(start + curlyLineWidth, y); + start += curlyLineWidth; + } + RETURN_HR_IF(E_FAIL, !MoveToEx(_hdcMemoryContext, x, y, nullptr)); + RETURN_HR_IF(E_FAIL, !PolyBezierTo(_hdcMemoryContext, points.data(), cPoints)); + return S_OK; + }; if (lines.test(GridLines::Left)) { @@ -581,21 +619,35 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) RETURN_HR_IF(E_FAIL, !DrawLine(ptTarget.x, y, widthOfAllCells, _lineMetrics.strikethroughWidth)); } - hbr.reset(CreateSolidBrush(underlineColor)); - wil::unique_hbrush hbrGridline(SelectBrush(_hdcMemoryContext, hbr.get())); - RETURN_HR_IF_NULL(E_FAIL, hbrGridline.get()); - hbr.release(); // If SelectBrush was successful, GDI owns the brush. Release for now. + // Apply the pen for underline drawing. + const auto prevPen = SelectPen(_hdcMemoryContext, hpen.get()); + RETURN_HR_IF_NULL(E_FAIL, prevPen); + // Release the pen since GDI owns it now. + hpen.release(); + const auto restorePenOnExit = wil::scope_exit([&] { hpen.reset(SelectPen(_hdcMemoryContext, prevPen)); }); - if (lines.any(GridLines::Underline, GridLines::DoubleUnderline)) + const auto underlineY = std::lround(ptTarget.y + _lineMetrics.underlineOffset + _lineMetrics.underlineWidth / 2.0f); + if (lines.test(GridLines::Underline)) { - const auto y = ptTarget.y + _lineMetrics.underlineOffset; - RETURN_HR_IF(E_FAIL, !DrawLine(ptTarget.x, y, widthOfAllCells, _lineMetrics.underlineWidth)); - - if (lines.test(GridLines::DoubleUnderline)) - { - const auto y2 = ptTarget.y + _lineMetrics.underlineOffset2; - RETURN_HR_IF(E_FAIL, !DrawLine(ptTarget.x, y2, widthOfAllCells, _lineMetrics.underlineWidth)); - } + return DrawStrokedLine(ptTarget.x, underlineY, widthOfAllCells); + } + else if (lines.test(GridLines::DoubleUnderline)) + { + const auto doubleUnderlineBottomLineY = std::lround(ptTarget.y + _lineMetrics.underlineOffset2 + _lineMetrics.underlineWidth / 2.0f); + RETURN_IF_FAILED(DrawStrokedLine(ptTarget.x, underlineY, widthOfAllCells)); + return DrawStrokedLine(ptTarget.x, doubleUnderlineBottomLineY, widthOfAllCells); + } + else if (lines.test(GridLines::CurlyUnderline)) + { + return DrawCurlyLine(ptTarget.x, underlineY, cchLine); + } + else if (lines.test(GridLines::DottedUnderline)) + { + return DrawStrokedLine(ptTarget.x, underlineY, widthOfAllCells); + } + else if (lines.test(GridLines::DashedUnderline)) + { + return DrawStrokedLine(ptTarget.x, underlineY, widthOfAllCells); } return S_OK; diff --git a/src/renderer/gdi/state.cpp b/src/renderer/gdi/state.cpp index 556f2df02fb..b720283fd2f 100644 --- a/src/renderer/gdi/state.cpp +++ b/src/renderer/gdi/state.cpp @@ -397,6 +397,20 @@ GdiEngine::~GdiEngine() _lineMetrics.underlineOffset2 = _lineMetrics.underlineOffset - _lineMetrics.gridlineWidth; } + _lineMetrics.curlylineHeight = gsl::narrow_cast(std::lround(fontSize * 0.07f)); + // We need to lower the curly line height incase the gap between the underline + // and cell bottom isn't enough. Curly-line is a stroked line so we account + // for that as well. + const auto maxCurlyLineHeight = gsl::narrow_cast(std::floor(Font.GetSize().height - (_lineMetrics.underlineOffset + _lineMetrics.underlineWidth))); + if (maxCurlyLineHeight <= 1) // too small to be curly, make it a straight line instead. + { + _lineMetrics.curlylineHeight = 0; + } + else + { + _lineMetrics.curlylineHeight = std::min(maxCurlyLineHeight, _lineMetrics.curlylineHeight); + } + // Now find the size of a 0 in this current font and save it for conversions done later. _coordFontLast = Font.GetSize(); From 4f13a3cf2e180b57b8921229fd5abc1bf11809cf Mon Sep 17 00:00:00 2001 From: Tushar Singh Date: Wed, 25 Oct 2023 00:00:18 +0530 Subject: [PATCH 11/24] add spellings --- .github/actions/spelling/expect/expect.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 4339b17eb04..edcd290f256 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -343,6 +343,7 @@ CTRLVOLUME Ctxt CUF cupxy +curlyline CURRENTFONT currentmode CURRENTPAGE @@ -578,6 +579,7 @@ elems emacs EMPTYBOX enabledelayedexpansion +ENDCAP endptr endregion ENTIREBUFFER @@ -826,6 +828,7 @@ hostlib HPA hpcon HPCON +hpen hpj HPR HProvider @@ -1010,6 +1013,7 @@ LOBYTE localappdata locsrc Loewen +LOGBRUSH LOGFONT LOGFONTA LOGFONTW From 1f3e535620d2f760b64decb36476b50ef159076d Mon Sep 17 00:00:00 2001 From: Tushar Singh Date: Wed, 25 Oct 2023 11:19:45 +0530 Subject: [PATCH 12/24] fix signed/unsigned mismatch --- src/renderer/gdi/paint.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/gdi/paint.cpp b/src/renderer/gdi/paint.cpp index b6a837934ea..eee5302c449 100644 --- a/src/renderer/gdi/paint.cpp +++ b/src/renderer/gdi/paint.cpp @@ -567,7 +567,7 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) std::vector points; points.reserve(cPoints); auto start = x; - for (auto i = 0; i < cCurlyLines; i++) + for (auto i = 0u; i < cCurlyLines; i++) { points.emplace_back(start + curlyLineHalfWidth, y - controlPointHeight); points.emplace_back(start + curlyLineHalfWidth, y + controlPointHeight); From 2babe7b35e002aa815e975c7dbdd7f5cd32ea243 Mon Sep 17 00:00:00 2001 From: Tushar Singh Date: Wed, 25 Oct 2023 23:41:36 +0530 Subject: [PATCH 13/24] make small improvements to Atlas curlyline rendering --- src/renderer/atlas/BackendD3D.cpp | 19 ++++++++++++--- src/renderer/atlas/BackendD3D.h | 6 +++-- src/renderer/atlas/shader_ps.hlsl | 40 +++++++++++++++---------------- 3 files changed, 39 insertions(+), 26 deletions(-) diff --git a/src/renderer/atlas/BackendD3D.cpp b/src/renderer/atlas/BackendD3D.cpp index e090e5f4a98..f3e74f13d93 100644 --- a/src/renderer/atlas/BackendD3D.cpp +++ b/src/renderer/atlas/BackendD3D.cpp @@ -308,7 +308,19 @@ void BackendD3D::_updateFontDependents(const RenderingPayload& p) // For curly line, we'll make room for the trough and crest (wave's peaks). // The baseline for curly-line is kept at the baseline of singly underline. const auto strokeWidthHalf = font.underline.height / 2.0f; - const auto curlyUnderlinePeakHeight = _curlyLineHeight * font.fontSize; + auto curlyUnderlinePeakHeight = _curlyLineHeightEm * font.fontSize; + // Lower the peak height when there isn't enough space at the cell bottom. + const auto maxUnderlinePeakHeight = ((font.cellSize.y - (font.underline.position + 2.0f * strokeWidthHalf)) / 2); + if (maxUnderlinePeakHeight < 2) // too small to be curly, make it straight instead. + { + curlyUnderlinePeakHeight = 0; + } + else + { + curlyUnderlinePeakHeight = std::min(curlyUnderlinePeakHeight, maxUnderlinePeakHeight); + } + // we draw a smaller curlyline to avoiding clipping due to rounding errors. + _curlyLineHeight = std::max(0.0f, curlyUnderlinePeakHeight - 1.0f); const auto curlyUnderlinePos = font.underline.position - curlyUnderlinePeakHeight; const auto curlyUnderlineWidth = 2.0f * (curlyUnderlinePeakHeight + strokeWidthHalf); const auto curlyUnderlinePosU16 = gsl::narrow_cast(lrintf(curlyUnderlinePos)); @@ -549,8 +561,9 @@ void BackendD3D::_recreateConstBuffer(const RenderingPayload& p) const DWrite_GetGammaRatios(_gamma, data.gammaRatios); data.enhancedContrast = p.s->font->antialiasingMode == AntialiasingMode::ClearType ? _cleartypeEnhancedContrast : _grayscaleEnhancedContrast; data.underlineWidth = p.s->font->underline.height; - data.curlyLineHeight = p.s->font->fontSize * _curlyLineHeight; - data.underlineCellOffset = p.s->font->underline.position; + data.curlyLineWaveFreq = 2.0f * 3.14f / p.s->font->cellSize.x; + data.curlyLineHeight = _curlyLineHeight; + data.curlyLineCellOffset = p.s->font->underline.position + p.s->font->underline.height / 2.0f; p.deviceContext->UpdateSubresource(_psConstantBuffer.get(), 0, nullptr, &data, 0, 0); } } diff --git a/src/renderer/atlas/BackendD3D.h b/src/renderer/atlas/BackendD3D.h index 46b9f6e3967..cd8770dbc58 100644 --- a/src/renderer/atlas/BackendD3D.h +++ b/src/renderer/atlas/BackendD3D.h @@ -43,7 +43,8 @@ namespace Microsoft::Console::Render::Atlas alignas(sizeof(f32)) f32 enhancedContrast = 0; alignas(sizeof(f32)) f32 underlineWidth = 0; alignas(sizeof(f32)) f32 curlyLineHeight = 0; - alignas(sizeof(f32)) f32 underlineCellOffset = 0; + alignas(sizeof(f32)) f32 curlyLineWaveFreq = 0; + alignas(sizeof(f32)) f32 curlyLineCellOffset = 0; #pragma warning(suppress : 4324) // 'PSConstBuffer': structure was padded due to alignment specifier }; @@ -297,7 +298,8 @@ namespace Microsoft::Console::Render::Atlas // The bounding rect of _cursorRects in pixels. til::rect _cursorPosition; - const f32 _curlyLineHeight = 0.1f; // in `em` units. + const f32 _curlyLineHeightEm = 0.075f; // in `em` units. + f32 _curlyLineHeight; FontDecorationPosition _curlyUnderline; bool _requiresContinuousRedraw = false; diff --git a/src/renderer/atlas/shader_ps.hlsl b/src/renderer/atlas/shader_ps.hlsl index bdf90344cb6..edffee06920 100644 --- a/src/renderer/atlas/shader_ps.hlsl +++ b/src/renderer/atlas/shader_ps.hlsl @@ -13,7 +13,8 @@ cbuffer ConstBuffer : register(b0) float enhancedContrast; float underlineWidth; float curlyLineHeight; - float underlineCellOffset; + float curlyLineWaveFreq; + float curlyLineCellOffset; } Texture2D background : register(t0); @@ -103,17 +104,16 @@ Output main(PSData data) : SV_Target } case SHADING_TYPE_CURLY_LINE: { + const int cellRow = data.position.y / backgroundCellSize.y; + const float cellTop = cellRow * backgroundCellSize.y; + const float centerY = cellTop + curlyLineCellOffset; const float strokeWidthHalf = underlineWidth / 2.0f; - const int cellIdxY = data.position.y / backgroundCellSize.y; - const float cellPosY = cellIdxY * backgroundCellSize.y; - const float centerY = cellPosY + underlineCellOffset + strokeWidthHalf; + + // The wave starts with a negative-peak(trough). We phase shift it by `Pi` to start with a positive peak(crest). const float Pi = radians(180); - const float freq = 2.0f * Pi / backgroundCellSize.x; - const float amp = curlyLineHeight - 1.0f; // -1.0f avoids clipping at the peaks + const float s = sin(data.position.x * curlyLineWaveFreq + Pi); - // The wave starts with a negative-peak(trough). To make it start with a positive-peak(crest), we phase shift it by `Pi`. - const float s = sin(data.position.x * freq + Pi); - const float d = abs(centerY + s * amp - data.position.y); + const float d = abs(centerY + s * curlyLineHeight - data.position.y); const float a = 1 - saturate(d - strokeWidthHalf); color = a * premultiplyColor(data.color); weights = color.aaaa; @@ -121,22 +121,20 @@ Output main(PSData data) : SV_Target } case SHADING_TYPE_CURLY_LINE_WIDE: { - float strokeWidthHalf = underlineWidth / 2.0f; - const int cellIdxY = data.position.y / backgroundCellSize.y; - const float cellPosY = cellIdxY * backgroundCellSize.y; - float centerY = cellPosY + underlineCellOffset + strokeWidthHalf; - const float Pi = radians(180); - float freq = 2.0f * Pi / backgroundCellSize.x; - float amp = curlyLineHeight - 1.0f; // -1.0f avoids clipping at the peaks + const int cellRow = data.position.y / backgroundCellSize.y; + const float cellTop = cellRow * backgroundCellSize.y; + float centerY = cellTop + curlyLineCellOffset; // In 'Wide' case, we need to draw the same wave on an area twice as big. - strokeWidthHalf *= 2; - centerY -= curlyLineHeight + strokeWidthHalf; - freq /= 2; - amp *= 2; + const float strokeWidthHalf = underlineWidth; + const float amp = curlyLineHeight * 2.0f; + const float freq = curlyLineWaveFreq / 2.0f; + centerY -= 2 * strokeWidthHalf; - // The wave starts with a negative-peak(trough). To make it start with a positive-peak(crest), we phase shift it by `Pi`. + // The wave starts with a negative peak(trough). We phase shift it by `Pi` to start with a positive peak(crest). + const float Pi = radians(180); const float s = sin(data.position.x * freq + Pi); + const float d = abs(centerY + s * amp - data.position.y); const float a = 1 - saturate(d - strokeWidthHalf); color = a * premultiplyColor(data.color); From e31d0fb186b087e8d0ccead06977cd8673bf1d1e Mon Sep 17 00:00:00 2001 From: Tushar Singh Date: Fri, 27 Oct 2023 18:28:02 +0530 Subject: [PATCH 14/24] AtlasEngine: use cellBottomGap as the peak height for curly line --- src/renderer/atlas/BackendD3D.cpp | 49 ++++++++++++++++++++----------- src/renderer/atlas/BackendD3D.h | 5 ++-- src/renderer/atlas/shader_ps.hlsl | 26 ++++++++-------- 3 files changed, 48 insertions(+), 32 deletions(-) diff --git a/src/renderer/atlas/BackendD3D.cpp b/src/renderer/atlas/BackendD3D.cpp index f3e74f13d93..a2f08885b72 100644 --- a/src/renderer/atlas/BackendD3D.cpp +++ b/src/renderer/atlas/BackendD3D.cpp @@ -42,6 +42,18 @@ TIL_FAST_MATH_BEGIN using namespace Microsoft::Console::Render::Atlas; +namespace +{ + + // The max height of Curly line peak in `em` units. + constexpr auto MaxCurlyLinePeakHeightEm = 0.075f; + + // We aim for atleast 1px height, but since we draw 1px smaller curly line, + // we aim for 2px height as a result. + constexpr auto MinCurlyLinePeakHeight = 2.0f; + +} + template<> struct std::hash { @@ -305,24 +317,27 @@ void BackendD3D::_updateFontDependents(const RenderingPayload& p) { const auto& font = *p.s->font; - // For curly line, we'll make room for the trough and crest (wave's peaks). - // The baseline for curly-line is kept at the baseline of singly underline. - const auto strokeWidthHalf = font.underline.height / 2.0f; - auto curlyUnderlinePeakHeight = _curlyLineHeightEm * font.fontSize; - // Lower the peak height when there isn't enough space at the cell bottom. - const auto maxUnderlinePeakHeight = ((font.cellSize.y - (font.underline.position + 2.0f * strokeWidthHalf)) / 2); - if (maxUnderlinePeakHeight < 2) // too small to be curly, make it straight instead. - { - curlyUnderlinePeakHeight = 0; - } - else + // Curlyline uses the gap between cell bottom and singly underline position + // as the height of the wave's peak. The baseline for curly-line is at the + // middle of singly underline. The gap could be too big, so we also apply + // a limit on the peak height. + const auto strokeHalfWidth = font.underline.height / 2.0f; + const auto underlineMidY = font.underline.position + strokeHalfWidth; + const auto cellBottomGap = font.cellSize.y - underlineMidY - strokeHalfWidth; + const auto maxCurlyLinePeakHeight = MaxCurlyLinePeakHeightEm * font.fontSize; + auto curlyLinePeakHeight = std::min(cellBottomGap, maxCurlyLinePeakHeight); + + // When it's too small to be curly, make it straight. + if (curlyLinePeakHeight < MinCurlyLinePeakHeight) { - curlyUnderlinePeakHeight = std::min(curlyUnderlinePeakHeight, maxUnderlinePeakHeight); + curlyLinePeakHeight = 0; } - // we draw a smaller curlyline to avoiding clipping due to rounding errors. - _curlyLineHeight = std::max(0.0f, curlyUnderlinePeakHeight - 1.0f); - const auto curlyUnderlinePos = font.underline.position - curlyUnderlinePeakHeight; - const auto curlyUnderlineWidth = 2.0f * (curlyUnderlinePeakHeight + strokeWidthHalf); + + // We draw a smaller curly line (-1px) to avoid clipping due to the rounding. + _curlyLineDrawPeakHeight = std::max(0.0f, curlyLinePeakHeight - 1.0f); + + const auto curlyUnderlinePos = font.underline.position - curlyLinePeakHeight; + const auto curlyUnderlineWidth = 2.0f * (curlyLinePeakHeight + strokeHalfWidth); const auto curlyUnderlinePosU16 = gsl::narrow_cast(lrintf(curlyUnderlinePos)); const auto curlyUnderlineWidthU16 = gsl::narrow_cast(lrintf(curlyUnderlineWidth)); _curlyUnderline = { curlyUnderlinePosU16, curlyUnderlineWidthU16 }; @@ -562,7 +577,7 @@ void BackendD3D::_recreateConstBuffer(const RenderingPayload& p) const data.enhancedContrast = p.s->font->antialiasingMode == AntialiasingMode::ClearType ? _cleartypeEnhancedContrast : _grayscaleEnhancedContrast; data.underlineWidth = p.s->font->underline.height; data.curlyLineWaveFreq = 2.0f * 3.14f / p.s->font->cellSize.x; - data.curlyLineHeight = _curlyLineHeight; + data.curlyLinePeakHeight = _curlyLineDrawPeakHeight; data.curlyLineCellOffset = p.s->font->underline.position + p.s->font->underline.height / 2.0f; p.deviceContext->UpdateSubresource(_psConstantBuffer.get(), 0, nullptr, &data, 0, 0); } diff --git a/src/renderer/atlas/BackendD3D.h b/src/renderer/atlas/BackendD3D.h index cd8770dbc58..e739013cc4a 100644 --- a/src/renderer/atlas/BackendD3D.h +++ b/src/renderer/atlas/BackendD3D.h @@ -42,7 +42,7 @@ namespace Microsoft::Console::Render::Atlas alignas(sizeof(f32x4)) f32 gammaRatios[4]{}; alignas(sizeof(f32)) f32 enhancedContrast = 0; alignas(sizeof(f32)) f32 underlineWidth = 0; - alignas(sizeof(f32)) f32 curlyLineHeight = 0; + alignas(sizeof(f32)) f32 curlyLinePeakHeight = 0; alignas(sizeof(f32)) f32 curlyLineWaveFreq = 0; alignas(sizeof(f32)) f32 curlyLineCellOffset = 0; #pragma warning(suppress : 4324) // 'PSConstBuffer': structure was padded due to alignment specifier @@ -298,8 +298,7 @@ namespace Microsoft::Console::Render::Atlas // The bounding rect of _cursorRects in pixels. til::rect _cursorPosition; - const f32 _curlyLineHeightEm = 0.075f; // in `em` units. - f32 _curlyLineHeight; + f32 _curlyLineDrawPeakHeight; FontDecorationPosition _curlyUnderline; bool _requiresContinuousRedraw = false; diff --git a/src/renderer/atlas/shader_ps.hlsl b/src/renderer/atlas/shader_ps.hlsl index edffee06920..337bb56ba1e 100644 --- a/src/renderer/atlas/shader_ps.hlsl +++ b/src/renderer/atlas/shader_ps.hlsl @@ -12,7 +12,7 @@ cbuffer ConstBuffer : register(b0) float4 gammaRatios; float enhancedContrast; float underlineWidth; - float curlyLineHeight; + float curlyLinePeakHeight; float curlyLineWaveFreq; float curlyLineCellOffset; } @@ -104,16 +104,17 @@ Output main(PSData data) : SV_Target } case SHADING_TYPE_CURLY_LINE: { - const int cellRow = data.position.y / backgroundCellSize.y; + const int cellRow = floor(data.position.y / backgroundCellSize.y); const float cellTop = cellRow * backgroundCellSize.y; const float centerY = cellTop + curlyLineCellOffset; const float strokeWidthHalf = underlineWidth / 2.0f; - // The wave starts with a negative-peak(trough). We phase shift it by `Pi` to start with a positive peak(crest). + // The wave begins with a negative peak. We phase shift the frequency by + // `Pi` to begin with a positive peak. const float Pi = radians(180); const float s = sin(data.position.x * curlyLineWaveFreq + Pi); - const float d = abs(centerY + s * curlyLineHeight - data.position.y); + const float d = abs(centerY + s * curlyLinePeakHeight - data.position.y); const float a = 1 - saturate(d - strokeWidthHalf); color = a * premultiplyColor(data.color); weights = color.aaaa; @@ -121,17 +122,18 @@ Output main(PSData data) : SV_Target } case SHADING_TYPE_CURLY_LINE_WIDE: { - const int cellRow = data.position.y / backgroundCellSize.y; - const float cellTop = cellRow * backgroundCellSize.y; - float centerY = cellTop + curlyLineCellOffset; + const int prevCellRow = floor(data.position.y / backgroundCellSize.y) - 1; + const float prevCellTop = prevCellRow * backgroundCellSize.y; - // In 'Wide' case, we need to draw the same wave on an area twice as big. - const float strokeWidthHalf = underlineWidth; - const float amp = curlyLineHeight * 2.0f; + // In 'Wide' case, we need to draw the same wave on an area twice as big, + // and the position is relative to the previous cell. + const float centerY = prevCellTop + 2.0f * curlyLineCellOffset; + const float amp = curlyLinePeakHeight * 2.0f; const float freq = curlyLineWaveFreq / 2.0f; - centerY -= 2 * strokeWidthHalf; + const float strokeWidthHalf = underlineWidth; - // The wave starts with a negative peak(trough). We phase shift it by `Pi` to start with a positive peak(crest). + // The wave begins with a negative peak. We phase shift the frequency by + // `Pi` to begin with a positive peak. const float Pi = radians(180); const float s = sin(data.position.x * freq + Pi); From 63ae6ce73778515f23b35b266cfc1ab93094d741 Mon Sep 17 00:00:00 2001 From: Tushar Singh Date: Fri, 27 Oct 2023 21:05:05 +0530 Subject: [PATCH 15/24] GDIRenderer: use cellBottomGap as the peak height for curly line --- src/renderer/gdi/gdirenderer.hpp | 2 +- src/renderer/gdi/paint.cpp | 21 ++++++++++--------- src/renderer/gdi/state.cpp | 36 +++++++++++++++++++++++++------- 3 files changed, 40 insertions(+), 19 deletions(-) diff --git a/src/renderer/gdi/gdirenderer.hpp b/src/renderer/gdi/gdirenderer.hpp index 4fcdc48ad7b..2cc5b4dc14c 100644 --- a/src/renderer/gdi/gdirenderer.hpp +++ b/src/renderer/gdi/gdirenderer.hpp @@ -121,7 +121,7 @@ namespace Microsoft::Console::Render int underlineWidth; int strikethroughOffset; int strikethroughWidth; - int curlylineHeight; + int curlylinePeakHeight; }; LineMetrics _lineMetrics; diff --git a/src/renderer/gdi/paint.cpp b/src/renderer/gdi/paint.cpp index eee5302c449..cea863e22b9 100644 --- a/src/renderer/gdi/paint.cpp +++ b/src/renderer/gdi/paint.cpp @@ -525,6 +525,7 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) wil::unique_hbrush hbr(CreateSolidBrush(gridlineColor)); RETURN_HR_IF_NULL(E_FAIL, hbr.get()); + // TODO: hbrPrev shouldn't be managed. wil::unique_hbrush hbrPrev(SelectBrush(_hdcMemoryContext, hbr.get())); RETURN_HR_IF_NULL(E_FAIL, hbrPrev.get()); hbr.release(); // If SelectBrush was successful, GDI owns the brush. Release for now. @@ -560,8 +561,8 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) }; const auto DrawCurlyLine = [&](const auto x, const auto y, const auto cCurlyLines) { const auto curlyLineWidth = fontWidth; - const auto curlyLineHalfWidth = gsl::narrow_cast(std::floor(curlyLineWidth / 2)); - const auto controlPointHeight = gsl::narrow_cast(std::floor(3.5f * _lineMetrics.curlylineHeight)); + const auto curlyLineHalfWidth = lrintf(curlyLineWidth / 2.0f); + const auto controlPointHeight = gsl::narrow_cast(std::floor(3.5f * _lineMetrics.curlylinePeakHeight)); // Each curlyLine requires 3 `POINT`s const auto cPoints = gsl::narrow(3 * cCurlyLines); std::vector points; @@ -626,28 +627,28 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) hpen.release(); const auto restorePenOnExit = wil::scope_exit([&] { hpen.reset(SelectPen(_hdcMemoryContext, prevPen)); }); - const auto underlineY = std::lround(ptTarget.y + _lineMetrics.underlineOffset + _lineMetrics.underlineWidth / 2.0f); + const auto underlineMidY = std::lround(ptTarget.y + _lineMetrics.underlineOffset + _lineMetrics.underlineWidth / 2.0f); if (lines.test(GridLines::Underline)) { - return DrawStrokedLine(ptTarget.x, underlineY, widthOfAllCells); + return DrawStrokedLine(ptTarget.x, underlineMidY, widthOfAllCells); } else if (lines.test(GridLines::DoubleUnderline)) { - const auto doubleUnderlineBottomLineY = std::lround(ptTarget.y + _lineMetrics.underlineOffset2 + _lineMetrics.underlineWidth / 2.0f); - RETURN_IF_FAILED(DrawStrokedLine(ptTarget.x, underlineY, widthOfAllCells)); - return DrawStrokedLine(ptTarget.x, doubleUnderlineBottomLineY, widthOfAllCells); + const auto doubleUnderlineBottomLineMidY = std::lround(ptTarget.y + _lineMetrics.underlineOffset2 + _lineMetrics.underlineWidth / 2.0f); + RETURN_IF_FAILED(DrawStrokedLine(ptTarget.x, underlineMidY, widthOfAllCells)); + return DrawStrokedLine(ptTarget.x, doubleUnderlineBottomLineMidY, widthOfAllCells); } else if (lines.test(GridLines::CurlyUnderline)) { - return DrawCurlyLine(ptTarget.x, underlineY, cchLine); + return DrawCurlyLine(ptTarget.x, underlineMidY, cchLine); } else if (lines.test(GridLines::DottedUnderline)) { - return DrawStrokedLine(ptTarget.x, underlineY, widthOfAllCells); + return DrawStrokedLine(ptTarget.x, underlineMidY, widthOfAllCells); } else if (lines.test(GridLines::DashedUnderline)) { - return DrawStrokedLine(ptTarget.x, underlineY, widthOfAllCells); + return DrawStrokedLine(ptTarget.x, underlineMidY, widthOfAllCells); } return S_OK; diff --git a/src/renderer/gdi/state.cpp b/src/renderer/gdi/state.cpp index b720283fd2f..099c2f43f78 100644 --- a/src/renderer/gdi/state.cpp +++ b/src/renderer/gdi/state.cpp @@ -11,6 +11,15 @@ using namespace Microsoft::Console::Render; +namespace +{ + // The max height of Curly line peak in `em` units. + constexpr auto MaxCurlyLinePeakHeightEm = 0.075f; + + // The min height of Curly line peak. + constexpr auto MinCurlyLinePeakHeight = 2.0f; +} + // Routine Description: // - Creates a new GDI-based rendering engine // - NOTE: Will throw if initialization failure. Caller must catch. @@ -397,18 +406,29 @@ GdiEngine::~GdiEngine() _lineMetrics.underlineOffset2 = _lineMetrics.underlineOffset - _lineMetrics.gridlineWidth; } - _lineMetrics.curlylineHeight = gsl::narrow_cast(std::lround(fontSize * 0.07f)); - // We need to lower the curly line height incase the gap between the underline - // and cell bottom isn't enough. Curly-line is a stroked line so we account - // for that as well. - const auto maxCurlyLineHeight = gsl::narrow_cast(std::floor(Font.GetSize().height - (_lineMetrics.underlineOffset + _lineMetrics.underlineWidth))); - if (maxCurlyLineHeight <= 1) // too small to be curly, make it a straight line instead. + // Curly line doesn't render properly below 1px stroke width. Make it a straight line. + if (_lineMetrics.underlineWidth < 1) { - _lineMetrics.curlylineHeight = 0; + _lineMetrics.curlylinePeakHeight = 0; } else { - _lineMetrics.curlylineHeight = std::min(maxCurlyLineHeight, _lineMetrics.curlylineHeight); + // Curlyline uses the gap between cell bottom and singly underline + // position as the height of the wave's peak. The baseline for curly + // line is at the middle of singly underline. The gap could be too big, + // so we also apply a limit on the peak height. + const auto strokeHalfWidth = _lineMetrics.underlineWidth / 2.0f; + const auto underlineMidY = _lineMetrics.underlineOffset + strokeHalfWidth; + const auto cellBottomGap = Font.GetSize().height - underlineMidY - strokeHalfWidth; + const auto maxCurlyLinePeakHeight = MaxCurlyLinePeakHeightEm * fontSize; + auto curlyLinePeakHeight = std::min(cellBottomGap, maxCurlyLinePeakHeight); + + // When it's too small to be curly, make it a straight line. + if (curlyLinePeakHeight < MinCurlyLinePeakHeight) + { + curlyLinePeakHeight = 0.0f; + } + _lineMetrics.curlylinePeakHeight = gsl::narrow_cast(std::floor(curlyLinePeakHeight)); } // Now find the size of a 0 in this current font and save it for conversions done later. From b5574d0d8068c744e78eb6360028b184f8d1baca Mon Sep 17 00:00:00 2001 From: Tushar Singh Date: Fri, 27 Oct 2023 23:00:44 +0530 Subject: [PATCH 16/24] initialize _curlyLineDrawPeakHeight --- src/renderer/atlas/BackendD3D.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/atlas/BackendD3D.h b/src/renderer/atlas/BackendD3D.h index e739013cc4a..dbac9a54445 100644 --- a/src/renderer/atlas/BackendD3D.h +++ b/src/renderer/atlas/BackendD3D.h @@ -298,7 +298,7 @@ namespace Microsoft::Console::Render::Atlas // The bounding rect of _cursorRects in pixels. til::rect _cursorPosition; - f32 _curlyLineDrawPeakHeight; + f32 _curlyLineDrawPeakHeight = 0; FontDecorationPosition _curlyUnderline; bool _requiresContinuousRedraw = false; From b8d3caaae06b23c66d75d227c8a145bf9c6d3187 Mon Sep 17 00:00:00 2001 From: Tushar Singh Date: Sat, 28 Oct 2023 21:26:43 +0530 Subject: [PATCH 17/24] send line rendition scale using texcoord --- src/renderer/atlas/BackendD3D.cpp | 23 +++++------ src/renderer/atlas/BackendD3D.h | 16 ++++---- src/renderer/atlas/shader_common.hlsl | 11 +++--- src/renderer/atlas/shader_ps.hlsl | 56 +++++++-------------------- src/renderer/atlas/shader_vs.hlsl | 7 +++- 5 files changed, 46 insertions(+), 67 deletions(-) diff --git a/src/renderer/atlas/BackendD3D.cpp b/src/renderer/atlas/BackendD3D.cpp index a2f08885b72..97f47a8df33 100644 --- a/src/renderer/atlas/BackendD3D.cpp +++ b/src/renderer/atlas/BackendD3D.cpp @@ -1683,10 +1683,6 @@ void BackendD3D::_drawGridlines(const RenderingPayload& p, u16 y) const auto verticalShift = static_cast(row->lineRendition >= LineRendition::DoubleHeightTop); const auto cellSize = p.s->font->cellSize; - const auto dottedLineType = horizontalShift ? ShadingType::DottedLineWide : ShadingType::DottedLine; - const auto dashedLineType = horizontalShift ? ShadingType::DashedLineWide : ShadingType::DashedLine; - const auto curlyLineType = horizontalShift ? ShadingType::CurlyLineWide : ShadingType::CurlyLine; - const auto rowTop = static_cast(cellSize.y * y); const auto rowBottom = static_cast(rowTop + cellSize.y); @@ -1735,6 +1731,7 @@ void BackendD3D::_drawGridlines(const RenderingPayload& p, u16 y) .shadingType = shadingType, .position = { left, static_cast(rt) }, .size = { width, static_cast(rb - rt) }, + .texcoord = { static_cast(1 << horizontalShift), static_cast(1 << verticalShift) }, .color = color, }; } @@ -1772,15 +1769,15 @@ void BackendD3D::_drawGridlines(const RenderingPayload& p, u16 y) } if (r.lines.any(GridLines::DottedUnderline, GridLines::HyperlinkUnderline)) { - appendHorizontalLine(r, p.s->font->underline, dottedLineType, r.underlineColor); + appendHorizontalLine(r, p.s->font->underline, ShadingType::DottedLine, r.underlineColor); } if (r.lines.test(GridLines::DashedUnderline)) { - appendHorizontalLine(r, p.s->font->underline, dashedLineType, r.underlineColor); + appendHorizontalLine(r, p.s->font->underline, ShadingType::DashedLine, r.underlineColor); } if (r.lines.test(GridLines::CurlyUnderline)) { - appendHorizontalLine(r, _curlyUnderline, curlyLineType, r.underlineColor); + appendHorizontalLine(r, _curlyUnderline, ShadingType::CurlyLine, r.underlineColor); } if (r.lines.test(GridLines::DoubleUnderline)) { @@ -2013,6 +2010,10 @@ size_t BackendD3D::_drawCursorForegroundSlowPath(const CursorRect& c, size_t off return 0; } + // Line drawing primitives requires texcoord to remain the same. Using this + // flag, we'll also avoid the need for branching in the calculations below. + const auto isStyledLineDrawing = static_cast(it.shadingType >= ShadingType::StyledLineDrawingFirst && it.shadingType <= ShadingType::StyledLineDrawingLast); + const int cursorL = c.position.x; const int cursorT = c.position.y; const int cursorR = cursorL + c.size.x; @@ -2092,8 +2093,8 @@ size_t BackendD3D::_drawCursorForegroundSlowPath(const CursorRect& c, size_t off target.position.y = static_cast(cutout.top); target.size.x = static_cast(cutout.right - cutout.left); target.size.y = static_cast(cutout.bottom - cutout.top); - target.texcoord.x = static_cast(it.texcoord.x + cutout.left - instanceL); - target.texcoord.y = static_cast(it.texcoord.y + cutout.top - instanceT); + target.texcoord.x = static_cast(it.texcoord.x + !isStyledLineDrawing * (cutout.left - instanceL)); + target.texcoord.y = static_cast(it.texcoord.y + !isStyledLineDrawing * (cutout.top - instanceT)); target.color = it.color; } @@ -2109,8 +2110,8 @@ size_t BackendD3D::_drawCursorForegroundSlowPath(const CursorRect& c, size_t off target.position.y = static_cast(intersectionT); target.size.x = static_cast(intersectionR - intersectionL); target.size.y = static_cast(intersectionB - intersectionT); - target.texcoord.x = static_cast(it.texcoord.x + intersectionL - instanceL); - target.texcoord.y = static_cast(it.texcoord.y + intersectionT - instanceT); + target.texcoord.x = static_cast(it.texcoord.x + !isStyledLineDrawing * (intersectionL - instanceL)); + target.texcoord.y = static_cast(it.texcoord.y + !isStyledLineDrawing * (intersectionT - instanceT)); target.color = color; return addedInstances; diff --git a/src/renderer/atlas/BackendD3D.h b/src/renderer/atlas/BackendD3D.h index dbac9a54445..9c421a00c56 100644 --- a/src/renderer/atlas/BackendD3D.h +++ b/src/renderer/atlas/BackendD3D.h @@ -68,20 +68,20 @@ namespace Microsoft::Console::Render::Atlas TextGrayscale = 1, TextClearType = 2, TextPassthrough = 3, + // Keep all styled line drawing primitives together. DottedLine = 4, - DottedLineWide = 5, - DashedLine = 6, - DashedLineWide = 7, - CurlyLine = 8, - CurlyLineWide = 9, + DashedLine = 5, + CurlyLine = 6, // All items starting here will be drawing as a solid RGBA color - SolidLine = 10, + SolidLine = 7, - Cursor = 11, - Selection = 12, + Cursor = 8, + Selection = 9, TextDrawingFirst = TextGrayscale, TextDrawingLast = SolidLine, + StyledLineDrawingFirst = DottedLine, + StyledLineDrawingLast = CurlyLine }; // NOTE: Don't initialize any members in this struct. This ensures that no diff --git a/src/renderer/atlas/shader_common.hlsl b/src/renderer/atlas/shader_common.hlsl index 5e384b5e8c8..b71907aba2f 100644 --- a/src/renderer/atlas/shader_common.hlsl +++ b/src/renderer/atlas/shader_common.hlsl @@ -6,12 +6,13 @@ #define SHADING_TYPE_TEXT_GRAYSCALE 1 #define SHADING_TYPE_TEXT_CLEARTYPE 2 #define SHADING_TYPE_TEXT_PASSTHROUGH 3 +#define SHADING_TYPE_TEXTURED_LAST SHADING_TYPE_TEXT_PASSTHROUGH +// Following types use texcoord as a +// non-interpolated value by sharing +// the same value across all vertices. #define SHADING_TYPE_DOTTED_LINE 4 -#define SHADING_TYPE_DOTTED_LINE_WIDE 5 -#define SHADING_TYPE_DASHED_LINE 6 -#define SHADING_TYPE_DASHED_LINE_WIDE 7 -#define SHADING_TYPE_CURLY_LINE 8 -#define SHADING_TYPE_CURLY_LINE_WIDE 9 +#define SHADING_TYPE_DASHED_LINE 5 +#define SHADING_TYPE_CURLY_LINE 6 // clang-format on struct VSData diff --git a/src/renderer/atlas/shader_ps.hlsl b/src/renderer/atlas/shader_ps.hlsl index 337bb56ba1e..4f7eb472310 100644 --- a/src/renderer/atlas/shader_ps.hlsl +++ b/src/renderer/atlas/shader_ps.hlsl @@ -33,6 +33,11 @@ Output main(PSData data) : SV_Target float4 color; float4 weights; + // When drawing (dotted/dashed/curly) lines, texcoord holds the line + // rendention scale (1x or 2x) which we use to draw wide/tall lines. + const uint horizontalScale = data.texcoord.x; + const uint verticalScale = data.texcoord.y; + switch (data.shadingType) { case SHADING_TYPE_TEXT_BACKGROUND: @@ -76,61 +81,28 @@ Output main(PSData data) : SV_Target } case SHADING_TYPE_DOTTED_LINE: { - const bool on = frac(data.position.x / (2.0f * underlineWidth)) < 0.5f; - color = on * premultiplyColor(data.color); - weights = color.aaaa; - break; - } - case SHADING_TYPE_DOTTED_LINE_WIDE: - { - const bool on = frac(data.position.x / (4.0f * underlineWidth)) < 0.5f; + const bool on = frac(data.position.x / (2.0f * underlineWidth * horizontalScale)) < 0.5f; color = on * premultiplyColor(data.color); weights = color.aaaa; break; } case SHADING_TYPE_DASHED_LINE: { - const bool on = frac(data.position.x / backgroundCellSize.x) < 0.5f; - color = on * premultiplyColor(data.color); - weights = color.aaaa; - break; - } - case SHADING_TYPE_DASHED_LINE_WIDE: - { - const bool on = frac(data.position.x / (2.0f * backgroundCellSize.x)) < 0.5f; + const bool on = frac(data.position.x / (backgroundCellSize.x * horizontalScale)) < 0.5f; color = on * premultiplyColor(data.color); weights = color.aaaa; break; } case SHADING_TYPE_CURLY_LINE: { - const int cellRow = floor(data.position.y / backgroundCellSize.y); + uint cellRow = floor(data.position.y / backgroundCellSize.y); + // Use the previous cell when drawing 'Double Height' curly line. + cellRow -= verticalScale - 1; const float cellTop = cellRow * backgroundCellSize.y; - const float centerY = cellTop + curlyLineCellOffset; - const float strokeWidthHalf = underlineWidth / 2.0f; - - // The wave begins with a negative peak. We phase shift the frequency by - // `Pi` to begin with a positive peak. - const float Pi = radians(180); - const float s = sin(data.position.x * curlyLineWaveFreq + Pi); - - const float d = abs(centerY + s * curlyLinePeakHeight - data.position.y); - const float a = 1 - saturate(d - strokeWidthHalf); - color = a * premultiplyColor(data.color); - weights = color.aaaa; - break; - } - case SHADING_TYPE_CURLY_LINE_WIDE: - { - const int prevCellRow = floor(data.position.y / backgroundCellSize.y) - 1; - const float prevCellTop = prevCellRow * backgroundCellSize.y; - - // In 'Wide' case, we need to draw the same wave on an area twice as big, - // and the position is relative to the previous cell. - const float centerY = prevCellTop + 2.0f * curlyLineCellOffset; - const float amp = curlyLinePeakHeight * 2.0f; - const float freq = curlyLineWaveFreq / 2.0f; - const float strokeWidthHalf = underlineWidth; + const float centerY = cellTop + curlyLineCellOffset * verticalScale; + const float strokeWidthHalf = underlineWidth * verticalScale / 2.0f; + const float amp = curlyLinePeakHeight * verticalScale; + const float freq = curlyLineWaveFreq / horizontalScale; // The wave begins with a negative peak. We phase shift the frequency by // `Pi` to begin with a positive peak. diff --git a/src/renderer/atlas/shader_vs.hlsl b/src/renderer/atlas/shader_vs.hlsl index 49b9030b156..daa7f31454c 100644 --- a/src/renderer/atlas/shader_vs.hlsl +++ b/src/renderer/atlas/shader_vs.hlsl @@ -19,6 +19,11 @@ PSData main(VSData data) // addition below this will transform our "position" from pixel into normalized device coordinate (NDC) space. output.position.xy = (data.position + data.vertex.xy * data.size) * positionScale + float2(-1.0f, 1.0f); output.position.zw = float2(0, 1); - output.texcoord = data.texcoord + data.vertex.xy * data.size; + output.texcoord = data.texcoord + + // `texcoord` needs to be non-interpolated for some items. + // E.g. `texcoord` holds the line rendition 'scale' when drawing + // styled lines, and we want all vertices to hold the same value + // so that 'scale' remains non-interpolated within pixel shader. + (data.shadingType <= SHADING_TYPE_TEXTURED_LAST) * data.vertex.xy * data.size; return output; } From a2c4a36e78971c4d7612e0077d1c0d5b66319146 Mon Sep 17 00:00:00 2001 From: Tushar Singh Date: Sat, 28 Oct 2023 21:50:53 +0530 Subject: [PATCH 18/24] underlines are mutually exclusive --- src/renderer/atlas/BackendD3D.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/renderer/atlas/BackendD3D.cpp b/src/renderer/atlas/BackendD3D.cpp index 97f47a8df33..4d860837be1 100644 --- a/src/renderer/atlas/BackendD3D.cpp +++ b/src/renderer/atlas/BackendD3D.cpp @@ -1767,19 +1767,19 @@ void BackendD3D::_drawGridlines(const RenderingPayload& p, u16 y) { appendHorizontalLine(r, p.s->font->underline, ShadingType::SolidLine, r.underlineColor); } - if (r.lines.any(GridLines::DottedUnderline, GridLines::HyperlinkUnderline)) + else if (r.lines.any(GridLines::DottedUnderline, GridLines::HyperlinkUnderline)) { appendHorizontalLine(r, p.s->font->underline, ShadingType::DottedLine, r.underlineColor); } - if (r.lines.test(GridLines::DashedUnderline)) + else if (r.lines.test(GridLines::DashedUnderline)) { appendHorizontalLine(r, p.s->font->underline, ShadingType::DashedLine, r.underlineColor); } - if (r.lines.test(GridLines::CurlyUnderline)) + else if (r.lines.test(GridLines::CurlyUnderline)) { appendHorizontalLine(r, _curlyUnderline, ShadingType::CurlyLine, r.underlineColor); } - if (r.lines.test(GridLines::DoubleUnderline)) + else if (r.lines.test(GridLines::DoubleUnderline)) { for (const auto pos : p.s->font->doubleUnderline) { From 6e4c65aa04d32d837d62949892ef7fe9c401cc8e Mon Sep 17 00:00:00 2001 From: Tushar Singh Date: Sat, 28 Oct 2023 23:33:16 +0530 Subject: [PATCH 19/24] revert underline thickness scaling --- src/renderer/atlas/AtlasEngine.api.cpp | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/renderer/atlas/AtlasEngine.api.cpp b/src/renderer/atlas/AtlasEngine.api.cpp index 8e0ec463e53..4459c97a8e5 100644 --- a/src/renderer/atlas/AtlasEngine.api.cpp +++ b/src/renderer/atlas/AtlasEngine.api.cpp @@ -686,19 +686,12 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo adjustedWidth = std::max(1.0f, adjustedWidth); adjustedHeight = std::max(1.0f, adjustedHeight); - // TODO: This helps in testing the underlines at different underline - // widths, particularly curly line, so they don't break if/when we get - // support for user customizable underline width. Remove it before merging. - // I'm using 1.5f as a hardcoded value below. In the future, we would - // get it from the user/config. - const auto underlineWidthScale = std::clamp(1.0f, 1.5f, 2.0f); - const auto baseline = std::roundf(ascent + (lineGap + adjustedHeight - advanceHeight) / 2.0f); const auto underlinePos = std::roundf(baseline + underlinePosition); - const auto underlineWidth = std::max(1.0f, std::roundf(underlineThickness * underlineWidthScale)); + const auto underlineWidth = std::max(1.0f, std::roundf(underlineThickness)); const auto strikethroughPos = std::roundf(baseline + strikethroughPosition); - const auto strikethroughWidth = std::max(1.0f, std::roundf(strikethroughThickness * underlineWidthScale)); - const auto thinLineWidth = std::max(1.0f, std::roundf(underlineThickness * underlineWidthScale / 2.0f)); + const auto strikethroughWidth = std::max(1.0f, std::roundf(strikethroughThickness)); + const auto thinLineWidth = std::max(1.0f, std::roundf(underlineThickness / 2.0f)); // For double underlines we loosely follow what Word does: // 1. The lines are half the width of an underline (= thinLineWidth) From b0be8b64413b49458d0febc142a631b02ee50091 Mon Sep 17 00:00:00 2001 From: Tushar Singh Date: Thu, 2 Nov 2023 19:43:47 +0530 Subject: [PATCH 20/24] move curlyline peak height constants into the function --- src/renderer/atlas/BackendD3D.cpp | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/renderer/atlas/BackendD3D.cpp b/src/renderer/atlas/BackendD3D.cpp index 4d860837be1..19a6df7f978 100644 --- a/src/renderer/atlas/BackendD3D.cpp +++ b/src/renderer/atlas/BackendD3D.cpp @@ -42,18 +42,6 @@ TIL_FAST_MATH_BEGIN using namespace Microsoft::Console::Render::Atlas; -namespace -{ - - // The max height of Curly line peak in `em` units. - constexpr auto MaxCurlyLinePeakHeightEm = 0.075f; - - // We aim for atleast 1px height, but since we draw 1px smaller curly line, - // we aim for 2px height as a result. - constexpr auto MinCurlyLinePeakHeight = 2.0f; - -} - template<> struct std::hash { @@ -317,6 +305,12 @@ void BackendD3D::_updateFontDependents(const RenderingPayload& p) { const auto& font = *p.s->font; + // The max height of Curly line peak in `em` units. + const auto maxCurlyLinePeakHeightEm = 0.075f; + // We aim for atleast 1px height, but since we draw 1px smaller curly line, + // we aim for 2px height as a result. + const auto minCurlyLinePeakHeight = 2.0f; + // Curlyline uses the gap between cell bottom and singly underline position // as the height of the wave's peak. The baseline for curly-line is at the // middle of singly underline. The gap could be too big, so we also apply @@ -324,11 +318,11 @@ void BackendD3D::_updateFontDependents(const RenderingPayload& p) const auto strokeHalfWidth = font.underline.height / 2.0f; const auto underlineMidY = font.underline.position + strokeHalfWidth; const auto cellBottomGap = font.cellSize.y - underlineMidY - strokeHalfWidth; - const auto maxCurlyLinePeakHeight = MaxCurlyLinePeakHeightEm * font.fontSize; + const auto maxCurlyLinePeakHeight = maxCurlyLinePeakHeightEm * font.fontSize; auto curlyLinePeakHeight = std::min(cellBottomGap, maxCurlyLinePeakHeight); // When it's too small to be curly, make it straight. - if (curlyLinePeakHeight < MinCurlyLinePeakHeight) + if (curlyLinePeakHeight < minCurlyLinePeakHeight) { curlyLinePeakHeight = 0; } From 1ba17822480fdff03397761a37ed0d77e83add75 Mon Sep 17 00:00:00 2001 From: Tushar Singh Date: Thu, 2 Nov 2023 20:19:05 +0530 Subject: [PATCH 21/24] fix curlyline equation that was making a negative peak at the start --- src/renderer/atlas/shader_ps.hlsl | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/renderer/atlas/shader_ps.hlsl b/src/renderer/atlas/shader_ps.hlsl index 4f7eb472310..6bd1759315d 100644 --- a/src/renderer/atlas/shader_ps.hlsl +++ b/src/renderer/atlas/shader_ps.hlsl @@ -104,12 +104,8 @@ Output main(PSData data) : SV_Target const float amp = curlyLinePeakHeight * verticalScale; const float freq = curlyLineWaveFreq / horizontalScale; - // The wave begins with a negative peak. We phase shift the frequency by - // `Pi` to begin with a positive peak. - const float Pi = radians(180); - const float s = sin(data.position.x * freq + Pi); - - const float d = abs(centerY + s * amp - data.position.y); + const float s = sin(data.position.x * freq); + const float d = abs(centerY - (s * amp) - data.position.y); const float a = 1 - saturate(d - strokeWidthHalf); color = a * premultiplyColor(data.color); weights = color.aaaa; From 4f7ba10596aa02135ce5578beea7db6038527018 Mon Sep 17 00:00:00 2001 From: Tushar Singh Date: Thu, 2 Nov 2023 21:06:00 +0530 Subject: [PATCH 22/24] BackendD2D: use `else if` --- src/renderer/atlas/BackendD2D.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/atlas/BackendD2D.cpp b/src/renderer/atlas/BackendD2D.cpp index 07713d0fb6f..ea4ea0923eb 100644 --- a/src/renderer/atlas/BackendD2D.cpp +++ b/src/renderer/atlas/BackendD2D.cpp @@ -463,11 +463,11 @@ void BackendD2D::_drawGridlineRow(const RenderingPayload& p, const ShapedRow* ro { appendHorizontalLine(r, p.s->font->underline, nullptr, r.underlineColor); } - if (r.lines.any(GridLines::DottedUnderline, GridLines::HyperlinkUnderline)) + else if (r.lines.any(GridLines::DottedUnderline, GridLines::HyperlinkUnderline)) { appendHorizontalLine(r, p.s->font->underline, _dottedStrokeStyle.get(), r.underlineColor); } - if (r.lines.test(GridLines::DoubleUnderline)) + else if (r.lines.test(GridLines::DoubleUnderline)) { for (const auto pos : p.s->font->doubleUnderline) { From c7a8a5580071166444edf94cb07b18f0abbe48eb Mon Sep 17 00:00:00 2001 From: Tushar Singh Date: Fri, 3 Nov 2023 21:12:14 +0530 Subject: [PATCH 23/24] shrink QuadInstance::ShadingType and add renditionScale member --- src/renderer/atlas/BackendD3D.cpp | 33 ++++++++++++++------------- src/renderer/atlas/BackendD3D.h | 15 ++++-------- src/renderer/atlas/common.h | 1 + src/renderer/atlas/shader_common.hlsl | 6 ++--- src/renderer/atlas/shader_ps.hlsl | 19 ++++++--------- src/renderer/atlas/shader_vs.hlsl | 8 ++----- 6 files changed, 33 insertions(+), 49 deletions(-) diff --git a/src/renderer/atlas/BackendD3D.cpp b/src/renderer/atlas/BackendD3D.cpp index 19a6df7f978..ce630dda716 100644 --- a/src/renderer/atlas/BackendD3D.cpp +++ b/src/renderer/atlas/BackendD3D.cpp @@ -90,7 +90,8 @@ BackendD3D::BackendD3D(const RenderingPayload& p) { static constexpr D3D11_INPUT_ELEMENT_DESC layout[]{ { "SV_Position", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }, - { "shadingType", 0, DXGI_FORMAT_R32_UINT, 1, offsetof(QuadInstance, shadingType), D3D11_INPUT_PER_INSTANCE_DATA, 1 }, + { "shadingType", 0, DXGI_FORMAT_R16_UINT, 1, offsetof(QuadInstance, shadingType), D3D11_INPUT_PER_INSTANCE_DATA, 1 }, + { "renditionScale", 0, DXGI_FORMAT_R8G8_UINT, 1, offsetof(QuadInstance, renditionScale), D3D11_INPUT_PER_INSTANCE_DATA, 1 }, { "position", 0, DXGI_FORMAT_R16G16_SINT, 1, offsetof(QuadInstance, position), D3D11_INPUT_PER_INSTANCE_DATA, 1 }, { "size", 0, DXGI_FORMAT_R16G16_UINT, 1, offsetof(QuadInstance, size), D3D11_INPUT_PER_INSTANCE_DATA, 1 }, { "texcoord", 0, DXGI_FORMAT_R16G16_UINT, 1, offsetof(QuadInstance, texcoord), D3D11_INPUT_PER_INSTANCE_DATA, 1 }, @@ -1054,7 +1055,7 @@ void BackendD3D::_drawText(RenderingPayload& p) goto drawGlyphRetry; } - if (glyphEntry.data.GetShadingType() != ShadingType::Default) + if (glyphEntry.data.shadingType != ShadingType::Default) { auto l = static_cast(lrintf((baselineX + row->glyphOffsets[x].advanceOffset) * scaleX)); auto t = static_cast(lrintf((baselineY - row->glyphOffsets[x].ascenderOffset) * scaleY)); @@ -1066,7 +1067,7 @@ void BackendD3D::_drawText(RenderingPayload& p) row->dirtyBottom = std::max(row->dirtyBottom, t + glyphEntry.data.size.y); _appendQuad() = { - .shadingType = glyphEntry.data.GetShadingType(), + .shadingType = glyphEntry.data.shadingType, .position = { static_cast(l), static_cast(t) }, .size = glyphEntry.data.size, .texcoord = glyphEntry.data.texcoord, @@ -1488,7 +1489,7 @@ bool BackendD3D::_drawGlyph(const RenderingPayload& p, const AtlasFontFaceEntryI const auto triggerRight = _ligatureOverhangTriggerRight * horizontalScale; const auto overlapSplit = rect.w >= p.s->font->cellSize.x && (bl <= triggerLeft || br >= triggerRight); - glyphEntry.data.shadingType = static_cast(isColorGlyph ? ShadingType::TextPassthrough : _textShadingType); + glyphEntry.data.shadingType = isColorGlyph ? ShadingType::TextPassthrough : _textShadingType; glyphEntry.data.overlapSplit = overlapSplit; glyphEntry.data.offset.x = bl; glyphEntry.data.offset.y = bt; @@ -1557,7 +1558,7 @@ bool BackendD3D::_drawSoftFontGlyph(const RenderingPayload& p, const AtlasFontFa _drawSoftFontGlyphInBitmap(p, glyphEntry); _d2dRenderTarget->DrawBitmap(_softFontBitmap.get(), &dest, 1, interpolation, nullptr, nullptr); - glyphEntry.data.shadingType = static_cast(ShadingType::TextGrayscale); + glyphEntry.data.shadingType = ShadingType::TextGrayscale; glyphEntry.data.overlapSplit = 0; glyphEntry.data.offset.x = 0; glyphEntry.data.offset.y = -baseline; @@ -1661,11 +1662,11 @@ void BackendD3D::_splitDoubleHeightGlyph(const RenderingPayload& p, const AtlasF // double-height row. This effectively turns the other (unneeded) side into whitespace. if (!top.data.size.y) { - top.data.shadingType = static_cast(ShadingType::Default); + top.data.shadingType = ShadingType::Default; } if (!bottom.data.size.y) { - bottom.data.shadingType = static_cast(ShadingType::Default); + bottom.data.shadingType = ShadingType::Default; } } @@ -1723,9 +1724,9 @@ void BackendD3D::_drawGridlines(const RenderingPayload& p, u16 y) { _appendQuad() = { .shadingType = shadingType, + .renditionScale = { static_cast(1 << horizontalShift), static_cast(1 << verticalShift) }, .position = { left, static_cast(rt) }, .size = { width, static_cast(rb - rt) }, - .texcoord = { static_cast(1 << horizontalShift), static_cast(1 << verticalShift) }, .color = color, }; } @@ -2004,10 +2005,6 @@ size_t BackendD3D::_drawCursorForegroundSlowPath(const CursorRect& c, size_t off return 0; } - // Line drawing primitives requires texcoord to remain the same. Using this - // flag, we'll also avoid the need for branching in the calculations below. - const auto isStyledLineDrawing = static_cast(it.shadingType >= ShadingType::StyledLineDrawingFirst && it.shadingType <= ShadingType::StyledLineDrawingLast); - const int cursorL = c.position.x; const int cursorT = c.position.y; const int cursorR = cursorL + c.size.x; @@ -2083,12 +2080,14 @@ size_t BackendD3D::_drawCursorForegroundSlowPath(const CursorRect& c, size_t off auto& target = _instances[offset + i]; target.shadingType = it.shadingType; + target.renditionScale.x = it.renditionScale.x; + target.renditionScale.y = it.renditionScale.y; target.position.x = static_cast(cutout.left); target.position.y = static_cast(cutout.top); target.size.x = static_cast(cutout.right - cutout.left); target.size.y = static_cast(cutout.bottom - cutout.top); - target.texcoord.x = static_cast(it.texcoord.x + !isStyledLineDrawing * (cutout.left - instanceL)); - target.texcoord.y = static_cast(it.texcoord.y + !isStyledLineDrawing * (cutout.top - instanceT)); + target.texcoord.x = static_cast(it.texcoord.x + cutout.left - instanceL); + target.texcoord.y = static_cast(it.texcoord.y + cutout.top - instanceT); target.color = it.color; } @@ -2100,12 +2099,14 @@ size_t BackendD3D::_drawCursorForegroundSlowPath(const CursorRect& c, size_t off auto& target = cutoutCount ? _appendQuad() : _instances[offset]; target.shadingType = it.shadingType; + target.renditionScale.x = it.renditionScale.x; + target.renditionScale.y = it.renditionScale.y; target.position.x = static_cast(intersectionL); target.position.y = static_cast(intersectionT); target.size.x = static_cast(intersectionR - intersectionL); target.size.y = static_cast(intersectionB - intersectionT); - target.texcoord.x = static_cast(it.texcoord.x + !isStyledLineDrawing * (intersectionL - instanceL)); - target.texcoord.y = static_cast(it.texcoord.y + !isStyledLineDrawing * (intersectionT - instanceT)); + target.texcoord.x = static_cast(it.texcoord.x + intersectionL - instanceL); + target.texcoord.y = static_cast(it.texcoord.y + intersectionT - instanceT); target.color = color; return addedInstances; diff --git a/src/renderer/atlas/BackendD3D.h b/src/renderer/atlas/BackendD3D.h index 9c421a00c56..0befe8fe342 100644 --- a/src/renderer/atlas/BackendD3D.h +++ b/src/renderer/atlas/BackendD3D.h @@ -58,7 +58,7 @@ namespace Microsoft::Console::Render::Atlas #pragma warning(suppress : 4324) // 'CustomConstBuffer': structure was padded due to alignment specifier }; - enum class ShadingType : u32 + enum class ShadingType : u16 { Default = 0, Background = 0, @@ -68,7 +68,6 @@ namespace Microsoft::Console::Render::Atlas TextGrayscale = 1, TextClearType = 2, TextPassthrough = 3, - // Keep all styled line drawing primitives together. DottedLine = 4, DashedLine = 5, CurlyLine = 6, @@ -80,8 +79,6 @@ namespace Microsoft::Console::Render::Atlas TextDrawingFirst = TextGrayscale, TextDrawingLast = SolidLine, - StyledLineDrawingFirst = DottedLine, - StyledLineDrawingLast = CurlyLine }; // NOTE: Don't initialize any members in this struct. This ensures that no @@ -93,7 +90,8 @@ namespace Microsoft::Console::Render::Atlas // impact on performance and power draw. If (when?) displays with >32k resolution make their // appearance in the future, this should be changed to f32x2. But if you do so, please change // all other occurrences of i16x2 positions/offsets throughout the class to keep it consistent. - alignas(u32) ShadingType shadingType; + alignas(u16) ShadingType shadingType; + alignas(u16) u8x2 renditionScale; alignas(u32) i16x2 position; alignas(u32) u16x2 size; alignas(u32) u16x2 texcoord; @@ -102,16 +100,11 @@ namespace Microsoft::Console::Render::Atlas struct alignas(u32) AtlasGlyphEntryData { - u16 shadingType; + ShadingType shadingType; u16 overlapSplit; i16x2 offset; u16x2 size; u16x2 texcoord; - - constexpr ShadingType GetShadingType() const noexcept - { - return static_cast(shadingType); - } }; // NOTE: Don't initialize any members in this struct. This ensures that no diff --git a/src/renderer/atlas/common.h b/src/renderer/atlas/common.h index 0ff0f01524e..7d3d0b982ed 100644 --- a/src/renderer/atlas/common.h +++ b/src/renderer/atlas/common.h @@ -125,6 +125,7 @@ namespace Microsoft::Console::Render::Atlas }; using u8 = uint8_t; + using u8x2 = vec2; using u16 = uint16_t; using u16x2 = vec2; diff --git a/src/renderer/atlas/shader_common.hlsl b/src/renderer/atlas/shader_common.hlsl index b71907aba2f..d712f081f28 100644 --- a/src/renderer/atlas/shader_common.hlsl +++ b/src/renderer/atlas/shader_common.hlsl @@ -6,10 +6,6 @@ #define SHADING_TYPE_TEXT_GRAYSCALE 1 #define SHADING_TYPE_TEXT_CLEARTYPE 2 #define SHADING_TYPE_TEXT_PASSTHROUGH 3 -#define SHADING_TYPE_TEXTURED_LAST SHADING_TYPE_TEXT_PASSTHROUGH -// Following types use texcoord as a -// non-interpolated value by sharing -// the same value across all vertices. #define SHADING_TYPE_DOTTED_LINE 4 #define SHADING_TYPE_DASHED_LINE 5 #define SHADING_TYPE_CURLY_LINE 6 @@ -19,6 +15,7 @@ struct VSData { float2 vertex : SV_Position; uint shadingType : shadingType; + uint2 renditionScale : renditionScale; int2 position : position; uint2 size : size; uint2 texcoord : texcoord; @@ -30,6 +27,7 @@ struct PSData float4 position : SV_Position; float2 texcoord : texcoord; nointerpolation uint shadingType : shadingType; + nointerpolation uint2 renditionScale : renditionScale; nointerpolation float4 color : color; }; diff --git a/src/renderer/atlas/shader_ps.hlsl b/src/renderer/atlas/shader_ps.hlsl index 6bd1759315d..e19ba955fe5 100644 --- a/src/renderer/atlas/shader_ps.hlsl +++ b/src/renderer/atlas/shader_ps.hlsl @@ -33,11 +33,6 @@ Output main(PSData data) : SV_Target float4 color; float4 weights; - // When drawing (dotted/dashed/curly) lines, texcoord holds the line - // rendention scale (1x or 2x) which we use to draw wide/tall lines. - const uint horizontalScale = data.texcoord.x; - const uint verticalScale = data.texcoord.y; - switch (data.shadingType) { case SHADING_TYPE_TEXT_BACKGROUND: @@ -81,14 +76,14 @@ Output main(PSData data) : SV_Target } case SHADING_TYPE_DOTTED_LINE: { - const bool on = frac(data.position.x / (2.0f * underlineWidth * horizontalScale)) < 0.5f; + const bool on = frac(data.position.x / (2.0f * underlineWidth * data.renditionScale.x)) < 0.5f; color = on * premultiplyColor(data.color); weights = color.aaaa; break; } case SHADING_TYPE_DASHED_LINE: { - const bool on = frac(data.position.x / (backgroundCellSize.x * horizontalScale)) < 0.5f; + const bool on = frac(data.position.x / (backgroundCellSize.x * data.renditionScale.x)) < 0.5f; color = on * premultiplyColor(data.color); weights = color.aaaa; break; @@ -97,12 +92,12 @@ Output main(PSData data) : SV_Target { uint cellRow = floor(data.position.y / backgroundCellSize.y); // Use the previous cell when drawing 'Double Height' curly line. - cellRow -= verticalScale - 1; + cellRow -= data.renditionScale.y - 1; const float cellTop = cellRow * backgroundCellSize.y; - const float centerY = cellTop + curlyLineCellOffset * verticalScale; - const float strokeWidthHalf = underlineWidth * verticalScale / 2.0f; - const float amp = curlyLinePeakHeight * verticalScale; - const float freq = curlyLineWaveFreq / horizontalScale; + const float centerY = cellTop + curlyLineCellOffset * data.renditionScale.y; + const float strokeWidthHalf = underlineWidth * data.renditionScale.y / 2.0f; + const float amp = curlyLinePeakHeight * data.renditionScale.y; + const float freq = curlyLineWaveFreq / data.renditionScale.x; const float s = sin(data.position.x * freq); const float d = abs(centerY - (s * amp) - data.position.y); diff --git a/src/renderer/atlas/shader_vs.hlsl b/src/renderer/atlas/shader_vs.hlsl index daa7f31454c..eb96fcf0e45 100644 --- a/src/renderer/atlas/shader_vs.hlsl +++ b/src/renderer/atlas/shader_vs.hlsl @@ -15,15 +15,11 @@ PSData main(VSData data) PSData output; output.color = data.color; output.shadingType = data.shadingType; + output.renditionScale = data.renditionScale; // positionScale is expected to be float2(2.0f / sizeInPixel.x, -2.0f / sizeInPixel.y). Together with the // addition below this will transform our "position" from pixel into normalized device coordinate (NDC) space. output.position.xy = (data.position + data.vertex.xy * data.size) * positionScale + float2(-1.0f, 1.0f); output.position.zw = float2(0, 1); - output.texcoord = data.texcoord + - // `texcoord` needs to be non-interpolated for some items. - // E.g. `texcoord` holds the line rendition 'scale' when drawing - // styled lines, and we want all vertices to hold the same value - // so that 'scale' remains non-interpolated within pixel shader. - (data.shadingType <= SHADING_TYPE_TEXTURED_LAST) * data.vertex.xy * data.size; + output.texcoord = data.texcoord + data.vertex.xy * data.size; return output; } From 3923fdee3aaabd8e268f44f7df924e30f9c60305 Mon Sep 17 00:00:00 2001 From: Tushar Singh Date: Sat, 4 Nov 2023 20:27:52 +0530 Subject: [PATCH 24/24] replace SelectObject with wil::SelectObject --- src/renderer/gdi/paint.cpp | 48 ++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/src/renderer/gdi/paint.cpp b/src/renderer/gdi/paint.cpp index cea863e22b9..9722703d105 100644 --- a/src/renderer/gdi/paint.cpp +++ b/src/renderer/gdi/paint.cpp @@ -521,30 +521,12 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) // Convert the target from characters to pixels. const auto ptTarget = coordTarget * _GetFontSize(); - // Set the brush color to the gridline color and save the previous brush to restore at the end. + + // Create a brush with the gridline color, and apply it. wil::unique_hbrush hbr(CreateSolidBrush(gridlineColor)); RETURN_HR_IF_NULL(E_FAIL, hbr.get()); - - // TODO: hbrPrev shouldn't be managed. - wil::unique_hbrush hbrPrev(SelectBrush(_hdcMemoryContext, hbr.get())); - RETURN_HR_IF_NULL(E_FAIL, hbrPrev.get()); - hbr.release(); // If SelectBrush was successful, GDI owns the brush. Release for now. - - // On exit, be sure we try to put the brush back how it was originally. - auto restoreBrushOnExit = wil::scope_exit([&] { hbr.reset(SelectBrush(_hdcMemoryContext, hbrPrev.get())); }); - - // Create a pen matching the underline style. - DWORD underlinePenType = PS_SOLID; - if (lines.test(GridLines::DottedUnderline)) - { - underlinePenType = PS_DOT; - } - else if (lines.test(GridLines::DashedUnderline)) - { - underlinePenType = PS_DASH; - } - const LOGBRUSH brushProp{ .lbStyle = BS_SOLID, .lbColor = underlineColor }; - wil::unique_hpen hpen(ExtCreatePen(underlinePenType | PS_GEOMETRIC | PS_ENDCAP_FLAT, _lineMetrics.underlineWidth, &brushProp, 0, nullptr)); + const auto prevBrush = wil::SelectObject(_hdcMemoryContext, hbr.get()); + RETURN_HR_IF_NULL(E_FAIL, prevBrush.get()); // Get the font size so we know the size of the rectangle lines we'll be inscribing. const auto fontWidth = _GetFontSize().width; @@ -620,12 +602,22 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) RETURN_HR_IF(E_FAIL, !DrawLine(ptTarget.x, y, widthOfAllCells, _lineMetrics.strikethroughWidth)); } - // Apply the pen for underline drawing. - const auto prevPen = SelectPen(_hdcMemoryContext, hpen.get()); - RETURN_HR_IF_NULL(E_FAIL, prevPen); - // Release the pen since GDI owns it now. - hpen.release(); - const auto restorePenOnExit = wil::scope_exit([&] { hpen.reset(SelectPen(_hdcMemoryContext, prevPen)); }); + // Create a pen matching the underline style. + DWORD underlinePenType = PS_SOLID; + if (lines.test(GridLines::DottedUnderline)) + { + underlinePenType = PS_DOT; + } + else if (lines.test(GridLines::DashedUnderline)) + { + underlinePenType = PS_DASH; + } + const LOGBRUSH brushProp{ .lbStyle = BS_SOLID, .lbColor = underlineColor }; + wil::unique_hpen hpen(ExtCreatePen(underlinePenType | PS_GEOMETRIC | PS_ENDCAP_FLAT, _lineMetrics.underlineWidth, &brushProp, 0, nullptr)); + + // Apply the pen. + const auto prevPen = wil::SelectObject(_hdcMemoryContext, hpen.get()); + RETURN_HR_IF_NULL(E_FAIL, prevPen.get()); const auto underlineMidY = std::lround(ptTarget.y + _lineMetrics.underlineOffset + _lineMetrics.underlineWidth / 2.0f); if (lines.test(GridLines::Underline))