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