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
37GrAtlasTextOp::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
60GrAtlasTextOp::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
86void 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
91void GrAtlasTextOp::visitProxies(const VisitProxyFunc& func) const {
92 fProcessors.visitProxies(func);
93}
94
95#if GR_TEST_UTILS
96SkString 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
112GrDrawOp::FixedFunctionFlags GrAtlasTextOp::fixedFunctionFlags() const {
113 return FixedFunctionFlags::kNone;
114}
115
116GrProcessorSet::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
148void 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
269void 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
277void 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
334GrOp::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
402static const int kDistanceAdjustLumShift = 5;
403
404// TODO trying to figure out why lcd is so whack
405GrGeometryProcessor* 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
458std::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
498GR_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