1 /*
2 * Copyright 2015 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "src/text/gpu/TextBlob.h"
9
10 #include "include/core/SkMatrix.h"
11 #include "include/core/SkScalar.h"
12 #include "include/private/base/SkTemplates.h"
13 #include "include/private/chromium/SkChromeRemoteGlyphCache.h"
14 #include "include/private/chromium/Slug.h"
15 #include "src/core/SkFontPriv.h"
16 #include "src/core/SkMaskFilterBase.h"
17 #include "src/core/SkMatrixProvider.h"
18 #include "src/core/SkPaintPriv.h"
19 #include "src/core/SkReadBuffer.h"
20 #include "src/core/SkRectPriv.h"
21 #include "src/core/SkStrikeCache.h"
22 #include "src/core/SkWriteBuffer.h"
23 #include "src/text/GlyphRun.h"
24 #include "src/text/gpu/SubRunAllocator.h"
25 #include "src/text/gpu/SubRunContainer.h"
26
27 #if defined(SK_GANESH) // Ganesh Support
28 #include "src/gpu/ganesh/Device_v1.h"
29 #include "src/gpu/ganesh/GrClip.h"
30 #include "src/gpu/ganesh/GrRecordingContextPriv.h"
31 #include "src/gpu/ganesh/SurfaceDrawContext.h"
32 #endif
33
34 using namespace sktext::gpu;
35 namespace {
position_matrix(const SkMatrix & drawMatrix,SkPoint drawOrigin)36 SkMatrix position_matrix(const SkMatrix& drawMatrix, SkPoint drawOrigin) {
37 SkMatrix position_matrix = drawMatrix;
38 return position_matrix.preTranslate(drawOrigin.x(), drawOrigin.y());
39 }
40
41 // Check for integer translate with the same 2x2 matrix.
42 // Returns the translation, and true if the change from initial matrix to the position matrix
43 // support using direct glyph masks.
can_use_direct(const SkMatrix & initialPositionMatrix,const SkMatrix & positionMatrix)44 std::tuple<bool, SkVector> can_use_direct(
45 const SkMatrix& initialPositionMatrix, const SkMatrix& positionMatrix) {
46 // The existing direct glyph info can be used if the initialPositionMatrix, and the
47 // positionMatrix have the same 2x2, and the translation between them is integer.
48 // Calculate the translation in source space to a translation in device space by mapping
49 // (0, 0) through both the initial position matrix and the position matrix; take the difference.
50 SkVector translation = positionMatrix.mapOrigin() - initialPositionMatrix.mapOrigin();
51 return {initialPositionMatrix.getScaleX() == positionMatrix.getScaleX() &&
52 initialPositionMatrix.getScaleY() == positionMatrix.getScaleY() &&
53 initialPositionMatrix.getSkewX() == positionMatrix.getSkewX() &&
54 initialPositionMatrix.getSkewY() == positionMatrix.getSkewY() &&
55 SkScalarIsInt(translation.x()) && SkScalarIsInt(translation.y()),
56 translation};
57 }
58
59
compute_canonical_color(const SkPaint & paint,bool lcd)60 static SkColor compute_canonical_color(const SkPaint& paint, bool lcd) {
61 SkColor canonicalColor = SkPaintPriv::ComputeLuminanceColor(paint);
62 if (lcd) {
63 // This is the correct computation for canonicalColor, but there are tons of cases where LCD
64 // can be modified. For now we just regenerate if any run in a textblob has LCD.
65 // TODO figure out where all of these modifications are and see if we can incorporate that
66 // logic at a higher level *OR* use sRGB
67 //canonicalColor = SkMaskGamma::CanonicalColor(canonicalColor);
68
69 // TODO we want to figure out a way to be able to use the canonical color on LCD text,
70 // see the note above. We pick a placeholder value for LCD text to ensure we always match
71 // the same key
72 return SK_ColorTRANSPARENT;
73 } else {
74 // A8, though can have mixed BMP text but it shouldn't matter because BMP text won't have
75 // gamma corrected masks anyways, nor color
76 U8CPU lum = SkComputeLuminance(SkColorGetR(canonicalColor),
77 SkColorGetG(canonicalColor),
78 SkColorGetB(canonicalColor));
79 // reduce to our finite number of bits
80 canonicalColor = SkMaskGamma::CanonicalColor(SkColorSetRGB(lum, lum, lum));
81 }
82 return canonicalColor;
83 }
84
85 // -- SlugImpl -------------------------------------------------------------------------------------
86 class SlugImpl final : public Slug {
87 public:
88 SlugImpl(SubRunAllocator&& alloc,
89 SubRunContainerOwner subRuns,
90 SkRect sourceBounds,
91 const SkPaint& paint,
92 SkPoint origin);
93 ~SlugImpl() override = default;
94
95 static sk_sp<SlugImpl> Make(const SkMatrixProvider& viewMatrix,
96 const sktext::GlyphRunList& glyphRunList,
97 const SkPaint& initialPaint,
98 const SkPaint& drawingPaint,
99 SkStrikeDeviceInfo strikeDeviceInfo,
100 sktext::StrikeForGPUCacheInterface* strikeCache);
101 static sk_sp<Slug> MakeFromBuffer(SkReadBuffer& buffer,
102 const SkStrikeClient* client);
103 void doFlatten(SkWriteBuffer& buffer) const override;
104
105 #if defined(SK_GANESH)
106 void surfaceDraw(SkCanvas*,
107 const GrClip* clip,
108 const SkMatrixProvider& viewMatrix,
109 const SkPaint& paint,
110 skgpu::v1::SurfaceDrawContext* sdc) const;
111 #endif
112
sourceBounds() const113 SkRect sourceBounds() const override { return fSourceBounds; }
sourceBoundsWithOrigin() const114 SkRect sourceBoundsWithOrigin() const override { return fSourceBounds.makeOffset(fOrigin); }
initialPaint() const115 const SkPaint& initialPaint() const override { return fInitialPaint; }
116
initialPositionMatrix() const117 const SkMatrix& initialPositionMatrix() const { return fSubRuns->initialPosition(); }
origin() const118 SkPoint origin() const { return fOrigin; }
119
120 // Change memory management to handle the data after Slug, but in the same allocation
121 // of memory. Only allow placement new.
operator delete(void * p)122 void operator delete(void* p) { ::operator delete(p); }
operator new(size_t)123 void* operator new(size_t) { SK_ABORT("All slugs are created by placement new."); }
operator new(size_t,void * p)124 void* operator new(size_t, void* p) { return p; }
125
126 private:
127 // The allocator must come first because it needs to be destroyed last. Other fields of this
128 // structure may have pointers into it.
129 SubRunAllocator fAlloc;
130 SubRunContainerOwner fSubRuns;
131 const SkRect fSourceBounds;
132 const SkPaint fInitialPaint;
133 const SkMatrix fInitialPositionMatrix;
134 const SkPoint fOrigin;
135 };
136
SlugImpl(SubRunAllocator && alloc,SubRunContainerOwner subRuns,SkRect sourceBounds,const SkPaint & paint,SkPoint origin)137 SlugImpl::SlugImpl(SubRunAllocator&& alloc,
138 SubRunContainerOwner subRuns,
139 SkRect sourceBounds,
140 const SkPaint& paint,
141 SkPoint origin)
142 : fAlloc {std::move(alloc)}
143 , fSubRuns(std::move(subRuns))
144 , fSourceBounds{sourceBounds}
145 , fInitialPaint{paint}
146 , fOrigin{origin} {}
147
148 #if defined(SK_GANESH)
surfaceDraw(SkCanvas * canvas,const GrClip * clip,const SkMatrixProvider & viewMatrix,const SkPaint & drawingPaint,skgpu::v1::SurfaceDrawContext * sdc) const149 void SlugImpl::surfaceDraw(SkCanvas* canvas, const GrClip* clip, const SkMatrixProvider& viewMatrix,
150 const SkPaint& drawingPaint, skgpu::v1::SurfaceDrawContext* sdc) const {
151 fSubRuns->draw(canvas, clip, viewMatrix, fOrigin, drawingPaint, this, sdc);
152 }
153 #endif
154
doFlatten(SkWriteBuffer & buffer) const155 void SlugImpl::doFlatten(SkWriteBuffer& buffer) const {
156 buffer.writeRect(fSourceBounds);
157 SkPaintPriv::Flatten(fInitialPaint, buffer);
158 buffer.writePoint(fOrigin);
159 fSubRuns->flattenAllocSizeHint(buffer);
160 fSubRuns->flattenRuns(buffer);
161 }
162
MakeFromBuffer(SkReadBuffer & buffer,const SkStrikeClient * client)163 sk_sp<Slug> SlugImpl::MakeFromBuffer(SkReadBuffer& buffer, const SkStrikeClient* client) {
164 SkRect sourceBounds = buffer.readRect();
165 SkASSERT(!sourceBounds.isEmpty());
166 if (!buffer.validate(!sourceBounds.isEmpty())) { return nullptr; }
167 SkPaint paint = buffer.readPaint();
168 SkPoint origin = buffer.readPoint();
169 int allocSizeHint = SubRunContainer::AllocSizeHintFromBuffer(buffer);
170
171 auto [initializer, _, alloc] =
172 SubRunAllocator::AllocateClassMemoryAndArena<SlugImpl>(allocSizeHint);
173
174 SubRunContainerOwner container = SubRunContainer::MakeFromBufferInAlloc(buffer, client, &alloc);
175
176 // Something went wrong while reading.
177 SkASSERT(buffer.isValid());
178 if (!buffer.isValid()) { return nullptr;}
179
180 return sk_sp<SlugImpl>(initializer.initialize(
181 std::move(alloc), std::move(container), sourceBounds, paint, origin));
182 }
183
Make(const SkMatrixProvider & viewMatrix,const sktext::GlyphRunList & glyphRunList,const SkPaint & initialPaint,const SkPaint & drawingPaint,SkStrikeDeviceInfo strikeDeviceInfo,sktext::StrikeForGPUCacheInterface * strikeCache)184 sk_sp<SlugImpl> SlugImpl::Make(const SkMatrixProvider& viewMatrix,
185 const sktext::GlyphRunList& glyphRunList,
186 const SkPaint& initialPaint,
187 const SkPaint& drawingPaint,
188 SkStrikeDeviceInfo strikeDeviceInfo,
189 sktext::StrikeForGPUCacheInterface* strikeCache) {
190 size_t subRunSizeHint = SubRunContainer::EstimateAllocSize(glyphRunList);
191 auto [initializer, _, alloc] =
192 SubRunAllocator::AllocateClassMemoryAndArena<SlugImpl>(subRunSizeHint);
193
194 const SkMatrix positionMatrix =
195 position_matrix(viewMatrix.localToDevice(), glyphRunList.origin());
196
197 auto subRuns = SubRunContainer::MakeInAlloc(glyphRunList,
198 positionMatrix,
199 drawingPaint,
200 strikeDeviceInfo,
201 strikeCache,
202 &alloc,
203 SubRunContainer::kAddSubRuns,
204 "Make Slug");
205
206 sk_sp<SlugImpl> slug = sk_sp<SlugImpl>(initializer.initialize(
207 std::move(alloc),
208 std::move(subRuns),
209 glyphRunList.sourceBounds(),
210 initialPaint,
211 glyphRunList.origin()));
212
213 // There is nothing to draw here. This is particularly a problem with RSX form blobs where a
214 // single space becomes a run with no glyphs.
215 if (slug->fSubRuns->isEmpty()) { return nullptr; }
216
217 return slug;
218 }
219 } // namespace
220
221 namespace sktext::gpu {
222 // -- TextBlob::Key ------------------------------------------------------------------------------
Make(const GlyphRunList & glyphRunList,const SkPaint & paint,const SkMatrix & drawMatrix,const SkStrikeDeviceInfo & strikeDevice)223 auto TextBlob::Key::Make(const GlyphRunList& glyphRunList,
224 const SkPaint& paint,
225 const SkMatrix& drawMatrix,
226 const SkStrikeDeviceInfo& strikeDevice) -> std::tuple<bool, Key> {
227 SkASSERT(strikeDevice.fSDFTControl != nullptr);
228 SkMaskFilterBase::BlurRec blurRec;
229 // It might be worth caching these things, but its not clear at this time
230 // TODO for animated mask filters, this will fill up our cache. We need a safeguard here
231 const SkMaskFilter* maskFilter = paint.getMaskFilter();
232 bool canCache = glyphRunList.canCache() &&
233 !(paint.getPathEffect() ||
234 (maskFilter && !as_MFB(maskFilter)->asABlur(&blurRec)));
235
236 TextBlob::Key key;
237 if (canCache) {
238 bool hasLCD = glyphRunList.anyRunsLCD();
239
240 // We canonicalize all non-lcd draws to use kUnknown_SkPixelGeometry
241 SkPixelGeometry pixelGeometry = hasLCD ? strikeDevice.fSurfaceProps.pixelGeometry()
242 : kUnknown_SkPixelGeometry;
243
244 SkColor canonicalColor = compute_canonical_color(paint, hasLCD);
245
246 key.fPixelGeometry = pixelGeometry;
247 key.fUniqueID = glyphRunList.uniqueID();
248 key.fStyle = paint.getStyle();
249 if (key.fStyle != SkPaint::kFill_Style) {
250 key.fFrameWidth = paint.getStrokeWidth();
251 key.fMiterLimit = paint.getStrokeMiter();
252 key.fJoin = paint.getStrokeJoin();
253 }
254 key.fHasBlur = maskFilter != nullptr;
255 if (key.fHasBlur) {
256 key.fBlurRec = blurRec;
257 }
258 key.fCanonicalColor = canonicalColor;
259 key.fScalerContextFlags = SkTo<uint32_t>(strikeDevice.fScalerContextFlags);
260
261 // Do any runs use direct drawing types?.
262 key.fHasSomeDirectSubRuns = false;
263 SkPoint glyphRunListLocation = glyphRunList.sourceBoundsWithOrigin().center();
264 for (auto& run : glyphRunList) {
265 SkScalar approximateDeviceTextSize =
266 SkFontPriv::ApproximateTransformedTextSize(run.font(), drawMatrix,
267 glyphRunListLocation);
268 key.fHasSomeDirectSubRuns |=
269 strikeDevice.fSDFTControl->isDirect(approximateDeviceTextSize, paint,
270 drawMatrix);
271 }
272
273 if (key.fHasSomeDirectSubRuns) {
274 // Store the fractional offset of the position. We know that the matrix can't be
275 // perspective at this point.
276 SkPoint mappedOrigin = drawMatrix.mapOrigin();
277 key.fPositionMatrix = drawMatrix;
278 key.fPositionMatrix.setTranslateX(
279 mappedOrigin.x() - SkScalarFloorToScalar(mappedOrigin.x()));
280 key.fPositionMatrix.setTranslateY(
281 mappedOrigin.y() - SkScalarFloorToScalar(mappedOrigin.y()));
282 } else {
283 // For path and SDFT, the matrix doesn't matter.
284 key.fPositionMatrix = SkMatrix::I();
285 }
286 }
287
288 return {canCache, key};
289 }
290
operator ==(const TextBlob::Key & that) const291 bool TextBlob::Key::operator==(const TextBlob::Key& that) const {
292 if (fUniqueID != that.fUniqueID) { return false; }
293 if (fCanonicalColor != that.fCanonicalColor) { return false; }
294 if (fStyle != that.fStyle) { return false; }
295 if (fStyle != SkPaint::kFill_Style) {
296 if (fFrameWidth != that.fFrameWidth ||
297 fMiterLimit != that.fMiterLimit ||
298 fJoin != that.fJoin) {
299 return false;
300 }
301 }
302 if (fPixelGeometry != that.fPixelGeometry) { return false; }
303 if (fHasBlur != that.fHasBlur) { return false; }
304 if (fHasBlur) {
305 if (fBlurRec.fStyle != that.fBlurRec.fStyle || fBlurRec.fSigma != that.fBlurRec.fSigma) {
306 return false;
307 }
308 }
309
310 if (fScalerContextFlags != that.fScalerContextFlags) { return false; }
311
312 // DirectSubRuns do not support perspective when used with a TextBlob. SDFT, Transformed,
313 // Path, and Drawable do support perspective.
314 if (fPositionMatrix.hasPerspective() && fHasSomeDirectSubRuns) { return false; }
315
316 if (fHasSomeDirectSubRuns != that.fHasSomeDirectSubRuns) { return false; }
317
318 if (fHasSomeDirectSubRuns) {
319 auto [compatible, _] = can_use_direct(fPositionMatrix, that.fPositionMatrix);
320 return compatible;
321 }
322
323 return true;
324 }
325
326 // -- TextBlob -----------------------------------------------------------------------------------
operator delete(void * p)327 void TextBlob::operator delete(void* p) { ::operator delete(p); }
operator new(size_t)328 void* TextBlob::operator new(size_t) { SK_ABORT("All blobs are created by placement new."); }
operator new(size_t,void * p)329 void* TextBlob::operator new(size_t, void* p) { return p; }
330
331 TextBlob::~TextBlob() = default;
332
Make(const GlyphRunList & glyphRunList,const SkPaint & paint,const SkMatrix & positionMatrix,SkStrikeDeviceInfo strikeDeviceInfo,StrikeForGPUCacheInterface * strikeCache)333 sk_sp<TextBlob> TextBlob::Make(const GlyphRunList& glyphRunList,
334 const SkPaint& paint,
335 const SkMatrix& positionMatrix,
336 SkStrikeDeviceInfo strikeDeviceInfo,
337 StrikeForGPUCacheInterface* strikeCache) {
338 size_t subRunSizeHint = SubRunContainer::EstimateAllocSize(glyphRunList);
339 auto [initializer, totalMemoryAllocated, alloc] =
340 SubRunAllocator::AllocateClassMemoryAndArena<TextBlob>(subRunSizeHint);
341
342 auto container = SubRunContainer::MakeInAlloc(
343 glyphRunList, positionMatrix, paint,
344 strikeDeviceInfo, strikeCache, &alloc, SubRunContainer::kAddSubRuns, "TextBlob");
345
346 SkColor initialLuminance = SkPaintPriv::ComputeLuminanceColor(paint);
347 sk_sp<TextBlob> blob = sk_sp<TextBlob>(initializer.initialize(std::move(alloc),
348 std::move(container),
349 totalMemoryAllocated,
350 initialLuminance));
351 return blob;
352 }
353
addKey(const Key & key)354 void TextBlob::addKey(const Key& key) {
355 fKey = key;
356 }
357
hasPerspective() const358 bool TextBlob::hasPerspective() const {
359 return fSubRuns->initialPosition().hasPerspective();
360 }
361
canReuse(const SkPaint & paint,const SkMatrix & positionMatrix) const362 bool TextBlob::canReuse(const SkPaint& paint, const SkMatrix& positionMatrix) const {
363 // A singular matrix will create a TextBlob with no SubRuns, but unknown glyphs can also
364 // cause empty runs. If there are no subRuns, then regenerate when the matrices don't match.
365 if (fSubRuns->isEmpty() && fSubRuns->initialPosition() != positionMatrix) {
366 return false;
367 }
368
369 // If we have LCD text then our canonical color will be set to transparent, in this case we have
370 // to regenerate the blob on any color change
371 // We use the grPaint to get any color filter effects
372 if (fKey.fCanonicalColor == SK_ColorTRANSPARENT &&
373 fInitialLuminance != SkPaintPriv::ComputeLuminanceColor(paint)) {
374 return false;
375 }
376
377 return fSubRuns->canReuse(paint, positionMatrix);
378 }
379
key() const380 const TextBlob::Key& TextBlob::key() const { return fKey; }
381
382 #if defined(SK_GANESH)
draw(SkCanvas * canvas,const GrClip * clip,const SkMatrixProvider & viewMatrix,SkPoint drawOrigin,const SkPaint & paint,skgpu::v1::SurfaceDrawContext * sdc)383 void TextBlob::draw(SkCanvas* canvas,
384 const GrClip* clip,
385 const SkMatrixProvider& viewMatrix,
386 SkPoint drawOrigin,
387 const SkPaint& paint,
388 skgpu::v1::SurfaceDrawContext* sdc) {
389 fSubRuns->draw(canvas, clip, viewMatrix, drawOrigin, paint, this, sdc);
390 }
391 #endif
392
393 #if defined(SK_GRAPHITE)
draw(SkCanvas * canvas,SkPoint drawOrigin,const SkPaint & paint,skgpu::graphite::Device * device)394 void TextBlob::draw(SkCanvas* canvas,
395 SkPoint drawOrigin,
396 const SkPaint& paint,
397 skgpu::graphite::Device* device) {
398 fSubRuns->draw(canvas, drawOrigin, paint, this, device);
399 }
400 #endif
401
402 #if GR_TEST_UTILS
403 struct SubRunContainerPeer {
getAtlasSubRunsktext::gpu::SubRunContainerPeer404 static const AtlasSubRun* getAtlasSubRun(const SubRunContainer& subRuns) {
405 if (subRuns.isEmpty()) {
406 return nullptr;
407 }
408 return subRuns.fSubRuns.front().testingOnly_atlasSubRun();
409 }
410 };
411 #endif
412
testingOnlyFirstSubRun() const413 const AtlasSubRun* TextBlob::testingOnlyFirstSubRun() const {
414 #if GR_TEST_UTILS
415 return SubRunContainerPeer::getAtlasSubRun(*fSubRuns);
416 #else
417 return nullptr;
418 #endif
419 }
420
TextBlob(SubRunAllocator && alloc,SubRunContainerOwner subRuns,int totalMemorySize,SkColor initialLuminance)421 TextBlob::TextBlob(SubRunAllocator&& alloc,
422 SubRunContainerOwner subRuns,
423 int totalMemorySize,
424 SkColor initialLuminance)
425 : fAlloc{std::move(alloc)}
426 , fSubRuns{std::move(subRuns)}
427 , fSize(totalMemorySize)
428 , fInitialLuminance{initialLuminance} { }
429
SkMakeSlugFromBuffer(SkReadBuffer & buffer,const SkStrikeClient * client)430 sk_sp<Slug> SkMakeSlugFromBuffer(SkReadBuffer& buffer, const SkStrikeClient* client) {
431 return SlugImpl::MakeFromBuffer(buffer, client);
432 }
433 } // namespace sktext::gpu
434
435 #if defined(SK_GANESH)
436 namespace skgpu::v1 {
437 sk_sp<Slug>
convertGlyphRunListToSlug(const sktext::GlyphRunList & glyphRunList,const SkPaint & initialPaint,const SkPaint & drawingPaint)438 Device::convertGlyphRunListToSlug(const sktext::GlyphRunList& glyphRunList,
439 const SkPaint& initialPaint,
440 const SkPaint& drawingPaint) {
441 return SlugImpl::Make(this->asMatrixProvider(),
442 glyphRunList,
443 initialPaint,
444 drawingPaint,
445 this->strikeDeviceInfo(),
446 SkStrikeCache::GlobalStrikeCache());
447 }
448
drawSlug(SkCanvas * canvas,const Slug * slug,const SkPaint & drawingPaint)449 void Device::drawSlug(SkCanvas* canvas, const Slug* slug, const SkPaint& drawingPaint) {
450 const SlugImpl* slugImpl = static_cast<const SlugImpl*>(slug);
451 auto matrixProvider = this->asMatrixProvider();
452 #if defined(SK_DEBUG)
453 if (!fContext->priv().options().fSupportBilerpFromGlyphAtlas) {
454 // We can draw a slug if the atlas has padding or if the creation matrix and the
455 // drawing matrix are the same. If they are the same, then the Slug will use the direct
456 // drawing code and not use bi-lerp.
457 SkMatrix slugMatrix = slugImpl->initialPositionMatrix();
458 SkMatrix positionMatrix = matrixProvider.localToDevice();
459 positionMatrix.preTranslate(slugImpl->origin().x(), slugImpl->origin().y());
460 SkASSERT(slugMatrix == positionMatrix);
461 }
462 #endif
463 slugImpl->surfaceDraw(
464 canvas, this->clip(), matrixProvider, drawingPaint, fSurfaceDrawContext.get());
465 }
466
MakeSlug(const SkMatrixProvider & drawMatrix,const sktext::GlyphRunList & glyphRunList,const SkPaint & initialPaint,const SkPaint & drawingPaint,SkStrikeDeviceInfo strikeDeviceInfo,sktext::StrikeForGPUCacheInterface * strikeCache)467 sk_sp<Slug> MakeSlug(const SkMatrixProvider& drawMatrix,
468 const sktext::GlyphRunList& glyphRunList,
469 const SkPaint& initialPaint,
470 const SkPaint& drawingPaint,
471 SkStrikeDeviceInfo strikeDeviceInfo,
472 sktext::StrikeForGPUCacheInterface* strikeCache) {
473 return SlugImpl::Make(
474 drawMatrix, glyphRunList, initialPaint, drawingPaint, strikeDeviceInfo, strikeCache);
475 }
476 } // namespace skgpu::v1
477 #endif
478