xref: /aosp_15_r20/external/skia/src/text/gpu/TextBlob.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2015 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "src/text/gpu/TextBlob.h"
9 
10 #include "include/core/SkMatrix.h"
11 #include "include/core/SkPoint.h"
12 #include "include/core/SkRect.h"
13 #include "include/core/SkScalar.h"
14 #include "include/private/SkColorData.h"
15 #include "include/private/base/SkAssert.h"
16 #include "include/private/base/SkCPUTypes.h"
17 #include "src/core/SkDevice.h"
18 #include "src/core/SkFontPriv.h"
19 #include "src/core/SkMaskFilterBase.h"
20 #include "src/core/SkPaintPriv.h"
21 #include "src/core/SkScalerContext.h"
22 #include "src/text/GlyphRun.h"
23 #include "src/text/gpu/SlugImpl.h"
24 #include "src/text/gpu/SubRunAllocator.h"
25 #include "src/text/gpu/SubRunContainer.h"
26 #include "src/text/gpu/SubRunControl.h"
27 
28 #include <memory>
29 #include <utility>
30 
31 class SkMaskFilter;
32 
33 using namespace sktext::gpu;
34 namespace {
35 
36 // Check for integer translate with the same 2x2 matrix.
37 // Returns the translation, and true if the change from initial matrix to the position matrix
38 // support using direct glyph masks.
can_use_direct(const SkMatrix & initialPositionMatrix,const SkMatrix & positionMatrix)39 std::tuple<bool, SkVector> can_use_direct(
40         const SkMatrix& initialPositionMatrix, const SkMatrix& positionMatrix) {
41     // The existing direct glyph info can be used if the initialPositionMatrix, and the
42     // positionMatrix have the same 2x2, and the translation between them is integer.
43     // Calculate the translation in source space to a translation in device space by mapping
44     // (0, 0) through both the initial position matrix and the position matrix; take the difference.
45     SkVector translation = positionMatrix.mapOrigin() - initialPositionMatrix.mapOrigin();
46     return {initialPositionMatrix.getScaleX() == positionMatrix.getScaleX() &&
47             initialPositionMatrix.getScaleY() == positionMatrix.getScaleY() &&
48             initialPositionMatrix.getSkewX()  == positionMatrix.getSkewX()  &&
49             initialPositionMatrix.getSkewY()  == positionMatrix.getSkewY()  &&
50             SkScalarIsInt(translation.x()) && SkScalarIsInt(translation.y()),
51             translation};
52 }
53 
54 
compute_canonical_color(const SkPaint & paint,bool lcd)55 static SkColor compute_canonical_color(const SkPaint& paint, bool lcd) {
56     SkColor canonicalColor = SkPaintPriv::ComputeLuminanceColor(paint);
57     if (lcd) {
58         // This is the correct computation for canonicalColor, but there are tons of cases where LCD
59         // can be modified. For now we just regenerate if any run in a textblob has LCD.
60         // TODO figure out where all of these modifications are and see if we can incorporate that
61         //      logic at a higher level *OR* use sRGB
62         //canonicalColor = SkMaskGamma::CanonicalColor(canonicalColor);
63 
64         // TODO we want to figure out a way to be able to use the canonical color on LCD text,
65         // see the note above.  We pick a placeholder value for LCD text to ensure we always match
66         // the same key
67         return SK_ColorTRANSPARENT;
68     } else {
69         // A8, though can have mixed BMP text but it shouldn't matter because BMP text won't have
70         // gamma corrected masks anyways, nor color
71         U8CPU lum = SkComputeLuminance(SkColorGetR(canonicalColor),
72                                        SkColorGetG(canonicalColor),
73                                        SkColorGetB(canonicalColor));
74         // reduce to our finite number of bits
75         canonicalColor = SkMaskGamma::CanonicalColor(SkColorSetRGB(lum, lum, lum));
76     }
77     return canonicalColor;
78 }
79 
80 }  // namespace
81 
82 namespace sktext::gpu {
83 // -- TextBlob::Key ------------------------------------------------------------------------------
Make(const GlyphRunList & glyphRunList,const SkPaint & paint,const SkMatrix & drawMatrix,const SkStrikeDeviceInfo & strikeDevice)84 auto TextBlob::Key::Make(const GlyphRunList& glyphRunList,
85                          const SkPaint& paint,
86                          const SkMatrix& drawMatrix,
87                          const SkStrikeDeviceInfo& strikeDevice) -> std::tuple<bool, Key> {
88     SkASSERT(strikeDevice.fSubRunControl != nullptr);
89     SkMaskFilterBase::BlurRec blurRec;
90     // It might be worth caching these things, but its not clear at this time
91     // TODO for animated mask filters, this will fill up our cache.  We need a safeguard here
92     const SkMaskFilter* maskFilter = paint.getMaskFilter();
93     bool canCache = glyphRunList.canCache() &&
94                     !(paint.getPathEffect() ||
95                         (maskFilter && !as_MFB(maskFilter)->asABlur(&blurRec)));
96 
97     TextBlob::Key key;
98     if (canCache) {
99         bool hasLCD = glyphRunList.anyRunsLCD();
100 
101         // We canonicalize all non-lcd draws to use kUnknown_SkPixelGeometry
102         SkPixelGeometry pixelGeometry = hasLCD ? strikeDevice.fSurfaceProps.pixelGeometry()
103                                                : kUnknown_SkPixelGeometry;
104 
105         SkColor canonicalColor = compute_canonical_color(paint, hasLCD);
106 
107         key.fPixelGeometry = pixelGeometry;
108         key.fUniqueID = glyphRunList.uniqueID();
109         key.fStyle = paint.getStyle();
110         if (key.fStyle != SkPaint::kFill_Style) {
111             key.fFrameWidth = paint.getStrokeWidth();
112             key.fMiterLimit = paint.getStrokeMiter();
113             key.fJoin = paint.getStrokeJoin();
114         }
115         key.fHasBlur = maskFilter != nullptr;
116         if (key.fHasBlur) {
117             key.fBlurRec = blurRec;
118         }
119         key.fCanonicalColor = canonicalColor;
120         key.fScalerContextFlags = SkTo<uint32_t>(strikeDevice.fScalerContextFlags);
121 
122         // Do any runs use direct drawing types?.
123         key.fHasSomeDirectSubRuns = false;
124         SkPoint glyphRunListLocation = glyphRunList.sourceBoundsWithOrigin().center();
125         for (auto& run : glyphRunList) {
126             SkScalar approximateDeviceTextSize =
127                     SkFontPriv::ApproximateTransformedTextSize(run.font(), drawMatrix,
128                                                                glyphRunListLocation);
129             key.fHasSomeDirectSubRuns |=
130                     strikeDevice.fSubRunControl->isDirect(approximateDeviceTextSize, paint,
131                                                           drawMatrix);
132         }
133 
134         if (key.fHasSomeDirectSubRuns) {
135             // Store the fractional offset of the position. We know that the matrix can't be
136             // perspective at this point.
137             SkPoint mappedOrigin = drawMatrix.mapOrigin();
138             key.fPositionMatrix = drawMatrix;
139             key.fPositionMatrix.setTranslateX(
140                     mappedOrigin.x() - SkScalarFloorToScalar(mappedOrigin.x()));
141             key.fPositionMatrix.setTranslateY(
142                     mappedOrigin.y() - SkScalarFloorToScalar(mappedOrigin.y()));
143         } else {
144             // For path and SDFT, the matrix doesn't matter.
145             key.fPositionMatrix = SkMatrix::I();
146         }
147     }
148 
149     return {canCache, key};
150 }
151 
operator ==(const TextBlob::Key & that) const152 bool TextBlob::Key::operator==(const TextBlob::Key& that) const {
153     if (fUniqueID != that.fUniqueID) { return false; }
154     if (fCanonicalColor != that.fCanonicalColor) { return false; }
155     if (fStyle != that.fStyle) { return false; }
156     if (fStyle != SkPaint::kFill_Style) {
157         if (fFrameWidth != that.fFrameWidth ||
158             fMiterLimit != that.fMiterLimit ||
159             fJoin != that.fJoin) {
160             return false;
161         }
162     }
163     if (fPixelGeometry != that.fPixelGeometry) { return false; }
164     if (fHasBlur != that.fHasBlur) { return false; }
165     if (fHasBlur) {
166         if (fBlurRec.fStyle != that.fBlurRec.fStyle || fBlurRec.fSigma != that.fBlurRec.fSigma) {
167             return false;
168         }
169     }
170 
171     if (fScalerContextFlags != that.fScalerContextFlags) { return false; }
172 
173     // DirectSubRuns do not support perspective when used with a TextBlob. SDFT, Transformed,
174     // Path, and Drawable do support perspective.
175     if (fPositionMatrix.hasPerspective() && fHasSomeDirectSubRuns) { return false; }
176 
177     if (fHasSomeDirectSubRuns != that.fHasSomeDirectSubRuns) { return false; }
178 
179     if (fHasSomeDirectSubRuns) {
180         auto [compatible, _] = can_use_direct(fPositionMatrix, that.fPositionMatrix);
181         return compatible;
182     }
183 
184     return true;
185 }
186 
187 // -- TextBlob -----------------------------------------------------------------------------------
operator delete(void * p)188 void TextBlob::operator delete(void* p) { ::operator delete(p); }
operator new(size_t)189 void* TextBlob::operator new(size_t) { SK_ABORT("All blobs are created by placement new."); }
operator new(size_t,void * p)190 void* TextBlob::operator new(size_t, void* p) { return p; }
191 
192 TextBlob::~TextBlob() = default;
193 
Make(const GlyphRunList & glyphRunList,const SkPaint & paint,const SkMatrix & positionMatrix,SkStrikeDeviceInfo strikeDeviceInfo,StrikeForGPUCacheInterface * strikeCache)194 sk_sp<TextBlob> TextBlob::Make(const GlyphRunList& glyphRunList,
195                                const SkPaint& paint,
196                                const SkMatrix& positionMatrix,
197                                SkStrikeDeviceInfo strikeDeviceInfo,
198                                StrikeForGPUCacheInterface* strikeCache) {
199     size_t subRunSizeHint = SubRunContainer::EstimateAllocSize(glyphRunList);
200     auto [initializer, totalMemoryAllocated, alloc] =
201             SubRunAllocator::AllocateClassMemoryAndArena<TextBlob>(subRunSizeHint);
202 
203     auto container = SubRunContainer::MakeInAlloc(
204             glyphRunList, positionMatrix, paint,
205             strikeDeviceInfo, strikeCache, &alloc, SubRunContainer::kAddSubRuns, "TextBlob");
206 
207     SkColor initialLuminance = SkPaintPriv::ComputeLuminanceColor(paint);
208     sk_sp<TextBlob> blob = sk_sp<TextBlob>(initializer.initialize(std::move(alloc),
209                                                                   std::move(container),
210                                                                   totalMemoryAllocated,
211                                                                   initialLuminance));
212     return blob;
213 }
214 
addKey(const Key & key)215 void TextBlob::addKey(const Key& key) {
216     fKey = key;
217 }
218 
canReuse(const SkPaint & paint,const SkMatrix & positionMatrix) const219 bool TextBlob::canReuse(const SkPaint& paint, const SkMatrix& positionMatrix) const {
220     // A singular matrix will create a TextBlob with no SubRuns, but unknown glyphs can also
221     // cause empty runs. If there are no subRuns, then regenerate when the matrices don't match.
222     if (fSubRuns->isEmpty() && fSubRuns->initialPosition() != positionMatrix) {
223         return false;
224     }
225 
226     // If we have LCD text then our canonical color will be set to transparent, in this case we have
227     // to regenerate the blob on any color change
228     // We use the grPaint to get any color filter effects
229     if (fKey.fCanonicalColor == SK_ColorTRANSPARENT &&
230         fInitialLuminance != SkPaintPriv::ComputeLuminanceColor(paint)) {
231         return false;
232     }
233 
234     return fSubRuns->canReuse(paint, positionMatrix);
235 }
236 
key() const237 const TextBlob::Key& TextBlob::key() const { return fKey; }
238 
draw(SkCanvas * canvas,SkPoint drawOrigin,const SkPaint & paint,const AtlasDrawDelegate & atlasDelegate)239 void TextBlob::draw(SkCanvas* canvas,
240                     SkPoint drawOrigin,
241                     const SkPaint& paint,
242                     const AtlasDrawDelegate& atlasDelegate) {
243     fSubRuns->draw(canvas, drawOrigin, paint, this, atlasDelegate);
244 }
245 
TextBlob(SubRunAllocator && alloc,SubRunContainerOwner subRuns,int totalMemorySize,SkColor initialLuminance)246 TextBlob::TextBlob(SubRunAllocator&& alloc,
247                    SubRunContainerOwner subRuns,
248                    int totalMemorySize,
249                    SkColor initialLuminance)
250         : fAlloc{std::move(alloc)}
251         , fSubRuns{std::move(subRuns)}
252         , fSize(totalMemorySize)
253         , fInitialLuminance{initialLuminance} { }
254 
MakeSlug(const SkMatrix & drawMatrix,const sktext::GlyphRunList & glyphRunList,const SkPaint & paint,SkStrikeDeviceInfo strikeDeviceInfo,sktext::StrikeForGPUCacheInterface * strikeCache)255 sk_sp<Slug> MakeSlug(const SkMatrix& drawMatrix,
256                      const sktext::GlyphRunList& glyphRunList,
257                      const SkPaint& paint,
258                      SkStrikeDeviceInfo strikeDeviceInfo,
259                      sktext::StrikeForGPUCacheInterface* strikeCache) {
260     return SlugImpl::Make(drawMatrix, glyphRunList, paint, strikeDeviceInfo, strikeCache);
261 }
262 }  // namespace sktext::gpu
263