Bug 1110580 - patch 1 - Accelerate canvas2d.fillText for simple unidirectional strings by reusing the same textrun to draw as we created to measure the text. r=lsalzman

In general, the canvas text-drawing code makes two passes over the string, one to measure it and the second to draw.
Each time, it has to find individual direction runs and create a separate gfxTextRun for each.
However, in the (common) case of a simple unidirectional string, we're only going to have a single run,
so it's wasteful to construct it twice; the CanvasBidiProcessor can just re-use the run from the measurement
pass when it needs to draw.

In particular, this will apply to pdf.js drawing, where each glyph is handled in a separate fillText call.
Many other uses of canvas text should also benefit somewhat.

Differential Revision: https://phabricator.services.mozilla.com/D145426
This commit is contained in:
Jonathan Kew 2022-05-05 08:14:11 +00:00
parent e52e664d49
commit 12816161da

View File

@ -3676,7 +3676,7 @@ bool CanvasRenderingContext2D::GetHitRegionRect(Element* aElement,
/**
* Used for nsBidiPresUtils::ProcessText
*/
struct MOZ_STACK_CLASS CanvasBidiProcessor
struct MOZ_STACK_CLASS CanvasBidiProcessor final
: public nsBidiPresUtils::BidiProcessor {
using Style = CanvasRenderingContext2D::Style;
@ -3687,7 +3687,9 @@ struct MOZ_STACK_CLASS CanvasBidiProcessor
mAppUnitsPerDevPixel(0),
mOp(CanvasRenderingContext2D::TextDrawOperation::FILL),
mTextRunFlags(),
mDoMeasureBoundingBox(false) {
mSetTextCount(0),
mDoMeasureBoundingBox(false),
mIgnoreSetText(false) {
if (StaticPrefs::gfx_missing_fonts_notify()) {
mMissingFonts = MakeUnique<gfxMissingFontRecorder>();
}
@ -3702,8 +3704,15 @@ struct MOZ_STACK_CLASS CanvasBidiProcessor
using ContextState = CanvasRenderingContext2D::ContextState;
virtual void SetText(const char16_t* aText, int32_t aLength,
intl::BidiDirection aDirection) override {
void SetText(const char16_t* aText, int32_t aLength,
intl::BidiDirection aDirection) override {
if (mIgnoreSetText) {
// We've been told to ignore SetText because the processor is only ever
// handling a single, fixed string.
MOZ_ASSERT(mTextRun && mTextRun->GetLength() == uint32_t(aLength));
return;
}
mSetTextCount++;
auto* pfl = gfxPlatformFontList::PlatformFontList();
pfl->Lock();
mFontgrp->CheckForUpdatedPlatformList();
@ -3722,7 +3731,7 @@ struct MOZ_STACK_CLASS CanvasBidiProcessor
pfl->Unlock();
}
virtual nscoord GetWidth() override {
nscoord GetWidth() override {
gfxTextRun::Metrics textRunMetrics = mTextRun->MeasureText(
mDoMeasureBoundingBox ? gfxFont::TIGHT_INK_EXTENTS
: gfxFont::LOOSE_INK_EXTENTS,
@ -3798,7 +3807,7 @@ struct MOZ_STACK_CLASS CanvasBidiProcessor
return pattern.forget();
}
virtual void DrawText(nscoord aXOffset, nscoord aWidth) override {
void DrawText(nscoord aXOffset, nscoord aWidth) override {
gfx::Point point = mPt;
bool rtl = mTextRun->IsRightToLeft();
bool verticalRun = mTextRun->IsVertical();
@ -3936,8 +3945,14 @@ struct MOZ_STACK_CLASS CanvasBidiProcessor
// flags to use when creating textrun, based on CSS style
gfx::ShapedTextFlags mTextRunFlags;
// Count of how many times SetText has been called on this processor.
uint32_t mSetTextCount;
// true iff the bounding box should be measured
bool mDoMeasureBoundingBox;
// true if future SetText calls should be ignored
bool mIgnoreSetText;
};
TextMetrics* CanvasRenderingContext2D::DrawOrMeasureText(
@ -4072,6 +4087,12 @@ TextMetrics* CanvasRenderingContext2D::DrawOrMeasureText(
nscoord totalWidthCoord;
processor.mFontgrp
->UpdateUserFonts(); // ensure user font generation is current
const gfxFont::Metrics& fontMetrics =
processor.mFontgrp->GetFirstValidFont()->GetMetrics(
nsFontMetrics::eHorizontal);
// calls bidi algo twice since it needs the full text width and the
// bounding boxes before rendering anything
aError = nsBidiPresUtils::ProcessText(
@ -4083,6 +4104,13 @@ TextMetrics* CanvasRenderingContext2D::DrawOrMeasureText(
return nullptr;
}
// If ProcessText only called SetText once, we're dealing with a single run,
// and so we don't need to repeat SetText and textRun construction at drawing
// time below; we can just re-use the existing textRun.
if (processor.mSetTextCount == 1) {
processor.mIgnoreSetText = true;
}
float totalWidth = float(totalWidthCoord) / processor.mAppUnitsPerDevPixel;
// offset pt.x based on text align
@ -4102,12 +4130,6 @@ TextMetrics* CanvasRenderingContext2D::DrawOrMeasureText(
processor.mPt.x -= offsetX;
// offset pt.y (or pt.x, for vertical text) based on text baseline
processor.mFontgrp
->UpdateUserFonts(); // ensure user font generation is current
const gfxFont::Metrics& fontMetrics =
processor.mFontgrp->GetFirstValidFont()->GetMetrics(
nsFontMetrics::eHorizontal);
gfxFloat baselineAnchor;
switch (state.textBaseline) {