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