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:
Leonard Hecker 2021-08-24 17:27:59 +02:00 committed by GitHub
parent 2c3368f766
commit 15c02b77a0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 155 additions and 245 deletions

View file

@ -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;

View file

@ -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() };

View file

@ -456,6 +456,7 @@ void VtIoTests::RendererDtorAndThreadAndDx()
// which is what CI uses.
/*Sleep(500);*/
(void)dxEngine->Enable();
pThread->EnablePainting();
pRenderer->TriggerTeardown();
pRenderer.reset();

View file

@ -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;
}

View file

@ -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" />

View file

@ -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">

View file

@ -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();
}

View file

@ -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;

View file

@ -24,7 +24,6 @@ PRECOMPILED_CXX = 1
PRECOMPILED_INCLUDE = ..\precomp.h
SOURCES = \
..\Cluster.cpp \
..\BlinkingState.cpp \
..\FontInfo.cpp \
..\FontInfoBase.cpp \

View file

@ -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;
};
}