Remove std::deque from Renderer (#10923)
This commit improves the renderer classes by: * reducing binary size by 4kB * improving performance by 5% * reducing code complexity ## References * #10563 -- vtebench tracking issue ## PR Checklist * [x] I work here * [x] Tests added/passed ## Validation Steps Performed * Ran vtebench/termbench and noted ~5% perf. improvements
This commit is contained in:
parent
2c3368f766
commit
15c02b77a0
|
@ -164,10 +164,7 @@ class AliasTests
|
|||
wcscpy_s(rgwchTargetBefore.get(), cchTarget, rgwchTarget.get());
|
||||
|
||||
size_t cbTargetUsed = 0;
|
||||
auto const cbTargetUsedBefore = cbTargetUsed;
|
||||
|
||||
DWORD dwLines = 0;
|
||||
auto const dwLinesBefore = dwLines;
|
||||
|
||||
// Register the wrong alias name before we try.
|
||||
std::wstring exe(L"exe.exe");
|
||||
|
@ -306,7 +303,6 @@ class AliasTests
|
|||
auto rgwchTargetBefore = std::make_unique<wchar_t[]>(cchTarget);
|
||||
wcscpy_s(rgwchTargetBefore.get(), cchTarget, rgwchTarget.get());
|
||||
|
||||
const size_t cbTarget = cchTarget * sizeof(wchar_t);
|
||||
size_t cbTargetUsed = 0;
|
||||
auto const cbTargetUsedBefore = cbTargetUsed;
|
||||
|
||||
|
|
|
@ -899,7 +899,6 @@ void TextBufferTests::TestUnBold()
|
|||
|
||||
const auto& row = tbi.GetRowByOffset(y);
|
||||
const auto attrRow = &row.GetAttrRow();
|
||||
const auto len = tbi.GetSize().Width();
|
||||
const std::vector<TextAttribute> attrs{ attrRow->begin(), attrRow->end() };
|
||||
const auto attrA = attrs[x - 2];
|
||||
const auto attrB = attrs[x - 1];
|
||||
|
@ -951,7 +950,6 @@ void TextBufferTests::TestUnBoldRgb()
|
|||
|
||||
const auto& row = tbi.GetRowByOffset(y);
|
||||
const auto attrRow = &row.GetAttrRow();
|
||||
const auto len = tbi.GetSize().Width();
|
||||
const std::vector<TextAttribute> attrs{ attrRow->begin(), attrRow->end() };
|
||||
const auto attrA = attrs[x - 2];
|
||||
const auto attrB = attrs[x - 1];
|
||||
|
@ -1011,7 +1009,6 @@ void TextBufferTests::TestComplexUnBold()
|
|||
|
||||
const auto& row = tbi.GetRowByOffset(y);
|
||||
const auto attrRow = &row.GetAttrRow();
|
||||
const auto len = tbi.GetSize().Width();
|
||||
const std::vector<TextAttribute> attrs{ attrRow->begin(), attrRow->end() };
|
||||
const auto attrA = attrs[x - 6];
|
||||
const auto attrB = attrs[x - 5];
|
||||
|
@ -1094,7 +1091,6 @@ void TextBufferTests::CopyAttrs()
|
|||
|
||||
const auto& row = tbi.GetRowByOffset(0);
|
||||
const auto attrRow = &row.GetAttrRow();
|
||||
const auto len = tbi.GetSize().Width();
|
||||
const std::vector<TextAttribute> attrs{ attrRow->begin(), attrRow->end() };
|
||||
const auto attrA = attrs[0];
|
||||
const auto attrB = attrs[1];
|
||||
|
@ -1146,7 +1142,6 @@ void TextBufferTests::EmptySgrTest()
|
|||
|
||||
const auto& row = tbi.GetRowByOffset(y);
|
||||
const auto attrRow = &row.GetAttrRow();
|
||||
const auto len = tbi.GetSize().Width();
|
||||
const std::vector<TextAttribute> attrs{ attrRow->begin(), attrRow->end() };
|
||||
const auto attrA = attrs[x - 3];
|
||||
const auto attrB = attrs[x - 2];
|
||||
|
@ -1208,7 +1203,6 @@ void TextBufferTests::TestReverseReset()
|
|||
|
||||
const auto& row = tbi.GetRowByOffset(y);
|
||||
const auto attrRow = &row.GetAttrRow();
|
||||
const auto len = tbi.GetSize().Width();
|
||||
const std::vector<TextAttribute> attrs{ attrRow->begin(), attrRow->end() };
|
||||
const auto attrA = attrs[x - 3];
|
||||
const auto attrB = attrs[x - 2];
|
||||
|
@ -1310,7 +1304,6 @@ void TextBufferTests::CopyLastAttr()
|
|||
const ROW& row1 = tbi.GetRowByOffset(y + 1);
|
||||
const ROW& row2 = tbi.GetRowByOffset(y + 2);
|
||||
const ROW& row3 = tbi.GetRowByOffset(y + 3);
|
||||
const auto len = tbi.GetSize().Width();
|
||||
|
||||
const std::vector<TextAttribute> attrs1{ row1.GetAttrRow().begin(), row1.GetAttrRow().end() };
|
||||
const std::vector<TextAttribute> attrs2{ row2.GetAttrRow().begin(), row2.GetAttrRow().end() };
|
||||
|
|
|
@ -456,6 +456,7 @@ void VtIoTests::RendererDtorAndThreadAndDx()
|
|||
// which is what CI uses.
|
||||
/*Sleep(500);*/
|
||||
|
||||
(void)dxEngine->Enable();
|
||||
pThread->EnablePainting();
|
||||
pRenderer->TriggerTeardown();
|
||||
pRenderer.reset();
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
|
||||
#include "../inc/Cluster.hpp"
|
||||
#include "../../inc/unicode.hpp"
|
||||
#include "../types/inc/convert.hpp"
|
||||
|
||||
using namespace Microsoft::Console::Render;
|
||||
|
||||
// Routine Description:
|
||||
// - Instantiates a new cluster structure
|
||||
// Arguments:
|
||||
// - text - This is a view of the text that forms this cluster (one or more wchar_t*s)
|
||||
// - columns - This is the number of columns in the grid that the cluster should consume when drawn
|
||||
Cluster::Cluster(const std::wstring_view text, const size_t columns) :
|
||||
_text(text),
|
||||
_columns(columns)
|
||||
{
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - 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
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - The only wchar_t in the string or the Unicode replacement character as appropriate.
|
||||
const wchar_t Cluster::GetTextAsSingle() const noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
return Utf16ToUcs2(_text);
|
||||
}
|
||||
CATCH_LOG();
|
||||
return UNICODE_REPLACEMENT;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Provides the string of wchar_ts for this cluster.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - String view of wchar_ts.
|
||||
const std::wstring_view& Cluster::GetText() const noexcept
|
||||
{
|
||||
return _text;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Gets the number of columns in the grid that this character should consume
|
||||
// visually when rendered onto a line.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - Number of columns to use when drawing (not a pixel count).
|
||||
const size_t Cluster::GetColumns() const noexcept
|
||||
{
|
||||
return _columns;
|
||||
}
|
|
@ -11,7 +11,6 @@
|
|||
<Import Project="$(SolutionDir)src\common.build.pre.props" />
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\BlinkingState.cpp" />
|
||||
<ClCompile Include="..\Cluster.cpp" />
|
||||
<ClCompile Include="..\FontInfo.cpp" />
|
||||
<ClCompile Include="..\FontInfoBase.cpp" />
|
||||
<ClCompile Include="..\FontInfoDesired.cpp" />
|
||||
|
|
|
@ -45,9 +45,6 @@
|
|||
<ClCompile Include="..\BlinkingState.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\Cluster.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\precomp.h">
|
||||
|
|
|
@ -16,6 +16,12 @@ 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:
|
||||
|
@ -23,22 +29,17 @@ static constexpr auto renderBackoffBaseTimeMilliseconds{ 150 };
|
|||
// - pEngine - The output engine for targeting each rendering frame
|
||||
// Return Value:
|
||||
// - An instance of a Renderer.
|
||||
// NOTE: CAN THROW IF MEMORY ALLOCATION FAILS.
|
||||
Renderer::Renderer(IRenderData* pData,
|
||||
_In_reads_(cEngines) IRenderEngine** const rgpEngines,
|
||||
const size_t cEngines,
|
||||
std::unique_ptr<IRenderThread> thread) :
|
||||
_pData(THROW_HR_IF_NULL(E_INVALIDARG, pData)),
|
||||
_pThread{ std::move(thread) },
|
||||
_destructing{ false },
|
||||
_clusterBuffer{},
|
||||
_viewport{ pData->GetViewport() }
|
||||
{
|
||||
for (size_t i = 0; i < cEngines; i++)
|
||||
{
|
||||
IRenderEngine* engine = rgpEngines[i];
|
||||
// NOTE: THIS CAN THROW IF MEMORY ALLOCATION FAILS.
|
||||
AddRenderEngine(engine);
|
||||
AddRenderEngine(rgpEngines[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,6 +51,7 @@ Renderer::Renderer(IRenderData* pData,
|
|||
// - <none>
|
||||
Renderer::~Renderer()
|
||||
{
|
||||
// IRenderThread blocks until it has shut down.
|
||||
_destructing = true;
|
||||
_pThread.reset();
|
||||
}
|
||||
|
@ -62,12 +64,7 @@ Renderer::~Renderer()
|
|||
// - HRESULT S_OK, GDI error, Safe Math error, or state/argument errors.
|
||||
[[nodiscard]] HRESULT Renderer::PaintFrame()
|
||||
{
|
||||
if (_destructing)
|
||||
{
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
for (IRenderEngine* const pEngine : _rgpEngines)
|
||||
FOREACH_ENGINE(pEngine)
|
||||
{
|
||||
auto tries = maxRetriesForRenderEngine;
|
||||
while (tries > 0)
|
||||
|
@ -93,6 +90,7 @@ Renderer::~Renderer()
|
|||
// 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));
|
||||
|
@ -202,9 +200,10 @@ void Renderer::_NotifyPaintFrame()
|
|||
// - <none>
|
||||
void Renderer::TriggerSystemRedraw(const RECT* const prcDirtyClient)
|
||||
{
|
||||
std::for_each(_rgpEngines.begin(), _rgpEngines.end(), [&](IRenderEngine* const pEngine) {
|
||||
FOREACH_ENGINE(pEngine)
|
||||
{
|
||||
LOG_IF_FAILED(pEngine->InvalidateSystem(prcDirtyClient));
|
||||
});
|
||||
}
|
||||
|
||||
_NotifyPaintFrame();
|
||||
}
|
||||
|
@ -235,9 +234,10 @@ void Renderer::TriggerRedraw(const Viewport& region)
|
|||
if (view.TrimToViewport(&srUpdateRegion))
|
||||
{
|
||||
view.ConvertToOrigin(&srUpdateRegion);
|
||||
std::for_each(_rgpEngines.begin(), _rgpEngines.end(), [&](IRenderEngine* const pEngine) {
|
||||
FOREACH_ENGINE(pEngine)
|
||||
{
|
||||
LOG_IF_FAILED(pEngine->Invalidate(&srUpdateRegion));
|
||||
});
|
||||
}
|
||||
|
||||
_NotifyPaintFrame();
|
||||
}
|
||||
|
@ -286,7 +286,7 @@ void Renderer::TriggerRedrawCursor(const COORD* const pcoord)
|
|||
if (cursorView.IsValid())
|
||||
{
|
||||
const SMALL_RECT updateRect = view.ConvertToOrigin(cursorView).ToExclusive();
|
||||
for (IRenderEngine* pEngine : _rgpEngines)
|
||||
FOREACH_ENGINE(pEngine)
|
||||
{
|
||||
LOG_IF_FAILED(pEngine->InvalidateCursor(&updateRect));
|
||||
}
|
||||
|
@ -305,9 +305,10 @@ void Renderer::TriggerRedrawCursor(const COORD* const pcoord)
|
|||
// - <none>
|
||||
void Renderer::TriggerRedrawAll()
|
||||
{
|
||||
std::for_each(_rgpEngines.begin(), _rgpEngines.end(), [&](IRenderEngine* const pEngine) {
|
||||
FOREACH_ENGINE(pEngine)
|
||||
{
|
||||
LOG_IF_FAILED(pEngine->InvalidateAll());
|
||||
});
|
||||
}
|
||||
|
||||
_NotifyPaintFrame();
|
||||
}
|
||||
|
@ -325,7 +326,7 @@ void Renderer::TriggerTeardown() noexcept
|
|||
_pThread->WaitForPaintCompletionAndDisable(INFINITE);
|
||||
|
||||
// Then walk through and do one final paint on the caller's thread.
|
||||
for (IRenderEngine* const pEngine : _rgpEngines)
|
||||
FOREACH_ENGINE(pEngine)
|
||||
{
|
||||
bool fEngineRequestsRepaint = false;
|
||||
HRESULT hr = pEngine->PrepareForTeardown(&fEngineRequestsRepaint);
|
||||
|
@ -349,7 +350,7 @@ void Renderer::TriggerSelection()
|
|||
try
|
||||
{
|
||||
// Get selection rectangles
|
||||
const auto rects = _GetSelectionRects();
|
||||
auto rects = _GetSelectionRects();
|
||||
|
||||
// Restrict all previous selection rectangles to inside the current viewport bounds
|
||||
for (auto& sr : _previousSelection)
|
||||
|
@ -367,12 +368,13 @@ void Renderer::TriggerSelection()
|
|||
sr = Viewport::FromInclusive(rc).ToExclusive();
|
||||
}
|
||||
|
||||
std::for_each(_rgpEngines.begin(), _rgpEngines.end(), [&](IRenderEngine* const pEngine) {
|
||||
FOREACH_ENGINE(pEngine)
|
||||
{
|
||||
LOG_IF_FAILED(pEngine->InvalidateSelection(_previousSelection));
|
||||
LOG_IF_FAILED(pEngine->InvalidateSelection(rects));
|
||||
});
|
||||
}
|
||||
|
||||
_previousSelection = rects;
|
||||
_previousSelection = std::move(rects);
|
||||
|
||||
_NotifyPaintFrame();
|
||||
}
|
||||
|
@ -390,36 +392,25 @@ bool Renderer::_CheckViewportAndScroll()
|
|||
SMALL_RECT const srOldViewport = _viewport.ToInclusive();
|
||||
SMALL_RECT const srNewViewport = _pData->GetViewport().ToInclusive();
|
||||
|
||||
COORD coordDelta;
|
||||
coordDelta.X = srOldViewport.Left - srNewViewport.Left;
|
||||
coordDelta.Y = srOldViewport.Top - srNewViewport.Top;
|
||||
|
||||
for (auto engine : _rgpEngines)
|
||||
if (srOldViewport == srNewViewport)
|
||||
{
|
||||
LOG_IF_FAILED(engine->UpdateViewport(srNewViewport));
|
||||
return false;
|
||||
}
|
||||
|
||||
_viewport = Viewport::FromInclusive(srNewViewport);
|
||||
|
||||
// If we're keeping some buffers between calls, let them know about the viewport size
|
||||
// so they can prepare the buffers for changes to either preallocate memory at once
|
||||
// (instead of growing naturally) or shrink down to reduce usage as appropriate.
|
||||
const size_t lineLength = gsl::narrow_cast<size_t>(til::rectangle{ srNewViewport }.width());
|
||||
til::manage_vector(_clusterBuffer, lineLength, _shrinkThreshold);
|
||||
COORD coordDelta;
|
||||
coordDelta.X = srOldViewport.Left - srNewViewport.Left;
|
||||
coordDelta.Y = srOldViewport.Top - srNewViewport.Top;
|
||||
|
||||
if (coordDelta.X != 0 || coordDelta.Y != 0)
|
||||
FOREACH_ENGINE(engine)
|
||||
{
|
||||
for (auto engine : _rgpEngines)
|
||||
{
|
||||
LOG_IF_FAILED(engine->InvalidateScroll(&coordDelta));
|
||||
}
|
||||
|
||||
_ScrollPreviousSelection(coordDelta);
|
||||
|
||||
return true;
|
||||
LOG_IF_FAILED(engine->UpdateViewport(srNewViewport));
|
||||
LOG_IF_FAILED(engine->InvalidateScroll(&coordDelta));
|
||||
}
|
||||
|
||||
return false;
|
||||
_ScrollPreviousSelection(coordDelta);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
|
@ -448,9 +439,10 @@ void Renderer::TriggerScroll()
|
|||
// - <none>
|
||||
void Renderer::TriggerScroll(const COORD* const pcoordDelta)
|
||||
{
|
||||
std::for_each(_rgpEngines.begin(), _rgpEngines.end(), [&](IRenderEngine* const pEngine) {
|
||||
FOREACH_ENGINE(pEngine)
|
||||
{
|
||||
LOG_IF_FAILED(pEngine->InvalidateScroll(pcoordDelta));
|
||||
});
|
||||
}
|
||||
|
||||
_ScrollPreviousSelection(*pcoordDelta);
|
||||
|
||||
|
@ -468,7 +460,7 @@ void Renderer::TriggerCircling()
|
|||
{
|
||||
const auto rects = _GetSelectionRects();
|
||||
|
||||
for (IRenderEngine* const pEngine : _rgpEngines)
|
||||
FOREACH_ENGINE(pEngine)
|
||||
{
|
||||
bool fEngineRequestsRepaint = false;
|
||||
HRESULT hr = pEngine->InvalidateCircling(&fEngineRequestsRepaint);
|
||||
|
@ -493,7 +485,7 @@ void Renderer::TriggerCircling()
|
|||
void Renderer::TriggerTitleChange()
|
||||
{
|
||||
const auto newTitle = _pData->GetConsoleTitle();
|
||||
for (IRenderEngine* const pEngine : _rgpEngines)
|
||||
FOREACH_ENGINE(pEngine)
|
||||
{
|
||||
LOG_IF_FAILED(pEngine->InvalidateTitle(newTitle));
|
||||
}
|
||||
|
@ -522,10 +514,11 @@ HRESULT Renderer::_PaintTitle(IRenderEngine* const pEngine)
|
|||
// - <none>
|
||||
void Renderer::TriggerFontChange(const int iDpi, const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo)
|
||||
{
|
||||
std::for_each(_rgpEngines.begin(), _rgpEngines.end(), [&](IRenderEngine* const pEngine) {
|
||||
FOREACH_ENGINE(pEngine)
|
||||
{
|
||||
LOG_IF_FAILED(pEngine->UpdateDpi(iDpi));
|
||||
LOG_IF_FAILED(pEngine->UpdateFont(FontInfoDesired, FontInfo));
|
||||
});
|
||||
}
|
||||
|
||||
_NotifyPaintFrame();
|
||||
}
|
||||
|
@ -547,16 +540,19 @@ void Renderer::UpdateSoftFont(const gsl::span<const uint16_t> bitPattern, const
|
|||
const auto softFontCharCount = cellSize.cy ? bitPattern.size() / cellSize.cy : 0;
|
||||
_lastSoftFontChar = _firstSoftFontChar + softFontCharCount - 1;
|
||||
|
||||
for (const auto pEngine : _rgpEngines)
|
||||
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.front() >= firstSoftFontChar && v.front() <= lastSoftFontChar;
|
||||
return v.size() == 1 && v[0] >= firstSoftFontChar && v[0] <= lastSoftFontChar;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
|
@ -570,21 +566,11 @@ bool Renderer::s_IsSoftFontChar(const std::wstring_view& v, const size_t firstSo
|
|||
// - S_OK if set successfully or relevant GDI error via HRESULT.
|
||||
[[nodiscard]] HRESULT Renderer::GetProposedFont(const int iDpi, const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo)
|
||||
{
|
||||
// If there's no head, return E_FAIL. The caller should decide how to
|
||||
// handle this.
|
||||
// Currently, the only caller is the WindowProc:WM_GETDPISCALEDSIZE handler.
|
||||
// It will assume that the proposed font is 1x1, regardless of DPI.
|
||||
if (_rgpEngines.size() < 1)
|
||||
{
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
// 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
|
||||
FAIL_FAST_IF(!(_rgpEngines.size() <= 2));
|
||||
for (IRenderEngine* const pEngine : _rgpEngines)
|
||||
FOREACH_ENGINE(pEngine)
|
||||
{
|
||||
const HRESULT hr = LOG_IF_FAILED(pEngine->GetProposedFont(FontInfoDesired, FontInfo, iDpi));
|
||||
// We're looking for specifically S_OK, S_FALSE is not good enough.
|
||||
|
@ -592,7 +578,7 @@ bool Renderer::s_IsSoftFontChar(const std::wstring_view& v, const size_t firstSo
|
|||
{
|
||||
return hr;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return E_FAIL;
|
||||
}
|
||||
|
@ -615,14 +601,13 @@ bool Renderer::IsGlyphWideByFont(const std::wstring_view glyph)
|
|||
// 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
|
||||
FAIL_FAST_IF(!(_rgpEngines.size() <= 2));
|
||||
for (IRenderEngine* const pEngine : _rgpEngines)
|
||||
FOREACH_ENGINE(pEngine)
|
||||
{
|
||||
const HRESULT 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)
|
||||
{
|
||||
return fIsFullWidth;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -691,6 +676,12 @@ void Renderer::_PaintBufferOutput(_In_ IRenderEngine* const pEngine)
|
|||
|
||||
for (const auto& dirtyRect : dirtyAreas)
|
||||
{
|
||||
// Shortcut: don't bother redrawing if the width is 0.
|
||||
if (dirtyRect.left() == dirtyRect.right())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auto dirty = Viewport::FromInclusive(dirtyRect);
|
||||
|
||||
// Shift the origin of the dirty region to match the underlying buffer so we can
|
||||
|
@ -702,47 +693,43 @@ void Renderer::_PaintBufferOutput(_In_ IRenderEngine* const pEngine)
|
|||
// we need to walk through line-by-line and repaint onto the screen.
|
||||
const auto redraw = Viewport::Intersect(dirty, view);
|
||||
|
||||
// Shortcut: don't bother redrawing if the width is 0.
|
||||
if (redraw.Width() > 0)
|
||||
// Retrieve the text buffer so we can read information out of it.
|
||||
const 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++)
|
||||
{
|
||||
// Retrieve the text buffer so we can read information out of it.
|
||||
const auto& buffer = _pData->GetTextBuffer();
|
||||
// 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 = SMALL_RECT{ redraw.Left(), row, redraw.RightInclusive(), row };
|
||||
|
||||
// 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 = SMALL_RECT{ redraw.Left(), row, redraw.RightInclusive(), row };
|
||||
// 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));
|
||||
|
||||
// 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() - COORD{ 0, view.Top() };
|
||||
|
||||
// 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() - COORD{ 0, view.Top() };
|
||||
// Retrieve the cell information iterator limited to just this line we want to redraw.
|
||||
auto it = buffer.GetCellDataAt(bufferLine.Origin(), bufferLine);
|
||||
|
||||
// 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());
|
||||
|
||||
// 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()));
|
||||
|
||||
// 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);
|
||||
}
|
||||
// Ask the helper to paint through this specific line.
|
||||
_PaintBufferOutputHelper(pEngine, it, screenPosition, lineWrapped);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -750,7 +737,7 @@ void Renderer::_PaintBufferOutput(_In_ IRenderEngine* const pEngine)
|
|||
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;
|
||||
return v.find_first_not_of(L' ') == decltype(v)::npos;
|
||||
}
|
||||
|
||||
void Renderer::_PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine,
|
||||
|
@ -839,7 +826,7 @@ void Renderer::_PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine,
|
|||
|
||||
// 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.
|
||||
size_t columnCount = 0;
|
||||
size_t 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.
|
||||
|
@ -850,14 +837,7 @@ void Renderer::_PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine,
|
|||
// 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 = it->Columns() + 1;
|
||||
_clusterBuffer.emplace_back(it->Chars(), columnCount);
|
||||
}
|
||||
// Otherwise if it's not a special case, just insert it as is.
|
||||
else
|
||||
{
|
||||
columnCount = it->Columns();
|
||||
_clusterBuffer.emplace_back(it->Chars(), columnCount);
|
||||
++columnCount;
|
||||
}
|
||||
|
||||
if (columnCount > 1)
|
||||
|
@ -866,6 +846,7 @@ void Renderer::_PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine,
|
|||
}
|
||||
|
||||
// Advance the cluster and column counts.
|
||||
_clusterBuffer.emplace_back(it->Chars(), columnCount);
|
||||
it += std::max<size_t>(it->Columns(), 1); // prevent infinite loop for no visible columns
|
||||
cols += columnCount;
|
||||
|
||||
|
@ -1263,6 +1244,7 @@ std::vector<SMALL_RECT> Renderer::_GetSelectionRects() const
|
|||
Viewport view = _pData->GetViewport();
|
||||
|
||||
std::vector<SMALL_RECT> result;
|
||||
result.reserve(rects.size());
|
||||
|
||||
for (auto rect : rects)
|
||||
{
|
||||
|
@ -1322,7 +1304,17 @@ void Renderer::_ScrollPreviousSelection(const til::point delta)
|
|||
void Renderer::AddRenderEngine(_In_ IRenderEngine* const pEngine)
|
||||
{
|
||||
THROW_HR_IF_NULL(E_INVALIDARG, pEngine);
|
||||
_rgpEngines.push_back(pEngine);
|
||||
|
||||
for (auto& p : _engines)
|
||||
{
|
||||
if (!p)
|
||||
{
|
||||
p = pEngine;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
THROW_HR_MSG(E_UNEXPECTED, "engines array is full");
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
@ -1354,7 +1346,7 @@ void Renderer::UpdateLastHoveredInterval(const std::optional<PointTree::interval
|
|||
// - Blocks until the engines are able to render without blocking.
|
||||
void Renderer::WaitUntilCanRender()
|
||||
{
|
||||
for (const auto pEngine : _rgpEngines)
|
||||
FOREACH_ENGINE(pEngine)
|
||||
{
|
||||
pEngine->WaitUntilCanRender();
|
||||
}
|
||||
|
|
|
@ -87,73 +87,39 @@ namespace Microsoft::Console::Render
|
|||
void UpdateLastHoveredInterval(const std::optional<interval_tree::IntervalTree<til::point, size_t>::interval>& newInterval);
|
||||
|
||||
private:
|
||||
std::deque<IRenderEngine*> _rgpEngines;
|
||||
|
||||
IRenderData* _pData; // Non-ownership pointer
|
||||
|
||||
std::unique_ptr<IRenderThread> _pThread;
|
||||
bool _destructing = false;
|
||||
|
||||
std::optional<interval_tree::IntervalTree<til::point, size_t>::interval> _hoveredInterval;
|
||||
static IRenderEngine::GridLines s_GetGridlines(const TextAttribute& textAttribute) noexcept;
|
||||
static bool s_IsSoftFontChar(const std::wstring_view& v, const size_t firstSoftFontChar, const size_t lastSoftFontChar);
|
||||
|
||||
void _NotifyPaintFrame();
|
||||
|
||||
[[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 COORD target,
|
||||
const bool lineWrapped);
|
||||
|
||||
static IRenderEngine::GridLines s_GetGridlines(const TextAttribute& textAttribute) noexcept;
|
||||
|
||||
void _PaintBufferOutputGridLineHelper(_In_ IRenderEngine* const pEngine,
|
||||
const TextAttribute textAttribute,
|
||||
const size_t cchLine,
|
||||
const COORD coordTarget);
|
||||
|
||||
void _PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine, TextBufferCellIterator it, const COORD target, const bool lineWrapped);
|
||||
void _PaintBufferOutputGridLineHelper(_In_ IRenderEngine* const pEngine, const TextAttribute textAttribute, const size_t cchLine, const COORD coordTarget);
|
||||
void _PaintSelection(_In_ IRenderEngine* const pEngine);
|
||||
void _PaintCursor(_In_ IRenderEngine* const pEngine);
|
||||
|
||||
void _PaintOverlays(_In_ IRenderEngine* const pEngine);
|
||||
void _PaintOverlay(IRenderEngine& engine, const RenderOverlay& overlay);
|
||||
|
||||
[[nodiscard]] HRESULT _UpdateDrawingBrushes(_In_ IRenderEngine* const pEngine,
|
||||
const TextAttribute attr,
|
||||
const bool usingSoftFont,
|
||||
const bool isSettingDefaultBrushes);
|
||||
|
||||
[[nodiscard]] HRESULT _UpdateDrawingBrushes(_In_ IRenderEngine* const pEngine, const TextAttribute attr, const bool usingSoftFont, const bool isSettingDefaultBrushes);
|
||||
[[nodiscard]] HRESULT _PerformScrolling(_In_ IRenderEngine* const pEngine);
|
||||
|
||||
Microsoft::Console::Types::Viewport _viewport;
|
||||
|
||||
static constexpr float _shrinkThreshold = 0.8f;
|
||||
std::vector<Cluster> _clusterBuffer;
|
||||
|
||||
std::vector<SMALL_RECT> _GetSelectionRects() const;
|
||||
void _ScrollPreviousSelection(const til::point delta);
|
||||
std::vector<SMALL_RECT> _previousSelection;
|
||||
|
||||
[[nodiscard]] HRESULT _PaintTitle(IRenderEngine* const pEngine);
|
||||
|
||||
[[nodiscard]] std::optional<CursorOptions> _GetCursorInfo();
|
||||
[[nodiscard]] HRESULT _PrepareRenderInfo(_In_ IRenderEngine* const pEngine);
|
||||
|
||||
const size_t _firstSoftFontChar = 0xEF20;
|
||||
std::array<IRenderEngine*, 2> _engines{};
|
||||
IRenderData* _pData = nullptr; // Non-ownership pointer
|
||||
std::unique_ptr<IRenderThread> _pThread;
|
||||
static constexpr size_t _firstSoftFontChar = 0xEF20;
|
||||
size_t _lastSoftFontChar = 0;
|
||||
static bool s_IsSoftFontChar(const std::wstring_view& v, const size_t firstSoftFontChar, const size_t lastSoftFontChar);
|
||||
|
||||
// Helper functions to diagnose issues with painting and layout.
|
||||
// These are only actually effective/on in Debug builds when the flag is set using an attached debugger.
|
||||
bool _fDebug = false;
|
||||
|
||||
std::optional<interval_tree::IntervalTree<til::point, size_t>::interval> _hoveredInterval;
|
||||
Microsoft::Console::Types::Viewport _viewport;
|
||||
std::vector<Cluster> _clusterBuffer;
|
||||
std::vector<SMALL_RECT> _previousSelection;
|
||||
std::function<void()> _pfnRendererEnteredErrorState;
|
||||
bool _destructing = false;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class ConptyOutputTests;
|
||||
|
|
|
@ -24,7 +24,6 @@ PRECOMPILED_CXX = 1
|
|||
PRECOMPILED_INCLUDE = ..\precomp.h
|
||||
|
||||
SOURCES = \
|
||||
..\Cluster.cpp \
|
||||
..\BlinkingState.cpp \
|
||||
..\FontInfo.cpp \
|
||||
..\FontInfoBase.cpp \
|
||||
|
|
|
@ -16,24 +16,52 @@ Author(s):
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <unicode.hpp>
|
||||
|
||||
namespace Microsoft::Console::Render
|
||||
{
|
||||
class Cluster
|
||||
{
|
||||
public:
|
||||
Cluster(const std::wstring_view text, const size_t columns);
|
||||
constexpr Cluster() noexcept = default;
|
||||
constexpr Cluster(const std::wstring_view text, const size_t columns) noexcept :
|
||||
_text{ text },
|
||||
_columns{ columns }
|
||||
{
|
||||
}
|
||||
|
||||
const wchar_t GetTextAsSingle() const noexcept;
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
const std::wstring_view& GetText() const noexcept;
|
||||
// Provides the string of wchar_ts for this cluster.
|
||||
constexpr std::wstring_view GetText() const noexcept
|
||||
{
|
||||
return _text;
|
||||
}
|
||||
|
||||
const size_t GetColumns() const noexcept;
|
||||
// Gets the number of columns in the grid that this character should consume
|
||||
// visually when rendered onto a line.
|
||||
constexpr size_t GetColumns() const noexcept
|
||||
{
|
||||
return _columns;
|
||||
}
|
||||
|
||||
private:
|
||||
// This is the UTF-16 string of characters that form a particular drawing cluster
|
||||
const std::wstring_view _text;
|
||||
std::wstring_view _text;
|
||||
|
||||
// This is how many columns we're expecting this cluster to take in the display grid
|
||||
const size_t _columns;
|
||||
size_t _columns = 0;
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue