From f919a46caf4d492c5d8e5cb3aa58b68a52d77eb0 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett (MSFT)" Date: Thu, 12 Mar 2020 17:54:43 -0700 Subject: [PATCH] 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 --- src/buffer/out/TextAttribute.cpp | 5 --- src/buffer/out/TextAttribute.hpp | 31 ++++++++++++++++++- src/cascadia/TerminalCore/Terminal.hpp | 1 + .../TerminalCore/terminalrenderdata.cpp | 10 ++++++ src/host/renderData.cpp | 12 +++++++ src/host/renderData.hpp | 2 ++ src/renderer/base/renderer.cpp | 18 +++++++++-- src/renderer/inc/IRenderData.hpp | 2 ++ 8 files changed, 73 insertions(+), 8 deletions(-) diff --git a/src/buffer/out/TextAttribute.cpp b/src/buffer/out/TextAttribute.cpp index c6ac011543e..f8f3f75e499 100644 --- a/src/buffer/out/TextAttribute.cpp +++ b/src/buffer/out/TextAttribute.cpp @@ -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); diff --git a/src/buffer/out/TextAttribute.hpp b/src/buffer/out/TextAttribute.hpp index 36fcc6c1d80..76fdd49e17d 100644 --- a/src/buffer/out/TextAttribute.hpp +++ b/src/buffer/out/TextAttribute.hpp @@ -163,12 +163,41 @@ class TextAttribute final 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 colorTable, COLORREF defaultColor) const noexcept; COLORREF _GetRgbBackground(std::basic_string_view 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; diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index 57ddc622047..8f0131a35cb 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -145,6 +145,7 @@ class Microsoft::Terminal::Core::Terminal final : CursorType GetCursorStyle() const noexcept override; COLORREF GetCursorColor() const noexcept override; bool IsCursorDoubleWidth() const noexcept override; + bool IsScreenReversed() const noexcept override; const std::vector GetOverlays() const noexcept override; const bool IsGridLineDrawingAllowed() noexcept override; #pragma endregion diff --git a/src/cascadia/TerminalCore/terminalrenderdata.cpp b/src/cascadia/TerminalCore/terminalrenderdata.cpp index f894a54c968..e49859cb374 100644 --- a/src/cascadia/TerminalCore/terminalrenderdata.cpp +++ b/src/cascadia/TerminalCore/terminalrenderdata.cpp @@ -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; +} diff --git a/src/host/renderData.cpp b/src/host/renderData.cpp index 5cd99ce185b..1b3f8610762 100644 --- a/src/host/renderData.cpp +++ b/src/host/renderData.cpp @@ -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: +// - +// 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 diff --git a/src/host/renderData.hpp b/src/host/renderData.hpp index 6005412ddf3..49902f48338 100644 --- a/src/host/renderData.hpp +++ b/src/host/renderData.hpp @@ -49,6 +49,8 @@ class RenderData final : COLORREF GetCursorColor() const noexcept override; bool IsCursorDoubleWidth() const noexcept override; + bool IsScreenReversed() const noexcept override; + const std::vector GetOverlays() const noexcept override; const bool IsGridLineDrawingAllowed() noexcept override; diff --git a/src/renderer/base/renderer.cpp b/src/renderer/base/renderer.cpp index 29a177a62dc..8191e0024ee 100644 --- a/src/renderer/base/renderer.cpp +++ b/src/renderer/base/renderer.cpp @@ -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. diff --git a/src/renderer/inc/IRenderData.hpp b/src/renderer/inc/IRenderData.hpp index 8fb42750cd0..860537514ff 100644 --- a/src/renderer/inc/IRenderData.hpp +++ b/src/renderer/inc/IRenderData.hpp @@ -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 GetOverlays() const noexcept = 0; virtual const bool IsGridLineDrawingAllowed() noexcept = 0;