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