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