From d74cee18c719be0342c923b57b7eaaf9ae916237 Mon Sep 17 00:00:00 2001 From: Michael Niksa Date: Fri, 26 Jun 2020 10:22:40 -0700 Subject: [PATCH 1/3] Revert "Revert "Skip ... analysis when the ... text is simple (6206)" (#6665)" This reverts commit cffd4eb8e912ec3a5ce538cd300788f30f5e8b15. --- src/renderer/dx/CustomTextLayout.cpp | 92 +++++++++++++++++++++++++--- src/renderer/dx/CustomTextLayout.h | 7 +++ 2 files changed, 90 insertions(+), 9 deletions(-) diff --git a/src/renderer/dx/CustomTextLayout.cpp b/src/renderer/dx/CustomTextLayout.cpp index f5f3e341634..4c78a74cf44 100644 --- a/src/renderer/dx/CustomTextLayout.cpp +++ b/src/renderer/dx/CustomTextLayout.cpp @@ -39,7 +39,8 @@ CustomTextLayout::CustomTextLayout(gsl::not_null const factory _runs{}, _breakpoints{}, _runIndex{ 0 }, - _width{ width } + _width{ width }, + _isEntireTextSimple{ false } { // Fetch the locale name out once now from the format _localeName.resize(gsl::narrow_cast(format->GetLocaleNameLength()) + 1); // +1 for null @@ -58,6 +59,7 @@ try _runs.clear(); _breakpoints.clear(); _runIndex = 0; + _isEntireTextSimple = false; _textClusterColumns.clear(); _text.clear(); return S_OK; @@ -105,6 +107,7 @@ CATCH_RETURN() RETURN_HR_IF_NULL(E_INVALIDARG, columns); *columns = 0; + RETURN_IF_FAILED(_AnalyzeTextComplexity()); RETURN_IF_FAILED(_AnalyzeRuns()); RETURN_IF_FAILED(_ShapeGlyphRuns()); @@ -135,6 +138,7 @@ CATCH_RETURN() FLOAT originX, FLOAT originY) noexcept { + RETURN_IF_FAILED(_AnalyzeTextComplexity()); RETURN_IF_FAILED(_AnalyzeRuns()); RETURN_IF_FAILED(_ShapeGlyphRuns()); RETURN_IF_FAILED(_CorrectGlyphRuns()); @@ -148,6 +152,44 @@ CATCH_RETURN() return S_OK; } +// Routine Description: +// - Uses the internal text information and the analyzers/font information from construction +// to determine the complexity of the text. If the text is determined to be entirely simple, +// we'll have more chances to optimize the layout process. +// Arguments: +// - - Uses internal state +// Return Value: +// - S_OK or suitable DirectWrite or STL error code +[[nodiscard]] HRESULT CustomTextLayout::_AnalyzeTextComplexity() noexcept +{ + try + { + const auto textLength = gsl::narrow(_text.size()); + + BOOL isTextSimple = FALSE; + UINT32 uiLengthRead = 0; + + // Start from the beginning. + const UINT32 glyphStart = 0; + + _glyphIndices.resize(textLength); + + const HRESULT hr = _analyzer->GetTextComplexity( + _text.c_str(), + textLength, + _font.Get(), + &isTextSimple, + &uiLengthRead, + &_glyphIndices.at(glyphStart)); + + RETURN_IF_FAILED(hr); + + _isEntireTextSimple = isTextSimple && uiLengthRead == textLength; + } + CATCH_RETURN(); + return S_OK; +} + // Routine Description: // - Uses the internal text information and the analyzers/font information from construction // to determine the complexity of the text inside this layout, compute the subsections (or runs) @@ -178,11 +220,7 @@ CATCH_RETURN() // Allocate enough room to have one breakpoint per code unit. _breakpoints.resize(_text.size()); - BOOL isTextSimple = FALSE; - UINT32 uiLengthRead = 0; - RETURN_IF_FAILED(_analyzer->GetTextComplexity(_text.c_str(), textLength, _font.Get(), &isTextSimple, &uiLengthRead, NULL)); - - if (!(isTextSimple && uiLengthRead == _text.size())) + if (!_isEntireTextSimple) { // Call each of the analyzers in sequence, recording their results. RETURN_IF_FAILED(_analyzer->AnalyzeLineBreakpoints(this, 0, textLength, this)); @@ -303,6 +341,39 @@ CATCH_RETURN() _glyphIndices.resize(totalGlyphsArrayCount); } + if (_isEntireTextSimple) + { + // When the entire text is simple, we can skip GetGlyphs and directly retrieve glyph indices and + // advances(in font design unit). With the help of font metrics, we can calculate the actual glyph + // advances without the need of GetGlyphPlacements. This shortcut will significantly reduce the time + // needed for text analysis. + DWRITE_FONT_METRICS1 metrics; + run.fontFace->GetMetrics(&metrics); + + // With simple text, there's only one run. The actual glyph count is the same as textLength. + _glyphDesignUnitAdvances.resize(textLength); + _glyphAdvances.resize(textLength); + _glyphOffsets.resize(textLength); + + USHORT designUnitsPerEm = metrics.designUnitsPerEm; + + RETURN_IF_FAILED(_font->GetDesignGlyphAdvances( + textLength, + &_glyphIndices.at(glyphStart), + &_glyphDesignUnitAdvances.at(glyphStart), + run.isSideways)); + + for (size_t i = glyphStart; i < _glyphAdvances.size(); i++) + { + _glyphAdvances.at(i) = (float)_glyphDesignUnitAdvances.at(i) / designUnitsPerEm * _format->GetFontSize() * run.fontScale; + } + + run.glyphCount = textLength; + glyphStart += textLength; + + return S_OK; + } + std::vector textProps(textLength); std::vector glyphProps(maxGlyphCount); @@ -400,6 +471,12 @@ CATCH_RETURN() { try { + // For simple text, there is no need to correct runs. + if (_isEntireTextSimple) + { + return S_OK; + } + // Correct each run separately. This is needed whenever script, locale, // or reading direction changes. for (UINT32 runIndex = 0; runIndex < _runs.size(); ++runIndex) @@ -537,9 +614,6 @@ try // We're going to walk through and check for advances that don't match the space that we expect to give out. - DWRITE_FONT_METRICS1 metrics; - run.fontFace->GetMetrics(&metrics); - // Glyph Indices represents the number inside the selected font where the glyph image/paths are found. // Text represents the original text we gave in. // Glyph Clusters represents the map between Text and Glyph Indices. diff --git a/src/renderer/dx/CustomTextLayout.h b/src/renderer/dx/CustomTextLayout.h index afa894eadff..dac30b06c12 100644 --- a/src/renderer/dx/CustomTextLayout.h +++ b/src/renderer/dx/CustomTextLayout.h @@ -137,6 +137,7 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT STDMETHODCALLTYPE _AnalyzeBoxDrawing(gsl::not_null const source, UINT32 textPosition, UINT32 textLength); [[nodiscard]] HRESULT STDMETHODCALLTYPE _SetBoxEffect(UINT32 textPosition, UINT32 textLength); + [[nodiscard]] HRESULT _AnalyzeTextComplexity() noexcept; [[nodiscard]] HRESULT _AnalyzeRuns() noexcept; [[nodiscard]] HRESULT _ShapeGlyphRuns() noexcept; [[nodiscard]] HRESULT _ShapeGlyphRun(const UINT32 runIndex, UINT32& glyphStart) noexcept; @@ -183,6 +184,9 @@ namespace Microsoft::Console::Render // Glyph shaping results + // Whether the entire text is determined to be simple and does not require full script shaping. + bool _isEntireTextSimple; + std::vector _glyphOffsets; // Clusters are complicated. They're in respect to each individual run. @@ -194,6 +198,9 @@ namespace Microsoft::Console::Render // This appears to be the index of the glyph inside each font. std::vector _glyphIndices; + // This is for calculating glyph advances when the entire text is simple. + std::vector _glyphDesignUnitAdvances; + std::vector _glyphAdvances; struct ScaleCorrection From 3065d4b739db8a2c779813f5af883a32c938266d Mon Sep 17 00:00:00 2001 From: Michael Niksa Date: Fri, 26 Jun 2020 10:19:37 -0700 Subject: [PATCH 2/3] Fix cluster map and offsets for simple runs during shaping. --- src/renderer/dx/CustomTextLayout.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/renderer/dx/CustomTextLayout.cpp b/src/renderer/dx/CustomTextLayout.cpp index 4c78a74cf44..50aa5eade8f 100644 --- a/src/renderer/dx/CustomTextLayout.cpp +++ b/src/renderer/dx/CustomTextLayout.cpp @@ -353,7 +353,6 @@ CATCH_RETURN() // With simple text, there's only one run. The actual glyph count is the same as textLength. _glyphDesignUnitAdvances.resize(textLength); _glyphAdvances.resize(textLength); - _glyphOffsets.resize(textLength); USHORT designUnitsPerEm = metrics.designUnitsPerEm; @@ -368,6 +367,13 @@ CATCH_RETURN() _glyphAdvances.at(i) = (float)_glyphDesignUnitAdvances.at(i) / designUnitsPerEm * _format->GetFontSize() * run.fontScale; } + // Empty all the offsets, we're not going to use them + _glyphOffsets.assign(textLength, {}); + + // Set all the clusters as sequential. In a simple run, we're going 1 to 1. + // Fill the clusters sequentially from 0 to N-1. + std::iota(_glyphClusters.begin(), _glyphClusters.end(), gsl::narrow_cast(0)); + run.glyphCount = textLength; glyphStart += textLength; From 6443e54a9f2de6cd230ea9e7fcf9e78452957ae6 Mon Sep 17 00:00:00 2001 From: Michael Niksa Date: Fri, 26 Jun 2020 14:52:41 -0700 Subject: [PATCH 3/3] Do better at cleaning stuff up for recycling so scale corrections and offsets and whatnot aren't left behind. Now covers #6669 --- src/renderer/dx/CustomTextLayout.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/renderer/dx/CustomTextLayout.cpp b/src/renderer/dx/CustomTextLayout.cpp index 50aa5eade8f..7c14d646134 100644 --- a/src/renderer/dx/CustomTextLayout.cpp +++ b/src/renderer/dx/CustomTextLayout.cpp @@ -62,6 +62,12 @@ try _isEntireTextSimple = false; _textClusterColumns.clear(); _text.clear(); + _glyphScaleCorrections.clear(); + _glyphClusters.clear(); + _glyphIndices.clear(); + _glyphDesignUnitAdvances.clear(); + _glyphAdvances.clear(); + _glyphOffsets.clear(); return S_OK; } CATCH_RETURN() @@ -212,8 +218,6 @@ CATCH_RETURN() // This result will be subdivided by the analysis processes. _runs.resize(1); auto& initialRun = _runs.front(); - initialRun.nextRunIndex = 0; - initialRun.textStart = 0; initialRun.textLength = textLength; initialRun.bidiLevel = (_readingDirection == DWRITE_READING_DIRECTION_RIGHT_TO_LEFT); @@ -367,9 +371,6 @@ CATCH_RETURN() _glyphAdvances.at(i) = (float)_glyphDesignUnitAdvances.at(i) / designUnitsPerEm * _format->GetFontSize() * run.fontScale; } - // Empty all the offsets, we're not going to use them - _glyphOffsets.assign(textLength, {}); - // Set all the clusters as sequential. In a simple run, we're going 1 to 1. // Fill the clusters sequentially from 0 to N-1. std::iota(_glyphClusters.begin(), _glyphClusters.end(), gsl::narrow_cast(0)); @@ -596,6 +597,10 @@ CATCH_RETURN() // 1 1 .8 1 1 } + // Dump the glyph scale corrections now that we're done with them. + _glyphScaleCorrections.clear(); + + // Order the runs. _OrderRuns(); } CATCH_RETURN();