Add support for line renditions in the DX renderer (#13102)

This PR adds support for the VT line rendition attributes in the DirectX
renderer, which allows for double-width and double-height line
renditions.

Line renditions were first implemented in conhost (with the GDI
renderer) in PR #8664.  Supporting them in the DX renderer now is a
small step towards #11595.

The DX implementation is very similar to the GDI one. When a particular
line rendition is requested, we create a transform that is applied to
the render target. And in the case of double-height renditions, we also
initialize some clipping offsets to allow for the fact that we only
render half of a line at a time.

One additional complication exists when drawing the cursor, which
requires a two part process where it first renders to a command list,
and then draw the command list in a second step. We need to temporarily
reset the transform in that first stage otherwise it ends up being
applied twice.

I've manually tested the renderer in conhost by setting the `UseDx`
registry entry and confirmed that it passes the _Vttest_ double-size
tests as well as several of my own tests. I've also checked that the
renderer can now handle horizontal scrolling, which is a feature we get
for free with the transforms.
This commit is contained in:
James Holderness 2022-05-18 19:17:06 +01:00 committed by GitHub
parent e1086de512
commit 0154da5d33
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 106 additions and 3 deletions

View File

@ -344,6 +344,7 @@ try
Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> brush;
Microsoft::WRL::ComPtr<ID2D1Image> originalTarget;
Microsoft::WRL::ComPtr<ID2D1CommandList> commandList;
D2D1::Matrix3x2F originalTransform;
if (!fInvert)
{
// Make sure to make the cursor opaque
@ -407,6 +408,9 @@ try
RETURN_IF_FAILED(d2dContext->CreateCommandList(&commandList));
d2dContext->GetTarget(&originalTarget);
d2dContext->SetTarget(commandList.Get());
// We use an identity transform here to avoid the active transform being applied twice.
d2dContext->GetTransform(&originalTransform);
d2dContext->SetTransform(D2D1::Matrix3x2F::Identity());
RETURN_IF_FAILED(d2dContext->CreateSolidColorBrush(COLOR_WHITE, &brush));
}
}
@ -449,6 +453,7 @@ try
// so now we draw that command list using MASK_INVERT over the existing image
RETURN_IF_FAILED(commandList->Close());
d2dContext->SetTarget(originalTarget.Get());
d2dContext->SetTransform(originalTransform);
d2dContext->DrawImage(commandList.Get(), D2D1_INTERPOLATION_MODE_LINEAR, D2D1_COMPOSITE_MODE_MASK_INVERT);
}
@ -501,8 +506,8 @@ CATCH_RETURN()
// Determine clip rectangle
D2D1_RECT_F clipRect;
clipRect.top = origin.y;
clipRect.bottom = clipRect.top + drawingContext->cellSize.height;
clipRect.top = origin.y + drawingContext->topClipOffset;
clipRect.bottom = origin.y + drawingContext->cellSize.height - drawingContext->bottomClipOffset;
clipRect.left = 0;
clipRect.right = drawingContext->targetSize.width;

View File

@ -32,7 +32,9 @@ namespace Microsoft::Console::Render
cellSize(cellSize),
targetSize(targetSize),
cursorInfo(cursorInfo),
options(options)
options(options),
topClipOffset(0),
bottomClipOffset(0)
{
}
@ -48,6 +50,8 @@ namespace Microsoft::Console::Render
D2D_SIZE_F targetSize;
std::optional<CursorOptions> cursorInfo;
D2D1_DRAW_TEXT_OPTIONS options;
FLOAT topClipOffset;
FLOAT bottomClipOffset;
};
// Helper to choose which Direct2D method to use when drawing the cursor rectangle

View File

@ -49,6 +49,14 @@ D3D11_INPUT_ELEMENT_DESC _shaderInputLayout[] = {
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 }
};
namespace
{
bool operator==(const D2D1::Matrix3x2F& lhs, const D2D1::Matrix3x2F& rhs) noexcept
{
return ::memcmp(&lhs.m[0][0], &rhs.m[0][0], sizeof(lhs.m)) == 0;
};
}
#pragma hdrstop
using namespace Microsoft::Console::Render;
@ -77,6 +85,8 @@ DxEngine::DxEngine() :
_foregroundColor{ 0 },
_backgroundColor{ 0 },
_selectionBackground{},
_currentLineRendition{ LineRendition::SingleWidth },
_currentLineTransform{ D2D1::Matrix3x2F::Identity() },
_haveDeviceResources{ false },
_swapChainHandle{ INVALID_HANDLE_VALUE },
_swapChainDesc{ 0 },
@ -2283,3 +2293,79 @@ void DxEngine::UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept
_drawingContext->cursorInfo = info.cursorInfo;
return S_OK;
}
// Routine Description
// - Resets the world transform to the identity matrix.
// Arguments:
// - <none>
// Return Value:
// - S_OK if successful. S_FALSE if already reset. E_FAIL if there was an error.
[[nodiscard]] HRESULT DxEngine::ResetLineTransform() noexcept
{
// Return early if the current transform is already the identity matrix.
RETURN_HR_IF(S_FALSE, _currentLineTransform.IsIdentity());
// Reset the active transform to the identity matrix.
_drawingContext->renderTarget->SetTransform(D2D1::Matrix3x2F::Identity());
// Reset the clipping offsets.
_drawingContext->topClipOffset = 0;
_drawingContext->bottomClipOffset = 0;
// Reset the current state.
_currentLineTransform = D2D1::Matrix3x2F::Identity();
_currentLineRendition = LineRendition::SingleWidth;
return S_OK;
}
// Routine Description
// - Applies an appropriate transform for the given line rendition and viewport offset.
// Arguments:
// - lineRendition - The line rendition specifying the scaling of the line.
// - targetRow - The row on which the line is expected to be rendered.
// - viewportLeft - The left offset of the current viewport.
// 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
{
auto lineTransform = D2D1::Matrix3x2F{ 0, 0, 0, 0, 0, 0 };
const auto fontSize = _fontRenderData->GlyphCell();
// The X delta is to account for the horizontal viewport offset.
lineTransform.dx = viewportLeft ? -1.0f * viewportLeft * fontSize.width : 0.0f;
switch (lineRendition)
{
case LineRendition::SingleWidth:
lineTransform.m11 = 1; // single width
lineTransform.m22 = 1; // single height
break;
case LineRendition::DoubleWidth:
lineTransform.m11 = 2; // double width
lineTransform.m22 = 1; // single height
break;
case LineRendition::DoubleHeightTop:
lineTransform.m11 = 2; // double width
lineTransform.m22 = 2; // double height
// The Y delta is to negate the offset caused by the scaled height.
lineTransform.dy = -1.0f * targetRow * fontSize.height;
break;
case LineRendition::DoubleHeightBottom:
lineTransform.m11 = 2; // double width
lineTransform.m22 = 2; // double height
// The Y delta is to negate the offset caused by the scaled height.
// An extra row is added because we need the bottom half of the line.
lineTransform.dy = -1.0f * (targetRow + 1) * fontSize.height;
break;
}
// Return early if the new matrix is the same as the current transform.
RETURN_HR_IF(S_FALSE, _currentLineRendition == lineRendition && _currentLineTransform == lineTransform);
// Set the active transform with the new matrix.
_drawingContext->renderTarget->SetTransform(lineTransform);
// If the line rendition is double height, we need to adjust the top or bottom
// of the clipping rect to clip half the height of the rendered characters.
const auto halfHeight = _drawingContext->cellSize.height / 2;
_drawingContext->topClipOffset = lineRendition == LineRendition::DoubleHeightBottom ? halfHeight : 0;
_drawingContext->bottomClipOffset = lineRendition == LineRendition::DoubleHeightTop ? halfHeight : 0;
// Save the current state.
_currentLineTransform = lineTransform;
_currentLineRendition = lineRendition;
return S_OK;
}

View File

@ -93,6 +93,11 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT PrepareRenderInfo(const RenderFrameInfo& info) noexcept override;
[[nodiscard]] HRESULT ResetLineTransform() noexcept override;
[[nodiscard]] HRESULT PrepareLineTransform(const LineRendition lineRendition,
const size_t targetRow,
const size_t viewportLeft) noexcept override;
[[nodiscard]] HRESULT PaintBackground() noexcept override;
[[nodiscard]] HRESULT PaintBufferLine(const gsl::span<const Cluster> clusters,
const COORD coord,
@ -167,6 +172,9 @@ namespace Microsoft::Console::Render
D2D1_COLOR_F _backgroundColor;
D2D1_COLOR_F _selectionBackground;
LineRendition _currentLineRendition;
D2D1::Matrix3x2F _currentLineTransform;
uint16_t _hyperlinkHoveredId;
bool _firstFrame;