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/gpu/GrRecordingContext.h" |
12 | #include "src/core/SkMathPriv.h" |
13 | #include "src/core/SkMatrixPriv.h" |
14 | #include "src/core/SkMatrixProvider.h" |
15 | #include "src/core/SkSpan.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/GrRenderTargetContext.h" |
22 | #include "src/gpu/GrRenderTargetContextPriv.h" |
23 | #include "src/gpu/GrResourceProvider.h" |
24 | #include "src/gpu/SkGr.h" |
25 | #include "src/gpu/effects/GrBitmapTextGeoProc.h" |
26 | #include "src/gpu/effects/GrDistanceFieldGeoProc.h" |
27 | #include "src/gpu/ops/GrSimpleMeshDrawOpHelper.h" |
28 | #include "src/gpu/text/GrAtlasManager.h" |
29 | #include "src/gpu/text/GrDistanceFieldAdjustTable.h" |
30 | |
31 | #if GR_TEST_UTILS |
32 | #include "src/gpu/GrDrawOpTest.h" |
33 | #endif |
34 | |
35 | /////////////////////////////////////////////////////////////////////////////////////////////////// |
36 | |
37 | GrAtlasTextOp::GrAtlasTextOp(MaskType maskType, |
38 | bool needsTransform, |
39 | int glyphCount, |
40 | SkRect deviceRect, |
41 | const Geometry& geo, |
42 | GrPaint&& paint) |
43 | : INHERITED{ClassID()} |
44 | , fMaskType{maskType} |
45 | , fNeedsGlyphTransform{needsTransform} |
46 | , fLuminanceColor{0} |
47 | , fUseGammaCorrectDistanceTable{false} |
48 | , fDFGPFlags{0} |
49 | , fGeoDataAllocSize{kMinGeometryAllocated} |
50 | , fProcessors{std::move(paint)} |
51 | , fNumGlyphs{glyphCount} { |
52 | new (&fGeoData[0]) Geometry{geo}; |
53 | fGeoCount = 1; |
54 | |
55 | // We don't have tight bounds on the glyph paths in device space. For the purposes of bounds |
56 | // we treat this as a set of non-AA rects rendered with a texture. |
57 | this->setBounds(deviceRect, HasAABloat::kNo, IsHairline::kNo); |
58 | } |
59 | |
60 | GrAtlasTextOp::GrAtlasTextOp(MaskType maskType, |
61 | bool needsTransform, |
62 | int glyphCount, |
63 | SkRect deviceRect, |
64 | SkColor luminanceColor, |
65 | bool useGammaCorrectDistanceTable, |
66 | uint32_t DFGPFlags, |
67 | const Geometry& geo, |
68 | GrPaint&& paint) |
69 | : INHERITED{ClassID()} |
70 | , fMaskType{maskType} |
71 | , fNeedsGlyphTransform{needsTransform} |
72 | , fLuminanceColor{luminanceColor} |
73 | , fUseGammaCorrectDistanceTable{useGammaCorrectDistanceTable} |
74 | , fDFGPFlags{DFGPFlags} |
75 | , fGeoDataAllocSize{kMinGeometryAllocated} |
76 | , fProcessors{std::move(paint)} |
77 | , fNumGlyphs{glyphCount} { |
78 | new (&fGeoData[0]) Geometry{geo}; |
79 | fGeoCount = 1; |
80 | |
81 | // We don't have tight bounds on the glyph paths in device space. For the purposes of bounds |
82 | // we treat this as a set of non-AA rects rendered with a texture. |
83 | this->setBounds(deviceRect, HasAABloat::kNo, IsHairline::kNo); |
84 | } |
85 | |
86 | void GrAtlasTextOp::Geometry::fillVertexData(void *dst, int offset, int count) const { |
87 | fSubRun.fillVertexData(dst, offset, count, fColor.toBytes_RGBA(), |
88 | fDrawMatrix, fDrawOrigin, fClipRect); |
89 | } |
90 | |
91 | void GrAtlasTextOp::visitProxies(const VisitProxyFunc& func) const { |
92 | fProcessors.visitProxies(func); |
93 | } |
94 | |
95 | #if GR_TEST_UTILS |
96 | SkString GrAtlasTextOp::onDumpInfo() const { |
97 | SkString str; |
98 | |
99 | for (int i = 0; i < fGeoCount; ++i) { |
100 | str.appendf("%d: Color: 0x%08x Trans: %.2f,%.2f\n" , |
101 | i, |
102 | fGeoData[i].fColor.toBytes_RGBA(), |
103 | fGeoData[i].fDrawOrigin.x(), |
104 | fGeoData[i].fDrawOrigin.y()); |
105 | } |
106 | |
107 | str += fProcessors.dumpProcessors(); |
108 | return str; |
109 | } |
110 | #endif |
111 | |
112 | GrDrawOp::FixedFunctionFlags GrAtlasTextOp::fixedFunctionFlags() const { |
113 | return FixedFunctionFlags::kNone; |
114 | } |
115 | |
116 | GrProcessorSet::Analysis GrAtlasTextOp::finalize( |
117 | const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage, |
118 | GrClampType clampType) { |
119 | GrProcessorAnalysisCoverage coverage; |
120 | GrProcessorAnalysisColor color; |
121 | if (kColorBitmapMask_MaskType == fMaskType) { |
122 | color.setToUnknown(); |
123 | } else { |
124 | color.setToConstant(this->color()); |
125 | } |
126 | switch (fMaskType) { |
127 | case kGrayscaleCoverageMask_MaskType: |
128 | case kAliasedDistanceField_MaskType: |
129 | case kGrayscaleDistanceField_MaskType: |
130 | coverage = GrProcessorAnalysisCoverage::kSingleChannel; |
131 | break; |
132 | case kLCDCoverageMask_MaskType: |
133 | case kLCDDistanceField_MaskType: |
134 | case kLCDBGRDistanceField_MaskType: |
135 | coverage = GrProcessorAnalysisCoverage::kLCD; |
136 | break; |
137 | case kColorBitmapMask_MaskType: |
138 | coverage = GrProcessorAnalysisCoverage::kNone; |
139 | break; |
140 | } |
141 | auto analysis = fProcessors.finalize( |
142 | color, coverage, clip, &GrUserStencilSettings::kUnused, hasMixedSampledCoverage, caps, |
143 | clampType, &fGeoData[0].fColor); |
144 | fUsesLocalCoords = analysis.usesLocalCoords(); |
145 | return analysis; |
146 | } |
147 | |
148 | void GrAtlasTextOp::onPrepareDraws(Target* target) { |
149 | auto resourceProvider = target->resourceProvider(); |
150 | |
151 | // if we have RGB, then we won't have any SkShaders so no need to use a localmatrix. |
152 | // TODO actually only invert if we don't have RGBA |
153 | SkMatrix localMatrix; |
154 | if (this->usesLocalCoords() && !fGeoData[0].fDrawMatrix.invert(&localMatrix)) { |
155 | return; |
156 | } |
157 | |
158 | GrAtlasManager* atlasManager = target->atlasManager(); |
159 | |
160 | GrMaskFormat maskFormat = this->maskFormat(); |
161 | |
162 | unsigned int numActiveViews; |
163 | const GrSurfaceProxyView* views = atlasManager->getViews(maskFormat, &numActiveViews); |
164 | if (!views) { |
165 | SkDebugf("Could not allocate backing texture for atlas\n" ); |
166 | return; |
167 | } |
168 | SkASSERT(views[0].proxy()); |
169 | |
170 | static constexpr int kMaxTextures = GrBitmapTextGeoProc::kMaxTextures; |
171 | static_assert(GrDistanceFieldA8TextGeoProc::kMaxTextures == kMaxTextures); |
172 | static_assert(GrDistanceFieldLCDTextGeoProc::kMaxTextures == kMaxTextures); |
173 | |
174 | auto primProcProxies = target->allocPrimProcProxyPtrs(kMaxTextures); |
175 | for (unsigned i = 0; i < numActiveViews; ++i) { |
176 | primProcProxies[i] = views[i].proxy(); |
177 | // This op does not know its atlas proxies when it is added to a GrOpsTasks, so the proxies |
178 | // don't get added during the visitProxies call. Thus we add them here. |
179 | target->sampledProxyArray()->push_back(views[i].proxy()); |
180 | } |
181 | |
182 | FlushInfo flushInfo; |
183 | flushInfo.fPrimProcProxies = primProcProxies; |
184 | flushInfo.fIndexBuffer = resourceProvider->refNonAAQuadIndexBuffer(); |
185 | |
186 | bool vmPerspective = fGeoData[0].fDrawMatrix.hasPerspective(); |
187 | if (this->usesDistanceFields()) { |
188 | flushInfo.fGeometryProcessor = this->setupDfProcessor(target->allocator(), |
189 | *target->caps().shaderCaps(), |
190 | views, numActiveViews); |
191 | } else { |
192 | auto filter = fNeedsGlyphTransform ? GrSamplerState::Filter::kLinear |
193 | : GrSamplerState::Filter::kNearest; |
194 | flushInfo.fGeometryProcessor = GrBitmapTextGeoProc::Make( |
195 | target->allocator(), *target->caps().shaderCaps(), this->color(), false, views, |
196 | numActiveViews, filter, maskFormat, localMatrix, vmPerspective); |
197 | } |
198 | |
199 | const int vertexStride = (int)flushInfo.fGeometryProcessor->vertexStride(); |
200 | |
201 | // Ensure we don't request an insanely large contiguous vertex allocation. |
202 | static const int kMaxVertexBytes = GrBufferAllocPool::kDefaultBufferSize; |
203 | const int quadSize = vertexStride * kVerticesPerGlyph; |
204 | const int maxQuadsPerBuffer = kMaxVertexBytes / quadSize; |
205 | |
206 | int allGlyphsCursor = 0; |
207 | const int allGlyphsEnd = this->numGlyphs(); |
208 | int quadCursor; |
209 | int quadEnd; |
210 | char* vertices; |
211 | |
212 | auto resetVertexBuffer = [&] { |
213 | quadCursor = 0; |
214 | quadEnd = std::min(maxQuadsPerBuffer, allGlyphsEnd - allGlyphsCursor); |
215 | |
216 | vertices = (char*)target->makeVertexSpace( |
217 | vertexStride, |
218 | kVerticesPerGlyph * quadEnd, |
219 | &flushInfo.fVertexBuffer, |
220 | &flushInfo.fVertexOffset); |
221 | |
222 | if (!vertices || !flushInfo.fVertexBuffer) { |
223 | SkDebugf("Could not allocate vertices\n" ); |
224 | return false; |
225 | } |
226 | return true; |
227 | }; |
228 | |
229 | resetVertexBuffer(); |
230 | |
231 | for (const Geometry& geo : SkMakeSpan(fGeoData.get(), fGeoCount)) { |
232 | const GrAtlasSubRun& subRun = geo.fSubRun; |
233 | SkASSERT((int)subRun.vertexStride() == vertexStride); |
234 | |
235 | const int subRunEnd = subRun.glyphCount(); |
236 | for (int subRunCursor = 0; subRunCursor < subRunEnd;) { |
237 | // Regenerate the atlas for the remainder of the glyphs in the run, or the remainder |
238 | // of the glyphs to fill the vertex buffer. |
239 | int regenEnd = subRunCursor + std::min(subRunEnd - subRunCursor, quadEnd - quadCursor); |
240 | auto[ok, glyphsRegenerated] = subRun.regenerateAtlas(subRunCursor, regenEnd, target); |
241 | // There was a problem allocating the glyph in the atlas. Bail. |
242 | if (!ok) { |
243 | return; |
244 | } |
245 | |
246 | geo.fillVertexData(vertices + quadCursor * quadSize, subRunCursor, glyphsRegenerated); |
247 | |
248 | subRunCursor += glyphsRegenerated; |
249 | quadCursor += glyphsRegenerated; |
250 | allGlyphsCursor += glyphsRegenerated; |
251 | flushInfo.fGlyphsToFlush += glyphsRegenerated; |
252 | |
253 | if (quadCursor == quadEnd || subRunCursor < subRunEnd) { |
254 | // Flush if not all the glyphs are drawn because either the quad buffer is full or |
255 | // the atlas is out of space. |
256 | this->createDrawForGeneratedGlyphs(target, &flushInfo); |
257 | if (quadCursor == quadEnd && allGlyphsCursor < allGlyphsEnd) { |
258 | // If the vertex buffer is full and there are still glyphs to draw then |
259 | // get a new buffer. |
260 | if(!resetVertexBuffer()) { |
261 | return; |
262 | } |
263 | } |
264 | } |
265 | } |
266 | } |
267 | } |
268 | |
269 | void GrAtlasTextOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) { |
270 | auto pipeline = GrSimpleMeshDrawOpHelper::CreatePipeline(flushState, |
271 | std::move(fProcessors), |
272 | GrPipeline::InputFlags::kNone); |
273 | |
274 | flushState->executeDrawsAndUploadsForMeshDrawOp(this, chainBounds, pipeline); |
275 | } |
276 | |
277 | void GrAtlasTextOp::createDrawForGeneratedGlyphs( |
278 | GrMeshDrawOp::Target* target, FlushInfo* flushInfo) const { |
279 | if (!flushInfo->fGlyphsToFlush) { |
280 | return; |
281 | } |
282 | |
283 | auto atlasManager = target->atlasManager(); |
284 | |
285 | GrGeometryProcessor* gp = flushInfo->fGeometryProcessor; |
286 | GrMaskFormat maskFormat = this->maskFormat(); |
287 | |
288 | unsigned int numActiveViews; |
289 | const GrSurfaceProxyView* views = atlasManager->getViews(maskFormat, &numActiveViews); |
290 | SkASSERT(views); |
291 | // Something has gone terribly wrong, bail |
292 | if (!views || 0 == numActiveViews) { |
293 | return; |
294 | } |
295 | if (gp->numTextureSamplers() != (int) numActiveViews) { |
296 | // During preparation the number of atlas pages has increased. |
297 | // Update the proxies used in the GP to match. |
298 | for (unsigned i = gp->numTextureSamplers(); i < numActiveViews; ++i) { |
299 | flushInfo->fPrimProcProxies[i] = views[i].proxy(); |
300 | // This op does not know its atlas proxies when it is added to a GrOpsTasks, so the |
301 | // proxies don't get added during the visitProxies call. Thus we add them here. |
302 | target->sampledProxyArray()->push_back(views[i].proxy()); |
303 | // These will get unreffed when the previously recorded draws destruct. |
304 | for (int d = 0; d < flushInfo->fNumDraws; ++d) { |
305 | flushInfo->fPrimProcProxies[i]->ref(); |
306 | } |
307 | } |
308 | if (this->usesDistanceFields()) { |
309 | if (this->isLCD()) { |
310 | reinterpret_cast<GrDistanceFieldLCDTextGeoProc*>(gp)->addNewViews( |
311 | views, numActiveViews, GrSamplerState::Filter::kLinear); |
312 | } else { |
313 | reinterpret_cast<GrDistanceFieldA8TextGeoProc*>(gp)->addNewViews( |
314 | views, numActiveViews, GrSamplerState::Filter::kLinear); |
315 | } |
316 | } else { |
317 | auto filter = fNeedsGlyphTransform ? GrSamplerState::Filter::kLinear |
318 | : GrSamplerState::Filter::kNearest; |
319 | reinterpret_cast<GrBitmapTextGeoProc*>(gp)->addNewViews(views, numActiveViews, filter); |
320 | } |
321 | } |
322 | int maxGlyphsPerDraw = static_cast<int>(flushInfo->fIndexBuffer->size() / sizeof(uint16_t) / 6); |
323 | GrSimpleMesh* mesh = target->allocMesh(); |
324 | mesh->setIndexedPatterned(flushInfo->fIndexBuffer, kIndicesPerGlyph, flushInfo->fGlyphsToFlush, |
325 | maxGlyphsPerDraw, flushInfo->fVertexBuffer, kVerticesPerGlyph, |
326 | flushInfo->fVertexOffset); |
327 | target->recordDraw(flushInfo->fGeometryProcessor, mesh, 1, flushInfo->fPrimProcProxies, |
328 | GrPrimitiveType::kTriangles); |
329 | flushInfo->fVertexOffset += kVerticesPerGlyph * flushInfo->fGlyphsToFlush; |
330 | flushInfo->fGlyphsToFlush = 0; |
331 | ++flushInfo->fNumDraws; |
332 | } |
333 | |
334 | GrOp::CombineResult GrAtlasTextOp::onCombineIfPossible(GrOp* t, GrRecordingContext::Arenas*, |
335 | const GrCaps& caps) { |
336 | GrAtlasTextOp* that = t->cast<GrAtlasTextOp>(); |
337 | if (fProcessors != that->fProcessors) { |
338 | return CombineResult::kCannotCombine; |
339 | } |
340 | |
341 | if (fMaskType != that->fMaskType) { |
342 | return CombineResult::kCannotCombine; |
343 | } |
344 | |
345 | const SkMatrix& thisFirstMatrix = fGeoData[0].fDrawMatrix; |
346 | const SkMatrix& thatFirstMatrix = that->fGeoData[0].fDrawMatrix; |
347 | |
348 | if (this->usesLocalCoords() && !SkMatrixPriv::CheapEqual(thisFirstMatrix, thatFirstMatrix)) { |
349 | return CombineResult::kCannotCombine; |
350 | } |
351 | |
352 | if (fNeedsGlyphTransform != that->fNeedsGlyphTransform) { |
353 | return CombineResult::kCannotCombine; |
354 | } |
355 | |
356 | if (fNeedsGlyphTransform && |
357 | (thisFirstMatrix.hasPerspective() != thatFirstMatrix.hasPerspective())) { |
358 | return CombineResult::kCannotCombine; |
359 | } |
360 | |
361 | if (this->usesDistanceFields()) { |
362 | if (fDFGPFlags != that->fDFGPFlags) { |
363 | return CombineResult::kCannotCombine; |
364 | } |
365 | |
366 | if (fLuminanceColor != that->fLuminanceColor) { |
367 | return CombineResult::kCannotCombine; |
368 | } |
369 | } else { |
370 | if (kColorBitmapMask_MaskType == fMaskType && this->color() != that->color()) { |
371 | return CombineResult::kCannotCombine; |
372 | } |
373 | } |
374 | |
375 | fNumGlyphs += that->numGlyphs(); |
376 | |
377 | // Reallocate space for geo data if necessary and then import that geo's data. |
378 | int newGeoCount = that->fGeoCount + fGeoCount; |
379 | |
380 | // We reallocate at a rate of 1.5x to try to get better total memory usage |
381 | if (newGeoCount > fGeoDataAllocSize) { |
382 | int newAllocSize = fGeoDataAllocSize + fGeoDataAllocSize / 2; |
383 | while (newAllocSize < newGeoCount) { |
384 | newAllocSize += newAllocSize / 2; |
385 | } |
386 | fGeoData.realloc(newAllocSize); |
387 | fGeoDataAllocSize = newAllocSize; |
388 | } |
389 | |
390 | // We steal the ref on the blobs from the other AtlasTextOp and set its count to 0 so that |
391 | // it doesn't try to unref them. |
392 | for (int i = 0; i < that->fGeoCount; i++) { |
393 | new (&fGeoData[fGeoCount + i]) Geometry{that->fGeoData[i]}; |
394 | } |
395 | |
396 | that->fGeoCount = 0; |
397 | fGeoCount = newGeoCount; |
398 | |
399 | return CombineResult::kMerged; |
400 | } |
401 | |
402 | static const int kDistanceAdjustLumShift = 5; |
403 | |
404 | // TODO trying to figure out why lcd is so whack |
405 | GrGeometryProcessor* GrAtlasTextOp::setupDfProcessor(SkArenaAlloc* arena, |
406 | const GrShaderCaps& caps, |
407 | const GrSurfaceProxyView* views, |
408 | unsigned int numActiveViews) const { |
409 | bool isLCD = this->isLCD(); |
410 | |
411 | SkMatrix localMatrix = SkMatrix::I(); |
412 | if (this->usesLocalCoords()) { |
413 | // If this fails we'll just use I(). |
414 | bool result = fGeoData[0].fDrawMatrix.invert(&localMatrix); |
415 | (void)result; |
416 | } |
417 | |
418 | auto dfAdjustTable = GrDistanceFieldAdjustTable::Get(); |
419 | |
420 | // see if we need to create a new effect |
421 | if (isLCD) { |
422 | float redCorrection = dfAdjustTable->getAdjustment( |
423 | SkColorGetR(fLuminanceColor) >> kDistanceAdjustLumShift, |
424 | fUseGammaCorrectDistanceTable); |
425 | float greenCorrection = dfAdjustTable->getAdjustment( |
426 | SkColorGetG(fLuminanceColor) >> kDistanceAdjustLumShift, |
427 | fUseGammaCorrectDistanceTable); |
428 | float blueCorrection = dfAdjustTable->getAdjustment( |
429 | SkColorGetB(fLuminanceColor) >> kDistanceAdjustLumShift, |
430 | fUseGammaCorrectDistanceTable); |
431 | GrDistanceFieldLCDTextGeoProc::DistanceAdjust widthAdjust = |
432 | GrDistanceFieldLCDTextGeoProc::DistanceAdjust::Make( |
433 | redCorrection, greenCorrection, blueCorrection); |
434 | return GrDistanceFieldLCDTextGeoProc::Make(arena, caps, views, numActiveViews, |
435 | GrSamplerState::Filter::kLinear, widthAdjust, |
436 | fDFGPFlags, localMatrix); |
437 | } else { |
438 | #ifdef SK_GAMMA_APPLY_TO_A8 |
439 | float correction = 0; |
440 | if (kAliasedDistanceField_MaskType != fMaskType) { |
441 | U8CPU lum = SkColorSpaceLuminance::computeLuminance(SK_GAMMA_EXPONENT, |
442 | fLuminanceColor); |
443 | correction = dfAdjustTable->getAdjustment(lum >> kDistanceAdjustLumShift, |
444 | fUseGammaCorrectDistanceTable); |
445 | } |
446 | return GrDistanceFieldA8TextGeoProc::Make(arena, caps, views, numActiveViews, |
447 | GrSamplerState::Filter::kLinear, correction, |
448 | fDFGPFlags, localMatrix); |
449 | #else |
450 | return GrDistanceFieldA8TextGeoProc::Make(arena, caps, views, numActiveViews, |
451 | GrSamplerState::Filter::kLinear, fDFGPFlags, |
452 | localMatrix); |
453 | #endif |
454 | } |
455 | } |
456 | |
457 | #if GR_TEST_UTILS |
458 | std::unique_ptr<GrDrawOp> GrAtlasTextOp::CreateOpTestingOnly(GrRenderTargetContext* rtc, |
459 | const SkPaint& skPaint, |
460 | const SkFont& font, |
461 | const SkMatrixProvider& mtxProvider, |
462 | const char* text, |
463 | int x, |
464 | int y) { |
465 | size_t textLen = (int)strlen(text); |
466 | |
467 | const SkMatrix& drawMatrix(mtxProvider.localToDevice()); |
468 | |
469 | auto drawOrigin = SkPoint::Make(x, y); |
470 | SkGlyphRunBuilder builder; |
471 | builder.drawTextUTF8(skPaint, font, text, textLen, drawOrigin); |
472 | |
473 | auto glyphRunList = builder.useGlyphRunList(); |
474 | if (glyphRunList.empty()) { |
475 | return nullptr; |
476 | } |
477 | |
478 | |
479 | auto rContext = rtc->priv().recordingContext(); |
480 | GrSDFTOptions SDFOptions = rContext->priv().SDFTOptions(); |
481 | |
482 | sk_sp<GrTextBlob> blob = GrTextBlob::Make(glyphRunList, drawMatrix); |
483 | SkGlyphRunListPainter* painter = rtc->priv().testingOnly_glyphRunPainter(); |
484 | painter->processGlyphRunList( |
485 | glyphRunList, drawMatrix, rtc->surfaceProps(), |
486 | rContext->priv().caps()->shaderCaps()->supportsDistanceFieldText(), |
487 | SDFOptions, blob.get()); |
488 | if (!blob->subRunList().head()) { |
489 | return nullptr; |
490 | } |
491 | |
492 | GrAtlasSubRun* subRun = static_cast<GrAtlasSubRun*>(blob->subRunList().head()); |
493 | std::unique_ptr<GrDrawOp> op; |
494 | std::tie(std::ignore, op) = subRun->makeAtlasTextOp(nullptr, mtxProvider, glyphRunList, rtc); |
495 | return op; |
496 | } |
497 | |
498 | GR_DRAW_OP_TEST_DEFINE(GrAtlasTextOp) { |
499 | // Setup dummy SkPaint / GrPaint / GrRenderTargetContext |
500 | auto rtc = GrRenderTargetContext::Make( |
501 | context, GrColorType::kRGBA_8888, nullptr, SkBackingFit::kApprox, {1024, 1024}); |
502 | |
503 | SkSimpleMatrixProvider matrixProvider(GrTest::TestMatrixInvertible(random)); |
504 | |
505 | SkPaint skPaint; |
506 | skPaint.setColor(random->nextU()); |
507 | |
508 | SkFont font; |
509 | if (random->nextBool()) { |
510 | font.setEdging(SkFont::Edging::kSubpixelAntiAlias); |
511 | } else { |
512 | font.setEdging(random->nextBool() ? SkFont::Edging::kAntiAlias : SkFont::Edging::kAlias); |
513 | } |
514 | font.setSubpixel(random->nextBool()); |
515 | |
516 | const char* text = "The quick brown fox jumps over the lazy dog." ; |
517 | |
518 | // create some random x/y offsets, including negative offsets |
519 | static const int kMaxTrans = 1024; |
520 | int xPos = (random->nextU() % 2) * 2 - 1; |
521 | int yPos = (random->nextU() % 2) * 2 - 1; |
522 | int xInt = (random->nextU() % kMaxTrans) * xPos; |
523 | int yInt = (random->nextU() % kMaxTrans) * yPos; |
524 | |
525 | return GrAtlasTextOp::CreateOpTestingOnly( |
526 | rtc.get(), skPaint, font, matrixProvider, text, xInt, yInt); |
527 | } |
528 | |
529 | #endif |
530 | |
531 | |
532 | |