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 "GrAtlasTextOp.h"
9 #include "GrContext.h"
10 #include "GrOpFlushState.h"
11 #include "GrResourceProvider.h"
12 #include "SkGlyphCache.h"
13 #include "SkMathPriv.h"
14 #include "SkMatrixPriv.h"
15 #include "SkPoint3.h"
16 #include "effects/GrBitmapTextGeoProc.h"
17 #include "effects/GrDistanceFieldGeoProc.h"
18 #include "text/GrAtlasGlyphCache.h"
19
20 ///////////////////////////////////////////////////////////////////////////////////////////////////
21
22 static const int kDistanceAdjustLumShift = 5;
23
init()24 void GrAtlasTextOp::init() {
25 const Geometry& geo = fGeoData[0];
26 SkRect bounds;
27 geo.fBlob->computeSubRunBounds(&bounds, geo.fRun, geo.fSubRun, geo.fViewMatrix, geo.fX, geo.fY);
28 // We don't have tight bounds on the glyph paths in device space. For the purposes of bounds
29 // we treat this as a set of non-AA rects rendered with a texture.
30 this->setBounds(bounds, HasAABloat::kNo, IsZeroArea::kNo);
31 if (this->usesDistanceFields()) {
32 bool isLCD = this->isLCD();
33
34 const SkMatrix& viewMatrix = geo.fViewMatrix;
35
36 fDFGPFlags = viewMatrix.isSimilarity() ? kSimilarity_DistanceFieldEffectFlag : 0;
37 fDFGPFlags |= viewMatrix.isScaleTranslate() ? kScaleOnly_DistanceFieldEffectFlag : 0;
38 fDFGPFlags |= viewMatrix.hasPerspective() ? kPerspective_DistanceFieldEffectFlag : 0;
39 fDFGPFlags |= fUseGammaCorrectDistanceTable ? kGammaCorrect_DistanceFieldEffectFlag : 0;
40 fDFGPFlags |= (kAliasedDistanceField_MaskType == fMaskType)
41 ? kAliased_DistanceFieldEffectFlag
42 : 0;
43
44 if (isLCD) {
45 fDFGPFlags |= kUseLCD_DistanceFieldEffectFlag;
46 fDFGPFlags |=
47 (kLCDBGRDistanceField_MaskType == fMaskType) ? kBGR_DistanceFieldEffectFlag : 0;
48 }
49 }
50 }
51
dumpInfo() const52 SkString GrAtlasTextOp::dumpInfo() const {
53 SkString str;
54
55 for (int i = 0; i < fGeoCount; ++i) {
56 str.appendf("%d: Color: 0x%08x Trans: %.2f,%.2f Runs: %d\n",
57 i,
58 fGeoData[i].fColor,
59 fGeoData[i].fX,
60 fGeoData[i].fY,
61 fGeoData[i].fBlob->runCount());
62 }
63
64 str += fProcessors.dumpProcessors();
65 str += INHERITED::dumpInfo();
66 return str;
67 }
68
fixedFunctionFlags() const69 GrDrawOp::FixedFunctionFlags GrAtlasTextOp::fixedFunctionFlags() const {
70 return FixedFunctionFlags::kNone;
71 }
72
finalize(const GrCaps & caps,const GrAppliedClip * clip,GrPixelConfigIsClamped dstIsClamped)73 GrDrawOp::RequiresDstTexture GrAtlasTextOp::finalize(const GrCaps& caps,
74 const GrAppliedClip* clip,
75 GrPixelConfigIsClamped dstIsClamped) {
76 GrProcessorAnalysisCoverage coverage;
77 GrProcessorAnalysisColor color;
78 if (kColorBitmapMask_MaskType == fMaskType) {
79 color.setToUnknown();
80 } else {
81 color.setToConstant(this->color());
82 }
83 switch (fMaskType) {
84 case kGrayscaleCoverageMask_MaskType:
85 case kAliasedDistanceField_MaskType:
86 case kGrayscaleDistanceField_MaskType:
87 coverage = GrProcessorAnalysisCoverage::kSingleChannel;
88 break;
89 case kLCDCoverageMask_MaskType:
90 case kLCDDistanceField_MaskType:
91 case kLCDBGRDistanceField_MaskType:
92 coverage = GrProcessorAnalysisCoverage::kLCD;
93 break;
94 case kColorBitmapMask_MaskType:
95 coverage = GrProcessorAnalysisCoverage::kNone;
96 break;
97 }
98 auto analysis = fProcessors.finalize(color, coverage, clip, false, caps, dstIsClamped,
99 &fGeoData[0].fColor);
100 fUsesLocalCoords = analysis.usesLocalCoords();
101 fCanCombineOnTouchOrOverlap =
102 !analysis.requiresDstTexture() &&
103 !(fProcessors.xferProcessor() && fProcessors.xferProcessor()->xferBarrierType(caps));
104 return analysis.requiresDstTexture() ? RequiresDstTexture::kYes : RequiresDstTexture::kNo;
105 }
106
clip_quads(const SkIRect & clipRect,char * currVertex,const char * blobVertices,size_t vertexStride,int glyphCount)107 static void clip_quads(const SkIRect& clipRect, char* currVertex, const char* blobVertices,
108 size_t vertexStride, int glyphCount) {
109 for (int i = 0; i < glyphCount; ++i) {
110 const SkPoint* blobPositionLT = reinterpret_cast<const SkPoint*>(blobVertices);
111 const SkPoint* blobPositionRB =
112 reinterpret_cast<const SkPoint*>(blobVertices + 3 * vertexStride);
113
114 // positions for bitmap glyphs are pixel boundary aligned
115 SkIRect positionRect = SkIRect::MakeLTRB(SkScalarRoundToInt(blobPositionLT->fX),
116 SkScalarRoundToInt(blobPositionLT->fY),
117 SkScalarRoundToInt(blobPositionRB->fX),
118 SkScalarRoundToInt(blobPositionRB->fY));
119 if (clipRect.contains(positionRect)) {
120 memcpy(currVertex, blobVertices, 4 * vertexStride);
121 currVertex += 4 * vertexStride;
122 } else {
123 // Pull out some more data that we'll need.
124 // In the LCD case the color will be garbage, but we'll overwrite it with the texcoords
125 // and it avoids a lot of conditionals.
126 auto color = *reinterpret_cast<const SkColor*>(blobVertices + sizeof(SkPoint));
127 size_t coordOffset = vertexStride - 2*sizeof(uint16_t);
128 auto* blobCoordsLT = reinterpret_cast<const uint16_t*>(blobVertices + coordOffset);
129 auto* blobCoordsRB = reinterpret_cast<const uint16_t*>(blobVertices + 3 * vertexStride +
130 coordOffset);
131 // Pull out the texel coordinates and texture index bits
132 uint16_t coordsRectL = blobCoordsLT[0] >> 1;
133 uint16_t coordsRectT = blobCoordsLT[1] >> 1;
134 uint16_t coordsRectR = blobCoordsRB[0] >> 1;
135 uint16_t coordsRectB = blobCoordsRB[1] >> 1;
136 uint16_t pageIndexX = blobCoordsLT[0] & 0x1;
137 uint16_t pageIndexY = blobCoordsLT[1] & 0x1;
138
139 int positionRectWidth = positionRect.width();
140 int positionRectHeight = positionRect.height();
141 SkASSERT(positionRectWidth == (coordsRectR - coordsRectL));
142 SkASSERT(positionRectHeight == (coordsRectB - coordsRectT));
143
144 // Clip position and texCoords to the clipRect
145 unsigned int delta;
146 delta = SkTMin(SkTMax(clipRect.fLeft - positionRect.fLeft, 0), positionRectWidth);
147 coordsRectL += delta;
148 positionRect.fLeft += delta;
149
150 delta = SkTMin(SkTMax(clipRect.fTop - positionRect.fTop, 0), positionRectHeight);
151 coordsRectT += delta;
152 positionRect.fTop += delta;
153
154 delta = SkTMin(SkTMax(positionRect.fRight - clipRect.fRight, 0), positionRectWidth);
155 coordsRectR -= delta;
156 positionRect.fRight -= delta;
157
158 delta = SkTMin(SkTMax(positionRect.fBottom - clipRect.fBottom, 0), positionRectHeight);
159 coordsRectB -= delta;
160 positionRect.fBottom -= delta;
161
162 // Repack texel coordinates and index
163 coordsRectL = coordsRectL << 1 | pageIndexX;
164 coordsRectT = coordsRectT << 1 | pageIndexY;
165 coordsRectR = coordsRectR << 1 | pageIndexX;
166 coordsRectB = coordsRectB << 1 | pageIndexY;
167
168 // Set new positions and coords
169 SkPoint* currPosition = reinterpret_cast<SkPoint*>(currVertex);
170 currPosition->fX = positionRect.fLeft;
171 currPosition->fY = positionRect.fTop;
172 *(reinterpret_cast<SkColor*>(currVertex + sizeof(SkPoint))) = color;
173 uint16_t* currCoords = reinterpret_cast<uint16_t*>(currVertex + coordOffset);
174 currCoords[0] = coordsRectL;
175 currCoords[1] = coordsRectT;
176 currVertex += vertexStride;
177
178 currPosition = reinterpret_cast<SkPoint*>(currVertex);
179 currPosition->fX = positionRect.fLeft;
180 currPosition->fY = positionRect.fBottom;
181 *(reinterpret_cast<SkColor*>(currVertex + sizeof(SkPoint))) = color;
182 currCoords = reinterpret_cast<uint16_t*>(currVertex + coordOffset);
183 currCoords[0] = coordsRectL;
184 currCoords[1] = coordsRectB;
185 currVertex += vertexStride;
186
187 currPosition = reinterpret_cast<SkPoint*>(currVertex);
188 currPosition->fX = positionRect.fRight;
189 currPosition->fY = positionRect.fTop;
190 *(reinterpret_cast<SkColor*>(currVertex + sizeof(SkPoint))) = color;
191 currCoords = reinterpret_cast<uint16_t*>(currVertex + coordOffset);
192 currCoords[0] = coordsRectR;
193 currCoords[1] = coordsRectT;
194 currVertex += vertexStride;
195
196 currPosition = reinterpret_cast<SkPoint*>(currVertex);
197 currPosition->fX = positionRect.fRight;
198 currPosition->fY = positionRect.fBottom;
199 *(reinterpret_cast<SkColor*>(currVertex + sizeof(SkPoint))) = color;
200 currCoords = reinterpret_cast<uint16_t*>(currVertex + coordOffset);
201 currCoords[0] = coordsRectR;
202 currCoords[1] = coordsRectB;
203 currVertex += vertexStride;
204 }
205
206 blobVertices += 4 * vertexStride;
207 }
208 }
209
onPrepareDraws(Target * target)210 void GrAtlasTextOp::onPrepareDraws(Target* target) {
211 // if we have RGB, then we won't have any SkShaders so no need to use a localmatrix.
212 // TODO actually only invert if we don't have RGBA
213 SkMatrix localMatrix;
214 if (this->usesLocalCoords() && !fGeoData[0].fViewMatrix.invert(&localMatrix)) {
215 SkDebugf("Cannot invert viewmatrix\n");
216 return;
217 }
218
219 GrMaskFormat maskFormat = this->maskFormat();
220
221 uint32_t atlasPageCount = fFontCache->getAtlasPageCount(maskFormat);
222 const sk_sp<GrTextureProxy>* proxies = fFontCache->getProxies(maskFormat);
223 if (!atlasPageCount || !proxies[0]) {
224 SkDebugf("Could not allocate backing texture for atlas\n");
225 return;
226 }
227
228 FlushInfo flushInfo;
229 flushInfo.fPipeline =
230 target->makePipeline(fSRGBFlags, std::move(fProcessors), target->detachAppliedClip());
231 SkDEBUGCODE(bool dfPerspective = false);
232 if (this->usesDistanceFields()) {
233 flushInfo.fGeometryProcessor = this->setupDfProcessor();
234 SkDEBUGCODE(dfPerspective = fGeoData[0].fViewMatrix.hasPerspective());
235 } else {
236 flushInfo.fGeometryProcessor = GrBitmapTextGeoProc::Make(
237 this->color(), proxies, GrSamplerState::ClampNearest(), maskFormat,
238 localMatrix, this->usesLocalCoords());
239 }
240
241 flushInfo.fGlyphsToFlush = 0;
242 size_t vertexStride = flushInfo.fGeometryProcessor->getVertexStride();
243 SkASSERT(vertexStride == GrAtlasTextBlob::GetVertexStride(maskFormat, dfPerspective));
244
245 int glyphCount = this->numGlyphs();
246 const GrBuffer* vertexBuffer;
247
248 void* vertices = target->makeVertexSpace(
249 vertexStride, glyphCount * kVerticesPerGlyph, &vertexBuffer, &flushInfo.fVertexOffset);
250 flushInfo.fVertexBuffer.reset(SkRef(vertexBuffer));
251 flushInfo.fIndexBuffer = target->resourceProvider()->refQuadIndexBuffer();
252 if (!vertices || !flushInfo.fVertexBuffer) {
253 SkDebugf("Could not allocate vertices\n");
254 return;
255 }
256
257 char* currVertex = reinterpret_cast<char*>(vertices);
258
259 SkAutoGlyphCache glyphCache;
260 // each of these is a SubRun
261 for (int i = 0; i < fGeoCount; i++) {
262 const Geometry& args = fGeoData[i];
263 Blob* blob = args.fBlob;
264 GrAtlasTextBlob::VertexRegenerator regenerator(
265 blob, args.fRun, args.fSubRun, args.fViewMatrix, args.fX, args.fY, args.fColor,
266 target->deferredUploadTarget(), fFontCache, &glyphCache);
267 GrAtlasTextBlob::VertexRegenerator::Result result;
268 do {
269 result = regenerator.regenerate();
270 // Copy regenerated vertices from the blob to our vertex buffer.
271 size_t vertexBytes = result.fGlyphsRegenerated * kVerticesPerGlyph * vertexStride;
272 if (args.fClipRect.isEmpty()) {
273 memcpy(currVertex, result.fFirstVertex, vertexBytes);
274 } else {
275 SkASSERT(!dfPerspective);
276 clip_quads(args.fClipRect, currVertex, result.fFirstVertex, vertexStride,
277 result.fGlyphsRegenerated);
278 }
279 if (this->usesDistanceFields() && !args.fViewMatrix.isIdentity()) {
280 // We always do the distance field view matrix transformation after copying rather
281 // than during blob vertex generation time in the blob as handling successive
282 // arbitrary transformations would be complicated and accumulate error.
283 if (args.fViewMatrix.hasPerspective()) {
284 auto* pos = reinterpret_cast<SkPoint3*>(currVertex);
285 SkMatrixPriv::MapHomogeneousPointsWithStride(
286 args.fViewMatrix, pos, vertexStride, pos, vertexStride,
287 result.fGlyphsRegenerated * kVerticesPerGlyph);
288 } else {
289 auto* pos = reinterpret_cast<SkPoint*>(currVertex);
290 SkMatrixPriv::MapPointsWithStride(
291 args.fViewMatrix, pos, vertexStride,
292 result.fGlyphsRegenerated * kVerticesPerGlyph);
293 }
294 }
295 flushInfo.fGlyphsToFlush += result.fGlyphsRegenerated;
296 if (!result.fFinished) {
297 this->flush(target, &flushInfo);
298 }
299 currVertex += vertexBytes;
300 } while (!result.fFinished);
301 }
302 this->flush(target, &flushInfo);
303 }
304
flush(GrMeshDrawOp::Target * target,FlushInfo * flushInfo) const305 void GrAtlasTextOp::flush(GrMeshDrawOp::Target* target, FlushInfo* flushInfo) const {
306 GrGeometryProcessor* gp = flushInfo->fGeometryProcessor.get();
307 GrMaskFormat maskFormat = this->maskFormat();
308 if (gp->numTextureSamplers() != (int)fFontCache->getAtlasPageCount(maskFormat)) {
309 // During preparation the number of atlas pages has increased.
310 // Update the proxies used in the GP to match.
311 if (this->usesDistanceFields()) {
312 if (this->isLCD()) {
313 reinterpret_cast<GrDistanceFieldLCDTextGeoProc*>(gp)->addNewProxies(
314 fFontCache->getProxies(maskFormat), GrSamplerState::ClampBilerp());
315 } else {
316 reinterpret_cast<GrDistanceFieldA8TextGeoProc*>(gp)->addNewProxies(
317 fFontCache->getProxies(maskFormat), GrSamplerState::ClampBilerp());
318 }
319 } else {
320 reinterpret_cast<GrBitmapTextGeoProc*>(gp)->addNewProxies(
321 fFontCache->getProxies(maskFormat), GrSamplerState::ClampNearest());
322 }
323 }
324
325 GrMesh mesh(GrPrimitiveType::kTriangles);
326 int maxGlyphsPerDraw =
327 static_cast<int>(flushInfo->fIndexBuffer->gpuMemorySize() / sizeof(uint16_t) / 6);
328 mesh.setIndexedPatterned(flushInfo->fIndexBuffer.get(), kIndicesPerGlyph, kVerticesPerGlyph,
329 flushInfo->fGlyphsToFlush, maxGlyphsPerDraw);
330 mesh.setVertexData(flushInfo->fVertexBuffer.get(), flushInfo->fVertexOffset);
331 target->draw(flushInfo->fGeometryProcessor.get(), flushInfo->fPipeline, mesh);
332 flushInfo->fVertexOffset += kVerticesPerGlyph * flushInfo->fGlyphsToFlush;
333 flushInfo->fGlyphsToFlush = 0;
334 }
335
onCombineIfPossible(GrOp * t,const GrCaps & caps)336 bool GrAtlasTextOp::onCombineIfPossible(GrOp* t, const GrCaps& caps) {
337 GrAtlasTextOp* that = t->cast<GrAtlasTextOp>();
338 if (fProcessors != that->fProcessors) {
339 return false;
340 }
341
342 if (!fCanCombineOnTouchOrOverlap && GrRectsTouchOrOverlap(this->bounds(), that->bounds())) {
343 return false;
344 }
345
346 if (fMaskType != that->fMaskType) {
347 return false;
348 }
349
350 const SkMatrix& thisFirstMatrix = fGeoData[0].fViewMatrix;
351 const SkMatrix& thatFirstMatrix = that->fGeoData[0].fViewMatrix;
352
353 if (this->usesLocalCoords() && !thisFirstMatrix.cheapEqualTo(thatFirstMatrix)) {
354 return false;
355 }
356
357 if (this->usesDistanceFields()) {
358 if (fDFGPFlags != that->fDFGPFlags) {
359 return false;
360 }
361
362 if (fLuminanceColor != that->fLuminanceColor) {
363 return false;
364 }
365 } else {
366 if (kColorBitmapMask_MaskType == fMaskType && this->color() != that->color()) {
367 return false;
368 }
369 }
370
371 // Keep the batch vertex buffer size below 32K so we don't have to create a special one
372 // We use the largest possible vertex size for this
373 static const int kVertexSize = sizeof(SkPoint) + sizeof(SkColor) + 2 * sizeof(uint16_t);
374 static const int kMaxGlyphs = 32768 / (kVerticesPerGlyph * kVertexSize);
375 if (this->fNumGlyphs + that->fNumGlyphs > kMaxGlyphs) {
376 return false;
377 }
378
379 fNumGlyphs += that->numGlyphs();
380
381 // Reallocate space for geo data if necessary and then import that geo's data.
382 int newGeoCount = that->fGeoCount + fGeoCount;
383
384 // We reallocate at a rate of 1.5x to try to get better total memory usage
385 if (newGeoCount > fGeoDataAllocSize) {
386 int newAllocSize = fGeoDataAllocSize + fGeoDataAllocSize / 2;
387 while (newAllocSize < newGeoCount) {
388 newAllocSize += newAllocSize / 2;
389 }
390 fGeoData.realloc(newAllocSize);
391 fGeoDataAllocSize = newAllocSize;
392 }
393
394 // We steal the ref on the blobs from the other AtlasTextOp and set its count to 0 so that
395 // it doesn't try to unref them.
396 memcpy(&fGeoData[fGeoCount], that->fGeoData.get(), that->fGeoCount * sizeof(Geometry));
397 #ifdef SK_DEBUG
398 for (int i = 0; i < that->fGeoCount; ++i) {
399 that->fGeoData.get()[i].fBlob = (Blob*)0x1;
400 }
401 #endif
402 that->fGeoCount = 0;
403 fGeoCount = newGeoCount;
404
405 this->joinBounds(*that);
406 return true;
407 }
408
409 // TODO trying to figure out why lcd is so whack
410 // (see comments in GrAtlasTextContext::ComputeCanonicalColor)
setupDfProcessor() const411 sk_sp<GrGeometryProcessor> GrAtlasTextOp::setupDfProcessor() const {
412 const sk_sp<GrTextureProxy>* p = fFontCache->getProxies(this->maskFormat());
413 bool isLCD = this->isLCD();
414
415 SkMatrix localMatrix = SkMatrix::I();
416 if (this->usesLocalCoords()) {
417 // If this fails we'll just use I().
418 bool result = fGeoData[0].fViewMatrix.invert(&localMatrix);
419 (void)result;
420 }
421
422 // see if we need to create a new effect
423 if (isLCD) {
424 float redCorrection = fDistanceAdjustTable->getAdjustment(
425 SkColorGetR(fLuminanceColor) >> kDistanceAdjustLumShift,
426 fUseGammaCorrectDistanceTable);
427 float greenCorrection = fDistanceAdjustTable->getAdjustment(
428 SkColorGetG(fLuminanceColor) >> kDistanceAdjustLumShift,
429 fUseGammaCorrectDistanceTable);
430 float blueCorrection = fDistanceAdjustTable->getAdjustment(
431 SkColorGetB(fLuminanceColor) >> kDistanceAdjustLumShift,
432 fUseGammaCorrectDistanceTable);
433 GrDistanceFieldLCDTextGeoProc::DistanceAdjust widthAdjust =
434 GrDistanceFieldLCDTextGeoProc::DistanceAdjust::Make(
435 redCorrection, greenCorrection, blueCorrection);
436 return GrDistanceFieldLCDTextGeoProc::Make(p, GrSamplerState::ClampBilerp(), widthAdjust,
437 fDFGPFlags, localMatrix);
438 } else {
439 #ifdef SK_GAMMA_APPLY_TO_A8
440 float correction = 0;
441 if (kAliasedDistanceField_MaskType != fMaskType) {
442 U8CPU lum = SkColorSpaceLuminance::computeLuminance(SK_GAMMA_EXPONENT,
443 fLuminanceColor);
444 correction = fDistanceAdjustTable->getAdjustment(lum >> kDistanceAdjustLumShift,
445 fUseGammaCorrectDistanceTable);
446 }
447 return GrDistanceFieldA8TextGeoProc::Make(p, GrSamplerState::ClampBilerp(),
448 correction, fDFGPFlags, localMatrix);
449 #else
450 return GrDistanceFieldA8TextGeoProc::Make(p, GrSamplerState::ClampBilerp(),
451 fDFGPFlags, localMatrix);
452 #endif
453 }
454 }
455
456