From cc8afea079ed90a9f7ddd3f2f9f9a78300f359a9 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 11 Nov 2021 18:57:46 +0100 Subject: [PATCH] Fixed race conditions, Fixed font scaling at >100%, Implemented underline/strikethrough --- src/renderer/atlas/AtlasEngine.api.cpp | 150 ++++++++++++------------- src/renderer/atlas/AtlasEngine.cpp | 57 ++++++---- src/renderer/atlas/AtlasEngine.h | 35 +++--- src/renderer/atlas/AtlasEngine.r.cpp | 6 +- src/renderer/atlas/shader_ps.hlsl | 33 ++++-- 5 files changed, 154 insertions(+), 127 deletions(-) diff --git a/src/renderer/atlas/AtlasEngine.api.cpp b/src/renderer/atlas/AtlasEngine.api.cpp index 70b6bcf9605..159b2b51eaf 100644 --- a/src/renderer/atlas/AtlasEngine.api.cpp +++ b/src/renderer/atlas/AtlasEngine.api.cpp @@ -68,8 +68,8 @@ constexpr HRESULT vec2_narrow(U x, U y, AtlasEngine::vec2& out) noexcept [[nodiscard]] HRESULT AtlasEngine::InvalidateSystem(const RECT* const prcDirtyClient) noexcept { - const auto top = prcDirtyClient->top / _api.cellSize.y; - const auto bottom = prcDirtyClient->bottom / _api.cellSize.y; + const auto top = prcDirtyClient->top / _api.fontMetrics.cellSize.y; + const auto bottom = prcDirtyClient->bottom / _api.fontMetrics.cellSize.y; // BeginPaint() protects against invalid out of bounds numbers. SMALL_RECT rect; @@ -227,17 +227,7 @@ try } #endif - const wchar_t* requestedFaceName = fontInfoDesired.GetFaceName().c_str(); - const auto requestedFamily = fontInfoDesired.GetFamily(); - const auto requestedWeight = fontInfoDesired.GetWeight(); - const auto requestedSize = fontInfoDesired.GetEngineSize(); - const auto metrics = _getFontMetrics(requestedFaceName, requestedSize.Y, static_cast(requestedWeight)); - - COORD resultingCellSize; - resultingCellSize.X = gsl::narrow(metrics.cellSize.x); - resultingCellSize.Y = gsl::narrow(metrics.cellSize.y); - - fontInfo.SetFromEngine(requestedFaceName, requestedFamily, requestedWeight, false, resultingCellSize, requestedSize); + _resolveFontMetrics(fontInfoDesired, fontInfo); return S_OK; } CATCH_RETURN() @@ -251,8 +241,8 @@ CATCH_RETURN() [[nodiscard]] HRESULT AtlasEngine::GetFontSize(_Out_ COORD* const pFontSize) noexcept { RETURN_HR_IF_NULL(E_INVALIDARG, pFontSize); - pFontSize->X = gsl::narrow_cast(_api.cellSize.x); - pFontSize->Y = gsl::narrow_cast(_api.cellSize.y); + pFontSize->X = gsl::narrow_cast(_api.fontMetrics.cellSize.x); + pFontSize->Y = gsl::narrow_cast(_api.fontMetrics.cellSize.y); return S_OK; } @@ -266,7 +256,7 @@ CATCH_RETURN() DWRITE_TEXT_METRICS metrics; RETURN_IF_FAILED(textLayout->GetMetrics(&metrics)); - *pResult = static_cast(std::ceil(metrics.width)) > _api.cellSize.x; + *pResult = static_cast(std::ceil(metrics.width)) > _api.fontMetrics.cellSize.x; return S_OK; } @@ -307,16 +297,16 @@ HRESULT AtlasEngine::Enable() noexcept [[nodiscard]] Microsoft::Console::Types::Viewport AtlasEngine::GetViewportInCharacters(const Types::Viewport& viewInPixels) const noexcept { - assert(_api.cellSize.x != 0); - assert(_api.cellSize.y != 0); - return Types::Viewport::FromDimensions(viewInPixels.Origin(), COORD{ gsl::narrow_cast(viewInPixels.Width() / _api.cellSize.x), gsl::narrow_cast(viewInPixels.Height() / _api.cellSize.y) }); + assert(_api.fontMetrics.cellSize.x != 0); + assert(_api.fontMetrics.cellSize.y != 0); + return Types::Viewport::FromDimensions(viewInPixels.Origin(), COORD{ gsl::narrow_cast(viewInPixels.Width() / _api.fontMetrics.cellSize.x), gsl::narrow_cast(viewInPixels.Height() / _api.fontMetrics.cellSize.y) }); } [[nodiscard]] Microsoft::Console::Types::Viewport AtlasEngine::GetViewportInPixels(const Types::Viewport& viewInCharacters) const noexcept { - assert(_api.cellSize.x != 0); - assert(_api.cellSize.y != 0); - return Types::Viewport::FromDimensions(viewInCharacters.Origin(), COORD{ gsl::narrow_cast(viewInCharacters.Width() * _api.cellSize.x), gsl::narrow_cast(viewInCharacters.Height() * _api.cellSize.y) }); + assert(_api.fontMetrics.cellSize.x != 0); + assert(_api.fontMetrics.cellSize.y != 0); + return Types::Viewport::FromDimensions(viewInCharacters.Origin(), COORD{ gsl::narrow_cast(viewInCharacters.Width() * _api.fontMetrics.cellSize.x), gsl::narrow_cast(viewInCharacters.Height() * _api.fontMetrics.cellSize.y) }); } void AtlasEngine::SetAntialiasingMode(const D2D1_TEXT_ANTIALIAS_MODE antialiasingMode) noexcept @@ -400,7 +390,7 @@ void AtlasEngine::SetWarningCallback(std::function pfn) noexcept if (_api.sizeInPixel != newSize && newSize != u16x2{}) { _api.sizeInPixel = newSize; - _api.cellCount = _api.sizeInPixel / _api.cellSize; + _api.cellCount = _api.sizeInPixel / _api.fontMetrics.cellSize; WI_SetFlag(_api.invalidations, ApiInvalidations::Size); } @@ -414,20 +404,6 @@ void AtlasEngine::ToggleShaderEffects() noexcept [[nodiscard]] HRESULT AtlasEngine::UpdateFont(const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, const std::unordered_map& features, const std::unordered_map& axes) noexcept try { - // vvvv This block of code is a copy from GetProposedFont(). - const wchar_t* requestedFaceName = fontInfoDesired.GetFaceName().c_str(); - const auto requestedFamily = fontInfoDesired.GetFamily(); - const auto requestedWeight = fontInfoDesired.GetWeight(); - const auto requestedSize = fontInfoDesired.GetEngineSize(); - const auto metrics = _getFontMetrics(requestedFaceName, requestedSize.Y, static_cast(requestedWeight)); - - COORD resultingCellSize; - resultingCellSize.X = gsl::narrow(metrics.cellSize.x); - resultingCellSize.Y = gsl::narrow(metrics.cellSize.y); - - fontInfo.SetFromEngine(requestedFaceName, requestedFamily, requestedWeight, false, resultingCellSize, requestedSize); - // ^^^^ This block of code is a copy from GetProposedFont(). - std::vector fontFeatures; if (!features.empty()) { @@ -502,25 +478,14 @@ try } } - const auto& faceName = fontInfo.GetFaceName(); - auto fontName = wil::make_process_heap_string(faceName.data(), faceName.size()); - RETURN_IF_NULL_ALLOC(fontName); - - // NOTE: From this point onward no early returns or throwing code should exist, - // as we might cause _api to be in an inconsistent state otherwise. + const auto previousCellSize = _api.fontMetrics.cellSize; + _resolveFontMetrics(fontInfoDesired, fontInfo, &_api.fontMetrics); - _api.fontFeatures = std::move(fontFeatures); - _api.fontAxisValues = std::move(fontAxisValues); - _api.fontName = std::move(fontName); - _api.baselineInDIP = metrics.baselineInDIP; - _api.fontSizeInDIP = metrics.fontSizeInDIP; - _api.fontWeight = gsl::narrow_cast(fontInfo.GetWeight()); WI_SetFlag(_api.invalidations, ApiInvalidations::Font); - if (metrics.cellSize != _api.cellSize) + if (previousCellSize != _api.fontMetrics.cellSize) { - _api.cellSize = metrics.cellSize; - _api.cellCount = _api.sizeInPixel / metrics.cellSize; + _api.cellCount = _api.sizeInPixel / _api.fontMetrics.cellSize; WI_SetFlag(_api.invalidations, ApiInvalidations::Size); } @@ -534,19 +499,24 @@ void AtlasEngine::UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept #pragma endregion -AtlasEngine::FontMetrics AtlasEngine::_getFontMetrics(const wchar_t* faceName, double fontSize, DWRITE_FONT_WEIGHT weight) +void AtlasEngine::_resolveFontMetrics(const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, FontMetrics* fontMetrics) const { - if (!faceName) + auto requestedFaceName = fontInfoDesired.GetFaceName().c_str(); + const auto requestedFamily = fontInfoDesired.GetFamily(); + auto requestedWeight = fontInfoDesired.GetWeight(); + auto requestedSize = fontInfoDesired.GetEngineSize(); + + if (!requestedFaceName) { - faceName = L"Consolas"; + requestedFaceName = L"Consolas"; } - if (!fontSize) + if (!requestedSize.Y) { - fontSize = 12.0f; + requestedSize = { 0, 12 }; } - if (!weight) + if (!requestedWeight) { - weight = DWRITE_FONT_WEIGHT_NORMAL; + requestedWeight = DWRITE_FONT_WEIGHT_NORMAL; } wil::com_ptr systemFontCollection; @@ -554,21 +524,20 @@ AtlasEngine::FontMetrics AtlasEngine::_getFontMetrics(const wchar_t* faceName, d u32 index = 0; BOOL exists = false; - THROW_IF_FAILED(systemFontCollection->FindFamilyName(faceName, &index, &exists)); + THROW_IF_FAILED(systemFontCollection->FindFamilyName(requestedFaceName, &index, &exists)); THROW_HR_IF(DWRITE_E_NOFONT, !exists); wil::com_ptr fontFamily; THROW_IF_FAILED(systemFontCollection->GetFontFamily(index, fontFamily.addressof())); wil::com_ptr font; - THROW_IF_FAILED(fontFamily->GetFirstMatchingFont(weight, DWRITE_FONT_STRETCH_NORMAL, DWRITE_FONT_STYLE_NORMAL, font.addressof())); + THROW_IF_FAILED(fontFamily->GetFirstMatchingFont(static_cast(requestedWeight), DWRITE_FONT_STRETCH_NORMAL, DWRITE_FONT_STYLE_NORMAL, font.addressof())); wil::com_ptr fontFace; THROW_IF_FAILED(font->CreateFontFace(fontFace.addressof())); -#pragma warning(suppress : 26494) // Variable 'fontMetrics' is uninitialized. Always initialize an object (type.5). - DWRITE_FONT_METRICS1 fontMetrics; - fontFace->GetMetrics(&fontMetrics); + DWRITE_FONT_METRICS metrics; + fontFace->GetMetrics(&metrics); static constexpr u32 codePoint = L'M'; u16 glyphIndex; @@ -580,22 +549,49 @@ AtlasEngine::FontMetrics AtlasEngine::_getFontMetrics(const wchar_t* faceName, d // Point sizes are commonly treated at a 72 DPI scale // (including by OpenType), whereas DirectWrite uses 96 DPI. // Since we want the height in px we multiply by the display's DPI. - const auto fontSizeInPx = std::ceil(fontSize / 72.0 * static_cast(_api.dpi)); + const auto fontSizeInPx = std::ceil(requestedSize.Y / 72.0 * _api.dpi); - const auto designUnitsPerPx = fontSizeInPx / static_cast(fontMetrics.designUnitsPerEm); - const auto ascentInPx = static_cast(fontMetrics.ascent) * designUnitsPerPx; - const auto descentInPx = static_cast(fontMetrics.descent) * designUnitsPerPx; - const auto lineGapInPx = static_cast(fontMetrics.lineGap) * designUnitsPerPx; + const auto designUnitsPerPx = fontSizeInPx / static_cast(metrics.designUnitsPerEm); + const auto ascentInPx = static_cast(metrics.ascent) * designUnitsPerPx; + const auto descentInPx = static_cast(metrics.descent) * designUnitsPerPx; + const auto lineGapInPx = static_cast(metrics.lineGap) * designUnitsPerPx; const auto advanceWidthInPx = static_cast(glyphMetrics.advanceWidth) * designUnitsPerPx; const auto halfGapInPx = lineGapInPx / 2.0; const auto baseline = std::ceil(ascentInPx + halfGapInPx); - const auto cellHeight = gsl::narrow(std::ceil((baseline + descentInPx + halfGapInPx))); - const auto cellWidth = gsl::narrow(std::round(advanceWidthInPx)); - - return { - { cellWidth, cellHeight }, - static_cast(fontSizeInPx / 96.0 * static_cast(_api.dpi)), - static_cast(baseline / 96.0 * static_cast(_api.dpi)), - }; + const auto cellWidth = gsl::narrow(std::ceil(advanceWidthInPx)); + const auto cellHeight = gsl::narrow(std::ceil(baseline + descentInPx + halfGapInPx)); + + { + COORD resultingCellSize; + resultingCellSize.X = gsl::narrow(cellWidth); + resultingCellSize.Y = gsl::narrow(cellHeight); + fontInfo.SetFromEngine(requestedFaceName, requestedFamily, requestedWeight, false, resultingCellSize, requestedSize); + } + + if (fontMetrics) + { + const auto underlineOffsetInPx = static_cast(-metrics.underlinePosition) * designUnitsPerPx; + const auto underlineThicknessInPx = static_cast(metrics.underlineThickness) * designUnitsPerPx; + const auto strikethroughOffsetInPx = static_cast(-metrics.strikethroughPosition) * designUnitsPerPx; + const auto strikethroughThicknessInPx = static_cast(metrics.strikethroughThickness) * designUnitsPerPx; + const auto lineThickness = gsl::narrow(std::round(std::min(underlineThicknessInPx, strikethroughThicknessInPx))); + const auto underlinePos = gsl::narrow(std::round(baseline + underlineOffsetInPx - lineThickness / 2.0)); + const auto strikethroughPos = gsl::narrow(std::round(baseline + strikethroughOffsetInPx - lineThickness / 2.0)); + + auto fontName = wil::make_process_heap_string(requestedFaceName); + const auto fontWeight = gsl::narrow(requestedWeight); + + // NOTE: From this point onward no early returns or throwing code should exist, + // as we might cause _api to be in an inconsistent state otherwise. + + fontMetrics->fontName = std::move(fontName); + fontMetrics->fontSizeInDIP = static_cast(fontSizeInPx / static_cast(_api.dpi) * 96.0); + fontMetrics->baselineInDIP = static_cast(baseline / static_cast(_api.dpi) * 96.0); + fontMetrics->cellSize = { cellWidth, cellHeight }; + fontMetrics->fontWeight = fontWeight; + fontMetrics->underlinePos = underlinePos; + fontMetrics->strikethroughPos = strikethroughPos; + fontMetrics->lineThickness = lineThickness; + } } diff --git a/src/renderer/atlas/AtlasEngine.cpp b/src/renderer/atlas/AtlasEngine.cpp index 88807897077..b2636dda3b8 100644 --- a/src/renderer/atlas/AtlasEngine.cpp +++ b/src/renderer/atlas/AtlasEngine.cpp @@ -228,7 +228,7 @@ try // But most of the time _invalidations will be ::none, making this very cheap. if (_api.invalidations != ApiInvalidations::None) { - RETURN_HR_IF(E_UNEXPECTED, _api.sizeInPixel == u16x2{} || _api.cellSize == u16x2{} || _api.cellCount == u16x2{}); + RETURN_HR_IF(E_UNEXPECTED, _api.cellCount == u16x2{}); if (WI_IsFlagSet(_api.invalidations, ApiInvalidations::Device)) { @@ -577,8 +577,8 @@ try WI_SetFlagIf(flags, CellFlags::UnderlineDouble, textAttributes.IsDoublyUnderlined()); WI_SetFlagIf(flags, CellFlags::Strikethrough, textAttributes.IsCrossedOut()); - u32x2 newColors{ gsl::narrow_cast(fg | 0xff000000), gsl::narrow_cast(bg | _api.backgroundOpaqueMixin) }; - AtlasKeyAttributes attributes{ 0, textAttributes.IsBold(), textAttributes.IsItalic(), 0 }; + const u32x2 newColors{ gsl::narrow_cast(fg | 0xff000000), gsl::narrow_cast(bg | _api.backgroundOpaqueMixin) }; + const AtlasKeyAttributes attributes{ 0, textAttributes.IsBold(), textAttributes.IsItalic(), 0 }; if (_api.attributes != attributes) { @@ -627,6 +627,7 @@ CATCH_RETURN() void AtlasEngine::_createResources() { + _releaseSwapChain(); _r = {}; #ifdef NDEBUG @@ -731,17 +732,26 @@ void AtlasEngine::_createResources() WI_SetAllFlags(_api.invalidations, ApiInvalidations::SwapChain | ApiInvalidations::Font); } -void AtlasEngine::_createSwapChain() +void AtlasEngine::_releaseSwapChain() { - // ResizeBuffer() docs: - // Before you call ResizeBuffers, ensure that the application releases all references [...]. - // You can use ID3D11DeviceContext::ClearState to ensure that all [internal] references are released. - if (_r.swapChain) + // Flush() docs: + // However, if an application must actually destroy an old swap chain and create a new swap chain, + // the application must force the destruction of all objects that the application freed. + // To force the destruction, call ID3D11DeviceContext::ClearState (or otherwise ensure + // no views are bound to pipeline state), and then call Flush on the immediate context. + if (_r.swapChain && _r.deviceContext) { + _r.frameLatencyWaitableObject.reset(); + _r.swapChain.reset(); _r.renderTargetView.reset(); _r.deviceContext->ClearState(); _r.deviceContext->Flush(); } +} + +void AtlasEngine::_createSwapChain() +{ + _releaseSwapChain(); // D3D swap chain setup (the thing that allows us to present frames on the screen) { @@ -805,8 +815,6 @@ void AtlasEngine::_createSwapChain() // See documentation for IDXGISwapChain2::GetFrameLatencyWaitableObject method: // > For every frame it renders, the app should wait on this handle before starting any rendering operations. // > Note that this requirement includes the first frame the app renders with the swap chain. - // - // TODO: In the future all D3D code should be moved into AtlasEngine.r.cpp WaitUntilCanRender(); if (_api.swapChainChangedCallback) @@ -925,8 +933,8 @@ void AtlasEngine::_recreateFontDependentResources() static constexpr size_t sizePerPixel = 4; static constexpr size_t sizeLimit = D3D10_REQ_RESOURCE_SIZE_IN_MEGABYTES * 1024 * 1024; const size_t dimensionLimit = _r.device->GetFeatureLevel() >= D3D_FEATURE_LEVEL_11_0 ? D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION : D3D10_REQ_TEXTURE2D_U_OR_V_DIMENSION; - const size_t csx = _api.cellSize.x; - const size_t csy = _api.cellSize.y; + const size_t csx = _api.fontMetrics.cellSize.x; + const size_t csy = _api.fontMetrics.cellSize.y; const auto xLimit = (dimensionLimit / csx) * csx; const auto pixelsPerCellRow = xLimit * csy; const auto yLimitDueToDimension = (dimensionLimit / csy) * csy; @@ -934,16 +942,16 @@ void AtlasEngine::_recreateFontDependentResources() const auto yLimit = std::min(yLimitDueToDimension, yLimitDueToSize); const auto scaling = GetScaling(); - _r.cellSizeDIP.x = static_cast(_api.cellSize.x) / scaling; - _r.cellSizeDIP.y = static_cast(_api.cellSize.y) / scaling; - _r.cellSize = _api.cellSize; + _r.cellSizeDIP.x = static_cast(_api.fontMetrics.cellSize.x) / scaling; + _r.cellSizeDIP.y = static_cast(_api.fontMetrics.cellSize.y) / scaling; + _r.cellSize = _api.fontMetrics.cellSize; _r.cellCount = _api.cellCount; // x/yLimit are strictly smaller than dimensionLimit, which is smaller than a u16. _r.atlasSizeInPixelLimit = u16x2{ gsl::narrow_cast(xLimit), gsl::narrow_cast(yLimit) }; _r.atlasSizeInPixel = { 0, 0 }; // The first Cell at {0, 0} is always our cursor texture. // --> The first glyph starts at {1, 0}. - _r.atlasPosition.x = _api.cellSize.x; + _r.atlasPosition.x = _api.fontMetrics.cellSize.x; _r.atlasPosition.y = 0; _r.glyphs = {}; @@ -966,6 +974,9 @@ void AtlasEngine::_recreateFontDependentResources() // D2D { + _r.underlinePos = _api.fontMetrics.underlinePos; + _r.strikethroughPos = _api.fontMetrics.strikethroughPos; + _r.lineThickness = _api.fontMetrics.lineThickness; _r.dpi = _api.dpi; _r.maxEncounteredCellCount = 0; _r.scratchpadCellWidth = 0; @@ -996,11 +1007,11 @@ void AtlasEngine::_recreateFontDependentResources() { for (auto bold = 0; bold < 2; ++bold) { - const auto fontWeight = bold ? DWRITE_FONT_WEIGHT_BOLD : static_cast(_api.fontWeight); + const auto fontWeight = bold ? DWRITE_FONT_WEIGHT_BOLD : static_cast(_api.fontMetrics.fontWeight); const auto fontStyle = italic ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL; auto& textFormat = _r.textFormats[italic][bold]; - THROW_IF_FAILED(_sr.dwriteFactory->CreateTextFormat(_api.fontName.get(), nullptr, fontWeight, fontStyle, DWRITE_FONT_STRETCH_NORMAL, _api.fontSizeInDIP, L"", textFormat.put())); + THROW_IF_FAILED(_sr.dwriteFactory->CreateTextFormat(_api.fontMetrics.fontName.get(), nullptr, fontWeight, fontStyle, DWRITE_FONT_STRETCH_NORMAL, _api.fontMetrics.fontSizeInDIP, L"", textFormat.put())); textFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER); textFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP); @@ -1012,7 +1023,7 @@ void AtlasEngine::_recreateFontDependentResources() // "baseline" parameter: // > Distance from top of line to baseline. A reasonable ratio to lineSpacing is 80%. // So... let's set it to 80%. - THROW_IF_FAILED(textFormat->SetLineSpacing(DWRITE_LINE_SPACING_METHOD_UNIFORM, _r.cellSizeDIP.y, _api.baselineInDIP)); + THROW_IF_FAILED(textFormat->SetLineSpacing(DWRITE_LINE_SPACING_METHOD_UNIFORM, _r.cellSizeDIP.y, _api.fontMetrics.baselineInDIP)); if (!_api.fontAxisValues.empty()) { @@ -1186,7 +1197,7 @@ void AtlasEngine::_flushBufferLine() /* textPosition */ idx, /* textLength */ gsl::narrow_cast(_api.bufferLine.size()) - idx, /* baseFontCollection */ fontCollection.get(), - /* baseFamilyName */ _api.fontName.get(), + /* baseFamilyName */ _api.fontMetrics.fontName.get(), /* fontAxisValues */ textFormatAxis.data(), /* fontAxisValueCount */ gsl::narrow_cast(textFormatAxis.size()), /* mappedLength */ &mappedLength, @@ -1196,7 +1207,7 @@ void AtlasEngine::_flushBufferLine() } else { - const auto baseWeight = _api.attributes.bold ? DWRITE_FONT_WEIGHT_BOLD : static_cast(_api.fontWeight); + const auto baseWeight = _api.attributes.bold ? DWRITE_FONT_WEIGHT_BOLD : static_cast(_api.fontMetrics.fontWeight); const auto baseStyle = _api.attributes.italic ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL; wil::com_ptr font; @@ -1205,7 +1216,7 @@ void AtlasEngine::_flushBufferLine() /* textPosition */ idx, /* textLength */ gsl::narrow_cast(_api.bufferLine.size()) - idx, /* baseFontCollection */ fontCollection.get(), - /* baseFamilyName */ _api.fontName.get(), + /* baseFamilyName */ _api.fontMetrics.fontName.get(), /* baseWeight */ baseWeight, /* baseStyle */ baseStyle, /* baseStretch */ DWRITE_FONT_STRETCH_NORMAL, @@ -1244,7 +1255,7 @@ void AtlasEngine::_flushBufferLine() { if (!mappedFontFace) { - const auto baseWeight = _api.attributes.bold ? DWRITE_FONT_WEIGHT_BOLD : static_cast(_api.fontWeight); + const auto baseWeight = _api.attributes.bold ? DWRITE_FONT_WEIGHT_BOLD : static_cast(_api.fontMetrics.fontWeight); const auto baseStyle = _api.attributes.italic ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL; wil::com_ptr fontFamily; diff --git a/src/renderer/atlas/AtlasEngine.h b/src/renderer/atlas/AtlasEngine.h index 4234d9e7efd..0c9132c0be8 100644 --- a/src/renderer/atlas/AtlasEngine.h +++ b/src/renderer/atlas/AtlasEngine.h @@ -369,9 +369,14 @@ namespace Microsoft::Console::Render struct FontMetrics { + wil::unique_process_heap_string fontName; + float baselineInDIP = 0.0f; + float fontSizeInDIP = 0.0f; u16x2 cellSize; - float fontSizeInDIP; - float baselineInDIP; + u16 fontWeight = 0; + u16 underlinePos = 0; + u16 strikethroughPos = 0; + u16 lineThickness = 0; }; // These flags are shared with shader_ps.hlsl. @@ -521,7 +526,7 @@ namespace Microsoft::Console::Render { u32 cursorColor = INVALID_COLOR; u16 cursorType = gsl::narrow_cast(CursorType::Legacy); - u8 ulCursorHeightPercent = 25; + u8 heightPercentage = 20; ATLAS_POD_OPS(CachedCursorOptions) }; @@ -546,10 +551,11 @@ namespace Microsoft::Console::Render alignas(sizeof(f32)) f32 grayscaleEnhancedContrast = 0; alignas(sizeof(u32)) u32 cellCountX = 0; alignas(sizeof(u32x2)) u32x2 cellSize; + alignas(sizeof(u32x2)) u32x2 underlinePos; + alignas(sizeof(u32x2)) u32x2 strikethroughPos; alignas(sizeof(u32)) u32 backgroundColor = 0; alignas(sizeof(u32)) u32 cursorColor = 0; alignas(sizeof(u32)) u32 selectionColor = 0; -#pragma warning(suppress : 4324) // structure was padded due to alignment specifier }; // Handled in BeginPaint() @@ -586,6 +592,7 @@ namespace Microsoft::Console::Render // AtlasEngine.cpp [[nodiscard]] HRESULT _handleException(const wil::ResultException& exception) noexcept; __declspec(noinline) void _createResources(); + void _releaseSwapChain(); __declspec(noinline) void _createSwapChain(); __declspec(noinline) void _recreateSizeDependentResources(); __declspec(noinline) void _recreateFontDependentResources(); @@ -599,7 +606,7 @@ namespace Microsoft::Console::Render void _emplaceGlyph(IDWriteFontFace* fontFace, float scale, size_t bufferPos1, size_t bufferPos2); // AtlasEngine.api.cpp - FontMetrics _getFontMetrics(const wchar_t* faceName, double fontSize, DWRITE_FONT_WEIGHT weight); + [[maybe_unused]] void _resolveFontMetrics(const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, FontMetrics* fontMetrics = nullptr) const; // AtlasEngine.r.cpp void _setShaderResources() const; @@ -666,6 +673,9 @@ namespace Microsoft::Console::Render f32x2 cellSizeDIP; // invalidated by ApiInvalidations::Font, caches _api.cellSize but in DIP u16x2 cellSize; // invalidated by ApiInvalidations::Font, caches _api.cellSize u16x2 cellCount; // invalidated by ApiInvalidations::Font|Size, caches _api.cellCount + u16 underlinePos = 0; + u16 strikethroughPos = 0; + u16 lineThickness = 0; u16 dpi = USER_DEFAULT_SCREEN_DPI; // invalidated by ApiInvalidations::Font, caches _api.dpi u16 maxEncounteredCellCount = 0; u16 scratchpadCellWidth = 0; @@ -705,8 +715,9 @@ namespace Microsoft::Console::Render Buffer glyphIndices; Buffer glyphProps; std::vector fontFeatures; // changes are flagged as ApiInvalidations::Font|Size - - u16x2 cellSize; // changes are flagged as ApiInvalidations::Font + std::vector fontAxisValues; // changes are flagged as ApiInvalidations::Font|Size + FontMetrics fontMetrics; // changes are flagged as ApiInvalidations::Font|Size + u16x2 cellCount; // caches `sizeInPixel / cellSize` u16x2 sizeInPixel; // changes are flagged as ApiInvalidations::Size @@ -726,18 +737,12 @@ namespace Microsoft::Console::Render u16x2 invalidatedRows = invalidatedRowsNone; // x is treated as "top" and y as "bottom" i16 scrollOffset = 0; - std::vector fontAxisValues; // changes are flagged as ApiInvalidations::Font|Size - wil::unique_process_heap_string fontName; // changes are flagged as ApiInvalidations::Font|Size - float baselineInDIP = 0.0f; // changes are flagged as ApiInvalidations::Font - float fontSizeInDIP = 0; // changes are flagged as ApiInvalidations::Font|Size - u16 fontWeight = 0; // changes are flagged as ApiInvalidations::Font - u16 dpi = USER_DEFAULT_SCREEN_DPI; // changes are flagged as ApiInvalidations::Font|Size - u16 antialiasingMode = D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE; // changes are flagged as ApiInvalidations::Font - std::function warningCallback; std::function swapChainChangedCallback; wil::unique_handle swapChainHandle; HWND hwnd = nullptr; + u16 dpi = USER_DEFAULT_SCREEN_DPI; // changes are flagged as ApiInvalidations::Font|Size + u16 antialiasingMode = D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE; // changes are flagged as ApiInvalidations::Font ApiInvalidations invalidations = ApiInvalidations::Device; } _api; diff --git a/src/renderer/atlas/AtlasEngine.r.cpp b/src/renderer/atlas/AtlasEngine.r.cpp index 4b28c446e02..4a3f3a2390a 100644 --- a/src/renderer/atlas/AtlasEngine.r.cpp +++ b/src/renderer/atlas/AtlasEngine.r.cpp @@ -148,6 +148,10 @@ void AtlasEngine::_updateConstantBuffer() const noexcept data.cellCountX = _r.cellCount.x; data.cellSize.x = _r.cellSize.x; data.cellSize.y = _r.cellSize.y; + data.underlinePos.x = _r.underlinePos; + data.underlinePos.y = _r.underlinePos + _r.lineThickness; + data.strikethroughPos.x = _r.strikethroughPos; + data.strikethroughPos.y = _r.strikethroughPos + _r.lineThickness; data.backgroundColor = _r.backgroundColor; data.cursorColor = _r.cursorOptions.cursorColor; data.selectionColor = _r.selectionColor; @@ -393,7 +397,7 @@ void AtlasEngine::_drawCursor() switch (cursorType) { case CursorType::Legacy: - rect.top = _r.cellSizeDIP.y * static_cast(100 - _r.cursorOptions.ulCursorHeightPercent) / 100.0f; + rect.top = _r.cellSizeDIP.y * static_cast(100 - _r.cursorOptions.heightPercentage) / 100.0f; break; case CursorType::VerticalBar: rect.right = lineWidth; diff --git a/src/renderer/atlas/shader_ps.hlsl b/src/renderer/atlas/shader_ps.hlsl index 9f615c51d2a..9a5a4205013 100644 --- a/src/renderer/atlas/shader_ps.hlsl +++ b/src/renderer/atlas/shader_ps.hlsl @@ -39,6 +39,8 @@ cbuffer ConstBuffer : register(b0) float grayscaleEnhancedContrast; uint cellCountX; uint2 cellSize; + uint2 underlinePos; + uint2 strikethroughPos; uint backgroundColor; uint cursorColor; uint selectionColor; @@ -63,12 +65,6 @@ uint2 decodeU16x2(uint i) return uint2(i & 0xffff, i >> 16); } -float insideRect(float2 pos, float4 boundaries) -{ - float2 v = step(boundaries.xy, pos) - step(boundaries.zw, pos); - return v.x * v.y; -} - float4 alphaBlendPremultiplied(float4 bottom, float4 top) { float ia = 1 - top.a; @@ -101,7 +97,7 @@ float applyAlphaCorrection(float a, float f, float4 g) float4 main(float4 pos: SV_Position): SV_Target // clang-format on { - if (!insideRect(pos.xy, viewport)) + if (any(pos.xy < viewport.xy) || any(pos.xy >= viewport.zw)) { return decodeRGBA(backgroundColor); } @@ -113,13 +109,15 @@ float4 main(float4 pos: SV_Position): SV_Target // Of course you wouldn't just return a red color there, but instead // for instance run your new code and compare it with the old. - uint2 cellIndex = pos.xy / cellSize; - uint2 cellPos = pos.xy % cellSize; + uint2 viewportPos = pos.xy - viewport.xy; + uint2 cellIndex = viewportPos / cellSize; + uint2 cellPos = viewportPos % cellSize; Cell cell = cells[cellIndex.y * cellCountX + cellIndex.x]; // Layer 0: // The cell's background color float4 color = decodeRGBA(cell.color.y); + float4 fg = decodeRGBA(cell.color.x); // Layer 1 (optional): // Colored cursors are drawn "in between" the background color and the text of a cell. @@ -132,13 +130,21 @@ float4 main(float4 pos: SV_Position): SV_Target } // Layer 2: - // The cell's glyph potentially drawn in the foreground color + // Step 1: Underlines + if ((cell.flags & CellFlags_Underline) && cellPos.y >= underlinePos.x && cellPos.y < underlinePos.y) + { + color = alphaBlendPremultiplied(color, fg); + } + if ((cell.flags & CellFlags_UnderlineDotted) && cellPos.y >= underlinePos.x && cellPos.y < underlinePos.y && (viewportPos.x / (underlinePos.y - underlinePos.x) & 1)) + { + color = alphaBlendPremultiplied(color, fg); + } + // Step 2: The cell's glyph, potentially drawn in the foreground color { float4 glyph = glyphs[decodeU16x2(cell.glyphPos) + cellPos]; if (!(cell.flags & CellFlags_ColoredGlyph)) { - float4 fg = decodeRGBA(cell.color.x); float contrastBoost = (cell.flags & CellFlags_ThinFont) == 0 ? 0.0f : 0.5f; float enhancedContrast = contrastBoost + applyLightOnDarkContrastAdjustment(fg.rgb); float intensity = calcColorIntensity(fg.rgb); @@ -149,6 +155,11 @@ float4 main(float4 pos: SV_Position): SV_Target color = alphaBlendPremultiplied(color, glyph); } + // Step 3: Lines, but not "under"lines + if ((cell.flags & CellFlags_Strikethrough) && cellPos.y >= strikethroughPos.x && cellPos.y < strikethroughPos.y) + { + color = alphaBlendPremultiplied(color, fg); + } // Layer 3 (optional): // Uncolored cursors invert the cells color.