Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
SubRunContainer.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2022 Google LLC
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
9
12#include "include/core/SkFont.h"
16#include "include/core/SkPath.h"
20#include "include/core/SkRect.h"
33#include "src/base/SkZip.h"
34#include "src/core/SkDevice.h"
37#include "src/core/SkFontPriv.h"
38#include "src/core/SkGlyph.h"
39#include "src/core/SkMask.h"
45#include "src/core/SkStrike.h"
49#include "src/gpu/AtlasTypes.h"
50#include "src/text/GlyphRun.h"
52#include "src/text/gpu/Glyph.h"
58
59#include <algorithm>
60#include <climits>
61#include <cstdint>
62#include <initializer_list>
63#include <new>
64#include <optional>
65#include <vector>
66
68
69#if defined(SK_GANESH) || defined(SK_USE_LEGACY_GANESH_TEXT_APIS)
74#include "src/gpu/ganesh/SkGr.h"
78using AtlasTextOp = skgpu::ganesh::AtlasTextOp;
79#endif // defined(SK_GANESH) || defined(SK_USE_LEGACY_GANESH_TEXT_APIS)
80
81using namespace skia_private;
82using namespace skglyph;
83
84// -- GPU Text -------------------------------------------------------------------------------------
85// Naming conventions
86// * drawMatrix - the CTM from the canvas.
87// * drawOrigin - the x, y location of the drawTextBlob call.
88// * positionMatrix - this is the combination of the drawMatrix and the drawOrigin:
89// positionMatrix = drawMatrix * TranslationMatrix(drawOrigin.x, drawOrigin.y);
90//
91// Note:
92// In order to transform Slugs, you need to set the fSupportBilerpFromGlyphAtlas on
93// GrContextOptions.
94
95namespace sktext::gpu {
96// -- SubRunStreamTag ------------------------------------------------------------------------------
98 kBad = 0, // Make this 0 to line up with errors from readInt.
100#if !defined(SK_DISABLE_SDF_TEXT)
102#endif
107};
108
109} // namespace sktext::gpu
110
112
113using namespace sktext;
114using namespace sktext::gpu;
115
116namespace {
117#if defined(SK_GANESH) || defined(SK_USE_LEGACY_GANESH_TEXT_APIS)
119 const SkPaint& paint,
120 const SkMatrix& matrix,
121 MaskFormat maskFormat,
122 GrPaint* grPaint) {
123 GrRecordingContext* rContext = sdc->recordingContext();
124 const GrColorInfo& colorInfo = sdc->colorInfo();
125 const SkSurfaceProps& props = sdc->surfaceProps();
126 if (maskFormat == MaskFormat::kARGB) {
127 SkPaintToGrPaintReplaceShader(rContext, colorInfo, paint, matrix, nullptr, props, grPaint);
128 float a = grPaint->getColor4f().fA;
129 return {a, a, a, a};
130 }
131 SkPaintToGrPaint(rContext, colorInfo, paint, matrix, props, grPaint);
132 return grPaint->getColor4f();
133}
134
135SkMatrix position_matrix(const SkMatrix& drawMatrix, SkPoint drawOrigin) {
136 SkMatrix position_matrix = drawMatrix;
137 return position_matrix.preTranslate(drawOrigin.x(), drawOrigin.y());
138}
139#endif // defined(SK_GANESH) || defined(SK_USE_LEGACY_GANESH_TEXT_APIS)
140
142 return accepted.get<0>();
143}
144
146 return accepted.get<0>();
147}
148
149template <typename U>
150SkSpan<const SkPoint> get_positions(SkZip<U, const SkPoint> accepted) {
151 return accepted.template get<1>();
152}
153
154// -- PathOpSubmitter ------------------------------------------------------------------------------
155// PathOpSubmitter holds glyph ids until ready to draw. During drawing, the glyph ids are
156// converted to SkPaths. PathOpSubmitter can only be serialized when it is holding glyph ids;
157// it can only be serialized before submitDraws has been called.
158class PathOpSubmitter {
159public:
160 PathOpSubmitter() = delete;
161 PathOpSubmitter(const PathOpSubmitter&) = delete;
162 const PathOpSubmitter& operator=(const PathOpSubmitter&) = delete;
163 PathOpSubmitter(PathOpSubmitter&& that)
164 // Transfer ownership of fIDsOrPaths from that to this.
165 : fIDsOrPaths{std::exchange(
166 const_cast<SkSpan<IDOrPath>&>(that.fIDsOrPaths), SkSpan<IDOrPath>{})}
167 , fPositions{that.fPositions}
168 , fStrikeToSourceScale{that.fStrikeToSourceScale}
169 , fIsAntiAliased{that.fIsAntiAliased}
170 , fStrikePromise{std::move(that.fStrikePromise)} {}
171 PathOpSubmitter& operator=(PathOpSubmitter&& that) {
172 this->~PathOpSubmitter();
173 new (this) PathOpSubmitter{std::move(that)};
174 return *this;
175 }
176 PathOpSubmitter(bool isAntiAliased,
177 SkScalar strikeToSourceScale,
178 SkSpan<SkPoint> positions,
179 SkSpan<IDOrPath> idsOrPaths,
180 SkStrikePromise&& strikePromise);
181
182 ~PathOpSubmitter();
183
184 static PathOpSubmitter Make(SkZip<const SkGlyphID, const SkPoint> accepted,
185 bool isAntiAliased,
186 SkScalar strikeToSourceScale,
187 SkStrikePromise&& strikePromise,
188 SubRunAllocator* alloc);
189
190 int unflattenSize() const;
191 void flatten(SkWriteBuffer& buffer) const;
192 static std::optional<PathOpSubmitter> MakeFromBuffer(SkReadBuffer& buffer,
193 SubRunAllocator* alloc,
194 const SkStrikeClient* client);
195
196 // submitDraws is not thread safe. It only occurs the single thread drawing portion of the GPU
197 // rendering.
198 void submitDraws(SkCanvas*,
199 SkPoint drawOrigin,
200 const SkPaint& paint) const;
201
202private:
203 // When PathOpSubmitter is created only the glyphIDs are needed, during the submitDraws call,
204 // the glyphIDs are converted to SkPaths.
205 const SkSpan<IDOrPath> fIDsOrPaths;
206 const SkSpan<const SkPoint> fPositions;
207 const SkScalar fStrikeToSourceScale;
208 const bool fIsAntiAliased;
209
210 mutable SkStrikePromise fStrikePromise;
211 mutable SkOnce fConvertIDsToPaths;
212 mutable bool fPathsAreCreated{false};
213};
214
215int PathOpSubmitter::unflattenSize() const {
216 return fPositions.size_bytes() + fIDsOrPaths.size_bytes();
217}
218
219void PathOpSubmitter::flatten(SkWriteBuffer& buffer) const {
220 fStrikePromise.flatten(buffer);
221
222 buffer.writeInt(fIsAntiAliased);
223 buffer.writeScalar(fStrikeToSourceScale);
224 buffer.writePointArray(fPositions.data(), SkCount(fPositions));
225 for (IDOrPath& idOrPath : fIDsOrPaths) {
226 buffer.writeInt(idOrPath.fGlyphID);
227 }
228}
229
230std::optional<PathOpSubmitter> PathOpSubmitter::MakeFromBuffer(SkReadBuffer& buffer,
231 SubRunAllocator* alloc,
232 const SkStrikeClient* client) {
233 std::optional<SkStrikePromise> strikePromise =
235 if (!buffer.validate(strikePromise.has_value())) {
236 return std::nullopt;
237 }
238
239 bool isAntiAlias = buffer.readInt();
240
241 SkScalar strikeToSourceScale = buffer.readScalar();
242 if (!buffer.validate(0 < strikeToSourceScale)) { return std::nullopt; }
243
244 SkSpan<SkPoint> positions = MakePointsFromBuffer(buffer, alloc);
245 if (positions.empty()) { return std::nullopt; }
246 const int glyphCount = SkCount(positions);
247
248 // Remember, we stored an int for glyph id.
249 if (!buffer.validateCanReadN<int>(glyphCount)) { return std::nullopt; }
250 auto idsOrPaths = SkSpan(alloc->makeUniqueArray<IDOrPath>(glyphCount).release(), glyphCount);
251 for (auto& idOrPath : idsOrPaths) {
252 idOrPath.fGlyphID = SkTo<SkGlyphID>(buffer.readInt());
253 }
254
255 if (!buffer.isValid()) { return std::nullopt; }
256
257 return PathOpSubmitter{isAntiAlias,
258 strikeToSourceScale,
259 positions,
260 idsOrPaths,
261 std::move(strikePromise.value())};
262}
263
264PathOpSubmitter::PathOpSubmitter(
265 bool isAntiAliased,
266 SkScalar strikeToSourceScale,
267 SkSpan<SkPoint> positions,
268 SkSpan<IDOrPath> idsOrPaths,
269 SkStrikePromise&& strikePromise)
270 : fIDsOrPaths{idsOrPaths}
271 , fPositions{positions}
272 , fStrikeToSourceScale{strikeToSourceScale}
273 , fIsAntiAliased{isAntiAliased}
274 , fStrikePromise{std::move(strikePromise)} {
275 SkASSERT(!fPositions.empty());
276}
277
278PathOpSubmitter::~PathOpSubmitter() {
279 // If we have converted glyph IDs to paths, then clean up the SkPaths.
280 if (fPathsAreCreated) {
281 for (auto& idOrPath : fIDsOrPaths) {
282 idOrPath.fPath.~SkPath();
283 }
284 }
285}
286
287PathOpSubmitter PathOpSubmitter::Make(SkZip<const SkGlyphID, const SkPoint> accepted,
288 bool isAntiAliased,
289 SkScalar strikeToSourceScale,
290 SkStrikePromise&& strikePromise,
291 SubRunAllocator* alloc) {
292 auto mapToIDOrPath = [](SkGlyphID glyphID) { return IDOrPath{glyphID}; };
293
294 IDOrPath* const rawIDsOrPaths =
295 alloc->makeUniqueArray<IDOrPath>(get_glyphIDs(accepted), mapToIDOrPath).release();
296
297 return PathOpSubmitter{isAntiAliased,
298 strikeToSourceScale,
299 alloc->makePODSpan(get_positions(accepted)),
300 SkSpan(rawIDsOrPaths, accepted.size()),
301 std::move(strikePromise)};
302}
303
304void
305PathOpSubmitter::submitDraws(SkCanvas* canvas, SkPoint drawOrigin, const SkPaint& paint) const {
306 // Convert the glyph IDs to paths if it hasn't been done yet. This is thread safe.
307 fConvertIDsToPaths([&]() {
308 if (SkStrike* strike = fStrikePromise.strike()) {
309 strike->glyphIDsToPaths(fIDsOrPaths);
310
311 // Drop ref to strike so that it can be purged from the cache if needed.
312 fStrikePromise.resetStrike();
313 fPathsAreCreated = true;
314 }
315 });
316
317 SkPaint runPaint{paint};
318 runPaint.setAntiAlias(fIsAntiAliased);
319
320 SkMaskFilterBase* maskFilter = as_MFB(runPaint.getMaskFilter());
321
322 // Calculate the matrix that maps the path glyphs from their size in the strike to
323 // the graphics source space.
324 SkMatrix strikeToSource = SkMatrix::Scale(fStrikeToSourceScale, fStrikeToSourceScale);
325 strikeToSource.postTranslate(drawOrigin.x(), drawOrigin.y());
326
327 // If there are shaders, non-blur mask filters or styles, the path must be scaled into source
328 // space independently of the CTM. This allows the CTM to be correct for the different effects.
329 SkStrokeRec style(runPaint);
330 bool needsExactCTM = runPaint.getShader()
331 || runPaint.getPathEffect()
332 || (!style.isFillStyle() && !style.isHairlineStyle())
333 || (maskFilter != nullptr && !maskFilter->asABlur(nullptr));
334 if (!needsExactCTM) {
336
337 // If there is a blur mask filter, then sigma needs to be adjusted to account for the
338 // scaling of fStrikeToSourceScale.
339 if (maskFilter != nullptr && maskFilter->asABlur(&blurRec)) {
340 runPaint.setMaskFilter(
341 SkMaskFilter::MakeBlur(blurRec.fStyle, blurRec.fSigma / fStrikeToSourceScale));
342 }
343 for (auto [idOrPath, pos] : SkMakeZip(fIDsOrPaths, fPositions)) {
344 // Transform the glyph to source space.
345 SkMatrix pathMatrix = strikeToSource;
346 pathMatrix.postTranslate(pos.x(), pos.y());
347
348 SkAutoCanvasRestore acr(canvas, true);
349 canvas->concat(pathMatrix);
350 canvas->drawPath(idOrPath.fPath, runPaint);
351 }
352 } else {
353 // Transform the path to device because the deviceMatrix must be unchanged to
354 // draw effect, filter or shader paths.
355 for (auto [idOrPath, pos] : SkMakeZip(fIDsOrPaths, fPositions)) {
356 // Transform the glyph to source space.
357 SkMatrix pathMatrix = strikeToSource;
358 pathMatrix.postTranslate(pos.x(), pos.y());
359
360 SkPath deviceOutline;
361 idOrPath.fPath.transform(pathMatrix, &deviceOutline);
362 deviceOutline.setIsVolatile(true);
363 canvas->drawPath(deviceOutline, runPaint);
364 }
365 }
366}
367
368// -- PathSubRun -----------------------------------------------------------------------------------
369class PathSubRun final : public SubRun {
370public:
371 PathSubRun(PathOpSubmitter&& pathDrawing) : fPathDrawing(std::move(pathDrawing)) {}
372
374 bool isAntiAliased,
375 SkScalar strikeToSourceScale,
376 SkStrikePromise&& strikePromise,
377 SubRunAllocator* alloc) {
378 return alloc->makeUnique<PathSubRun>(
379 PathOpSubmitter::Make(
380 accepted, isAntiAliased, strikeToSourceScale, std::move(strikePromise), alloc));
381 }
382
383 void draw(SkCanvas* canvas,
384 SkPoint drawOrigin,
385 const SkPaint& paint,
387 const AtlasDrawDelegate&) const override {
388 fPathDrawing.submitDraws(canvas, drawOrigin, paint);
389 }
390
391 int unflattenSize() const override;
392
393 bool canReuse(const SkPaint& paint, const SkMatrix& positionMatrix) const override {
394 return true;
395 }
396 const AtlasSubRun* testingOnly_atlasSubRun() const override { return nullptr; }
398 SubRunAllocator* alloc,
399 const SkStrikeClient* client);
400
401protected:
402 SubRunStreamTag subRunStreamTag() const override { return SubRunStreamTag::kPathStreamTag; }
403 void doFlatten(SkWriteBuffer& buffer) const override;
404
405private:
406 PathOpSubmitter fPathDrawing;
407};
408
409int PathSubRun::unflattenSize() const {
410 return sizeof(PathSubRun) + fPathDrawing.unflattenSize();
411}
412
413void PathSubRun::doFlatten(SkWriteBuffer& buffer) const {
414 fPathDrawing.flatten(buffer);
415}
416
417SubRunOwner PathSubRun::MakeFromBuffer(SkReadBuffer& buffer,
418 SubRunAllocator* alloc,
419 const SkStrikeClient* client) {
420 auto pathOpSubmitter = PathOpSubmitter::MakeFromBuffer(buffer, alloc, client);
421 if (!buffer.validate(pathOpSubmitter.has_value())) { return nullptr; }
422 return alloc->makeUnique<PathSubRun>(std::move(*pathOpSubmitter));
423}
424
425// -- DrawableOpSubmitter --------------------------------------------------------------------------
426// Shared code for submitting GPU ops for drawing glyphs as drawables.
427class DrawableOpSubmitter {
428public:
429 DrawableOpSubmitter() = delete;
430 DrawableOpSubmitter(const DrawableOpSubmitter&) = delete;
431 const DrawableOpSubmitter& operator=(const DrawableOpSubmitter&) = delete;
432 DrawableOpSubmitter(DrawableOpSubmitter&& that)
433 : fStrikeToSourceScale{that.fStrikeToSourceScale}
434 , fPositions{that.fPositions}
435 , fIDsOrDrawables{that.fIDsOrDrawables}
436 , fStrikePromise{std::move(that.fStrikePromise)} {}
437 DrawableOpSubmitter& operator=(DrawableOpSubmitter&& that) {
438 this->~DrawableOpSubmitter();
439 new (this) DrawableOpSubmitter{std::move(that)};
440 return *this;
441 }
442 DrawableOpSubmitter(SkScalar strikeToSourceScale,
443 SkSpan<SkPoint> positions,
444 SkSpan<IDOrDrawable> idsOrDrawables,
445 SkStrikePromise&& strikePromise);
446
447 static DrawableOpSubmitter Make(SkZip<const SkGlyphID, const SkPoint> accepted,
448 SkScalar strikeToSourceScale,
449 SkStrikePromise&& strikePromise,
450 SubRunAllocator* alloc) {
451 auto mapToIDOrDrawable = [](const SkGlyphID glyphID) { return IDOrDrawable{glyphID}; };
452
453 return DrawableOpSubmitter{
454 strikeToSourceScale,
455 alloc->makePODSpan(get_positions(accepted)),
456 alloc->makePODArray<IDOrDrawable>(get_glyphIDs(accepted), mapToIDOrDrawable),
457 std::move(strikePromise)};
458 }
459
460 int unflattenSize() const;
461 void flatten(SkWriteBuffer& buffer) const;
462 static std::optional<DrawableOpSubmitter> MakeFromBuffer(SkReadBuffer& buffer,
463 SubRunAllocator* alloc,
464 const SkStrikeClient* client);
465 void submitDraws(SkCanvas* canvas, SkPoint drawOrigin, const SkPaint& paint) const;
466
467private:
468 const SkScalar fStrikeToSourceScale;
469 const SkSpan<SkPoint> fPositions;
470 const SkSpan<IDOrDrawable> fIDsOrDrawables;
471 // When the promise is converted to a strike it acts as the ref on the strike to keep the
472 // SkDrawable data alive.
473 mutable SkStrikePromise fStrikePromise;
474 mutable SkOnce fConvertIDsToDrawables;
475};
476
477int DrawableOpSubmitter::unflattenSize() const {
478 return fPositions.size_bytes() + fIDsOrDrawables.size_bytes();
479}
480
481void DrawableOpSubmitter::flatten(SkWriteBuffer& buffer) const {
482 fStrikePromise.flatten(buffer);
483
484 buffer.writeScalar(fStrikeToSourceScale);
485 buffer.writePointArray(fPositions.data(), SkCount(fPositions));
486 for (IDOrDrawable idOrDrawable : fIDsOrDrawables) {
487 buffer.writeInt(idOrDrawable.fGlyphID);
488 }
489}
490
491std::optional<DrawableOpSubmitter> DrawableOpSubmitter::MakeFromBuffer(
492 SkReadBuffer& buffer, SubRunAllocator* alloc, const SkStrikeClient* client) {
493 std::optional<SkStrikePromise> strikePromise =
495 if (!buffer.validate(strikePromise.has_value())) {
496 return std::nullopt;
497 }
498
499 SkScalar strikeToSourceScale = buffer.readScalar();
500 if (!buffer.validate(0 < strikeToSourceScale)) { return std::nullopt; }
501
502 SkSpan<SkPoint> positions = MakePointsFromBuffer(buffer, alloc);
503 if (positions.empty()) { return std::nullopt; }
504 const int glyphCount = SkCount(positions);
505
506 if (!buffer.validateCanReadN<int>(glyphCount)) { return std::nullopt; }
507 auto idsOrDrawables = alloc->makePODArray<IDOrDrawable>(glyphCount);
508 for (int i = 0; i < SkToInt(glyphCount); ++i) {
509 // Remember, we stored an int for glyph id.
510 idsOrDrawables[i].fGlyphID = SkTo<SkGlyphID>(buffer.readInt());
511 }
512
513 SkASSERT(buffer.isValid());
514 return DrawableOpSubmitter{strikeToSourceScale,
515 positions,
516 SkSpan(idsOrDrawables, glyphCount),
517 std::move(strikePromise.value())};
518}
519
520DrawableOpSubmitter::DrawableOpSubmitter(
521 SkScalar strikeToSourceScale,
522 SkSpan<SkPoint> positions,
523 SkSpan<IDOrDrawable> idsOrDrawables,
524 SkStrikePromise&& strikePromise)
525 : fStrikeToSourceScale{strikeToSourceScale}
526 , fPositions{positions}
527 , fIDsOrDrawables{idsOrDrawables}
528 , fStrikePromise(std::move(strikePromise)) {
529 SkASSERT(!fPositions.empty());
530}
531
532void
533DrawableOpSubmitter::submitDraws(SkCanvas* canvas, SkPoint drawOrigin,const SkPaint& paint) const {
534 // Convert glyph IDs to Drawables if it hasn't been done yet.
535 fConvertIDsToDrawables([&]() {
536 fStrikePromise.strike()->glyphIDsToDrawables(fIDsOrDrawables);
537 // Do not call resetStrike() because the strike must remain owned to ensure the Drawable
538 // data is not freed.
539 });
540
541 // Calculate the matrix that maps the path glyphs from their size in the strike to
542 // the graphics source space.
543 SkMatrix strikeToSource = SkMatrix::Scale(fStrikeToSourceScale, fStrikeToSourceScale);
544 strikeToSource.postTranslate(drawOrigin.x(), drawOrigin.y());
545
546 // Transform the path to device because the deviceMatrix must be unchanged to
547 // draw effect, filter or shader paths.
548 for (auto [i, position] : SkMakeEnumerate(fPositions)) {
549 SkDrawable* drawable = fIDsOrDrawables[i].fDrawable;
550
551 if (drawable == nullptr) {
552 // This better be pinned to keep the drawable data alive.
553 fStrikePromise.strike()->verifyPinnedStrike();
554 SkDEBUGFAIL("Drawable should not be nullptr.");
555 continue;
556 }
557
558 // Transform the glyph to source space.
559 SkMatrix pathMatrix = strikeToSource;
560 pathMatrix.postTranslate(position.x(), position.y());
561
562 SkAutoCanvasRestore acr(canvas, false);
563 SkRect drawableBounds = drawable->getBounds();
564 pathMatrix.mapRect(&drawableBounds);
565 canvas->saveLayer(&drawableBounds, &paint);
566 drawable->draw(canvas, &pathMatrix);
567 }
568}
569
570// -- DrawableSubRun -------------------------------------------------------------------------------
571class DrawableSubRun : public SubRun {
572public:
573 DrawableSubRun(DrawableOpSubmitter&& drawingDrawing)
574 : fDrawingDrawing(std::move(drawingDrawing)) {}
575
577 SkScalar strikeToSourceScale,
578 SkStrikePromise&& strikePromise,
579 SubRunAllocator* alloc) {
580 return alloc->makeUnique<DrawableSubRun>(
581 DrawableOpSubmitter::Make(drawables,
582 strikeToSourceScale,
583 std::move(strikePromise),
584 alloc));
585 }
586
588 SubRunAllocator* alloc,
589 const SkStrikeClient* client);
590
591 void draw(SkCanvas* canvas,
592 SkPoint drawOrigin,
593 const SkPaint& paint,
595 const AtlasDrawDelegate&) const override {
596 fDrawingDrawing.submitDraws(canvas, drawOrigin, paint);
597 }
598
599 int unflattenSize() const override;
600
601 bool canReuse(const SkPaint& paint, const SkMatrix& positionMatrix) const override;
602
603 const AtlasSubRun* testingOnly_atlasSubRun() const override;
604
605protected:
606 SubRunStreamTag subRunStreamTag() const override { return SubRunStreamTag::kDrawableStreamTag; }
607 void doFlatten(SkWriteBuffer& buffer) const override;
608
609private:
610 DrawableOpSubmitter fDrawingDrawing;
611};
612
613int DrawableSubRun::unflattenSize() const {
614 return sizeof(DrawableSubRun) + fDrawingDrawing.unflattenSize();
615}
616
617void DrawableSubRun::doFlatten(SkWriteBuffer& buffer) const {
618 fDrawingDrawing.flatten(buffer);
619}
620
621SubRunOwner DrawableSubRun::MakeFromBuffer(SkReadBuffer& buffer,
622 SubRunAllocator* alloc,
623 const SkStrikeClient* client) {
624 auto drawableOpSubmitter = DrawableOpSubmitter::MakeFromBuffer(buffer, alloc, client);
625 if (!buffer.validate(drawableOpSubmitter.has_value())) { return nullptr; }
626 return alloc->makeUnique<DrawableSubRun>(std::move(*drawableOpSubmitter));
627}
628
629bool DrawableSubRun::canReuse(const SkPaint& paint, const SkMatrix& positionMatrix) const {
630 return true;
631}
632
633const AtlasSubRun* DrawableSubRun::testingOnly_atlasSubRun() const {
634 return nullptr;
635}
636
637#if defined(SK_GANESH) || defined(SK_USE_LEGACY_GANESH_TEXT_APIS)
638enum ClipMethod {
639 kClippedOut,
640 kUnclipped,
641 kGPUClipped,
642 kGeometryClipped
643};
644
645std::tuple<ClipMethod, SkIRect>
646calculate_clip(const GrClip* clip, SkRect deviceBounds, SkRect glyphBounds) {
647 if (clip == nullptr && !deviceBounds.intersects(glyphBounds)) {
648 return {kClippedOut, SkIRect::MakeEmpty()};
649 } else if (clip != nullptr) {
650 switch (auto result = clip->preApply(glyphBounds, GrAA::kNo); result.fEffect) {
652 return {kClippedOut, SkIRect::MakeEmpty()};
654 return {kUnclipped, SkIRect::MakeEmpty()};
656 if (result.fIsRRect && result.fRRect.isRect()) {
657 SkRect r = result.fRRect.rect();
658 if (result.fAA == GrAA::kNo || GrClip::IsPixelAligned(r)) {
659 SkIRect clipRect = SkIRect::MakeEmpty();
660 // Clip geometrically during onPrepare using clipRect.
661 r.round(&clipRect);
662 if (clipRect.contains(glyphBounds)) {
663 // If fully within the clip, signal no clipping using the empty rect.
664 return {kUnclipped, SkIRect::MakeEmpty()};
665 }
666 // Use the clipRect to clip the geometry.
667 return {kGeometryClipped, clipRect};
668 }
669 // Partial pixel clipped at this point. Have the GPU handle it.
670 }
671 }
672 break;
673 }
674 }
675 return {kGPUClipped, SkIRect::MakeEmpty()};
676}
677#endif // defined(SK_GANESH) || defined(SK_USE_LEGACY_GANESH_TEXT_APIS)
678
679// -- DirectMaskSubRun -----------------------------------------------------------------------------
680class DirectMaskSubRun final : public SubRun, public AtlasSubRun {
681public:
682 DirectMaskSubRun(VertexFiller&& vertexFiller,
684 : fVertexFiller{std::move(vertexFiller)}
685 , fGlyphs{std::move(glyphs)} {}
686
687 static SubRunOwner Make(SkRect creationBounds,
689 const SkMatrix& creationMatrix,
690 SkStrikePromise&& strikePromise,
691 MaskFormat maskType,
692 SubRunAllocator* alloc) {
693 auto vertexFiller = VertexFiller::Make(maskType,
694 creationMatrix,
695 creationBounds,
696 get_positions(accepted),
697 alloc,
698 kIsDirect);
699
700 auto glyphVector =
701 GlyphVector::Make(std::move(strikePromise), get_packedIDs(accepted), alloc);
702
703 return alloc->makeUnique<DirectMaskSubRun>(std::move(vertexFiller), std::move(glyphVector));
704 }
705
707 SubRunAllocator* alloc,
708 const SkStrikeClient* client) {
709 auto vertexFiller = VertexFiller::MakeFromBuffer(buffer, alloc);
710 if (!buffer.validate(vertexFiller.has_value())) { return nullptr; }
711
712 auto glyphVector = GlyphVector::MakeFromBuffer(buffer, client, alloc);
713 if (!buffer.validate(glyphVector.has_value())) { return nullptr; }
714 if (!buffer.validate(SkCount(glyphVector->glyphs()) == vertexFiller->count())) {
715 return nullptr;
716 }
717
718 SkASSERT(buffer.isValid());
719 return alloc->makeUnique<DirectMaskSubRun>(
720 std::move(*vertexFiller), std::move(*glyphVector));
721 }
722
723 void draw(SkCanvas*,
724 SkPoint drawOrigin,
725 const SkPaint& paint,
726 sk_sp<SkRefCnt> subRunStorage,
727 const AtlasDrawDelegate& drawAtlas) const override {
728 drawAtlas(this, drawOrigin, paint, std::move(subRunStorage),
729 {/* isSDF = */false, fVertexFiller.isLCD()});
730 }
731
732 int unflattenSize() const override {
733 return sizeof(DirectMaskSubRun) +
734 fGlyphs.unflattenSize() +
735 fVertexFiller.unflattenSize();
736 }
737
738 int glyphCount() const override {
739 return SkCount(fGlyphs.glyphs());
740 }
741
742 SkSpan<const Glyph*> glyphs() const override {
743 return fGlyphs.glyphs();
744 }
745
746 MaskFormat maskFormat() const override { return fVertexFiller.grMaskType(); }
747
748 int glyphSrcPadding() const override { return 0; }
749
750 unsigned short instanceFlags() const override {
751 return (unsigned short)fVertexFiller.grMaskType();
752 }
753
754 void testingOnly_packedGlyphIDToGlyph(StrikeCache* cache) const override {
755 fGlyphs.packedGlyphIDToGlyph(cache);
756 }
757
758#if defined(SK_GANESH) || defined(SK_USE_LEGACY_GANESH_TEXT_APIS)
759 size_t vertexStride(const SkMatrix& drawMatrix) const override {
760 return fVertexFiller.vertexStride(drawMatrix);
761 }
762
763 std::tuple<const GrClip*, GrOp::Owner> makeAtlasTextOp(
764 const GrClip* clip,
765 const SkMatrix& viewMatrix,
766 SkPoint drawOrigin,
767 const SkPaint& paint,
768 sk_sp<SkRefCnt>&& subRunStorage,
769 skgpu::ganesh::SurfaceDrawContext* sdc) const override {
770 SkASSERT(this->glyphCount() != 0);
771 const SkMatrix& positionMatrix = position_matrix(viewMatrix, drawOrigin);
772
773 auto [integerTranslate, subRunDeviceBounds] =
774 fVertexFiller.deviceRectAndCheckTransform(positionMatrix);
775 if (subRunDeviceBounds.isEmpty()) {
776 return {nullptr, nullptr};
777 }
778 // Rect for optimized bounds clipping when doing an integer translate.
779 SkIRect geometricClipRect = SkIRect::MakeEmpty();
780 if (integerTranslate) {
781 // We can clip geometrically using clipRect and ignore clip when an axis-aligned
782 // rectangular non-AA clip is used. If clipRect is empty, and clip is nullptr, then
783 // there is no clipping needed.
784 const SkRect deviceBounds = SkRect::MakeWH(sdc->width(), sdc->height());
785 auto [clipMethod, clipRect] = calculate_clip(clip, deviceBounds, subRunDeviceBounds);
786
787 switch (clipMethod) {
788 case kClippedOut:
789 // Returning nullptr as op means skip this op.
790 return {nullptr, nullptr};
791 case kUnclipped:
792 case kGeometryClipped:
793 // GPU clip is not needed.
794 clip = nullptr;
795 break;
796 case kGPUClipped:
797 // Use th GPU clip; clipRect is ignored.
798 break;
799 }
800 geometricClipRect = clipRect;
801
802 if (!geometricClipRect.isEmpty()) { SkASSERT(clip == nullptr); }
803 }
804
805 GrPaint grPaint;
806 const SkPMColor4f drawingColor = calculate_colors(sdc,
807 paint,
808 viewMatrix,
809 fVertexFiller.grMaskType(),
810 &grPaint);
811
812 auto geometry = AtlasTextOp::Geometry::Make(*this,
813 viewMatrix,
814 drawOrigin,
815 geometricClipRect,
816 std::move(subRunStorage),
817 drawingColor,
818 sdc->arenaAlloc());
819
820 GrRecordingContext* const rContext = sdc->recordingContext();
821
822 GrOp::Owner op = GrOp::Make<AtlasTextOp>(rContext,
823 fVertexFiller.opMaskType(),
824 !integerTranslate,
825 this->glyphCount(),
826 subRunDeviceBounds,
827 geometry,
828 sdc->colorInfo(),
829 std::move(grPaint));
830 return {clip, std::move(op)};
831 }
832
833 void fillVertexData(void* vertexDst, int offset, int count,
835 const SkMatrix& drawMatrix, SkPoint drawOrigin,
836 SkIRect clip) const override {
837 const SkMatrix positionMatrix = position_matrix(drawMatrix, drawOrigin);
838 fVertexFiller.fillVertexData(offset, count,
839 fGlyphs.glyphs(),
840 color,
841 positionMatrix,
842 clip,
843 vertexDst);
844 }
845#endif // defined(SK_GANESH) || defined(SK_USE_LEGACY_GANESH_TEXT_APIS)
846
847 std::tuple<bool, int> regenerateAtlas(int begin, int end,
848 RegenerateAtlasDelegate regenerateAtlas) const override {
849 return regenerateAtlas(
850 &fGlyphs, begin, end, fVertexFiller.grMaskType(), this->glyphSrcPadding());
851 }
852
853 const VertexFiller& vertexFiller() const override { return fVertexFiller; }
854
855 bool canReuse(const SkPaint& paint, const SkMatrix& positionMatrix) const override {
856 auto [reuse, _] = fVertexFiller.deviceRectAndCheckTransform(positionMatrix);
857 return reuse;
858 }
859
860 const AtlasSubRun* testingOnly_atlasSubRun() const override {
861 return this;
862 }
863
864protected:
865 SubRunStreamTag subRunStreamTag() const override {
866 return SubRunStreamTag::kDirectMaskStreamTag;
867 }
868
869 void doFlatten(SkWriteBuffer& buffer) const override {
870 fVertexFiller.flatten(buffer);
871 fGlyphs.flatten(buffer);
872 }
873
874private:
875 const VertexFiller fVertexFiller;
876
877 // The regenerateAtlas method mutates fGlyphs. It should be called from onPrepare which must
878 // be single threaded.
879 mutable GlyphVector fGlyphs;
880};
881
882// -- TransformedMaskSubRun ------------------------------------------------------------------------
883class TransformedMaskSubRun final : public SubRun, public AtlasSubRun {
884public:
885 TransformedMaskSubRun(bool isBigEnough,
886 VertexFiller&& vertexFiller,
888 : fIsBigEnough{isBigEnough}
889 , fVertexFiller{std::move(vertexFiller)}
890 , fGlyphs{std::move(glyphs)} {}
891
893 const SkMatrix& initialPositionMatrix,
894 SkStrikePromise&& strikePromise,
895 SkMatrix creationMatrix,
896 SkRect creationBounds,
897 MaskFormat maskType,
898 SubRunAllocator* alloc) {
899 auto vertexFiller = VertexFiller::Make(maskType,
900 creationMatrix,
901 creationBounds,
902 get_positions(accepted),
903 alloc,
905
906 auto glyphVector = GlyphVector::Make(
907 std::move(strikePromise), get_packedIDs(accepted), alloc);
908
909 return alloc->makeUnique<TransformedMaskSubRun>(
910 initialPositionMatrix.getMaxScale() >= 1,
911 std::move(vertexFiller),
912 std::move(glyphVector));
913 }
914
916 SubRunAllocator* alloc,
917 const SkStrikeClient* client) {
918 auto vertexFiller = VertexFiller::MakeFromBuffer(buffer, alloc);
919 if (!buffer.validate(vertexFiller.has_value())) { return nullptr; }
920
921 auto glyphVector = GlyphVector::MakeFromBuffer(buffer, client, alloc);
922 if (!buffer.validate(glyphVector.has_value())) { return nullptr; }
923 if (!buffer.validate(SkCount(glyphVector->glyphs()) == vertexFiller->count())) {
924 return nullptr;
925 }
926 const bool isBigEnough = buffer.readBool();
927 return alloc->makeUnique<TransformedMaskSubRun>(
928 isBigEnough, std::move(*vertexFiller), std::move(*glyphVector));
929 }
930
931 int unflattenSize() const override {
932 return sizeof(TransformedMaskSubRun) +
933 fGlyphs.unflattenSize() +
934 fVertexFiller.unflattenSize();
935 }
936
937 bool canReuse(const SkPaint& paint, const SkMatrix& positionMatrix) const override {
938 // If we are not scaling the cache entry to be larger, than a cache with smaller glyphs may
939 // be better.
940 return fIsBigEnough;
941 }
942
943 const AtlasSubRun* testingOnly_atlasSubRun() const override { return this; }
944
945 void testingOnly_packedGlyphIDToGlyph(StrikeCache *cache) const override {
946 fGlyphs.packedGlyphIDToGlyph(cache);
947 }
948
949 int glyphCount() const override { return SkCount(fGlyphs.glyphs()); }
950
951 SkSpan<const Glyph*> glyphs() const override {
952 return fGlyphs.glyphs();
953 }
954
955 unsigned short instanceFlags() const override {
956 return (unsigned short)fVertexFiller.grMaskType();
957 }
958
959 MaskFormat maskFormat() const override { return fVertexFiller.grMaskType(); }
960
961 int glyphSrcPadding() const override { return 1; }
962
963 void draw(SkCanvas*,
964 SkPoint drawOrigin,
965 const SkPaint& paint,
966 sk_sp<SkRefCnt> subRunStorage,
967 const AtlasDrawDelegate& drawAtlas) const override {
968 drawAtlas(this, drawOrigin, paint, std::move(subRunStorage),
969 {/* isSDF = */false, fVertexFiller.isLCD()});
970 }
971
972#if defined(SK_GANESH) || defined(SK_USE_LEGACY_GANESH_TEXT_APIS)
973
974 size_t vertexStride(const SkMatrix& drawMatrix) const override {
975 return fVertexFiller.vertexStride(drawMatrix);
976 }
977
978 std::tuple<const GrClip*, GrOp::Owner> makeAtlasTextOp(
979 const GrClip* clip,
980 const SkMatrix& viewMatrix,
981 SkPoint drawOrigin,
982 const SkPaint& paint,
983 sk_sp<SkRefCnt>&& subRunStorage,
984 skgpu::ganesh::SurfaceDrawContext* sdc) const override {
985 SkASSERT(this->glyphCount() != 0);
986
987 GrPaint grPaint;
988 SkPMColor4f drawingColor = calculate_colors(sdc,
989 paint,
990 viewMatrix,
991 fVertexFiller.grMaskType(),
992 &grPaint);
993
994 auto geometry = AtlasTextOp::Geometry::Make(*this,
995 viewMatrix,
996 drawOrigin,
998 std::move(subRunStorage),
999 drawingColor,
1000 sdc->arenaAlloc());
1001
1002 GrRecordingContext* const rContext = sdc->recordingContext();
1003 SkMatrix positionMatrix = position_matrix(viewMatrix, drawOrigin);
1004 auto [_, deviceRect] = fVertexFiller.deviceRectAndCheckTransform(positionMatrix);
1005 GrOp::Owner op = GrOp::Make<AtlasTextOp>(rContext,
1006 fVertexFiller.opMaskType(),
1007 true,
1008 this->glyphCount(),
1009 deviceRect,
1010 geometry,
1011 sdc->colorInfo(),
1012 std::move(grPaint));
1013 return {clip, std::move(op)};
1014 }
1015
1016 void fillVertexData(
1017 void* vertexDst, int offset, int count,
1018 GrColor color,
1019 const SkMatrix& drawMatrix, SkPoint drawOrigin,
1020 SkIRect clip) const override {
1021 const SkMatrix positionMatrix = position_matrix(drawMatrix, drawOrigin);
1022 fVertexFiller.fillVertexData(offset, count,
1023 fGlyphs.glyphs(),
1024 color,
1025 positionMatrix,
1026 clip,
1027 vertexDst);
1028 }
1029#endif // defined(SK_GANESH) || defined(SK_USE_LEGACY_GANESH_TEXT_APIS)
1030
1031 std::tuple<bool, int> regenerateAtlas(int begin, int end,
1032 RegenerateAtlasDelegate regenerateAtlas) const override {
1033 return regenerateAtlas(
1034 &fGlyphs, begin, end, fVertexFiller.grMaskType(), this->glyphSrcPadding());
1035 }
1036
1037 const VertexFiller& vertexFiller() const override { return fVertexFiller; }
1038
1039protected:
1040 SubRunStreamTag subRunStreamTag() const override {
1041 return SubRunStreamTag::kTransformMaskStreamTag;
1042 }
1043
1044 void doFlatten(SkWriteBuffer& buffer) const override {
1045 fVertexFiller.flatten(buffer);
1046 fGlyphs.flatten(buffer);
1047 buffer.writeBool(fIsBigEnough);
1048 }
1049
1050private:
1051 const bool fIsBigEnough;
1052
1053 const VertexFiller fVertexFiller;
1054
1055 // The regenerateAtlas method mutates fGlyphs. It should be called from onPrepare which must
1056 // be single threaded.
1057 mutable GlyphVector fGlyphs;
1058}; // class TransformedMaskSubRun
1059
1060// -- SDFTSubRun -----------------------------------------------------------------------------------
1061
1062bool has_some_antialiasing(const SkFont& font ) {
1063 SkFont::Edging edging = font.getEdging();
1064 return edging == SkFont::Edging::kAntiAlias
1066}
1067
1068#if !defined(SK_DISABLE_SDF_TEXT)
1069
1070#if defined(SK_GANESH) || defined(SK_USE_LEGACY_GANESH_TEXT_APIS)
1071
1072static std::tuple<AtlasTextOp::MaskType, uint32_t, bool> calculate_sdf_parameters(
1074 const SkMatrix& drawMatrix,
1075 bool useLCDText,
1076 bool isAntiAliased) {
1077 const GrColorInfo& colorInfo = sdc.colorInfo();
1078 const SkSurfaceProps& props = sdc.surfaceProps();
1079 bool isBGR = SkPixelGeometryIsBGR(props.pixelGeometry());
1080 bool isLCD = useLCDText && SkPixelGeometryIsH(props.pixelGeometry());
1081 using MT = AtlasTextOp::MaskType;
1082 MT maskType = !isAntiAliased ? MT::kAliasedDistanceField
1083 : isLCD ? (isBGR ? MT::kLCDBGRDistanceField
1084 : MT::kLCDDistanceField)
1085 : MT::kGrayscaleDistanceField;
1086
1087 bool useGammaCorrectDistanceTable = colorInfo.isLinearlyBlended();
1088 uint32_t DFGPFlags = drawMatrix.isSimilarity() ? kSimilarity_DistanceFieldEffectFlag : 0;
1089 DFGPFlags |= drawMatrix.isScaleTranslate() ? kScaleOnly_DistanceFieldEffectFlag : 0;
1090 DFGPFlags |= useGammaCorrectDistanceTable ? kGammaCorrect_DistanceFieldEffectFlag : 0;
1091 DFGPFlags |= MT::kAliasedDistanceField == maskType ? kAliased_DistanceFieldEffectFlag : 0;
1092 DFGPFlags |= drawMatrix.hasPerspective() ? kPerspective_DistanceFieldEffectFlag : 0;
1093
1094 if (isLCD) {
1096 DFGPFlags |= MT::kLCDBGRDistanceField == maskType ? kBGR_DistanceFieldEffectFlag : 0;
1097 }
1098 return {maskType, DFGPFlags, useGammaCorrectDistanceTable};
1099}
1100
1101#endif // defined(SK_GANESH) || defined(SK_USE_LEGACY_GANESH_TEXT_APIS)
1102
1103class SDFTSubRun final : public SubRun, public AtlasSubRun {
1104public:
1105 SDFTSubRun(bool useLCDText,
1106 bool antiAliased,
1107 const SDFTMatrixRange& matrixRange,
1108 VertexFiller&& vertexFiller,
1110 : fUseLCDText{useLCDText}
1111 , fAntiAliased{antiAliased}
1112 , fMatrixRange{matrixRange}
1113 , fVertexFiller{std::move(vertexFiller)}
1114 , fGlyphs{std::move(glyphs)} { }
1115
1117 const SkFont& runFont,
1118 SkStrikePromise&& strikePromise,
1119 const SkMatrix& creationMatrix,
1120 SkRect creationBounds,
1121 const SDFTMatrixRange& matrixRange,
1122 SubRunAllocator* alloc) {
1123 auto vertexFiller = VertexFiller::Make(MaskFormat::kA8,
1124 creationMatrix,
1125 creationBounds,
1126 get_positions(accepted),
1127 alloc,
1129
1130 auto glyphVector = GlyphVector::Make(
1131 std::move(strikePromise), get_packedIDs(accepted), alloc);
1132
1133 return alloc->makeUnique<SDFTSubRun>(
1135 has_some_antialiasing(runFont),
1136 matrixRange,
1137 std::move(vertexFiller),
1138 std::move(glyphVector));
1139 }
1140
1142 SubRunAllocator* alloc,
1143 const SkStrikeClient* client) {
1144 int useLCD = buffer.readInt();
1145 int isAntiAliased = buffer.readInt();
1147 auto vertexFiller = VertexFiller::MakeFromBuffer(buffer, alloc);
1148 if (!buffer.validate(vertexFiller.has_value())) { return nullptr; }
1149 if (!buffer.validate(vertexFiller.value().grMaskType() == MaskFormat::kA8)) {
1150 return nullptr;
1151 }
1152 auto glyphVector = GlyphVector::MakeFromBuffer(buffer, client, alloc);
1153 if (!buffer.validate(glyphVector.has_value())) { return nullptr; }
1154 if (!buffer.validate(SkCount(glyphVector->glyphs()) == vertexFiller->count())) {
1155 return nullptr;
1156 }
1157 return alloc->makeUnique<SDFTSubRun>(useLCD,
1158 isAntiAliased,
1159 matrixRange,
1160 std::move(*vertexFiller),
1161 std::move(*glyphVector));
1162 }
1163
1164 int unflattenSize() const override {
1165 return sizeof(SDFTSubRun) + fGlyphs.unflattenSize() + fVertexFiller.unflattenSize();
1166 }
1167
1168 bool canReuse(const SkPaint& paint, const SkMatrix& positionMatrix) const override {
1169 return fMatrixRange.matrixInRange(positionMatrix);
1170 }
1171
1172 const AtlasSubRun* testingOnly_atlasSubRun() const override { return this; }
1173
1174 void testingOnly_packedGlyphIDToGlyph(StrikeCache *cache) const override {
1175 fGlyphs.packedGlyphIDToGlyph(cache);
1176 }
1177
1178 int glyphCount() const override { return fVertexFiller.count(); }
1179 MaskFormat maskFormat() const override {
1180 SkASSERT(fVertexFiller.grMaskType() == MaskFormat::kA8);
1181 return MaskFormat::kA8;
1182 }
1183 int glyphSrcPadding() const override { return SK_DistanceFieldInset; }
1184
1185 SkSpan<const Glyph*> glyphs() const override {
1186 return fGlyphs.glyphs();
1187 }
1188
1189 unsigned short instanceFlags() const override {
1190 return (unsigned short)MaskFormat::kA8;
1191 }
1192
1193 void draw(SkCanvas*,
1194 SkPoint drawOrigin,
1195 const SkPaint& paint,
1196 sk_sp<SkRefCnt> subRunStorage,
1197 const AtlasDrawDelegate& drawAtlas) const override {
1198 drawAtlas(this, drawOrigin, paint, std::move(subRunStorage),
1199 {/* isSDF = */true, /* isLCD = */fUseLCDText});
1200 }
1201
1202#if defined(SK_GANESH) || defined(SK_USE_LEGACY_GANESH_TEXT_APIS)
1203 size_t vertexStride(const SkMatrix& drawMatrix) const override {
1204 return fVertexFiller.vertexStride(drawMatrix);
1205 }
1206
1207 std::tuple<const GrClip*, GrOp::Owner> makeAtlasTextOp(
1208 const GrClip* clip,
1209 const SkMatrix& viewMatrix,
1210 SkPoint drawOrigin,
1211 const SkPaint& paint,
1212 sk_sp<SkRefCnt>&& subRunStorage,
1213 skgpu::ganesh::SurfaceDrawContext* sdc) const override {
1214 SkASSERT(this->glyphCount() != 0);
1215
1216 GrPaint grPaint;
1217 SkPMColor4f drawingColor = calculate_colors(sdc,
1218 paint,
1219 viewMatrix,
1220 MaskFormat::kA8,
1221 &grPaint);
1222
1223 auto [maskType, DFGPFlags, useGammaCorrectDistanceTable] =
1224 calculate_sdf_parameters(*sdc, viewMatrix, fUseLCDText, fAntiAliased);
1225
1226 auto geometry = AtlasTextOp::Geometry::Make(*this,
1227 viewMatrix,
1228 drawOrigin,
1230 std::move(subRunStorage),
1231 drawingColor,
1232 sdc->arenaAlloc());
1233
1234 GrRecordingContext* const rContext = sdc->recordingContext();
1235 SkMatrix positionMatrix = position_matrix(viewMatrix, drawOrigin);
1236 auto [_, deviceRect] = fVertexFiller.deviceRectAndCheckTransform(positionMatrix);
1237 GrOp::Owner op = GrOp::Make<AtlasTextOp>(rContext,
1238 maskType,
1239 true,
1240 this->glyphCount(),
1241 deviceRect,
1243 useGammaCorrectDistanceTable,
1244 DFGPFlags,
1245 geometry,
1246 std::move(grPaint));
1247
1248 return {clip, std::move(op)};
1249 }
1250
1251 void fillVertexData(
1252 void *vertexDst, int offset, int count,
1253 GrColor color,
1254 const SkMatrix& drawMatrix, SkPoint drawOrigin,
1255 SkIRect clip) const override {
1256 const SkMatrix positionMatrix = position_matrix(drawMatrix, drawOrigin);
1257
1258 fVertexFiller.fillVertexData(offset, count,
1259 fGlyphs.glyphs(),
1260 color,
1261 positionMatrix,
1262 clip,
1263 vertexDst);
1264 }
1265
1266#endif // defined(SK_GANESH) || defined(SK_USE_LEGACY_GANESH_TEXT_APIS)
1267
1268 std::tuple<bool, int> regenerateAtlas(int begin, int end,
1269 RegenerateAtlasDelegate regenerateAtlas) const override {
1270 return regenerateAtlas(&fGlyphs, begin, end, MaskFormat::kA8, this->glyphSrcPadding());
1271 }
1272
1273 const VertexFiller& vertexFiller() const override { return fVertexFiller; }
1274
1275protected:
1276 SubRunStreamTag subRunStreamTag() const override { return SubRunStreamTag::kSDFTStreamTag; }
1277 void doFlatten(SkWriteBuffer& buffer) const override {
1278 buffer.writeInt(fUseLCDText);
1279 buffer.writeInt(fAntiAliased);
1280 fMatrixRange.flatten(buffer);
1281 fVertexFiller.flatten(buffer);
1282 fGlyphs.flatten(buffer);
1283 }
1284
1285private:
1286 const bool fUseLCDText;
1287 const bool fAntiAliased;
1288 const SDFTMatrixRange fMatrixRange;
1289
1290 const VertexFiller fVertexFiller;
1291
1292 // The regenerateAtlas method mutates fGlyphs. It should be called from onPrepare which must
1293 // be single threaded.
1294 mutable GlyphVector fGlyphs;
1295}; // class SDFTSubRun
1296
1297#endif // !defined(SK_DISABLE_SDF_TEXT)
1298
1299// -- SubRun ---------------------------------------------------------------------------------------
1300
1301template<typename AddSingleMaskFormat>
1302void add_multi_mask_format(
1303 AddSingleMaskFormat addSingleMaskFormat,
1305 if (accepted.empty()) { return; }
1306
1307 auto maskSpan = accepted.get<2>();
1309 size_t startIndex = 0;
1310 for (size_t i = 1; i < accepted.size(); i++) {
1311 MaskFormat nextFormat = Glyph::FormatFromSkGlyph(maskSpan[i]);
1312 if (format != nextFormat) {
1313 auto interval = accepted.subspan(startIndex, i - startIndex);
1314 // Only pass the packed glyph ids and positions.
1315 auto glyphsWithSameFormat = SkMakeZip(interval.get<0>(), interval.get<1>());
1316 // Take a ref on the strike. This should rarely happen.
1317 addSingleMaskFormat(glyphsWithSameFormat, format);
1318 format = nextFormat;
1319 startIndex = i;
1320 }
1321 }
1322 auto interval = accepted.last(accepted.size() - startIndex);
1323 auto glyphsWithSameFormat = SkMakeZip(interval.get<0>(), interval.get<1>());
1324 addSingleMaskFormat(glyphsWithSameFormat, format);
1325}
1326} // namespace
1327
1328namespace sktext::gpu {
1329SubRun::~SubRun() = default;
1331 buffer.writeInt(this->subRunStreamTag());
1332 this->doFlatten(buffer);
1333}
1334
1336 SubRunAllocator* alloc,
1337 const SkStrikeClient* client) {
1338 using Maker = SubRunOwner (*)(SkReadBuffer&,
1340 const SkStrikeClient*);
1341
1342 static Maker makers[kSubRunStreamTagCount] = {
1343 nullptr, // 0 index is bad.
1344 DirectMaskSubRun::MakeFromBuffer,
1345#if !defined(SK_DISABLE_SDF_TEXT)
1346 SDFTSubRun::MakeFromBuffer,
1347#endif
1348 TransformedMaskSubRun::MakeFromBuffer,
1349 PathSubRun::MakeFromBuffer,
1350 DrawableSubRun::MakeFromBuffer,
1351 };
1352 int subRunTypeInt = buffer.readInt();
1353 SkASSERT(kBad < subRunTypeInt && subRunTypeInt < kSubRunStreamTagCount);
1354 if (!buffer.validate(kBad < subRunTypeInt && subRunTypeInt < kSubRunStreamTagCount)) {
1355 return nullptr;
1356 }
1357 auto maker = makers[subRunTypeInt];
1358 if (!buffer.validate(maker != nullptr)) { return nullptr; }
1359 return maker(buffer, alloc, client);
1360}
1361
1362// -- SubRunContainer ------------------------------------------------------------------------------
1363SubRunContainer::SubRunContainer(const SkMatrix& initialPositionMatrix)
1364 : fInitialPositionMatrix{initialPositionMatrix} {}
1365
1367 int unflattenSizeHint = 0;
1368 for (auto& subrun : fSubRuns) {
1369 unflattenSizeHint += subrun.unflattenSize();
1370 }
1371 buffer.writeInt(unflattenSizeHint);
1372}
1373
1375 int subRunsSizeHint = buffer.readInt();
1376
1377 // Since the hint doesn't affect correctness, if it looks fishy just pick a reasonable
1378 // value.
1379 if (subRunsSizeHint < 0 || (1 << 16) < subRunsSizeHint) {
1380 subRunsSizeHint = 128;
1381 }
1382 return subRunsSizeHint;
1383}
1384
1386 buffer.writeMatrix(fInitialPositionMatrix);
1387 int subRunCount = 0;
1388 for ([[maybe_unused]] auto& subRun : fSubRuns) {
1389 subRunCount += 1;
1390 }
1391 buffer.writeInt(subRunCount);
1392 for (auto& subRun : fSubRuns) {
1393 subRun.flatten(buffer);
1394 }
1395}
1396
1398 const SkStrikeClient* client,
1399 SubRunAllocator* alloc) {
1400 SkMatrix positionMatrix;
1401 buffer.readMatrix(&positionMatrix);
1402 if (!buffer.isValid()) { return nullptr; }
1403 SubRunContainerOwner container = alloc->makeUnique<SubRunContainer>(positionMatrix);
1404
1405 int subRunCount = buffer.readInt();
1406 SkASSERT(subRunCount > 0);
1407 if (!buffer.validate(subRunCount > 0)) { return nullptr; }
1408 for (int i = 0; i < subRunCount; ++i) {
1409 auto subRunOwner = SubRun::MakeFromBuffer(buffer, alloc, client);
1410 if (!buffer.validate(subRunOwner != nullptr)) { return nullptr; }
1411 if (subRunOwner != nullptr) {
1412 container->fSubRuns.append(std::move(subRunOwner));
1413 }
1414 }
1415 return container;
1416}
1417
1419 // The difference in alignment from the per-glyph data to the SubRun;
1420 constexpr size_t alignDiff = alignof(DirectMaskSubRun) - alignof(SkPoint);
1421 constexpr size_t vertexDataToSubRunPadding = alignDiff > 0 ? alignDiff : 0;
1422 size_t totalGlyphCount = glyphRunList.totalGlyphCount();
1423 // This is optimized for DirectMaskSubRun which is by far the most common case.
1424 return totalGlyphCount * sizeof(SkPoint)
1425 + GlyphVector::GlyphVectorSize(totalGlyphCount)
1426 + glyphRunList.runCount() * (sizeof(DirectMaskSubRun) + vertexDataToSubRunPadding)
1427 + sizeof(SubRunContainer);
1428}
1429
1430SkScalar find_maximum_glyph_dimension(StrikeForGPU* strike, SkSpan<const SkGlyphID> glyphs) {
1431 StrikeMutationMonitor m{strike};
1432 SkScalar maxDimension = 0;
1433 for (SkGlyphID glyphID : glyphs) {
1434 SkGlyphDigest digest = strike->digestFor(kMask, SkPackedGlyphID{glyphID});
1435 maxDimension = std::max(static_cast<SkScalar>(digest.maxDimension()), maxDimension);
1436 }
1437
1438 return maxDimension;
1439}
1440
1441#if !defined(SK_DISABLE_SDF_TEXT)
1442std::tuple<SkZip<const SkPackedGlyphID, const SkPoint>, SkZip<SkGlyphID, SkPoint>, SkRect>
1443prepare_for_SDFT_drawing(StrikeForGPU* strike,
1444 const SkMatrix& creationMatrix,
1446 SkZip<SkPackedGlyphID, SkPoint> acceptedBuffer,
1447 SkZip<SkGlyphID, SkPoint> rejectedBuffer) {
1448 int acceptedSize = 0,
1449 rejectedSize = 0;
1450 SkGlyphRect boundingRect = skglyph::empty_rect();
1451 StrikeMutationMonitor m{strike};
1452 for (const auto [glyphID, pos] : source) {
1453 if (!SkIsFinite(pos.x(), pos.y())) {
1454 continue;
1455 }
1456
1457 const SkPackedGlyphID packedID{glyphID};
1458 switch (const SkGlyphDigest digest = strike->digestFor(skglyph::kSDFT, packedID);
1459 digest.actionFor(skglyph::kSDFT)) {
1460 case GlyphAction::kAccept: {
1461 SkPoint mappedPos = creationMatrix.mapPoint(pos);
1462 const SkGlyphRect glyphBounds =
1463 digest.bounds()
1464 // The SDFT glyphs have 2-pixel wide padding that should
1465 // not be used in calculating the source rectangle.
1467 .offset(mappedPos);
1468 boundingRect = skglyph::rect_union(boundingRect, glyphBounds);
1469 acceptedBuffer[acceptedSize++] = std::make_tuple(packedID, glyphBounds.leftTop());
1470 break;
1471 }
1472 case GlyphAction::kReject:
1473 rejectedBuffer[rejectedSize++] = std::make_tuple(glyphID, pos);
1474 break;
1475 default:
1476 break;
1477 }
1478 }
1479
1480 return {acceptedBuffer.first(acceptedSize),
1481 rejectedBuffer.first(rejectedSize),
1482 boundingRect.rect()};
1483}
1484#endif
1485
1486std::tuple<SkZip<const SkPackedGlyphID, const SkPoint, const SkMask::Format>,
1488 SkRect>
1489prepare_for_direct_mask_drawing(StrikeForGPU* strike,
1490 const SkMatrix& positionMatrix,
1493 SkZip<SkGlyphID, SkPoint> rejectedBuffer) {
1494 const SkIPoint mask = strike->roundingSpec().ignorePositionFieldMask;
1495 const SkPoint halfSampleFreq = strike->roundingSpec().halfAxisSampleFreq;
1496
1497 // Build up the mapping from source space to device space. Add the rounding constant
1498 // halfSampleFreq, so we just need to floor to get the device result.
1499 SkMatrix positionMatrixWithRounding = positionMatrix;
1500 positionMatrixWithRounding.postTranslate(halfSampleFreq.x(), halfSampleFreq.y());
1501
1502 int acceptedSize = 0,
1503 rejectedSize = 0;
1504 SkGlyphRect boundingRect = skglyph::empty_rect();
1505 StrikeMutationMonitor m{strike};
1506 for (auto [glyphID, pos] : source) {
1507 if (!SkIsFinite(pos.x(), pos.y())) {
1508 continue;
1509 }
1510
1511 const SkPoint mappedPos = positionMatrixWithRounding.mapPoint(pos);
1512 const SkPackedGlyphID packedID{glyphID, mappedPos, mask};
1513 switch (const SkGlyphDigest digest = strike->digestFor(skglyph::kDirectMask, packedID);
1514 digest.actionFor(skglyph::kDirectMask)) {
1515 case GlyphAction::kAccept: {
1516 const SkPoint roundedPos{SkScalarFloorToScalar(mappedPos.x()),
1517 SkScalarFloorToScalar(mappedPos.y())};
1518 const SkGlyphRect glyphBounds = digest.bounds().offset(roundedPos);
1519 boundingRect = skglyph::rect_union(boundingRect, glyphBounds);
1520 acceptedBuffer[acceptedSize++] =
1521 std::make_tuple(packedID, glyphBounds.leftTop(), digest.maskFormat());
1522 break;
1523 }
1524 case GlyphAction::kReject:
1525 rejectedBuffer[rejectedSize++] = std::make_tuple(glyphID, pos);
1526 break;
1527 default:
1528 break;
1529 }
1530 }
1531
1532 return {acceptedBuffer.first(acceptedSize),
1533 rejectedBuffer.first(rejectedSize),
1534 boundingRect.rect()};
1535}
1536
1537std::tuple<SkZip<const SkPackedGlyphID, const SkPoint, const SkMask::Format>,
1539 SkRect>
1540prepare_for_mask_drawing(StrikeForGPU* strike,
1541 const SkMatrix& creationMatrix,
1544 SkZip<SkGlyphID, SkPoint> rejectedBuffer) {
1545 int acceptedSize = 0,
1546 rejectedSize = 0;
1547 SkGlyphRect boundingRect = skglyph::empty_rect();
1548 StrikeMutationMonitor m{strike};
1549 for (auto [glyphID, pos] : source) {
1550 if (!SkIsFinite(pos.x(), pos.y())) {
1551 continue;
1552 }
1553
1554 const SkPackedGlyphID packedID{glyphID};
1555 switch (const SkGlyphDigest digest = strike->digestFor(kMask, packedID);
1556 digest.actionFor(kMask)) {
1557 case GlyphAction::kAccept: {
1558 const SkPoint mappedPos = creationMatrix.mapPoint(pos);
1559 const SkGlyphRect glyphBounds = digest.bounds().offset(mappedPos);
1560 boundingRect = skglyph::rect_union(boundingRect, glyphBounds);
1561 acceptedBuffer[acceptedSize++] =
1562 std::make_tuple(packedID, glyphBounds.leftTop(), digest.maskFormat());
1563 break;
1564 }
1565 case GlyphAction::kReject:
1566 rejectedBuffer[rejectedSize++] = std::make_tuple(glyphID, pos);
1567 break;
1568 default:
1569 break;
1570 }
1571 }
1572
1573 return {acceptedBuffer.first(acceptedSize),
1574 rejectedBuffer.first(rejectedSize),
1575 boundingRect.rect()};
1576}
1577
1578std::tuple<SkZip<const SkGlyphID, const SkPoint>, SkZip<SkGlyphID, SkPoint>>
1579prepare_for_path_drawing(StrikeForGPU* strike,
1581 SkZip<SkGlyphID, SkPoint> acceptedBuffer,
1582 SkZip<SkGlyphID, SkPoint> rejectedBuffer) {
1583 int acceptedSize = 0;
1584 int rejectedSize = 0;
1585 StrikeMutationMonitor m{strike};
1586 for (const auto [glyphID, pos] : source) {
1587 if (!SkIsFinite(pos.x(), pos.y())) {
1588 continue;
1589 }
1590
1591 switch (strike->digestFor(skglyph::kPath, SkPackedGlyphID{glyphID})
1592 .actionFor(skglyph::kPath)) {
1593 case GlyphAction::kAccept:
1594 acceptedBuffer[acceptedSize++] = std::make_tuple(glyphID, pos);
1595 break;
1596 case GlyphAction::kReject:
1597 rejectedBuffer[rejectedSize++] = std::make_tuple(glyphID, pos);
1598 break;
1599 default:
1600 break;
1601 }
1602 }
1603 return {acceptedBuffer.first(acceptedSize), rejectedBuffer.first(rejectedSize)};
1604}
1605
1606std::tuple<SkZip<const SkGlyphID, const SkPoint>, SkZip<SkGlyphID, SkPoint>>
1607 prepare_for_drawable_drawing(StrikeForGPU* strike,
1609 SkZip<SkGlyphID, SkPoint> acceptedBuffer,
1610 SkZip<SkGlyphID, SkPoint> rejectedBuffer) {
1611 int acceptedSize = 0;
1612 int rejectedSize = 0;
1613 StrikeMutationMonitor m{strike};
1614 for (const auto [glyphID, pos] : source) {
1615 if (!SkIsFinite(pos.x(), pos.y())) {
1616 continue;
1617 }
1618
1619 switch (strike->digestFor(skglyph::kDrawable, SkPackedGlyphID{glyphID})
1620 .actionFor(skglyph::kDrawable)) {
1621 case GlyphAction::kAccept:
1622 acceptedBuffer[acceptedSize++] = std::make_tuple(glyphID, pos);
1623 break;
1624 case GlyphAction::kReject:
1625 rejectedBuffer[rejectedSize++] = std::make_tuple(glyphID, pos);
1626 break;
1627 default:
1628 break;
1629 }
1630 }
1631 return {acceptedBuffer.first(acceptedSize), rejectedBuffer.first(rejectedSize)};
1632}
1633
1634#if !defined(SK_DISABLE_SDF_TEXT)
1635static std::tuple<SkStrikeSpec, SkScalar, sktext::gpu::SDFTMatrixRange>
1636make_sdft_strike_spec(const SkFont& font, const SkPaint& paint,
1637 const SkSurfaceProps& surfaceProps, const SkMatrix& deviceMatrix,
1638 const SkPoint& textLocation, const sktext::gpu::SDFTControl& control) {
1639 // Add filter to the paint which creates the SDFT data for A8 masks.
1640 SkPaint dfPaint{paint};
1641 dfPaint.setMaskFilter(sktext::gpu::SDFMaskFilter::Make());
1642
1643 auto [dfFont, strikeToSourceScale, matrixRange] = control.getSDFFont(font, deviceMatrix,
1644 textLocation);
1645
1646 // Adjust the stroke width by the scale factor for drawing the SDFT.
1647 dfPaint.setStrokeWidth(paint.getStrokeWidth() / strikeToSourceScale);
1648
1649 // Check for dashing and adjust the intervals.
1650 if (SkPathEffect* pathEffect = paint.getPathEffect(); pathEffect != nullptr) {
1651 SkPathEffect::DashInfo dashInfo;
1652 if (pathEffect->asADash(&dashInfo) == SkPathEffect::kDash_DashType) {
1653 if (dashInfo.fCount > 0) {
1654 // Allocate the intervals.
1655 std::vector<SkScalar> scaledIntervals(dashInfo.fCount);
1656 dashInfo.fIntervals = scaledIntervals.data();
1657 // Call again to get the interval data.
1658 (void)pathEffect->asADash(&dashInfo);
1659 for (SkScalar& interval : scaledIntervals) {
1660 interval /= strikeToSourceScale;
1661 }
1662 auto scaledDashes = SkDashPathEffect::Make(scaledIntervals.data(),
1663 scaledIntervals.size(),
1664 dashInfo.fPhase / strikeToSourceScale);
1665 dfPaint.setPathEffect(scaledDashes);
1666 }
1667 }
1668 }
1669
1670 // Fake-gamma and subpixel antialiasing are applied in the shader, so we ignore the
1671 // passed-in scaler context flags. (It's only used when we fall-back to bitmap text).
1673 SkStrikeSpec strikeSpec = SkStrikeSpec::MakeMask(dfFont, dfPaint, surfaceProps, flags,
1674 SkMatrix::I());
1675
1676 return std::make_tuple(std::move(strikeSpec), strikeToSourceScale, matrixRange);
1677}
1678#endif
1679
1681 const GlyphRunList& glyphRunList,
1682 const SkMatrix& positionMatrix,
1683 const SkPaint& runPaint,
1684 SkStrikeDeviceInfo strikeDeviceInfo,
1685 StrikeForGPUCacheInterface* strikeCache,
1686 SubRunAllocator* alloc,
1687 SubRunCreationBehavior creationBehavior,
1688 const char* tag) {
1689 SkASSERT(alloc != nullptr);
1690 SkASSERT(strikeDeviceInfo.fSDFTControl != nullptr);
1691
1692 SubRunContainerOwner container = alloc->makeUnique<SubRunContainer>(positionMatrix);
1693 // If there is no SDFT description ignore all SubRuns.
1694 if (strikeDeviceInfo.fSDFTControl == nullptr) {
1695 return container;
1696 }
1697
1698 const SkSurfaceProps deviceProps = strikeDeviceInfo.fSurfaceProps;
1699 const SkScalerContextFlags scalerContextFlags = strikeDeviceInfo.fScalerContextFlags;
1700#if !defined(SK_DISABLE_SDF_TEXT)
1701 const SDFTControl SDFTControl = *strikeDeviceInfo.fSDFTControl;
1702 const SkScalar maxMaskSize = SDFTControl.maxSize();
1703#else
1704 const SkScalar maxMaskSize = 256;
1705#endif
1706
1707 // TODO: hoist the buffer structure to the GlyphRunBuilder. The buffer structure here is
1708 // still begin tuned, and this is expected to be slower until tuned.
1709 const int maxGlyphRunSize = glyphRunList.maxGlyphRunSize();
1710
1711 // Accepted buffers.
1712 STArray<64, SkPackedGlyphID> acceptedPackedGlyphIDs;
1713 STArray<64, SkGlyphID> acceptedGlyphIDs;
1714 STArray<64, SkPoint> acceptedPositions;
1715 STArray<64, SkMask::Format> acceptedFormats;
1716 acceptedPackedGlyphIDs.resize(maxGlyphRunSize);
1717 acceptedGlyphIDs.resize(maxGlyphRunSize);
1718 acceptedPositions.resize(maxGlyphRunSize);
1719 acceptedFormats.resize(maxGlyphRunSize);
1720
1721 // Rejected buffers.
1722 STArray<64, SkGlyphID> rejectedGlyphIDs;
1723 STArray<64, SkPoint> rejectedPositions;
1724 rejectedGlyphIDs.resize(maxGlyphRunSize);
1725 rejectedPositions.resize(maxGlyphRunSize);
1726 const auto rejectedBuffer = SkMakeZip(rejectedGlyphIDs, rejectedPositions);
1727
1728 const SkPoint glyphRunListLocation = glyphRunList.sourceBounds().center();
1729
1730 // Handle all the runs in the glyphRunList
1731 for (auto& glyphRun : glyphRunList) {
1733 const SkFont& runFont = glyphRun.font();
1734
1735 const SkScalar approximateDeviceTextSize =
1736 // Since the positionMatrix has the origin prepended, use the plain
1737 // sourceBounds from above.
1738 SkFontPriv::ApproximateTransformedTextSize(runFont, positionMatrix,
1739 glyphRunListLocation);
1740
1741 // Atlas mask cases - SDFT and direct mask
1742 // Only consider using direct or SDFT drawing if not drawing hairlines and not too big.
1743 if ((runPaint.getStyle() != SkPaint::kStroke_Style || runPaint.getStrokeWidth() != 0) &&
1744 approximateDeviceTextSize < maxMaskSize) {
1745
1746#if !defined(SK_DISABLE_SDF_TEXT)
1747 // SDFT case
1748 if (SDFTControl.isSDFT(approximateDeviceTextSize, runPaint, positionMatrix)) {
1749 // Process SDFT - This should be the .009% case.
1750 const auto& [strikeSpec, strikeToSourceScale, matrixRange] =
1751 make_sdft_strike_spec(
1752 runFont, runPaint, deviceProps, positionMatrix,
1753 glyphRunListLocation, SDFTControl);
1754
1755 if (!SkScalarNearlyZero(strikeToSourceScale)) {
1756 sk_sp<StrikeForGPU> strike = strikeSpec.findOrCreateScopedStrike(strikeCache);
1757
1758 // The creationMatrix needs to scale the strike data when inverted and
1759 // multiplied by the positionMatrix. The final CTM should be:
1760 // [positionMatrix][scale by strikeToSourceScale],
1761 // which should equal the following because of the transform during the vertex
1762 // calculation,
1763 // [positionMatrix][creationMatrix]^-1.
1764 // So, the creation matrix needs to be
1765 // [scale by 1/strikeToSourceScale].
1766 SkMatrix creationMatrix =
1767 SkMatrix::Scale(1.f/strikeToSourceScale, 1.f/strikeToSourceScale);
1768
1769 auto acceptedBuffer = SkMakeZip(acceptedPackedGlyphIDs, acceptedPositions);
1770 auto [accepted, rejected, creationBounds] = prepare_for_SDFT_drawing(
1771 strike.get(), creationMatrix, source, acceptedBuffer, rejectedBuffer);
1772 source = rejected;
1773
1774 if (creationBehavior == kAddSubRuns && !accepted.empty()) {
1775 container->fSubRuns.append(SDFTSubRun::Make(
1776 accepted,
1777 runFont,
1778 strike->strikePromise(),
1779 creationMatrix,
1780 creationBounds,
1781 matrixRange,
1782 alloc));
1783 }
1784 }
1785 }
1786#endif // !defined(SK_DISABLE_SDF_TEXT)
1787
1788 // Direct Mask case
1789 // Handle all the directly mapped mask subruns.
1790 if (!source.empty() && !positionMatrix.hasPerspective()) {
1791 // Process masks including ARGB - this should be the 99.99% case.
1792 // This will handle medium size emoji that are sharing the run with SDFT drawn text.
1793 // If things are too big they will be passed along to the drawing of last resort
1794 // below.
1796 runFont, runPaint, deviceProps, scalerContextFlags, positionMatrix);
1797
1798 sk_sp<StrikeForGPU> strike = strikeSpec.findOrCreateScopedStrike(strikeCache);
1799
1800 auto acceptedBuffer = SkMakeZip(acceptedPackedGlyphIDs,
1801 acceptedPositions,
1802 acceptedFormats);
1803 auto [accepted, rejected, creationBounds] = prepare_for_direct_mask_drawing(
1804 strike.get(), positionMatrix, source, acceptedBuffer, rejectedBuffer);
1805 source = rejected;
1806
1807 if (creationBehavior == kAddSubRuns && !accepted.empty()) {
1808 auto addGlyphsWithSameFormat =
1809 [&, bounds = creationBounds](
1812 container->fSubRuns.append(
1813 DirectMaskSubRun::Make(bounds,
1814 subrun,
1815 container->initialPosition(),
1816 strike->strikePromise(),
1817 format,
1818 alloc));
1819 };
1820 add_multi_mask_format(addGlyphsWithSameFormat, accepted);
1821 }
1822 }
1823 }
1824
1825 // Drawable case
1826 // Handle all the drawable glyphs - usually large or perspective color glyphs.
1827 if (!source.empty()) {
1828 auto [strikeSpec, strikeToSourceScale] =
1829 SkStrikeSpec::MakePath(runFont, runPaint, deviceProps, scalerContextFlags);
1830
1831 if (!SkScalarNearlyZero(strikeToSourceScale)) {
1832 sk_sp<StrikeForGPU> strike = strikeSpec.findOrCreateScopedStrike(strikeCache);
1833
1834 auto acceptedBuffer = SkMakeZip(acceptedGlyphIDs, acceptedPositions);
1835 auto [accepted, rejected] =
1836 prepare_for_drawable_drawing(strike.get(), source, acceptedBuffer, rejectedBuffer);
1837 source = rejected;
1838
1839 if (creationBehavior == kAddSubRuns && !accepted.empty()) {
1840 container->fSubRuns.append(
1841 DrawableSubRun::Make(
1842 accepted,
1843 strikeToSourceScale,
1844 strike->strikePromise(),
1845 alloc));
1846 }
1847 }
1848 }
1849
1850 // Path case
1851 // Handle path subruns. Mainly, large or large perspective glyphs with no color.
1852 if (!source.empty()) {
1853 auto [strikeSpec, strikeToSourceScale] =
1854 SkStrikeSpec::MakePath(runFont, runPaint, deviceProps, scalerContextFlags);
1855
1856 if (!SkScalarNearlyZero(strikeToSourceScale)) {
1857 sk_sp<StrikeForGPU> strike = strikeSpec.findOrCreateScopedStrike(strikeCache);
1858
1859 auto acceptedBuffer = SkMakeZip(acceptedGlyphIDs, acceptedPositions);
1860 auto [accepted, rejected] =
1861 prepare_for_path_drawing(strike.get(), source, acceptedBuffer, rejectedBuffer);
1862 source = rejected;
1863
1864 if (creationBehavior == kAddSubRuns && !accepted.empty()) {
1865 container->fSubRuns.append(
1866 PathSubRun::Make(accepted,
1867 has_some_antialiasing(runFont),
1868 strikeToSourceScale,
1869 strike->strikePromise(),
1870 alloc));
1871 }
1872 }
1873 }
1874
1875 // Drawing of last resort case
1876 // Draw all the rest of the rejected glyphs from above. This scales out of the atlas to
1877 // the screen, so quality will suffer. This mainly handles large color or perspective
1878 // color not handled by Drawables.
1879 if (!source.empty() && !SkScalarNearlyZero(approximateDeviceTextSize)) {
1880 // Creation matrix will be changed below to meet the following criteria:
1881 // * No perspective - the font scaler and the strikes can't handle perspective masks.
1882 // * Fits atlas - creationMatrix will be conditioned so that the maximum glyph
1883 // dimension for this run will be < kMaxBilerpAtlasDimension.
1884 SkMatrix creationMatrix = positionMatrix;
1885
1886 // Condition creationMatrix for perspective.
1887 if (creationMatrix.hasPerspective()) {
1888 // Find a scale factor that reduces pixelation caused by keystoning.
1889 SkPoint center = glyphRunList.sourceBounds().center();
1890 SkScalar maxAreaScale = SkMatrixPriv::DifferentialAreaScale(creationMatrix, center);
1891 SkScalar perspectiveFactor = 1;
1892 if (SkIsFinite(maxAreaScale) && !SkScalarNearlyZero(maxAreaScale)) {
1893 perspectiveFactor = SkScalarSqrt(maxAreaScale);
1894 }
1895
1896 // Masks can not be created in perspective. Create a non-perspective font with a
1897 // scale that will support the perspective keystoning.
1898 creationMatrix = SkMatrix::Scale(perspectiveFactor, perspectiveFactor);
1899 }
1900
1901 // Reduce to make a one pixel border for the bilerp padding.
1902 static const constexpr SkScalar kMaxBilerpAtlasDimension =
1904
1905 // Get the raw glyph IDs to simulate device drawing to figure the maximum device
1906 // dimension.
1907 const SkSpan<const SkGlyphID> glyphs = get_glyphIDs(source);
1908
1909 // maxGlyphDimension always returns an integer even though the return type is SkScalar.
1910 auto maxGlyphDimension = [&](const SkMatrix& m) {
1912 runFont, runPaint, deviceProps, scalerContextFlags, m);
1913 const sk_sp<StrikeForGPU> gaugingStrike =
1914 strikeSpec.findOrCreateScopedStrike(strikeCache);
1915 const SkScalar maxDimension =
1916 find_maximum_glyph_dimension(gaugingStrike.get(), glyphs);
1917 // TODO: There is a problem where a small character (say .) and a large
1918 // character (say M) are in the same run. If the run is scaled to be very
1919 // large, then the M may return 0 because its dimensions are > 65535, but
1920 // the small character produces regular result because its largest dimension
1921 // is < 65535. This will create an improper scale factor causing the M to be
1922 // too large to fit in the atlas. Tracked by skia:13714.
1923 return maxDimension;
1924 };
1925
1926 // Condition the creationMatrix so that glyphs fit in the atlas.
1927 for (SkScalar maxDimension = maxGlyphDimension(creationMatrix);
1928 kMaxBilerpAtlasDimension < maxDimension;
1929 maxDimension = maxGlyphDimension(creationMatrix))
1930 {
1931 // The SkScalerContext has a limit of 65536 maximum dimension.
1932 // reductionFactor will always be < 1 because
1933 // maxDimension > kMaxBilerpAtlasDimension, and because maxDimension will always
1934 // be an integer the reduction factor will always be at most 254 / 255.
1935 SkScalar reductionFactor = kMaxBilerpAtlasDimension / maxDimension;
1936 creationMatrix.postScale(reductionFactor, reductionFactor);
1937 }
1938
1939 // Draw using the creationMatrix.
1941 runFont, runPaint, deviceProps, scalerContextFlags, creationMatrix);
1942
1943 sk_sp<StrikeForGPU> strike = strikeSpec.findOrCreateScopedStrike(strikeCache);
1944
1945 auto acceptedBuffer =
1946 SkMakeZip(acceptedPackedGlyphIDs, acceptedPositions, acceptedFormats);
1947 auto [accepted, rejected, creationBounds] =
1948 prepare_for_mask_drawing(
1949 strike.get(), creationMatrix, source, acceptedBuffer, rejectedBuffer);
1950 source = rejected;
1951
1952 if (creationBehavior == kAddSubRuns && !accepted.empty()) {
1953
1954 auto addGlyphsWithSameFormat =
1955 [&, bounds = creationBounds](
1958 container->fSubRuns.append(
1959 TransformedMaskSubRun::Make(subrun,
1960 container->initialPosition(),
1961 strike->strikePromise(),
1962 creationMatrix,
1963 bounds,
1964 format,
1965 alloc));
1966 };
1967 add_multi_mask_format(addGlyphsWithSameFormat, accepted);
1968 }
1969 }
1970 }
1971
1972 return container;
1973}
1974
1976 SkPoint drawOrigin,
1977 const SkPaint& paint,
1978 const SkRefCnt* subRunStorage,
1979 const AtlasDrawDelegate& atlasDelegate) const {
1980 for (auto& subRun : fSubRuns) {
1981 subRun.draw(canvas, drawOrigin, paint, sk_ref_sp(subRunStorage), atlasDelegate);
1982 }
1983}
1984
1985bool SubRunContainer::canReuse(const SkPaint& paint, const SkMatrix& positionMatrix) const {
1986 for (const SubRun& subRun : fSubRuns) {
1987 if (!subRun.canReuse(paint, positionMatrix)) {
1988 return false;
1989 }
1990 }
1991 return true;
1992}
1993
1994// Returns the empty span if there is a problem reading the positions.
1995SkSpan<SkPoint> MakePointsFromBuffer(SkReadBuffer& buffer, SubRunAllocator* alloc) {
1996 uint32_t glyphCount = buffer.getArrayCount();
1997
1998 // Zero indicates a problem with serialization.
1999 if (!buffer.validate(glyphCount != 0)) { return {}; }
2000
2001 // Check that the count will not overflow the arena.
2002 if (!buffer.validate(glyphCount <= INT_MAX &&
2003 BagOfBytes::WillCountFit<SkPoint>(glyphCount))) { return {}; }
2004
2005 SkPoint* positionsData = alloc->makePODArray<SkPoint>(glyphCount);
2006 if (!buffer.readPointArray(positionsData, glyphCount)) { return {}; }
2007 return {positionsData, glyphCount};
2008}
2009
2010} // namespace sktext::gpu
uint16_t glyphs[5]
int count
uint32_t GrColor
Definition GrColor.h:25
@ kGammaCorrect_DistanceFieldEffectFlag
@ kUseLCD_DistanceFieldEffectFlag
@ kPerspective_DistanceFieldEffectFlag
@ kSimilarity_DistanceFieldEffectFlag
@ kBGR_DistanceFieldEffectFlag
@ kScaleOnly_DistanceFieldEffectFlag
@ kAliased_DistanceFieldEffectFlag
SkPoint pos
SkColor4f color
#define SkDEBUGFAIL(message)
Definition SkAssert.h:118
#define SkASSERT(cond)
Definition SkAssert.h:116
#define SK_DistanceFieldInset
constexpr SkEnumerate< Iter > SkMakeEnumerate(C &c)
static bool SkIsFinite(T x, Pack... values)
static bool isLCD(const SkScalerContextRec &rec)
bool SkPaintToGrPaintReplaceShader(GrRecordingContext *context, const GrColorInfo &dstColorInfo, const SkPaint &skPaint, const SkMatrix &ctm, std::unique_ptr< GrFragmentProcessor > shaderFP, const SkSurfaceProps &surfaceProps, GrPaint *grPaint)
Definition SkGr.cpp:570
bool SkPaintToGrPaint(GrRecordingContext *context, const GrColorInfo &dstColorInfo, const SkPaint &skPaint, const SkMatrix &ctm, const SkSurfaceProps &surfaceProps, GrPaint *grPaint)
Definition SkGr.cpp:553
static std::unique_ptr< SkEncoder > Make(SkWStream *dst, const SkPixmap *src, const SkYUVAPixmaps *srcYUVA, const SkColorSpace *srcYUVAColorSpace, const SkJpegEncoder::Options &options)
SkMaskFilterBase * as_MFB(SkMaskFilter *mf)
static SkPath clip(const SkPath &path, const SkHalfPlane &plane)
Definition SkPath.cpp:3824
sk_sp< T > sk_ref_sp(T *obj)
Definition SkRefCnt.h:381
#define SkScalarFloorToScalar(x)
Definition SkScalar.h:30
static bool SkScalarNearlyZero(SkScalar x, SkScalar tolerance=SK_ScalarNearlyZero)
Definition SkScalar.h:101
#define SkScalarSqrt(x)
Definition SkScalar.h:42
SkScalerContextFlags
static bool SkPixelGeometryIsH(SkPixelGeometry geo)
static bool SkPixelGeometryIsBGR(SkPixelGeometry geo)
constexpr int SkCount(const Container &c)
Definition SkTLogic.h:54
constexpr int SkToInt(S x)
Definition SkTo.h:29
uint16_t SkGlyphID
Definition SkTypes.h:179
constexpr auto SkMakeZip(Ts &&... ts)
Definition SkZip.h:212
static SkScalar center(float pos0, float pos1)
static void draw(SkCanvas *canvas, SkRect &target, int x, int y)
Definition aaclip.cpp:27
static bool IsPixelAligned(const SkRect &rect)
Definition GrClip.h:202
bool isLinearlyBlended() const
std::unique_ptr< GrOp > Owner
Definition GrOp.h:72
const SkPMColor4f & getColor4f() const
Definition GrPaint.h:51
bool empty() const
Definition SkBitmap.h:210
int saveLayer(const SkRect *bounds, const SkPaint *paint)
Definition SkCanvas.cpp:500
void drawPath(const SkPath &path, const SkPaint &paint)
void concat(const SkMatrix &matrix)
static sk_sp< SkPathEffect > Make(const SkScalar intervals[], int count, SkScalar phase)
SkRect getBounds()
void draw(SkCanvas *, const SkMatrix *=nullptr)
static SkScalar ApproximateTransformedTextSize(const SkFont &font, const SkMatrix &matrix, const SkPoint &textLocation)
Definition SkFont.cpp:366
Edging getEdging() const
Definition SkFont.h:180
Edging
Definition SkFont.h:39
@ kAntiAlias
may have transparent pixels on glyph edges
@ kSubpixelAntiAlias
glyph positioned in pixel using transparency
SkGlyphRect bounds() const
Definition SkGlyph.h:362
SkMask::Format maskFormat() const
Definition SkGlyph.h:341
uint16_t maxDimension() const
Definition SkGlyph.h:349
static constexpr uint16_t kSkSideTooBigForAtlas
Definition SkGlyph.h:333
skglyph::GlyphAction actionFor(skglyph::ActionType actionType) const
Definition SkGlyph.h:343
SkPoint leftTop() const
Definition SkGlyph.h:275
SkRect rect() const
Definition SkGlyph.h:259
SkGlyphRect offset(SkScalar x, SkScalar y) const
Definition SkGlyph.h:262
SkGlyphRect inset(SkScalar dx, SkScalar dy) const
Definition SkGlyph.h:272
virtual bool asABlur(BlurRec *) const
static sk_sp< SkMaskFilter > MakeBlur(SkBlurStyle style, SkScalar sigma, bool respectCTM=true)
static SkScalar DifferentialAreaScale(const SkMatrix &m, const SkPoint &p)
static SkMatrix Scale(SkScalar sx, SkScalar sy)
Definition SkMatrix.h:75
SkMatrix & postTranslate(SkScalar dx, SkScalar dy)
Definition SkMatrix.cpp:281
SkMatrix & postScale(SkScalar sx, SkScalar sy, SkScalar px, SkScalar py)
Definition SkMatrix.cpp:360
SkPoint mapPoint(SkPoint pt) const
Definition SkMatrix.h:1374
static const SkMatrix & I()
SkMatrix & preTranslate(SkScalar dx, SkScalar dy)
Definition SkMatrix.cpp:263
bool isScaleTranslate() const
Definition SkMatrix.h:236
SkScalar getMaxScale() const
bool hasPerspective() const
Definition SkMatrix.h:312
bool isSimilarity(SkScalar tol=SK_ScalarNearlyZero) const
Definition SkMatrix.cpp:180
bool mapRect(SkRect *dst, const SkRect &src, SkApplyPerspectiveClip pc=SkApplyPerspectiveClip::kYes) const
static SkColor ComputeLuminanceColor(const SkPaint &)
Style getStyle() const
Definition SkPaint.h:204
@ kStroke_Style
set to stroke geometry
Definition SkPaint.h:194
SkScalar getStrokeWidth() const
Definition SkPaint.h:300
@ kDash_DashType
fills in all of the info parameter
SkPath & setIsVolatile(bool isVolatile)
Definition SkPath.h:370
void transform(const SkMatrix &matrix, SkPath *dst, SkApplyPerspectiveClip pc=SkApplyPerspectiveClip::kYes) const
Definition SkPath.cpp:1647
constexpr bool empty() const
Definition SkSpan_impl.h:96
static SkStrikeCache * GlobalStrikeCache()
sk_sp< sktext::StrikeForGPU > findOrCreateScopedStrike(sktext::StrikeForGPUCacheInterface *cache) const
static SkStrikeSpec MakeTransformMask(const SkFont &font, const SkPaint &paint, const SkSurfaceProps &surfaceProps, SkScalerContextFlags scalerContextFlags, const SkMatrix &deviceMatrix)
static SkStrikeSpec MakeMask(const SkFont &font, const SkPaint &paint, const SkSurfaceProps &surfaceProps, SkScalerContextFlags scalerContextFlags, const SkMatrix &deviceMatrix)
static std::tuple< SkStrikeSpec, SkScalar > MakePath(const SkFont &font, const SkPaint &paint, const SkSurfaceProps &surfaceProps, SkScalerContextFlags scalerContextFlags)
bool isHairlineStyle() const
Definition SkStrokeRec.h:47
bool isFillStyle() const
Definition SkStrokeRec.h:51
SkPixelGeometry pixelGeometry() const
Definition SkZip.h:25
constexpr SkZip last(size_t n) const
Definition SkZip.h:91
constexpr size_t size() const
Definition SkZip.h:76
constexpr SkZip first(size_t n) const
Definition SkZip.h:86
constexpr SkZip subspan(size_t offset, size_t count) const
Definition SkZip.h:96
constexpr bool empty() const
Definition SkZip.h:77
constexpr auto get() const
Definition SkZip.h:82
T * get() const
Definition SkRefCnt.h:303
GrRecordingContext * recordingContext() const
const GrColorInfo & colorInfo() const
const SkSurfaceProps & surfaceProps() const
void resize(size_t count)
Definition SkTArray.h:418
SkRect sourceBounds() const
Definition GlyphRun.h:115
size_t maxGlyphRunSize() const
Definition GlyphRun.h:97
size_t runCount() const
Definition GlyphRun.h:89
size_t totalGlyphCount() const
Definition GlyphRun.h:90
static std::optional< SkStrikePromise > MakeFromBuffer(SkReadBuffer &buffer, const SkStrikeClient *client, SkStrikeCache *strikeCache)
virtual SkGlyphDigest digestFor(skglyph::ActionType, SkPackedGlyphID)=0
virtual const SkGlyphPositionRoundingSpec & roundingSpec() const =0
virtual skgpu::MaskFormat maskFormat() const =0
virtual const VertexFiller & vertexFiller() const =0
virtual unsigned short instanceFlags() const =0
virtual int glyphSrcPadding() const =0
virtual void testingOnly_packedGlyphIDToGlyph(StrikeCache *cache) const =0
virtual std::tuple< bool, int > regenerateAtlas(int begin, int end, RegenerateAtlasDelegate) const =0
virtual int glyphCount() const =0
static GlyphVector Make(SkStrikePromise &&promise, SkSpan< const SkPackedGlyphID > glyphs, SubRunAllocator *alloc)
static size_t GlyphVectorSize(size_t count)
Definition GlyphVector.h:72
static std::optional< GlyphVector > MakeFromBuffer(SkReadBuffer &buffer, const SkStrikeClient *strikeClient, SubRunAllocator *alloc)
static skgpu::MaskFormat FormatFromSkGlyph(SkMask::Format format)
Definition Glyph.h:19
static sk_sp< SkMaskFilter > Make()
std::tuple< SkFont, SkScalar, SDFTMatrixRange > getSDFFont(const SkFont &font, const SkMatrix &viewMatrix, const SkPoint &textLocation) const
bool isSDFT(SkScalar approximateDeviceTextSize, const SkPaint &paint, const SkMatrix &matrix) const
SkScalar maxSize() const
Definition SDFTControl.h:54
static SDFTMatrixRange MakeFromBuffer(SkReadBuffer &buffer)
std::unique_ptr< T[], ArrayDestroyer > makeUniqueArray(int n)
std::unique_ptr< T, Destroyer > makeUnique(Args &&... args)
SkSpan< T > makePODSpan(SkSpan< const T > s)
static SubRunContainerOwner MakeInAlloc(const GlyphRunList &glyphRunList, const SkMatrix &positionMatrix, const SkPaint &runPaint, SkStrikeDeviceInfo strikeDeviceInfo, StrikeForGPUCacheInterface *strikeCache, sktext::gpu::SubRunAllocator *alloc, SubRunCreationBehavior creationBehavior, const char *tag)
static size_t EstimateAllocSize(const GlyphRunList &glyphRunList)
void flattenAllocSizeHint(SkWriteBuffer &buffer) const
static SubRunContainerOwner MakeFromBufferInAlloc(SkReadBuffer &buffer, const SkStrikeClient *client, SubRunAllocator *alloc)
void draw(SkCanvas *, SkPoint drawOrigin, const SkPaint &, const SkRefCnt *subRunStorage, const AtlasDrawDelegate &) const
void flattenRuns(SkWriteBuffer &buffer) const
static int AllocSizeHintFromBuffer(SkReadBuffer &buffer)
bool canReuse(const SkPaint &paint, const SkMatrix &positionMatrix) const
virtual void doFlatten(SkWriteBuffer &buffer) const =0
virtual int unflattenSize() const =0
virtual void draw(SkCanvas *, SkPoint drawOrigin, const SkPaint &, sk_sp< SkRefCnt > subRunStorage, const AtlasDrawDelegate &) const =0
virtual bool canReuse(const SkPaint &paint, const SkMatrix &positionMatrix) const =0
void flatten(SkWriteBuffer &buffer) const
virtual SubRunStreamTag subRunStreamTag() const =0
virtual const AtlasSubRun * testingOnly_atlasSubRun() const =0
static SubRunOwner MakeFromBuffer(SkReadBuffer &buffer, sktext::gpu::SubRunAllocator *alloc, const SkStrikeClient *client)
static std::optional< VertexFiller > MakeFromBuffer(SkReadBuffer &buffer, SubRunAllocator *alloc)
static VertexFiller Make(skgpu::MaskFormat maskType, const SkMatrix &creationMatrix, SkRect creationBounds, SkSpan< const SkPoint > positions, SubRunAllocator *alloc, FillerType fillerType)
std::tuple< bool, SkRect > deviceRectAndCheckTransform(const SkMatrix &positionMatrix) const
void flatten(SkWriteBuffer &buffer) const
const Paint & paint
static const char * begin(const StringSlice &s)
Definition editor.cpp:252
SkBitmap source
Definition examples.cpp:28
float SkScalar
Definition extension.cpp:12
struct MyStruct a[10]
FlutterSemanticsFlag flags
glong glong end
static const uint8_t buffer[]
GAsyncResult * result
uint32_t uint32_t * format
std::unique_ptr< SubRun, SubRunAllocator::Destroyer > SubRunOwner
std::function< std::tuple< bool, int >(GlyphVector *, int begin, int end, skgpu::MaskFormat, int padding)> RegenerateAtlasDelegate
std::unique_ptr< SubRunContainer, SubRunAllocator::Destroyer > SubRunContainerOwner
std::function< void(const sktext::gpu::AtlasSubRun *subRun, SkPoint drawOrigin, const SkPaint &paint, sk_sp< SkRefCnt > subRunStorage, sktext::gpu::RendererData)> AtlasDrawDelegate
SkMatrix position_matrix(const SkMatrix &drawMatrix, SkPoint drawOrigin)
Definition SlugImpl.cpp:69
Point offset
const SkVector halfAxisSampleFreq
Definition SkGlyph.h:233
const SkIPoint ignorePositionFieldMask
Definition SkGlyph.h:235
static constexpr SkIRect MakeEmpty()
Definition SkRect.h:45
bool isEmpty() const
Definition SkRect.h:202
SkScalar fPhase
Offset into the dashed interval pattern.
int32_t fCount
Number of intervals in the dash. Should be even number.
SkScalar * fIntervals
Length of on/off intervals for dashed lines.
constexpr float y() const
constexpr float x() const
float fA
alpha component
Definition SkColor.h:266
bool intersects(const SkRect &r) const
Definition SkRect.h:1121
void round(SkIRect *dst) const
Definition SkRect.h:1228
constexpr SkPoint center() const
Definition SkRect.h:792
static constexpr SkRect MakeWH(float w, float h)
Definition SkRect.h:609
const SkSurfaceProps fSurfaceProps
Definition SkDevice.h:80
const SkScalerContextFlags fScalerContextFlags
Definition SkDevice.h:81
const sktext::gpu::SDFTControl *const fSDFTControl
Definition SkDevice.h:83