Pass through line rendition attributes over conpty (#13933)

This PR introduces a mechanism for passing through line rendition
attributes to the conpty client, so we can support double-width and
double-height text in Windows Terminal.

Line renditions were first implemented in conhost (with the GDI
renderer) in PR #8664, and were implemented in the DX renderer in PR
#13102.

By default this won't add any additional overhead to the conpty output,
but as soon as there is any usage of double-size text, we switch to a
mode in which every line output will be prefixed with a line rendition
sequence. This is to ensure that the line attributes in the client
terminal are always in sync with the host.

Since this does add some overhead to the conpty output, we'd prefer not
to remain in this mode longer than necessary. So whenever there is a
full repaint of the entire viewport, we check to see if all of the lines
are single-width. If that is the case, we can then safely skip the line
rendition sequences in future updates.

One other small optimization is when the conpty update is only writing
out a single character (this is something we already check for). When
that is the case, we can safely skip the line rendition prefix, because
a single character update should never include a change of the line
rendition.

## Validation Steps Performed

I've manually tested that Windows Terminal now passes the double-size
tests in _Vttest_, and also confirmed various edge cases are working
correctly in my own double-size tests.

Closes #11595
This commit is contained in:
James Holderness
2022-09-09 19:34:30 +01:00
committed by GitHub
parent 3958c938af
commit 3e6abd37df
12 changed files with 72 additions and 15 deletions

View File

@@ -325,7 +325,7 @@ CATCH_RETURN()
return S_OK;
}
[[nodiscard]] HRESULT AtlasEngine::PrepareLineTransform(const LineRendition lineRendition, const size_t targetRow, const size_t viewportLeft) noexcept
[[nodiscard]] HRESULT AtlasEngine::PrepareLineTransform(const LineRendition lineRendition, const til::CoordType targetRow, const til::CoordType viewportLeft) noexcept
{
return S_OK;
}

View File

@@ -41,7 +41,7 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT NotifyNewText(const std::wstring_view newText) noexcept override;
[[nodiscard]] HRESULT PrepareRenderInfo(const RenderFrameInfo& info) noexcept override;
[[nodiscard]] HRESULT ResetLineTransform() noexcept override;
[[nodiscard]] HRESULT PrepareLineTransform(LineRendition lineRendition, size_t targetRow, size_t viewportLeft) noexcept override;
[[nodiscard]] HRESULT PrepareLineTransform(LineRendition lineRendition, til::CoordType targetRow, til::CoordType viewportLeft) noexcept override;
[[nodiscard]] HRESULT PaintBackground() noexcept override;
[[nodiscard]] HRESULT PaintBufferLine(gsl::span<const Cluster> clusters, til::point coord, bool fTrimLeft, bool lineWrapped) noexcept override;
[[nodiscard]] HRESULT PaintBufferGridLines(GridLineSet lines, COLORREF color, size_t cchLine, til::point coordTarget) noexcept override;

View File

@@ -53,8 +53,8 @@ HRESULT RenderEngineBase::ResetLineTransform() noexcept
}
HRESULT RenderEngineBase::PrepareLineTransform(const LineRendition /*lineRendition*/,
const size_t /*targetRow*/,
const size_t /*viewportLeft*/) noexcept
const til::CoordType /*targetRow*/,
const til::CoordType /*viewportLeft*/) noexcept
{
return S_FALSE;
}

View File

@@ -2343,8 +2343,8 @@ CATCH_RETURN();
// Return Value:
// - S_OK if successful. S_FALSE if already set. E_FAIL if there was an error.
[[nodiscard]] HRESULT DxEngine::PrepareLineTransform(const LineRendition lineRendition,
const size_t targetRow,
const size_t viewportLeft) noexcept
const til::CoordType targetRow,
const til::CoordType viewportLeft) noexcept
{
auto lineTransform = D2D1::Matrix3x2F{ 0, 0, 0, 0, 0, 0 };
const auto fontSize = _fontRenderData->GlyphCell();

View File

@@ -97,8 +97,8 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT ResetLineTransform() noexcept override;
[[nodiscard]] HRESULT PrepareLineTransform(const LineRendition lineRendition,
const size_t targetRow,
const size_t viewportLeft) noexcept override;
const til::CoordType targetRow,
const til::CoordType viewportLeft) noexcept override;
[[nodiscard]] HRESULT PaintBackground() noexcept override;
[[nodiscard]] HRESULT PaintBufferLine(const gsl::span<const Cluster> clusters,

View File

@@ -43,8 +43,8 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT ResetLineTransform() noexcept override;
[[nodiscard]] HRESULT PrepareLineTransform(const LineRendition lineRendition,
const size_t targetRow,
const size_t viewportLeft) noexcept override;
const til::CoordType targetRow,
const til::CoordType viewportLeft) noexcept override;
[[nodiscard]] HRESULT PaintBackground() noexcept override;
[[nodiscard]] HRESULT PaintBufferLine(const gsl::span<const Cluster> clusters,

View File

@@ -210,8 +210,8 @@ GdiEngine::~GdiEngine()
// Return Value:
// - S_OK if successful. S_FALSE if already set. E_FAIL if there was an error.
[[nodiscard]] HRESULT GdiEngine::PrepareLineTransform(const LineRendition lineRendition,
const size_t targetRow,
const size_t viewportLeft) noexcept
const til::CoordType targetRow,
const til::CoordType viewportLeft) noexcept
{
XFORM lineTransform = {};
// The X delta is to account for the horizontal viewport offset.

View File

@@ -72,7 +72,7 @@ namespace Microsoft::Console::Render
[[nodiscard]] virtual HRESULT NotifyNewText(const std::wstring_view newText) noexcept = 0;
[[nodiscard]] virtual HRESULT PrepareRenderInfo(const RenderFrameInfo& info) noexcept = 0;
[[nodiscard]] virtual HRESULT ResetLineTransform() noexcept = 0;
[[nodiscard]] virtual HRESULT PrepareLineTransform(LineRendition lineRendition, size_t targetRow, size_t viewportLeft) noexcept = 0;
[[nodiscard]] virtual HRESULT PrepareLineTransform(LineRendition lineRendition, til::CoordType targetRow, til::CoordType viewportLeft) noexcept = 0;
[[nodiscard]] virtual HRESULT PaintBackground() noexcept = 0;
[[nodiscard]] virtual HRESULT PaintBufferLine(gsl::span<const Cluster> clusters, til::point coord, bool fTrimLeft, bool lineWrapped) noexcept = 0;
[[nodiscard]] virtual HRESULT PaintBufferGridLines(GridLineSet lines, COLORREF color, size_t cchLine, til::point coordTarget) noexcept = 0;

View File

@@ -38,8 +38,8 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT ResetLineTransform() noexcept override;
[[nodiscard]] HRESULT PrepareLineTransform(const LineRendition lineRendition,
const size_t targetRow,
const size_t viewportLeft) noexcept override;
const til::CoordType targetRow,
const til::CoordType viewportLeft) noexcept override;
[[nodiscard]] virtual bool RequiresContinuousRedraw() noexcept override;

View File

@@ -25,6 +25,10 @@ using namespace Microsoft::Console::Types;
return S_FALSE;
}
// If we're using line renditions, and this is a full screen paint, we can
// potentially stop using them at the end of this frame.
_stopUsingLineRenditions = _usingLineRenditions && _AllIsInvalid();
// If there's nothing to do, quick return
auto somethingToDo = _invalidMap.any() ||
_scrollDelta != til::point{ 0, 0 } ||
@@ -75,6 +79,14 @@ using namespace Microsoft::Console::Types;
}
_circled = false;
// If _stopUsingLineRenditions is still true at the end of the frame, that
// means we've refreshed the entire viewport with every line being single
// width, so we can safely stop using them from now on.
if (_stopUsingLineRenditions)
{
_usingLineRenditions = false;
}
// If we deferred a cursor movement during the frame, make sure we put the
// cursor in the right place before we end the frame.
if (_deferredCursorPos != INVALID_COORDS)
@@ -100,6 +112,45 @@ using namespace Microsoft::Console::Types;
return S_FALSE;
}
[[nodiscard]] HRESULT VtEngine::ResetLineTransform() noexcept
{
return S_FALSE;
}
[[nodiscard]] HRESULT VtEngine::PrepareLineTransform(const LineRendition lineRendition,
const til::CoordType targetRow,
const til::CoordType /*viewportLeft*/) noexcept
{
// We don't want to waste bandwidth writing out line rendition attributes
// until we know they're in use. But once they are in use, we have to keep
// applying them on every line until we know they definitely aren't being
// used anymore (we check that at the end of any fullscreen paint).
if (lineRendition != LineRendition::SingleWidth)
{
_stopUsingLineRenditions = false;
_usingLineRenditions = true;
}
// One simple optimization is that we can skip sending the line attributes
// when _quickReturn is true. That indicates that we're writing out a single
// character, which should preclude there being a rendition switch.
if (_usingLineRenditions && !_quickReturn)
{
RETURN_IF_FAILED(_MoveCursor({ _lastText.x, targetRow }));
switch (lineRendition)
{
case LineRendition::SingleWidth:
return _Write("\x1b#5");
case LineRendition::DoubleWidth:
return _Write("\x1b#6");
case LineRendition::DoubleHeightTop:
return _Write("\x1b#3");
case LineRendition::DoubleHeightBottom:
return _Write("\x1b#4");
}
}
return S_OK;
}
// Routine Description:
// - Paints the background of the invalid area of the frame.
// Arguments:

View File

@@ -29,6 +29,8 @@ VtEngine::VtEngine(_In_ wil::unique_hfile pipe,
const Viewport initialViewport) :
RenderEngineBase(),
_hFile(std::move(pipe)),
_usingLineRenditions(false),
_stopUsingLineRenditions(false),
_lastTextAttributes(INVALID_COLOR, INVALID_COLOR),
_lastViewport(initialViewport),
_pool(til::pmr::get_default_resource()),

View File

@@ -58,6 +58,8 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT InvalidateSelection(const std::vector<til::rect>& rectangles) noexcept override;
[[nodiscard]] HRESULT InvalidateAll() noexcept override;
[[nodiscard]] HRESULT InvalidateFlush(_In_ const bool circled, _Out_ bool* const pForcePaint) noexcept override;
[[nodiscard]] HRESULT ResetLineTransform() noexcept override;
[[nodiscard]] HRESULT PrepareLineTransform(const LineRendition lineRendition, const til::CoordType targetRow, const til::CoordType viewportLeft) noexcept override;
[[nodiscard]] HRESULT PaintBackground() noexcept override;
[[nodiscard]] HRESULT PaintBufferLine(gsl::span<const Cluster> clusters, til::point coord, bool fTrimLeft, bool lineWrapped) noexcept override;
[[nodiscard]] HRESULT PaintBufferGridLines(GridLineSet lines, COLORREF color, size_t cchLine, til::point coordTarget) noexcept override;
@@ -96,6 +98,8 @@ namespace Microsoft::Console::Render
std::string _formatBuffer;
std::string _conversionBuffer;
bool _usingLineRenditions;
bool _stopUsingLineRenditions;
TextAttribute _lastTextAttributes;
std::function<void(bool)> _pfnSetLookingForDSR;