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