From c3ce3daedc33e4004aec1656717e3f273cd1e25f Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Fri, 27 Sep 2024 12:32:22 +0200 Subject: [PATCH] wip --- src/buffer/out/cursor.cpp | 2 +- src/buffer/out/search.cpp | 45 +- src/buffer/out/textBuffer.cpp | 54 +- src/buffer/out/textBuffer.hpp | 9 +- src/host/exe/Host.EXE.vcxproj | 5 +- src/host/exe/Host.EXE.vcxproj.filters | 1 + src/interactivity/win32/lib/win32.LIB.vcxproj | 7 +- .../win32/lib/win32.LIB.vcxproj.filters | 3 +- src/renderer/base/RenderEngineBase.cpp | 99 -- src/renderer/base/RenderSettings.cpp | 2 +- src/renderer/base/lib/base.vcxproj | 14 +- src/renderer/base/lib/base.vcxproj.filters | 39 +- src/renderer/base/renderer.cpp | 1446 +---------------- src/renderer/base/renderer.hpp | 137 +- src/renderer/base/thread.cpp | 301 ---- src/renderer/base/thread.hpp | 50 - src/renderer/gdi/GdiEngine.cpp | 26 + src/renderer/gdi/GdiEngine.h | 19 + src/renderer/gdi/gdirenderer.hpp | 225 --- src/renderer/gdi/invalidate.cpp | 185 --- src/renderer/gdi/lib/gdi.vcxproj | 9 +- src/renderer/gdi/lib/gdi.vcxproj.filters | 17 +- src/renderer/gdi/math.cpp | 152 -- src/renderer/gdi/paint.cpp | 987 ----------- src/renderer/gdi/state.cpp | 788 --------- src/renderer/gdi/tool/main.cpp | 63 - src/renderer/inc/Cluster.hpp | 67 - src/renderer/inc/CursorOptions.h | 62 - src/renderer/inc/DummyRenderer.hpp | 24 - src/renderer/inc/IRenderData.hpp | 158 +- src/renderer/inc/IRenderEngine.hpp | 89 +- src/renderer/inc/RenderEngineBase.hpp | 61 - src/tsf/Handle.cpp | 6 + src/tsf/Handle.h | 3 +- src/tsf/Implementation.cpp | 43 +- src/tsf/Implementation.h | 6 + src/types/ScreenInfoUiaProviderBase.cpp | 2 +- src/types/ScreenInfoUiaProviderBase.h | 27 +- src/types/TermControlUiaProvider.cpp | 2 +- src/types/TermControlUiaProvider.hpp | 2 +- src/types/TermControlUiaTextRange.cpp | 8 +- src/types/TermControlUiaTextRange.hpp | 8 +- src/types/UiaTextRangeBase.cpp | 15 +- src/types/UiaTextRangeBase.hpp | 10 +- 44 files changed, 276 insertions(+), 5002 deletions(-) delete mode 100644 src/renderer/base/RenderEngineBase.cpp delete mode 100644 src/renderer/base/thread.cpp delete mode 100644 src/renderer/base/thread.hpp create mode 100644 src/renderer/gdi/GdiEngine.cpp create mode 100644 src/renderer/gdi/GdiEngine.h delete mode 100644 src/renderer/gdi/gdirenderer.hpp delete mode 100644 src/renderer/gdi/invalidate.cpp delete mode 100644 src/renderer/gdi/math.cpp delete mode 100644 src/renderer/gdi/paint.cpp delete mode 100644 src/renderer/gdi/state.cpp delete mode 100644 src/renderer/gdi/tool/main.cpp delete mode 100644 src/renderer/inc/Cluster.hpp delete mode 100644 src/renderer/inc/CursorOptions.h delete mode 100644 src/renderer/inc/DummyRenderer.hpp delete mode 100644 src/renderer/inc/RenderEngineBase.hpp diff --git a/src/buffer/out/cursor.cpp b/src/buffer/out/cursor.cpp index 43c0a70293..a9b9d52f6e 100644 --- a/src/buffer/out/cursor.cpp +++ b/src/buffer/out/cursor.cpp @@ -175,7 +175,7 @@ void Cursor::_RedrawCursor() noexcept // - void Cursor::_RedrawCursorAlways() noexcept { - _parentBuffer.NotifyPaintFrame(); + _parentBuffer.TriggerRedraw(); } void Cursor::SetPosition(const til::point cPosition) noexcept diff --git a/src/buffer/out/search.cpp b/src/buffer/out/search.cpp index 75143f047c..1397e07eca 100644 --- a/src/buffer/out/search.cpp +++ b/src/buffer/out/search.cpp @@ -10,35 +10,18 @@ using namespace Microsoft::Console::Types; bool Search::IsStale(const Microsoft::Console::Render::IRenderData& renderData, const std::wstring_view& needle, SearchFlag flags) const noexcept { - return _renderData != &renderData || - _needle != needle || - _flags != flags || - _lastMutationId != renderData.GetTextBuffer().GetLastMutationId(); + UNREFERENCED_PARAMETER(renderData); + UNREFERENCED_PARAMETER(needle); + UNREFERENCED_PARAMETER(flags); + return false; } void Search::Reset(Microsoft::Console::Render::IRenderData& renderData, const std::wstring_view& needle, SearchFlag flags, bool reverse) { - const auto& textBuffer = renderData.GetTextBuffer(); - - _renderData = &renderData; - _needle = needle; - _flags = flags; - _lastMutationId = textBuffer.GetLastMutationId(); - - auto result = textBuffer.SearchText(needle, _flags); - _ok = result.has_value(); - _results = std::move(result).value_or(std::vector{}); - _index = reverse ? gsl::narrow_cast(_results.size()) - 1 : 0; - _step = reverse ? -1 : 1; - - if (_renderData->IsSelectionActive()) - { - MoveToPoint(_renderData->GetTextBuffer().ScreenToBufferPosition(_renderData->GetSelectionAnchor())); - } - else if (const auto span = _renderData->GetSearchHighlightFocused()) - { - MoveToPoint(_step > 0 ? span->start : span->end); - } + UNREFERENCED_PARAMETER(renderData); + UNREFERENCED_PARAMETER(needle); + UNREFERENCED_PARAMETER(flags); + UNREFERENCED_PARAMETER(reverse); } void Search::MoveToPoint(const til::point anchor) noexcept @@ -117,18 +100,6 @@ const til::point_span* Search::GetCurrent() const noexcept bool Search::SelectCurrent() const { - if (const auto s = GetCurrent()) - { - // Convert buffer selection offsets into the equivalent screen coordinates - // required by SelectNewRegion, taking line renditions into account. - const auto& textBuffer = _renderData->GetTextBuffer(); - const auto selStart = textBuffer.BufferToScreenPosition(s->start); - const auto selEnd = textBuffer.BufferToScreenPosition(s->end); - _renderData->SelectNewRegion(selStart, selEnd); - return true; - } - - _renderData->ClearSelection(); return false; } diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index 802e48e7c3..1c3d163022 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -530,7 +530,7 @@ void TextBuffer::Replace(til::CoordType row, const TextAttribute& attributes, Ro r.ReplaceText(state); r.ReplaceAttributes(state.columnBegin, state.columnEnd, attributes); ImageSlice::EraseCells(r, state.columnBegin, state.columnEnd); - TriggerRedraw(Viewport::FromExclusive({ state.columnBeginDirty, row, state.columnEndDirty, row + 1 })); + TriggerRedraw(); } void TextBuffer::Insert(til::CoordType row, const TextAttribute& attributes, RowWriteState& state) @@ -564,7 +564,7 @@ void TextBuffer::Insert(til::CoordType row, const TextAttribute& attributes, Row // Image content at the insert position needs to be erased. ImageSlice::EraseCells(r, state.columnBegin, restoreState.columnBegin); - TriggerRedraw(Viewport::FromExclusive({ state.columnBeginDirty, row, restoreState.columnEndDirty, row + 1 })); + TriggerRedraw(); } // Fills an area of the buffer with a given fill character(s) and attributes. @@ -618,7 +618,7 @@ void TextBuffer::FillRect(const til::rect& rect, const std::wstring_view& fill, r.CopyTextFrom(state); r.ReplaceAttributes(rect.left, rect.right, attributes); ImageSlice::EraseCells(r, rect.left, rect.right); - TriggerRedraw(Viewport::FromExclusive({ state.columnBeginDirty, y, state.columnEndDirty, y + 1 })); + TriggerRedraw(); } } } @@ -699,10 +699,7 @@ OutputCellIterator TextBuffer::WriteLine(const OutputCellIterator givenIt, auto& row = GetMutableRowByOffset(target.y); const auto newIt = row.WriteCells(givenIt, target.x, wrap, limitRight); - // Take the cell distance written and notify that it needs to be repainted. - const auto written = newIt.GetCellDistance(givenIt); - const auto paint = Viewport::FromDimensions(target, { written, 1 }); - TriggerRedraw(paint); + TriggerRedraw(); return newIt; } @@ -919,7 +916,7 @@ void TextBuffer::SetCurrentLineRendition(const LineRendition lineRendition, cons // We also need to make sure the cursor is clamped within the new width. GetCursor().SetPosition(ClampPositionWithinLine(cursorPosition)); } - TriggerRedraw(Viewport::FromDimensions({ 0, rowIndex }, { GetSize().Width(), 1 })); + TriggerRedraw(); } } @@ -1070,52 +1067,17 @@ Microsoft::Console::Render::Renderer* TextBuffer::GetRenderer() noexcept return _renderer; } -void TextBuffer::NotifyPaintFrame() noexcept +void TextBuffer::TriggerRedraw() noexcept { if (_isActiveBuffer && _renderer) { - _renderer->NotifyPaintFrame(); - } -} - -void TextBuffer::TriggerRedraw(const Viewport& viewport) -{ - if (_isActiveBuffer && _renderer) - { - _renderer->TriggerRedraw(viewport); - } -} - -void TextBuffer::TriggerRedrawAll() -{ - if (_isActiveBuffer && _renderer) - { - _renderer->TriggerRedrawAll(); - } -} - -void TextBuffer::TriggerScroll() -{ - if (_isActiveBuffer && _renderer) - { - _renderer->TriggerScroll(); - } -} - -void TextBuffer::TriggerScroll(const til::point delta) -{ - if (_isActiveBuffer && _renderer) - { - _renderer->TriggerScroll(&delta); + _renderer->TriggerRedraw(); } } void TextBuffer::TriggerNewTextNotification(const std::wstring_view newText) { - if (_isActiveBuffer && _renderer) - { - _renderer->TriggerNewTextNotification(newText); - } + UNREFERENCED_PARAMETER(newText); } // Method Description: diff --git a/src/buffer/out/textBuffer.hpp b/src/buffer/out/textBuffer.hpp index a39ebd4db4..16a94fed33 100644 --- a/src/buffer/out/textBuffer.hpp +++ b/src/buffer/out/textBuffer.hpp @@ -68,6 +68,7 @@ namespace Microsoft::Console::Render class TextBuffer final { public: + TextBuffer() = default; TextBuffer(const til::size screenBufferSize, const TextAttribute defaultAttributes, const UINT cursorSize, @@ -164,11 +165,7 @@ public: Microsoft::Console::Render::Renderer* GetRenderer() noexcept; - void NotifyPaintFrame() noexcept; - void TriggerRedraw(const Microsoft::Console::Types::Viewport& viewport); - void TriggerRedrawAll(); - void TriggerScroll(); - void TriggerScroll(const til::point delta); + void TriggerRedraw() noexcept; void TriggerNewTextNotification(const std::wstring_view newText); til::point GetWordStart(const til::point target, const std::wstring_view wordDelimiters, bool accessibilityMode = false, std::optional limitOptional = std::nullopt) const; @@ -401,7 +398,7 @@ private: til::CoordType _firstRow = 0; // indexes top row (not necessarily 0) uint64_t _lastMutationId = 0; - Cursor _cursor; + Cursor _cursor{ 25, *this }; bool _isActiveBuffer = false; #ifdef UNIT_TESTING diff --git a/src/host/exe/Host.EXE.vcxproj b/src/host/exe/Host.EXE.vcxproj index 0bc466b548..095a676edd 100644 --- a/src/host/exe/Host.EXE.vcxproj +++ b/src/host/exe/Host.EXE.vcxproj @@ -44,9 +44,6 @@ {af0a096a-8b3a-4949-81ef-7df8f0fee91f} - - {8222900C-8B6C-452A-91AC-BE95DB04B95F} - {1c959542-bac2-4e55-9a6d-13251914cbb9} @@ -92,4 +89,4 @@ - + \ No newline at end of file diff --git a/src/host/exe/Host.EXE.vcxproj.filters b/src/host/exe/Host.EXE.vcxproj.filters index 3b33223f7e..7fc8b05d3e 100644 --- a/src/host/exe/Host.EXE.vcxproj.filters +++ b/src/host/exe/Host.EXE.vcxproj.filters @@ -43,6 +43,7 @@ + diff --git a/src/interactivity/win32/lib/win32.LIB.vcxproj b/src/interactivity/win32/lib/win32.LIB.vcxproj index 6d6df33f5f..63246f3564 100644 --- a/src/interactivity/win32/lib/win32.LIB.vcxproj +++ b/src/interactivity/win32/lib/win32.LIB.vcxproj @@ -57,11 +57,6 @@ - - - {8222900C-8B6C-452A-91AC-BE95DB04B95F} - - ..\..\..\inc;%(AdditionalIncludeDirectories) @@ -70,4 +65,4 @@ - + \ No newline at end of file diff --git a/src/interactivity/win32/lib/win32.LIB.vcxproj.filters b/src/interactivity/win32/lib/win32.LIB.vcxproj.filters index afe3fd0fba..6c30ee34b8 100644 --- a/src/interactivity/win32/lib/win32.LIB.vcxproj.filters +++ b/src/interactivity/win32/lib/win32.LIB.vcxproj.filters @@ -128,5 +128,6 @@ + - + \ No newline at end of file diff --git a/src/renderer/base/RenderEngineBase.cpp b/src/renderer/base/RenderEngineBase.cpp deleted file mode 100644 index 2bfc568c9d..0000000000 --- a/src/renderer/base/RenderEngineBase.cpp +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" -#include "../inc/RenderEngineBase.hpp" -#pragma hdrstop -using namespace Microsoft::Console; -using namespace Microsoft::Console::Render; - -[[nodiscard]] HRESULT RenderEngineBase::InvalidateSelection(std::span /*selections*/) noexcept -{ - return S_OK; -} - -[[nodiscard]] HRESULT RenderEngineBase::InvalidateHighlight(std::span /*highlights*/, const TextBuffer& /*renditions*/) noexcept -{ - return S_OK; -} - -HRESULT RenderEngineBase::InvalidateTitle(const std::wstring_view proposedTitle) noexcept -{ - if (proposedTitle != _lastFrameTitle) - { - _titleChanged = true; - } - - return S_OK; -} - -HRESULT RenderEngineBase::UpdateTitle(const std::wstring_view newTitle) noexcept -{ - auto hr = S_FALSE; - if (newTitle != _lastFrameTitle) - { - RETURN_IF_FAILED(_DoUpdateTitle(newTitle)); - _lastFrameTitle = newTitle; - _titleChanged = false; - hr = S_OK; - } - return hr; -} - -HRESULT RenderEngineBase::NotifyNewText(const std::wstring_view /*newText*/) noexcept -{ - return S_FALSE; -} - -HRESULT RenderEngineBase::UpdateSoftFont(const std::span /*bitPattern*/, - const til::size /*cellSize*/, - const size_t /*centeringHint*/) noexcept -{ - return S_FALSE; -} - -HRESULT RenderEngineBase::PrepareRenderInfo(RenderFrameInfo /*info*/) noexcept -{ - return S_FALSE; -} - -HRESULT RenderEngineBase::ResetLineTransform() noexcept -{ - return S_FALSE; -} - -HRESULT RenderEngineBase::PrepareLineTransform(const LineRendition /*lineRendition*/, - const til::CoordType /*targetRow*/, - const til::CoordType /*viewportLeft*/) noexcept -{ - return S_FALSE; -} - -HRESULT RenderEngineBase::PaintImageSlice(const ImageSlice& /*imageSlice*/, - const til::CoordType /*targetRow*/, - const til::CoordType /*viewportLeft*/) noexcept -{ - return S_FALSE; -} - -// Method Description: -// - By default, no one should need continuous redraw. It ruins performance -// in terms of CPU, memory, and battery life to just paint forever. -// That's why we sleep when there's nothing to draw. -// But if you REALLY WANT to do special effects... you need to keep painting. -[[nodiscard]] bool RenderEngineBase::RequiresContinuousRedraw() noexcept -{ - return false; -} - -// Method Description: -// - Blocks until the engine is able to render without blocking. -void RenderEngineBase::WaitUntilCanRender() noexcept -{ - // Throttle the render loop a bit by default (~60 FPS), improving throughput. - Sleep(8); -} - -void RenderEngineBase::UpdateHyperlinkHoveredId(const uint16_t /*hoveredId*/) noexcept -{ -} diff --git a/src/renderer/base/RenderSettings.cpp b/src/renderer/base/RenderSettings.cpp index 0410211143..3cd6002934 100644 --- a/src/renderer/base/RenderSettings.cpp +++ b/src/renderer/base/RenderSettings.cpp @@ -303,7 +303,7 @@ try _blinkIsInUse = false; if (renderer) { - renderer->TriggerRedrawAll(); + renderer->TriggerRedraw(); } } } diff --git a/src/renderer/base/lib/base.vcxproj b/src/renderer/base/lib/base.vcxproj index 3c177d955e..790a11cc1b 100644 --- a/src/renderer/base/lib/base.vcxproj +++ b/src/renderer/base/lib/base.vcxproj @@ -12,36 +12,24 @@ - - - - - Create - - - - - - - - + \ No newline at end of file diff --git a/src/renderer/base/lib/base.vcxproj.filters b/src/renderer/base/lib/base.vcxproj.filters index 8887f1c610..94138074c2 100644 --- a/src/renderer/base/lib/base.vcxproj.filters +++ b/src/renderer/base/lib/base.vcxproj.filters @@ -18,30 +18,15 @@ - - Source Files - - - Source Files - - - Source Files - Source Files Source Files - - Source Files - Source Files - - Source Files - Source Files @@ -56,36 +41,15 @@ Header Files - - Header Files - - - Header Files\inc - - - Header Files\inc - - - Header Files\inc - Header Files\inc - - Header Files\inc - Header Files\inc Header Files\inc - - Header Files - - - Header Files\inc - Header Files\inc @@ -98,5 +62,6 @@ + - + \ No newline at end of file diff --git a/src/renderer/base/renderer.cpp b/src/renderer/base/renderer.cpp index c44216ac47..0ee94417c5 100644 --- a/src/renderer/base/renderer.cpp +++ b/src/renderer/base/renderer.cpp @@ -4,1468 +4,42 @@ #include "precomp.h" #include "renderer.hpp" -#pragma hdrstop - using namespace Microsoft::Console::Render; using namespace Microsoft::Console::Types; -using PointTree = interval_tree::IntervalTree; - -static constexpr auto maxRetriesForRenderEngine = 3; -// The renderer will wait this number of milliseconds * how many tries have elapsed before trying again. -static constexpr auto renderBackoffBaseTimeMilliseconds{ 150 }; - -#define FOREACH_ENGINE(var) \ - for (auto var : _engines) \ - if (!var) \ - break; \ - else - -// Routine Description: -// - Creates a new renderer controller for a console. -// Arguments: -// - pData - The interface to console data structures required for rendering -// - pEngine - The output engine for targeting each rendering frame -// Return Value: -// - An instance of a Renderer. -Renderer::Renderer(const RenderSettings& renderSettings, - IRenderData* pData, - _In_reads_(cEngines) IRenderEngine** const rgpEngines, - const size_t cEngines, - std::unique_ptr thread) : - _renderSettings(renderSettings), - _pData(pData), - _pThread{ std::move(thread) } +Renderer::Renderer(IRenderData* renderData) : + _renderData{ renderData } { - for (size_t i = 0; i < cEngines; i++) - { - AddRenderEngine(rgpEngines[i]); - } } -// Routine Description: -// - Destroys an instance of a renderer -// Arguments: -// - -// Return Value: -// - Renderer::~Renderer() { - // RenderThread blocks until it has shut down. - _destructing = true; - _pThread.reset(); } -IRenderData* Renderer::GetRenderData() const noexcept +void Renderer::AddRenderEngine(IRenderEngine* const renderEngine) { - return _pData; + _renderEngines.emplace_back(renderEngine); } -// Routine Description: -// - Walks through the console data structures to compose a new frame based on the data that has changed since last call and outputs it to the connected rendering engine. -// Arguments: -// - -// Return Value: -// - HRESULT S_OK, GDI error, Safe Math error, or state/argument errors. -[[nodiscard]] HRESULT Renderer::PaintFrame() +void Renderer::RemoveRenderEngine(IRenderEngine* const renderEngine) { - auto tries = maxRetriesForRenderEngine; - while (tries > 0) - { - if (_destructing) - { - return S_FALSE; - } - - // BODGY: Optimally we would want to retry per engine, but that causes different - // problems (intermittent inconsistent states between text renderer and UIA output, - // not being able to lock the cursor location, etc.). - const auto hr = _PaintFrame(); - if (SUCCEEDED(hr)) - { - break; - } - - LOG_HR_IF(hr, hr != E_PENDING); - - if (--tries == 0) - { - // Stop trying. - _pThread->DisablePainting(); - if (_pfnRendererEnteredErrorState) - { - _pfnRendererEnteredErrorState(); - } - // If there's no callback, we still don't want to FAIL_FAST: the renderer going black - // isn't near as bad as the entire application aborting. We're a component. We shouldn't - // abort applications that host us. - return S_FALSE; - } - - // Add a bit of backoff. - // Sleep 150ms, 300ms, 450ms before failing out and disabling the renderer. - Sleep(renderBackoffBaseTimeMilliseconds * (maxRetriesForRenderEngine - tries)); - } - - return S_OK; + _renderEngines.erase(std::remove(_renderEngines.begin(), _renderEngines.end(), renderEngine), _renderEngines.end()); } -[[nodiscard]] HRESULT Renderer::_PaintFrame() noexcept +void Renderer::TriggerRedraw() noexcept { - { - _pData->LockConsole(); - auto unlock = wil::scope_exit([&]() { - _pData->UnlockConsole(); - }); - - // Last chance check if anything scrolled without an explicit invalidate notification since the last frame. - _CheckViewportAndScroll(); - - _invalidateCurrentCursor(); // Invalidate the previous cursor position. - _invalidateOldComposition(); - - _updateCursorInfo(); - _compositionCache.reset(); - - _invalidateCurrentCursor(); // Invalidate the new cursor position. - _prepareNewComposition(); - - FOREACH_ENGINE(pEngine) - { - RETURN_IF_FAILED(_PaintFrameForEngine(pEngine)); - } - } - - FOREACH_ENGINE(pEngine) - { - RETURN_IF_FAILED(pEngine->Present()); - } - - return S_OK; } -[[nodiscard]] HRESULT Renderer::_PaintFrameForEngine(_In_ IRenderEngine* const pEngine) noexcept -try -{ - FAIL_FAST_IF_NULL(pEngine); // This is a programming error. Fail fast. - - // Try to start painting a frame - const auto hr = pEngine->StartPaint(); - RETURN_IF_FAILED(hr); - - // Return early if there's nothing to paint. - // The renderer itself tracks if there's something to do with the title, the - // engine won't know that. - if (S_FALSE == hr) - { - return S_OK; - } - - auto endPaint = wil::scope_exit([&]() { - LOG_IF_FAILED(pEngine->EndPaint()); - - // If the engine tells us it really wants to redraw immediately, - // tell the thread so it doesn't go to sleep and ticks again - // at the next opportunity. - if (pEngine->RequiresContinuousRedraw()) - { - NotifyPaintFrame(); - } - }); - - // A. Prep Colors - RETURN_IF_FAILED(_UpdateDrawingBrushes(pEngine, {}, false, true)); - - // B. Perform Scroll Operations - RETURN_IF_FAILED(_PerformScrolling(pEngine)); - - // C. Prepare the engine with additional information before we start drawing. - RETURN_IF_FAILED(_PrepareRenderInfo(pEngine)); - - // 1. Paint Background - RETURN_IF_FAILED(_PaintBackground(pEngine)); - - // 2. Paint Rows of Text - _PaintBufferOutput(pEngine); - - // 4. Paint Selection - _PaintSelection(pEngine); - - // 5. Paint Cursor - _PaintCursor(pEngine); - - // 6. Paint window title - RETURN_IF_FAILED(_PaintTitle(pEngine)); - - // Force scope exit end paint to finish up collecting information and possibly painting - endPaint.reset(); - - // As we leave the scope, EndPaint will be called (declared above) - return S_OK; -} -CATCH_RETURN() - -void Renderer::NotifyPaintFrame() noexcept -{ - // If we're running in the unittests, we might not have a render thread. - if (_pThread) - { - // The thread will provide throttling for us. - _pThread->NotifyPaint(); - } -} - -// Routine Description: -// - Called when the system has requested we redraw a portion of the console. -// Arguments: -// - -// Return Value: -// - -void Renderer::TriggerSystemRedraw(const til::rect* const prcDirtyClient) -{ - FOREACH_ENGINE(pEngine) - { - LOG_IF_FAILED(pEngine->InvalidateSystem(prcDirtyClient)); - } - - NotifyPaintFrame(); -} - -// Routine Description: -// - Called when a particular region within the console buffer has changed. -// Arguments: -// - -// Return Value: -// - -void Renderer::TriggerRedraw(const Viewport& region) -{ - auto view = _pData->GetViewport(); - auto srUpdateRegion = region.ToExclusive(); - - // If the dirty region has double width lines, we need to double the size of - // the right margin to make sure all the affected cells are invalidated. - const auto& buffer = _pData->GetTextBuffer(); - for (auto row = srUpdateRegion.top; row < srUpdateRegion.bottom; row++) - { - if (buffer.IsDoubleWidthLine(row)) - { - srUpdateRegion.right *= 2; - break; - } - } - - if (view.TrimToViewport(&srUpdateRegion)) - { - view.ConvertToOrigin(&srUpdateRegion); - FOREACH_ENGINE(pEngine) - { - LOG_IF_FAILED(pEngine->Invalidate(&srUpdateRegion)); - } - - NotifyPaintFrame(); - } -} - -// Routine Description: -// - Called when a particular coordinate within the console buffer has changed. -// Arguments: -// - pcoord: The buffer-space coordinate that has changed. -// Return Value: -// - -void Renderer::TriggerRedraw(const til::point* const pcoord) -{ - TriggerRedraw(Viewport::FromDimensions(*pcoord, { 1, 1 })); // this will notify to paint if we need it. -} - -// Routine Description: -// - Called when something that changes the output state has occurred and the entire frame is now potentially invalid. -// - NOTE: Use sparingly. Try to reduce the refresh region where possible. Only use when a global state change has occurred. -// Arguments: -// - backgroundChanged - Set to true if the background color has changed. -// - frameChanged - Set to true if the frame colors have changed. -// Return Value: -// - -void Renderer::TriggerRedrawAll(const bool backgroundChanged, const bool frameChanged) -{ - FOREACH_ENGINE(pEngine) - { - LOG_IF_FAILED(pEngine->InvalidateAll()); - } - - NotifyPaintFrame(); - - if (backgroundChanged && _pfnBackgroundColorChanged) - { - _pfnBackgroundColorChanged(); - } - - if (frameChanged && _pfnFrameColorChanged) - { - _pfnFrameColorChanged(); - } -} - -// Method Description: -// - Called when the host is about to die, to give the renderer one last chance -// to paint before the host exits. -// Arguments: -// - -// Return Value: -// - -void Renderer::TriggerTeardown() noexcept -{ - // We need to shut down the paint thread on teardown. - _pThread->WaitForPaintCompletionAndDisable(INFINITE); -} - -// Routine Description: -// - Called when the selected area in the console has changed. -// Arguments: -// - -// Return Value: -// - -void Renderer::TriggerSelection() -try -{ - const auto spans = _pData->GetSelectionSpans(); - if (spans.size() != _lastSelectionPaintSize || (!spans.empty() && _lastSelectionPaintSpan != til::point_span{ spans.front().start, spans.back().end })) - { - std::vector newSelectionViewportRects; - - _lastSelectionPaintSize = spans.size(); - if (_lastSelectionPaintSize) - { - _lastSelectionPaintSpan = til::point_span{ spans.front().start, spans.back().end }; - - const auto& buffer = _pData->GetTextBuffer(); - const auto bufferWidth = buffer.GetSize().Width(); - const til::rect vp{ _viewport.ToExclusive() }; - for (auto&& sp : spans) - { - sp.iterate_rows(bufferWidth, [&](til::CoordType row, til::CoordType min, til::CoordType max) { - const auto shift = buffer.GetLineRendition(row) != LineRendition::SingleWidth ? 1 : 0; - max += 1; // Selection spans are inclusive (still) - min <<= shift; - max <<= shift; - til::rect r{ min, row, max, row + 1 }; - newSelectionViewportRects.emplace_back(r.to_origin(vp)); - }); - } - } - - FOREACH_ENGINE(pEngine) - { - LOG_IF_FAILED(pEngine->InvalidateSelection(_lastSelectionRectsByViewport)); - LOG_IF_FAILED(pEngine->InvalidateSelection(newSelectionViewportRects)); - } - - std::exchange(_lastSelectionRectsByViewport, newSelectionViewportRects); - - NotifyPaintFrame(); - } -} -CATCH_LOG() - -// Routine Description: -// - Called when the search highlight areas in the console have changed. -void Renderer::TriggerSearchHighlight(const std::vector& oldHighlights) -try -{ - // no need to invalidate focused search highlight separately as they are - // included in (all) search highlights. - const auto newHighlights = _pData->GetSearchHighlights(); - - if (oldHighlights.empty() && newHighlights.empty()) - { - return; - } - - const auto& buffer = _pData->GetTextBuffer(); - - FOREACH_ENGINE(pEngine) - { - LOG_IF_FAILED(pEngine->InvalidateHighlight(oldHighlights, buffer)); - LOG_IF_FAILED(pEngine->InvalidateHighlight(newHighlights, buffer)); - } - - NotifyPaintFrame(); -} -CATCH_LOG() - -// Routine Description: -// - Called when we want to check if the viewport has moved and scroll accordingly if so. -// Arguments: -// - -// Return Value: -// - True if something changed and we scrolled. False otherwise. -bool Renderer::_CheckViewportAndScroll() -{ - const auto srOldViewport = _viewport.ToInclusive(); - const auto srNewViewport = _pData->GetViewport().ToInclusive(); - - if (!_forceUpdateViewport && srOldViewport == srNewViewport) - { - return false; - } - - _viewport = Viewport::FromInclusive(srNewViewport); - _forceUpdateViewport = false; - - til::point coordDelta; - coordDelta.x = srOldViewport.left - srNewViewport.left; - coordDelta.y = srOldViewport.top - srNewViewport.top; - - FOREACH_ENGINE(engine) - { - LOG_IF_FAILED(engine->UpdateViewport(srNewViewport)); - LOG_IF_FAILED(engine->InvalidateScroll(&coordDelta)); - } - - _ScrollPreviousSelection(coordDelta); - - // The cursor may have moved out of or into the viewport. Update the .inViewport property. - { - const auto view = ScreenToBufferLine(srNewViewport, _currentCursorOptions.lineRendition); - auto coordCursor = _currentCursorOptions.coordCursor; - - // `coordCursor` was stored in viewport-relative while `view` is in absolute coordinates. - // --> Turn it back into the absolute coordinates with the help of the viewport. - // We have to use the new viewport, because _ScrollPreviousSelection adjusts the cursor position to match the new one. - coordCursor.y += srNewViewport.top; - - // Note that we allow the X coordinate to be outside the left border by 1 position, - // because the cursor could still be visible if the focused character is double width. - const auto xInRange = coordCursor.x >= view.left - 1 && coordCursor.x <= view.right; - const auto yInRange = coordCursor.y >= view.top && coordCursor.y <= view.bottom; - - _currentCursorOptions.inViewport = xInRange && yInRange; - } - - return true; -} - -// Routine Description: -// - Called when a scroll operation has occurred by manipulating the viewport. -// - This is a special case as calling out scrolls explicitly drastically improves performance. -// Arguments: -// - -// Return Value: -// - -void Renderer::TriggerScroll() -{ - if (_CheckViewportAndScroll()) - { - NotifyPaintFrame(); - } -} - -// Routine Description: -// - Called when a scroll operation wishes to explicitly adjust the frame by the given coordinate distance. -// - This is a special case as calling out scrolls explicitly drastically improves performance. -// - This should only be used when the viewport is not modified. It lets us know we can "scroll anyway" to save perf, -// because the backing circular buffer rotated out from behind the viewport. -// Arguments: -// - -// Return Value: -// - -void Renderer::TriggerScroll(const til::point* const pcoordDelta) -{ - FOREACH_ENGINE(pEngine) - { - LOG_IF_FAILED(pEngine->InvalidateScroll(pcoordDelta)); - } - - _ScrollPreviousSelection(*pcoordDelta); - - NotifyPaintFrame(); -} - -// Routine Description: -// - Called when the title of the console window has changed. Indicates that we -// should update the title on the next frame. -// Arguments: -// - -// Return Value: -// - -void Renderer::TriggerTitleChange() -{ - const auto newTitle = _pData->GetConsoleTitle(); - FOREACH_ENGINE(pEngine) - { - LOG_IF_FAILED(pEngine->InvalidateTitle(newTitle)); - } - NotifyPaintFrame(); -} - -void Renderer::TriggerNewTextNotification(const std::wstring_view newText) -{ - FOREACH_ENGINE(pEngine) - { - LOG_IF_FAILED(pEngine->NotifyNewText(newText)); - } -} - -// Routine Description: -// - Update the title for a particular engine. -// Arguments: -// - pEngine: the engine to update the title for. -// Return Value: -// - the HRESULT of the underlying engine's UpdateTitle call. -HRESULT Renderer::_PaintTitle(IRenderEngine* const pEngine) -{ - const auto newTitle = _pData->GetConsoleTitle(); - return pEngine->UpdateTitle(newTitle); -} - -// Routine Description: -// - Called when a change in font or DPI has been detected. -// Arguments: -// - iDpi - New DPI value -// - FontInfoDesired - A description of the font we would like to have. -// - FontInfo - Data that will be fixed up/filled on return with the chosen font data. -// Return Value: -// - -void Renderer::TriggerFontChange(const int iDpi, const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo) -{ - FOREACH_ENGINE(pEngine) - { - LOG_IF_FAILED(pEngine->UpdateDpi(iDpi)); - LOG_IF_FAILED(pEngine->UpdateFont(FontInfoDesired, FontInfo)); - } - - NotifyPaintFrame(); -} - -// Routine Description: -// - Called when the active soft font has been updated. -// Arguments: -// - bitPattern - An array of scanlines representing all the glyphs in the font. -// - cellSize - The cell size for an individual glyph. -// - centeringHint - The horizontal extent that glyphs are offset from center. -// Return Value: -// - -void Renderer::UpdateSoftFont(const std::span bitPattern, const til::size cellSize, const size_t centeringHint) -{ - // We reserve PUA code points U+EF20 to U+EF7F for soft fonts, but the range - // that we test for in _IsSoftFontChar will depend on the size of the active - // bitPattern. If it's empty (i.e. no soft font is set), then nothing will - // match, and those code points will be treated the same as everything else. - const auto softFontCharCount = cellSize.height ? bitPattern.size() / cellSize.height : 0; - _lastSoftFontChar = _firstSoftFontChar + softFontCharCount - 1; - - FOREACH_ENGINE(pEngine) - { - LOG_IF_FAILED(pEngine->UpdateSoftFont(bitPattern, cellSize, centeringHint)); - } - TriggerRedrawAll(); -} - -// We initially tried to have a "_isSoftFontChar" member function, but MSVC -// failed to inline it at _all_ call sites (check invocations inside loops). -// This issue strangely doesn't occur with static functions. -bool Renderer::s_IsSoftFontChar(const std::wstring_view& v, const size_t firstSoftFontChar, const size_t lastSoftFontChar) -{ - return v.size() == 1 && v[0] >= firstSoftFontChar && v[0] <= lastSoftFontChar; -} - -// Routine Description: -// - Get the information on what font we would be using if we decided to create a font with the given parameters -// - This is for use with speculative calculations. -// Arguments: -// - iDpi - The DPI of the target display -// - pFontInfoDesired - A description of the font we would like to have. -// - pFontInfo - Data that will be fixed up/filled on return with the chosen font data. -// Return Value: -// - S_OK if set successfully or relevant GDI error via HRESULT. -[[nodiscard]] HRESULT Renderer::GetProposedFont(const int iDpi, const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo) -{ - // There will only every really be two engines - the real head and the VT - // renderer. We won't know which is which, so iterate over them. - // Only return the result of the successful one if it's not S_FALSE (which is the VT renderer) - // TODO: 14560740 - The Window might be able to get at this info in a more sane manner - FOREACH_ENGINE(pEngine) - { - const auto hr = LOG_IF_FAILED(pEngine->GetProposedFont(FontInfoDesired, FontInfo, iDpi)); - // We're looking for specifically S_OK, S_FALSE is not good enough. - if (hr == S_OK) - { - return hr; - } - } - - return E_FAIL; -} - -// Routine Description: -// - Tests against the current rendering engine to see if this particular character would be considered -// full-width (inscribed in a square, twice as wide as a standard Western character, typically used for CJK -// languages) or half-width. -// - Typically used to determine how many positions in the backing buffer a particular character should fill. -// NOTE: This only handles 1 or 2 wide (in monospace terms) characters. -// Arguments: -// - glyph - the utf16 encoded codepoint to test -// Return Value: -// - True if the codepoint is full-width (two wide), false if it is half-width (one wide). bool Renderer::IsGlyphWideByFont(const std::wstring_view glyph) { - auto fIsFullWidth = false; - - // There will only every really be two engines - the real head and the VT - // renderer. We won't know which is which, so iterate over them. - // Only return the result of the successful one if it's not S_FALSE (which is the VT renderer) - // TODO: 14560740 - The Window might be able to get at this info in a more sane manner - FOREACH_ENGINE(pEngine) - { - const auto hr = LOG_IF_FAILED(pEngine->IsGlyphWideByFont(glyph, &fIsFullWidth)); - // We're looking for specifically S_OK, S_FALSE is not good enough. - if (hr == S_OK) - { - break; - } - } - - return fIsFullWidth; + return glyph.size() > 1; } -// Routine Description: -// - Sets an event in the render thread that allows it to proceed, thus enabling painting. -// Arguments: -// - -// Return Value: -// - -void Renderer::EnablePainting() +void Renderer::SetRendererEnteredErrorStateCallback(std::function callback) { - // When the renderer is constructed, the initial viewport won't be available yet, - // but once EnablePainting is called it should be safe to retrieve. - _viewport = _pData->GetViewport(); - - // When running the unit tests, we may be using a render without a render thread. - if (_pThread) - { - _pThread->EnablePainting(); - } + _errorStateCallback = std::move(callback); } -// Routine Description: -// - Waits for the current paint operation to complete, if any, up to the specified timeout. -// - Resets an event in the render thread that precludes it from advancing, thus disabling rendering. -// - If no paint operation is currently underway, returns immediately. -// Arguments: -// - dwTimeoutMs - Milliseconds to wait for the current paint operation to complete, if any (can be INFINITE). -// Return Value: -// - -void Renderer::WaitForPaintCompletionAndDisable(const DWORD dwTimeoutMs) -{ - _pThread->WaitForPaintCompletionAndDisable(dwTimeoutMs); -} - -// Routine Description: -// - Paint helper to fill in the background color of the invalid area within the frame. -// Arguments: -// - -// Return Value: -// - -[[nodiscard]] HRESULT Renderer::_PaintBackground(_In_ IRenderEngine* const pEngine) -{ - return pEngine->PaintBackground(); -} - -// Routine Description: -// - Paint helper to copy the primary console buffer text onto the screen. -// - This portion primarily handles figuring the current viewport, comparing it/trimming it versus the invalid portion of the frame, and queuing up, row by row, which pieces of text need to be further processed. -// - See also: Helper functions that separate out each complexity of text rendering. -// Arguments: -// - -// Return Value: -// - -void Renderer::_PaintBufferOutput(_In_ IRenderEngine* const pEngine) -{ - // This is the subsection of the entire screen buffer that is currently being presented. - // It can move left/right or top/bottom depending on how the viewport is scrolled - // relative to the entire buffer. - const auto view = _pData->GetViewport(); - const auto compositionRow = _compositionCache ? _compositionCache->absoluteOrigin.y : -1; - const auto& activeComposition = _pData->GetActiveComposition(); - - // This is effectively the number of cells on the visible screen that need to be redrawn. - // The origin is always 0, 0 because it represents the screen itself, not the underlying buffer. - std::span dirtyAreas; - LOG_IF_FAILED(pEngine->GetDirtyArea(dirtyAreas)); - - // This is to make sure any transforms are reset when this paint is finished. - auto resetLineTransform = wil::scope_exit([&]() { - LOG_IF_FAILED(pEngine->ResetLineTransform()); - }); - - for (const auto& dirtyRect : dirtyAreas) - { - if (!dirtyRect) - { - continue; - } - - auto dirty = Viewport::FromExclusive(dirtyRect); - - // Shift the origin of the dirty region to match the underlying buffer so we can - // compare the two regions directly for intersection. - dirty = Viewport::Offset(dirty, view.Origin()); - - // The intersection between what is dirty on the screen (in need of repaint) - // and what is supposed to be visible on the screen (the viewport) is what - // we need to walk through line-by-line and repaint onto the screen. - const auto redraw = Viewport::Intersect(dirty, view); - - // Retrieve the text buffer so we can read information out of it. - auto& buffer = _pData->GetTextBuffer(); - // Now walk through each row of text that we need to redraw. - for (auto row = redraw.Top(); row < redraw.BottomExclusive(); row++) - { - // Calculate the boundaries of a single line. This is from the left to right edge of the dirty - // area in width and exactly 1 tall. - const auto screenLine = til::inclusive_rect{ redraw.Left(), row, redraw.RightInclusive(), row }; - const auto& r = buffer.GetRowByOffset(row); - - // Draw the active composition. - // We have to use some tricks here with const_cast, because the code after it relies on TextBufferCellIterator, - // which isn't compatible with the scratchpad row. This forces us to back up and modify the actual row `r`. - ROW* rowBackup = nullptr; - if (row == compositionRow) - { - auto& scratch = buffer.GetScratchpadRow(); - scratch.CopyFrom(r); - rowBackup = &scratch; - - std::wstring_view text{ activeComposition.text }; - RowWriteState state{ - .columnLimit = r.GetReadableColumnCount(), - .columnEnd = _compositionCache->absoluteOrigin.x, - }; - - size_t off = 0; - for (const auto& range : activeComposition.attributes) - { - const auto len = range.len; - auto attr = range.attr; - - // Use the color at the cursor if TSF didn't specify any explicit color. - if (attr.GetBackground().IsDefault()) - { - attr.SetBackground(_compositionCache->baseAttribute.GetBackground()); - } - if (attr.GetForeground().IsDefault()) - { - attr.SetForeground(_compositionCache->baseAttribute.GetForeground()); - } - - state.text = text.substr(off, len); - state.columnBegin = state.columnEnd; - const_cast(r).ReplaceText(state); - const_cast(r).ReplaceAttributes(state.columnBegin, state.columnEnd, attr); - off += len; - } - } - const auto restore = wil::scope_exit([&] { - if (rowBackup) - { - const_cast(r).CopyFrom(*rowBackup); - } - }); - - // Convert the screen coordinates of the line to an equivalent - // range of buffer cells, taking line rendition into account. - const auto lineRendition = buffer.GetLineRendition(row); - const auto bufferLine = Viewport::FromInclusive(ScreenToBufferLine(screenLine, lineRendition)); - - // Find where on the screen we should place this line information. This requires us to re-map - // the buffer-based origin of the line back onto the screen-based origin of the line. - // For example, the screen might say we need to paint line 1 because it is dirty but the viewport - // is actually looking at line 26 relative to the buffer. This means that we need line 27 out - // of the backing buffer to fill in line 1 of the screen. - const auto screenPosition = bufferLine.Origin() - til::point{ 0, view.Top() }; - - // Retrieve the cell information iterator limited to just this line we want to redraw. - auto it = buffer.GetCellDataAt(bufferLine.Origin(), bufferLine); - - // Calculate if two things are true: - // 1. this row wrapped - // 2. We're painting the last col of the row. - // In that case, set lineWrapped=true for the _PaintBufferOutputHelper call. - const auto lineWrapped = (buffer.GetRowByOffset(bufferLine.Origin().y).WasWrapForced()) && - (bufferLine.RightExclusive() == buffer.GetSize().Width()); - - // Prepare the appropriate line transform for the current row and viewport offset. - LOG_IF_FAILED(pEngine->PrepareLineTransform(lineRendition, screenPosition.y, view.Left())); - - // Ask the helper to paint through this specific line. - _PaintBufferOutputHelper(pEngine, it, screenPosition, lineWrapped); - - // Paint any image content on top of the text. - const auto imageSlice = buffer.GetRowByOffset(row).GetImageSlice(); - if (imageSlice) [[unlikely]] - { - LOG_IF_FAILED(pEngine->PaintImageSlice(*imageSlice, screenPosition.y, view.Left())); - } - } - } -} - -static bool _IsAllSpaces(const std::wstring_view v) -{ - // first non-space char is not found (is npos) - return v.find_first_not_of(L' ') == decltype(v)::npos; -} - -void Renderer::_PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine, - TextBufferCellIterator it, - const til::point target, - const bool lineWrapped) -{ - auto globalInvert{ _renderSettings.GetRenderMode(RenderSettings::Mode::ScreenReversed) }; - - // If we have valid data, let's figure out how to draw it. - if (it) - { - // TODO: MSFT: 20961091 - This is a perf issue. Instead of rebuilding this and allocing memory to hold the reinterpretation, - // we should have an iterator/view adapter for the rendering. - // That would probably also eliminate the RenderData needing to give us the entire TextBuffer as well... - // Retrieve the iterator for one line of information. - til::CoordType cols = 0; - - // Retrieve the first color. - auto color = it->TextAttr(); - // Retrieve the first pattern id - auto patternIds = _pData->GetPatternId(target); - // Determine whether we're using a soft font. - auto usingSoftFont = s_IsSoftFontChar(it->Chars(), _firstSoftFontChar, _lastSoftFontChar); - - // And hold the point where we should start drawing. - auto screenPoint = target; - - // This outer loop will continue until we reach the end of the text we are trying to draw. - while (it) - { - // Hold onto the current run color right here for the length of the outer loop. - // We'll be changing the persistent one as we run through the inner loops to detect - // when a run changes, but we will still need to know this color at the bottom - // when we go to draw gridlines for the length of the run. - const auto currentRunColor = color; - - // Hold onto the current pattern id as well - const auto currentPatternId = patternIds; - - // Update the drawing brushes with our color and font usage. - THROW_IF_FAILED(_UpdateDrawingBrushes(pEngine, currentRunColor, usingSoftFont, false)); - - // Advance the point by however many columns we've just outputted and reset the accumulator. - screenPoint.x += cols; - cols = 0; - - // Hold onto the start of this run iterator and the target location where we started - // in case we need to do some special work to paint the line drawing characters. - const auto currentRunItStart = it; - const auto currentRunTargetStart = screenPoint; - - // Ensure that our cluster vector is clear. - _clusterBuffer.clear(); - - // Reset our flag to know when we're in the special circumstance - // of attempting to draw only the right-half of a two-column character - // as the first item in our run. - auto trimLeft = false; - - // Run contains wide character (>1 columns) - auto containsWideCharacter = false; - - // This inner loop will accumulate clusters until the color changes. - // When the color changes, it will save the new color off and break. - // We also accumulate clusters according to regex patterns - do - { - til::point thisPoint{ screenPoint.x + cols, screenPoint.y }; - const auto thisPointPatterns = _pData->GetPatternId(thisPoint); - const auto thisUsingSoftFont = s_IsSoftFontChar(it->Chars(), _firstSoftFontChar, _lastSoftFontChar); - const auto changedPatternOrFont = patternIds != thisPointPatterns || usingSoftFont != thisUsingSoftFont; - if (color != it->TextAttr() || changedPatternOrFont) - { - auto newAttr{ it->TextAttr() }; - // foreground doesn't matter for runs of spaces (!) - // if we trick it . . . we call Paint far fewer times for cmatrix - if (!_IsAllSpaces(it->Chars()) || !newAttr.HasIdenticalVisualRepresentationForBlankSpace(color, globalInvert) || changedPatternOrFont) - { - color = newAttr; - patternIds = thisPointPatterns; - usingSoftFont = thisUsingSoftFont; - break; // vend this run - } - } - - // Walk through the text data and turn it into rendering clusters. - // Keep the columnCount as we go to improve performance over digging it out of the vector at the end. - auto columnCount = it->Columns(); - - // If we're on the first cluster to be added and it's marked as "trailing" - // (a.k.a. the right half of a two column character), then we need some special handling. - if (_clusterBuffer.empty() && it->DbcsAttr() == DbcsAttribute::Trailing) - { - // Move left to the one so the whole character can be struck correctly. - --screenPoint.x; - // And tell the next function to trim off the left half of it. - trimLeft = true; - // And add one to the number of columns we expect it to take as we insert it. - ++columnCount; - } - - if (columnCount > 1) - { - containsWideCharacter = true; - } - - // Advance the cluster and column counts. - _clusterBuffer.emplace_back(it->Chars(), columnCount); - it += std::max(it->Columns(), 1); // prevent infinite loop for no visible columns - cols += columnCount; - - } while (it); - - // Do the painting. - THROW_IF_FAILED(pEngine->PaintBufferLine({ _clusterBuffer.data(), _clusterBuffer.size() }, screenPoint, trimLeft, lineWrapped)); - - // If we're allowed to do grid drawing, draw that now too (since it will be coupled with the color data) - // We're only allowed to draw the grid lines under certain circumstances. - if (_pData->IsGridLineDrawingAllowed()) - { - // See GH: 803 - // If we found a wide character while we looped above, it's possible we skipped over the right half - // attribute that could have contained different line information than the left half. - if (containsWideCharacter) - { - // Start from the original position in this run. - auto lineIt = currentRunItStart; - // Start from the original target in this run. - auto lineTarget = currentRunTargetStart; - - // We need to go through the iterators again to ensure we get the lines associated with each - // exact column. The code above will condense two-column characters into one, but it is possible - // (like with the IME) that the line drawing characters will vary from the left to right half - // of a wider character. - // We could theoretically pre-pass for this in the loop above to be more efficient about walking - // the iterator, but I fear it would make the code even more confusing than it already is. - // Do that in the future if some WPR trace points you to this spot as super bad. - for (til::CoordType colsPainted = 0; colsPainted < cols; ++colsPainted, ++lineIt, ++lineTarget.x) - { - auto lines = lineIt->TextAttr(); - _PaintBufferOutputGridLineHelper(pEngine, lines, 1, lineTarget); - } - } - else - { - // If nothing exciting is going on, draw the lines in bulk. - _PaintBufferOutputGridLineHelper(pEngine, currentRunColor, cols, screenPoint); - } - } - } - } -} - -// Method Description: -// - Generates a GridLines structure from the values in the -// provided textAttribute -// Arguments: -// - textAttribute: the TextAttribute to generate GridLines from. -// Return Value: -// - a GridLineSet containing all the gridline info from the TextAttribute -GridLineSet Renderer::s_GetGridlines(const TextAttribute& textAttribute) noexcept -{ - // Convert console grid line representations into rendering engine enum representations. - GridLineSet lines; - - if (textAttribute.IsTopHorizontalDisplayed()) - { - lines.set(GridLines::Top); - } - - if (textAttribute.IsBottomHorizontalDisplayed()) - { - lines.set(GridLines::Bottom); - } - - if (textAttribute.IsLeftVerticalDisplayed()) - { - lines.set(GridLines::Left); - } - - if (textAttribute.IsRightVerticalDisplayed()) - { - lines.set(GridLines::Right); - } - - if (textAttribute.IsCrossedOut()) - { - lines.set(GridLines::Strikethrough); - } - - const auto underlineStyle = textAttribute.GetUnderlineStyle(); - switch (underlineStyle) - { - case UnderlineStyle::NoUnderline: - break; - case UnderlineStyle::SinglyUnderlined: - lines.set(GridLines::Underline); - break; - case UnderlineStyle::DoublyUnderlined: - lines.set(GridLines::DoubleUnderline); - break; - 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; - } - - if (textAttribute.IsHyperlink()) - { - lines.set(GridLines::HyperlinkUnderline); - } - return lines; -} - -// Routine Description: -// - Paint helper for primary buffer output function. -// - This particular helper sets up the various box drawing lines that can be inscribed around any character in the buffer (left, right, top, underline). -// - See also: All related helpers and buffer output functions. -// Arguments: -// - textAttribute - The line/box drawing attributes to use for this particular run. -// - cchLine - The length of both pwsLine and pbKAttrsLine. -// - coordTarget - The X/Y coordinate position in the buffer which we're attempting to start rendering from. -// Return Value: -// - -void Renderer::_PaintBufferOutputGridLineHelper(_In_ IRenderEngine* const pEngine, - const TextAttribute textAttribute, - const size_t cchLine, - const til::point coordTarget) -{ - // Convert console grid line representations into rendering engine enum representations. - auto lines = Renderer::s_GetGridlines(textAttribute); - - // For now, we dash underline patterns and switch to regular underline on hover - if (_isHoveredHyperlink(textAttribute) || _isInHoveredInterval(coordTarget)) - { - lines.reset(GridLines::HyperlinkUnderline); - lines.set(GridLines::Underline); - } - - // Return early if there are no lines to paint. - if (lines.any()) - { - // 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, fg, underlineColor, cchLine, coordTarget)); - } -} - -bool Renderer::_isHoveredHyperlink(const TextAttribute& textAttribute) const noexcept -{ - return _hyperlinkHoveredId && _hyperlinkHoveredId == textAttribute.GetHyperlinkId(); -} - -bool Renderer::_isInHoveredInterval(const til::point coordTarget) const noexcept -{ - return _hoveredInterval && - _hoveredInterval->start <= coordTarget && coordTarget <= _hoveredInterval->stop && - _pData->GetPatternId(coordTarget).size() > 0; -} - -// Routine Description: -// - Retrieve information about the cursor, and pack it into a CursorOptions -// which the render engine can use for painting the cursor. -// - If the cursor is "off", or the cursor is out of bounds of the viewport, -// this will return nullopt (indicating the cursor shouldn't be painted this -// frame) -// Arguments: -// - -// Return Value: -// - nullopt if the cursor is off or out-of-frame, otherwise a CursorOptions -void Renderer::_updateCursorInfo() -{ - // Get cursor position in buffer - auto coordCursor = _pData->GetCursorPosition(); - - // GH#3166: Only draw the cursor if it's actually in the viewport. It - // might be on the line that's in that partially visible row at the - // bottom of the viewport, the space that's not quite a full line in - // height. Since we don't draw that text, we shouldn't draw the cursor - // there either. - - // The cursor is never rendered as double height, so we don't care about - // the exact line rendition - only whether it's double width or not. - const auto doubleWidth = _pData->GetTextBuffer().IsDoubleWidthLine(coordCursor.y); - const auto lineRendition = doubleWidth ? LineRendition::DoubleWidth : LineRendition::SingleWidth; - - // We need to convert the screen coordinates of the viewport to an - // equivalent range of buffer cells, taking line rendition into account. - const auto viewport = _pData->GetViewport().ToInclusive(); - const auto view = ScreenToBufferLine(viewport, lineRendition); - - // Note that we allow the X coordinate to be outside the left border by 1 position, - // because the cursor could still be visible if the focused character is double width. - const auto xInRange = coordCursor.x >= view.left - 1 && coordCursor.x <= view.right; - const auto yInRange = coordCursor.y >= view.top && coordCursor.y <= view.bottom; - - // Adjust cursor Y offset to viewport. - // The viewport X offset is saved in the options and handled with a transform. - coordCursor.y -= view.top; - - const auto cursorColor = _renderSettings.GetColorTableEntry(TextColor::CURSOR_COLOR); - const auto useColor = cursorColor != INVALID_COLOR; - - _currentCursorOptions.coordCursor = coordCursor; - _currentCursorOptions.viewportLeft = viewport.left; - _currentCursorOptions.lineRendition = lineRendition; - _currentCursorOptions.ulCursorHeightPercent = _pData->GetCursorHeight(); - _currentCursorOptions.cursorPixelWidth = _pData->GetCursorPixelWidth(); - _currentCursorOptions.fIsDoubleWidth = _pData->IsCursorDoubleWidth(); - _currentCursorOptions.cursorType = _pData->GetCursorStyle(); - _currentCursorOptions.fUseColor = useColor; - _currentCursorOptions.cursorColor = cursorColor; - _currentCursorOptions.isVisible = _pData->IsCursorVisible(); - _currentCursorOptions.isOn = _currentCursorOptions.isVisible && _pData->IsCursorOn(); - _currentCursorOptions.inViewport = xInRange && yInRange; -} - -void Renderer::_invalidateCurrentCursor() const -{ - if (!_currentCursorOptions.inViewport || !_currentCursorOptions.isOn) - { - return; - } - - const auto& buffer = _pData->GetTextBuffer(); - const auto view = buffer.GetSize(); - const auto coord = _currentCursorOptions.coordCursor; - - const auto lineRendition = _currentCursorOptions.lineRendition; - const auto cursorWidth = _currentCursorOptions.fIsDoubleWidth ? 2 : 1; - const auto x = coord.x - _viewport.Left(); - - til::rect rect{ x, coord.y, x + cursorWidth, coord.y + 1 }; - rect = BufferToScreenLine(rect, lineRendition); - - if (view.TrimToViewport(&rect)) - { - FOREACH_ENGINE(pEngine) - { - LOG_IF_FAILED(pEngine->InvalidateCursor(&rect)); - } - } -} - -// If we had previously drawn a composition at the previous cursor position -// we need to invalidate the entire line because who knows what changed. -// (It's possible to figure that out, but not worth the effort right now.) -void Renderer::_invalidateOldComposition() const -{ - if (!_compositionCache || !_currentCursorOptions.inViewport) - { - return; - } - - const auto& buffer = _pData->GetTextBuffer(); - const auto view = buffer.GetSize(); - const auto coord = _currentCursorOptions.coordCursor; - - til::rect rect{ 0, coord.y, til::CoordTypeMax, coord.y + 1 }; - if (view.TrimToViewport(&rect)) - { - FOREACH_ENGINE(pEngine) - { - LOG_IF_FAILED(pEngine->Invalidate(&rect)); - } - } -} - -// Invalidate the line that the active TSF composition is on, -// so that _PaintBufferOutput() actually gets a chance to draw it. -void Renderer::_prepareNewComposition() -{ - if (_pData->GetActiveComposition().text.empty()) - { - return; - } - - const auto viewport = _pData->GetViewport(); - const auto coordCursor = _pData->GetCursorPosition(); - - til::rect line{ 0, coordCursor.y, til::CoordTypeMax, coordCursor.y + 1 }; - if (viewport.TrimToViewport(&line)) - { - viewport.ConvertToOrigin(&line); - - FOREACH_ENGINE(pEngine) - { - LOG_IF_FAILED(pEngine->Invalidate(&line)); - } - - auto& buffer = _pData->GetTextBuffer(); - auto& scratch = buffer.GetScratchpadRow(); - const auto& activeComposition = _pData->GetActiveComposition(); - - std::wstring_view text{ activeComposition.text }; - RowWriteState state{ - .columnLimit = buffer.GetRowByOffset(line.top).GetReadableColumnCount(), - }; - - state.text = text.substr(0, activeComposition.cursorPos); - scratch.ReplaceText(state); - const auto cursorOffset = state.columnEnd; - - state.text = text.substr(activeComposition.cursorPos); - state.columnBegin = state.columnEnd; - scratch.ReplaceText(state); - - // Ideally the text is inserted at the position of the cursor (`coordCursor`), - // but if we got more text than fits into the remaining space until the end of the line, - // then we'll insert the text aligned to the end of the line. - const auto remaining = state.columnLimit - state.columnEnd; - const auto beg = std::clamp(coordCursor.x, 0, remaining); - - const auto baseAttribute = buffer.GetRowByOffset(coordCursor.y).GetAttrByColumn(coordCursor.x); - _compositionCache.emplace(til::point{ beg, coordCursor.y }, baseAttribute); - - // Fake-move the cursor to where it needs to be in the active composition. - _currentCursorOptions.coordCursor.x = std::min(beg + cursorOffset, line.right - 1); - } -} - -// Routine Description: -// - Paint helper to draw the cursor within the buffer. -// Arguments: -// - engine - The render engine that we're targeting. -// Return Value: -// - -void Renderer::_PaintCursor(_In_ IRenderEngine* const pEngine) -{ - if (_currentCursorOptions.inViewport && _currentCursorOptions.isVisible) - { - LOG_IF_FAILED(pEngine->PaintCursor(_currentCursorOptions)); - } -} - -// Routine Description: -// - Retrieves info from the render data to prepare the engine with, before the -// frame is drawn. Some renderers might want to use this information to affect -// later drawing decisions. -// * Namely, the DX renderer uses this to know the cursor position and state -// before PaintCursor is called, so it can draw the cursor underneath the -// text. -// Arguments: -// - engine - The render engine that we're targeting. -// Return Value: -// - S_OK if the engine prepared successfully, or a relevant error via HRESULT. -[[nodiscard]] HRESULT Renderer::_PrepareRenderInfo(_In_ IRenderEngine* const pEngine) -{ - RenderFrameInfo info; - info.searchHighlights = _pData->GetSearchHighlights(); - info.searchHighlightFocused = _pData->GetSearchHighlightFocused(); - info.selectionSpans = _pData->GetSelectionSpans(); - info.selectionBackground = _renderSettings.GetColorTableEntry(TextColor::SELECTION_BACKGROUND); - return pEngine->PrepareRenderInfo(std::move(info)); -} - -// Routine Description: -// - Paint helper to draw the selected area of the window. -// Arguments: -// - -// Return Value: -// - -void Renderer::_PaintSelection(_In_ IRenderEngine* const pEngine) -{ - try - { - std::span dirtyAreas; - LOG_IF_FAILED(pEngine->GetDirtyArea(dirtyAreas)); - - for (auto&& dirtyRect : dirtyAreas) - { - for (const auto& rect : _lastSelectionRectsByViewport) - { - if (const auto rectCopy{ rect & dirtyRect }) - { - LOG_IF_FAILED(pEngine->PaintSelection(rectCopy)); - } - } - } - } - CATCH_LOG(); -} - -// Routine Description: -// - Helper to convert the text attributes to actual RGB colors and update the rendering pen/brush within the rendering engine before the next draw operation. -// Arguments: -// - pEngine - Which engine is being updated -// - textAttributes - The 16 color foreground/background combination to set -// - usingSoftFont - Whether we're rendering characters from a soft font -// - isSettingDefaultBrushes - Alerts that the default brushes are being set which will -// impact whether or not to include the hung window/erase window brushes in this operation -// and can affect other draw state that wants to know the default color scheme. -// (Usually only happens when the default is changed, not when each individual color is swapped in a multi-color run.) -// Return Value: -// - -[[nodiscard]] HRESULT Renderer::_UpdateDrawingBrushes(_In_ IRenderEngine* const pEngine, - const TextAttribute textAttributes, - const bool usingSoftFont, - const bool isSettingDefaultBrushes) -{ - // The last color needs to be each engine's responsibility. If it's local to this function, - // then on the next engine we might not update the color. - return pEngine->UpdateDrawingBrushes(textAttributes, _renderSettings, _pData, usingSoftFont, isSettingDefaultBrushes); -} - -// Routine Description: -// - Helper called before a majority of paint operations to scroll most of the previous frame into the appropriate -// position before we paint the remaining invalid area. -// - Used to save drawing time/improve performance -// Arguments: -// - -// Return Value: -// - -[[nodiscard]] HRESULT Renderer::_PerformScrolling(_In_ IRenderEngine* const pEngine) -{ - return pEngine->ScrollFrame(); -} - -// Method Description: -// - Offsets all of the selection rectangles we might be holding onto -// as the previously selected area. If the whole viewport scrolls, -// we need to scroll these areas also to ensure they're invalidated -// properly when the selection further changes. -// Arguments: -// - delta - The scroll delta -// Return Value: -// - - Updates internal state instead. -void Renderer::_ScrollPreviousSelection(const til::point delta) -{ - if (delta != til::point{ 0, 0 }) - { - for (auto& rc : _lastSelectionRectsByViewport) - { - rc += delta; - } - - _currentCursorOptions.coordCursor += delta; - } -} - -// Method Description: -// - Adds another Render engine to this renderer. Future rendering calls will -// also be sent to the new renderer. -// Arguments: -// - pEngine: The new render engine to be added -// Return Value: -// - -// Throws if we ran out of memory or there was some other error appending the -// engine to our collection. -void Renderer::AddRenderEngine(_In_ IRenderEngine* const pEngine) -{ - THROW_HR_IF_NULL(E_INVALIDARG, pEngine); - - for (auto& p : _engines) - { - if (!p) - { - p = pEngine; - _forceUpdateViewport = true; - return; - } - } - - THROW_HR_MSG(E_UNEXPECTED, "engines array is full"); -} - -void Renderer::RemoveRenderEngine(_In_ IRenderEngine* const pEngine) -{ - THROW_HR_IF_NULL(E_INVALIDARG, pEngine); - - for (auto& p : _engines) - { - if (p == pEngine) - { - p = nullptr; - return; - } - } -} - -// Method Description: -// - Registers a callback for when the background color is changed -// Arguments: -// - pfn: the callback -// Return Value: -// - -void Renderer::SetBackgroundColorChangedCallback(std::function pfn) -{ - _pfnBackgroundColorChanged = std::move(pfn); -} - -// Method Description: -// - Registers a callback for when the frame colors have changed -// Arguments: -// - pfn: the callback -// Return Value: -// - -void Renderer::SetFrameColorChangedCallback(std::function pfn) -{ - _pfnFrameColorChanged = std::move(pfn); -} - -// Method Description: -// - Registers a callback that will be called when this renderer gives up. -// An application consuming a renderer can use this to display auxiliary Retry UI -// Arguments: -// - pfn: the callback -// Return Value: -// - -void Renderer::SetRendererEnteredErrorStateCallback(std::function pfn) -{ - _pfnRendererEnteredErrorState = std::move(pfn); -} - -// Method Description: -// - Attempts to restart the renderer. void Renderer::ResetErrorStateAndResume() { - // because we're not stateful (we could be in the future), all we want to do is reenable painting. - EnablePainting(); -} - -void Renderer::UpdateHyperlinkHoveredId(uint16_t id) noexcept -{ - _hyperlinkHoveredId = id; - FOREACH_ENGINE(pEngine) - { - pEngine->UpdateHyperlinkHoveredId(id); - } -} - -void Renderer::UpdateLastHoveredInterval(const std::optional& newInterval) -{ - _hoveredInterval = newInterval; -} - -// Method Description: -// - Blocks until the engines are able to render without blocking. -void Renderer::WaitUntilCanRender() -{ - FOREACH_ENGINE(pEngine) - { - pEngine->WaitUntilCanRender(); - } } diff --git a/src/renderer/base/renderer.hpp b/src/renderer/base/renderer.hpp index 8921937462..0aad55c60c 100644 --- a/src/renderer/base/renderer.hpp +++ b/src/renderer/base/renderer.hpp @@ -1,143 +1,30 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- Renderer.hpp - -Abstract: -- This is the definition of our renderer. -- It provides interfaces for the console application to notify when various portions of the console state have changed and need to be redrawn. -- It requires a data interface to fetch relevant console structures required for drawing and a drawing engine target for output. - -Author(s): -- Michael Niksa (MiNiksa) 17-Nov-2015 ---*/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. #pragma once #include "../inc/IRenderEngine.hpp" -#include "../inc/RenderSettings.hpp" - -#include "thread.hpp" - -#include "../../buffer/out/textBuffer.hpp" namespace Microsoft::Console::Render { class Renderer { public: - Renderer(const RenderSettings& renderSettings, - IRenderData* pData, - _In_reads_(cEngines) IRenderEngine** const pEngine, - const size_t cEngines, - std::unique_ptr thread); - + Renderer(IRenderData* renderData); ~Renderer(); + + void AddRenderEngine(_In_ IRenderEngine* pEngine); + void RemoveRenderEngine(_In_ IRenderEngine* pEngine); - IRenderData* GetRenderData() const noexcept; + void TriggerRedraw() noexcept; - [[nodiscard]] HRESULT PaintFrame(); - - void NotifyPaintFrame() noexcept; - void TriggerSystemRedraw(const til::rect* const prcDirtyClient); - void TriggerRedraw(const Microsoft::Console::Types::Viewport& region); - void TriggerRedraw(const til::point* const pcoord); - void TriggerRedrawAll(const bool backgroundChanged = false, const bool frameChanged = false); - void TriggerTeardown() noexcept; - - void TriggerSelection(); - void TriggerSearchHighlight(const std::vector& oldHighlights); - void TriggerScroll(); - void TriggerScroll(const til::point* const pcoordDelta); - - void TriggerTitleChange(); - - void TriggerNewTextNotification(const std::wstring_view newText); - - void TriggerFontChange(const int iDpi, - const FontInfoDesired& FontInfoDesired, - _Out_ FontInfo& FontInfo); - - void UpdateSoftFont(const std::span bitPattern, - const til::size cellSize, - const size_t centeringHint); - - [[nodiscard]] HRESULT GetProposedFont(const int iDpi, - const FontInfoDesired& FontInfoDesired, - _Out_ FontInfo& FontInfo); - - bool IsGlyphWideByFont(const std::wstring_view glyph); - - void EnablePainting(); - void WaitForPaintCompletionAndDisable(const DWORD dwTimeoutMs); - void WaitUntilCanRender(); - - void AddRenderEngine(_In_ IRenderEngine* const pEngine); - void RemoveRenderEngine(_In_ IRenderEngine* const pEngine); - - void SetBackgroundColorChangedCallback(std::function pfn); - void SetFrameColorChangedCallback(std::function pfn); - void SetRendererEnteredErrorStateCallback(std::function pfn); + bool IsGlyphWideByFont(std::wstring_view glyph); + void SetRendererEnteredErrorStateCallback(std::function callback); void ResetErrorStateAndResume(); - void UpdateHyperlinkHoveredId(uint16_t id) noexcept; - void UpdateLastHoveredInterval(const std::optional::interval>& newInterval); - private: - // Caches some essential information about the active composition. - // This allows us to properly invalidate it between frames, etc. - struct CompositionCache - { - til::point absoluteOrigin; - TextAttribute baseAttribute; - }; - - static GridLineSet s_GetGridlines(const TextAttribute& textAttribute) noexcept; - static bool s_IsSoftFontChar(const std::wstring_view& v, const size_t firstSoftFontChar, const size_t lastSoftFontChar); - - [[nodiscard]] HRESULT _PaintFrame() noexcept; - [[nodiscard]] HRESULT _PaintFrameForEngine(_In_ IRenderEngine* const pEngine) noexcept; - bool _CheckViewportAndScroll(); - [[nodiscard]] HRESULT _PaintBackground(_In_ IRenderEngine* const pEngine); - void _PaintBufferOutput(_In_ IRenderEngine* const pEngine); - void _PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine, TextBufferCellIterator it, const til::point target, const bool lineWrapped); - void _PaintBufferOutputGridLineHelper(_In_ IRenderEngine* const pEngine, const TextAttribute textAttribute, const size_t cchLine, const til::point coordTarget); - bool _isHoveredHyperlink(const TextAttribute& textAttribute) const noexcept; - void _PaintSelection(_In_ IRenderEngine* const pEngine); - void _PaintCursor(_In_ IRenderEngine* const pEngine); - [[nodiscard]] HRESULT _UpdateDrawingBrushes(_In_ IRenderEngine* const pEngine, const TextAttribute attr, const bool usingSoftFont, const bool isSettingDefaultBrushes); - [[nodiscard]] HRESULT _PerformScrolling(_In_ IRenderEngine* const pEngine); - void _ScrollPreviousSelection(const til::point delta); - [[nodiscard]] HRESULT _PaintTitle(IRenderEngine* const pEngine); - bool _isInHoveredInterval(til::point coordTarget) const noexcept; - void _updateCursorInfo(); - void _invalidateCurrentCursor() const; - void _invalidateOldComposition() const; - void _prepareNewComposition(); - [[nodiscard]] HRESULT _PrepareRenderInfo(_In_ IRenderEngine* const pEngine); - - const RenderSettings& _renderSettings; - std::array _engines{}; - IRenderData* _pData = nullptr; // Non-ownership pointer - std::unique_ptr _pThread; - static constexpr size_t _firstSoftFontChar = 0xEF20; - size_t _lastSoftFontChar = 0; - uint16_t _hyperlinkHoveredId = 0; - std::optional::interval> _hoveredInterval; - Microsoft::Console::Types::Viewport _viewport; - CursorOptions _currentCursorOptions; - std::optional _compositionCache; - std::vector _clusterBuffer; - std::function _pfnBackgroundColorChanged; - std::function _pfnFrameColorChanged; - std::function _pfnRendererEnteredErrorState; - bool _destructing = false; - bool _forceUpdateViewport = false; - - til::point_span _lastSelectionPaintSpan{}; - size_t _lastSelectionPaintSize{}; - std::vector _lastSelectionRectsByViewport{}; + IRenderData* _renderData; + til::small_vector _renderEngines; + std::function _errorStateCallback; }; } diff --git a/src/renderer/base/thread.cpp b/src/renderer/base/thread.cpp deleted file mode 100644 index 904cf2075a..0000000000 --- a/src/renderer/base/thread.cpp +++ /dev/null @@ -1,301 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" -#include "thread.hpp" - -#include "renderer.hpp" - -#pragma hdrstop - -using namespace Microsoft::Console::Render; - -RenderThread::RenderThread() : - _pRenderer(nullptr), - _hThread(nullptr), - _hEvent(nullptr), - _hPaintCompletedEvent(nullptr), - _fKeepRunning(true), - _hPaintEnabledEvent(nullptr), - _fNextFrameRequested(false), - _fWaiting(false) -{ -} - -RenderThread::~RenderThread() -{ - if (_hThread) - { - _fKeepRunning = false; // stop loop after final run - EnablePainting(); // if we want to get the last frame out, we need to make sure it's enabled - SignalObjectAndWait(_hEvent, _hThread, INFINITE, FALSE); // signal final paint and wait for thread to finish. - - CloseHandle(_hThread); - _hThread = nullptr; - } - - if (_hEvent) - { - CloseHandle(_hEvent); - _hEvent = nullptr; - } - - if (_hPaintEnabledEvent) - { - CloseHandle(_hPaintEnabledEvent); - _hPaintEnabledEvent = nullptr; - } - - if (_hPaintCompletedEvent) - { - CloseHandle(_hPaintCompletedEvent); - _hPaintCompletedEvent = nullptr; - } -} - -// Method Description: -// - Create all of the Events we'll need, and the actual thread we'll be doing -// work on. -// Arguments: -// - pRendererParent: the Renderer that owns this thread, and which we should -// trigger frames for. -// Return Value: -// - S_OK if we succeeded, else an HRESULT corresponding to a failure to create -// an Event or Thread. -[[nodiscard]] HRESULT RenderThread::Initialize(Renderer* const pRendererParent) noexcept -{ - _pRenderer = pRendererParent; - - auto hr = S_OK; - // Create event before thread as thread will start immediately. - if (SUCCEEDED(hr)) - { - auto hEvent = CreateEventW(nullptr, // non-inheritable security attributes - FALSE, // auto reset event - FALSE, // initially unsignaled - nullptr // no name - ); - - if (hEvent == nullptr) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - } - else - { - _hEvent = hEvent; - } - } - - if (SUCCEEDED(hr)) - { - auto hPaintEnabledEvent = CreateEventW(nullptr, - TRUE, // manual reset event - FALSE, // initially signaled - nullptr); - - if (hPaintEnabledEvent == nullptr) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - } - else - { - _hPaintEnabledEvent = hPaintEnabledEvent; - } - } - - if (SUCCEEDED(hr)) - { - auto hPaintCompletedEvent = CreateEventW(nullptr, - TRUE, // manual reset event - TRUE, // initially signaled - nullptr); - - if (hPaintCompletedEvent == nullptr) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - } - else - { - _hPaintCompletedEvent = hPaintCompletedEvent; - } - } - - if (SUCCEEDED(hr)) - { - auto hThread = CreateThread(nullptr, // non-inheritable security attributes - 0, // use default stack size - s_ThreadProc, - this, - 0, // create immediately - nullptr // we don't need the thread ID - ); - - if (hThread == nullptr) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - } - else - { - _hThread = hThread; - - // SetThreadDescription only works on 1607 and higher. If we cannot find it, - // then it's no big deal. Just skip setting the description. - auto func = GetProcAddressByFunctionDeclaration(GetModuleHandleW(L"kernel32.dll"), SetThreadDescription); - if (func) - { - LOG_IF_FAILED(func(hThread, L"Rendering Output Thread")); - } - } - } - - return hr; -} - -DWORD WINAPI RenderThread::s_ThreadProc(_In_ LPVOID lpParameter) -{ - const auto pContext = static_cast(lpParameter); - - if (pContext != nullptr) - { - return pContext->_ThreadProc(); - } - else - { - return (DWORD)E_INVALIDARG; - } -} - -DWORD WINAPI RenderThread::_ThreadProc() -{ - while (_fKeepRunning) - { - // Between waiting on _hEvent and calling PaintFrame() there should be a minimal delay, - // so that a key press progresses to a drawing operation as quickly as possible. - // As such, we wait for the renderer to complete _before_ waiting on _hEvent. - _pRenderer->WaitUntilCanRender(); - - WaitForSingleObject(_hPaintEnabledEvent, INFINITE); - - if (!_fNextFrameRequested.exchange(false, std::memory_order_acq_rel)) - { - // <-- - // If `NotifyPaint` is called at this point, then it will not - // set the event because `_fWaiting` is not `true` yet so we have - // to check again below. - - _fWaiting.store(true, std::memory_order_release); - - // check again now (see comment above) - if (!_fNextFrameRequested.exchange(false, std::memory_order_acq_rel)) - { - // Wait until a next frame is requested. - WaitForSingleObject(_hEvent, INFINITE); - } - - // <-- - // If `NotifyPaint` is called at this point, then it _will_ set - // the event because `_fWaiting` is `true`, but we're not waiting - // anymore! - // This can probably happen quite often: imagine a scenario where - // we are waiting, and the terminal calls `NotifyPaint` twice - // very quickly. - // In that case, both calls might end up calling `SetEvent`. The - // first one will resume this thread and the second one will - // `SetEvent` the event. So the next time we wait, the event will - // already be set and we won't actually wait. - // Because it can happen often, and because rendering is an - // expensive operation, we should reset the event to not render - // again if nothing changed. - - _fWaiting.store(false, std::memory_order_release); - - // see comment above - ResetEvent(_hEvent); - } - - ResetEvent(_hPaintCompletedEvent); - LOG_IF_FAILED(_pRenderer->PaintFrame()); - SetEvent(_hPaintCompletedEvent); - } - - return S_OK; -} - -void RenderThread::NotifyPaint() noexcept -{ - if (_fWaiting.load(std::memory_order_acquire)) - { - SetEvent(_hEvent); - } - else - { - _fNextFrameRequested.store(true, std::memory_order_release); - } -} - -void RenderThread::EnablePainting() noexcept -{ - SetEvent(_hPaintEnabledEvent); -} - -void RenderThread::DisablePainting() noexcept -{ - ResetEvent(_hPaintEnabledEvent); -} - -void RenderThread::WaitForPaintCompletionAndDisable(const DWORD dwTimeoutMs) noexcept -{ - // When rendering takes place via DirectX, and a console application - // currently owns the screen, and a new console application is launched (or - // the user switches to another console application), the new application - // cannot take over the screen until the active one relinquishes it. This - // blocking mechanism goes as follows: - // - // 1. The console input thread of the new console application connects to - // ConIoSrv; - // 2. While servicing the new connection request, ConIoSrv sends an event to - // the active application letting it know that it has lost focus; - // 3.1 ConIoSrv waits for a reply from the client application; - // 3.2 Meanwhile, the active application receives the focus event and calls - // this method, waiting for the current paint operation to - // finish. - // - // This means that the new application is waiting on the connection request - // reply from ConIoSrv, ConIoSrv is waiting on the active application to - // acknowledge the lost focus event to reply to the new application, and the - // console input thread in the active application is waiting on the renderer - // thread to finish its current paint operation. - // - // Question: what should happen if the wait on the paint operation times - // out? - // - // There are three options: - // - // 1. On timeout, the active console application could reply with an error - // message and terminate itself, effectively relinquishing control of the - // display; - // - // 2. ConIoSrv itself could time out on waiting for a reply, and forcibly - // terminate the active console application; - // - // 3. Let the wait time out and let the user deal with it. Because the wait - // occurs on a single iteration of the renderer thread, it seemed to me that - // the likelihood of failure is extremely small, especially since the client - // console application that the active conhost instance is servicing has no - // say over what happens in the renderer thread, only by proxy. Thus, the - // chance of failure (timeout) is minimal and since the OneCoreUAP console - // is not a massively used piece of software, it didn’t seem that it would - // be a good use of time to build the requisite infrastructure to deal with - // a timeout here, at least not for now. In case of a timeout DirectX will - // catch the mistake of a new application attempting to acquire the display - // while another one still owns it and will flag it as a DWM bug. Right now, - // the active application will wait one second for the paint operation to - // finish. - // - // TODO: MSFT: 11833883 - Determine action when wait on paint operation via - // DirectX on OneCoreUAP times out while switching console - // applications. - - ResetEvent(_hPaintEnabledEvent); - WaitForSingleObject(_hPaintCompletedEvent, dwTimeoutMs); -} diff --git a/src/renderer/base/thread.hpp b/src/renderer/base/thread.hpp deleted file mode 100644 index 982eccd99b..0000000000 --- a/src/renderer/base/thread.hpp +++ /dev/null @@ -1,50 +0,0 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- Thread.hpp - -Abstract: -- This is the definition of our rendering thread designed to throttle and compartmentalize drawing operations. - -Author(s): -- Michael Niksa (MiNiksa) Feb 2016 ---*/ - -#pragma once - -namespace Microsoft::Console::Render -{ - class Renderer; - - class RenderThread - { - public: - RenderThread(); - ~RenderThread(); - - [[nodiscard]] HRESULT Initialize(Renderer* const pRendererParent) noexcept; - - void NotifyPaint() noexcept; - void EnablePainting() noexcept; - void DisablePainting() noexcept; - void WaitForPaintCompletionAndDisable(const DWORD dwTimeoutMs) noexcept; - - private: - static DWORD WINAPI s_ThreadProc(_In_ LPVOID lpParameter); - DWORD WINAPI _ThreadProc(); - - HANDLE _hThread; - HANDLE _hEvent; - - HANDLE _hPaintEnabledEvent; - HANDLE _hPaintCompletedEvent; - - Renderer* _pRenderer; // Non-ownership pointer - - bool _fKeepRunning; - std::atomic _fNextFrameRequested; - std::atomic _fWaiting; - }; -} diff --git a/src/renderer/gdi/GdiEngine.cpp b/src/renderer/gdi/GdiEngine.cpp new file mode 100644 index 0000000000..b8658ed33b --- /dev/null +++ b/src/renderer/gdi/GdiEngine.cpp @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "precomp.h" + +#include "GdiEngine.h" + +using namespace Microsoft::Console::Types; +using namespace Microsoft::Console::Render; + +GdiEngine::GdiEngine() +{ +} + +GdiEngine::~GdiEngine() +{ +} + +void GdiEngine::WaitUntilCanRender() +{ +} + +void GdiEngine::Render(RenderingPayload& payload) +{ + UNREFERENCED_PARAMETER(payload); +} diff --git a/src/renderer/gdi/GdiEngine.h b/src/renderer/gdi/GdiEngine.h new file mode 100644 index 0000000000..2288ce1856 --- /dev/null +++ b/src/renderer/gdi/GdiEngine.h @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +#include "../inc/IRenderEngine.hpp" + +namespace Microsoft::Console::Render +{ + class GdiEngine final : public IRenderEngine + { + public: + GdiEngine(); + ~GdiEngine() override; + + void WaitUntilCanRender() override; + void Render(RenderingPayload& payload) override; + }; +} diff --git a/src/renderer/gdi/gdirenderer.hpp b/src/renderer/gdi/gdirenderer.hpp deleted file mode 100644 index d88f10de78..0000000000 --- a/src/renderer/gdi/gdirenderer.hpp +++ /dev/null @@ -1,225 +0,0 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- GdiRenderer.hpp - -Abstract: -- This is the definition of the GDI specific implementation of the renderer. - -Author(s): -- Michael Niksa (MiNiksa) 17-Nov-2015 ---*/ - -#pragma once - -#include "../inc/RenderEngineBase.hpp" -#include "../inc/FontResource.hpp" - -namespace Microsoft::Console::Render -{ - class GdiEngine final : public RenderEngineBase - { - public: - GdiEngine(); - ~GdiEngine() override; - - [[nodiscard]] HRESULT SetHwnd(const HWND hwnd) noexcept; - - [[nodiscard]] HRESULT InvalidateSelection(std::span selections) noexcept override; - [[nodiscard]] HRESULT InvalidateScroll(const til::point* const pcoordDelta) noexcept override; - [[nodiscard]] HRESULT InvalidateSystem(const til::rect* const prcDirtyClient) noexcept override; - [[nodiscard]] HRESULT Invalidate(const til::rect* const psrRegion) noexcept override; - [[nodiscard]] HRESULT InvalidateCursor(const til::rect* const psrRegion) noexcept override; - [[nodiscard]] HRESULT InvalidateAll() noexcept override; - - [[nodiscard]] HRESULT StartPaint() noexcept override; - [[nodiscard]] HRESULT EndPaint() noexcept override; - [[nodiscard]] HRESULT Present() noexcept override; - - [[nodiscard]] HRESULT ScrollFrame() noexcept override; - - [[nodiscard]] HRESULT ResetLineTransform() noexcept override; - [[nodiscard]] HRESULT PrepareLineTransform(const LineRendition lineRendition, - const til::CoordType targetRow, - const til::CoordType viewportLeft) noexcept override; - - [[nodiscard]] HRESULT PaintBackground() noexcept override; - [[nodiscard]] HRESULT PaintBufferLine(const std::span clusters, - const til::point coord, - const bool trimLeft, - const bool lineWrapped) 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 PaintImageSlice(const ImageSlice& imageSlice, - const til::CoordType targetRow, - const til::CoordType viewportLeft) 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; - [[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& FontInfoDesired, - _Out_ FontInfo& FontInfo) noexcept override; - [[nodiscard]] HRESULT UpdateSoftFont(const std::span bitPattern, - const til::size cellSize, - const size_t centeringHint) noexcept override; - [[nodiscard]] HRESULT UpdateDpi(const int iDpi) noexcept override; - [[nodiscard]] HRESULT UpdateViewport(const til::inclusive_rect& srNewViewport) noexcept override; - - [[nodiscard]] HRESULT GetProposedFont(const FontInfoDesired& FontDesired, - _Out_ FontInfo& Font, - const int iDpi) noexcept override; - - [[nodiscard]] HRESULT GetDirtyArea(std::span& area) noexcept override; - [[nodiscard]] HRESULT GetFontSize(_Out_ til::size* const pFontSize) noexcept override; - [[nodiscard]] HRESULT IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept override; - - protected: - [[nodiscard]] HRESULT _DoUpdateTitle(_In_ const std::wstring_view newTitle) noexcept override; - - private: - HWND _hwndTargetWindow; - - [[nodiscard]] static HRESULT s_SetWindowLongWHelper(const HWND hWnd, - const int nIndex, - const LONG dwNewLong) noexcept; - - static bool FontHasWesternScript(HDC hdc); - - bool _fPaintStarted; - - til::rect _invalidCharacters; - PAINTSTRUCT _psInvalidData; - HDC _hdcMemoryContext; - bool _isTrueTypeFont; - UINT _fontCodepage; - HFONT _hfont; - HFONT _hfontItalic; - TEXTMETRICW _tmFontMetrics; - FontResource _softFont; - - static const size_t s_cPolyTextCache = 80; - POLYTEXTW _pPolyText[s_cPolyTextCache]; - size_t _cPolyText; - [[nodiscard]] HRESULT _FlushBufferLines() noexcept; - - std::vector cursorInvertRects; - XFORM cursorInvertTransform; - - struct LineMetrics - { - int gridlineWidth; - int underlineCenter; - int underlineWidth; - int doubleUnderlinePosTop; - int doubleUnderlinePosBottom; - int doubleUnderlineWidth; - int strikethroughOffset; - int strikethroughWidth; - int curlyLineCenter; - int curlyLinePeriod; - int curlyLineControlPointOffset; - }; - - LineMetrics _lineMetrics; - til::size _coordFontLast; - int _iCurrentDpi; - - static const int s_iBaseDpi = USER_DEFAULT_SCREEN_DPI; - - til::size _szMemorySurface; - HBITMAP _hbitmapMemorySurface; - [[nodiscard]] HRESULT _PrepareMemoryBitmap(const HWND hwnd) noexcept; - - til::size _szInvalidScroll; - til::rect _rcInvalid; - bool _fInvalidRectUsed; - - COLORREF _lastFg; - COLORREF _lastBg; - - enum class FontType : uint8_t - { - Undefined, - Default, - Italic, - Soft - }; - FontType _lastFontType; - bool _fontHasWesternScript = false; - - XFORM _currentLineTransform; - LineRendition _currentLineRendition; - - // Memory pooling to save alloc/free work to the OS for things - // frequently created and dropped. - // It's important the pool is first so it can be given to the others on construction. - std::pmr::unsynchronized_pool_resource _pool; - std::pmr::vector _polyStrings; - std::pmr::vector> _polyWidths; - - std::vector _imageMask; - - [[nodiscard]] HRESULT _InvalidCombine(const til::rect* const prc) noexcept; - [[nodiscard]] HRESULT _InvalidOffset(const til::point* const ppt) noexcept; - [[nodiscard]] HRESULT _InvalidRestrict() noexcept; - - [[nodiscard]] HRESULT _InvalidateRect(const til::rect* const prc) noexcept; - - [[nodiscard]] HRESULT _PaintBackgroundColor(const RECT* const prc) noexcept; - - static const ULONG s_ulMinCursorHeightPercent = 25; - static const ULONG s_ulMaxCursorHeightPercent = 100; - - static int s_ScaleByDpi(const int iPx, const int iDpi); - static int s_ShrinkByDpi(const int iPx, const int iDpi); - - til::point _GetInvalidRectPoint() const; - til::size _GetInvalidRectSize() const; - til::size _GetRectSize(const RECT* const pRect) const; - - void _OrRect(_In_ til::rect* const pRectExisting, const til::rect* const pRectToOr) const; - - bool _IsFontTrueType() const; - - [[nodiscard]] HRESULT _GetProposedFont(const FontInfoDesired& FontDesired, - _Out_ FontInfo& Font, - const int iDpi, - _Inout_ wil::unique_hfont& hFont, - _Inout_ wil::unique_hfont& hFontItalic) noexcept; - - til::size _GetFontSize() const; - bool _IsMinimized() const; - bool _IsWindowValid() const; - -#ifdef DBG - // Helper functions to diagnose issues with painting from the in-memory buffer. - // These are only actually effective/on in Debug builds when the flag is set using an attached debugger. - bool _fDebug = false; - void _PaintDebugRect(const RECT* const prc) const; - void _DoDebugBlt(const RECT* const prc) const; - - void _DebugBltAll() const; - - HWND _debugWindow; - void _CreateDebugWindow(); - HDC _debugContext; -#endif - }; - - constexpr XFORM IDENTITY_XFORM = { 1, 0, 0, 1 }; - - inline bool operator==(const XFORM& lhs, const XFORM& rhs) noexcept - { - return ::memcmp(&lhs, &rhs, sizeof(XFORM)) == 0; - }; -} diff --git a/src/renderer/gdi/invalidate.cpp b/src/renderer/gdi/invalidate.cpp deleted file mode 100644 index f24cf1ff1d..0000000000 --- a/src/renderer/gdi/invalidate.cpp +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" - -#include "gdirenderer.hpp" -#include "../../types/inc/Viewport.hpp" -#include "../buffer/out/textBuffer.hpp" - -#pragma hdrstop - -using namespace Microsoft::Console::Types; -using namespace Microsoft::Console::Render; - -// Routine Description: -// - Notifies us that the system has requested a particular pixel area of the client rectangle should be redrawn. (On WM_PAINT) -// Arguments: -// - prcDirtyClient - Pointer to pixel area (til::rect) of client region the system believes is dirty -// Return Value: -// - HRESULT S_OK, GDI-based error code, or safemath error -HRESULT GdiEngine::InvalidateSystem(const til::rect* const prcDirtyClient) noexcept -{ - RETURN_HR(_InvalidCombine(prcDirtyClient)); -} - -// Routine Description: -// - Notifies us that the console is attempting to scroll the existing screen area -// Arguments: -// - pcoordDelta - Pointer to character dimension (til::point) of the distance the console would like us to move while scrolling. -// Return Value: -// - HRESULT S_OK, GDI-based error code, or safemath error -HRESULT GdiEngine::InvalidateScroll(const til::point* const pcoordDelta) noexcept -{ - if (pcoordDelta->x != 0 || pcoordDelta->y != 0) - { - const auto ptDelta = *pcoordDelta * _GetFontSize(); - RETURN_IF_FAILED(_InvalidOffset(&ptDelta)); - _szInvalidScroll = _szInvalidScroll + ptDelta; - } - - return S_OK; -} - -// Routine Description: -// - Notifies us that the console has changed the selection region and would like it updated -// Arguments: -// - rectangles - Vector of rectangles to draw, line by line -// Return Value: -// - HRESULT S_OK or GDI-based error code -HRESULT GdiEngine::InvalidateSelection(std::span selections) noexcept -{ - for (auto&& rect : selections) - { - RETURN_IF_FAILED(Invalidate(&rect)); - } - return S_OK; -} - -// Routine Description: -// - Notifies us that the console has changed the character region specified. -// - NOTE: This typically triggers on cursor or text buffer changes -// Arguments: -// - psrRegion - Character region (til::rect) that has been changed -// Return Value: -// - S_OK, GDI related failure, or safemath failure. -HRESULT GdiEngine::Invalidate(const til::rect* const psrRegion) noexcept -{ - const auto rcRegion = psrRegion->scale_up(_GetFontSize()); - RETURN_HR(_InvalidateRect(&rcRegion)); -} - -// Routine Description: -// - Notifies us that the console has changed the position of the cursor. -// Arguments: -// - psrRegion - the region covered by the cursor -// Return Value: -// - S_OK, else an appropriate HRESULT for failing to allocate or write. -HRESULT GdiEngine::InvalidateCursor(const til::rect* const psrRegion) noexcept -{ - return this->Invalidate(psrRegion); -} - -// Routine Description: -// - Notifies to repaint everything. -// - NOTE: Use sparingly. Only use when something that could affect the entire frame simultaneously occurs. -// Arguments: -// - -// Return Value: -// - S_OK, S_FALSE (if no window yet), GDI related failure, or safemath failure. -HRESULT GdiEngine::InvalidateAll() noexcept -{ - // If we don't have a window, don't bother. - if (!_IsWindowValid()) - { - return S_FALSE; - } - - til::rect rc; - RETURN_HR_IF(E_FAIL, !(GetClientRect(_hwndTargetWindow, rc.as_win32_rect()))); - RETURN_HR(InvalidateSystem(&rc)); -} - -// Routine Description: -// - Helper to combine the given rectangle into the invalid region to be updated on the next paint -// Arguments: -// - prc - Pixel region (til::rect) that should be repainted on the next frame -// Return Value: -// - S_OK, GDI related failure, or safemath failure. -HRESULT GdiEngine::_InvalidCombine(const til::rect* const prc) noexcept -{ - if (!_fInvalidRectUsed) - { - _rcInvalid = *prc; - _fInvalidRectUsed = true; - } - else - { - _OrRect(&_rcInvalid, prc); - } - - // Ensure invalid areas remain within bounds of window. - RETURN_IF_FAILED(_InvalidRestrict()); - - return S_OK; -} - -// Routine Description: -// - Helper to adjust the invalid region by the given offset such as when a scroll operation occurs. -// Arguments: -// - ppt - Distances by which we should move the invalid region in response to a scroll -// Return Value: -// - S_OK, GDI related failure, or safemath failure. -HRESULT GdiEngine::_InvalidOffset(const til::point* ppt) noexcept -{ - if (_fInvalidRectUsed) - { - til::rect rcInvalidNew; - rcInvalidNew.left = _rcInvalid.left + ppt->x; - rcInvalidNew.right = _rcInvalid.right + ppt->x; - rcInvalidNew.top = _rcInvalid.top + ppt->y; - rcInvalidNew.bottom = _rcInvalid.bottom + ppt->y; - - // Add the scrolled invalid rectangle to what was left behind to get the new invalid area. - // This is the equivalent of adding in the "update rectangle" that we would get out of ScrollWindowEx/ScrollDC. - _rcInvalid |= rcInvalidNew; - - // Ensure invalid areas remain within bounds of window. - RETURN_IF_FAILED(_InvalidRestrict()); - } - - return S_OK; -} - -// Routine Description: -// - Helper to ensure the invalid region remains within the bounds of the window. -// Arguments: -// - -// Return Value: -// - S_OK, GDI related failure, or safemath failure. -HRESULT GdiEngine::_InvalidRestrict() noexcept -{ - // Ensure that the invalid area remains within the bounds of the client area - til::rect rcClient; - - // Do restriction only if retrieving the client rect was successful. - RETURN_HR_IF(E_FAIL, !(GetClientRect(_hwndTargetWindow, rcClient.as_win32_rect()))); - - _rcInvalid.left = rcClient.left; - _rcInvalid.right = rcClient.right; - _rcInvalid.top = std::clamp(_rcInvalid.top, rcClient.top, rcClient.bottom); - _rcInvalid.bottom = std::clamp(_rcInvalid.bottom, rcClient.top, rcClient.bottom); - - return S_OK; -} - -// Routine Description: -// - Helper to add a pixel rectangle to the invalid area -// Arguments: -// - prc - Pointer to pixel rectangle representing invalid area to add to next paint frame -// Return Value: -// - S_OK, GDI related failure, or safemath failure. -HRESULT GdiEngine::_InvalidateRect(const til::rect* const prc) noexcept -{ - RETURN_HR(_InvalidCombine(prc)); -} diff --git a/src/renderer/gdi/lib/gdi.vcxproj b/src/renderer/gdi/lib/gdi.vcxproj index a16c0c3818..53eb03d4ee 100644 --- a/src/renderer/gdi/lib/gdi.vcxproj +++ b/src/renderer/gdi/lib/gdi.vcxproj @@ -11,16 +11,13 @@ - - - - + Create - + @@ -31,4 +28,4 @@ - + \ No newline at end of file diff --git a/src/renderer/gdi/lib/gdi.vcxproj.filters b/src/renderer/gdi/lib/gdi.vcxproj.filters index 31413c8a32..d768d21695 100644 --- a/src/renderer/gdi/lib/gdi.vcxproj.filters +++ b/src/renderer/gdi/lib/gdi.vcxproj.filters @@ -15,16 +15,7 @@ - - Source Files - - - Source Files - - - Source Files - - + Source Files @@ -32,11 +23,15 @@ - + Header Files Header Files + + + + \ No newline at end of file diff --git a/src/renderer/gdi/math.cpp b/src/renderer/gdi/math.cpp deleted file mode 100644 index eb5c845ff5..0000000000 --- a/src/renderer/gdi/math.cpp +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" - -#include "gdirenderer.hpp" - -#pragma hdrstop - -using namespace Microsoft::Console::Render; - -// Routine Description: -// - Gets the size in characters of the current dirty portion of the frame. -// Arguments: -// - area - The character dimensions of the current dirty area of the frame. -// This is an Inclusive rect. -// Return Value: -// - S_OK or math failure -[[nodiscard]] HRESULT GdiEngine::GetDirtyArea(std::span& area) noexcept -{ - _invalidCharacters = til::rect{ _psInvalidData.rcPaint }.scale_down(_GetFontSize()); - - area = { &_invalidCharacters, 1 }; - - return S_OK; -} - -// Routine Description: -// - Uses the currently selected font to determine how wide the given character will be when rendered. -// - NOTE: Only supports determining half-width/full-width status for CJK-type languages (e.g. is it 1 character wide or 2. a.k.a. is it a rectangle or square.) -// Arguments: -// - glyph - utf16 encoded codepoint to check -// - pResult - receives return value, True if it is full-width (2 wide). False if it is half-width (1 wide). -// Return Value: -// - S_OK -[[nodiscard]] HRESULT GdiEngine::IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept -{ - auto isFullWidth = false; - - if (glyph.size() == 1) - { - const auto wch = glyph.front(); - if (_IsFontTrueType()) - { - ABC abc; - if (GetCharABCWidthsW(_hdcMemoryContext, wch, wch, &abc)) - { - const int totalWidth = abc.abcA + abc.abcB + abc.abcC; - - isFullWidth = totalWidth > _GetFontSize().width; - } - } - else - { - auto cpxWidth = 0; - if (GetCharWidth32W(_hdcMemoryContext, wch, wch, &cpxWidth)) - { - isFullWidth = cpxWidth > _GetFontSize().width; - } - } - } - else - { - // can't find a way to make gdi measure the width of utf16 surrogate pairs. - // in the meantime, better to be too wide than too narrow. - isFullWidth = true; - } - - *pResult = isFullWidth; - return S_OK; -} - -// Routine Description: -// - Scales the given pixel measurement up from the typical system DPI (generally 96) to whatever the given DPI is. -// Arguments: -// - iPx - Pixel length measurement. -// - iDpi - Given DPI scalar value -// Return Value: -// - Pixel measurement scaled against the given DPI scalar. -int GdiEngine::s_ScaleByDpi(const int iPx, const int iDpi) -{ - return MulDiv(iPx, iDpi, s_iBaseDpi); -} - -// Routine Description: -// - Shrinks the given pixel measurement down from whatever the given DPI is to the typical system DPI (generally 96). -// Arguments: -// - iPx - Pixel measurement scaled against the given DPI. -// - iDpi - Given DPI for pixel scaling -// Return Value: -// - Pixel length measurement. -int GdiEngine::s_ShrinkByDpi(const int iPx, const int iDpi) -{ - return MulDiv(iPx, s_iBaseDpi, iDpi); -} - -// Routine Description: -// - Uses internal invalid structure to determine the top left pixel point of the invalid frame to be painted. -// Arguments: -// - -// Return Value: -// - Top left corner in pixels of where to start repainting the frame. -til::point GdiEngine::_GetInvalidRectPoint() const -{ - til::point pt; - pt.x = _psInvalidData.rcPaint.left; - pt.y = _psInvalidData.rcPaint.top; - - return pt; -} - -// Routine Description: -// - Uses internal invalid structure to determine the size of the invalid area of the frame to be painted. -// Arguments: -// - -// Return Value: -// - Width and height in pixels of the invalid area of the frame. -til::size GdiEngine::_GetInvalidRectSize() const -{ - return _GetRectSize(&_psInvalidData.rcPaint); -} - -// Routine Description: -// - Converts a pixel region (til::rect) into its width/height (til::size) -// Arguments: -// - Pixel region (til::rect) -// Return Value: -// - Pixel dimensions (til::size) -til::size GdiEngine::_GetRectSize(const RECT* const pRect) const -{ - til::size sz; - sz.width = pRect->right - pRect->left; - sz.height = pRect->bottom - pRect->top; - - return sz; -} - -// Routine Description: -// - Performs a "CombineRect" with the "OR" operation. -// - Basically extends the existing rect outward to also encompass the passed-in region. -// Arguments: -// - pRectExisting - Expand this rectangle to encompass the add rect. -// - pRectToOr - Add this rectangle to the existing one. -// Return Value: -// - -void GdiEngine::_OrRect(_In_ til::rect* pRectExisting, const til::rect* pRectToOr) const -{ - pRectExisting->left = std::min(pRectExisting->left, pRectToOr->left); - pRectExisting->top = std::min(pRectExisting->top, pRectToOr->top); - pRectExisting->right = std::max(pRectExisting->right, pRectToOr->right); - pRectExisting->bottom = std::max(pRectExisting->bottom, pRectToOr->bottom); -} diff --git a/src/renderer/gdi/paint.cpp b/src/renderer/gdi/paint.cpp deleted file mode 100644 index 58eca36f84..0000000000 --- a/src/renderer/gdi/paint.cpp +++ /dev/null @@ -1,987 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" -#include "gdirenderer.hpp" - -#include "../inc/unicode.hpp" - -#pragma hdrstop - -using namespace Microsoft::Console::Render; - -// This is an excerpt of GDI's FontHasWesternScript() as -// used by InternalTextOut() which is part of ExtTextOutW(). -bool GdiEngine::FontHasWesternScript(HDC hdc) -{ - WORD glyphs[4]; - return (GetGlyphIndicesW(hdc, L"dMr\"", 4, glyphs, GGI_MARK_NONEXISTING_GLYPHS) == 4) && - (glyphs[0] != 0xFFFF && glyphs[1] != 0xFFFF && glyphs[2] != 0xFFFF && glyphs[3] != 0xFFFF); -} - -// Routine Description: -// - Prepares internal structures for a painting operation. -// Arguments: -// - -// Return Value: -// - S_OK if we started to paint. S_FALSE if we didn't need to paint. HRESULT error code if painting didn't start successfully. -[[nodiscard]] HRESULT GdiEngine::StartPaint() noexcept -{ - // If we have no handle, we don't need to paint. Return quickly. - RETURN_HR_IF(S_FALSE, !_IsWindowValid()); - - // If we're already painting, we don't need to paint. Return quickly. - RETURN_HR_IF(S_FALSE, _fPaintStarted); - - // If the window we're painting on is invisible, we don't need to paint. Return quickly. - // If the title changed, we will need to try and paint this frame. This will - // make sure the window's title is updated, even if the window isn't visible. - RETURN_HR_IF(S_FALSE, (!IsWindowVisible(_hwndTargetWindow) && !_titleChanged)); - - // At the beginning of a new frame, we have 0 lines ready for painting in PolyTextOut - _cPolyText = 0; - - // Prepare our in-memory bitmap for double-buffered composition. - RETURN_IF_FAILED(_PrepareMemoryBitmap(_hwndTargetWindow)); - - // We must use Get and Release DC because BeginPaint/EndPaint can only be called in response to a WM_PAINT message (and may hang otherwise) - // We'll still use the PAINTSTRUCT for information because it's convenient. - _psInvalidData.hdc = GetDC(_hwndTargetWindow); - RETURN_HR_IF_NULL(E_FAIL, _psInvalidData.hdc); - - // We need the advanced graphics mode in order to set a transform. - SetGraphicsMode(_psInvalidData.hdc, GM_ADVANCED); - - // Signal that we're starting to paint. - _fPaintStarted = true; - - _psInvalidData.fErase = TRUE; - _psInvalidData.rcPaint = _rcInvalid.to_win32_rect(); - -#if DBG - _debugContext = GetDC(_debugWindow); -#endif - - _lastFontType = FontType::Undefined; - - return S_OK; -} - -// Routine Description: -// - Scrolls the existing data on the in-memory frame by the scroll region -// deltas we have collectively received through the Invalidate methods -// since the last time this was called. -// Arguments: -// - -// Return Value: -// - S_OK, suitable GDI HRESULT error, error from Win32 windowing, or safemath error. -[[nodiscard]] HRESULT GdiEngine::ScrollFrame() noexcept -{ - // If we don't have any scrolling to do, return early. - RETURN_HR_IF(S_OK, 0 == _szInvalidScroll.width && 0 == _szInvalidScroll.height); - - // If we have an inverted cursor, we have to see if we have to clean it before we scroll to prevent - // left behind cursor copies in the scrolled region. - if (cursorInvertRects.size() > 0) - { - // We first need to apply the transform that was active at the time the cursor - // was rendered otherwise we won't be clearing the right area of the display. - // We don't need to do this if it was an identity transform though. - const auto identityTransform = cursorInvertTransform == IDENTITY_XFORM; - if (!identityTransform) - { - LOG_HR_IF(E_FAIL, !SetWorldTransform(_hdcMemoryContext, &cursorInvertTransform)); - LOG_HR_IF(E_FAIL, !SetWorldTransform(_psInvalidData.hdc, &cursorInvertTransform)); - } - - for (const auto& r : cursorInvertRects) - { - // Clean both the in-memory and actual window context. - LOG_HR_IF(E_FAIL, !(InvertRect(_hdcMemoryContext, &r))); - LOG_HR_IF(E_FAIL, !(InvertRect(_psInvalidData.hdc, &r))); - } - - // If we've applied a transform, then we need to reset it. - if (!identityTransform) - { - LOG_HR_IF(E_FAIL, !ModifyWorldTransform(_hdcMemoryContext, nullptr, MWT_IDENTITY)); - LOG_HR_IF(E_FAIL, !ModifyWorldTransform(_psInvalidData.hdc, nullptr, MWT_IDENTITY)); - } - - cursorInvertRects.clear(); - } - - // We have to limit the region that can be scrolled to not include the gutters. - // Gutters are defined as sub-character width pixels at the bottom or right of the screen. - const auto coordFontSize = _GetFontSize(); - RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), coordFontSize.width == 0 || coordFontSize.height == 0); - - til::size szGutter; - szGutter.width = _szMemorySurface.width % coordFontSize.width; - szGutter.height = _szMemorySurface.height % coordFontSize.height; - - RECT rcScrollLimit{}; - RETURN_IF_FAILED(LongSub(_szMemorySurface.width, szGutter.width, &rcScrollLimit.right)); - RETURN_IF_FAILED(LongSub(_szMemorySurface.height, szGutter.height, &rcScrollLimit.bottom)); - - // Scroll real window and memory buffer in-sync. - LOG_LAST_ERROR_IF(!ScrollWindowEx(_hwndTargetWindow, - _szInvalidScroll.width, - _szInvalidScroll.height, - &rcScrollLimit, - &rcScrollLimit, - nullptr, - nullptr, - 0)); - - til::rect rcUpdate; - LOG_HR_IF(E_FAIL, !(ScrollDC(_hdcMemoryContext, _szInvalidScroll.width, _szInvalidScroll.height, &rcScrollLimit, &rcScrollLimit, nullptr, rcUpdate.as_win32_rect()))); - - LOG_IF_FAILED(_InvalidCombine(&rcUpdate)); - - // update invalid rect for the remainder of paint functions - _psInvalidData.rcPaint = _rcInvalid.to_win32_rect(); - - return S_OK; -} - -// Routine Description: -// - BeginPaint helper to prepare the in-memory bitmap for double-buffering -// Arguments: -// - hwnd - Window handle to use for the DC properties when creating a memory DC and for checking the client area size. -// Return Value: -// - S_OK or suitable GDI HRESULT error. -[[nodiscard]] HRESULT GdiEngine::_PrepareMemoryBitmap(const HWND hwnd) noexcept -{ - RECT rcClient; - RETURN_HR_IF(E_FAIL, !(GetClientRect(hwnd, &rcClient))); - - const auto szClient = _GetRectSize(&rcClient); - - // Only do work if the existing memory surface is a different size from the client area. - // Return quickly if they're the same. - RETURN_HR_IF(S_OK, _szMemorySurface.width == szClient.width && _szMemorySurface.height == szClient.height); - - wil::unique_hdc hdcRealWindow(GetDC(_hwndTargetWindow)); - RETURN_HR_IF_NULL(E_FAIL, hdcRealWindow.get()); - - // If we already had a bitmap, Blt the old one onto the new one and clean up the old one. - if (nullptr != _hbitmapMemorySurface) - { - // Make a temporary DC for us to Blt with. - wil::unique_hdc hdcTemp(CreateCompatibleDC(hdcRealWindow.get())); - RETURN_HR_IF_NULL(E_FAIL, hdcTemp.get()); - - // Make the new bitmap we'll use going forward with the new size. - wil::unique_hbitmap hbitmapNew(CreateCompatibleBitmap(hdcRealWindow.get(), szClient.width, szClient.height)); - RETURN_HR_IF_NULL(E_FAIL, hbitmapNew.get()); - - // Select it into the DC, but hold onto the junky one pixel bitmap (made by default) to give back when we need to Delete. - wil::unique_hbitmap hbitmapOnePixelJunk(SelectBitmap(hdcTemp.get(), hbitmapNew.get())); - RETURN_HR_IF_NULL(E_FAIL, hbitmapOnePixelJunk.get()); - hbitmapNew.release(); // if SelectBitmap worked, GDI took ownership. Detach from smart object. - - // Blt from the DC/bitmap we're already holding onto into the new one. - RETURN_HR_IF(E_FAIL, !(BitBlt(hdcTemp.get(), 0, 0, _szMemorySurface.width, _szMemorySurface.height, _hdcMemoryContext, 0, 0, SRCCOPY))); - - // Put the junky bitmap back into the temp DC and get our new one out. - hbitmapNew.reset(SelectBitmap(hdcTemp.get(), hbitmapOnePixelJunk.get())); - RETURN_HR_IF_NULL(E_FAIL, hbitmapNew.get()); - hbitmapOnePixelJunk.release(); // if SelectBitmap worked, GDI took ownership. Detach from smart object. - - // Move our new bitmap into the long-standing DC we're holding onto. - wil::unique_hbitmap hbitmapOld(SelectBitmap(_hdcMemoryContext, hbitmapNew.get())); - RETURN_HR_IF_NULL(E_FAIL, hbitmapOld.get()); - - // Now save a pointer to our new bitmap into the class state. - _hbitmapMemorySurface = hbitmapNew.release(); // and prevent it from being freed now that GDI is holding onto it as well. - } - else - { - _hbitmapMemorySurface = CreateCompatibleBitmap(hdcRealWindow.get(), szClient.width, szClient.height); - RETURN_HR_IF_NULL(E_FAIL, _hbitmapMemorySurface); - - wil::unique_hbitmap hOldBitmap(SelectBitmap(_hdcMemoryContext, _hbitmapMemorySurface)); // DC has a default junk bitmap, take it and delete it. - RETURN_HR_IF_NULL(E_FAIL, hOldBitmap.get()); - } - - // Save the new client size. - _szMemorySurface = szClient; - - return S_OK; -} - -// Routine Description: -// - EndPaint helper to perform the final BitBlt copy from the memory bitmap onto the final window bitmap (double-buffering.) Also cleans up structures used while painting. -// Arguments: -// - -// Return Value: -// - S_OK or suitable GDI HRESULT error. -[[nodiscard]] HRESULT GdiEngine::EndPaint() noexcept -{ - // If we try to end a paint that wasn't started, it's invalid. Return. - RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !(_fPaintStarted)); - - LOG_IF_FAILED(_FlushBufferLines()); - - const auto pt = _GetInvalidRectPoint(); - const auto sz = _GetInvalidRectSize(); - - LOG_HR_IF(E_FAIL, !(BitBlt(_psInvalidData.hdc, pt.x, pt.y, sz.width, sz.height, _hdcMemoryContext, pt.x, pt.y, SRCCOPY))); - WHEN_DBG(_DebugBltAll()); - - _rcInvalid = {}; - _fInvalidRectUsed = false; - _szInvalidScroll = {}; - - LOG_HR_IF(E_FAIL, !(GdiFlush())); - LOG_HR_IF(E_FAIL, !(ReleaseDC(_hwndTargetWindow, _psInvalidData.hdc))); - _psInvalidData.hdc = nullptr; - - _fPaintStarted = false; - -#if DBG - ReleaseDC(_debugWindow, _debugContext); - _debugContext = nullptr; -#endif - - return S_OK; -} - -// Routine Description: -// - Used to perform longer running presentation steps outside the lock so the other threads can continue. -// - Not currently used by GdiEngine. -// Arguments: -// - -// Return Value: -// - S_FALSE since we do nothing. -[[nodiscard]] HRESULT GdiEngine::Present() noexcept -{ - return S_FALSE; -} - -// Routine Description: -// - Fills the given rectangle with the background color on the drawing context. -// Arguments: -// - prc - Rectangle to fill with color -// Return Value: -// - S_OK or suitable GDI HRESULT error. -[[nodiscard]] HRESULT GdiEngine::_PaintBackgroundColor(const RECT* const prc) noexcept -{ - wil::unique_hbrush hbr(GetStockBrush(DC_BRUSH)); - RETURN_HR_IF_NULL(E_FAIL, hbr.get()); - - WHEN_DBG(_PaintDebugRect(prc)); - - LOG_HR_IF(E_FAIL, !(FillRect(_hdcMemoryContext, prc, hbr.get()))); - - WHEN_DBG(_DoDebugBlt(prc)); - - return S_OK; -} - -// Routine Description: -// - Paints the background of the invalid area of the frame. -// Arguments: -// - -// Return Value: -// - S_OK or suitable GDI HRESULT error. -[[nodiscard]] HRESULT GdiEngine::PaintBackground() noexcept -{ - // We need to clear the cursorInvertRects at the start of a paint cycle so - // we don't inadvertently retain the invert region from the last paint after - // the cursor is hidden. If we don't, the ScrollFrame method may attempt to - // clean up a cursor that is no longer there, and instead leave a bunch of - // "ghost" cursor instances on the screen. - cursorInvertRects.clear(); - - if (_psInvalidData.fErase) - { - RETURN_IF_FAILED(_PaintBackgroundColor(&_psInvalidData.rcPaint)); - } - - return S_OK; -} - -// Routine Description: -// - Draws one line of the buffer to the screen. -// - This will now be cached in a PolyText buffer and flushed periodically instead of drawing every individual segment. Note this means that the PolyText buffer must be flushed before some operations (changing the brush color, drawing lines on top of the characters, inverting for cursor/selection, etc.) -// Arguments: -// - clusters - text to be written and columns expected per cluster -// - coord - character coordinate target to render within viewport -// - trimLeft - This specifies whether to trim one character width off the left side of the output. Used for drawing the right-half only of a double-wide character. -// Return Value: -// - S_OK or suitable GDI HRESULT error. -// - HISTORICAL NOTES: -// ETO_OPAQUE will paint the background color before painting the text. -// ETO_CLIPPED required for ClearType fonts. Cleartype rendering can escape bounding rectangle unless clipped. -// Unclipped rectangles results in ClearType cutting off the right edge of the previous character when adding chars -// and in leaving behind artifacts when backspace/removing chars. -// This mainly applies to ClearType fonts like Lucida Console at small font sizes (10pt) or bolded. -// See: Win7: 390673, 447839 and then superseded by http://osgvsowi/638274 when FE/non-FE rendering condensed. -//#define CONSOLE_EXTTEXTOUT_FLAGS ETO_OPAQUE | ETO_CLIPPED -//#define MAX_POLY_LINES 80 -[[nodiscard]] HRESULT GdiEngine::PaintBufferLine(const std::span clusters, - const til::point coord, - const bool trimLeft, - const bool /*lineWrapped*/) noexcept -{ - try - { - const auto cchLine = clusters.size(); - - // Exit early if there are no lines to draw. - RETURN_HR_IF(S_OK, 0 == cchLine); - - const auto ptDraw = coord * _GetFontSize(); - - const auto pPolyTextLine = &_pPolyText[_cPolyText]; - - auto& polyString = _polyStrings.emplace_back(); - polyString.reserve(cchLine); - - const auto coordFontSize = _GetFontSize(); - - auto& polyWidth = _polyWidths.emplace_back(); - polyWidth.reserve(cchLine); - - // If we have a soft font, we only use the character's lower 7 bits. - const auto softFontCharMask = _lastFontType == FontType::Soft ? L'\x7F' : ~0; - - // Sum up the total widths the entire line/run is expected to take while - // copying the pixel widths into a structure to direct GDI how many pixels to use per character. - size_t cchCharWidths = 0; - - // Convert data from clusters into the text array and the widths array. - for (size_t i = 0; i < cchLine; i++) - { - const auto& cluster = til::at(clusters, i); - - const auto text = cluster.GetText(); - polyString += text; - polyString.back() &= softFontCharMask; - polyWidth.push_back(gsl::narrow(cluster.GetColumns()) * coordFontSize.width); - cchCharWidths += polyWidth.back(); - polyWidth.append(text.size() - 1, 0); - } - - // Detect and convert for raster font... - if (!_isTrueTypeFont) - { - // dispatch conversion into our codepage - - // Find out the bytes required - const auto cbRequired = WideCharToMultiByte(_fontCodepage, 0, polyString.data(), (int)cchLine, nullptr, 0, nullptr, nullptr); - - if (cbRequired != 0) - { - // Allocate buffer for MultiByte - auto psConverted = std::make_unique(cbRequired); - - // Attempt conversion to current codepage - const auto cbConverted = WideCharToMultiByte(_fontCodepage, 0, polyString.data(), (int)cchLine, psConverted.get(), cbRequired, nullptr, nullptr); - - // If successful... - if (cbConverted != 0) - { - // Now we have to convert back to Unicode but using the system ANSI codepage. Find buffer size first. - const auto cchRequired = MultiByteToWideChar(CP_ACP, 0, psConverted.get(), cbRequired, nullptr, 0); - - if (cchRequired != 0) - { - std::pmr::wstring polyConvert(cchRequired, UNICODE_NULL, &_pool); - - // Then do the actual conversion. - const auto cchConverted = MultiByteToWideChar(CP_ACP, 0, psConverted.get(), cbRequired, polyConvert.data(), cchRequired); - - if (cchConverted != 0) - { - // If all successful, use this instead. - polyString.swap(polyConvert); - } - } - } - } - } - - // If the line rendition is double height, we need to adjust the top or bottom - // of the clipping rect to clip half the height of the rendered characters. - const auto halfHeight = coordFontSize.height >> 1; - const auto topOffset = _currentLineRendition == LineRendition::DoubleHeightBottom ? halfHeight : 0; - const auto bottomOffset = _currentLineRendition == LineRendition::DoubleHeightTop ? halfHeight : 0; - - pPolyTextLine->lpstr = polyString.data(); - pPolyTextLine->n = gsl::narrow(polyString.size()); - pPolyTextLine->x = ptDraw.x; - pPolyTextLine->y = ptDraw.y; - pPolyTextLine->uiFlags = ETO_OPAQUE | ETO_CLIPPED; - pPolyTextLine->rcl.left = pPolyTextLine->x; - pPolyTextLine->rcl.top = pPolyTextLine->y + topOffset; - pPolyTextLine->rcl.right = pPolyTextLine->rcl.left + (til::CoordType)cchCharWidths; - pPolyTextLine->rcl.bottom = pPolyTextLine->y + coordFontSize.height - bottomOffset; - pPolyTextLine->pdx = polyWidth.data(); - - if (trimLeft) - { - pPolyTextLine->rcl.left += coordFontSize.width; - } - - _cPolyText++; - - if (_cPolyText >= s_cPolyTextCache) - { - LOG_IF_FAILED(_FlushBufferLines()); - } - - return S_OK; - } - CATCH_RETURN(); -} - -// Routine Description: -// - Flushes any buffer lines in the PolyTextOut cache by drawing them and freeing the strings. -// - See also: PaintBufferLine -// Arguments: -// - -// Return Value: -// - S_OK or E_FAIL if GDI failed. -[[nodiscard]] HRESULT GdiEngine::_FlushBufferLines() noexcept -{ - auto hr = S_OK; - - if (_cPolyText > 0) - { - for (size_t i = 0; i != _cPolyText; ++i) - { - const auto& t = _pPolyText[i]; - - // The following if/else replicates the essentials of how ExtTextOutW() without ETO_IGNORELANGUAGE works. - // See InternalTextOut(). - // - // Unlike the original, we don't check for `GetTextCharacterExtra(hdc) != 0`, - // because we don't ever call SetTextCharacterExtra() anyways. - // - // GH#12294: - // Additionally we set ss.fOverrideDirection to TRUE, because we need to present RTL - // text in logical order in order to be compatible with applications like `vim -H`. - if (_fontHasWesternScript && ScriptIsComplex(t.lpstr, t.n, SIC_COMPLEX) == S_FALSE) - { - if (!ExtTextOutW(_hdcMemoryContext, t.x, t.y, t.uiFlags | ETO_IGNORELANGUAGE, &t.rcl, t.lpstr, t.n, t.pdx)) - { - hr = E_FAIL; - break; - } - } - else - { - SCRIPT_STATE ss{}; - ss.fOverrideDirection = TRUE; - - SCRIPT_STRING_ANALYSIS ssa; - hr = ScriptStringAnalyse(_hdcMemoryContext, t.lpstr, t.n, 0, -1, SSA_GLYPHS | SSA_FALLBACK, 0, nullptr, &ss, t.pdx, nullptr, nullptr, &ssa); - if (FAILED(hr)) - { - break; - } - - hr = ScriptStringOut(ssa, t.x, t.y, t.uiFlags, &t.rcl, 0, 0, FALSE); - std::ignore = ScriptStringFree(&ssa); - if (FAILED(hr)) - { - break; - } - } - } - - _polyStrings.clear(); - _polyWidths.clear(); - - ZeroMemory(_pPolyText, sizeof(_pPolyText)); - - _cPolyText = 0; - } - - RETURN_HR(hr); -} - -// Routine Description: -// - Draws up to one line worth of grid lines on top of characters. -// Arguments: -// - lines - Enum defining which edges of the rectangle to draw -// - 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 gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept -try -{ - LOG_IF_FAILED(_FlushBufferLines()); - - // Convert the target from characters to pixels. - const auto ptTarget = coordTarget * _GetFontSize(); - - // Create a brush with the gridline color, and apply it. - wil::unique_hbrush hbr(CreateSolidBrush(gridlineColor)); - RETURN_HR_IF_NULL(E_FAIL, hbr.get()); - 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; - const auto fontHeight = _GetFontSize().height; - const auto widthOfAllCells = fontWidth * gsl::narrow_cast(cchLine); - - const auto DrawLine = [=](const til::CoordType x, const til::CoordType y, const til::CoordType w, const til::CoordType h) { - return PatBlt(_hdcMemoryContext, x, y, w, h, PATCOPY); - }; - const auto DrawStrokedLine = [&](const til::CoordType x, const til::CoordType y, const til::CoordType 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 til::CoordType begX, const til::CoordType y, const til::CoordType width) { - const auto period = _lineMetrics.curlyLinePeriod; - const auto halfPeriod = period / 2; - const auto controlPointOffset = _lineMetrics.curlyLineControlPointOffset; - - // To ensure proper continuity of the wavy line between cells of different line color - // this code starts/ends the line earlier/later than it should and then clips it. - // Clipping in GDI is expensive, but it was the easiest approach. - // I've noticed that subtracting -1px prevents missing pixels when GDI draws. They still - // occur at certain (small) font sizes, but I couldn't figure out how to prevent those. - const auto lineStart = ((begX - 1) / period) * period; - const auto lineEnd = begX + width; - - IntersectClipRect(_hdcMemoryContext, begX, ptTarget.y, begX + width, ptTarget.y + fontHeight); - const auto restoreRegion = wil::scope_exit([&]() { - // Luckily no one else uses clip regions. They're weird to use. - SelectClipRgn(_hdcMemoryContext, nullptr); - }); - - // You can assume that each cell has roughly 5 POINTs on average. 128 POINTs is 1KiB. - til::small_vector points; - - // This is the start point of the Bézier curve. - points.emplace_back(lineStart, y); - - for (auto x = lineStart; x < lineEnd; x += period) - { - points.emplace_back(x + halfPeriod, y - controlPointOffset); - points.emplace_back(x + halfPeriod, y + controlPointOffset); - points.emplace_back(x + period, y); - } - - const auto cpt = gsl::narrow_cast(points.size()); - return PolyBezier(_hdcMemoryContext, points.data(), cpt); - }; - - if (lines.test(GridLines::Left)) - { - auto x = ptTarget.x; - for (size_t i = 0; i < cchLine; i++, x += fontWidth) - { - RETURN_HR_IF(E_FAIL, !DrawLine(x, ptTarget.y, _lineMetrics.gridlineWidth, fontHeight)); - } - } - - if (lines.test(GridLines::Right)) - { - // NOTE: We have to subtract the stroke width from the cell width - // to ensure the x coordinate remains inside the clipping rectangle. - auto x = ptTarget.x + fontWidth - _lineMetrics.gridlineWidth; - for (size_t i = 0; i < cchLine; i++, x += fontWidth) - { - RETURN_HR_IF(E_FAIL, !DrawLine(x, ptTarget.y, _lineMetrics.gridlineWidth, fontHeight)); - } - } - - if (lines.test(GridLines::Top)) - { - const auto y = ptTarget.y; - RETURN_HR_IF(E_FAIL, !DrawLine(ptTarget.x, y, widthOfAllCells, _lineMetrics.gridlineWidth)); - } - - if (lines.test(GridLines::Bottom)) - { - // NOTE: We have to subtract the stroke width from the cell height - // to ensure the y coordinate remains inside the clipping rectangle. - const auto y = ptTarget.y + fontHeight - _lineMetrics.gridlineWidth; - 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)); - } - - DWORD underlinePenType = PS_SOLID; - if (lines.test(GridLines::DottedUnderline)) - { - underlinePenType = PS_DOT; - } - else if (lines.test(GridLines::DashedUnderline)) - { - underlinePenType = PS_DASH; - } - - DWORD underlineWidth = _lineMetrics.underlineWidth; - if (lines.any(GridLines::DoubleUnderline, GridLines::CurlyUnderline)) - { - underlineWidth = _lineMetrics.doubleUnderlineWidth; - } - - const LOGBRUSH brushProp{ .lbStyle = BS_SOLID, .lbColor = underlineColor }; - wil::unique_hpen hpen(ExtCreatePen(underlinePenType | PS_GEOMETRIC | PS_ENDCAP_FLAT, underlineWidth, &brushProp, 0, nullptr)); - - // Apply the pen. - const auto prevPen = wil::SelectObject(_hdcMemoryContext, hpen.get()); - RETURN_HR_IF_NULL(E_FAIL, prevPen.get()); - - if (lines.test(GridLines::Underline)) - { - return DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineCenter, widthOfAllCells); - } - else if (lines.test(GridLines::DoubleUnderline)) - { - RETURN_IF_FAILED(DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.doubleUnderlinePosTop, widthOfAllCells)); - return DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.doubleUnderlinePosBottom, widthOfAllCells); - } - else if (lines.test(GridLines::CurlyUnderline)) - { - return DrawCurlyLine(ptTarget.x, ptTarget.y + _lineMetrics.curlyLineCenter, widthOfAllCells); - } - else if (lines.test(GridLines::DottedUnderline)) - { - return DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineCenter, widthOfAllCells); - } - else if (lines.test(GridLines::DashedUnderline)) - { - return DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineCenter, widthOfAllCells); - } - - return S_OK; -} -CATCH_RETURN(); - -[[nodiscard]] HRESULT GdiEngine::PaintImageSlice(const ImageSlice& imageSlice, - const til::CoordType targetRow, - const til::CoordType viewportLeft) noexcept -try -{ - LOG_IF_FAILED(_FlushBufferLines()); - LOG_IF_FAILED(ResetLineTransform()); - - const auto& imagePixels = imageSlice.Pixels(); - if (_imageMask.size() < imagePixels.size()) - { - _imageMask.resize(imagePixels.size()); - } - - const auto srcCellSize = imageSlice.CellSize(); - const auto dstCellSize = _GetFontSize(); - const auto srcWidth = imageSlice.PixelWidth(); - const auto srcHeight = srcCellSize.height; - const auto dstWidth = srcWidth * dstCellSize.width / srcCellSize.width; - const auto dstHeight = dstCellSize.height; - const auto x = (imageSlice.ColumnOffset() - viewportLeft) * dstCellSize.width; - const auto y = targetRow * dstCellSize.height; - - auto bitmapInfo = BITMAPINFO{ - .bmiHeader = { - .biSize = sizeof(BITMAPINFOHEADER), - .biWidth = srcWidth, - .biHeight = -srcHeight, - .biPlanes = 1, - .biBitCount = 32, - .biCompression = BI_RGB, - } - }; - - auto allOpaque = true; - auto allTransparent = true; - for (size_t i = 0; i < imagePixels.size(); i++) - { - const auto opaque = til::at(imagePixels, i).rgbReserved != 0; - allOpaque &= opaque; - allTransparent &= !opaque; - til::at(_imageMask, i) = (opaque ? 0 : 0xFFFFFF); - } - - if (allOpaque) - { - StretchDIBits(_hdcMemoryContext, x, y, dstWidth, dstHeight, 0, 0, srcWidth, srcHeight, imagePixels.data(), &bitmapInfo, DIB_RGB_COLORS, SRCCOPY); - } - else if (!allTransparent) - { - StretchDIBits(_hdcMemoryContext, x, y, dstWidth, dstHeight, 0, 0, srcWidth, srcHeight, _imageMask.data(), &bitmapInfo, DIB_RGB_COLORS, SRCAND); - StretchDIBits(_hdcMemoryContext, x, y, dstWidth, dstHeight, 0, 0, srcWidth, srcHeight, imagePixels.data(), &bitmapInfo, DIB_RGB_COLORS, SRCPAINT); - } - - return S_OK; -} -CATCH_RETURN(); - -// Routine Description: -// - Draws the cursor on the screen -// Arguments: -// - options - Parameters that affect the way that the cursor is drawn -// Return Value: -// - S_OK, suitable GDI HRESULT error, or safemath error, or E_FAIL in a GDI error where a specific error isn't set. -[[nodiscard]] HRESULT GdiEngine::PaintCursor(const CursorOptions& options) noexcept -{ - // if the cursor is off, do nothing - it should not be visible. - if (!options.isOn) - { - return S_FALSE; - } - LOG_IF_FAILED(_FlushBufferLines()); - - const auto coordFontSize = _GetFontSize(); - RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), coordFontSize.width == 0 || coordFontSize.height == 0); - - // First set up a block cursor the size of the font. - RECT rcBoundaries; - rcBoundaries.left = options.coordCursor.x * coordFontSize.width; - rcBoundaries.top = options.coordCursor.y * coordFontSize.height; - rcBoundaries.right = rcBoundaries.left + coordFontSize.width; - rcBoundaries.bottom = rcBoundaries.top + coordFontSize.height; - - // If we're double-width cursor, make it an extra font wider. - if (options.fIsDoubleWidth) - { - rcBoundaries.right = rcBoundaries.right + coordFontSize.width; - } - - // Make a set of RECTs to paint. - cursorInvertRects.clear(); - - auto rcInvert = rcBoundaries; - // depending on the cursorType, add rects to that set - switch (options.cursorType) - { - case CursorType::Legacy: - { - // Now adjust the cursor height - // enforce min/max cursor height - auto ulHeight = options.ulCursorHeightPercent; - ulHeight = std::max(ulHeight, s_ulMinCursorHeightPercent); // No smaller than 25% - ulHeight = std::min(ulHeight, s_ulMaxCursorHeightPercent); // No larger than 100% - - ulHeight = MulDiv(coordFontSize.height, ulHeight, 100); // divide by 100 because percent. - - // Reduce the height of the top to be relative to the bottom by the height we want. - rcInvert.top = rcInvert.bottom - ulHeight; - - cursorInvertRects.push_back(rcInvert); - } - break; - - case CursorType::VerticalBar: - LONG proposedWidth; - proposedWidth = rcInvert.left + options.cursorPixelWidth; - // It can't be wider than one cell or we'll have problems in invalidation, so restrict here. - // It's either the left + the proposed width from the ease of access setting, or - // it's the right edge of the block cursor as a maximum. - rcInvert.right = std::min(rcInvert.right, proposedWidth); - cursorInvertRects.push_back(rcInvert); - break; - - case CursorType::Underscore: - rcInvert.top = rcInvert.bottom + -1; - cursorInvertRects.push_back(rcInvert); - break; - - case CursorType::DoubleUnderscore: - { - RECT top, bottom; - top = bottom = rcBoundaries; - bottom.top = bottom.bottom + -1; - top.top = top.bottom + -3; - top.bottom = top.top + 1; - - cursorInvertRects.push_back(top); - cursorInvertRects.push_back(bottom); - } - break; - - case CursorType::EmptyBox: - { - RECT top, left, right, bottom; - top = left = right = bottom = rcBoundaries; - top.bottom = top.top + 1; - bottom.top = bottom.bottom + -1; - left.right = left.left + 1; - right.left = right.right + -1; - - top.left = top.left + 1; - bottom.left = bottom.left + 1; - top.right = top.right + -1; - bottom.right = bottom.right + -1; - - cursorInvertRects.push_back(top); - cursorInvertRects.push_back(left); - cursorInvertRects.push_back(right); - cursorInvertRects.push_back(bottom); - } - break; - - case CursorType::FullBox: - cursorInvertRects.push_back(rcInvert); - break; - - default: - return E_NOTIMPL; - } - - // Prepare the appropriate line transform for the current row. - LOG_IF_FAILED(PrepareLineTransform(options.lineRendition, 0, options.viewportLeft)); - auto resetLineTransform = wil::scope_exit([&]() { - LOG_IF_FAILED(ResetLineTransform()); - }); - - // Either invert all the RECTs, or paint them. - if (options.fUseColor) - { - auto hCursorBrush = CreateSolidBrush(options.cursorColor); - for (auto r : cursorInvertRects) - { - RETURN_HR_IF(E_FAIL, !(FillRect(_hdcMemoryContext, &r, hCursorBrush))); - } - DeleteObject(hCursorBrush); - // Clear out the inverted rects, so that we don't re-invert them next frame. - cursorInvertRects.clear(); - } - else - { - // Save the current line transform in case we need to reapply these - // inverted rects to hide the cursor in the ScrollFrame method. - cursorInvertTransform = _currentLineTransform; - - for (auto r : cursorInvertRects) - { - // Make sure the cursor is always readable (see gh-3647) - const auto PrevObject = SelectObject(_hdcMemoryContext, GetStockObject(LTGRAY_BRUSH)); - const auto Result = PatBlt(_hdcMemoryContext, r.left, r.top, r.right - r.left, r.bottom - r.top, PATINVERT); - SelectObject(_hdcMemoryContext, PrevObject); - RETURN_HR_IF(E_FAIL, !Result); - } - } - - return S_OK; -} - -// Routine Description: -// - Inverts the selected region on the current screen buffer. -// - Reads the selected area, selection mode, and active screen buffer -// from the global properties and dispatches a GDI invert on the selected text area. -// Arguments: -// - rect - Rectangle to invert or highlight to make the selection area -// Return Value: -// - S_OK or suitable GDI HRESULT error. -[[nodiscard]] HRESULT GdiEngine::PaintSelection(const til::rect& rect) noexcept -{ - LOG_IF_FAILED(_FlushBufferLines()); - - const auto pixelRect = rect.scale_up(_GetFontSize()).to_win32_rect(); - - RETURN_HR_IF(E_FAIL, !InvertRect(_hdcMemoryContext, &pixelRect)); - - return S_OK; -} - -#ifdef DBG - -void GdiEngine::_CreateDebugWindow() -{ - if (_fDebug) - { - const auto className = L"ConsoleGdiDebugWindow"; - - WNDCLASSEX wc = { 0 }; - wc.cbSize = sizeof(WNDCLASSEX); - wc.style = CS_OWNDC; - wc.lpfnWndProc = DefWindowProcW; - wc.hInstance = nullptr; - wc.lpszClassName = className; - - THROW_LAST_ERROR_IF(0 == RegisterClassExW(&wc)); - - _debugWindow = CreateWindowExW(0, - className, - L"ConhostGdiDebugWindow", - 0, - 0, - 0, - 0, - 0, - nullptr, - nullptr, - nullptr, - nullptr); - - THROW_LAST_ERROR_IF_NULL(_debugWindow); - - ShowWindow(_debugWindow, SW_SHOWNORMAL); - } -} - -// Routine Description: -// - Will fill a given rectangle with a gray shade to help identify which portion of the screen is being debugged. -// - Will attempt immediate BLT so you can see it. -// - NOTE: You must set _fDebug flag for this to operate using a debugger. -// - NOTE: This only works in Debug (DBG) builds. -// Arguments: -// - prc - Pointer to rectangle to fill -// Return Value: -// - -void GdiEngine::_PaintDebugRect(const RECT* const prc) const -{ - if (_fDebug) - { - if (!IsRectEmpty(prc)) - { - wil::unique_hbrush hbr(GetStockBrush(GRAY_BRUSH)); - if (nullptr != LOG_HR_IF_NULL(E_FAIL, hbr.get())) - { - LOG_HR_IF(E_FAIL, !(FillRect(_hdcMemoryContext, prc, hbr.get()))); - - _DoDebugBlt(prc); - } - } - } -} - -// Routine Description: -// - Will immediately Blt the given rectangle to the screen for aid in debugging when it is tough to see -// what is occurring with the in-memory DC. -// - This will pause the thread for 200ms when called to give you an opportunity to see the paint. -// - NOTE: You must set _fDebug flag for this to operate using a debugger. -// - NOTE: This only works in Debug (DBG) builds. -// Arguments: -// - prc - Pointer to region to immediately Blt to the real screen DC. -// Return Value: -// - -void GdiEngine::_DoDebugBlt(const RECT* const prc) const -{ - if (_fDebug) - { - if (!IsRectEmpty(prc)) - { - LOG_HR_IF(E_FAIL, !(BitBlt(_debugContext, prc->left, prc->top, prc->right - prc->left, prc->bottom - prc->top, _hdcMemoryContext, prc->left, prc->top, SRCCOPY))); - Sleep(100); - } - } -} - -void GdiEngine::_DebugBltAll() const -{ - if (_fDebug) - { - BitBlt(_debugContext, 0, 0, _szMemorySurface.width, _szMemorySurface.height, _hdcMemoryContext, 0, 0, SRCCOPY); - Sleep(100); - } -} -#endif diff --git a/src/renderer/gdi/state.cpp b/src/renderer/gdi/state.cpp deleted file mode 100644 index 7563a68b7d..0000000000 --- a/src/renderer/gdi/state.cpp +++ /dev/null @@ -1,788 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" - -#include "gdirenderer.hpp" -#include "../../inc/conattrs.hpp" -#include // for GWL_CONSOLE_BKCOLOR -#include "../../interactivity/win32/CustomWindowMessages.h" -#pragma hdrstop - -using namespace Microsoft::Console::Render; - -// Routine Description: -// - Creates a new GDI-based rendering engine -// - NOTE: Will throw if initialization failure. Caller must catch. -// Arguments: -// - -// Return Value: -// - An instance of a Renderer. -GdiEngine::GdiEngine() : - _hwndTargetWindow((HWND)INVALID_HANDLE_VALUE), -#if DBG - _debugWindow((HWND)INVALID_HANDLE_VALUE), -#endif - _iCurrentDpi(s_iBaseDpi), - _hbitmapMemorySurface(nullptr), - _cPolyText(0), - _fInvalidRectUsed(false), - _lastFg(INVALID_COLOR), - _lastBg(INVALID_COLOR), - _lastFontType(FontType::Undefined), - _currentLineTransform(IDENTITY_XFORM), - _currentLineRendition(LineRendition::SingleWidth), - _fPaintStarted(false), - _invalidCharacters{}, - _hfont(nullptr), - _hfontItalic(nullptr), - _pool{ til::pmr::get_default_resource() }, // It's important the pool is first so it can be given to the others on construction. - _polyStrings{ &_pool }, - _polyWidths{ &_pool } -{ - ZeroMemory(_pPolyText, sizeof(POLYTEXTW) * s_cPolyTextCache); - - _hdcMemoryContext = CreateCompatibleDC(nullptr); - THROW_HR_IF_NULL(E_FAIL, _hdcMemoryContext); - - // We need the advanced graphics mode in order to set a transform. - SetGraphicsMode(_hdcMemoryContext, GM_ADVANCED); - - // On session zero, text GDI APIs might not be ready. - // Calling GetTextFace causes a wait that will be - // satisfied while GDI text APIs come online. - // - // (Session zero is the non-interactive session - // where long running services processes are hosted. - // this increase security and reliability as user - // applications in interactive session will not be - // able to interact with services through the common - // desktop (e.g., window messages)). - GetTextFaceW(_hdcMemoryContext, 0, nullptr); - -#if DBG - if (_fDebug) - { - _CreateDebugWindow(); - } -#endif -} - -// Routine Description: -// - Destroys an instance of a GDI-based rendering engine -// Arguments: -// - -// Return Value: -// - -GdiEngine::~GdiEngine() -{ - for (size_t iPoly = 0; iPoly < _cPolyText; iPoly++) - { - if (_pPolyText[iPoly].lpstr != nullptr) - { - delete[] _pPolyText[iPoly].lpstr; - } - } - - if (_hbitmapMemorySurface != nullptr) - { - LOG_HR_IF(E_FAIL, !(DeleteObject(_hbitmapMemorySurface))); - _hbitmapMemorySurface = nullptr; - } - - if (_hfont != nullptr) - { - LOG_HR_IF(E_FAIL, !(DeleteObject(_hfont))); - _hfont = nullptr; - } - - if (_hfontItalic != nullptr) - { - LOG_HR_IF(E_FAIL, !(DeleteObject(_hfontItalic))); - _hfontItalic = nullptr; - } - - if (_hdcMemoryContext != nullptr) - { - LOG_HR_IF(E_FAIL, !(DeleteObject(_hdcMemoryContext))); - _hdcMemoryContext = nullptr; - } -} - -// Routine Description: -// - Updates the window to which this GDI renderer will be bound. -// - A window handle is required for determining the client area and other properties about the rendering surface and monitor. -// Arguments: -// - hwnd - Handle to the window on which we will be drawing. -// Return Value: -// - S_OK if set successfully or relevant GDI error via HRESULT. -[[nodiscard]] HRESULT GdiEngine::SetHwnd(const HWND hwnd) noexcept -{ - // First attempt to get the DC and create an appropriate DC - const auto hdcRealWindow = GetDC(hwnd); - RETURN_HR_IF_NULL(E_FAIL, hdcRealWindow); - - const auto hdcNewMemoryContext = CreateCompatibleDC(hdcRealWindow); - RETURN_HR_IF_NULL(E_FAIL, hdcNewMemoryContext); - - // We need the advanced graphics mode in order to set a transform. - SetGraphicsMode(hdcNewMemoryContext, GM_ADVANCED); - - // If we had an existing memory context stored, release it before proceeding. - if (nullptr != _hdcMemoryContext) - { - LOG_HR_IF(E_FAIL, !(DeleteObject(_hdcMemoryContext))); - _hdcMemoryContext = nullptr; - } - - // Store new window handle and memory context - _hwndTargetWindow = hwnd; - _hdcMemoryContext = hdcNewMemoryContext; - - if (nullptr != hdcRealWindow) - { - LOG_HR_IF(E_FAIL, !(ReleaseDC(_hwndTargetWindow, hdcRealWindow))); - } - -#if DBG - if (_debugWindow != INVALID_HANDLE_VALUE && _debugWindow != nullptr) - { - RECT rc{}; - THROW_IF_WIN32_BOOL_FALSE(GetWindowRect(_hwndTargetWindow, &rc)); - - THROW_IF_WIN32_BOOL_FALSE(SetWindowPos(_debugWindow, nullptr, 0, 0, rc.right - rc.left, rc.bottom - rc.top, SWP_NOMOVE)); - } -#endif - - return S_OK; -} - -// Routine Description: -// - This routine will help call SetWindowLongW with the correct semantics to retrieve the appropriate error code. -// Arguments: -// - hWnd - Window handle to use for setting -// - nIndex - Window handle item offset -// - dwNewLong - Value to update in window structure -// Return Value: -// - S_OK or converted HRESULT from last Win32 error from SetWindowLongW -[[nodiscard]] HRESULT GdiEngine::s_SetWindowLongWHelper(const HWND hWnd, const int nIndex, const LONG dwNewLong) noexcept -{ - // SetWindowLong has strange error handling. On success, it returns the previous Window Long value and doesn't modify the Last Error state. - // To deal with this, we set the last error to 0/S_OK first, call it, and if the previous long was 0, we check if the error was non-zero before reporting. - // Otherwise, we'll get an "Error: The operation has completed successfully." and there will be another screenshot on the internet making fun of Windows. - // See: https://msdn.microsoft.com/en-us/library/windows/desktop/ms633591(v=vs.85).aspx - SetLastError(0); - const auto lResult = SetWindowLongW(hWnd, nIndex, dwNewLong); - if (0 == lResult) - { - RETURN_LAST_ERROR_IF(0 != GetLastError()); - } - - return S_OK; -} - -// Routine Description -// - Resets the world transform to the identity matrix. -// Arguments: -// - -// Return Value: -// - S_OK if successful. S_FALSE if already reset. E_FAIL if there was an error. -[[nodiscard]] HRESULT GdiEngine::ResetLineTransform() noexcept -{ - // Return early if the current transform is already the identity matrix. - RETURN_HR_IF(S_FALSE, _currentLineTransform == IDENTITY_XFORM); - // Flush any buffer lines which would be expecting to use the current transform. - LOG_IF_FAILED(_FlushBufferLines()); - // Reset the active transform to the identity matrix. - RETURN_HR_IF(E_FAIL, !ModifyWorldTransform(_hdcMemoryContext, nullptr, MWT_IDENTITY)); - // Reset the current state. - _currentLineTransform = IDENTITY_XFORM; - _currentLineRendition = LineRendition::SingleWidth; - return S_OK; -} - -// Routine Description -// - Applies an appropriate transform for the given line rendition and viewport offset. -// Arguments: -// - lineRendition - The line rendition specifying the scaling of the line. -// - targetRow - The row on which the line is expected to be rendered. -// - viewportLeft - The left offset of the current viewport. -// Return Value: -// - S_OK if successful. S_FALSE if already set. E_FAIL if there was an error. -[[nodiscard]] HRESULT GdiEngine::PrepareLineTransform(const LineRendition lineRendition, - const til::CoordType targetRow, - const til::CoordType viewportLeft) noexcept -{ - XFORM lineTransform = {}; - // The X delta is to account for the horizontal viewport offset. - lineTransform.eDx = viewportLeft ? -1.0f * viewportLeft * _GetFontSize().width : 0.0f; - switch (lineRendition) - { - case LineRendition::SingleWidth: - lineTransform.eM11 = 1; // single width - lineTransform.eM22 = 1; // single height - break; - case LineRendition::DoubleWidth: - lineTransform.eM11 = 2; // double width - lineTransform.eM22 = 1; // single height - break; - case LineRendition::DoubleHeightTop: - lineTransform.eM11 = 2; // double width - lineTransform.eM22 = 2; // double height - // The Y delta is to negate the offset caused by the scaled height. - lineTransform.eDy = -1.0f * targetRow * _GetFontSize().height; - break; - case LineRendition::DoubleHeightBottom: - lineTransform.eM11 = 2; // double width - lineTransform.eM22 = 2; // double height - // The Y delta is to negate the offset caused by the scaled height. - // An extra row is added because we need the bottom half of the line. - lineTransform.eDy = -1.0f * (targetRow + 1) * _GetFontSize().height; - break; - } - // Return early if the new matrix is the same as the current transform. - RETURN_HR_IF(S_FALSE, _currentLineRendition == lineRendition && _currentLineTransform == lineTransform); - // Flush any buffer lines which would be expecting to use the current transform. - LOG_IF_FAILED(_FlushBufferLines()); - // Set the active transform with the new matrix. - RETURN_HR_IF(E_FAIL, !SetWorldTransform(_hdcMemoryContext, &lineTransform)); - // Save the current state. - _currentLineTransform = lineTransform; - _currentLineRendition = lineRendition; - return S_OK; -} - -// Routine Description: -// - This method will set the GDI brushes in the drawing context (and update the hung-window background color) -// Arguments: -// - textAttributes - Text attributes to use for the brush color -// - renderSettings - The color table and modes required for rendering -// - pData - The interface to console data structures required for rendering -// - usingSoftFont - Whether we're rendering characters from a soft font -// - isSettingDefaultBrushes - Lets us know that the default brushes are being set so we can update the DC background -// and the hung app background painting color -// Return Value: -// - S_OK if set successfully or relevant GDI error via HRESULT. -[[nodiscard]] HRESULT GdiEngine::UpdateDrawingBrushes(const TextAttribute& textAttributes, - const RenderSettings& renderSettings, - const gsl::not_null /*pData*/, - const bool usingSoftFont, - const bool isSettingDefaultBrushes) noexcept -{ - RETURN_IF_FAILED(_FlushBufferLines()); - - RETURN_HR_IF_NULL(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), _hdcMemoryContext); - - // Set the colors for painting text - const auto [colorForeground, colorBackground] = renderSettings.GetAttributeColors(textAttributes); - - if (colorForeground != _lastFg) - { - RETURN_HR_IF(E_FAIL, CLR_INVALID == SetTextColor(_hdcMemoryContext, colorForeground)); - _lastFg = colorForeground; - } - if (colorBackground != _lastBg) - { - RETURN_HR_IF(E_FAIL, CLR_INVALID == SetBkColor(_hdcMemoryContext, colorBackground)); - _lastBg = colorBackground; - } - - if (isSettingDefaultBrushes) - { - // Set the color for painting the extra DC background area - RETURN_HR_IF(E_FAIL, CLR_INVALID == SetDCBrushColor(_hdcMemoryContext, colorBackground)); - - // Set the hung app background painting color - RETURN_IF_FAILED(s_SetWindowLongWHelper(_hwndTargetWindow, GWL_CONSOLE_BKCOLOR, colorBackground)); - } - - // If the font type has changed, select an appropriate font variant or soft font. - const auto usingItalicFont = textAttributes.IsItalic(); - const auto fontType = usingSoftFont ? FontType::Soft : - usingItalicFont ? FontType::Italic : - FontType::Default; - if (fontType != _lastFontType) - { - switch (fontType) - { - case FontType::Soft: - SelectFont(_hdcMemoryContext, _softFont); - break; - case FontType::Italic: - SelectFont(_hdcMemoryContext, _hfontItalic); - break; - case FontType::Default: - default: - SelectFont(_hdcMemoryContext, _hfont); - break; - } - _lastFontType = fontType; - _fontHasWesternScript = FontHasWesternScript(_hdcMemoryContext); - } - - return S_OK; -} - -// Routine Description: -// - This method will update the active font on the current device context -// - NOTE: It is left up to the underling rendering system to choose the nearest font. Please ask for the font dimensions if they are required using the interface. Do not use the size you requested with this structure. -// Arguments: -// - FontDesired - reference to font information we should use while instantiating a font. -// - Font - reference to font information where the chosen font information will be populated. -// Return Value: -// - S_OK if set successfully or relevant GDI error via HRESULT. -[[nodiscard]] HRESULT GdiEngine::UpdateFont(const FontInfoDesired& FontDesired, _Out_ FontInfo& Font) noexcept -{ - wil::unique_hfont hFont, hFontItalic; - RETURN_IF_FAILED(_GetProposedFont(FontDesired, Font, _iCurrentDpi, hFont, hFontItalic)); - - // Select into DC - RETURN_HR_IF_NULL(E_FAIL, SelectFont(_hdcMemoryContext, hFont.get())); - - // Save off the font metrics for various other calculations - RETURN_HR_IF(E_FAIL, !(GetTextMetricsW(_hdcMemoryContext, &_tmFontMetrics))); - - // There is no font metric for the grid line width, so we use a small - // multiple of the font size, which typically rounds to a pixel. - const auto cellHeight = static_cast(Font.GetSize().height); - const auto fontSize = static_cast(_tmFontMetrics.tmHeight - _tmFontMetrics.tmInternalLeading); - const auto baseline = static_cast(_tmFontMetrics.tmAscent); - float idealGridlineWidth = std::max(1.0f, fontSize * 0.025f); - float idealUnderlineTop = 0; - float idealUnderlineWidth = 0; - float idealStrikethroughTop = 0; - float idealStrikethroughWidth = 0; - - OUTLINETEXTMETRICW outlineMetrics; - if (GetOutlineTextMetricsW(_hdcMemoryContext, sizeof(outlineMetrics), &outlineMetrics)) - { - // For TrueType fonts, the other line metrics can be obtained from - // the font's outline text metric structure. - idealUnderlineTop = static_cast(baseline - outlineMetrics.otmsUnderscorePosition); - idealUnderlineWidth = static_cast(outlineMetrics.otmsUnderscoreSize); - idealStrikethroughWidth = static_cast(outlineMetrics.otmsStrikeoutSize); - idealStrikethroughTop = static_cast(baseline - outlineMetrics.otmsStrikeoutPosition); - } - else - { - // If we can't obtain the outline metrics for the font, we just pick some reasonable values for the offsets and widths. - idealUnderlineTop = std::max(1.0f, roundf(baseline - fontSize * 0.05f)); - idealUnderlineWidth = idealGridlineWidth; - idealStrikethroughTop = std::max(1.0f, roundf(baseline * (2.0f / 3.0f))); - idealStrikethroughWidth = idealGridlineWidth; - } - - // GdiEngine::PaintBufferGridLines paints underlines using HPEN and LineTo, etc., which draws lines centered on the given coordinates. - // This means we need to shift the limit (cellHeight - underlineWidth) and offset (idealUnderlineTop) by half the width. - const auto underlineWidth = std::max(1.0f, roundf(idealUnderlineWidth)); - const auto underlineCenter = std::min(floorf(cellHeight - underlineWidth / 2.0f), roundf(idealUnderlineTop + underlineWidth / 2.0f)); - - const auto strikethroughWidth = std::max(1.0f, roundf(idealStrikethroughWidth)); - const auto strikethroughOffset = std::min(cellHeight - strikethroughWidth, roundf(idealStrikethroughTop)); - - // For double underlines we loosely follow what Word does: - // 1. The lines are half the width of an underline - // 2. Ideally the bottom line is aligned with the bottom of the underline - // 3. The top underline is vertically in the middle between baseline and ideal bottom underline - // 4. If the top line gets too close to the baseline the underlines are shifted downwards - // 5. The minimum gap between the two lines appears to be similar to Tex (1.2pt) - // (Additional notes below.) - - // 1. - const auto doubleUnderlineWidth = std::max(1.0f, roundf(idealUnderlineWidth / 2.0f)); - // 2. - auto doubleUnderlinePosBottom = underlineCenter + underlineWidth - doubleUnderlineWidth; - // 3. Since we don't align the center of our two lines, but rather the top borders - // we need to subtract half a line width from our center point. - auto doubleUnderlinePosTop = roundf((baseline + doubleUnderlinePosBottom - doubleUnderlineWidth) / 2.0f); - // 4. - doubleUnderlinePosTop = std::max(doubleUnderlinePosTop, baseline + doubleUnderlineWidth); - // 5. The gap is only the distance _between_ the lines, but we need the distance from the - // top border of the top and bottom lines, which includes an additional line width. - const auto doubleUnderlineGap = std::max(1.0f, roundf(1.2f / 72.0f * _iCurrentDpi)); - doubleUnderlinePosBottom = std::max(doubleUnderlinePosBottom, doubleUnderlinePosTop + doubleUnderlineGap + doubleUnderlineWidth); - // Our cells can't overlap each other so we additionally clamp the bottom line to be inside the cell boundaries. - doubleUnderlinePosBottom = std::min(doubleUnderlinePosBottom, cellHeight - doubleUnderlineWidth); - - // The wave line is drawn using a cubic Bézier curve (PolyBezier), because that happens to be cheap with GDI. - // We use a Bézier curve where, if the start (a) and end (c) points are at (0,0) and (1,0), the control points are - // at (0.5,0.5) (b) and (0.5,-0.5) (d) respectively. Like this but a/b/c/d are square and the lines are round: - // - // b - // - // ^ - // / \ here's some text so the compiler ignores the trailing \ character - // a \ c - // \ / - // v - // - // d - // - // If you punch x=0.25 into the cubic bezier formula you get y=0.140625. This constant is - // important to us because it (plus the line width) tells us the amplitude of the wave. - // - // We can use the inverse of the constant to figure out how many px one period of the wave has to be to end up being 1px tall. - // In our case we want the amplitude of the wave to have a peak-to-peak amplitude that matches our double-underline. - const auto doubleUnderlineHalfDistance = 0.5f * (doubleUnderlinePosBottom - doubleUnderlinePosTop); - const auto doubleUnderlineCenter = doubleUnderlinePosTop + doubleUnderlineHalfDistance; - const auto curlyLineIdealAmplitude = std::max(1.0f, doubleUnderlineHalfDistance); - // Since GDI can't deal with fractional pixels, we first calculate the control point offsets (0.5 and -0.5) by multiplying by 0.5 and - // then undo that by multiplying by 2.0 for the period. This ensures that our control points can be at curlyLinePeriod/2, an integer. - const auto curlyLineControlPointOffset = roundf(curlyLineIdealAmplitude * (1.0f / 0.140625f) * 0.5f); - const auto curlyLinePeriod = curlyLineControlPointOffset * 2.0f; - // We can reverse the above to get back the actual amplitude of our Bézier curve. The line - // will be drawn with a width of doubleUnderlineWidth in the center of the curve (= 0.5x padding). - const auto curlyLineAmplitude = 0.140625f * curlyLinePeriod + 0.5f * doubleUnderlineWidth; - // To make the wavy line with its double-underline amplitude look consistent with the double-underline we position it at its center. - const auto curlyLineOffset = std::min(roundf(doubleUnderlineCenter), floorf(cellHeight - curlyLineAmplitude)); - - _lineMetrics.gridlineWidth = lroundf(idealGridlineWidth); - _lineMetrics.doubleUnderlineWidth = lroundf(doubleUnderlineWidth); - _lineMetrics.underlineCenter = lroundf(underlineCenter); - _lineMetrics.underlineWidth = lroundf(underlineWidth); - _lineMetrics.doubleUnderlinePosTop = lroundf(doubleUnderlinePosTop); - _lineMetrics.doubleUnderlinePosBottom = lroundf(doubleUnderlinePosBottom); - _lineMetrics.strikethroughOffset = lroundf(strikethroughOffset); - _lineMetrics.strikethroughWidth = lroundf(strikethroughWidth); - _lineMetrics.curlyLineCenter = lroundf(curlyLineOffset); - _lineMetrics.curlyLinePeriod = lroundf(curlyLinePeriod); - _lineMetrics.curlyLineControlPointOffset = lroundf(curlyLineControlPointOffset); - - // Now find the size of a 0 in this current font and save it for conversions done later. - _coordFontLast = Font.GetSize(); - - // Persist font for cleanup (and free existing if necessary) - if (_hfont != nullptr) - { - LOG_HR_IF(E_FAIL, !(DeleteObject(_hfont))); - _hfont = nullptr; - } - - // Save the font. - _hfont = hFont.release(); - - // Persist italic font for cleanup (and free existing if necessary) - if (_hfontItalic != nullptr) - { - LOG_HR_IF(E_FAIL, !(DeleteObject(_hfontItalic))); - _hfontItalic = nullptr; - } - - // Save the italic font. - _hfontItalic = hFontItalic.release(); - - // Save raster vs. TrueType and codepage data in case we need to convert. - _isTrueTypeFont = Font.IsTrueTypeFont(); - _fontCodepage = Font.GetCodePage(); - - // Inform the soft font of the change in size. - _softFont.SetTargetSize(_GetFontSize()); - - LOG_IF_FAILED(InvalidateAll()); - - return S_OK; -} - -// Routine Description: -// - This method will replace the active soft font with the given bit pattern. -// Arguments: -// - bitPattern - An array of scanlines representing all the glyphs in the font. -// - cellSize - The cell size for an individual glyph. -// - centeringHint - The horizontal extent that glyphs are offset from center. -// Return Value: -// - S_OK if successful. E_FAIL if there was an error. -[[nodiscard]] HRESULT GdiEngine::UpdateSoftFont(const std::span bitPattern, - const til::size cellSize, - const size_t centeringHint) noexcept -{ - // If we previously called SelectFont(_hdcMemoryContext, _softFont), it will - // still hold a reference to the _softFont object we're planning to overwrite. - // --> First revert back to the standard _hfont, lest we have dangling pointers. - if (_lastFontType == FontType::Soft) - { - RETURN_HR_IF_NULL(E_FAIL, SelectFont(_hdcMemoryContext, _hfont)); - _lastFontType = FontType::Default; - } - - // Create a new font resource with the updated pattern, or delete if empty. - _softFont = FontResource{ bitPattern, cellSize, _GetFontSize(), centeringHint }; - - return S_OK; -} - -// Routine Description: -// - This method will modify the DPI we're using for scaling calculations. -// Arguments: -// - iDpi - The Dots Per Inch to use for scaling. We will use this relative to the system default DPI defined in Windows headers as a constant. -// Return Value: -// - HRESULT S_OK, GDI-based error code, or safemath error -[[nodiscard]] HRESULT GdiEngine::UpdateDpi(const int iDpi) noexcept -{ - _iCurrentDpi = iDpi; - return S_OK; -} - -// Method Description: -// - This method will update our internal reference for how big the viewport is. -// Does nothing for GDI. -// Arguments: -// - srNewViewport - The bounds of the new viewport. -// Return Value: -// - HRESULT S_OK -[[nodiscard]] HRESULT GdiEngine::UpdateViewport(const til::inclusive_rect& /*srNewViewport*/) noexcept -{ - return S_OK; -} - -// Routine Description: -// - This method will figure out what the new font should be given the starting font information and a DPI. -// - When the final font is determined, the FontInfo structure given will be updated with the actual resulting font chosen as the nearest match. -// - NOTE: It is left up to the underling rendering system to choose the nearest font. Please ask for the font dimensions if they are required using the interface. Do not use the size you requested with this structure. -// - If the intent is to immediately turn around and use this font, pass the optional handle parameter and use it immediately. -// Arguments: -// - FontDesired - reference to font information we should use while instantiating a font. -// - Font - reference to font information where the chosen font information will be populated. -// - iDpi - The DPI we will have when rendering -// Return Value: -// - S_OK if set successfully or relevant GDI error via HRESULT. -[[nodiscard]] HRESULT GdiEngine::GetProposedFont(const FontInfoDesired& FontDesired, _Out_ FontInfo& Font, const int iDpi) noexcept -{ - wil::unique_hfont hFont, hFontItalic; - return _GetProposedFont(FontDesired, Font, iDpi, hFont, hFontItalic); -} - -// Method Description: -// - Updates the window's title string. For GDI, this does nothing, because the -// title must be updated on the main window's windowproc thread. -// Arguments: -// - newTitle: the new string to use for the title of the window -// Return Value: -// - S_OK if PostMessageW succeeded, otherwise E_FAIL -[[nodiscard]] HRESULT GdiEngine::_DoUpdateTitle(_In_ const std::wstring_view /*newTitle*/) noexcept -{ - // the CM_UPDATE_TITLE handler in windowproc will query the updated title. - return PostMessageW(_hwndTargetWindow, CM_UPDATE_TITLE, 0, (LPARAM) nullptr) ? S_OK : E_FAIL; -} - -// Routine Description: -// - This method will figure out what the new font should be given the starting font information and a DPI. -// - When the final font is determined, the FontInfo structure given will be updated with the actual resulting font chosen as the nearest match. -// - NOTE: It is left up to the underling rendering system to choose the nearest font. Please ask for the font dimensions if they are required using the interface. Do not use the size you requested with this structure. -// - If the intent is to immediately turn around and use this font, pass the optional handle parameter and use it immediately. -// Arguments: -// - FontDesired - reference to font information we should use while instantiating a font. -// - Font - the actual font -// - iDpi - The DPI we will have when rendering -// - hFont - A smart pointer to receive a handle to a ready-to-use GDI font. -// - hFontItalic - A smart pointer to receive a handle to an italic variant of the font. -// Return Value: -// - S_OK if set successfully or relevant GDI error via HRESULT. -[[nodiscard]] HRESULT GdiEngine::_GetProposedFont(const FontInfoDesired& FontDesired, - _Out_ FontInfo& Font, - const int iDpi, - _Inout_ wil::unique_hfont& hFont, - _Inout_ wil::unique_hfont& hFontItalic) noexcept -{ - wil::unique_hdc hdcTemp(CreateCompatibleDC(_hdcMemoryContext)); - RETURN_HR_IF_NULL(E_FAIL, hdcTemp.get()); - - // Get a special engine size because TT fonts can't specify X or we'll get weird scaling under some circumstances. - auto coordFontRequested = FontDesired.GetEngineSize(); - - // First, check to see if we're asking for the default raster font. - if (FontDesired.IsDefaultRasterFont()) - { - // We're being asked for the default raster font, which gets special handling. In particular, it's the font - // returned by GetStockObject(OEM_FIXED_FONT). - // We do this because, for instance, if we ask GDI for an 8x12 OEM_FIXED_FONT, - // it may very well decide to choose Courier New instead of the Terminal raster. -#pragma prefast(suppress : 38037, "raster fonts get special handling, we need to get it this way") - hFont.reset((HFONT)GetStockObject(OEM_FIXED_FONT)); - hFontItalic.reset((HFONT)GetStockObject(OEM_FIXED_FONT)); - } - else - { - // For future reference, here is the engine weighting and internal details on Windows Font Mapping: - // https://msdn.microsoft.com/en-us/library/ms969909.aspx - // More relevant links: - // https://support.microsoft.com/en-us/kb/94646 - - // IMPORTANT: Be very careful when modifying the values being passed in below. Even the slightest change can cause - // GDI to return a font other than the one being requested. If you must change the below for any reason, make sure - // these fonts continue to work correctly, as they've been known to break: - // * Monofur - // * Iosevka Extralight - // - // While you're at it, make sure that the behavior matches what happens in the Fonts property sheet. Pay very close - // attention to the font previews to ensure that the font being selected by GDI is exactly the font requested -- - // some monospace fonts look very similar. - LOGFONTW lf = { 0 }; - lf.lfHeight = s_ScaleByDpi(coordFontRequested.height, iDpi); - lf.lfWidth = s_ScaleByDpi(coordFontRequested.width, iDpi); - lf.lfWeight = FontDesired.GetWeight(); - - // If we're searching for Terminal, our supported Raster Font, then we must use OEM_CHARSET. - // If the System's Non-Unicode Setting is set to English (United States) which is 437 - // and we try to enumerate Terminal with the console codepage as 932, that will turn into SHIFTJIS_CHARSET. - // Despite C:\windows\fonts\vga932.fon always being present, GDI will refuse to load the Terminal font - // that doesn't correspond to the current System Non-Unicode Setting. It will then fall back to a TrueType - // font that does support the SHIFTJIS_CHARSET (because Terminal with CP 437 a.k.a. C:\windows\fonts\vgaoem.fon does NOT support it.) - // This is OK for display purposes (things will render properly) but not OK for API purposes. - // Because the API is affected by the raster/TT status of the actively selected font, we can't have - // GDI choosing a TT font for us when we ask for Raster. We have to settle for forcing the current system - // Terminal font to load even if it doesn't have the glyphs necessary such that the APIs continue to work fine. - if (FontDesired.GetFaceName() == DEFAULT_RASTER_FONT_FACENAME) - { - lf.lfCharSet = OEM_CHARSET; - } - else - { - CHARSETINFO csi; - if (!TranslateCharsetInfo((DWORD*)IntToPtr(FontDesired.GetCodePage()), &csi, TCI_SRCCODEPAGE)) - { - // if we failed to translate from codepage to charset, choose our charset depending on what kind of font we're - // dealing with. Raster Fonts need to be presented with the OEM charset, while TT fonts need to be ANSI. - csi.ciCharset = FontDesired.IsTrueTypeFont() ? ANSI_CHARSET : OEM_CHARSET; - } - - lf.lfCharSet = (BYTE)csi.ciCharset; - } - - lf.lfQuality = DRAFT_QUALITY; - - // NOTE: not using what GDI gave us because some fonts don't quite roundtrip (e.g. MS Gothic and VL Gothic) - lf.lfPitchAndFamily = (FIXED_PITCH | FF_MODERN); - - FontDesired.FillLegacyNameBuffer(lf.lfFaceName); - - // Create font. - hFont.reset(CreateFontIndirectW(&lf)); - RETURN_HR_IF_NULL(E_FAIL, hFont.get()); - - // Create italic variant of the font. - lf.lfItalic = TRUE; - hFontItalic.reset(CreateFontIndirectW(&lf)); - RETURN_HR_IF_NULL(E_FAIL, hFontItalic.get()); - } - - // Select into DC - wil::unique_hfont hFontOld(SelectFont(hdcTemp.get(), hFont.get())); - RETURN_HR_IF_NULL(E_FAIL, hFontOld.get()); - - // Save off the font metrics for various other calculations - TEXTMETRICW tm; - RETURN_HR_IF(E_FAIL, !(GetTextMetricsW(hdcTemp.get(), &tm))); - - // Now find the size of a 0 in this current font and save it for conversions done later. - SIZE sz; - RETURN_HR_IF(E_FAIL, !(GetTextExtentPoint32W(hdcTemp.get(), L"0", 1, &sz))); - - til::size coordFont; - coordFont.width = sz.cx; - coordFont.height = sz.cy; - - // The extent point won't necessarily be perfect for the width, so get the ABC metrics for the 0 if possible to improve the measurement. - // This will fail for non-TrueType fonts and we'll fall back to what GetTextExtentPoint said. - { - ABC abc; - if (0 != GetCharABCWidthsW(hdcTemp.get(), '0', '0', &abc)) - { - const auto abcTotal = abc.abcA + abc.abcB + abc.abcC; - - // No negatives or zeros or we'll have bad character-to-pixel math later. - if (abcTotal > 0) - { - coordFont.width = abcTotal; - } - } - } - - // Now fill up the FontInfo we were passed with the full details of which font we actually chose - { - // Get the actual font face that we chose - const auto faceNameLength{ gsl::narrow(GetTextFaceW(hdcTemp.get(), 0, nullptr)) }; - - std::wstring currentFaceName{}; - currentFaceName.resize(faceNameLength); - - RETURN_HR_IF(E_FAIL, !(GetTextFaceW(hdcTemp.get(), gsl::narrow_cast(faceNameLength), currentFaceName.data()))); - - currentFaceName.resize(faceNameLength - 1); // remove the null terminator (wstring!) - - if (FontDesired.IsDefaultRasterFont()) - { - coordFontRequested = coordFont; - } - else if (coordFontRequested.width == 0) - { - coordFontRequested.width = s_ShrinkByDpi(coordFont.width, iDpi); - } - - Font.SetFromEngine(currentFaceName, - tm.tmPitchAndFamily, - gsl::narrow_cast(tm.tmWeight), - FontDesired.IsDefaultRasterFont(), - coordFont, - coordFontRequested); - } - - return S_OK; -} - -// Routine Description: -// - Retrieves the current pixel size of the font we have selected for drawing. -// Arguments: -// - pFontSize - receives the current X by Y size of the font. -// Return Value: -// - S_OK -[[nodiscard]] HRESULT GdiEngine::GetFontSize(_Out_ til::size* pFontSize) noexcept -{ - *pFontSize = _GetFontSize(); - return S_OK; -} - -// Routine Description: -// - Retrieves the current pixel size of the font we have selected for drawing. -// Arguments: -// - -// Return Value: -// - X by Y size of the font. -til::size GdiEngine::_GetFontSize() const -{ - return _coordFontLast; -} - -// Routine Description: -// - Retrieves whether or not the window is currently minimized. -// Arguments: -// - -// Return Value: -// - True if minimized (don't need to draw anything). False otherwise. -bool GdiEngine::_IsMinimized() const -{ - return !!IsIconic(_hwndTargetWindow); -} - -// Routine Description: -// - Determines whether or not we have a TrueType font selected. -// - Intended only for determining whether we need to perform special raster font scaling. -// Arguments: -// - -// Return Value: -// - True if TrueType. False otherwise (and generally assumed to be raster font type.) -bool GdiEngine::_IsFontTrueType() const -{ - return !!(_tmFontMetrics.tmPitchAndFamily & TMPF_TRUETYPE); -} - -// Routine Description: -// - Helper to determine whether our window handle is valid. -// Allows us to skip operations if we don't have a window. -// Return Value: -// - True if it is valid. -// - False if it is known invalid. -bool GdiEngine::_IsWindowValid() const -{ - return _hwndTargetWindow != INVALID_HANDLE_VALUE && - _hwndTargetWindow != nullptr; -} diff --git a/src/renderer/gdi/tool/main.cpp b/src/renderer/gdi/tool/main.cpp deleted file mode 100644 index c26b129f64..0000000000 --- a/src/renderer/gdi/tool/main.cpp +++ /dev/null @@ -1,63 +0,0 @@ -#include "precomp.h" -#include -#include -#include "wincon.h" - -int CALLBACK EnumFontFamiliesExProc(ENUMLOGFONTEX* lpelfe, NEWTEXTMETRICEX* lpntme, int FontType, LPARAM lParam) -{ - lParam; - FontType; - lpntme; - - if (lpntme->ntmTm.tmPitchAndFamily & TMPF_FIXED_PITCH) - { - // skip non-monospace fonts - // NOTE: this is weird/backwards and the presence of this flag means non-monospace and the absence means monospace. - return 1; - } - - if (lpelfe->elfFullName[0] == L'@') - { - return 1; // skip vertical fonts - } - - if (FontType & DEVICE_FONTTYPE) - { - return 1; // skip device type fonts. we're only going to do raster and truetype. - } - - if (FontType & RASTER_FONTTYPE) - { - if (wcscmp(lpelfe->elfFullName, L"Terminal") != 0) - { - return 1; // skip non-"Terminal" raster fonts. - } - } - - wprintf(L"Charset: %d ", lpntme->ntmTm.tmCharSet); - - wprintf(L"W: %d H: %d", lpntme->ntmTm.tmMaxCharWidth, lpntme->ntmTm.tmHeight); - - wprintf(L"%s, %s, %s\n", lpelfe->elfFullName, lpelfe->elfScript, lpelfe->elfStyle); - return 1; -} - -int __cdecl wmain(int argc, wchar_t** argv) -{ - argc; - argv; - - HDC hDC = GetDC(NULL); - - /*LOGFONTW lf = { 0, 0, 0, 0, 0, 0, 0, 0, DEFAULT_CHARSET, 0, 0, 0, - 0, L"Courier New" };*/ - - LOGFONTW lf = { 0 }; - lf.lfCharSet = DEFAULT_CHARSET; // enumerate this charset. - lf.lfFaceName[0] = L'\0'; // enumerate all font names - lf.lfPitchAndFamily = 0; // required by API. - - EnumFontFamiliesExW(hDC, &lf, (FONTENUMPROC)EnumFontFamiliesExProc, 0, 0); - ReleaseDC(NULL, hDC); - return 0; -} diff --git a/src/renderer/inc/Cluster.hpp b/src/renderer/inc/Cluster.hpp deleted file mode 100644 index 52ff70f95d..0000000000 --- a/src/renderer/inc/Cluster.hpp +++ /dev/null @@ -1,67 +0,0 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- Cluster.hpp - -Abstract: -- This serves as a structure to represent a single glyph cluster drawn on the screen -- This is required to enable N wchar_ts to consume M columns in the display -- Historically, the console only supported 1 wchar_t = 1 column or 1 wchar_t = 2 columns. - -Author(s): -- Michael Niksa (MiNiksa) 25-Mar-2019 ---*/ - -#pragma once - -#include - -namespace Microsoft::Console::Render -{ - class Cluster - { - public: - constexpr Cluster() noexcept = default; - constexpr Cluster(const std::wstring_view text, const til::CoordType columns) noexcept : - _text{ text }, - _columns{ columns } - { - } - - // Provides the embedded text as a single character - // This might replace the string with the replacement character if it doesn't fit as one wchar_t. - constexpr wchar_t GetTextAsSingle() const noexcept - { - if (_text.size() == 1) - { - return til::at(_text, 0); - } - else - { - return UNICODE_REPLACEMENT; - } - } - - // Provides the string of wchar_ts for this cluster. - constexpr std::wstring_view GetText() const noexcept - { - return _text; - } - - // Gets the number of columns in the grid that this character should consume - // visually when rendered onto a line. - constexpr til::CoordType GetColumns() const noexcept - { - return _columns; - } - - private: - // This is the UTF-16 string of characters that form a particular drawing cluster - std::wstring_view _text; - - // This is how many columns we're expecting this cluster to take in the display grid - til::CoordType _columns = 0; - }; -} diff --git a/src/renderer/inc/CursorOptions.h b/src/renderer/inc/CursorOptions.h deleted file mode 100644 index a030426114..0000000000 --- a/src/renderer/inc/CursorOptions.h +++ /dev/null @@ -1,62 +0,0 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- CursorOptions.h - -Abstract: -- A collection of state about the cursor that a render engine might need to display the cursor correctly. - -Author(s): -- Mike Griese, 03-Jun-2020 ---*/ - -#pragma once - -#include "../../buffer/out/LineRendition.hpp" -#include "../../inc/conattrs.hpp" - -namespace Microsoft::Console::Render -{ - struct CursorOptions - { - // Character cell in the grid to draw at - // This is relative to the top of the viewport, not the buffer - til::point coordCursor; - - // Left offset of the viewport, which may alter the horizontal position - til::CoordType viewportLeft; - - // Line rendition of the current row, which can affect the cursor width - LineRendition lineRendition; - - // For an underscore type _ cursor, how tall it should be as a % of cell height - ULONG ulCursorHeightPercent; - - // For a vertical bar type | cursor, how many pixels wide it should be per ease of access preferences - ULONG cursorPixelWidth; - - // Whether to draw the cursor 2 cells wide (+X from the coordinate given) - bool fIsDoubleWidth; - - // Chooses a special cursor type like a full box, a vertical bar, etc. - CursorType cursorType; - - // Specifies to use the color below instead of the default color - bool fUseColor; - - // Color to use for drawing instead of the default - COLORREF cursorColor; - - // The other kind of on/off state for the cursor, because VtEngine needs it to handle \x1b[?25l/h. - bool isVisible; - // Is the cursor currently visually visible? - // If the cursor has blinked off, this is false. - // if the cursor has blinked on, this is true. - bool isOn; - - // Is the cursor within the viewport of the renderer? - bool inViewport; - }; -} diff --git a/src/renderer/inc/DummyRenderer.hpp b/src/renderer/inc/DummyRenderer.hpp deleted file mode 100644 index b1e43b9d5f..0000000000 --- a/src/renderer/inc/DummyRenderer.hpp +++ /dev/null @@ -1,24 +0,0 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- DummyRenderer.hpp - -Abstract: -- Provides a minimal instantiation of the Renderer class. - This is needed for some tests, where certain objects need a reference to a - Renderer ---*/ - -#pragma once -#include "../base/renderer.hpp" - -class DummyRenderer final : public Microsoft::Console::Render::Renderer -{ -public: - DummyRenderer(Microsoft::Console::Render::IRenderData* pData = nullptr) : - Microsoft::Console::Render::Renderer(_renderSettings, pData, nullptr, 0, nullptr) {} - - Microsoft::Console::Render::RenderSettings _renderSettings; -}; diff --git a/src/renderer/inc/IRenderData.hpp b/src/renderer/inc/IRenderData.hpp index c2923df0cc..8442fada0c 100644 --- a/src/renderer/inc/IRenderData.hpp +++ b/src/renderer/inc/IRenderData.hpp @@ -1,28 +1,83 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- IRenderData.hpp - -Abstract: -- This serves as the interface defining all information needed to render to the screen. - -Author(s): -- Michael Niksa (MiNiksa) 17-Nov-2015 ---*/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. #pragma once -#include "../../buffer/out/TextAttribute.hpp" -#include "../../renderer/inc/FontInfo.hpp" -#include "../../types/inc/viewport.hpp" +#include -class Cursor; -class TextBuffer; +#include "CSSLengthPercentage.h" +#include "../../buffer/out/textBuffer.hpp" namespace Microsoft::Console::Render { + enum class CursorStyle + { + Invert = 0, + Color, + }; + + enum class TextAntialiasMode + { + Default = 0, + ClearType, + Grayscale, + Aliased, + }; + + struct TargetSettings + { + COLORREF paddingColor = 0; + bool forceFullRepaint = false; + bool transparentBackground = false; + bool softwareRendering = false; + }; + + struct FontSettings + { + std::wstring faceName; + int family = 0; + int weight = 0; + int codePage = 0; + float fontSize = 0; + CSSLengthPercentage cellWidth; + CSSLengthPercentage cellHeight; + std::unordered_map features; + std::unordered_map axes; + TextAntialiasMode antialiasingMode = TextAntialiasMode::Default; + float dpi = 0; + }; + + struct CursorSettings + { + CursorType type = CursorType::Legacy; + CursorStyle style = CursorStyle::Invert; + COLORREF color = 0xffffffff; + float heightPercentage = 0.2f; + float widthInDIP = 1.0f; + }; + + struct SelectionSettings + { + COLORREF selectionColor = 0x7fffffff; + }; + + struct ShaderSettings + { + std::wstring shaderPath; + bool retroTerminalEffect = false; + }; + + struct Settings + { + til::generational target; + til::generational font; + til::generational cursor; + til::generational selection; + til::generational shader; + + til::size targetSizeInPixel; + }; + struct CompositionRange { size_t len; // The number of chars in Composition::text that this .attr applies to @@ -36,55 +91,28 @@ namespace Microsoft::Console::Render size_t cursorPos = 0; }; - class IRenderData + struct RenderData + { + til::generational settings; + std::vector selections; + uint16_t hoveredHyperlinkId = 0; + }; + + struct RenderingPayload + { + til::generational settings; + + Composition composition; + std::vector selections; + uint16_t hoveredHyperlinkId = 0; + + TextBuffer buffer; + }; + + struct IRenderData { - public: virtual ~IRenderData() = default; - // This block used to be IBaseData. - virtual Microsoft::Console::Types::Viewport GetViewport() noexcept = 0; - virtual til::point GetTextBufferEndPosition() const noexcept = 0; - virtual TextBuffer& GetTextBuffer() const noexcept = 0; - virtual const FontInfo& GetFontInfo() const noexcept = 0; - virtual std::span GetSearchHighlights() const noexcept = 0; - virtual const til::point_span* GetSearchHighlightFocused() const noexcept = 0; - virtual std::span GetSelectionSpans() const noexcept = 0; - virtual void LockConsole() noexcept = 0; - virtual void UnlockConsole() noexcept = 0; - - // This block used to be the original IRenderData. - virtual til::point GetCursorPosition() const noexcept = 0; - virtual bool IsCursorVisible() const noexcept = 0; - virtual bool IsCursorOn() const noexcept = 0; - virtual ULONG GetCursorHeight() const noexcept = 0; - virtual CursorType GetCursorStyle() const noexcept = 0; - virtual ULONG GetCursorPixelWidth() const noexcept = 0; - virtual bool IsCursorDoubleWidth() const = 0; - virtual const bool IsGridLineDrawingAllowed() noexcept = 0; - virtual const std::wstring_view GetConsoleTitle() const noexcept = 0; - virtual const std::wstring GetHyperlinkUri(uint16_t id) const = 0; - virtual const std::wstring GetHyperlinkCustomId(uint16_t id) const = 0; - virtual const std::vector GetPatternId(const til::point location) const = 0; - - // This block used to be IUiaData. - virtual std::pair GetAttributeColors(const TextAttribute& attr) const noexcept = 0; - virtual const bool IsSelectionActive() const = 0; - virtual const bool IsBlockSelection() const = 0; - virtual void ClearSelection() = 0; - virtual void SelectNewRegion(const til::point coordStart, const til::point coordEnd) = 0; - virtual const til::point GetSelectionAnchor() const noexcept = 0; - virtual const til::point GetSelectionEnd() const noexcept = 0; - virtual const bool IsUiaDataInitialized() const noexcept = 0; - - // Ideally this would not be stored on an interface, however ideally IRenderData should not be an interface in the first place. - // This is because we should have only 1 way how to represent render data across the codebase anyway, and it should - // be by-value in a struct so that we can snapshot it and release the terminal lock as quickly as possible. - const Composition& GetActiveComposition() const noexcept - { - return !snippetPreview.text.empty() ? snippetPreview : tsfPreview; - } - - Composition tsfPreview; - Composition snippetPreview; + virtual void UpdateRenderData(RenderData& data) = 0; }; } diff --git a/src/renderer/inc/IRenderEngine.hpp b/src/renderer/inc/IRenderEngine.hpp index a2cc944f29..f103a554f8 100644 --- a/src/renderer/inc/IRenderEngine.hpp +++ b/src/renderer/inc/IRenderEngine.hpp @@ -1,99 +1,18 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- IRenderEngine.hpp - -Abstract: -- This serves as the entry point for a specific graphics engine specific renderer. - -Author(s): -- Michael Niksa (MiNiksa) 17-Nov-2015 ---*/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. #pragma once -#include - -#include "CursorOptions.h" -#include "Cluster.hpp" -#include "FontInfoDesired.hpp" #include "IRenderData.hpp" -#include "RenderSettings.hpp" -#include "../../buffer/out/LineRendition.hpp" -#include "../../buffer/out/ImageSlice.hpp" -#pragma warning(push) -#pragma warning(disable : 4100) // '...': unreferenced formal parameter namespace Microsoft::Console::Render { - struct RenderFrameInfo - { - std::span searchHighlights; - const til::point_span* searchHighlightFocused; - std::span selectionSpans; - til::color selectionBackground; - }; - - enum class GridLines - { - None, - Top, - Bottom, - Left, - Right, - Underline, - DoubleUnderline, - CurlyUnderline, - DottedUnderline, - DashedUnderline, - Strikethrough, - HyperlinkUnderline - }; - using GridLineSet = til::enumset; - class __declspec(novtable) IRenderEngine { public: -#pragma warning(suppress : 26432) // If you define or delete any default operation in the type '...', define or delete them all (c.21). virtual ~IRenderEngine() = default; - [[nodiscard]] virtual HRESULT StartPaint() noexcept = 0; - [[nodiscard]] virtual HRESULT EndPaint() noexcept = 0; - [[nodiscard]] virtual bool RequiresContinuousRedraw() noexcept = 0; - virtual void WaitUntilCanRender() noexcept = 0; - [[nodiscard]] virtual HRESULT Present() noexcept = 0; - [[nodiscard]] virtual HRESULT ScrollFrame() noexcept = 0; - [[nodiscard]] virtual HRESULT Invalidate(const til::rect* psrRegion) noexcept = 0; - [[nodiscard]] virtual HRESULT InvalidateCursor(const til::rect* psrRegion) noexcept = 0; - [[nodiscard]] virtual HRESULT InvalidateSystem(const til::rect* prcDirtyClient) noexcept = 0; - [[nodiscard]] virtual HRESULT InvalidateSelection(std::span selections) noexcept = 0; - [[nodiscard]] virtual HRESULT InvalidateHighlight(std::span highlights, const TextBuffer& buffer) noexcept = 0; - [[nodiscard]] virtual HRESULT InvalidateScroll(const til::point* pcoordDelta) noexcept = 0; - [[nodiscard]] virtual HRESULT InvalidateAll() noexcept = 0; - [[nodiscard]] virtual HRESULT InvalidateTitle(std::wstring_view proposedTitle) noexcept = 0; - [[nodiscard]] virtual HRESULT NotifyNewText(const std::wstring_view newText) noexcept = 0; - [[nodiscard]] virtual HRESULT PrepareRenderInfo(RenderFrameInfo info) noexcept = 0; - [[nodiscard]] virtual HRESULT ResetLineTransform() noexcept = 0; - [[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 gridlineColor, COLORREF underlineColor, size_t cchLine, til::point coordTarget) noexcept = 0; - [[nodiscard]] virtual HRESULT PaintImageSlice(const ImageSlice& imageSlice, til::CoordType targetRow, til::CoordType viewportLeft) 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; - [[nodiscard]] virtual HRESULT UpdateFont(const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo) noexcept = 0; - [[nodiscard]] virtual HRESULT UpdateSoftFont(std::span bitPattern, til::size cellSize, size_t centeringHint) noexcept = 0; - [[nodiscard]] virtual HRESULT UpdateDpi(int iDpi) noexcept = 0; - [[nodiscard]] virtual HRESULT UpdateViewport(const til::inclusive_rect& srNewViewport) noexcept = 0; - [[nodiscard]] virtual HRESULT GetProposedFont(const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo, int iDpi) noexcept = 0; - [[nodiscard]] virtual HRESULT GetDirtyArea(std::span& area) noexcept = 0; - [[nodiscard]] virtual HRESULT GetFontSize(_Out_ til::size* pFontSize) noexcept = 0; - [[nodiscard]] virtual HRESULT IsGlyphWideByFont(std::wstring_view glyph, _Out_ bool* pResult) noexcept = 0; - [[nodiscard]] virtual HRESULT UpdateTitle(std::wstring_view newTitle) noexcept = 0; - virtual void UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept = 0; + virtual void WaitUntilCanRender() = 0; + virtual void Render(RenderingPayload& payload) = 0; }; } -#pragma warning(pop) diff --git a/src/renderer/inc/RenderEngineBase.hpp b/src/renderer/inc/RenderEngineBase.hpp deleted file mode 100644 index 2b168121a9..0000000000 --- a/src/renderer/inc/RenderEngineBase.hpp +++ /dev/null @@ -1,61 +0,0 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- RenderEngineBase.hpp - -Abstract: -- Implements a set of functions with common behavior across all render engines. - For example, the behavior for setting the title. The title may change many - times in the course of a single frame, but the RenderEngine should only - actually perform its update operation if at the start of a frame, the new - window title will be different then the last frames, and it should only ever - update the title once per frame. - -Author(s): -- Mike Griese (migrie) 10-July-2018 ---*/ -#include "IRenderEngine.hpp" - -#pragma once -namespace Microsoft::Console::Render -{ - class RenderEngineBase : public IRenderEngine - { - public: - [[nodiscard]] HRESULT InvalidateSelection(std::span selections) noexcept override; - [[nodiscard]] HRESULT InvalidateHighlight(std::span highlights, const TextBuffer& buffer) noexcept override; - [[nodiscard]] HRESULT InvalidateTitle(const std::wstring_view proposedTitle) noexcept override; - - [[nodiscard]] HRESULT UpdateTitle(const std::wstring_view newTitle) noexcept override; - - [[nodiscard]] HRESULT NotifyNewText(const std::wstring_view newText) noexcept override; - - [[nodiscard]] HRESULT UpdateSoftFont(const std::span bitPattern, - const til::size cellSize, - const size_t centeringHint) noexcept override; - - [[nodiscard]] HRESULT PrepareRenderInfo(RenderFrameInfo info) noexcept override; - - [[nodiscard]] HRESULT ResetLineTransform() noexcept override; - [[nodiscard]] HRESULT PrepareLineTransform(const LineRendition lineRendition, - const til::CoordType targetRow, - const til::CoordType viewportLeft) noexcept override; - - [[nodiscard]] HRESULT PaintImageSlice(const ImageSlice& imageSlice, - const til::CoordType targetRow, - const til::CoordType viewportLeft) noexcept override; - - [[nodiscard]] bool RequiresContinuousRedraw() noexcept override; - - void WaitUntilCanRender() noexcept override; - void UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept override; - - protected: - [[nodiscard]] virtual HRESULT _DoUpdateTitle(const std::wstring_view newTitle) noexcept = 0; - - bool _titleChanged = false; - std::wstring _lastFrameTitle; - }; -} diff --git a/src/tsf/Handle.cpp b/src/tsf/Handle.cpp index e60e251307..bdef9c96ea 100644 --- a/src/tsf/Handle.cpp +++ b/src/tsf/Handle.cpp @@ -5,6 +5,7 @@ #include "Handle.h" #include "Implementation.h" +#include "../renderer/inc/IRenderData.hpp" using namespace Microsoft::Console::TSF; @@ -85,3 +86,8 @@ bool Handle::HasActiveComposition() const noexcept { return _impl ? _impl->HasActiveComposition() : false; } + +Microsoft::Console::Render::Composition Handle::GetComposition() const +{ + return _impl ? _impl->GetComposition() : Render::Composition{}; +} diff --git a/src/tsf/Handle.h b/src/tsf/Handle.h index 7e2e912574..ef0abb359f 100644 --- a/src/tsf/Handle.h +++ b/src/tsf/Handle.h @@ -5,6 +5,7 @@ namespace Microsoft::Console::Render { + struct Composition; class Renderer; } @@ -24,7 +25,6 @@ namespace Microsoft::Console::TSF virtual RECT GetViewport() = 0; virtual RECT GetCursorPosition() = 0; virtual void HandleOutput(std::wstring_view text) = 0; - virtual Render::Renderer* GetRenderer() = 0; }; // A pimpl idiom wrapper for `Implementation` so that we don't pull in all the TSF headers everywhere. @@ -48,6 +48,7 @@ namespace Microsoft::Console::TSF void Focus(IDataProvider* provider) const; void Unfocus(IDataProvider* provider) const; bool HasActiveComposition() const noexcept; + Render::Composition GetComposition() const; private: Implementation* _impl = nullptr; diff --git a/src/tsf/Implementation.cpp b/src/tsf/Implementation.cpp index 234c43df23..a364cf9dd7 100644 --- a/src/tsf/Implementation.cpp +++ b/src/tsf/Implementation.cpp @@ -6,7 +6,7 @@ #include "Handle.h" #include "../buffer/out/TextAttribute.hpp" -#include "../renderer/base/renderer.hpp" +#include "../renderer/inc/IRenderData.hpp" #pragma warning(disable : 4100) // '...': unreferenced formal parameter @@ -142,24 +142,7 @@ void Implementation::Unfocus(IDataProvider* provider) return; } - { - const auto renderer = _provider->GetRenderer(); - const auto renderData = renderer->GetRenderData(); - - renderData->LockConsole(); - const auto unlock = wil::scope_exit([&]() { - renderData->UnlockConsole(); - }); - - if (!renderData->tsfPreview.text.empty()) - { - auto& comp = renderData->tsfPreview; - comp.text.clear(); - comp.attributes.clear(); - renderer->NotifyPaintFrame(); - } - } - + *_activeComposition.lock() = {}; _provider.reset(); if (_compositions > 0 && _ownerCompositionServices) @@ -173,6 +156,11 @@ bool Implementation::HasActiveComposition() const noexcept return _compositions > 0; } +Microsoft::Console::Render::Composition Implementation::GetComposition() const +{ + return *_activeComposition.lock_shared(); +} + #pragma region IUnknown STDMETHODIMP Implementation::QueryInterface(REFIID riid, void** ppvObj) noexcept @@ -648,20 +636,11 @@ void Implementation::_doCompositionUpdate(TfEditCookie ec) if (_provider) { { - const auto renderer = _provider->GetRenderer(); - const auto renderData = renderer->GetRenderData(); - - renderData->LockConsole(); - const auto unlock = wil::scope_exit([&]() { - renderData->UnlockConsole(); - }); - - auto& comp = renderData->tsfPreview; - comp.text = std::move(activeComposition); - comp.attributes = std::move(activeCompositionRanges); + const auto ac = _activeComposition.lock(); + ac->text = std::move(activeComposition); + ac->attributes = std::move(activeCompositionRanges); // The code block above that calculates the `cursorPos` will clamp it to a positive number. - comp.cursorPos = static_cast(cursorPos); - renderer->NotifyPaintFrame(); + ac->cursorPos = static_cast(cursorPos); } if (!finalizedString.empty()) diff --git a/src/tsf/Implementation.h b/src/tsf/Implementation.h index 5bf7251f6a..3322a0e9b8 100644 --- a/src/tsf/Implementation.h +++ b/src/tsf/Implementation.h @@ -3,10 +3,13 @@ #pragma once +#include + class TextAttribute; namespace Microsoft::Console::Render { + struct Composition; class Renderer; } @@ -27,6 +30,7 @@ namespace Microsoft::Console::TSF void Focus(IDataProvider* provider); void Unfocus(IDataProvider* provider); bool HasActiveComposition() const noexcept; + Render::Composition GetComposition() const; // IUnknown methods STDMETHODIMP QueryInterface(REFIID riid, void** ppvObj) noexcept override; @@ -130,5 +134,7 @@ namespace Microsoft::Console::TSF AnsiInputScope _ansiInputScope{ this }; inline static std::atomic _wantsAnsiInputScope{ false }; + + til::shared_mutex _activeComposition; }; } diff --git a/src/types/ScreenInfoUiaProviderBase.cpp b/src/types/ScreenInfoUiaProviderBase.cpp index 27dfb8c3b7..97ec7c21c8 100644 --- a/src/types/ScreenInfoUiaProviderBase.cpp +++ b/src/types/ScreenInfoUiaProviderBase.cpp @@ -31,7 +31,7 @@ SAFEARRAY* BuildIntSafeArray(std::span data) } #pragma warning(suppress : 26434) // WRL RuntimeClassInitialize base is a no-op and we need this for MakeAndInitialize -HRESULT ScreenInfoUiaProviderBase::RuntimeClassInitialize(_In_ Render::IRenderData* pData, _In_ std::wstring_view wordDelimiters) noexcept +HRESULT ScreenInfoUiaProviderBase::RuntimeClassInitialize(_In_ IUiaData* pData, _In_ std::wstring_view wordDelimiters) noexcept try { RETURN_HR_IF_NULL(E_INVALIDARG, pData); diff --git a/src/types/ScreenInfoUiaProviderBase.h b/src/types/ScreenInfoUiaProviderBase.h index 2634d468fa..4a41e022d0 100644 --- a/src/types/ScreenInfoUiaProviderBase.h +++ b/src/types/ScreenInfoUiaProviderBase.h @@ -22,7 +22,7 @@ Author(s): #pragma once #include "../buffer/out/textBuffer.hpp" -#include "../renderer/inc/IRenderData.hpp" +#include "../renderer/inc/FontInfo.hpp" #include "UiaTextRangeBase.hpp" #include "IUiaTraceable.h" @@ -34,12 +34,33 @@ namespace Microsoft::Console::Types { class Viewport; + class IUiaData + { + public: + virtual ~IUiaData() = default; + + virtual void LockConsole() noexcept = 0; + virtual void UnlockConsole() noexcept = 0; + virtual Viewport GetViewport() noexcept = 0; + virtual til::point GetTextBufferEndPosition() const noexcept = 0; + virtual TextBuffer& GetTextBuffer() const noexcept = 0; + virtual const FontInfo& GetFontInfo() const noexcept = 0; + virtual std::pair GetAttributeColors(const TextAttribute& attr) const noexcept = 0; + virtual const bool IsSelectionActive() const = 0; + virtual const bool IsBlockSelection() const = 0; + virtual void ClearSelection() = 0; + virtual void SelectNewRegion(const til::point coordStart, const til::point coordEnd) = 0; + virtual const til::point GetSelectionAnchor() const noexcept = 0; + virtual const til::point GetSelectionEnd() const noexcept = 0; + virtual const bool IsUiaDataInitialized() const noexcept = 0; + }; + class ScreenInfoUiaProviderBase : public WRL::RuntimeClass, IRawElementProviderSimple, IRawElementProviderFragment, ITextProvider>, public IUiaTraceable { public: - virtual HRESULT RuntimeClassInitialize(_In_ Render::IRenderData* pData, _In_ std::wstring_view wordDelimiters = UiaTextRangeBase::DefaultWordDelimiter) noexcept; + virtual HRESULT RuntimeClassInitialize(_In_ IUiaData* pData, _In_ std::wstring_view wordDelimiters = UiaTextRangeBase::DefaultWordDelimiter) noexcept; ScreenInfoUiaProviderBase(const ScreenInfoUiaProviderBase&) = delete; ScreenInfoUiaProviderBase(ScreenInfoUiaProviderBase&&) = delete; @@ -104,7 +125,7 @@ namespace Microsoft::Console::Types _COM_Outptr_result_maybenull_ UiaTextRangeBase** ppUtr) = 0; // weak reference to IRenderData - Render::IRenderData* _pData{ nullptr }; + IUiaData* _pData{ nullptr }; std::wstring _wordDelimiters{}; diff --git a/src/types/TermControlUiaProvider.cpp b/src/types/TermControlUiaProvider.cpp index 3cfc041f5d..84cfcdbaf9 100644 --- a/src/types/TermControlUiaProvider.cpp +++ b/src/types/TermControlUiaProvider.cpp @@ -9,7 +9,7 @@ using namespace Microsoft::Console::Types; using namespace Microsoft::WRL; #pragma warning(suppress : 26434) // WRL RuntimeClassInitialize base is a no-op and we need this for MakeAndInitialize -HRESULT TermControlUiaProvider::RuntimeClassInitialize(_In_ Console::Render::IRenderData* const renderData, +HRESULT TermControlUiaProvider::RuntimeClassInitialize(_In_ IUiaData* const renderData, _In_ ::Microsoft::Console::Types::IControlAccessibilityInfo* controlInfo) noexcept { RETURN_HR_IF_NULL(E_INVALIDARG, renderData); diff --git a/src/types/TermControlUiaProvider.hpp b/src/types/TermControlUiaProvider.hpp index ff2e244141..8d43492f25 100644 --- a/src/types/TermControlUiaProvider.hpp +++ b/src/types/TermControlUiaProvider.hpp @@ -29,7 +29,7 @@ namespace Microsoft::Terminal { public: TermControlUiaProvider() = default; - HRESULT RuntimeClassInitialize(_In_ Console::Render::IRenderData* const renderData, + HRESULT RuntimeClassInitialize(_In_ Console::Types::IUiaData* const renderData, _In_ ::Microsoft::Console::Types::IControlAccessibilityInfo* controlInfo) noexcept; // IRawElementProviderSimple methods diff --git a/src/types/TermControlUiaTextRange.cpp b/src/types/TermControlUiaTextRange.cpp index 5b26dd5189..c073447cc9 100644 --- a/src/types/TermControlUiaTextRange.cpp +++ b/src/types/TermControlUiaTextRange.cpp @@ -10,12 +10,12 @@ using namespace Microsoft::Console::Types; using namespace Microsoft::WRL; // degenerate range constructor. -HRESULT TermControlUiaTextRange::RuntimeClassInitialize(_In_ Console::Render::IRenderData* pData, _In_ IRawElementProviderSimple* const pProvider, _In_ const std::wstring_view wordDelimiters) noexcept +HRESULT TermControlUiaTextRange::RuntimeClassInitialize(_In_ Console::Types::IUiaData* pData, _In_ IRawElementProviderSimple* const pProvider, _In_ const std::wstring_view wordDelimiters) noexcept { return UiaTextRangeBase::RuntimeClassInitialize(pData, pProvider, wordDelimiters); } -HRESULT TermControlUiaTextRange::RuntimeClassInitialize(_In_ Console::Render::IRenderData* pData, +HRESULT TermControlUiaTextRange::RuntimeClassInitialize(_In_ Console::Types::IUiaData* pData, _In_ IRawElementProviderSimple* const pProvider, const Cursor& cursor, const std::wstring_view wordDelimiters) noexcept @@ -23,7 +23,7 @@ HRESULT TermControlUiaTextRange::RuntimeClassInitialize(_In_ Console::Render::IR return UiaTextRangeBase::RuntimeClassInitialize(pData, pProvider, cursor, wordDelimiters); } -HRESULT TermControlUiaTextRange::RuntimeClassInitialize(_In_ Console::Render::IRenderData* pData, +HRESULT TermControlUiaTextRange::RuntimeClassInitialize(_In_ Console::Types::IUiaData* pData, _In_ IRawElementProviderSimple* const pProvider, const til::point start, const til::point end, @@ -35,7 +35,7 @@ HRESULT TermControlUiaTextRange::RuntimeClassInitialize(_In_ Console::Render::IR // returns a degenerate text range of the start of the row closest to the y value of point #pragma warning(suppress : 26434) // WRL RuntimeClassInitialize base is a no-op and we need this for MakeAndInitialize -HRESULT TermControlUiaTextRange::RuntimeClassInitialize(_In_ Console::Render::IRenderData* pData, +HRESULT TermControlUiaTextRange::RuntimeClassInitialize(_In_ Console::Types::IUiaData* pData, _In_ IRawElementProviderSimple* const pProvider, const UiaPoint point, const std::wstring_view wordDelimiters) diff --git a/src/types/TermControlUiaTextRange.hpp b/src/types/TermControlUiaTextRange.hpp index e619583a9e..68bb5455a3 100644 --- a/src/types/TermControlUiaTextRange.hpp +++ b/src/types/TermControlUiaTextRange.hpp @@ -27,18 +27,18 @@ namespace Microsoft::Terminal TermControlUiaTextRange() = default; // degenerate range - HRESULT RuntimeClassInitialize(_In_ Console::Render::IRenderData* pData, + HRESULT RuntimeClassInitialize(_In_ Console::Types::IUiaData* pData, _In_ IRawElementProviderSimple* const pProvider, _In_ const std::wstring_view wordDelimiters = DefaultWordDelimiter) noexcept override; // degenerate range at cursor position - HRESULT RuntimeClassInitialize(_In_ Console::Render::IRenderData* pData, + HRESULT RuntimeClassInitialize(_In_ Console::Types::IUiaData* pData, _In_ IRawElementProviderSimple* const pProvider, const Cursor& cursor, const std::wstring_view wordDelimiters = DefaultWordDelimiter) noexcept override; // specific endpoint range - HRESULT RuntimeClassInitialize(_In_ Console::Render::IRenderData* pData, + HRESULT RuntimeClassInitialize(_In_ Console::Types::IUiaData* pData, _In_ IRawElementProviderSimple* const pProvider, const til::point start, const til::point end, @@ -46,7 +46,7 @@ namespace Microsoft::Terminal const std::wstring_view wordDelimiters = DefaultWordDelimiter) noexcept override; // range from a UiaPoint - HRESULT RuntimeClassInitialize(_In_ Console::Render::IRenderData* pData, + HRESULT RuntimeClassInitialize(_In_ Console::Types::IUiaData* pData, _In_ IRawElementProviderSimple* const pProvider, const UiaPoint point, const std::wstring_view wordDelimiters = DefaultWordDelimiter); diff --git a/src/types/UiaTextRangeBase.cpp b/src/types/UiaTextRangeBase.cpp index 0ce958e698..60f21e2a40 100644 --- a/src/types/UiaTextRangeBase.cpp +++ b/src/types/UiaTextRangeBase.cpp @@ -16,7 +16,7 @@ static constexpr long _RemoveAlpha(COLORREF color) noexcept // degenerate range constructor. #pragma warning(suppress : 26434) // WRL RuntimeClassInitialize base is a no-op and we need this for MakeAndInitialize -HRESULT UiaTextRangeBase::RuntimeClassInitialize(_In_ Render::IRenderData* pData, _In_ IRawElementProviderSimple* const pProvider, _In_ std::wstring_view wordDelimiters) noexcept +HRESULT UiaTextRangeBase::RuntimeClassInitialize(_In_ IUiaData* pData, _In_ IRawElementProviderSimple* const pProvider, _In_ std::wstring_view wordDelimiters) noexcept try { RETURN_HR_IF_NULL(E_INVALIDARG, pProvider); @@ -40,7 +40,7 @@ try CATCH_RETURN(); #pragma warning(suppress : 26434) // WRL RuntimeClassInitialize base is a no-op and we need this for MakeAndInitialize -HRESULT UiaTextRangeBase::RuntimeClassInitialize(_In_ Render::IRenderData* pData, +HRESULT UiaTextRangeBase::RuntimeClassInitialize(_In_ IUiaData* pData, _In_ IRawElementProviderSimple* const pProvider, _In_ const Cursor& cursor, _In_ std::wstring_view wordDelimiters) noexcept @@ -72,7 +72,7 @@ try CATCH_RETURN(); #pragma warning(suppress : 26434) // WRL RuntimeClassInitialize base is a no-op and we need this for MakeAndInitialize -HRESULT UiaTextRangeBase::RuntimeClassInitialize(_In_ Render::IRenderData* pData, +HRESULT UiaTextRangeBase::RuntimeClassInitialize(_In_ IUiaData* pData, _In_ IRawElementProviderSimple* const pProvider, _In_ const til::point start, _In_ const til::point end, @@ -625,10 +625,11 @@ try // -> We need to turn [_beg,_end) into (_beg,_end). exclusiveBegin.x--; - if (_searcher.IsStale(*_pData, queryText, ignoreCase ? SearchFlag::CaseInsensitive : SearchFlag::None)) - { - _searcher.Reset(*_pData, queryText, ignoreCase ? SearchFlag::CaseInsensitive : SearchFlag::None, searchBackward); - } + // TODO: + //if (_searcher.IsStale(*_pData, queryText, ignoreCase ? SearchFlag::CaseInsensitive : SearchFlag::None)) + //{ + // _searcher.Reset(*_pData, queryText, ignoreCase ? SearchFlag::CaseInsensitive : SearchFlag::None, searchBackward); + //} _searcher.MovePastPoint(searchBackward ? _end : exclusiveBegin); til::point hitBeg{ til::CoordTypeMax, til::CoordTypeMax }; diff --git a/src/types/UiaTextRangeBase.hpp b/src/types/UiaTextRangeBase.hpp index 2b36ccc0ef..6cc348f510 100644 --- a/src/types/UiaTextRangeBase.hpp +++ b/src/types/UiaTextRangeBase.hpp @@ -30,6 +30,8 @@ class UiaTextRangeTests; namespace Microsoft::Console::Types { + class IUiaData; + class UiaTextRangeBase : public WRL::RuntimeClass, ITextRangeProvider>, public IUiaTraceable { protected: @@ -46,18 +48,18 @@ namespace Microsoft::Console::Types static constexpr std::wstring_view DefaultWordDelimiter{ &UNICODE_SPACE, 1 }; // degenerate range - virtual HRESULT RuntimeClassInitialize(_In_ Render::IRenderData* pData, + virtual HRESULT RuntimeClassInitialize(_In_ IUiaData* pData, _In_ IRawElementProviderSimple* const pProvider, _In_ std::wstring_view wordDelimiters = DefaultWordDelimiter) noexcept; // degenerate range at cursor position - virtual HRESULT RuntimeClassInitialize(_In_ Render::IRenderData* pData, + virtual HRESULT RuntimeClassInitialize(_In_ IUiaData* pData, _In_ IRawElementProviderSimple* const pProvider, _In_ const Cursor& cursor, _In_ std::wstring_view wordDelimiters = DefaultWordDelimiter) noexcept; // specific endpoint range - virtual HRESULT RuntimeClassInitialize(_In_ Render::IRenderData* pData, + virtual HRESULT RuntimeClassInitialize(_In_ IUiaData* pData, _In_ IRawElementProviderSimple* const pProvider, _In_ const til::point start, _In_ const til::point end, @@ -116,7 +118,7 @@ namespace Microsoft::Console::Types protected: UiaTextRangeBase() = default; - Render::IRenderData* _pData{ nullptr }; + IUiaData* _pData{ nullptr }; IRawElementProviderSimple* _pProvider{ nullptr };