Optimize rendering runs of spaces when there is no visual change (#4877)
cmatrix is somewhat of a pathological case for our infrastructure: it prints out a bunch of green and white characters and then updates them a million times a second. It also maintains a column of space between every green character. When it prints this column, it prints it in "default" or "white". This ends up making runs of text that look like this: (def: G=green B=bright white W=white *=matrix char =space) G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W G W As characters trickle in: G*W G*W G*W G*W G*W G*W G*W B*W G*W G*W G*W G*W G*W G*W G*W G W G*W G*W G*W B*W G*W G*W G*W G W G*W B*W G*W G W G*W G*W G*W G*W G*W G W G*W G W G*W B*W G*W G*W B*W G W G*W G W G*W G W B*W G*W G W G W G*W G W G*W G W G W B*W G W G W B*W G W G*W G W G W G W Every one of those color transitions causes us to break up the run of text and start rendering it again. This impacts GDI, Direct2D *and* ConPTY. In the example above, there are 120 runs. The problem is, printing a space doesn't **use** the foreground color! This commit introduces an optimization. When we're about to break a text cluster because its attributes changed, we make sure that it's not just filled with spaces and doesn't differ in any visually-meaningful way (like underline or strikethrough, considering global invert state). This lets us optimize both the rendering _and_ the PTY output to look like this: G* * * * * * * B*G G* * * * * * * G* * * B*G * * * G* B*G * * * * * G* * * B*G * * B*G * * B*G * G * * B*G G B*G * Text will be printed at best line-by-line and at worst only when the visible properties of the screen actually change. In the example above, there are only 21 runs. This speeds up cmatrix remarkably. Refs #1064
This commit is contained in:
parent
ae71dce2ca
commit
f919a46caf
|
@ -155,11 +155,6 @@ void TextAttribute::SetColor(const COLORREF rgbColor, const bool fIsForeground)
|
|||
}
|
||||
}
|
||||
|
||||
bool TextAttribute::_IsReverseVideo() const noexcept
|
||||
{
|
||||
return WI_IsFlagSet(_wAttrLegacy, COMMON_LVB_REVERSE_VIDEO);
|
||||
}
|
||||
|
||||
bool TextAttribute::IsLeadingByte() const noexcept
|
||||
{
|
||||
return WI_IsFlagSet(_wAttrLegacy, COMMON_LVB_LEADING_BYTE);
|
||||
|
|
|
@ -163,12 +163,41 @@ public:
|
|||
return _foreground.IsRgb() || _background.IsRgb();
|
||||
}
|
||||
|
||||
// This returns whether this attribute, if printed directly next to another attribute, for the space
|
||||
// character, would look identical to the other one.
|
||||
constexpr bool HasIdenticalVisualRepresentationForBlankSpace(const TextAttribute& other, const bool inverted = false) const noexcept
|
||||
{
|
||||
// sneaky-sneaky: I'm using xor here
|
||||
// inverted is whether there's a global invert; Reverse is a local one.
|
||||
// global ^ local == true : the background attribute is actually the visible foreground, so we care about the foregrounds being identical
|
||||
// global ^ local == false: the foreground attribute is the visible foreground, so we care about the backgrounds being identical
|
||||
const auto checkForeground = (inverted != _IsReverseVideo());
|
||||
return !IsAnyGridLineEnabled() && // grid lines have a visual representation
|
||||
// crossed out, doubly and singly underlined have a visual representation
|
||||
WI_AreAllFlagsClear(_extendedAttrs, ExtendedAttributes::CrossedOut | ExtendedAttributes::DoublyUnderlined | ExtendedAttributes::Underlined) &&
|
||||
// all other attributes do not have a visual representation
|
||||
(_wAttrLegacy & META_ATTRS) == (other._wAttrLegacy & META_ATTRS) &&
|
||||
((checkForeground && _foreground == other._foreground) ||
|
||||
(!checkForeground && _background == other._background)) &&
|
||||
_extendedAttrs == other._extendedAttrs;
|
||||
}
|
||||
|
||||
constexpr bool IsAnyGridLineEnabled() const noexcept
|
||||
{
|
||||
return WI_IsAnyFlagSet(_wAttrLegacy, COMMON_LVB_GRID_HORIZONTAL | COMMON_LVB_GRID_LVERTICAL | COMMON_LVB_GRID_RVERTICAL | COMMON_LVB_UNDERSCORE);
|
||||
}
|
||||
|
||||
private:
|
||||
COLORREF _GetRgbForeground(std::basic_string_view<COLORREF> colorTable,
|
||||
COLORREF defaultColor) const noexcept;
|
||||
COLORREF _GetRgbBackground(std::basic_string_view<COLORREF> colorTable,
|
||||
COLORREF defaultColor) const noexcept;
|
||||
bool _IsReverseVideo() const noexcept;
|
||||
|
||||
constexpr bool _IsReverseVideo() const noexcept
|
||||
{
|
||||
return WI_IsFlagSet(_wAttrLegacy, COMMON_LVB_REVERSE_VIDEO);
|
||||
}
|
||||
|
||||
void _SetBoldness(const bool isBold) noexcept;
|
||||
|
||||
WORD _wAttrLegacy;
|
||||
|
|
|
@ -145,6 +145,7 @@ public:
|
|||
CursorType GetCursorStyle() const noexcept override;
|
||||
COLORREF GetCursorColor() const noexcept override;
|
||||
bool IsCursorDoubleWidth() const noexcept override;
|
||||
bool IsScreenReversed() const noexcept override;
|
||||
const std::vector<Microsoft::Console::Render::RenderOverlay> GetOverlays() const noexcept override;
|
||||
const bool IsGridLineDrawingAllowed() noexcept override;
|
||||
#pragma endregion
|
||||
|
|
|
@ -204,3 +204,13 @@ void Terminal::UnlockConsole() noexcept
|
|||
{
|
||||
_readWriteLock.unlock_shared();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Returns whether the screen is inverted;
|
||||
// This state is not currently known to Terminal.
|
||||
// Return Value:
|
||||
// - false.
|
||||
bool Terminal::IsScreenReversed() const noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -445,4 +445,16 @@ void RenderData::ColorSelection(const COORD coordSelectionStart, const COORD coo
|
|||
{
|
||||
Selection::Instance().ColorSelection(coordSelectionStart, coordSelectionEnd, attr);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Returns true if the screen is globally inverted
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - true if the screen is globally inverted
|
||||
bool RenderData::IsScreenReversed() const noexcept
|
||||
{
|
||||
const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
return gci.IsScreenReversed();
|
||||
}
|
||||
#pragma endregion
|
||||
|
|
|
@ -49,6 +49,8 @@ public:
|
|||
COLORREF GetCursorColor() const noexcept override;
|
||||
bool IsCursorDoubleWidth() const noexcept override;
|
||||
|
||||
bool IsScreenReversed() const noexcept override;
|
||||
|
||||
const std::vector<Microsoft::Console::Render::RenderOverlay> GetOverlays() const noexcept override;
|
||||
|
||||
const bool IsGridLineDrawingAllowed() noexcept override;
|
||||
|
|
|
@ -615,11 +615,19 @@ 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;
|
||||
}
|
||||
|
||||
void Renderer::_PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine,
|
||||
TextBufferCellIterator it,
|
||||
const COORD target,
|
||||
const bool lineWrapped)
|
||||
{
|
||||
auto globalInvert{ _pData->IsScreenReversed() };
|
||||
|
||||
// If we have valid data, let's figure out how to draw it.
|
||||
if (it)
|
||||
{
|
||||
|
@ -666,8 +674,14 @@ void Renderer::_PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine,
|
|||
{
|
||||
if (color != it->TextAttr())
|
||||
{
|
||||
color = it->TextAttr();
|
||||
break;
|
||||
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))
|
||||
{
|
||||
color = newAttr;
|
||||
break; // vend this run
|
||||
}
|
||||
}
|
||||
|
||||
// Walk through the text data and turn it into rendering clusters.
|
||||
|
|
|
@ -60,6 +60,8 @@ namespace Microsoft::Console::Render
|
|||
virtual COLORREF GetCursorColor() const noexcept = 0;
|
||||
virtual bool IsCursorDoubleWidth() const noexcept = 0;
|
||||
|
||||
virtual bool IsScreenReversed() const noexcept = 0;
|
||||
|
||||
virtual const std::vector<RenderOverlay> GetOverlays() const noexcept = 0;
|
||||
|
||||
virtual const bool IsGridLineDrawingAllowed() noexcept = 0;
|
||||
|
|
Loading…
Reference in a new issue