• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/gpu/ops/AtlasTextOp.h"
9 
10 #include "include/core/SkPoint3.h"
11 #include "include/core/SkSpan.h"
12 #include "include/gpu/GrRecordingContext.h"
13 #include "src/core/SkMathPriv.h"
14 #include "src/core/SkMatrixPriv.h"
15 #include "src/core/SkMatrixProvider.h"
16 #include "src/core/SkStrikeCache.h"
17 #include "src/gpu/GrCaps.h"
18 #include "src/gpu/GrMemoryPool.h"
19 #include "src/gpu/GrOpFlushState.h"
20 #include "src/gpu/GrRecordingContextPriv.h"
21 #include "src/gpu/GrResourceProvider.h"
22 #include "src/gpu/SkGr.h"
23 #include "src/gpu/effects/GrBitmapTextGeoProc.h"
24 #include "src/gpu/effects/GrDistanceFieldGeoProc.h"
25 #include "src/gpu/ops/GrSimpleMeshDrawOpHelper.h"
26 #include "src/gpu/text/GrAtlasManager.h"
27 #include "src/gpu/text/GrDistanceFieldAdjustTable.h"
28 #include "src/gpu/v1/SurfaceDrawContext_v1.h"
29 
30 #include <new>
31 #include <utility>
32 
33 namespace skgpu::v1 {
34 
35 // If we have thread local, then cache memory for a single AtlasTextOp.
36 static thread_local void* gCache = nullptr;
operator new(size_t s)37 void* AtlasTextOp::operator new(size_t s) {
38     if (gCache != nullptr) {
39         return std::exchange(gCache, nullptr);
40     }
41 
42     return ::operator new(s);
43 }
44 
operator delete(void * bytes)45 void AtlasTextOp::operator delete(void* bytes) noexcept {
46     if (gCache == nullptr) {
47         gCache = bytes;
48         return;
49     }
50     ::operator delete(bytes);
51 }
52 
ClearCache()53 void AtlasTextOp::ClearCache() {
54     ::operator delete(gCache);
55     gCache = nullptr;
56 }
57 
AtlasTextOp(MaskType maskType,bool needsTransform,int glyphCount,SkRect deviceRect,Geometry * geo,GrPaint && paint)58 AtlasTextOp::AtlasTextOp(MaskType maskType,
59                          bool needsTransform,
60                          int glyphCount,
61                          SkRect deviceRect,
62                          Geometry* geo,
63                          GrPaint&& paint)
64         : INHERITED{ClassID()}
65         , fProcessors(std::move(paint))
66         , fNumGlyphs(glyphCount)
67         , fDFGPFlags(0)
68         , fMaskType(static_cast<uint32_t>(maskType))
69         , fUsesLocalCoords(false)
70         , fNeedsGlyphTransform(needsTransform)
71         , fHasPerspective(needsTransform && geo->fDrawMatrix.hasPerspective())
72         , fUseGammaCorrectDistanceTable(false)
73         , fHead{geo}
74         , fTail{&fHead->fNext} {
75     // We don't have tight bounds on the glyph paths in device space. For the purposes of bounds
76     // we treat this as a set of non-AA rects rendered with a texture.
77     this->setBounds(deviceRect, HasAABloat::kNo, IsHairline::kNo);
78 }
79 
AtlasTextOp(MaskType maskType,bool needsTransform,int glyphCount,SkRect deviceRect,SkColor luminanceColor,bool useGammaCorrectDistanceTable,uint32_t DFGPFlags,Geometry * geo,GrPaint && paint)80 AtlasTextOp::AtlasTextOp(MaskType maskType,
81                          bool needsTransform,
82                          int glyphCount,
83                          SkRect deviceRect,
84                          SkColor luminanceColor,
85                          bool useGammaCorrectDistanceTable,
86                          uint32_t DFGPFlags,
87                          Geometry* geo,
88                          GrPaint&& paint)
89         : INHERITED{ClassID()}
90         , fProcessors(std::move(paint))
91         , fNumGlyphs(glyphCount)
92         , fDFGPFlags(DFGPFlags)
93         , fMaskType(static_cast<uint32_t>(maskType))
94         , fUsesLocalCoords(false)
95         , fNeedsGlyphTransform(needsTransform)
96         , fHasPerspective(needsTransform && geo->fDrawMatrix.hasPerspective())
97         , fUseGammaCorrectDistanceTable(useGammaCorrectDistanceTable)
98         , fLuminanceColor(luminanceColor)
99         , fHead{geo}
100         , fTail{&fHead->fNext} {
101     // We don't have tight bounds on the glyph paths in device space. For the purposes of bounds
102     // we treat this as a set of non-AA rects rendered with a texture.
103     this->setBounds(deviceRect, HasAABloat::kNo, IsHairline::kNo);
104 }
105 
MakeForBlob(const GrAtlasSubRun & subRun,const SkMatrix & drawMatrix,SkPoint drawOrigin,SkIRect clipRect,sk_sp<GrTextBlob> blob,const SkPMColor4f & color,SkArenaAlloc * alloc)106 auto AtlasTextOp::Geometry::MakeForBlob(const GrAtlasSubRun& subRun,
107                                         const SkMatrix& drawMatrix,
108                                         SkPoint drawOrigin,
109                                         SkIRect clipRect,
110                                         sk_sp<GrTextBlob> blob,
111                                         const SkPMColor4f& color,
112                                         SkArenaAlloc* alloc) -> Geometry* {
113     // Bypass the automatic dtor behavior in SkArenaAlloc. I'm leaving this up to the Op to run
114     // all geometry dtors for now.
115     void* geo = alloc->makeBytesAlignedTo(sizeof(Geometry), alignof(Geometry));
116     return new(geo) Geometry{subRun,
117                              drawMatrix,
118                              drawOrigin,
119                              clipRect,
120                              std::move(blob),
121                              nullptr,
122                              color};
123 }
124 
fillVertexData(void * dst,int offset,int count) const125 void AtlasTextOp::Geometry::fillVertexData(void *dst, int offset, int count) const {
126     SkMatrix positionMatrix = fDrawMatrix;
127     positionMatrix.preTranslate(fDrawOrigin.x(), fDrawOrigin.y());
128     fSubRun.fillVertexData(
129             dst, offset, count, fColor.toBytes_RGBA(), positionMatrix, fClipRect);
130 }
131 
visitProxies(const GrVisitProxyFunc & func) const132 void AtlasTextOp::visitProxies(const GrVisitProxyFunc& func) const {
133     fProcessors.visitProxies(func);
134 }
135 
136 #if GR_TEST_UTILS
onDumpInfo() const137 SkString AtlasTextOp::onDumpInfo() const {
138     SkString str;
139     int i = 0;
140     for(Geometry* geom = fHead; geom != nullptr; geom = geom->fNext) {
141         str.appendf("%d: Color: 0x%08x Trans: %.2f,%.2f\n",
142                     i++,
143                     geom->fColor.toBytes_RGBA(),
144                     geom->fDrawOrigin.x(),
145                     geom->fDrawOrigin.y());
146     }
147 
148     str += fProcessors.dumpProcessors();
149     return str;
150 }
151 #endif
152 
fixedFunctionFlags() const153 GrDrawOp::FixedFunctionFlags AtlasTextOp::fixedFunctionFlags() const {
154     return FixedFunctionFlags::kNone;
155 }
156 
finalize(const GrCaps & caps,const GrAppliedClip * clip,GrClampType clampType)157 GrProcessorSet::Analysis AtlasTextOp::finalize(const GrCaps& caps,
158                                                const GrAppliedClip* clip,
159                                                GrClampType clampType) {
160     GrProcessorAnalysisCoverage coverage;
161     GrProcessorAnalysisColor color;
162     if (this->maskType() == MaskType::kColorBitmap) {
163         color.setToUnknown();
164     } else {
165         // finalize() is called before any merging is done, so at this point there's at most one
166         // Geometry with a color. Later, for non-bitmap ops, we may have mixed colors.
167         color.setToConstant(fHead->fColor);
168     }
169 
170     switch (this->maskType()) {
171         case MaskType::kGrayscaleCoverage:
172         case MaskType::kAliasedDistanceField:
173         case MaskType::kGrayscaleDistanceField:
174             coverage = GrProcessorAnalysisCoverage::kSingleChannel;
175             break;
176         case MaskType::kLCDCoverage:
177         case MaskType::kLCDDistanceField:
178         case MaskType::kLCDBGRDistanceField:
179             coverage = GrProcessorAnalysisCoverage::kLCD;
180             break;
181         case MaskType::kColorBitmap:
182             coverage = GrProcessorAnalysisCoverage::kNone;
183             break;
184     }
185 
186     auto analysis = fProcessors.finalize(color, coverage, clip, &GrUserStencilSettings::kUnused,
187                                          caps, clampType, &fHead->fColor);
188     // TODO(michaelludwig): Once processor analysis can be done external to op creation/finalization
189     // the atlas op metadata can be fully const. This is okay for now since finalize() happens
190     // before the op is merged, so during combineIfPossible, metadata is effectively const.
191     fUsesLocalCoords = analysis.usesLocalCoords();
192     return analysis;
193 }
194 
onPrepareDraws(GrMeshDrawTarget * target)195 void AtlasTextOp::onPrepareDraws(GrMeshDrawTarget* target) {
196     auto resourceProvider = target->resourceProvider();
197 
198     // If we need local coordinates, compute an inverse view matrix. If this is solid color, the
199     // processor analysis will not require local coords and the GPs will skip local coords when
200     // the matrix is identity. When the shaders require local coords, combineIfPossible requires all
201     // all geometries to have same draw matrix.
202     SkMatrix localMatrix = SkMatrix::I();
203     if (fUsesLocalCoords && !fHead->fDrawMatrix.invert(&localMatrix)) {
204         return;
205     }
206 
207     GrAtlasManager* atlasManager = target->atlasManager();
208 
209     GrMaskFormat maskFormat = this->maskFormat();
210 
211     unsigned int numActiveViews;
212     const GrSurfaceProxyView* views = atlasManager->getViews(maskFormat, &numActiveViews);
213     if (!views) {
214         SkDebugf("Could not allocate backing texture for atlas\n");
215         return;
216     }
217     SkASSERT(views[0].proxy());
218 
219     static constexpr int kMaxTextures = GrBitmapTextGeoProc::kMaxTextures;
220     static_assert(GrDistanceFieldA8TextGeoProc::kMaxTextures == kMaxTextures);
221     static_assert(GrDistanceFieldLCDTextGeoProc::kMaxTextures == kMaxTextures);
222 
223     auto primProcProxies = target->allocPrimProcProxyPtrs(kMaxTextures);
224     for (unsigned i = 0; i < numActiveViews; ++i) {
225         primProcProxies[i] = views[i].proxy();
226         // This op does not know its atlas proxies when it is added to a OpsTasks, so the proxies
227         // don't get added during the visitProxies call. Thus we add them here.
228         target->sampledProxyArray()->push_back(views[i].proxy());
229     }
230 
231     FlushInfo flushInfo;
232     flushInfo.fPrimProcProxies = primProcProxies;
233     flushInfo.fIndexBuffer = resourceProvider->refNonAAQuadIndexBuffer();
234 
235     if (this->usesDistanceFields()) {
236         flushInfo.fGeometryProcessor = this->setupDfProcessor(target->allocator(),
237                                                               *target->caps().shaderCaps(),
238                                                               localMatrix, views, numActiveViews);
239     } else {
240         auto filter = fNeedsGlyphTransform ? GrSamplerState::Filter::kLinear
241                                            : GrSamplerState::Filter::kNearest;
242         // Bitmap text uses a single color, combineIfPossible ensures all geometries have the same
243         // color, so we can use the first's without worry.
244         flushInfo.fGeometryProcessor = GrBitmapTextGeoProc::Make(
245                 target->allocator(), *target->caps().shaderCaps(), fHead->fColor,
246                 false, views, numActiveViews, filter, maskFormat, localMatrix, fHasPerspective);
247     }
248 
249     const int vertexStride = (int)flushInfo.fGeometryProcessor->vertexStride();
250 
251     // Ensure we don't request an insanely large contiguous vertex allocation.
252     static const int kMaxVertexBytes = GrBufferAllocPool::kDefaultBufferSize;
253     const int quadSize = vertexStride * kVerticesPerGlyph;
254     const int maxQuadsPerBuffer = kMaxVertexBytes / quadSize;
255 
256     int allGlyphsCursor = 0;
257     const int allGlyphsEnd = fNumGlyphs;
258     int quadCursor;
259     int quadEnd;
260     char* vertices;
261 
262     auto resetVertexBuffer = [&] {
263         quadCursor = 0;
264         quadEnd = std::min(maxQuadsPerBuffer, allGlyphsEnd - allGlyphsCursor);
265 
266         vertices = (char*)target->makeVertexSpace(
267                 vertexStride,
268                 kVerticesPerGlyph * quadEnd,
269                 &flushInfo.fVertexBuffer,
270                 &flushInfo.fVertexOffset);
271 
272         if (!vertices || !flushInfo.fVertexBuffer) {
273             SkDebugf("Could not allocate vertices\n");
274             return false;
275         }
276         return true;
277     };
278 
279     resetVertexBuffer();
280 
281     for (const Geometry* geo = fHead; geo != nullptr; geo = geo->fNext) {
282         const GrAtlasSubRun& subRun = geo->fSubRun;
283         SkASSERTF((int) subRun.vertexStride(geo->fDrawMatrix) == vertexStride,
284                   "subRun stride: %d vertex buffer stride: %d\n",
285                   (int)subRun.vertexStride(geo->fDrawMatrix), vertexStride);
286 
287         const int subRunEnd = subRun.glyphCount();
288         for (int subRunCursor = 0; subRunCursor < subRunEnd;) {
289             // Regenerate the atlas for the remainder of the glyphs in the run, or the remainder
290             // of the glyphs to fill the vertex buffer.
291             int regenEnd = subRunCursor + std::min(subRunEnd - subRunCursor, quadEnd - quadCursor);
292             auto[ok, glyphsRegenerated] = subRun.regenerateAtlas(subRunCursor, regenEnd, target);
293             // There was a problem allocating the glyph in the atlas. Bail.
294             if (!ok) {
295                 return;
296             }
297 
298             geo->fillVertexData(vertices + quadCursor * quadSize, subRunCursor, glyphsRegenerated);
299 
300             subRunCursor += glyphsRegenerated;
301             quadCursor += glyphsRegenerated;
302             allGlyphsCursor += glyphsRegenerated;
303             flushInfo.fGlyphsToFlush += glyphsRegenerated;
304 
305             if (quadCursor == quadEnd || subRunCursor < subRunEnd) {
306                 // Flush if not all the glyphs are drawn because either the quad buffer is full or
307                 // the atlas is out of space.
308                 if (subRunCursor < subRunEnd) {
309                     ATRACE_ANDROID_FRAMEWORK_ALWAYS("Atlas full");
310                 }
311                 this->createDrawForGeneratedGlyphs(target, &flushInfo);
312                 if (quadCursor == quadEnd && allGlyphsCursor < allGlyphsEnd) {
313                     // If the vertex buffer is full and there are still glyphs to draw then
314                     // get a new buffer.
315                     if(!resetVertexBuffer()) {
316                         return;
317                     }
318                 }
319             }
320         }
321     }
322 }
323 
onExecute(GrOpFlushState * flushState,const SkRect & chainBounds)324 void AtlasTextOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) {
325     auto pipeline = GrSimpleMeshDrawOpHelper::CreatePipeline(flushState,
326                                                              std::move(fProcessors),
327                                                              GrPipeline::InputFlags::kNone);
328 
329     flushState->executeDrawsAndUploadsForMeshDrawOp(this, chainBounds, pipeline,
330                                                     &GrUserStencilSettings::kUnused);
331 }
332 
createDrawForGeneratedGlyphs(GrMeshDrawTarget * target,FlushInfo * flushInfo) const333 void AtlasTextOp::createDrawForGeneratedGlyphs(GrMeshDrawTarget* target,
334                                                FlushInfo* flushInfo) const {
335     if (!flushInfo->fGlyphsToFlush) {
336         return;
337     }
338 
339     auto atlasManager = target->atlasManager();
340 
341     GrGeometryProcessor* gp = flushInfo->fGeometryProcessor;
342     GrMaskFormat maskFormat = this->maskFormat();
343 
344     unsigned int numActiveViews;
345     const GrSurfaceProxyView* views = atlasManager->getViews(maskFormat, &numActiveViews);
346     SkASSERT(views);
347     // Something has gone terribly wrong, bail
348     if (!views || 0 == numActiveViews) {
349         return;
350     }
351     if (gp->numTextureSamplers() != (int) numActiveViews) {
352         // During preparation the number of atlas pages has increased.
353         // Update the proxies used in the GP to match.
354         for (unsigned i = gp->numTextureSamplers(); i < numActiveViews; ++i) {
355             flushInfo->fPrimProcProxies[i] = views[i].proxy();
356             // This op does not know its atlas proxies when it is added to a OpsTasks, so the
357             // proxies don't get added during the visitProxies call. Thus we add them here.
358             target->sampledProxyArray()->push_back(views[i].proxy());
359             // These will get unreffed when the previously recorded draws destruct.
360             for (int d = 0; d < flushInfo->fNumDraws; ++d) {
361                 flushInfo->fPrimProcProxies[i]->ref();
362             }
363         }
364         if (this->usesDistanceFields()) {
365             if (this->isLCD()) {
366                 reinterpret_cast<GrDistanceFieldLCDTextGeoProc*>(gp)->addNewViews(
367                         views, numActiveViews, GrSamplerState::Filter::kLinear);
368             } else {
369                 reinterpret_cast<GrDistanceFieldA8TextGeoProc*>(gp)->addNewViews(
370                         views, numActiveViews, GrSamplerState::Filter::kLinear);
371             }
372         } else {
373             auto filter = fNeedsGlyphTransform ? GrSamplerState::Filter::kLinear
374                                                : GrSamplerState::Filter::kNearest;
375             reinterpret_cast<GrBitmapTextGeoProc*>(gp)->addNewViews(views, numActiveViews, filter);
376         }
377     }
378     int maxGlyphsPerDraw = static_cast<int>(flushInfo->fIndexBuffer->size() / sizeof(uint16_t) / 6);
379     GrSimpleMesh* mesh = target->allocMesh();
380     mesh->setIndexedPatterned(flushInfo->fIndexBuffer, kIndicesPerGlyph, flushInfo->fGlyphsToFlush,
381                               maxGlyphsPerDraw, flushInfo->fVertexBuffer, kVerticesPerGlyph,
382                               flushInfo->fVertexOffset);
383     target->recordDraw(flushInfo->fGeometryProcessor, mesh, 1, flushInfo->fPrimProcProxies,
384                        GrPrimitiveType::kTriangles);
385     flushInfo->fVertexOffset += kVerticesPerGlyph * flushInfo->fGlyphsToFlush;
386     flushInfo->fGlyphsToFlush = 0;
387     ++flushInfo->fNumDraws;
388 }
389 
onCombineIfPossible(GrOp * t,SkArenaAlloc *,const GrCaps & caps)390 GrOp::CombineResult AtlasTextOp::onCombineIfPossible(GrOp* t, SkArenaAlloc*, const GrCaps& caps) {
391     auto that = t->cast<AtlasTextOp>();
392 
393     if (fDFGPFlags != that->fDFGPFlags ||
394         fMaskType != that->fMaskType ||
395         fUsesLocalCoords != that->fUsesLocalCoords ||
396         fNeedsGlyphTransform != that->fNeedsGlyphTransform ||
397         fHasPerspective != that->fHasPerspective ||
398         fUseGammaCorrectDistanceTable != that->fUseGammaCorrectDistanceTable) {
399         // All flags must match for an op to be combined
400         return CombineResult::kCannotCombine;
401     }
402 
403     if (fProcessors != that->fProcessors) {
404         return CombineResult::kCannotCombine;
405     }
406 
407     if (fUsesLocalCoords) {
408         // If the fragment processors use local coordinates, the GPs compute them using the inverse
409         // of the view matrix stored in a uniform, so all geometries must have the same matrix.
410         const SkMatrix& thisFirstMatrix = fHead->fDrawMatrix;
411         const SkMatrix& thatFirstMatrix = that->fHead->fDrawMatrix;
412         if (!SkMatrixPriv::CheapEqual(thisFirstMatrix, thatFirstMatrix)) {
413             return CombineResult::kCannotCombine;
414         }
415     }
416 
417     if (this->usesDistanceFields()) {
418         SkASSERT(that->usesDistanceFields());
419         if (fLuminanceColor != that->fLuminanceColor) {
420             return CombineResult::kCannotCombine;
421         }
422     } else {
423         if (this->maskType() == MaskType::kColorBitmap &&
424             fHead->fColor != that->fHead->fColor) {
425             // This ensures all merged bitmap color text ops have a constant color
426             return CombineResult::kCannotCombine;
427         }
428     }
429 
430     fNumGlyphs += that->fNumGlyphs;
431 
432     // After concat, that's geometry list is emptied so it will not unref the blobs when destructed
433     this->addGeometry(that->fHead);
434     that->fHead = nullptr;
435     return CombineResult::kMerged;
436 }
437 
438 // TODO trying to figure out why lcd is so whack
setupDfProcessor(SkArenaAlloc * arena,const GrShaderCaps & caps,const SkMatrix & localMatrix,const GrSurfaceProxyView * views,unsigned int numActiveViews) const439 GrGeometryProcessor* AtlasTextOp::setupDfProcessor(SkArenaAlloc* arena,
440                                                    const GrShaderCaps& caps,
441                                                    const SkMatrix& localMatrix,
442                                                    const GrSurfaceProxyView* views,
443                                                    unsigned int numActiveViews) const {
444     static constexpr int kDistanceAdjustLumShift = 5;
445     auto dfAdjustTable = GrDistanceFieldAdjustTable::Get();
446 
447     // see if we need to create a new effect
448     if (this->isLCD()) {
449         float redCorrection = dfAdjustTable->getAdjustment(
450                 SkColorGetR(fLuminanceColor) >> kDistanceAdjustLumShift,
451                 fUseGammaCorrectDistanceTable);
452         float greenCorrection = dfAdjustTable->getAdjustment(
453                 SkColorGetG(fLuminanceColor) >> kDistanceAdjustLumShift,
454                 fUseGammaCorrectDistanceTable);
455         float blueCorrection = dfAdjustTable->getAdjustment(
456                 SkColorGetB(fLuminanceColor) >> kDistanceAdjustLumShift,
457                 fUseGammaCorrectDistanceTable);
458         GrDistanceFieldLCDTextGeoProc::DistanceAdjust widthAdjust =
459                 GrDistanceFieldLCDTextGeoProc::DistanceAdjust::Make(
460                         redCorrection, greenCorrection, blueCorrection);
461         return GrDistanceFieldLCDTextGeoProc::Make(arena, caps, views, numActiveViews,
462                                                    GrSamplerState::Filter::kLinear, widthAdjust,
463                                                    fDFGPFlags, localMatrix);
464     } else {
465 #ifdef SK_GAMMA_APPLY_TO_A8
466         float correction = 0;
467         if (this->maskType() != MaskType::kAliasedDistanceField) {
468             U8CPU lum = SkColorSpaceLuminance::computeLuminance(SK_GAMMA_EXPONENT,
469                                                                 fLuminanceColor);
470             correction = dfAdjustTable->getAdjustment(lum >> kDistanceAdjustLumShift,
471                                                       fUseGammaCorrectDistanceTable);
472         }
473         return GrDistanceFieldA8TextGeoProc::Make(arena, caps, views, numActiveViews,
474                                                   GrSamplerState::Filter::kLinear, correction,
475                                                   fDFGPFlags, localMatrix);
476 #else
477         return GrDistanceFieldA8TextGeoProc::Make(arena, caps, views, numActiveViews,
478                                                   GrSamplerState::Filter::kLinear, fDFGPFlags,
479                                                   localMatrix);
480 #endif
481     }
482 }
483 
484 #if GR_TEST_UTILS
485 #include "src/gpu/GrDrawOpTest.h"
486 
CreateOpTestingOnly(SurfaceDrawContext * sdc,const SkPaint & skPaint,const SkFont & font,const SkMatrixProvider & mtxProvider,const char * text,int x,int y)487 GrOp::Owner AtlasTextOp::CreateOpTestingOnly(SurfaceDrawContext* sdc,
488                                              const SkPaint& skPaint,
489                                              const SkFont& font,
490                                              const SkMatrixProvider& mtxProvider,
491                                              const char* text,
492                                              int x,
493                                              int y) {
494     size_t textLen = (int)strlen(text);
495 
496     SkMatrix drawMatrix(mtxProvider.localToDevice());
497     drawMatrix.preTranslate(x, y);
498     auto drawOrigin = SkPoint::Make(x, y);
499     SkGlyphRunBuilder builder;
500     auto glyphRunList = builder.textToGlyphRunList(font, skPaint, text, textLen, drawOrigin);
501     if (glyphRunList.empty()) {
502         return nullptr;
503     }
504 
505     auto rContext = sdc->recordingContext();
506     GrSDFTControl control =
507             rContext->priv().getSDFTControl(sdc->surfaceProps().isUseDeviceIndependentFonts());
508 
509     SkGlyphRunListPainter* painter = sdc->glyphRunPainter();
510     sk_sp<GrTextBlob> blob = GrTextBlob::Make(glyphRunList, skPaint, drawMatrix, control, painter);
511 
512     if (blob->subRunList().isEmpty()) {
513         return nullptr;
514     }
515 
516     GrAtlasSubRun* subRun = blob->subRunList().front().testingOnly_atlasSubRun();
517     SkASSERT(subRun);
518     GrOp::Owner op;
519     std::tie(std::ignore, op) = subRun->makeAtlasTextOp(
520             nullptr, mtxProvider, glyphRunList.origin(), skPaint, sdc, nullptr);
521     return op;
522 }
523 
524 #endif
525 
526 } // namespace skgpu::v1
527 
528 #if GR_TEST_UTILS
529 
GR_DRAW_OP_TEST_DEFINE(AtlasTextOp)530 GR_DRAW_OP_TEST_DEFINE(AtlasTextOp) {
531     SkSimpleMatrixProvider matrixProvider(GrTest::TestMatrixInvertible(random));
532 
533     SkPaint skPaint;
534     skPaint.setColor(random->nextU());
535 
536     SkFont font;
537     if (random->nextBool()) {
538         font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
539     } else {
540         font.setEdging(random->nextBool() ? SkFont::Edging::kAntiAlias : SkFont::Edging::kAlias);
541     }
542     font.setSubpixel(random->nextBool());
543 
544     const char* text = "The quick brown fox jumps over the lazy dog.";
545 
546     // create some random x/y offsets, including negative offsets
547     static const int kMaxTrans = 1024;
548     int xPos = (random->nextU() % 2) * 2 - 1;
549     int yPos = (random->nextU() % 2) * 2 - 1;
550     int xInt = (random->nextU() % kMaxTrans) * xPos;
551     int yInt = (random->nextU() % kMaxTrans) * yPos;
552 
553     return skgpu::v1::AtlasTextOp::CreateOpTestingOnly(sdc, skPaint, font, matrixProvider,
554                                                        text, xInt, yInt);
555 }
556 
557 #endif
558