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