From 15a732fbb50f6bf0cb620157c04770b4f0f683f2 Mon Sep 17 00:00:00 2001 From: Jonathan Kew Date: Tue, 16 Aug 2022 10:39:51 +0000 Subject: [PATCH] Bug 1740530 - patch 3 - Replace GetColorGlyphLayers and the layer-rendering code in gfxFont with a zero-copy implementation in COLRFonts. r=gfx-reviewers,lsalzman Depends on D152037 Differential Revision: https://phabricator.services.mozilla.com/D152038 --- gfx/thebes/COLRFonts.cpp | 231 ++++++++++++++++++++++++-------------- gfx/thebes/COLRFonts.h | 25 +++-- gfx/thebes/gfxFont.cpp | 66 +++-------- gfx/thebes/gfxFontEntry.h | 3 - 4 files changed, 177 insertions(+), 148 deletions(-) diff --git a/gfx/thebes/COLRFonts.cpp b/gfx/thebes/COLRFonts.cpp index 1becbb81a576..7a5bf2ac2e2b 100644 --- a/gfx/thebes/COLRFonts.cpp +++ b/gfx/thebes/COLRFonts.cpp @@ -7,11 +7,17 @@ #include "gfxFontUtils.h" #include "gfxUtils.h" #include "harfbuzz/hb.h" +#include "mozilla/gfx/Helpers.h" +#include "TextDrawTarget.h" using namespace mozilla; using namespace mozilla::gfx; -#pragma pack(1) +namespace { // anonymous namespace for implementation internals + +#pragma pack(1) // ensure no padding is added to the COLR structs + +struct LayerRecord; struct COLRHeader { AutoSwap_PRUint16 version; @@ -19,17 +25,16 @@ struct COLRHeader { AutoSwap_PRUint32 offsetBaseGlyphRecord; AutoSwap_PRUint32 offsetLayerRecord; AutoSwap_PRUint16 numLayerRecords; -}; -struct COLRBaseGlyphRecord { - AutoSwap_PRUint16 glyphId; - AutoSwap_PRUint16 firstLayerIndex; - AutoSwap_PRUint16 numLayers; -}; + const COLRBaseGlyphRecord* GetBaseGlyphRecords() const { + return reinterpret_cast( + reinterpret_cast(this) + offsetBaseGlyphRecord); + } -struct COLRLayerRecord { - AutoSwap_PRUint16 glyphId; - AutoSwap_PRUint16 paletteEntryIndex; + const LayerRecord* GetLayerRecords() const { + return reinterpret_cast( + reinterpret_cast(this) + offsetLayerRecord); + } }; struct CPALHeaderVersion0 { @@ -48,8 +53,82 @@ struct CPALColorRecord { uint8_t alpha; }; +struct PaintState { + const COLRHeader* mHeader; + const CPALColorRecord* mPalette; + DrawTarget* mDrawTarget; + ScaledFont* mScaledFont; + DrawOptions mDrawOptions; + sRGBColor mCurrentColor; +}; + +static const CPALColorRecord* GetPaletteByIndex(hb_blob_t* aCPAL, + uint32_t aIndex) { + const auto* cpal = reinterpret_cast( + hb_blob_get_data(aCPAL, nullptr)); + const uint32_t offset = cpal->offsetFirstColorRecord; + return reinterpret_cast( + reinterpret_cast(cpal) + offset); +} + +static DeviceColor DoGetColor(PaintState& aState, uint16_t aPaletteIndex, + float aAlpha) { + sRGBColor color; + if (aPaletteIndex == 0xFFFF) { + color = aState.mCurrentColor; + } else { + const CPALColorRecord& c = aState.mPalette[uint16_t(aPaletteIndex)]; + color = sRGBColor(c.red / 255.0, c.green / 255.0, c.blue / 255.0, + c.alpha / 255.0 * aAlpha); + } + return ToDeviceColor(color); +} + +struct LayerRecord { + AutoSwap_PRUint16 glyphId; + AutoSwap_PRUint16 paletteEntryIndex; + + bool Paint(PaintState& aState, float aAlpha, const Point& aPoint) const { + Glyph glyph{uint16_t(glyphId), aPoint}; + // TODO validate glyph.mIndex is within range for the font + GlyphBuffer buffer{&glyph, 1}; + aState.mDrawTarget->FillGlyphs( + aState.mScaledFont, buffer, + ColorPattern(DoGetColor(aState, paletteEntryIndex, aAlpha)), + aState.mDrawOptions); + return true; + } +}; + #pragma pack() +} // end anonymous namespace + +namespace mozilla::gfx { + +struct COLRBaseGlyphRecord { + AutoSwap_PRUint16 glyphId; + AutoSwap_PRUint16 firstLayerIndex; + AutoSwap_PRUint16 numLayers; + + bool Paint(PaintState& aState, float aAlpha, const Point& aPoint) const { + uint32_t layerIndex = uint16_t(firstLayerIndex); + uint32_t end = layerIndex + uint16_t(numLayers); + if (end > uint16_t(aState.mHeader->numLayerRecords)) { + MOZ_ASSERT_UNREACHABLE("bad COLRv0 table"); + return false; + } + const auto* layers = aState.mHeader->GetLayerRecords(); + while (layerIndex < end) { + if (!layers[layerIndex].Paint(aState, aAlpha, aPoint)) { + return false; + } + ++layerIndex; + } + return true; + } +}; + bool COLRFonts::ValidateColorGlyphs(hb_blob_t* aCOLR, hb_blob_t* aCPAL) { unsigned int colrLength; const COLRHeader* colr = @@ -98,8 +177,7 @@ bool COLRFonts::ValidateColorGlyphs(hb_blob_t* aCOLR, hb_blob_t* aCPAL) { return false; } - if (sizeof(COLRLayerRecord) * numLayerRecords > - colrLength - offsetLayerRecord) { + if (sizeof(LayerRecord) * numLayerRecords > colrLength - offsetLayerRecord) { // COLR layer record will be overflow return false; } @@ -141,7 +219,7 @@ bool COLRFonts::ValidateColorGlyphs(hb_blob_t* aCOLR, hb_blob_t* aCPAL) { } } - const COLRLayerRecord* layer = reinterpret_cast( + const LayerRecord* layer = reinterpret_cast( reinterpret_cast(colr) + offsetLayerRecord); for (uint16_t i = 0; i < numLayerRecords; i++, layer++) { @@ -155,80 +233,63 @@ bool COLRFonts::ValidateColorGlyphs(hb_blob_t* aCOLR, hb_blob_t* aCPAL) { return true; } -static int CompareBaseGlyph(const void* key, const void* data) { - uint32_t glyphId = (uint32_t)(uintptr_t)key; - const COLRBaseGlyphRecord* baseGlyph = - reinterpret_cast(data); - uint32_t baseGlyphId = uint16_t(baseGlyph->glyphId); - - if (baseGlyphId == glyphId) { - return 0; - } - - return baseGlyphId > glyphId ? -1 : 1; -} - -static COLRBaseGlyphRecord* LookForBaseGlyphRecord(const COLRHeader* aCOLR, - uint32_t aGlyphId) { - const uint8_t* baseGlyphRecords = reinterpret_cast(aCOLR) + - uint32_t(aCOLR->offsetBaseGlyphRecord); - // BaseGlyphRecord is sorted by glyphId - return reinterpret_cast( - bsearch((void*)(uintptr_t)aGlyphId, baseGlyphRecords, - uint16_t(aCOLR->numBaseGlyphRecord), sizeof(COLRBaseGlyphRecord), - CompareBaseGlyph)); -} - -bool COLRFonts::GetColorGlyphLayers( - hb_blob_t* aCOLR, hb_blob_t* aCPAL, uint32_t aGlyphId, - const mozilla::gfx::DeviceColor& aDefaultColor, nsTArray& aGlyphs, - nsTArray& aColors) { - unsigned int blobLength; +const COLRBaseGlyphRecord* COLRFonts::GetGlyphLayers(hb_blob_t* aCOLR, + uint32_t aGlyphId) { + unsigned int length; const COLRHeader* colr = - reinterpret_cast(hb_blob_get_data(aCOLR, &blobLength)); - MOZ_ASSERT(colr, "Cannot get COLR raw data"); - MOZ_ASSERT(blobLength, "Found COLR data, but length is 0"); - - COLRBaseGlyphRecord* baseGlyph = LookForBaseGlyphRecord(colr, aGlyphId); - if (!baseGlyph) { - return false; - } - - const CPALHeaderVersion0* cpal = reinterpret_cast( - hb_blob_get_data(aCPAL, &blobLength)); - MOZ_ASSERT(cpal, "Cannot get CPAL raw data"); - MOZ_ASSERT(blobLength, "Found CPAL data, but length is 0"); - - const COLRLayerRecord* layer = reinterpret_cast( - reinterpret_cast(colr) + - uint32_t(colr->offsetLayerRecord) + - sizeof(COLRLayerRecord) * uint16_t(baseGlyph->firstLayerIndex)); - const uint16_t numLayers = baseGlyph->numLayers; - const uint32_t offsetFirstColorRecord = cpal->offsetFirstColorRecord; - - for (uint16_t layerIndex = 0; layerIndex < numLayers; layerIndex++) { - aGlyphs.AppendElement(uint16_t(layer->glyphId)); - if (uint16_t(layer->paletteEntryIndex) == 0xFFFF) { - aColors.AppendElement(aDefaultColor); - } else { - const CPALColorRecord* color = reinterpret_cast( - reinterpret_cast(cpal) + offsetFirstColorRecord + - sizeof(CPALColorRecord) * uint16_t(layer->paletteEntryIndex)); - aColors.AppendElement( - mozilla::gfx::ToDeviceColor(mozilla::gfx::sRGBColor::FromU8( - color->red, color->green, color->blue, color->alpha))); + reinterpret_cast(hb_blob_get_data(aCOLR, &length)); + // This should never be called unless we have checked that the COLR table is + // structurally valid, so it will be safe to read the header fields. + MOZ_RELEASE_ASSERT(colr && length >= sizeof(COLRHeader), "bad COLR table!"); + auto compareBaseGlyph = [](const void* key, const void* data) -> int { + uint32_t glyphId = (uint32_t)(uintptr_t)key; + const COLRBaseGlyphRecord* baseGlyph = + reinterpret_cast(data); + uint32_t baseGlyphId = uint16_t(baseGlyph->glyphId); + if (baseGlyphId == glyphId) { + return 0; } - layer++; + return baseGlyphId > glyphId ? -1 : 1; + }; + return reinterpret_cast( + bsearch((void*)(uintptr_t)aGlyphId, colr->GetBaseGlyphRecords(), + uint16_t(colr->numBaseGlyphRecord), sizeof(COLRBaseGlyphRecord), + compareBaseGlyph)); +} + +bool COLRFonts::PaintGlyphLayers( + hb_blob_t* aCOLR, hb_blob_t* aCPAL, const COLRBaseGlyphRecord* aLayers, + DrawTarget* aDrawTarget, layout::TextDrawTarget* aTextDrawer, + ScaledFont* aScaledFont, DrawOptions aDrawOptions, + const sRGBColor& aCurrentColor, const Point& aPoint) { + // Default to opaque rendering (non-webrender applies alpha with a layer) + float alpha = 1.0; + if (aTextDrawer) { + // defaultColor is the one that comes from CSS, so it has transparency info. + bool hasComplexTransparency = + 0.0 < aCurrentColor.a && aCurrentColor.a < 1.0; + if (hasComplexTransparency && uint16_t(aLayers->numLayers) > 1) { + // WebRender doesn't support drawing multi-layer transparent color-glyphs, + // as it requires compositing all the layers before applying transparency. + // (pretend to succeed, output doesn't matter, we will emit a blob) + aTextDrawer->FoundUnsupportedFeature(); + return true; + } + + // If we get here, then either alpha is 0 or 1, or there's only one layer + // which shouldn't have composition issues. In all of these cases, applying + // transparency directly to the glyph should work perfectly fine. + // + // Note that we must still emit completely transparent emoji, because they + // might be wrapped in a shadow that uses the text run's glyphs. + alpha = aCurrentColor.a; } - return true; + auto* colr = + reinterpret_cast(hb_blob_get_data(aCOLR, nullptr)); + auto* palette = GetPaletteByIndex(aCPAL, 0); // TODO: font-palette support + PaintState state{colr, palette, aDrawTarget, + aScaledFont, aDrawOptions, aCurrentColor}; + return aLayers->Paint(state, alpha, aPoint); } -bool COLRFonts::HasColorLayersForGlyph(hb_blob_t* aCOLR, uint32_t aGlyphId) { - unsigned int blobLength; - const COLRHeader* colr = - reinterpret_cast(hb_blob_get_data(aCOLR, &blobLength)); - MOZ_ASSERT(colr, "Cannot get COLR raw data"); - MOZ_ASSERT(blobLength, "Found COLR data, but length is 0"); - - return LookForBaseGlyphRecord(colr, aGlyphId); -} +} // end namespace mozilla::gfx diff --git a/gfx/thebes/COLRFonts.h b/gfx/thebes/COLRFonts.h index 3cee7c1fa623..da63afc9cbc9 100644 --- a/gfx/thebes/COLRFonts.h +++ b/gfx/thebes/COLRFonts.h @@ -6,27 +6,36 @@ #ifndef COLR_FONTS_H #define COLR_FONTS_H -#include "nsTArray.h" +#include "mozilla/gfx/2D.h" struct hb_blob_t; namespace mozilla { + +namespace layout { +class TextDrawTarget; +} + namespace gfx { -struct DeviceColor; +struct COLRBaseGlyphRecord; class COLRFonts { public: // for color layer from glyph using COLR and CPAL tables static bool ValidateColorGlyphs(hb_blob_t* aCOLR, hb_blob_t* aCPAL); - static bool GetColorGlyphLayers( - hb_blob_t* aCOLR, hb_blob_t* aCPAL, uint32_t aGlyphId, - const mozilla::gfx::DeviceColor& aDefaultColor, - nsTArray& aGlyphs, - nsTArray& aColors); - static bool HasColorLayersForGlyph(hb_blob_t* aCOLR, uint32_t aGlyphId); + + static const COLRBaseGlyphRecord* GetGlyphLayers(hb_blob_t* aCOLR, + uint32_t aGlyphId); + + static bool PaintGlyphLayers( + hb_blob_t* aCOLR, hb_blob_t* aCPAL, const COLRBaseGlyphRecord* aLayers, + DrawTarget* aDrawTarget, layout::TextDrawTarget* aTextDrawer, + ScaledFont* aScaledFont, DrawOptions aDrawOptions, + const sRGBColor& aCurrentColor, const Point& aPoint); }; } // namespace gfx + } // namespace mozilla #endif // COLR_FONTS_H diff --git a/gfx/thebes/gfxFont.cpp b/gfx/thebes/gfxFont.cpp index 3cf098670238..d6fac32595d2 100644 --- a/gfx/thebes/gfxFont.cpp +++ b/gfx/thebes/gfxFont.cpp @@ -2464,61 +2464,23 @@ bool gfxFont::RenderSVGGlyph(gfxContext* aContext, bool gfxFont::RenderColorGlyph(DrawTarget* aDrawTarget, gfxContext* aContext, layout::TextDrawTarget* aTextDrawer, - mozilla::gfx::ScaledFont* scaledFont, - mozilla::gfx::DrawOptions aDrawOptions, - const mozilla::gfx::Point& aPoint, + ScaledFont* aScaledFont, + DrawOptions aDrawOptions, const Point& aPoint, uint32_t aGlyphId) const { - AutoTArray layerGlyphs; - AutoTArray layerColors; + DeviceColor ctxColor; + sRGBColor currentColor = aContext->GetDeviceColor(ctxColor) + ? sRGBColor::FromABGR(ctxColor.ToABGR()) + : sRGBColor::OpaqueBlack(); - mozilla::gfx::DeviceColor ctxColor; - if (!aContext->GetDeviceColor(ctxColor)) { - ctxColor = ToDeviceColor(mozilla::gfx::sRGBColor::OpaqueBlack()); - } - if (!COLRFonts::GetColorGlyphLayers(GetFontEntry()->GetCOLR(), - GetFontEntry()->GetCPAL(), aGlyphId, - ctxColor, layerGlyphs, layerColors)) { - return false; + if (const auto* layers = + COLRFonts::GetGlyphLayers(GetFontEntry()->GetCOLR(), aGlyphId)) { + return COLRFonts::PaintGlyphLayers(GetFontEntry()->GetCOLR(), + GetFontEntry()->GetCPAL(), layers, + aDrawTarget, aTextDrawer, aScaledFont, + aDrawOptions, currentColor, aPoint); } - // Default to opaque rendering (non-webrender applies alpha with a layer) - float alpha = 1.0; - if (aTextDrawer) { - // defaultColor is the one that comes from CSS, so it has transparency info. - bool hasComplexTransparency = 0.f < ctxColor.a && ctxColor.a < 1.f; - if (hasComplexTransparency && layerGlyphs.Length() > 1) { - // WebRender doesn't support drawing multi-layer transparent color-glyphs, - // as it requires compositing all the layers before applying transparency. - // (pretend to succeed, output doesn't matter, we will emit a blob) - aTextDrawer->FoundUnsupportedFeature(); - return true; - } - - // If we get here, then either alpha is 0 or 1, or there's only one layer - // which shouldn't have composition issues. In all of these cases, applying - // transparency directly to the glyph should work perfectly fine. - // - // Note that we must still emit completely transparent emoji, because they - // might be wrapped in a shadow that uses the text run's glyphs. - alpha = ctxColor.a; - } - - for (uint32_t layerIndex = 0; layerIndex < layerGlyphs.Length(); - layerIndex++) { - Glyph glyph; - glyph.mIndex = layerGlyphs[layerIndex]; - glyph.mPosition = aPoint; - - mozilla::gfx::GlyphBuffer buffer; - buffer.mGlyphs = &glyph; - buffer.mNumGlyphs = 1; - - mozilla::gfx::DeviceColor layerColor = layerColors[layerIndex]; - layerColor.a *= alpha; - aDrawTarget->FillGlyphs(scaledFont, buffer, ColorPattern(layerColor), - aDrawOptions); - } - return true; + return false; } bool gfxFont::HasColorGlyphFor(uint32_t aCh, uint32_t aNextCh) { @@ -2544,7 +2506,7 @@ bool gfxFont::HasColorGlyphFor(uint32_t aCh, uint32_t aNextCh) { } // Check if there is a COLR/CPAL or SVG glyph for this ID. if (fe->TryGetColorGlyphs() && - COLRFonts::HasColorLayersForGlyph(fe->GetCOLR(), gid)) { + COLRFonts::GetGlyphLayers(fe->GetCOLR(), gid)) { return true; } if (fe->TryGetSVGData(this) && fe->HasSVGGlyph(gid)) { diff --git a/gfx/thebes/gfxFontEntry.h b/gfx/thebes/gfxFontEntry.h index 0faab5837fd4..fb900e09c2d5 100644 --- a/gfx/thebes/gfxFontEntry.h +++ b/gfx/thebes/gfxFontEntry.h @@ -58,9 +58,6 @@ namespace fontlist { struct Face; struct Family; } // namespace fontlist -namespace gfx { -struct DeviceColor; -} } // namespace mozilla typedef struct gr_face gr_face;