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