From 694daa7f24e07abd7b6709c984dbc0c3374a36ce Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 20 Mar 2023 20:09:59 +0100 Subject: [PATCH] Fix dirty area calculation, Add ATLAS_DEBUG_SHOW_DIRTY --- src/host/CursorBlinker.cpp | 4 +- src/renderer/atlas/AtlasEngine.cpp | 2 +- src/renderer/atlas/AtlasEngine.r.cpp | 4 +- src/renderer/atlas/Backend.cpp | 94 +++++-------------- src/renderer/atlas/Backend.h | 14 ++- src/renderer/atlas/BackendD2D.cpp | 73 ++++++++++++++- src/renderer/atlas/BackendD2D.h | 5 + src/renderer/atlas/BackendD3D.cpp | 109 +++++++++++++++++----- src/renderer/atlas/BackendD3D.h | 8 ++ src/renderer/atlas/DWriteTextAnalysis.cpp | 3 + src/renderer/atlas/colorbrewer.h | 35 +++++++ src/renderer/atlas/common.h | 54 ++++++++++- src/renderer/atlas/custom_shader_ps.hlsl | 3 + src/renderer/atlas/custom_shader_vs.hlsl | 3 + src/renderer/atlas/stb_rect_pack.cpp | 3 + tools/ConsoleTypes.natvis | 7 ++ 16 files changed, 307 insertions(+), 114 deletions(-) create mode 100644 src/renderer/atlas/colorbrewer.h diff --git a/src/host/CursorBlinker.cpp b/src/host/CursorBlinker.cpp index 78f072ccc94..014d4529c74 100644 --- a/src/host/CursorBlinker.cpp +++ b/src/host/CursorBlinker.cpp @@ -55,7 +55,7 @@ void CursorBlinker::SettingsChanged() noexcept { KillCaretTimer(); _uCaretBlinkTime = dwCaretBlinkTime; - SetCaretTimer(); + //SetCaretTimer(); } } @@ -66,7 +66,7 @@ void CursorBlinker::FocusEnd() const noexcept void CursorBlinker::FocusStart() const noexcept { - SetCaretTimer(); + //SetCaretTimer(); } // Routine Description: diff --git a/src/renderer/atlas/AtlasEngine.cpp b/src/renderer/atlas/AtlasEngine.cpp index 925fe5b0c1c..2891172d0a9 100644 --- a/src/renderer/atlas/AtlasEngine.cpp +++ b/src/renderer/atlas/AtlasEngine.cpp @@ -69,7 +69,7 @@ try _handleSettingsUpdate(); } - if constexpr (debugDisablePartialInvalidation) + if constexpr (ATLAS_DEBUG_DISABLE_PARTIAL_INVALIDATION) { _api.invalidatedRows = invalidatedRowsAll; _api.scrollOffset = 0; diff --git a/src/renderer/atlas/AtlasEngine.r.cpp b/src/renderer/atlas/AtlasEngine.r.cpp index 8c5a4693ec1..3d6c168df00 100644 --- a/src/renderer/atlas/AtlasEngine.r.cpp +++ b/src/renderer/atlas/AtlasEngine.r.cpp @@ -73,7 +73,7 @@ CATCH_RETURN() [[nodiscard]] bool AtlasEngine::RequiresContinuousRedraw() noexcept { - return debugContinuousRedraw || (_b && _b->RequiresContinuousRedraw()); + return ATLAS_DEBUG_CONTINUOUS_REDRAW || (_b && _b->RequiresContinuousRedraw()); } void AtlasEngine::WaitUntilCanRender() noexcept @@ -123,7 +123,7 @@ void AtlasEngine::_recreateBackend() // IID_PPV_ARGS doesn't work here for some reason. THROW_IF_FAILED(CreateDXGIFactory2(flags, __uuidof(_p.dxgiFactory), _p.dxgiFactory.put_void())); - auto d2dMode = debugForceD2DMode; + auto d2dMode = ATLAS_DEBUG_FORCE_D2D_MODE; auto deviceFlags = D3D11_CREATE_DEVICE_SINGLETHREADED #ifndef NDEBUG //| D3D11_CREATE_DEVICE_DEBUG diff --git a/src/renderer/atlas/Backend.cpp b/src/renderer/atlas/Backend.cpp index 67649202320..72d3e27bada 100644 --- a/src/renderer/atlas/Backend.cpp +++ b/src/renderer/atlas/Backend.cpp @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + #include "pch.h" #include "Backend.h" @@ -36,24 +39,27 @@ void SwapChainManager::Present(const RenderingPayload& p) dirtyRect.right = std::min(dirtyRect.right, til::CoordType{ _targetSize.x }); dirtyRect.bottom = std::min(dirtyRect.bottom, til::CoordType{ _targetSize.y }); - if (dirtyRect != fullRect) + if constexpr (!ATLAS_DEBUG_SHOW_DIRTY) { - params.DirtyRectsCount = 1; - params.pDirtyRects = dirtyRect.as_win32_rect(); - - if (p.scrollOffset) + if (dirtyRect != fullRect) { - const auto offsetInPx = p.scrollOffset * p.s->font->cellSize.y; - const auto width = p.s->targetSize.x; - const auto height = p.s->cellCount.y * p.s->font->cellSize.y; - const auto top = std::max(0, offsetInPx); - const auto bottom = height + std::min(0, offsetInPx); + params.DirtyRectsCount = 1; + params.pDirtyRects = dirtyRect.as_win32_rect(); + + if (p.scrollOffset) + { + const auto offsetInPx = p.scrollOffset * p.s->font->cellSize.y; + const auto width = p.s->targetSize.x; + const auto height = p.s->cellCount.y * p.s->font->cellSize.y; + const auto top = std::max(0, offsetInPx); + const auto bottom = height + std::min(0, offsetInPx); - scrollRect = { 0, top, width, bottom }; - scrollOffset = { 0, offsetInPx }; + scrollRect = { 0, top, width, bottom }; + scrollOffset = { 0, offsetInPx }; - params.pScrollRect = &scrollRect; - params.pScrollOffset = &scrollOffset; + params.pScrollRect = &scrollRect; + params.pScrollOffset = &scrollOffset; + } } } @@ -66,7 +72,7 @@ void SwapChainManager::WaitUntilCanRender() noexcept // IDXGISwapChain2::GetFrameLatencyWaitableObject returns an auto-reset event. // Once we've waited on the event, waiting on it again will block until the timeout elapses. // _waitForPresentation guards against this. - if constexpr (!debugDisableFrameLatencyWaitableObject) + if constexpr (!ATLAS_DEBUG_DISABLE_FRAME_LATENCY_WAITABLE_OBJECT) { if (_waitForPresentation) { @@ -155,64 +161,6 @@ void SwapChainManager::_updateMatrixTransform(const RenderingPayload& p) const } } -// Returns the theoretical/design design size of the given `DWRITE_GLYPH_RUN`, relative the the given baseline origin. -f32r Microsoft::Console::Render::Atlas::GetGlyphRunBlackBox(const DWRITE_GLYPH_RUN& glyphRun, f32 baselineX, f32 baselineY) -{ - DWRITE_FONT_METRICS fontMetrics; - glyphRun.fontFace->GetMetrics(&fontMetrics); - - std::unique_ptr glyphRunMetricsHeap; - std::array glyphRunMetricsStack; - DWRITE_GLYPH_METRICS* glyphRunMetrics = glyphRunMetricsStack.data(); - - if (glyphRun.glyphCount > glyphRunMetricsStack.size()) - { - glyphRunMetricsHeap = std::make_unique_for_overwrite(glyphRun.glyphCount); - glyphRunMetrics = glyphRunMetricsHeap.get(); - } - - glyphRun.fontFace->GetDesignGlyphMetrics(glyphRun.glyphIndices, glyphRun.glyphCount, glyphRunMetrics, false); - - f32 const fontScale = glyphRun.fontEmSize / fontMetrics.designUnitsPerEm; - f32r accumulatedBounds{ - FLT_MAX, - FLT_MAX, - FLT_MIN, - FLT_MIN, - }; - - for (uint32_t i = 0; i < glyphRun.glyphCount; ++i) - { - const auto& glyphMetrics = glyphRunMetrics[i]; - const auto glyphAdvance = glyphRun.glyphAdvances ? glyphRun.glyphAdvances[i] : glyphMetrics.advanceWidth * fontScale; - - const auto left = static_cast(glyphMetrics.leftSideBearing) * fontScale; - const auto top = static_cast(glyphMetrics.topSideBearing - glyphMetrics.verticalOriginY) * fontScale; - const auto right = static_cast(gsl::narrow_cast(glyphMetrics.advanceWidth) - glyphMetrics.rightSideBearing) * fontScale; - const auto bottom = static_cast(gsl::narrow_cast(glyphMetrics.advanceHeight) - glyphMetrics.bottomSideBearing - glyphMetrics.verticalOriginY) * fontScale; - - if (left < right && top < bottom) - { - auto glyphX = baselineX; - auto glyphY = baselineY; - if (glyphRun.glyphOffsets) - { - glyphX += glyphRun.glyphOffsets[i].advanceOffset; - glyphY -= glyphRun.glyphOffsets[i].ascenderOffset; - } - - accumulatedBounds.left = std::min(accumulatedBounds.left, left + glyphX); - accumulatedBounds.top = std::min(accumulatedBounds.top, top + glyphY); - accumulatedBounds.right = std::max(accumulatedBounds.right, right + glyphX); - accumulatedBounds.bottom = std::max(accumulatedBounds.bottom, bottom + glyphY); - } - - baselineX += glyphAdvance; - } - - return accumulatedBounds; -} - // Draws a `DWRITE_GLYPH_RUN` at `baselineOrigin` into the given `ID2D1DeviceContext`. // `d2dRenderTarget4` and `dwriteFactory4` are optional and used to draw colored glyphs. // Returns true if the `DWRITE_GLYPH_RUN` contained a color glyph. diff --git a/src/renderer/atlas/Backend.h b/src/renderer/atlas/Backend.h index a4da5a982d7..8abd023d06e 100644 --- a/src/renderer/atlas/Backend.h +++ b/src/renderer/atlas/Backend.h @@ -1,13 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + #pragma once #include "common.h" namespace Microsoft::Console::Render::Atlas { - inline constexpr bool debugContinuousRedraw = false; - inline constexpr bool debugDisableFrameLatencyWaitableObject = false; - inline constexpr bool debugDisablePartialInvalidation = false; - inline constexpr bool debugForceD2DMode = false; +#define ATLAS_DEBUG_CONTINUOUS_REDRAW 0 +#define ATLAS_DEBUG_DISABLE_FRAME_LATENCY_WAITABLE_OBJECT 0 +#define ATLAS_DEBUG_DISABLE_PARTIAL_INVALIDATION 0 +#define ATLAS_DEBUG_FORCE_D2D_MODE 0 +#define ATLAS_DEBUG_SHOW_DIRTY 0 struct SwapChainManager { @@ -39,7 +43,7 @@ namespace Microsoft::Console::Render::Atlas void _createSwapChain(const RenderingPayload& p, IUnknown* device); void _updateMatrixTransform(const RenderingPayload& p) const; - static constexpr DXGI_SWAP_CHAIN_FLAG flags = debugDisableFrameLatencyWaitableObject ? DXGI_SWAP_CHAIN_FLAG{} : DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT; + static constexpr DXGI_SWAP_CHAIN_FLAG flags = ATLAS_DEBUG_DISABLE_FRAME_LATENCY_WAITABLE_OBJECT ? DXGI_SWAP_CHAIN_FLAG{} : DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT; wil::com_ptr _swapChain; wil::unique_handle _swapChainHandle; diff --git a/src/renderer/atlas/BackendD2D.cpp b/src/renderer/atlas/BackendD2D.cpp index 6047ab455c6..20517803800 100644 --- a/src/renderer/atlas/BackendD2D.cpp +++ b/src/renderer/atlas/BackendD2D.cpp @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + #include "pch.h" #include "BackendD2D.h" @@ -193,11 +196,11 @@ void BackendD2D::_drawText(RenderingPayload& p) DrawGlyphRun(_renderTarget.get(), _renderTarget4.get(), p.dwriteFactory4.get(), { baselineX, baselineY }, &glyphRun, brush); - const auto blackBox = GetGlyphRunBlackBox(glyphRun, baselineX, baselineY); + const auto blackBox = _getGlyphRunBlackBox(glyphRun, baselineX, baselineY); // Add a 1px padding to avoid inaccuracies with the blackbox measurement. // It's only an estimate based on the design size after all. - row->top = std::min(row->top, static_cast(lround(blackBox.top - 1.5f))); - row->bottom = std::max(row->bottom, static_cast(lround(blackBox.bottom + 1.5f))); + row->top = std::min(row->top, static_cast(lround(blackBox.top) - 1)); + row->bottom = std::max(row->bottom, static_cast(lround(blackBox.bottom) + 1)); for (UINT32 i = 0; i < glyphRun.glyphCount; ++i) { @@ -205,8 +208,8 @@ void BackendD2D::_drawText(RenderingPayload& p) } } while (it != end); } - - if (row->top < p.dirtyRectInPx.bottom && p.dirtyRectInPx.top < row->bottom) + + if (y >= p.invalidatedRows.x && y < p.invalidatedRows.y) { dirtyTop = std::min(dirtyTop, row->top); dirtyBottom = std::max(dirtyBottom, row->bottom); @@ -222,6 +225,66 @@ void BackendD2D::_drawText(RenderingPayload& p) } } +// Returns the theoretical/design design size of the given `DWRITE_GLYPH_RUN`, relative the the given baseline origin. +// This algorithm replicates what DirectWrite does internally to provide `IDWriteTextLayout::GetMetrics`. +f32r BackendD2D::_getGlyphRunBlackBox(const DWRITE_GLYPH_RUN& glyphRun, f32 baselineX, f32 baselineY) +{ + DWRITE_FONT_METRICS fontMetrics; + glyphRun.fontFace->GetMetrics(&fontMetrics); + + if (glyphRun.glyphCount > _glyphMetrics.size()) + { + // Growth factor 1.5x. + auto size = _glyphMetrics.size(); + size = size + (size >> 1); + size = std::max(size, glyphRun.glyphCount); + // Overflow check. + Expects(size > _glyphMetrics.size()); + _glyphMetrics = Buffer{ size }; + } + + glyphRun.fontFace->GetDesignGlyphMetrics(glyphRun.glyphIndices, glyphRun.glyphCount, _glyphMetrics.data(), false); + + const f32 fontScale = glyphRun.fontEmSize / fontMetrics.designUnitsPerEm; + f32r accumulatedBounds{ + FLT_MAX, + FLT_MAX, + FLT_MIN, + FLT_MIN, + }; + + for (uint32_t i = 0; i < glyphRun.glyphCount; ++i) + { + const auto& glyphMetrics = _glyphMetrics[i]; + const auto glyphAdvance = glyphRun.glyphAdvances ? glyphRun.glyphAdvances[i] : glyphMetrics.advanceWidth * fontScale; + + const auto left = static_cast(glyphMetrics.leftSideBearing) * fontScale; + const auto top = static_cast(glyphMetrics.topSideBearing - glyphMetrics.verticalOriginY) * fontScale; + const auto right = static_cast(gsl::narrow_cast(glyphMetrics.advanceWidth) - glyphMetrics.rightSideBearing) * fontScale; + const auto bottom = static_cast(gsl::narrow_cast(glyphMetrics.advanceHeight) - glyphMetrics.bottomSideBearing - glyphMetrics.verticalOriginY) * fontScale; + + if (left < right && top < bottom) + { + auto glyphX = baselineX; + auto glyphY = baselineY; + if (glyphRun.glyphOffsets) + { + glyphX += glyphRun.glyphOffsets[i].advanceOffset; + glyphY -= glyphRun.glyphOffsets[i].ascenderOffset; + } + + accumulatedBounds.left = std::min(accumulatedBounds.left, left + glyphX); + accumulatedBounds.top = std::min(accumulatedBounds.top, top + glyphY); + accumulatedBounds.right = std::max(accumulatedBounds.right, right + glyphX); + accumulatedBounds.bottom = std::max(accumulatedBounds.bottom, bottom + glyphY); + } + + baselineX += glyphAdvance; + } + + return accumulatedBounds; +} + void BackendD2D::_drawGridlines(const RenderingPayload& p) { u16 y = 0; diff --git a/src/renderer/atlas/BackendD2D.h b/src/renderer/atlas/BackendD2D.h index 2df604e59be..64f6ea4b0ee 100644 --- a/src/renderer/atlas/BackendD2D.h +++ b/src/renderer/atlas/BackendD2D.h @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + #pragma once #include "Backend.h" @@ -16,6 +19,7 @@ namespace Microsoft::Console::Render::Atlas __declspec(noinline) void _handleSettingsUpdate(const RenderingPayload& p); void _drawBackground(const RenderingPayload& p) noexcept; void _drawText(RenderingPayload& p); + f32r _getGlyphRunBlackBox(const DWRITE_GLYPH_RUN& glyphRun, f32 baselineX, f32 baselineY); void _drawGridlines(const RenderingPayload& p); void _drawGridlineRow(const RenderingPayload& p, const ShapedRow* row, u16 y); void _drawCursor(const RenderingPayload& p); @@ -36,6 +40,7 @@ namespace Microsoft::Console::Render::Atlas wil::com_ptr _backgroundBrush; til::generation_t _backgroundBitmapGeneration; + Buffer _glyphMetrics; u32 _brushColor = 0; til::generation_t _generation; diff --git a/src/renderer/atlas/BackendD3D.cpp b/src/renderer/atlas/BackendD3D.cpp index 914a8fc0efd..db45984e8d3 100644 --- a/src/renderer/atlas/BackendD3D.cpp +++ b/src/renderer/atlas/BackendD3D.cpp @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + #include "pch.h" #include "BackendD3D.h" @@ -10,6 +13,8 @@ #include "dwrite.h" +#include "colorbrewer.h" + TIL_FAST_MATH_BEGIN // This code packs various data into smaller-than-int types to save both CPU and GPU memory. This warning would force @@ -291,6 +296,31 @@ void BackendD3D::Render(RenderingPayload& p) _drawGridlines(p); _drawCursorPart2(p); _drawSelection(p); + +#if ATLAS_DEBUG_SHOW_DIRTY + { + _presentRects[_presentRectsPos] = p.dirtyRectInPx; + _presentRectsPos = (_presentRectsPos + 1) % std::size(_presentRects); + + for (size_t i = 0; i < std::size(_presentRects); ++i) + { + if (const auto& rect = _presentRects[i]) + { + const i16x2 position{ + static_cast(rect.left), + static_cast(rect.top), + }; + const u16x2 size{ + static_cast(rect.right - rect.left), + static_cast(rect.bottom - rect.top), + }; + const auto color = 0x3f000000 | colorbrewer::pastel1[i]; + _appendQuad(position, size, color, ShadingType::SolidFill); + } + } + } +#endif + _flushQuads(p); if (_customPixelShader) @@ -373,6 +403,11 @@ void BackendD3D::_handleSettingsUpdate(const RenderingPayload& p) _miscGeneration = p.s->misc.generation(); _targetSize = p.s->targetSize; _cellCount = p.s->cellCount; + +#if ATLAS_DEBUG_SHOW_DIRTY + std::ranges::fill(_presentRects, til::rect{}); + _presentRectsPos = 0; +#endif } void BackendD3D::_recreateCustomShader(const RenderingPayload& p) @@ -835,6 +870,7 @@ void BackendD3D::_flushQuads(const RenderingPayload& p) return; } + // TODO: Shrink instances buffer if (_instancesCount > _instanceBufferCapacity) { _recreateInstanceBuffers(p); @@ -889,20 +925,20 @@ void BackendD3D::_flushQuads(const RenderingPayload& p) void BackendD3D::_recreateInstanceBuffers(const RenderingPayload& p) { - static constexpr size_t R16max = 1 << 16; - // While the viewport size of the terminal is probably a good initial estimate for the amount of instances we'll see, - // I feel like we should ensure that the estimate doesn't exceed the limit for a DXGI_FORMAT_R16_UINT index buffer. - const auto estimatedInstances = std::min(R16max / 4, static_cast(p.s->cellCount.x) * p.s->cellCount.y); - const auto minSize = std::max(_instancesCount, estimatedInstances); - // std::bit_ceil will result in a nice exponential growth curve. I don't know exactly how structured buffers are treated - // by various drivers, but I'm assuming that they prefer buffer sizes that are close to power-of-2 sizes as well. - const auto newInstancesCapacity = std::bit_ceil(minSize * sizeof(QuadInstance)) / sizeof(QuadInstance); + // We use the viewport size of the terminal as the initial estimate for the amount of instances we'll see. + const auto minCapacity = static_cast(p.s->cellCount.x) * p.s->cellCount.y; + auto newCapacity = std::max(_instancesCount, minCapacity); + auto newSize = newCapacity * sizeof(QuadInstance); + // Round up to multiples of 64kB to avoid reallocating too often. + // 64kB is the minimum alignment for committed resources in D3D12. + newSize = (newSize + 0xffff) & ~size_t{ 0xffff }; + newCapacity = newSize / sizeof(QuadInstance); _instanceBuffer.reset(); { D3D11_BUFFER_DESC desc{}; - desc.ByteWidth = gsl::narrow(newInstancesCapacity * sizeof(QuadInstance)); + desc.ByteWidth = gsl::narrow(newSize); desc.Usage = D3D11_USAGE_DYNAMIC; desc.BindFlags = D3D11_BIND_VERTEX_BUFFER; desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; @@ -916,7 +952,7 @@ void BackendD3D::_recreateInstanceBuffers(const RenderingPayload& p) static constexpr UINT offsets[]{ 0, 0 }; _deviceContext->IASetVertexBuffers(0, 2, &vertexBuffers[0], &strides[0], &offsets[0]); - _instanceBufferCapacity = newInstancesCapacity; + _instanceBufferCapacity = newCapacity; } void BackendD3D::_drawBackground(const RenderingPayload& p) @@ -978,7 +1014,7 @@ void BackendD3D::_drawText(RenderingPayload& p) } } - if (row->top < p.dirtyRectInPx.bottom && p.dirtyRectInPx.top < row->bottom) + if (y >= p.invalidatedRows.x && y < p.invalidatedRows.y) { dirtyTop = std::min(dirtyTop, row->top); dirtyBottom = std::max(dirtyBottom, row->bottom); @@ -996,18 +1032,42 @@ void BackendD3D::_drawText(RenderingPayload& p) _d2dEndDrawing(); } +#pragma warning(disable : 4189) + void BackendD3D::_drawGlyph(const RenderingPayload& p, GlyphCacheEntry& entry, f32 fontEmSize) { - DWRITE_GLYPH_RUN glyphRun{}; - glyphRun.fontFace = entry.fontFace; - glyphRun.fontEmSize = fontEmSize; - glyphRun.glyphCount = 1; - glyphRun.glyphIndices = &entry.glyphIndex; + const DWRITE_GLYPH_RUN glyphRun{ + .fontFace = entry.fontFace, + .fontEmSize = fontEmSize, + .glyphCount = 1, + .glyphIndices = &entry.glyphIndex, + }; + + DWRITE_FONT_METRICS fontMetrics; + glyphRun.fontFace->GetMetrics(&fontMetrics); + + DWRITE_GLYPH_METRICS glyphMetrics; + glyphRun.fontFace->GetDesignGlyphMetrics(glyphRun.glyphIndices, glyphRun.glyphCount, &glyphMetrics, false); + + // This calculates the black box of the glyph, or in other words, it's extents/size relative to its baseline origin (at 0,0). + // The algorithm below is a reverse engineered variant of `IDWriteTextLayout::GetMetrics`. The coordinates will be in pixel + // and the positive direction will be bottom/right. A `.left` of -3px would indicate that the glyph overlaps it's bounding box + // by 3px to the left and would thus overlap it's neighbor to the left by 3px. `.bottom` is the same but for the descender. + // `.right` and `.top` are not overlaps per se, but rather the distance to the right/top edge relative to the baseline origin. + // The width of the glyph for instance is thus `.right - .left`. + const f32 fontScale = p.d.font.pixelPerDIP * glyphRun.fontEmSize / fontMetrics.designUnitsPerEm; + const f32r box{ + static_cast(glyphMetrics.leftSideBearing) * fontScale, + static_cast(glyphMetrics.topSideBearing - glyphMetrics.verticalOriginY) * fontScale, + static_cast(static_cast(glyphMetrics.advanceWidth) - glyphMetrics.rightSideBearing) * fontScale, + static_cast(static_cast(glyphMetrics.advanceHeight) - glyphMetrics.bottomSideBearing - glyphMetrics.verticalOriginY) * fontScale, + }; - const auto box = GetGlyphRunBlackBox(glyphRun, 0, 0); + // box may be empty if the glyph is whitespace. if (box.empty()) { - // This will indicate to BackendD3D::_drawText that this glyph is whitespace. + // This will indicate to `BackendD3D::_drawText` that this glyph is whitespace. It's important to set this member, + // because `GlyphCacheMap` does not zero out inserted entries and `shadingType` might still contain "garbage". entry.shadingType = 0; return; } @@ -1015,14 +1075,15 @@ void BackendD3D::_drawGlyph(const RenderingPayload& p, GlyphCacheEntry& entry, f bool retry = false; for (;;) { - // We'll add a 1px padding on all 4 sides to avoid neighboring glyphs - // from overlapping, since the blackbox measurement is only an estimate. + // We'll add a 1px padding on all 4 sides to avoid neighboring glyphs from overlapping, + // since the blackbox measurement is only an estimate based on the design metrics. // We need to use round (and not ceil/floor) to ensure we pixel-snap individual // glyphs correctly and form a consistent baseline across an entire run of glyphs. - const auto l = lround(box.left * p.d.font.pixelPerDIP) - 1; - const auto t = lround(box.top * p.d.font.pixelPerDIP) - 1; - const auto r = lround(box.right * p.d.font.pixelPerDIP) + 1; - const auto b = lround(box.bottom * p.d.font.pixelPerDIP) + 1; + // Also, ClearType might draw (rounded) up to 1.2px away from the design outline. + const auto l = lround(box.left) - 1; + const auto t = lround(box.top) - 1; + const auto r = lround(box.right) + 1; + const auto b = lround(box.bottom) + 1; stbrp_rect rect{}; rect.w = r - l; diff --git a/src/renderer/atlas/BackendD3D.h b/src/renderer/atlas/BackendD3D.h index 68ba7d3dfc3..a1769b12f5c 100644 --- a/src/renderer/atlas/BackendD3D.h +++ b/src/renderer/atlas/BackendD3D.h @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + #pragma once #include @@ -223,6 +226,11 @@ namespace Microsoft::Console::Render::Atlas til::small_vector _cursorRects; bool _requiresContinuousRedraw = false; + +#if ATLAS_DEBUG_SHOW_DIRTY + til::rect _presentRects[9]{}; + size_t _presentRectsPos = 0; +#endif #ifndef NDEBUG std::filesystem::path _sourceDirectory; diff --git a/src/renderer/atlas/DWriteTextAnalysis.cpp b/src/renderer/atlas/DWriteTextAnalysis.cpp index d9456c4ddf9..94764c59a10 100644 --- a/src/renderer/atlas/DWriteTextAnalysis.cpp +++ b/src/renderer/atlas/DWriteTextAnalysis.cpp @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + #include "pch.h" #include "DWriteTextAnalysis.h" diff --git a/src/renderer/atlas/colorbrewer.h b/src/renderer/atlas/colorbrewer.h new file mode 100644 index 00000000000..990ce4638a6 --- /dev/null +++ b/src/renderer/atlas/colorbrewer.h @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +namespace Microsoft::Console::Render::Atlas::colorbrewer { + // The following list of colors is only used as a debug aid and not part of the final product. + // They're licensed under: + // + // Apache-Style Software License for ColorBrewer software and ColorBrewer Color Schemes + // + // Copyright (c) 2002 Cynthia Brewer, Mark Harrower, and The Pennsylvania State University. + // + // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. + // You may obtain a copy of the License at + // + // http://www.apache.org/licenses/LICENSE-2.0 + // + // Unless required by applicable law or agreed to in writing, software distributed + // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + // CONDITIONS OF ANY KIND, either express or implied. See the License for the + // specific language governing permissions and limitations under the License. + // + inline constexpr u32 pastel1[]{ + 0xfbb4ae, + 0xb3cde3, + 0xccebc5, + 0xdecbe4, + 0xfed9a6, + 0xffffcc, + 0xe5d8bd, + 0xfddaec, + 0xf2f2f2, + }; +} diff --git a/src/renderer/atlas/common.h b/src/renderer/atlas/common.h index d9d2a73bf43..ac7f4d06724 100644 --- a/src/renderer/atlas/common.h +++ b/src/renderer/atlas/common.h @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + #pragma once #include @@ -93,6 +96,15 @@ namespace Microsoft::Console::Render::Atlas } }; + template + struct range + { + T start{}; + T end{}; + + ATLAS_POD_OPS(range) + }; + using u8 = uint8_t; using u16 = uint16_t; @@ -146,7 +158,7 @@ namespace Microsoft::Console::Render::Atlas // be a good future extension, but not to improve security here. // You can trivially construct std::span's from invalid ranges. // Until then the raw-pointer style is more practical. -#pragma warning(suppress : 26459) // You called an STL function '...' with a raw pointer parameter at position '3' that may be unsafe [...]. +#pragma warning(suppress : 26459) // You called an STL function '...' with a raw pointer parameter at position '...' that may be unsafe [...]. std::uninitialized_copy_n(data, size, _data); } @@ -161,7 +173,6 @@ namespace Microsoft::Console::Render::Atlas { } -#pragma warning(suppress : 26432) // If you define or delete any default operation in the type '...', define or delete them all (c.21). Buffer& operator=(Buffer&& other) noexcept { destroy(); @@ -170,6 +181,40 @@ namespace Microsoft::Console::Render::Atlas return *this; } +#if 0 + Buffer(const Buffer& other) noexcept : + _data{ allocate(other._size) }, + _size{ other._size } + { +#pragma warning(suppress : 26459) // You called an STL function '...' with a raw pointer parameter at position '...' that may be unsafe [...]. + std::uninitialized_copy_n(other._data, other._size, _data); + } + + Buffer& operator=(const Buffer& other) noexcept + { + destroy(); + _data = nullptr; + _size = 0; + + _data = allocate(other._size); + _size = other._size; + +#pragma warning(suppress : 26459) // You called an STL function '...' with a raw pointer parameter at position '...' that may be unsafe [...]. + std::uninitialized_copy_n(other._data, other._size, _data); + return *this; + } + + bool operator==(const Buffer& other) const + { + return memcmp(_data, other._data, _size * sizeof(T)) == 0; + } + + bool operator!=(const Buffer& other) const + { + return memcmp(_data, other._data, _size * sizeof(T)) != 0; + } +#endif + explicit operator bool() const noexcept { return _data != nullptr; @@ -234,6 +279,10 @@ namespace Microsoft::Console::Render::Atlas #pragma warning(disable : 26409) // Avoid calling new and delete explicitly, use std::make_unique instead (r.11). static T* allocate(size_t size) { + if (!size) + { + return nullptr; + } if constexpr (Alignment <= __STDCPP_DEFAULT_NEW_ALIGNMENT__) { return static_cast(::operator new(size * sizeof(T))); @@ -428,6 +477,7 @@ namespace Microsoft::Console::Render::Atlas // is entirely black, just like `backgroundBitmap` after it gets created. til::generation_t backgroundBitmapGeneration{ 1 }; til::rect dirtyRectInPx; + u16x2 invalidatedRows; u16r cursorRect; i16 scrollOffset = 0; }; diff --git a/src/renderer/atlas/custom_shader_ps.hlsl b/src/renderer/atlas/custom_shader_ps.hlsl index 0073f2ca87c..221be267b42 100644 --- a/src/renderer/atlas/custom_shader_ps.hlsl +++ b/src/renderer/atlas/custom_shader_ps.hlsl @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + // The original retro pixel shader Texture2D shaderTexture; SamplerState samplerState; diff --git a/src/renderer/atlas/custom_shader_vs.hlsl b/src/renderer/atlas/custom_shader_vs.hlsl index 5bb9fbff70b..97b51c4c23e 100644 --- a/src/renderer/atlas/custom_shader_vs.hlsl +++ b/src/renderer/atlas/custom_shader_vs.hlsl @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + struct VS_OUTPUT { float4 pos : SV_POSITION; diff --git a/src/renderer/atlas/stb_rect_pack.cpp b/src/renderer/atlas/stb_rect_pack.cpp index 7cba8a5330b..306f747a7f5 100644 --- a/src/renderer/atlas/stb_rect_pack.cpp +++ b/src/renderer/atlas/stb_rect_pack.cpp @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + #include "pch.h" #define STB_RECT_PACK_IMPLEMENTATION diff --git a/tools/ConsoleTypes.natvis b/tools/ConsoleTypes.natvis index 48f950c55c7..317371f7d52 100644 --- a/tools/ConsoleTypes.natvis +++ b/tools/ConsoleTypes.natvis @@ -105,6 +105,13 @@ + + {{ generation={_generation._value}}} + + _value + + + {{ size={_size} }}