Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recycle assorted rendering components to accelerate drawing #6483

Merged
15 commits merged into from
Jun 22, 2020
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 38 additions & 15 deletions src/renderer/dx/CustomTextLayout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,13 @@ using namespace Microsoft::Console::Render;
// - analyzer - DirectWrite text analyzer from the factory that has been cached at a level above this layout (expensive to create)
// - format - The DirectWrite format object representing the size and other text properties to be applied (by default) to a layout
// - font - The DirectWrite font face to use while calculating layout (by default, will fallback if necessary)
// - clusters - From the backing buffer, the text to be displayed clustered by the columns it should consume.

// - width - The count of pixels available per column (the expected pixel width of every column)
// - boxEffect - Box drawing scaling effects that are cached for the base font across layouts.
CustomTextLayout::CustomTextLayout(gsl::not_null<IDWriteFactory1*> const factory,
gsl::not_null<IDWriteTextAnalyzer1*> const analyzer,
gsl::not_null<IDWriteTextFormat*> const format,
gsl::not_null<IDWriteFontFace1*> const font,
std::basic_string_view<Cluster> const clusters,
size_t const width,
IBoxDrawingEffect* const boxEffect) :
_factory{ factory.get() },
Expand All @@ -47,8 +46,37 @@ CustomTextLayout::CustomTextLayout(gsl::not_null<IDWriteFactory1*> const factory
// Fetch the locale name out once now from the format
_localeName.resize(gsl::narrow_cast<size_t>(format->GetLocaleNameLength()) + 1); // +1 for null
THROW_IF_FAILED(format->GetLocaleName(_localeName.data(), gsl::narrow<UINT32>(_localeName.size())));
}

_textClusterColumns.reserve(clusters.size());
//Routine Description:
// - Resets this custom text layout to the freshly allocated state in terms of text analysis.
// Arguments:
// - <none>, modifies internal state
// Return Value:
// - S_OK or suitable memory management issue
[[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::Reset()
try
{
_runs.clear();
_breakpoints.clear();
_runIndex = 0;
_isEntireTextSimple = false;
_textClusterColumns.clear();
_text.clear();
return S_OK;
}
CATCH_RETURN()

// Routine Description:
// - Appends text to this layout for analysis/processing.
// Arguments:
// - clusters - From the backing buffer, the text to be displayed clustered by the columns it should consume.
// Return Value:
// - S_OK or suitable memory management issue.
[[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::AppendClusters(const std::basic_string_view<::Microsoft::Console::Render::Cluster> clusters)
try
{
_textClusterColumns.reserve(_textClusterColumns.size() + clusters.size());

for (const auto& cluster : clusters)
{
Expand All @@ -64,7 +92,10 @@ CustomTextLayout::CustomTextLayout(gsl::not_null<IDWriteFactory1*> const factory

_text += text;
}

return S_OK;
}
CATCH_RETURN()

// Routine Description:
// - Figures out how many columns this layout should take. This will use the analyze step only.
Expand Down Expand Up @@ -1827,21 +1858,13 @@ void CustomTextLayout::_SplitCurrentRun(const UINT32 splitPosition)
// - <none>
void CustomTextLayout::_OrderRuns()
{
const size_t totalRuns = _runs.size();
std::vector<LinkedRun> runs;
runs.resize(totalRuns);

UINT32 nextRunIndex = 0;
for (UINT32 i = 0; i < totalRuns; ++i)
std::sort(_runs.begin(), _runs.end(), [](auto& a, auto& b) { return a.textStart < b.textStart; });
for (UINT32 i = 0; i < _runs.size() - 1; ++i)
{
runs.at(i) = _runs.at(nextRunIndex);
runs.at(i).nextRunIndex = i + 1;
nextRunIndex = _runs.at(nextRunIndex).nextRunIndex;
_runs[i].nextRunIndex = i + 1;
}

runs.back().nextRunIndex = 0;

_runs.swap(runs);
_runs.back().nextRunIndex = 0;
}

#pragma endregion
5 changes: 4 additions & 1 deletion src/renderer/dx/CustomTextLayout.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,13 @@ namespace Microsoft::Console::Render
gsl::not_null<IDWriteTextAnalyzer1*> const analyzer,
gsl::not_null<IDWriteTextFormat*> const format,
gsl::not_null<IDWriteFontFace1*> const font,
const std::basic_string_view<::Microsoft::Console::Render::Cluster> clusters,
size_t const width,
IBoxDrawingEffect* const boxEffect);

[[nodiscard]] HRESULT STDMETHODCALLTYPE AppendClusters(const std::basic_string_view<::Microsoft::Console::Render::Cluster> clusters);

[[nodiscard]] HRESULT STDMETHODCALLTYPE Reset();
miniksa marked this conversation as resolved.
Show resolved Hide resolved

[[nodiscard]] HRESULT STDMETHODCALLTYPE GetColumns(_Out_ UINT32* columns);

// IDWriteTextLayout methods (but we don't actually want to implement them all, so just this one matching the existing interface)
Expand Down
54 changes: 42 additions & 12 deletions src/renderer/dx/CustomTextRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,33 @@ CATCH_RETURN()
::Microsoft::WRL::ComPtr<ID2D1DeviceContext> d2dContext;
RETURN_IF_FAILED(drawingContext->renderTarget->QueryInterface(d2dContext.GetAddressOf()));

// Determine clip rectangle
D2D1_RECT_F clipRect;
clipRect.top = origin.y;
clipRect.bottom = clipRect.top + drawingContext->cellSize.height;
clipRect.left = 0;
clipRect.right = drawingContext->targetSize.width;

if (_clipRect.top != clipRect.top || _clipRect.bottom != clipRect.bottom ||
_clipRect.left != clipRect.left || _clipRect.right != clipRect.right)
{
if (_hasClipPushed)
{
d2dContext->PopAxisAlignedClip();
_hasClipPushed = false;
}

// Clip all drawing in this glyph run to where we expect.
// We need the AntialiasMode here to be Aliased to ensure
// that background boxes line up with each other and don't leave behind
// stray colors.
// See GH#3626 for more details.
d2dContext->PushAxisAlignedClip(clipRect, D2D1_ANTIALIAS_MODE_ALIASED);

_clipRect = clipRect;
_hasClipPushed = true;
}

// Draw the background
// The rectangle needs to be deduced based on the origin and the BidiDirection
const auto advancesSpan = gsl::make_span(glyphRun->glyphAdvances, glyphRun->glyphCount);
Expand All @@ -425,18 +452,6 @@ CATCH_RETURN()
}
rect.right = rect.left + totalSpan;

// Clip all drawing in this glyph run to where we expect.
// We need the AntialiasMode here to be Aliased to ensure
// that background boxes line up with each other and don't leave behind
// stray colors.
// See GH#3626 for more details.
d2dContext->PushAxisAlignedClip(rect, D2D1_ANTIALIAS_MODE_ALIASED);

// Ensure we pop it on the way out
auto popclip = wil::scope_exit([&d2dContext]() noexcept {
d2dContext->PopAxisAlignedClip();
});

d2dContext->FillRectangle(rect, drawingContext->backgroundBrush);

RETURN_IF_FAILED(_drawCursor(d2dContext.Get(), rect, *drawingContext, true));
Expand Down Expand Up @@ -635,6 +650,21 @@ CATCH_RETURN()
}
#pragma endregion

[[nodiscard]] HRESULT CustomTextRenderer::EndFrame(void* clientDrawingContext) noexcept
try
{
DrawingContext* drawingContext = static_cast<DrawingContext*>(clientDrawingContext);

if (_hasClipPushed)
{
drawingContext->renderTarget->PopAxisAlignedClip();
miniksa marked this conversation as resolved.
Show resolved Hide resolved
_hasClipPushed = false;
}

return S_OK;
}
CATCH_RETURN()

[[nodiscard]] HRESULT CustomTextRenderer::_DrawBasicGlyphRun(DrawingContext* clientDrawingContext,
D2D1_POINT_2F baselineOrigin,
DWRITE_MEASURING_MODE measuringMode,
Expand Down
8 changes: 8 additions & 0 deletions src/renderer/dx/CustomTextRenderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ namespace Microsoft::Console::Render
IDWriteFactory* dwriteFactory,
const DWRITE_LINE_SPACING spacing,
const D2D_SIZE_F cellSize,
const D2D_SIZE_F targetSize,
const std::optional<CursorOptions>& cursorInfo,
const D2D1_DRAW_TEXT_OPTIONS options = D2D1_DRAW_TEXT_OPTIONS_NONE) noexcept
{
Expand All @@ -28,6 +29,7 @@ namespace Microsoft::Console::Render
this->dwriteFactory = dwriteFactory;
this->spacing = spacing;
this->cellSize = cellSize;
this->targetSize = targetSize;
miniksa marked this conversation as resolved.
Show resolved Hide resolved
this->cursorInfo = cursorInfo;
this->options = options;
}
Expand All @@ -39,6 +41,7 @@ namespace Microsoft::Console::Render
IDWriteFactory* dwriteFactory;
DWRITE_LINE_SPACING spacing;
D2D_SIZE_F cellSize;
D2D_SIZE_F targetSize;
std::optional<CursorOptions> cursorInfo;
D2D1_DRAW_TEXT_OPTIONS options;
};
Expand Down Expand Up @@ -98,6 +101,8 @@ namespace Microsoft::Console::Render
BOOL isRightToLeft,
IUnknown* clientDrawingEffect) noexcept override;

[[nodiscard]] HRESULT STDMETHODCALLTYPE EndFrame(void* clientDrawingContext) noexcept;

private:
[[nodiscard]] HRESULT _FillRectangle(void* clientDrawingContext,
IUnknown* clientDrawingEffect,
Expand Down Expand Up @@ -128,5 +133,8 @@ namespace Microsoft::Console::Render
DWRITE_MEASURING_MODE measuringMode,
_In_ const DWRITE_GLYPH_RUN* glyphRun,
_In_opt_ const DWRITE_GLYPH_RUN_DESCRIPTION* glyphRunDescription) noexcept;

D2D1_RECT_F _clipRect;
bool _hasClipPushed = false;
};
}
119 changes: 61 additions & 58 deletions src/renderer/dx/DxRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@ DxEngine::DxEngine() :
_scale{ 1.0f },
_prevScale{ 1.0f },
_chainMode{ SwapChainMode::ForComposition },
_customRenderer{ ::Microsoft::WRL::Make<CustomTextRenderer>() }
_customLayout{},
_customRenderer{ ::Microsoft::WRL::Make<CustomTextRenderer>() },
_drawingContext{}
{
const auto was = _tracelogCount.fetch_add(1);
if (0 == was)
Expand Down Expand Up @@ -692,6 +694,7 @@ CATCH_RETURN()
try
{
_sizeTarget = Pixels;

_invalidMap.resize(_sizeTarget / _glyphCell, true);
return S_OK;
}
Expand Down Expand Up @@ -997,6 +1000,49 @@ try

_d2dRenderTarget->BeginDraw();
_isPainting = true;

{
// Get the baseline for this font as that's where we draw from
DWRITE_LINE_SPACING spacing;
RETURN_IF_FAILED(_dwriteTextFormat->GetLineSpacing(&spacing.method, &spacing.height, &spacing.baseline));

// GH#5098: If we're rendering with cleartype text, we need to always
// render onto an opaque background. If our background's opacity is
// 1.0f, that's great, we can use that. Otherwise, we need to force the
// text renderer to render this text in grayscale. In
// UpdateDrawingBrushes, we'll set the backgroundColor's a channel to
// 1.0 if we're in cleartype mode and the background's opacity is 1.0.
// Otherwise, at this point, the _backgroundColor's alpha is <1.0.
//
// Currently, only text with the default background color uses an alpha
// of 0, every other background uses 1.0
//
// DANGER: Layers slow us down. Only do this in the specific case where
// someone has chosen the slower ClearType antialiasing (versus the faster
// grayscale antialiasing)
const bool usingCleartype = _antialiasingMode == D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE;
const bool usingTransparency = _defaultTextBackgroundOpacity != 1.0f;
// Another way of naming "bgIsDefault" is "bgHasTransparency"
const auto bgIsDefault = (_backgroundColor.a == _defaultBackgroundColor.a) &&
miniksa marked this conversation as resolved.
Show resolved Hide resolved
(_backgroundColor.r == _defaultBackgroundColor.r) &&
(_backgroundColor.g == _defaultBackgroundColor.g) &&
(_backgroundColor.b == _defaultBackgroundColor.b);
const bool forceGrayscaleAA = usingCleartype &&
usingTransparency &&
bgIsDefault;

// Assemble the drawing context information
_drawingContext = std::make_unique<DrawingContext>(_d2dRenderTarget.Get(),
_d2dBrushForeground.Get(),
miniksa marked this conversation as resolved.
Show resolved Hide resolved
_d2dBrushBackground.Get(),
forceGrayscaleAA,
_dwriteFactory.Get(),
spacing,
_glyphCell,
_d2dRenderTarget->GetSize(),
_frameInfo.cursorInfo,
D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT);
}
}

return S_OK;
Expand All @@ -1020,6 +1066,8 @@ try
{
_isPainting = false;

LOG_IF_FAILED(_customRenderer->EndFrame(_drawingContext.get()));

hr = _d2dRenderTarget->EndDraw();

if (SUCCEEDED(hr))
Expand Down Expand Up @@ -1295,56 +1343,14 @@ try
const D2D1_POINT_2F origin = til::point{ coord } * _glyphCell;

// Create the text layout
CustomTextLayout layout(_dwriteFactory.Get(),
_dwriteTextAnalyzer.Get(),
_dwriteTextFormat.Get(),
_dwriteFontFace.Get(),
clusters,
_glyphCell.width(),
_boxDrawingEffect.Get());

// Get the baseline for this font as that's where we draw from
DWRITE_LINE_SPACING spacing;
RETURN_IF_FAILED(_dwriteTextFormat->GetLineSpacing(&spacing.method, &spacing.height, &spacing.baseline));

// GH#5098: If we're rendering with cleartype text, we need to always
// render onto an opaque background. If our background's opacity is
// 1.0f, that's great, we can use that. Otherwise, we need to force the
// text renderer to render this text in grayscale. In
// UpdateDrawingBrushes, we'll set the backgroundColor's a channel to
// 1.0 if we're in cleartype mode and the background's opacity is 1.0.
// Otherwise, at this point, the _backgroundColor's alpha is <1.0.
//
// Currently, only text with the default background color uses an alpha
// of 0, every other background uses 1.0
//
// DANGER: Layers slow us down. Only do this in the specific case where
// someone has chosen the slower ClearType antialiasing (versus the faster
// grayscale antialiasing)
const bool usingCleartype = _antialiasingMode == D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE;
const bool usingTransparency = _defaultTextBackgroundOpacity != 1.0f;
// Another way of naming "bgIsDefault" is "bgHasTransparency"
const auto bgIsDefault = (_backgroundColor.a == _defaultBackgroundColor.a) &&
(_backgroundColor.r == _defaultBackgroundColor.r) &&
(_backgroundColor.g == _defaultBackgroundColor.g) &&
(_backgroundColor.b == _defaultBackgroundColor.b);
const bool forceGrayscaleAA = usingCleartype &&
usingTransparency &&
bgIsDefault;

// Assemble the drawing context information
DrawingContext context(_d2dRenderTarget.Get(),
_d2dBrushForeground.Get(),
_d2dBrushBackground.Get(),
forceGrayscaleAA,
_dwriteFactory.Get(),
spacing,
_glyphCell,
_frameInfo.cursorInfo,
D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT);
RETURN_IF_FAILED(_customLayout->Reset());
RETURN_IF_FAILED(_customLayout->AppendClusters(clusters));

// Copy cursor info into drawing context
miniksa marked this conversation as resolved.
Show resolved Hide resolved
_drawingContext->cursorInfo = _frameInfo.cursorInfo;

// Layout then render the text
RETURN_IF_FAILED(layout.Draw(&context, _customRenderer.Get(), origin.x, origin.y));
RETURN_IF_FAILED(_customLayout->Draw(_drawingContext.get(), _customRenderer.Get(), origin.x, origin.y));

return S_OK;
}
Expand Down Expand Up @@ -1589,6 +1595,9 @@ try
// Calculate and cache the box effect for the base font. Scale is 1.0f because the base font is exactly the scale we want already.
RETURN_IF_FAILED(CustomTextLayout::s_CalculateBoxEffect(_dwriteTextFormat.Get(), _glyphCell.width(), _dwriteFontFace.Get(), 1.0f, &_boxDrawingEffect));

// Prepare the text layout
_customLayout = WRL::Make<CustomTextLayout>(_dwriteFactory.Get(), _dwriteTextAnalyzer.Get(), _dwriteTextFormat.Get(), _dwriteFontFace.Get(), _glyphCell.width(), _boxDrawingEffect.Get());

return S_OK;
}
CATCH_RETURN();
Expand Down Expand Up @@ -1716,17 +1725,11 @@ try

const Cluster cluster(glyph, 0); // columns don't matter, we're doing analysis not layout.

// Create the text layout
CustomTextLayout layout(_dwriteFactory.Get(),
_dwriteTextAnalyzer.Get(),
_dwriteTextFormat.Get(),
_dwriteFontFace.Get(),
{ &cluster, 1 },
_glyphCell.width(),
_boxDrawingEffect.Get());
RETURN_IF_FAILED(_customLayout->Reset());
RETURN_IF_FAILED(_customLayout->AppendClusters({ &cluster, 1 }));

UINT32 columns = 0;
RETURN_IF_FAILED(layout.GetColumns(&columns));
RETURN_IF_FAILED(_customLayout->GetColumns(&columns));

*pResult = columns != 1;

Expand Down
Loading