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:
Dustin L. Howett (MSFT) 2020-03-12 17:54:43 -07:00 committed by GitHub
parent ae71dce2ca
commit f919a46caf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 73 additions and 8 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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