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 "include/core/SkColorFilter.h"
9 #include "include/gpu/GrContext.h"
10 #include "include/private/SkTemplates.h"
11 #include "src/core/SkMaskFilterBase.h"
12 #include "src/core/SkPaintPriv.h"
13 #include "src/gpu/GrBlurUtils.h"
14 #include "src/gpu/GrClip.h"
15 #include "src/gpu/GrStyle.h"
16 #include "src/gpu/geometry/GrShape.h"
17 #include "src/gpu/ops/GrAtlasTextOp.h"
18 #include "src/gpu/text/GrAtlasManager.h"
19 #include "src/gpu/text/GrTextBlob.h"
20 #include "src/gpu/text/GrTextTarget.h"
21
22 #include <cstddef>
23 #include <new>
24
make_inverse(const SkMatrix & matrix)25 static SkMatrix make_inverse(const SkMatrix& matrix) {
26 SkMatrix inverseMatrix;
27 if (!matrix.invert(&inverseMatrix)) {
28 inverseMatrix = SkMatrix::I();
29 }
30 return inverseMatrix;
31 }
32
33 // -- GrTextBlob::Key ------------------------------------------------------------------------------
Key()34 GrTextBlob::Key::Key() { sk_bzero(this, sizeof(Key)); }
35
operator ==(const GrTextBlob::Key & other) const36 bool GrTextBlob::Key::operator==(const GrTextBlob::Key& other) const {
37 return 0 == memcmp(this, &other, sizeof(Key));
38 }
39
40 // -- GrTextBlob::PathGlyph ------------------------------------------------------------------------
PathGlyph(const SkPath & path,SkPoint origin)41 GrTextBlob::PathGlyph::PathGlyph(const SkPath& path, SkPoint origin)
42 : fPath(path)
43 , fOrigin(origin) {}
44
45 // -- GrTextBlob::SubRun ---------------------------------------------------------------------------
SubRun(SubRunType type,GrTextBlob * textBlob,const SkStrikeSpec & strikeSpec,GrMaskFormat format,const SkSpan<GrGlyph * > & glyphs,const SkSpan<char> & vertexData,sk_sp<GrTextStrike> && grStrike)46 GrTextBlob::SubRun::SubRun(SubRunType type, GrTextBlob* textBlob, const SkStrikeSpec& strikeSpec,
47 GrMaskFormat format,
48 const SkSpan<GrGlyph*>& glyphs, const SkSpan<char>& vertexData,
49 sk_sp<GrTextStrike>&& grStrike)
50 : fType{type}
51 , fBlob{textBlob}
52 , fMaskFormat{format}
53 , fGlyphs{glyphs}
54 , fVertexData{vertexData}
55 , fStrikeSpec{strikeSpec}
56 , fStrike{grStrike}
57 , fCurrentColor{textBlob->fColor}
58 , fCurrentOrigin{textBlob->fInitialOrigin}
59 , fCurrentMatrix{textBlob->fInitialMatrix} {
60 SkASSERT(type != kTransformedPath);
61 textBlob->insertSubRun(this);
62 }
63
SubRun(GrTextBlob * textBlob,const SkStrikeSpec & strikeSpec)64 GrTextBlob::SubRun::SubRun(GrTextBlob* textBlob, const SkStrikeSpec& strikeSpec)
65 : fType{kTransformedPath}
66 , fBlob{textBlob}
67 , fMaskFormat{kA8_GrMaskFormat}
68 , fGlyphs{SkSpan<GrGlyph*>{}}
69 , fVertexData{SkSpan<char>{}}
70 , fStrikeSpec{strikeSpec}
71 , fStrike{nullptr}
72 , fCurrentColor{textBlob->fColor}
73 , fPaths{} {
74 textBlob->insertSubRun(this);
75 }
76
appendGlyphs(const SkZip<SkGlyphVariant,SkPoint> & drawables)77 void GrTextBlob::SubRun::appendGlyphs(const SkZip<SkGlyphVariant, SkPoint>& drawables) {
78 GrTextStrike* grStrike = fStrike.get();
79 SkScalar strikeToSource = fStrikeSpec.strikeToSourceRatio();
80 GrGlyph** glyphCursor = fGlyphs.data();
81 char* vertexCursor = fVertexData.data();
82 size_t vertexStride = this->vertexStride();
83 // We always write the third position component used by SDFs. If it is unused it gets
84 // overwritten. Similarly, we always write the color and the blob will later overwrite it
85 // with texture coords if it is unused.
86 size_t colorOffset = this->colorOffset();
87 for (auto [variant, pos] : drawables) {
88 SkGlyph* skGlyph = variant;
89 GrGlyph* grGlyph = grStrike->getGlyph(*skGlyph);
90 // Only floor the device coordinates.
91 SkRect dstRect;
92 if (!this->needsTransform()) {
93 pos = {SkScalarFloorToScalar(pos.x()), SkScalarFloorToScalar(pos.y())};
94 dstRect = grGlyph->destRect(pos);
95 } else {
96 dstRect = grGlyph->destRect(pos, strikeToSource);
97 }
98
99 this->joinGlyphBounds(dstRect);
100
101 // V0
102 *reinterpret_cast<SkPoint3*>(vertexCursor) = {dstRect.fLeft, dstRect.fTop, 1.f};
103 *reinterpret_cast<GrColor*>(vertexCursor + colorOffset) = fCurrentColor;
104 vertexCursor += vertexStride;
105
106 // V1
107 *reinterpret_cast<SkPoint3*>(vertexCursor) = {dstRect.fLeft, dstRect.fBottom, 1.f};
108 *reinterpret_cast<GrColor*>(vertexCursor + colorOffset) = fCurrentColor;
109 vertexCursor += vertexStride;
110
111 // V2
112 *reinterpret_cast<SkPoint3*>(vertexCursor) = {dstRect.fRight, dstRect.fTop, 1.f};
113 *reinterpret_cast<GrColor*>(vertexCursor + colorOffset) = fCurrentColor;
114 vertexCursor += vertexStride;
115
116 // V3
117 *reinterpret_cast<SkPoint3*>(vertexCursor) = {dstRect.fRight, dstRect.fBottom, 1.f};
118 *reinterpret_cast<GrColor*>(vertexCursor + colorOffset) = fCurrentColor;
119 vertexCursor += vertexStride;
120
121 *glyphCursor++ = grGlyph;
122 }
123 }
124
resetBulkUseToken()125 void GrTextBlob::SubRun::resetBulkUseToken() { fBulkUseToken.reset(); }
126
bulkUseToken()127 GrDrawOpAtlas::BulkUseTokenUpdater* GrTextBlob::SubRun::bulkUseToken() { return &fBulkUseToken; }
setStrike(sk_sp<GrTextStrike> strike)128 void GrTextBlob::SubRun::setStrike(sk_sp<GrTextStrike> strike) { fStrike = std::move(strike); }
strike() const129 GrTextStrike* GrTextBlob::SubRun::strike() const { return fStrike.get(); }
maskFormat() const130 GrMaskFormat GrTextBlob::SubRun::maskFormat() const { return fMaskFormat; }
vertexStride() const131 size_t GrTextBlob::SubRun::vertexStride() const {
132 return GetVertexStride(this->maskFormat(), this->hasW());
133 }
colorOffset() const134 size_t GrTextBlob::SubRun::colorOffset() const {
135 return this->hasW() ? offsetof(SDFT3DVertex, color) : offsetof(Mask2DVertex, color);
136 }
137
texCoordOffset() const138 size_t GrTextBlob::SubRun::texCoordOffset() const {
139 switch (fMaskFormat) {
140 case kA8_GrMaskFormat:
141 return this->hasW() ? offsetof(SDFT3DVertex, atlasPos)
142 : offsetof(Mask2DVertex, atlasPos);
143 case kARGB_GrMaskFormat:
144 return this->hasW() ? offsetof(ARGB3DVertex, atlasPos)
145 : offsetof(ARGB2DVertex, atlasPos);
146 default:
147 SkASSERT(!this->hasW());
148 return offsetof(Mask2DVertex, atlasPos);
149 }
150 }
151
quadStart(size_t index) const152 char* GrTextBlob::SubRun::quadStart(size_t index) const {
153 return SkTAddOffset<char>(fVertexData.data(), this->quadOffset(index));
154 }
155
quadOffset(size_t index) const156 size_t GrTextBlob::SubRun::quadOffset(size_t index) const {
157 return index * kVerticesPerGlyph * this->vertexStride();
158 }
159
vertexBounds() const160 const SkRect& GrTextBlob::SubRun::vertexBounds() const { return fVertexBounds; }
joinGlyphBounds(const SkRect & glyphBounds)161 void GrTextBlob::SubRun::joinGlyphBounds(const SkRect& glyphBounds) {
162 fVertexBounds.joinNonEmptyArg(glyphBounds);
163 }
164
drawAsDistanceFields() const165 bool GrTextBlob::SubRun::drawAsDistanceFields() const { return fType == kTransformedSDFT; }
166
drawAsPaths() const167 bool GrTextBlob::SubRun::drawAsPaths() const { return fType == kTransformedPath; }
168
needsTransform() const169 bool GrTextBlob::SubRun::needsTransform() const {
170 return fType == kTransformedPath
171 || fType == kTransformedMask
172 || fType == kTransformedSDFT;
173 }
174
hasW() const175 bool GrTextBlob::SubRun::hasW() const {
176 return fBlob->hasW(fType);
177 }
178
translateVerticesIfNeeded(const SkMatrix & drawMatrix,SkPoint drawOrigin)179 void GrTextBlob::SubRun::translateVerticesIfNeeded(
180 const SkMatrix& drawMatrix, SkPoint drawOrigin) {
181 SkVector translation;
182 if (this->needsTransform()) {
183 // If transform is needed, then the vertices are in source space, calculate the source
184 // space translation.
185 translation = drawOrigin - fCurrentOrigin;
186 } else {
187 // Calculate the translation in source space to a translation in device space. Calculate
188 // the translation by mapping (0, 0) through both the current matrix, and the draw
189 // matrix, and taking the difference.
190 SkMatrix currentMatrix{fCurrentMatrix};
191 currentMatrix.preTranslate(fCurrentOrigin.x(), fCurrentOrigin.y());
192 SkPoint currentDeviceOrigin{0, 0};
193 currentMatrix.mapPoints(¤tDeviceOrigin, 1);
194 SkMatrix completeDrawMatrix{drawMatrix};
195 completeDrawMatrix.preTranslate(drawOrigin.x(), drawOrigin.y());
196 SkPoint drawDeviceOrigin{0, 0};
197 completeDrawMatrix.mapPoints(&drawDeviceOrigin, 1);
198 translation = drawDeviceOrigin - currentDeviceOrigin;
199 }
200
201 if (translation != SkPoint{0, 0}) {
202 size_t vertexStride = this->vertexStride();
203 for (size_t quad = 0; quad < fGlyphs.size(); quad++) {
204 SkPoint* vertexCursor = reinterpret_cast<SkPoint*>(quadStart(quad));
205 for (int i = 0; i < 4; ++i) {
206 *vertexCursor += translation;
207 vertexCursor = SkTAddOffset<SkPoint>(vertexCursor, vertexStride);
208 }
209 }
210 fCurrentMatrix = drawMatrix;
211 fCurrentOrigin = drawOrigin;
212 }
213 }
214
updateVerticesColorIfNeeded(GrColor newColor)215 void GrTextBlob::SubRun::updateVerticesColorIfNeeded(GrColor newColor) {
216 if (this->maskFormat() != kARGB_GrMaskFormat && fCurrentColor != newColor) {
217 size_t vertexStride = this->vertexStride();
218 size_t colorOffset = this->colorOffset();
219 for (size_t quad = 0; quad < fGlyphs.size(); quad++) {
220 GrColor* colorCursor = SkTAddOffset<GrColor>(quadStart(quad), colorOffset);
221 for (int i = 0; i < 4; ++i) {
222 *colorCursor = newColor;
223 colorCursor = SkTAddOffset<GrColor>(colorCursor, vertexStride);
224 }
225 }
226 this->fCurrentColor = newColor;
227 }
228 }
229
updateTexCoords(int begin,int end)230 void GrTextBlob::SubRun::updateTexCoords(int begin, int end) {
231 const size_t vertexStride = this->vertexStride();
232 const size_t texCoordOffset = this->texCoordOffset();
233 char* vertex = this->quadStart(begin);
234 uint16_t* textureCoords = reinterpret_cast<uint16_t*>(vertex + texCoordOffset);
235 for (int i = begin; i < end; i++) {
236 GrGlyph* glyph = this->fGlyphs[i];
237 SkASSERT(glyph != nullptr);
238
239 int width = glyph->fBounds.width();
240 int height = glyph->fBounds.height();
241 uint16_t u0, v0, u1, v1;
242 if (this->drawAsDistanceFields()) {
243 u0 = glyph->fAtlasLocation.fX + SK_DistanceFieldInset;
244 v0 = glyph->fAtlasLocation.fY + SK_DistanceFieldInset;
245 u1 = u0 + width - 2 * SK_DistanceFieldInset;
246 v1 = v0 + height - 2 * SK_DistanceFieldInset;
247 } else {
248 u0 = glyph->fAtlasLocation.fX;
249 v0 = glyph->fAtlasLocation.fY;
250 u1 = u0 + width;
251 v1 = v0 + height;
252 }
253
254 // We pack the 2bit page index in the low bit of the u and v texture coords
255 uint32_t pageIndex = glyph->pageIndex();
256 std::tie(u0, v0) = GrDrawOpAtlas::PackIndexInTexCoords(u0, v0, pageIndex);
257 std::tie(u1, v1) = GrDrawOpAtlas::PackIndexInTexCoords(u1, v1, pageIndex);
258
259 textureCoords[0] = u0;
260 textureCoords[1] = v0;
261 textureCoords = SkTAddOffset<uint16_t>(textureCoords, vertexStride);
262 textureCoords[0] = u0;
263 textureCoords[1] = v1;
264 textureCoords = SkTAddOffset<uint16_t>(textureCoords, vertexStride);
265 textureCoords[0] = u1;
266 textureCoords[1] = v0;
267 textureCoords = SkTAddOffset<uint16_t>(textureCoords, vertexStride);
268 textureCoords[0] = u1;
269 textureCoords[1] = v1;
270 textureCoords = SkTAddOffset<uint16_t>(textureCoords, vertexStride);
271 }
272 }
273
274
setUseLCDText(bool useLCDText)275 void GrTextBlob::SubRun::setUseLCDText(bool useLCDText) { fFlags.useLCDText = useLCDText; }
hasUseLCDText() const276 bool GrTextBlob::SubRun::hasUseLCDText() const { return fFlags.useLCDText; }
setAntiAliased(bool antiAliased)277 void GrTextBlob::SubRun::setAntiAliased(bool antiAliased) { fFlags.antiAliased = antiAliased; }
isAntiAliased() const278 bool GrTextBlob::SubRun::isAntiAliased() const { return fFlags.antiAliased; }
strikeSpec() const279 const SkStrikeSpec& GrTextBlob::SubRun::strikeSpec() const { return fStrikeSpec; }
280
281 // -- GrTextBlob -----------------------------------------------------------------------------------
operator delete(void * p)282 void GrTextBlob::operator delete(void* p) { ::operator delete(p); }
operator new(size_t)283 void* GrTextBlob::operator new(size_t) { SK_ABORT("All blobs are created by placement new."); }
operator new(size_t,void * p)284 void* GrTextBlob::operator new(size_t, void* p) { return p; }
285
286 GrTextBlob::~GrTextBlob() = default;
287
Make(const SkGlyphRunList & glyphRunList,GrStrikeCache * strikeCache,const SkMatrix & drawMatrix,GrColor color,bool forceWForDistanceFields)288 sk_sp<GrTextBlob> GrTextBlob::Make(const SkGlyphRunList& glyphRunList,
289 GrStrikeCache* strikeCache,
290 const SkMatrix& drawMatrix,
291 GrColor color,
292 bool forceWForDistanceFields) {
293
294 static_assert(sizeof(ARGB2DVertex) <= sizeof(Mask2DVertex));
295 static_assert(alignof(ARGB2DVertex) <= alignof(Mask2DVertex));
296 size_t quadSize = sizeof(Mask2DVertex) * kVerticesPerGlyph;
297 if (drawMatrix.hasPerspective() || forceWForDistanceFields) {
298 static_assert(sizeof(ARGB3DVertex) <= sizeof(SDFT3DVertex));
299 static_assert(alignof(ARGB3DVertex) <= alignof(SDFT3DVertex));
300 quadSize = sizeof(SDFT3DVertex) * kVerticesPerGlyph;
301 }
302
303 // We can use the alignment of SDFT3DVertex as a proxy for all Vertex alignments.
304 static_assert(alignof(SDFT3DVertex) >= alignof(Mask2DVertex));
305 // Assume there is no padding needed between glyph pointers and vertices.
306 static_assert(alignof(GrGlyph*) >= alignof(SDFT3DVertex));
307
308 // In the arena, the layout is GrGlyph*... | SDFT3DVertex... | SubRun, so there is no padding
309 // between GrGlyph* and SDFT3DVertex, but padding is needed between the Mask2DVertex array
310 // and the SubRun.
311 size_t vertexToSubRunPadding = alignof(SDFT3DVertex) - alignof(SubRun);
312 size_t arenaSize =
313 sizeof(GrGlyph*) * glyphRunList.totalGlyphCount()
314 + quadSize * glyphRunList.totalGlyphCount()
315 + glyphRunList.runCount() * (sizeof(SubRun) + vertexToSubRunPadding);
316
317 size_t allocationSize = sizeof(GrTextBlob) + arenaSize;
318
319 void* allocation = ::operator new (allocationSize);
320
321 SkColor initialLuminance = SkPaintPriv::ComputeLuminanceColor(glyphRunList.paint());
322 sk_sp<GrTextBlob> blob{new (allocation) GrTextBlob{
323 arenaSize, strikeCache, drawMatrix, glyphRunList.origin(),
324 color, initialLuminance, forceWForDistanceFields}};
325
326 return blob;
327 }
328
setupKey(const GrTextBlob::Key & key,const SkMaskFilterBase::BlurRec & blurRec,const SkPaint & paint)329 void GrTextBlob::setupKey(const GrTextBlob::Key& key, const SkMaskFilterBase::BlurRec& blurRec,
330 const SkPaint& paint) {
331 fKey = key;
332 if (key.fHasBlur) {
333 fBlurRec = blurRec;
334 }
335 if (key.fStyle != SkPaint::kFill_Style) {
336 fStrokeInfo.fFrameWidth = paint.getStrokeWidth();
337 fStrokeInfo.fMiterLimit = paint.getStrokeMiter();
338 fStrokeInfo.fJoin = paint.getStrokeJoin();
339 }
340 }
GetKey(const GrTextBlob & blob)341 const GrTextBlob::Key& GrTextBlob::GetKey(const GrTextBlob& blob) { return blob.fKey; }
Hash(const GrTextBlob::Key & key)342 uint32_t GrTextBlob::Hash(const GrTextBlob::Key& key) { return SkOpts::hash(&key, sizeof(Key)); }
343
hasDistanceField() const344 bool GrTextBlob::hasDistanceField() const {
345 return SkToBool(fTextType & kHasDistanceField_TextType);
346 }
hasBitmap() const347 bool GrTextBlob::hasBitmap() const { return SkToBool(fTextType & kHasBitmap_TextType); }
hasPerspective() const348 bool GrTextBlob::hasPerspective() const { return fInitialMatrix.hasPerspective(); }
349
setHasDistanceField()350 void GrTextBlob::setHasDistanceField() { fTextType |= kHasDistanceField_TextType; }
setHasBitmap()351 void GrTextBlob::setHasBitmap() { fTextType |= kHasBitmap_TextType; }
setMinAndMaxScale(SkScalar scaledMin,SkScalar scaledMax)352 void GrTextBlob::setMinAndMaxScale(SkScalar scaledMin, SkScalar scaledMax) {
353 // we init fMaxMinScale and fMinMaxScale in the constructor
354 fMaxMinScale = std::max(scaledMin, fMaxMinScale);
355 fMinMaxScale = std::min(scaledMax, fMinMaxScale);
356 }
357
GetVertexStride(GrMaskFormat maskFormat,bool hasWCoord)358 size_t GrTextBlob::GetVertexStride(GrMaskFormat maskFormat, bool hasWCoord) {
359 switch (maskFormat) {
360 case kA8_GrMaskFormat:
361 return hasWCoord ? sizeof(SDFT3DVertex) : sizeof(Mask2DVertex);
362 case kARGB_GrMaskFormat:
363 return hasWCoord ? sizeof(ARGB3DVertex) : sizeof(ARGB2DVertex);
364 default:
365 SkASSERT(!hasWCoord);
366 return sizeof(Mask2DVertex);
367 }
368 }
369
mustRegenerate(const SkPaint & paint,bool anyRunHasSubpixelPosition,const SkMaskFilterBase::BlurRec & blurRec,const SkMatrix & drawMatrix,SkPoint drawOrigin)370 bool GrTextBlob::mustRegenerate(const SkPaint& paint, bool anyRunHasSubpixelPosition,
371 const SkMaskFilterBase::BlurRec& blurRec,
372 const SkMatrix& drawMatrix, SkPoint drawOrigin) {
373 // If we have LCD text then our canonical color will be set to transparent, in this case we have
374 // to regenerate the blob on any color change
375 // We use the grPaint to get any color filter effects
376 if (fKey.fCanonicalColor == SK_ColorTRANSPARENT &&
377 fInitialLuminance != SkPaintPriv::ComputeLuminanceColor(paint)) {
378 return true;
379 }
380
381 if (fInitialMatrix.hasPerspective() != drawMatrix.hasPerspective()) {
382 return true;
383 }
384
385 /** This could be relaxed for blobs with only distance field glyphs. */
386 if (fInitialMatrix.hasPerspective() && !SkMatrixPriv::CheapEqual(fInitialMatrix, drawMatrix)) {
387 return true;
388 }
389
390 // We only cache one masked version
391 if (fKey.fHasBlur &&
392 (fBlurRec.fSigma != blurRec.fSigma || fBlurRec.fStyle != blurRec.fStyle)) {
393 return true;
394 }
395
396 // Similarly, we only cache one version for each style
397 if (fKey.fStyle != SkPaint::kFill_Style &&
398 (fStrokeInfo.fFrameWidth != paint.getStrokeWidth() ||
399 fStrokeInfo.fMiterLimit != paint.getStrokeMiter() ||
400 fStrokeInfo.fJoin != paint.getStrokeJoin())) {
401 return true;
402 }
403
404 // Mixed blobs must be regenerated. We could probably figure out a way to do integer scrolls
405 // for mixed blobs if this becomes an issue.
406 if (this->hasBitmap() && this->hasDistanceField()) {
407 // Identical view matrices and we can reuse in all cases
408 return !(SkMatrixPriv::CheapEqual(fInitialMatrix, drawMatrix) &&
409 drawOrigin == fInitialOrigin);
410 }
411
412 if (this->hasBitmap()) {
413 if (fInitialMatrix.getScaleX() != drawMatrix.getScaleX() ||
414 fInitialMatrix.getScaleY() != drawMatrix.getScaleY() ||
415 fInitialMatrix.getSkewX() != drawMatrix.getSkewX() ||
416 fInitialMatrix.getSkewY() != drawMatrix.getSkewY()) {
417 return true;
418 }
419
420 // TODO(herb): this is not needed for full pixel glyph choice, but is needed to adjust
421 // the quads properly. Devise a system that regenerates the quads from original data
422 // using the transform to allow this to be used in general.
423
424 // We can update the positions in the text blob without regenerating the whole
425 // blob, but only for integer translations.
426 // Calculate the translation in source space to a translation in device space by mapping
427 // (0, 0) through both the initial matrix and the draw matrix; take the difference.
428 SkMatrix initialMatrix{fInitialMatrix};
429 initialMatrix.preTranslate(fInitialOrigin.x(), fInitialOrigin.y());
430 SkPoint initialDeviceOrigin{0, 0};
431 initialMatrix.mapPoints(&initialDeviceOrigin, 1);
432 SkMatrix completeDrawMatrix{drawMatrix};
433 completeDrawMatrix.preTranslate(drawOrigin.x(), drawOrigin.y());
434 SkPoint drawDeviceOrigin{0, 0};
435 completeDrawMatrix.mapPoints(&drawDeviceOrigin, 1);
436 SkPoint translation = drawDeviceOrigin - initialDeviceOrigin;
437
438 if (!SkScalarIsInt(translation.x()) || !SkScalarIsInt(translation.y())) {
439 return true;
440 }
441 } else if (this->hasDistanceField()) {
442 // A scale outside of [blob.fMaxMinScale, blob.fMinMaxScale] would result in a different
443 // distance field being generated, so we have to regenerate in those cases
444 SkScalar newMaxScale = drawMatrix.getMaxScale();
445 SkScalar oldMaxScale = fInitialMatrix.getMaxScale();
446 SkScalar scaleAdjust = newMaxScale / oldMaxScale;
447 if (scaleAdjust < fMaxMinScale || scaleAdjust > fMinMaxScale) {
448 return true;
449 }
450 }
451
452 // It is possible that a blob has neither distanceField nor bitmaptext. This is in the case
453 // when all of the runs inside the blob are drawn as paths. In this case, we always regenerate
454 // the blob anyways at flush time, so no need to regenerate explicitly
455 return false;
456 }
457
flush(GrTextTarget * target,const SkSurfaceProps & props,const GrDistanceFieldAdjustTable * distanceAdjustTable,const SkPaint & paint,const SkPMColor4f & filteredColor,const GrClip & clip,const SkMatrix & drawMatrix,SkPoint drawOrigin)458 void GrTextBlob::flush(GrTextTarget* target, const SkSurfaceProps& props,
459 const GrDistanceFieldAdjustTable* distanceAdjustTable,
460 const SkPaint& paint, const SkPMColor4f& filteredColor, const GrClip& clip,
461 const SkMatrix& drawMatrix, SkPoint drawOrigin) {
462
463 for (SubRun* subRun = fFirstSubRun; subRun != nullptr; subRun = subRun->fNextSubRun) {
464 if (subRun->drawAsPaths()) {
465 SkPaint runPaint{paint};
466 runPaint.setAntiAlias(subRun->isAntiAliased());
467 // If there are shaders, blurs or styles, the path must be scaled into source
468 // space independently of the CTM. This allows the CTM to be correct for the
469 // different effects.
470 GrStyle style(runPaint);
471
472 bool scalePath = runPaint.getShader()
473 || style.applies()
474 || runPaint.getMaskFilter();
475
476
477 for (const auto& pathGlyph : subRun->fPaths) {
478 SkMatrix ctm{drawMatrix};
479 ctm.preTranslate(drawOrigin.x(), drawOrigin.y());
480 SkMatrix pathMatrix = SkMatrix::MakeScale(
481 subRun->fStrikeSpec.strikeToSourceRatio());
482 pathMatrix.postTranslate(pathGlyph.fOrigin.x(), pathGlyph.fOrigin.y());
483
484 // TmpPath must be in the same scope as GrShape shape below.
485 SkTLazy<SkPath> tmpPath;
486 const SkPath* path = &pathGlyph.fPath;
487 if (!scalePath) {
488 // Scale can be applied to CTM -- no effects.
489 ctm.preConcat(pathMatrix);
490 } else {
491 // Scale the outline into source space.
492
493 // Transform the path form the normalized outline to source space. This
494 // way the CTM will remain the same so it can be used by the effects.
495 SkPath* sourceOutline = tmpPath.init();
496 path->transform(pathMatrix, sourceOutline);
497 sourceOutline->setIsVolatile(true);
498 path = sourceOutline;
499 }
500
501 // TODO: we are losing the mutability of the path here
502 GrShape shape(*path, paint);
503 target->drawShape(clip, runPaint, ctm, shape);
504 }
505 } else {
506 int glyphCount = subRun->fGlyphs.size();
507 if (0 == glyphCount) {
508 continue;
509 }
510
511 bool skipClip = false;
512 bool submitOp = true;
513 SkIRect clipRect = SkIRect::MakeEmpty();
514 SkRect rtBounds = SkRect::MakeWH(target->width(), target->height());
515 SkRRect clipRRect;
516 GrAA aa;
517 // We can clip geometrically if we're not using SDFs or transformed glyphs,
518 // and we have an axis-aligned rectangular non-AA clip
519 if (!subRun->drawAsDistanceFields() && !subRun->needsTransform() &&
520 clip.isRRect(rtBounds, &clipRRect, &aa) &&
521 clipRRect.isRect() && GrAA::kNo == aa) {
522 skipClip = true;
523 // We only need to do clipping work if the subrun isn't contained by the clip
524 SkRect subRunBounds;
525 this->computeSubRunBounds(
526 &subRunBounds, *subRun, drawMatrix, drawOrigin, false);
527 if (!clipRRect.getBounds().contains(subRunBounds)) {
528 // If the subrun is completely outside, don't add an op for it
529 if (!clipRRect.getBounds().intersects(subRunBounds)) {
530 submitOp = false;
531 }
532 else {
533 clipRRect.getBounds().round(&clipRect);
534 }
535 }
536 }
537
538 if (submitOp) {
539 auto op = this->makeOp(*subRun, glyphCount, drawMatrix, drawOrigin,
540 clipRect, paint, filteredColor, props, distanceAdjustTable,
541 target);
542 if (op) {
543 if (skipClip) {
544 target->addDrawOp(GrNoClip(), std::move(op));
545 }
546 else {
547 target->addDrawOp(clip, std::move(op));
548 }
549 }
550 }
551 }
552 }
553 }
554
computeSubRunBounds(SkRect * outBounds,const SubRun & subRun,const SkMatrix & drawMatrix,SkPoint drawOrigin,bool needsGlyphTransform)555 void GrTextBlob::computeSubRunBounds(SkRect* outBounds, const SubRun& subRun,
556 const SkMatrix& drawMatrix, SkPoint drawOrigin,
557 bool needsGlyphTransform) {
558 // We don't yet position distance field text on the cpu, so we have to map the vertex bounds
559 // into device space.
560 // We handle vertex bounds differently for distance field text and bitmap text because
561 // the vertex bounds of bitmap text are in device space. If we are flushing multiple runs
562 // from one blob then we are going to pay the price here of mapping the rect for each run.
563 *outBounds = subRun.vertexBounds();
564 if (needsGlyphTransform) {
565 // Distance field text is positioned with the (X,Y) as part of the glyph position,
566 // and currently the view matrix is applied on the GPU
567 outBounds->offset(drawOrigin - fInitialOrigin);
568 drawMatrix.mapRect(outBounds);
569 } else {
570 // Bitmap text is fully positioned on the CPU, and offset by an (X,Y) translate in
571 // device space.
572 SkMatrix boundsMatrix = fInitialMatrixInverse;
573
574 boundsMatrix.postTranslate(-fInitialOrigin.x(), -fInitialOrigin.y());
575
576 boundsMatrix.postTranslate(drawOrigin.x(), drawOrigin.y());
577
578 boundsMatrix.postConcat(drawMatrix);
579 boundsMatrix.mapRect(outBounds);
580
581 // Due to floating point numerical inaccuracies, we have to round out here
582 outBounds->roundOut(outBounds);
583 }
584 }
585
key() const586 const GrTextBlob::Key& GrTextBlob::key() const { return fKey; }
size() const587 size_t GrTextBlob::size() const { return fSize; }
588
test_makeOp(int glyphCount,const SkMatrix & drawMatrix,SkPoint drawOrigin,const SkPaint & paint,const SkPMColor4f & filteredColor,const SkSurfaceProps & props,const GrDistanceFieldAdjustTable * distanceAdjustTable,GrTextTarget * target)589 std::unique_ptr<GrDrawOp> GrTextBlob::test_makeOp(
590 int glyphCount, const SkMatrix& drawMatrix,
591 SkPoint drawOrigin, const SkPaint& paint, const SkPMColor4f& filteredColor,
592 const SkSurfaceProps& props, const GrDistanceFieldAdjustTable* distanceAdjustTable,
593 GrTextTarget* target) {
594 SubRun* info = fFirstSubRun;
595 SkIRect emptyRect = SkIRect::MakeEmpty();
596 return this->makeOp(*info, glyphCount, drawMatrix, drawOrigin, emptyRect,
597 paint, filteredColor, props, distanceAdjustTable, target);
598 }
599
hasW(GrTextBlob::SubRunType type) const600 bool GrTextBlob::hasW(GrTextBlob::SubRunType type) const {
601 if (type == kTransformedSDFT) {
602 return this->hasPerspective() || fForceWForDistanceFields;
603 } else if (type == kTransformedMask || type == kTransformedPath) {
604 return this->hasPerspective();
605 }
606
607 // The viewMatrix is implicitly SkMatrix::I when drawing kDirectMask, because it is not
608 // used.
609 return false;
610 }
611
makeSubRun(SubRunType type,const SkZip<SkGlyphVariant,SkPoint> & drawables,const SkStrikeSpec & strikeSpec,GrMaskFormat format)612 GrTextBlob::SubRun* GrTextBlob::makeSubRun(SubRunType type,
613 const SkZip<SkGlyphVariant, SkPoint>& drawables,
614 const SkStrikeSpec& strikeSpec,
615 GrMaskFormat format) {
616 SkSpan<GrGlyph*> glyphs{fAlloc.makeArrayDefault<GrGlyph*>(drawables.size()), drawables.size()};
617 bool hasW = this->hasW(type);
618
619 SkASSERT(!fInitialMatrix.hasPerspective() || hasW);
620
621 size_t vertexDataSize = drawables.size() * GetVertexStride(format, hasW) * kVerticesPerGlyph;
622 SkSpan<char> vertexData{fAlloc.makeArrayDefault<char>(vertexDataSize), vertexDataSize};
623
624 sk_sp<GrTextStrike> grStrike = strikeSpec.findOrCreateGrStrike(fStrikeCache);
625
626 SubRun* subRun = fAlloc.make<SubRun>(
627 type, this, strikeSpec, format, glyphs, vertexData, std::move(grStrike));
628
629 subRun->appendGlyphs(drawables);
630
631 return subRun;
632 }
633
addSingleMaskFormat(SubRunType type,const SkZip<SkGlyphVariant,SkPoint> & drawables,const SkStrikeSpec & strikeSpec,GrMaskFormat format)634 void GrTextBlob::addSingleMaskFormat(
635 SubRunType type,
636 const SkZip<SkGlyphVariant, SkPoint>& drawables,
637 const SkStrikeSpec& strikeSpec,
638 GrMaskFormat format) {
639 this->makeSubRun(type, drawables, strikeSpec, format);
640 }
641
addMultiMaskFormat(SubRunType type,const SkZip<SkGlyphVariant,SkPoint> & drawables,const SkStrikeSpec & strikeSpec)642 void GrTextBlob::addMultiMaskFormat(
643 SubRunType type,
644 const SkZip<SkGlyphVariant, SkPoint>& drawables,
645 const SkStrikeSpec& strikeSpec) {
646 this->setHasBitmap();
647 if (drawables.empty()) { return; }
648
649 auto glyphSpan = drawables.get<0>();
650 SkGlyph* glyph = glyphSpan[0];
651 GrMaskFormat format = GrGlyph::FormatFromSkGlyph(glyph->maskFormat());
652 size_t startIndex = 0;
653 for (size_t i = 1; i < drawables.size(); i++) {
654 glyph = glyphSpan[i];
655 GrMaskFormat nextFormat = GrGlyph::FormatFromSkGlyph(glyph->maskFormat());
656 if (format != nextFormat) {
657 auto sameFormat = drawables.subspan(startIndex, i - startIndex);
658 this->addSingleMaskFormat(type, sameFormat, strikeSpec, format);
659 format = nextFormat;
660 startIndex = i;
661 }
662 }
663 auto sameFormat = drawables.last(drawables.size() - startIndex);
664 this->addSingleMaskFormat(type, sameFormat, strikeSpec, format);
665 }
666
addSDFT(const SkZip<SkGlyphVariant,SkPoint> & drawables,const SkStrikeSpec & strikeSpec,const SkFont & runFont,SkScalar minScale,SkScalar maxScale)667 void GrTextBlob::addSDFT(const SkZip<SkGlyphVariant, SkPoint>& drawables,
668 const SkStrikeSpec& strikeSpec,
669 const SkFont& runFont,
670 SkScalar minScale,
671 SkScalar maxScale) {
672 this->setHasDistanceField();
673 this->setMinAndMaxScale(minScale, maxScale);
674
675 SubRun* subRun = this->makeSubRun(kTransformedSDFT, drawables, strikeSpec, kA8_GrMaskFormat);
676 subRun->setUseLCDText(runFont.getEdging() == SkFont::Edging::kSubpixelAntiAlias);
677 subRun->setAntiAliased(runFont.hasSomeAntiAliasing());
678 }
679
GrTextBlob(size_t allocSize,GrStrikeCache * strikeCache,const SkMatrix & drawMatrix,SkPoint origin,GrColor color,SkColor initialLuminance,bool forceWForDistanceFields)680 GrTextBlob::GrTextBlob(size_t allocSize,
681 GrStrikeCache* strikeCache,
682 const SkMatrix& drawMatrix,
683 SkPoint origin,
684 GrColor color,
685 SkColor initialLuminance,
686 bool forceWForDistanceFields)
687 : fSize{allocSize}
688 , fStrikeCache{strikeCache}
689 , fInitialMatrix{drawMatrix}
690 , fInitialMatrixInverse{make_inverse(drawMatrix)}
691 , fInitialOrigin{origin}
692 , fForceWForDistanceFields{forceWForDistanceFields}
693 , fColor{color}
694 , fInitialLuminance{initialLuminance}
695 , fAlloc{SkTAddOffset<char>(this, sizeof(GrTextBlob)), allocSize, allocSize/2} { }
696
insertSubRun(SubRun * subRun)697 void GrTextBlob::insertSubRun(SubRun* subRun) {
698 if (fFirstSubRun == nullptr) {
699 fFirstSubRun = subRun;
700 fLastSubRun = subRun;
701 } else {
702 fLastSubRun->fNextSubRun = subRun;
703 fLastSubRun = subRun;
704 }
705 }
706
makeOp(SubRun & info,int glyphCount,const SkMatrix & drawMatrix,SkPoint drawOrigin,const SkIRect & clipRect,const SkPaint & paint,const SkPMColor4f & filteredColor,const SkSurfaceProps & props,const GrDistanceFieldAdjustTable * distanceAdjustTable,GrTextTarget * target)707 std::unique_ptr<GrAtlasTextOp> GrTextBlob::makeOp(
708 SubRun& info, int glyphCount,
709 const SkMatrix& drawMatrix, SkPoint drawOrigin, const SkIRect& clipRect,
710 const SkPaint& paint, const SkPMColor4f& filteredColor, const SkSurfaceProps& props,
711 const GrDistanceFieldAdjustTable* distanceAdjustTable, GrTextTarget* target) {
712 GrMaskFormat format = info.maskFormat();
713
714 GrPaint grPaint;
715 target->makeGrPaint(info.maskFormat(), paint, drawMatrix, &grPaint);
716 std::unique_ptr<GrAtlasTextOp> op;
717 if (info.drawAsDistanceFields()) {
718 // TODO: Can we be even smarter based on the dest transfer function?
719 op = GrAtlasTextOp::MakeDistanceField(
720 target->getContext(), std::move(grPaint), glyphCount, distanceAdjustTable,
721 target->colorInfo().isLinearlyBlended(), SkPaintPriv::ComputeLuminanceColor(paint),
722 props, info.isAntiAliased(), info.hasUseLCDText());
723 } else {
724 op = GrAtlasTextOp::MakeBitmap(target->getContext(), std::move(grPaint), format, glyphCount,
725 info.needsTransform());
726 }
727 GrAtlasTextOp::Geometry& geometry = op->geometry();
728 geometry.fDrawMatrix = drawMatrix;
729 geometry.fClipRect = clipRect;
730 geometry.fBlob = SkRef(this);
731 geometry.fSubRunPtr = &info;
732 geometry.fColor = info.maskFormat() == kARGB_GrMaskFormat ? SK_PMColor4fWHITE : filteredColor;
733 geometry.fDrawOrigin = drawOrigin;
734 op->init();
735 return op;
736 }
737
processDeviceMasks(const SkZip<SkGlyphVariant,SkPoint> & drawables,const SkStrikeSpec & strikeSpec)738 void GrTextBlob::processDeviceMasks(const SkZip<SkGlyphVariant, SkPoint>& drawables,
739 const SkStrikeSpec& strikeSpec) {
740 this->addMultiMaskFormat(kDirectMask, drawables, strikeSpec);
741 }
742
processSourcePaths(const SkZip<SkGlyphVariant,SkPoint> & drawables,const SkFont & runFont,const SkStrikeSpec & strikeSpec)743 void GrTextBlob::processSourcePaths(const SkZip<SkGlyphVariant, SkPoint>& drawables,
744 const SkFont& runFont,
745 const SkStrikeSpec& strikeSpec) {
746 this->setHasBitmap();
747 SubRun* subRun = fAlloc.make<SubRun>(this, strikeSpec);
748 subRun->setAntiAliased(runFont.hasSomeAntiAliasing());
749 for (auto [variant, pos] : drawables) {
750 subRun->fPaths.emplace_back(*variant.path(), pos);
751 }
752 }
753
processSourceSDFT(const SkZip<SkGlyphVariant,SkPoint> & drawables,const SkStrikeSpec & strikeSpec,const SkFont & runFont,SkScalar minScale,SkScalar maxScale)754 void GrTextBlob::processSourceSDFT(const SkZip<SkGlyphVariant, SkPoint>& drawables,
755 const SkStrikeSpec& strikeSpec,
756 const SkFont& runFont,
757 SkScalar minScale,
758 SkScalar maxScale) {
759 this->addSDFT(drawables, strikeSpec, runFont, minScale, maxScale);
760 }
761
processSourceMasks(const SkZip<SkGlyphVariant,SkPoint> & drawables,const SkStrikeSpec & strikeSpec)762 void GrTextBlob::processSourceMasks(const SkZip<SkGlyphVariant, SkPoint>& drawables,
763 const SkStrikeSpec& strikeSpec) {
764 this->addMultiMaskFormat(kTransformedMask, drawables, strikeSpec);
765 }
766
767 // -- GrTextBlob::VertexRegenerator ----------------------------------------------------------------
VertexRegenerator(GrResourceProvider * resourceProvider,GrTextBlob::SubRun * subRun,GrDeferredUploadTarget * uploadTarget,GrAtlasManager * fullAtlasManager)768 GrTextBlob::VertexRegenerator::VertexRegenerator(GrResourceProvider* resourceProvider,
769 GrTextBlob::SubRun* subRun,
770 GrDeferredUploadTarget* uploadTarget,
771 GrAtlasManager* fullAtlasManager)
772 : fResourceProvider(resourceProvider)
773 , fUploadTarget(uploadTarget)
774 , fFullAtlasManager(fullAtlasManager)
775 , fSubRun(subRun) { }
776
updateTextureCoordinates(const int begin,const int end)777 std::tuple<bool, int> GrTextBlob::VertexRegenerator::updateTextureCoordinates(
778 const int begin, const int end) {
779
780 const SkStrikeSpec& strikeSpec = fSubRun->strikeSpec();
781
782 if (!fMetricsAndImages.isValid()
783 || fMetricsAndImages->descriptor() != strikeSpec.descriptor()) {
784 fMetricsAndImages.init(strikeSpec);
785 }
786
787 // Update the atlas information in the GrStrike.
788 auto code = GrDrawOpAtlas::ErrorCode::kSucceeded;
789 GrTextStrike* grStrike = fSubRun->strike();
790 auto tokenTracker = fUploadTarget->tokenTracker();
791 int i = begin;
792 for (; i < end; i++) {
793 GrGlyph* grGlyph = fSubRun->fGlyphs[i];
794 SkASSERT(grGlyph && grGlyph->fMaskFormat == fSubRun->maskFormat());
795
796 if (!fFullAtlasManager->hasGlyph(grGlyph)) {
797 const SkGlyph& skGlyph = *fMetricsAndImages->glyph(grGlyph->fPackedID);
798 if (skGlyph.image() == nullptr) { return {false, 0}; }
799 code = grStrike->addGlyphToAtlas(skGlyph,
800 fSubRun->maskFormat(),
801 fSubRun->needsTransform(),
802 fResourceProvider, fUploadTarget, fFullAtlasManager, grGlyph);
803 if (code != GrDrawOpAtlas::ErrorCode::kSucceeded) {
804 break;
805 }
806 }
807 fFullAtlasManager->addGlyphToBulkAndSetUseToken(
808 fSubRun->bulkUseToken(), grGlyph, tokenTracker->nextDrawToken());
809 }
810 int glyphsPlacedInAtlas = i - begin;
811
812 // Update the quads with the new atlas coordinates.
813 fSubRun->updateTexCoords(begin, begin + glyphsPlacedInAtlas);
814
815 return {code != GrDrawOpAtlas::ErrorCode::kError, glyphsPlacedInAtlas};
816 }
817
regenerate(int begin,int end)818 std::tuple<bool, int> GrTextBlob::VertexRegenerator::regenerate(int begin, int end) {
819 uint64_t currentAtlasGen = fFullAtlasManager->atlasGeneration(fSubRun->maskFormat());
820
821 if (fSubRun->fAtlasGeneration != currentAtlasGen) {
822 // Calculate the texture coordinates for the vertexes during first use (fAtlasGeneration
823 // is set to kInvalidAtlasGeneration) or the atlas has changed in subsequent calls..
824 fSubRun->resetBulkUseToken();
825 auto [success, glyphsPlacedInAtlas] = this->updateTextureCoordinates(begin, end);
826
827 // Update atlas generation if there are no more glyphs to put in the atlas.
828 if (success && begin + glyphsPlacedInAtlas == (int)fSubRun->fGlyphs.size()) {
829 // Need to get the freshest value of the atlas' generation because
830 // updateTextureCoordinates may have changed it.
831 fSubRun->fAtlasGeneration = fFullAtlasManager->atlasGeneration(fSubRun->maskFormat());
832 }
833 return {success, glyphsPlacedInAtlas};
834 } else {
835 // The atlas hasn't changed, so our texture coordinates are still valid.
836 if (end == (int)fSubRun->fGlyphs.size()) {
837 // The atlas hasn't changed and the texture coordinates are all still valid. Update
838 // all the plots used to the new use token.
839 fFullAtlasManager->setUseTokenBulk(*fSubRun->bulkUseToken(),
840 fUploadTarget->tokenTracker()->nextDrawToken(),
841 fSubRun->maskFormat());
842 }
843 return {true, end - begin};
844 }
845 }
846