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