From 4da9367ea9441e516ba58dfe48e4534c181e668d Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Tue, 6 Aug 2024 13:03:19 -0500 Subject: [PATCH 1/3] atlas,d2d: overdraw the background bitmap by one cell on all sides BackendD2D will now draw one extra cell on all sides when rendering the background, filled with the expected background color, starting at (-1, -1) to ensure that cell backgrounds do not bleed over the edges of the viewport. Fixes #17672 --- src/renderer/atlas/BackendD2D.cpp | 49 ++++++++++++++++++++++++++----- src/renderer/atlas/BackendD2D.h | 1 + 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/src/renderer/atlas/BackendD2D.cpp b/src/renderer/atlas/BackendD2D.cpp index 8dc330dfe0c..b10fd3b41ab 100644 --- a/src/renderer/atlas/BackendD2D.cpp +++ b/src/renderer/atlas/BackendD2D.cpp @@ -80,6 +80,7 @@ void BackendD2D::_handleSettingsUpdate(const RenderingPayload& p) const auto renderTargetChanged = !_renderTarget; const auto fontChanged = _fontGeneration != p.s->font.generation(); const auto cursorChanged = _cursorGeneration != p.s->cursor.generation(); + const auto backgroundColorChanged = _miscGeneration != p.s->misc.generation(); const auto cellCountChanged = _viewportCellCount != p.s->viewportCellCount; if (renderTargetChanged) @@ -125,7 +126,7 @@ void BackendD2D::_handleSettingsUpdate(const RenderingPayload& p) _builtinGlyphsRenderTargetActive = false; } - if (renderTargetChanged || fontChanged || cellCountChanged) + if (renderTargetChanged || fontChanged || cellCountChanged || backgroundColorChanged) { const D2D1_BITMAP_PROPERTIES props{ .pixelFormat = { DXGI_FORMAT_R8G8B8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED }, @@ -133,14 +134,35 @@ void BackendD2D::_handleSettingsUpdate(const RenderingPayload& p) .dpiY = static_cast(p.s->font->dpi), }; const D2D1_SIZE_U size{ - p.s->viewportCellCount.x, - p.s->viewportCellCount.y, + p.s->viewportCellCount.x + 2u, + p.s->viewportCellCount.y + 2u, }; const D2D1_MATRIX_3X2_F transform{ .m11 = static_cast(p.s->font->cellSize.x), .m22 = static_cast(p.s->font->cellSize.y), + .dx = -static_cast(p.s->font->cellSize.x), + .dy = -static_cast(p.s->font->cellSize.y), }; - THROW_IF_FAILED(_renderTarget->CreateBitmap(size, nullptr, 0, &props, _backgroundBitmap.put())); + + /* + We're allocating a bitmap that is one pixel wider on every side than the viewport so that we can fill in the gutter + with the background color. D2D doesn't have an equivalent to D3D11_TEXTURE_ADDRESS_BORDER, which we use in the D3D + backend to ensure the colors don't bleed off the edges. + + XXXXXXXXXXXXXXXX <- background color + X+------------+X + X| viewport |X + X| |X + X| |X + X+------------+X + XXXXXXXXXXXXXXXX + + The translation in `transform` ensures that we render it off the top left of the render target. + */ + auto backgroundFill = std::make_unique_for_overwrite(static_cast(size.width * size.height)); + std::fill_n(backgroundFill.get(), size.width * size.height, u32ColorPremultiply(p.s->misc->backgroundColor)); + + THROW_IF_FAILED(_renderTarget->CreateBitmap(size, backgroundFill.get(), size.width * sizeof(u32), &props, _backgroundBitmap.put())); THROW_IF_FAILED(_renderTarget->CreateBitmapBrush(_backgroundBitmap.get(), _backgroundBrush.put())); _backgroundBrush->SetInterpolationMode(D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR); _backgroundBrush->SetExtendModeX(D2D1_EXTEND_MODE_MIRROR); @@ -158,6 +180,7 @@ void BackendD2D::_handleSettingsUpdate(const RenderingPayload& p) _generation = p.s.generation(); _fontGeneration = p.s->font.generation(); _cursorGeneration = p.s->cursor.generation(); + _miscGeneration = p.s->misc.generation(); _viewportCellCount = p.s->viewportCellCount; } @@ -165,13 +188,25 @@ void BackendD2D::_drawBackground(const RenderingPayload& p) { if (_backgroundBitmapGeneration != p.colorBitmapGenerations[0]) { - THROW_IF_FAILED(_backgroundBitmap->CopyFromMemory(nullptr, p.backgroundBitmap.data(), gsl::narrow_cast(p.colorBitmapRowStride * sizeof(u32)))); + const D2D1_RECT_U rect{ + 1u, + 1u, + 1u + p.s->viewportCellCount.x, + 1u + p.s->viewportCellCount.y, + }; + THROW_IF_FAILED(_backgroundBitmap->CopyFromMemory(&rect, p.backgroundBitmap.data(), gsl::narrow_cast(p.colorBitmapRowStride * sizeof(u32)))); _backgroundBitmapGeneration = p.colorBitmapGenerations[0]; } // If the terminal was 120x30 cells and 1200x600 pixels large, this would draw the - // background by upscaling a 120x30 pixel bitmap to fill the entire render target. - const D2D1_RECT_F rect{ 0, 0, static_cast(p.s->targetSize.x), static_cast(p.s->targetSize.y) }; + // background by upscaling a 122x32 pixel bitmap to fill the entire render target, + // with some overhang on all four sides to paint the gutters. + const D2D1_RECT_F rect{ + static_cast(-(p.s->font->cellSize.x)), + static_cast(-(p.s->font->cellSize.y)), + static_cast(p.s->targetSize.x + (2 * p.s->font->cellSize.x)), + static_cast(p.s->targetSize.y + (2 * p.s->font->cellSize.y)) + }; _renderTarget->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_COPY); _renderTarget->FillRectangle(&rect, _backgroundBrush.get()); _renderTarget->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_SOURCE_OVER); diff --git a/src/renderer/atlas/BackendD2D.h b/src/renderer/atlas/BackendD2D.h index c5f7dfbe7d2..da4991bc417 100644 --- a/src/renderer/atlas/BackendD2D.h +++ b/src/renderer/atlas/BackendD2D.h @@ -67,6 +67,7 @@ namespace Microsoft::Console::Render::Atlas til::generation_t _generation; til::generation_t _fontGeneration; til::generation_t _cursorGeneration; + til::generation_t _miscGeneration; u16x2 _viewportCellCount{}; #if ATLAS_DEBUG_SHOW_DIRTY From 1d54c808c542d6a1b8b32810971781a900d8da5e Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Tue, 6 Aug 2024 15:02:41 -0500 Subject: [PATCH 2/3] We don't need to paint off the edge of the swapchain, there's nothing there! --- src/renderer/atlas/BackendD2D.cpp | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/renderer/atlas/BackendD2D.cpp b/src/renderer/atlas/BackendD2D.cpp index b10fd3b41ab..602f532481d 100644 --- a/src/renderer/atlas/BackendD2D.cpp +++ b/src/renderer/atlas/BackendD2D.cpp @@ -140,6 +140,7 @@ void BackendD2D::_handleSettingsUpdate(const RenderingPayload& p) const D2D1_MATRIX_3X2_F transform{ .m11 = static_cast(p.s->font->cellSize.x), .m22 = static_cast(p.s->font->cellSize.y), + /* Brushes are transformed relative to the render target, not the rect into which they are painted. */ .dx = -static_cast(p.s->font->cellSize.x), .dy = -static_cast(p.s->font->cellSize.y), }; @@ -165,8 +166,8 @@ void BackendD2D::_handleSettingsUpdate(const RenderingPayload& p) THROW_IF_FAILED(_renderTarget->CreateBitmap(size, backgroundFill.get(), size.width * sizeof(u32), &props, _backgroundBitmap.put())); THROW_IF_FAILED(_renderTarget->CreateBitmapBrush(_backgroundBitmap.get(), _backgroundBrush.put())); _backgroundBrush->SetInterpolationMode(D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR); - _backgroundBrush->SetExtendModeX(D2D1_EXTEND_MODE_MIRROR); - _backgroundBrush->SetExtendModeY(D2D1_EXTEND_MODE_MIRROR); + _backgroundBrush->SetExtendModeX(D2D1_EXTEND_MODE_CLAMP); + _backgroundBrush->SetExtendModeY(D2D1_EXTEND_MODE_CLAMP); _backgroundBrush->SetTransform(&transform); _backgroundBitmapGeneration = {}; } @@ -199,14 +200,8 @@ void BackendD2D::_drawBackground(const RenderingPayload& p) } // If the terminal was 120x30 cells and 1200x600 pixels large, this would draw the - // background by upscaling a 122x32 pixel bitmap to fill the entire render target, - // with some overhang on all four sides to paint the gutters. - const D2D1_RECT_F rect{ - static_cast(-(p.s->font->cellSize.x)), - static_cast(-(p.s->font->cellSize.y)), - static_cast(p.s->targetSize.x + (2 * p.s->font->cellSize.x)), - static_cast(p.s->targetSize.y + (2 * p.s->font->cellSize.y)) - }; + // background by upscaling a 120x30 pixel bitmap to fill the entire render target. + const D2D1_RECT_F rect{ 0, 0, static_cast(p.s->targetSize.x), static_cast(p.s->targetSize.y) }; _renderTarget->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_COPY); _renderTarget->FillRectangle(&rect, _backgroundBrush.get()); _renderTarget->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_SOURCE_OVER); From 9f14782d3dab08b45f70d07ab878209e2def8d8b Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Tue, 6 Aug 2024 15:13:27 -0500 Subject: [PATCH 3/3] Okay audit boomer --- src/renderer/atlas/BackendD2D.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/atlas/BackendD2D.cpp b/src/renderer/atlas/BackendD2D.cpp index 602f532481d..8a7861ecd9b 100644 --- a/src/renderer/atlas/BackendD2D.cpp +++ b/src/renderer/atlas/BackendD2D.cpp @@ -160,7 +160,7 @@ void BackendD2D::_handleSettingsUpdate(const RenderingPayload& p) The translation in `transform` ensures that we render it off the top left of the render target. */ - auto backgroundFill = std::make_unique_for_overwrite(static_cast(size.width * size.height)); + auto backgroundFill = std::make_unique_for_overwrite(static_cast(size.width) * size.height); std::fill_n(backgroundFill.get(), size.width * size.height, u32ColorPremultiply(p.s->misc->backgroundColor)); THROW_IF_FAILED(_renderTarget->CreateBitmap(size, backgroundFill.get(), size.width * sizeof(u32), &props, _backgroundBitmap.put()));