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 #include "GrAtlasTextContext.h"
8
9 #include "GrBatch.h"
10 #include "GrBatchFontCache.h"
11 #include "GrBatchTarget.h"
12 #include "GrBatchTest.h"
13 #include "GrDefaultGeoProcFactory.h"
14 #include "GrDrawTarget.h"
15 #include "GrFontScaler.h"
16 #include "GrIndexBuffer.h"
17 #include "GrResourceProvider.h"
18 #include "GrStrokeInfo.h"
19 #include "GrTextBlobCache.h"
20 #include "GrTexturePriv.h"
21 #include "GrVertexBuffer.h"
22
23 #include "SkAutoKern.h"
24 #include "SkColorPriv.h"
25 #include "SkColorFilter.h"
26 #include "SkDistanceFieldGen.h"
27 #include "SkDraw.h"
28 #include "SkDrawFilter.h"
29 #include "SkDrawProcs.h"
30 #include "SkGlyphCache.h"
31 #include "SkGpuDevice.h"
32 #include "SkGr.h"
33 #include "SkPath.h"
34 #include "SkRTConf.h"
35 #include "SkStrokeRec.h"
36 #include "SkTextBlob.h"
37 #include "SkTextMapStateProc.h"
38
39 #include "effects/GrBitmapTextGeoProc.h"
40 #include "effects/GrDistanceFieldGeoProc.h"
41
42 namespace {
43 static const size_t kLCDTextVASize = sizeof(SkPoint) + sizeof(SkIPoint16);
44
45 // position + local coord
46 static const size_t kColorTextVASize = sizeof(SkPoint) + sizeof(SkIPoint16);
47
48 static const size_t kGrayTextVASize = sizeof(SkPoint) + sizeof(GrColor) + sizeof(SkIPoint16);
49
50 static const int kMinDFFontSize = 18;
51 static const int kSmallDFFontSize = 32;
52 static const int kSmallDFFontLimit = 32;
53 static const int kMediumDFFontSize = 72;
54 static const int kMediumDFFontLimit = 72;
55 static const int kLargeDFFontSize = 162;
56 static const int kLargeDFFontLimit = 2 * kLargeDFFontSize;
57
58 SkDEBUGCODE(static const int kExpectedDistanceAdjustTableSize = 8;)
59 static const int kDistanceAdjustLumShift = 5;
60
61 static const int kVerticesPerGlyph = 4;
62 static const int kIndicesPerGlyph = 6;
63
get_vertex_stride(GrMaskFormat maskFormat)64 static size_t get_vertex_stride(GrMaskFormat maskFormat) {
65 switch (maskFormat) {
66 case kA8_GrMaskFormat:
67 return kGrayTextVASize;
68 case kARGB_GrMaskFormat:
69 return kColorTextVASize;
70 default:
71 return kLCDTextVASize;
72 }
73 }
74
get_vertex_stride_df(GrMaskFormat maskFormat,bool useLCDText)75 static size_t get_vertex_stride_df(GrMaskFormat maskFormat, bool useLCDText) {
76 SkASSERT(maskFormat == kA8_GrMaskFormat);
77 if (useLCDText) {
78 return kLCDTextVASize;
79 } else {
80 return kGrayTextVASize;
81 }
82 }
83
skcolor_to_grcolor_nopremultiply(SkColor c)84 static inline GrColor skcolor_to_grcolor_nopremultiply(SkColor c) {
85 unsigned r = SkColorGetR(c);
86 unsigned g = SkColorGetG(c);
87 unsigned b = SkColorGetB(c);
88 return GrColorPackRGBA(r, g, b, 0xff);
89 }
90
91 };
92
93 // TODO
94 // Distance field text in textblobs
95
GrAtlasTextContext(GrContext * context,SkGpuDevice * gpuDevice,const SkDeviceProperties & properties,bool enableDistanceFields)96 GrAtlasTextContext::GrAtlasTextContext(GrContext* context,
97 SkGpuDevice* gpuDevice,
98 const SkDeviceProperties& properties,
99 bool enableDistanceFields)
100 : INHERITED(context, gpuDevice, properties)
101 , fDistanceAdjustTable(SkNEW_ARGS(DistanceAdjustTable, (properties.gamma()))) {
102 // We overallocate vertices in our textblobs based on the assumption that A8 has the greatest
103 // vertexStride
104 SK_COMPILE_ASSERT(kGrayTextVASize >= kColorTextVASize && kGrayTextVASize >= kLCDTextVASize,
105 vertex_attribute_changed);
106 fCurrStrike = NULL;
107 fCache = context->getTextBlobCache();
108
109 #if SK_FORCE_DISTANCE_FIELD_TEXT
110 fEnableDFRendering = true;
111 #else
112 fEnableDFRendering = enableDistanceFields;
113 #endif
114 }
115
buildDistanceAdjustTable(float gamma)116 void GrAtlasTextContext::DistanceAdjustTable::buildDistanceAdjustTable(float gamma) {
117
118 // This is used for an approximation of the mask gamma hack, used by raster and bitmap
119 // text. The mask gamma hack is based off of guessing what the blend color is going to
120 // be, and adjusting the mask so that when run through the linear blend will
121 // produce the value closest to the desired result. However, in practice this means
122 // that the 'adjusted' mask is just increasing or decreasing the coverage of
123 // the mask depending on what it is thought it will blit against. For black (on
124 // assumed white) this means that coverages are decreased (on a curve). For white (on
125 // assumed black) this means that coverages are increased (on a a curve). At
126 // middle (perceptual) gray (which could be blit against anything) the coverages
127 // remain the same.
128 //
129 // The idea here is that instead of determining the initial (real) coverage and
130 // then adjusting that coverage, we determine an adjusted coverage directly by
131 // essentially manipulating the geometry (in this case, the distance to the glyph
132 // edge). So for black (on assumed white) this thins a bit; for white (on
133 // assumed black) this fake bolds the geometry a bit.
134 //
135 // The distance adjustment is calculated by determining the actual coverage value which
136 // when fed into in the mask gamma table gives us an 'adjusted coverage' value of 0.5. This
137 // actual coverage value (assuming it's between 0 and 1) corresponds to a distance from the
138 // actual edge. So by subtracting this distance adjustment and computing without the
139 // the coverage adjustment we should get 0.5 coverage at the same point.
140 //
141 // This has several implications:
142 // For non-gray lcd smoothed text, each subpixel essentially is using a
143 // slightly different geometry.
144 //
145 // For black (on assumed white) this may not cover some pixels which were
146 // previously covered; however those pixels would have been only slightly
147 // covered and that slight coverage would have been decreased anyway. Also, some pixels
148 // which were previously fully covered may no longer be fully covered.
149 //
150 // For white (on assumed black) this may cover some pixels which weren't
151 // previously covered at all.
152
153 int width, height;
154 size_t size;
155
156 #ifdef SK_GAMMA_CONTRAST
157 SkScalar contrast = SK_GAMMA_CONTRAST;
158 #else
159 SkScalar contrast = 0.5f;
160 #endif
161 SkScalar paintGamma = gamma;
162 SkScalar deviceGamma = gamma;
163
164 size = SkScalerContext::GetGammaLUTSize(contrast, paintGamma, deviceGamma,
165 &width, &height);
166
167 SkASSERT(kExpectedDistanceAdjustTableSize == height);
168 fTable = SkNEW_ARRAY(SkScalar, height);
169
170 SkAutoTArray<uint8_t> data((int)size);
171 SkScalerContext::GetGammaLUTData(contrast, paintGamma, deviceGamma, data.get());
172
173 // find the inverse points where we cross 0.5
174 // binsearch might be better, but we only need to do this once on creation
175 for (int row = 0; row < height; ++row) {
176 uint8_t* rowPtr = data.get() + row*width;
177 for (int col = 0; col < width - 1; ++col) {
178 if (rowPtr[col] <= 127 && rowPtr[col + 1] >= 128) {
179 // compute point where a mask value will give us a result of 0.5
180 float interp = (127.5f - rowPtr[col]) / (rowPtr[col + 1] - rowPtr[col]);
181 float borderAlpha = (col + interp) / 255.f;
182
183 // compute t value for that alpha
184 // this is an approximate inverse for smoothstep()
185 float t = borderAlpha*(borderAlpha*(4.0f*borderAlpha - 6.0f) + 5.0f) / 3.0f;
186
187 // compute distance which gives us that t value
188 const float kDistanceFieldAAFactor = 0.65f; // should match SK_DistanceFieldAAFactor
189 float d = 2.0f*kDistanceFieldAAFactor*t - kDistanceFieldAAFactor;
190
191 fTable[row] = d;
192 break;
193 }
194 }
195 }
196 }
197
Create(GrContext * context,SkGpuDevice * gpuDevice,const SkDeviceProperties & props,bool enableDistanceFields)198 GrAtlasTextContext* GrAtlasTextContext::Create(GrContext* context,
199 SkGpuDevice* gpuDevice,
200 const SkDeviceProperties& props,
201 bool enableDistanceFields) {
202 return SkNEW_ARGS(GrAtlasTextContext, (context, gpuDevice, props, enableDistanceFields));
203 }
204
canDraw(const GrRenderTarget *,const GrClip &,const GrPaint &,const SkPaint & skPaint,const SkMatrix & viewMatrix)205 bool GrAtlasTextContext::canDraw(const GrRenderTarget*,
206 const GrClip&,
207 const GrPaint&,
208 const SkPaint& skPaint,
209 const SkMatrix& viewMatrix) {
210 return this->canDrawAsDistanceFields(skPaint, viewMatrix) ||
211 !SkDraw::ShouldDrawTextAsPaths(skPaint, viewMatrix);
212 }
213
ComputeCanonicalColor(const SkPaint & paint,bool lcd)214 GrColor GrAtlasTextContext::ComputeCanonicalColor(const SkPaint& paint, bool lcd) {
215 GrColor canonicalColor = paint.computeLuminanceColor();
216 if (lcd) {
217 // This is the correct computation, but there are tons of cases where LCD can be overridden.
218 // For now we just regenerate if any run in a textblob has LCD.
219 // TODO figure out where all of these overrides are and see if we can incorporate that logic
220 // at a higher level *OR* use sRGB
221 SkASSERT(false);
222 //canonicalColor = SkMaskGamma::CanonicalColor(canonicalColor);
223 } else {
224 // A8, though can have mixed BMP text but it shouldn't matter because BMP text won't have
225 // gamma corrected masks anyways, nor color
226 U8CPU lum = SkComputeLuminance(SkColorGetR(canonicalColor),
227 SkColorGetG(canonicalColor),
228 SkColorGetB(canonicalColor));
229 // reduce to our finite number of bits
230 canonicalColor = SkMaskGamma::CanonicalColor(SkColorSetRGB(lum, lum, lum));
231 }
232 return canonicalColor;
233 }
234
235 // TODO if this function ever shows up in profiling, then we can compute this value when the
236 // textblob is being built and cache it. However, for the time being textblobs mostly only have 1
237 // run so this is not a big deal to compute here.
HasLCD(const SkTextBlob * blob)238 bool GrAtlasTextContext::HasLCD(const SkTextBlob* blob) {
239 SkTextBlob::RunIterator it(blob);
240 for (; !it.done(); it.next()) {
241 if (it.isLCD()) {
242 return true;
243 }
244 }
245 return false;
246 }
247
MustRegenerateBlob(SkScalar * outTransX,SkScalar * outTransY,const BitmapTextBlob & blob,const SkPaint & paint,const SkMaskFilter::BlurRec & blurRec,const SkMatrix & viewMatrix,SkScalar x,SkScalar y)248 bool GrAtlasTextContext::MustRegenerateBlob(SkScalar* outTransX, SkScalar* outTransY,
249 const BitmapTextBlob& blob, const SkPaint& paint,
250 const SkMaskFilter::BlurRec& blurRec,
251 const SkMatrix& viewMatrix, SkScalar x, SkScalar y) {
252 // If we have LCD text then our canonical color will be set to transparent, in this case we have
253 // to regenerate the blob on any color change
254 if (blob.fKey.fCanonicalColor == SK_ColorTRANSPARENT && blob.fPaintColor != paint.getColor()) {
255 return true;
256 }
257
258 if (blob.fViewMatrix.hasPerspective() != viewMatrix.hasPerspective()) {
259 return true;
260 }
261
262 if (blob.fViewMatrix.hasPerspective() && !blob.fViewMatrix.cheapEqualTo(viewMatrix)) {
263 return true;
264 }
265
266 // We only cache one masked version
267 if (blob.fKey.fHasBlur &&
268 (blob.fBlurRec.fSigma != blurRec.fSigma ||
269 blob.fBlurRec.fStyle != blurRec.fStyle ||
270 blob.fBlurRec.fQuality != blurRec.fQuality)) {
271 return true;
272 }
273
274 // Similarly, we only cache one version for each style
275 if (blob.fKey.fStyle != SkPaint::kFill_Style &&
276 (blob.fStrokeInfo.fFrameWidth != paint.getStrokeWidth() ||
277 blob.fStrokeInfo.fMiterLimit != paint.getStrokeMiter() ||
278 blob.fStrokeInfo.fJoin != paint.getStrokeJoin())) {
279 return true;
280 }
281
282 // Mixed blobs must be regenerated. We could probably figure out a way to do integer scrolls
283 // for mixed blobs if this becomes an issue.
284 if (blob.hasBitmap() && blob.hasDistanceField()) {
285 // Identical viewmatrices and we can reuse in all cases
286 if (blob.fViewMatrix.cheapEqualTo(viewMatrix) && x == blob.fX && y == blob.fY) {
287 return false;
288 }
289 return true;
290 }
291
292 if (blob.hasBitmap()) {
293 if (blob.fViewMatrix.getScaleX() != viewMatrix.getScaleX() ||
294 blob.fViewMatrix.getScaleY() != viewMatrix.getScaleY() ||
295 blob.fViewMatrix.getSkewX() != viewMatrix.getSkewX() ||
296 blob.fViewMatrix.getSkewY() != viewMatrix.getSkewY()) {
297 return true;
298 }
299
300 // We can update the positions in the cachedtextblobs without regenerating the whole blob,
301 // but only for integer translations.
302 // This cool bit of math will determine the necessary translation to apply to the already
303 // generated vertex coordinates to move them to the correct position
304 SkScalar transX = viewMatrix.getTranslateX() +
305 viewMatrix.getScaleX() * (x - blob.fX) +
306 viewMatrix.getSkewX() * (y - blob.fY) -
307 blob.fViewMatrix.getTranslateX();
308 SkScalar transY = viewMatrix.getTranslateY() +
309 viewMatrix.getSkewY() * (x - blob.fX) +
310 viewMatrix.getScaleY() * (y - blob.fY) -
311 blob.fViewMatrix.getTranslateY();
312 if (!SkScalarIsInt(transX) || !SkScalarIsInt(transY) ) {
313 return true;
314 }
315
316 (*outTransX) = transX;
317 (*outTransY) = transY;
318 } else if (blob.hasDistanceField()) {
319 // A scale outside of [blob.fMaxMinScale, blob.fMinMaxScale] would result in a different
320 // distance field being generated, so we have to regenerate in those cases
321 SkScalar newMaxScale = viewMatrix.getMaxScale();
322 SkScalar oldMaxScale = blob.fViewMatrix.getMaxScale();
323 SkScalar scaleAdjust = newMaxScale / oldMaxScale;
324 if (scaleAdjust < blob.fMaxMinScale || scaleAdjust > blob.fMinMaxScale) {
325 return true;
326 }
327
328 (*outTransX) = x - blob.fX;
329 (*outTransY) = y - blob.fY;
330 }
331 // It is possible that a blob has neither distanceField nor bitmaptext. This is in the case
332 // when all of the runs inside the blob are drawn as paths. In this case, we always regenerate
333 // the blob anyways at flush time, so no need to regenerate explicitly
334
335 return false;
336 }
337
338
setupCache(BitmapTextBlob::Run * run,const SkPaint & skPaint,const SkMatrix * viewMatrix,bool noGamma)339 inline SkGlyphCache* GrAtlasTextContext::setupCache(BitmapTextBlob::Run* run,
340 const SkPaint& skPaint,
341 const SkMatrix* viewMatrix,
342 bool noGamma) {
343 skPaint.getScalerContextDescriptor(&run->fDescriptor, &fDeviceProperties, viewMatrix, noGamma);
344 run->fTypeface.reset(SkSafeRef(skPaint.getTypeface()));
345 return SkGlyphCache::DetachCache(run->fTypeface, run->fDescriptor.getDesc());
346 }
347
drawTextBlob(GrRenderTarget * rt,const GrClip & clip,const SkPaint & skPaint,const SkMatrix & viewMatrix,const SkTextBlob * blob,SkScalar x,SkScalar y,SkDrawFilter * drawFilter,const SkIRect & clipBounds)348 void GrAtlasTextContext::drawTextBlob(GrRenderTarget* rt, const GrClip& clip,
349 const SkPaint& skPaint, const SkMatrix& viewMatrix,
350 const SkTextBlob* blob, SkScalar x, SkScalar y,
351 SkDrawFilter* drawFilter, const SkIRect& clipBounds) {
352 // If we have been abandoned, then don't draw
353 if (!fContext->getTextTarget()) {
354 return;
355 }
356
357 SkAutoTUnref<BitmapTextBlob> cacheBlob;
358 SkMaskFilter::BlurRec blurRec;
359 BitmapTextBlob::Key key;
360 // It might be worth caching these things, but its not clear at this time
361 // TODO for animated mask filters, this will fill up our cache. We need a safeguard here
362 const SkMaskFilter* mf = skPaint.getMaskFilter();
363 bool canCache = !(skPaint.getPathEffect() ||
364 (mf && !mf->asABlur(&blurRec)) ||
365 drawFilter);
366
367 if (canCache) {
368 bool hasLCD = HasLCD(blob);
369
370 // We canonicalize all non-lcd draws to use kUnknown_SkPixelGeometry
371 SkPixelGeometry pixelGeometry = hasLCD ? fDeviceProperties.pixelGeometry() :
372 kUnknown_SkPixelGeometry;
373
374 // TODO we want to figure out a way to be able to use the canonical color on LCD text,
375 // see the note on ComputeCanonicalColor above. We pick a dummy value for LCD text to
376 // ensure we always match the same key
377 GrColor canonicalColor = hasLCD ? SK_ColorTRANSPARENT :
378 ComputeCanonicalColor(skPaint, hasLCD);
379
380 key.fPixelGeometry = pixelGeometry;
381 key.fUniqueID = blob->uniqueID();
382 key.fStyle = skPaint.getStyle();
383 key.fHasBlur = SkToBool(mf);
384 key.fCanonicalColor = canonicalColor;
385 cacheBlob.reset(SkSafeRef(fCache->find(key)));
386 }
387
388 SkIRect clipRect;
389 clip.getConservativeBounds(rt->width(), rt->height(), &clipRect);
390
391 SkScalar transX = 0.f;
392 SkScalar transY = 0.f;
393
394 // Though for the time being runs in the textblob can override the paint, they only touch font
395 // info.
396 GrPaint grPaint;
397 if (!SkPaint2GrPaint(fContext, rt, skPaint, viewMatrix, true, &grPaint)) {
398 return;
399 }
400
401 if (cacheBlob) {
402 if (MustRegenerateBlob(&transX, &transY, *cacheBlob, skPaint, blurRec, viewMatrix, x, y)) {
403 // We have to remake the blob because changes may invalidate our masks.
404 // TODO we could probably get away reuse most of the time if the pointer is unique,
405 // but we'd have to clear the subrun information
406 fCache->remove(cacheBlob);
407 cacheBlob.reset(SkRef(fCache->createCachedBlob(blob, key, blurRec, skPaint,
408 kGrayTextVASize)));
409 this->regenerateTextBlob(cacheBlob, skPaint, grPaint.getColor(), viewMatrix, blob, x, y,
410 drawFilter, clipRect, rt, clip, grPaint);
411 } else {
412 // If we can reuse the blob, then make sure we update the blob's viewmatrix, and x/y
413 // offsets
414 cacheBlob->fViewMatrix = viewMatrix;
415 cacheBlob->fX = x;
416 cacheBlob->fY = y;
417 fCache->makeMRU(cacheBlob);
418 }
419 } else {
420 if (canCache) {
421 cacheBlob.reset(SkRef(fCache->createCachedBlob(blob, key, blurRec, skPaint,
422 kGrayTextVASize)));
423 } else {
424 cacheBlob.reset(fCache->createBlob(blob, kGrayTextVASize));
425 }
426 this->regenerateTextBlob(cacheBlob, skPaint, grPaint.getColor(), viewMatrix, blob, x, y,
427 drawFilter, clipRect, rt, clip, grPaint);
428 }
429
430 cacheBlob->fPaintColor = skPaint.getColor();
431 this->flush(fContext->getTextTarget(), blob, cacheBlob, rt, skPaint, grPaint, drawFilter,
432 clip, viewMatrix, clipBounds, x, y, transX, transY);
433 }
434
canDrawAsDistanceFields(const SkPaint & skPaint,const SkMatrix & viewMatrix)435 inline bool GrAtlasTextContext::canDrawAsDistanceFields(const SkPaint& skPaint,
436 const SkMatrix& viewMatrix) {
437 // TODO: support perspective (need getMaxScale replacement)
438 if (viewMatrix.hasPerspective()) {
439 return false;
440 }
441
442 SkScalar maxScale = viewMatrix.getMaxScale();
443 SkScalar scaledTextSize = maxScale*skPaint.getTextSize();
444 // Hinted text looks far better at small resolutions
445 // Scaling up beyond 2x yields undesireable artifacts
446 if (scaledTextSize < kMinDFFontSize || scaledTextSize > kLargeDFFontLimit) {
447 return false;
448 }
449
450 if (!fEnableDFRendering && !skPaint.isDistanceFieldTextTEMP() &&
451 scaledTextSize < kLargeDFFontSize) {
452 return false;
453 }
454
455 // rasterizers and mask filters modify alpha, which doesn't
456 // translate well to distance
457 if (skPaint.getRasterizer() || skPaint.getMaskFilter() ||
458 !fContext->getTextTarget()->caps()->shaderCaps()->shaderDerivativeSupport()) {
459 return false;
460 }
461
462 // TODO: add some stroking support
463 if (skPaint.getStyle() != SkPaint::kFill_Style) {
464 return false;
465 }
466
467 return true;
468 }
469
regenerateTextBlob(BitmapTextBlob * cacheBlob,const SkPaint & skPaint,GrColor color,const SkMatrix & viewMatrix,const SkTextBlob * blob,SkScalar x,SkScalar y,SkDrawFilter * drawFilter,const SkIRect & clipRect,GrRenderTarget * rt,const GrClip & clip,const GrPaint & paint)470 void GrAtlasTextContext::regenerateTextBlob(BitmapTextBlob* cacheBlob,
471 const SkPaint& skPaint, GrColor color,
472 const SkMatrix& viewMatrix,
473 const SkTextBlob* blob, SkScalar x, SkScalar y,
474 SkDrawFilter* drawFilter, const SkIRect& clipRect,
475 GrRenderTarget* rt, const GrClip& clip,
476 const GrPaint& paint) {
477 cacheBlob->fViewMatrix = viewMatrix;
478 cacheBlob->fX = x;
479 cacheBlob->fY = y;
480
481 // Regenerate textblob
482 SkPaint runPaint = skPaint;
483 SkTextBlob::RunIterator it(blob);
484 for (int run = 0; !it.done(); it.next(), run++) {
485 int glyphCount = it.glyphCount();
486 size_t textLen = glyphCount * sizeof(uint16_t);
487 const SkPoint& offset = it.offset();
488 // applyFontToPaint() always overwrites the exact same attributes,
489 // so it is safe to not re-seed the paint for this reason.
490 it.applyFontToPaint(&runPaint);
491
492 if (drawFilter && !drawFilter->filter(&runPaint, SkDrawFilter::kText_Type)) {
493 // A false return from filter() means we should abort the current draw.
494 runPaint = skPaint;
495 continue;
496 }
497
498 runPaint.setFlags(fGpuDevice->filterTextFlags(runPaint));
499
500 // setup vertex / glyphIndex for the new run
501 if (run > 0) {
502 PerSubRunInfo& newRun = cacheBlob->fRuns[run].fSubRunInfo.back();
503 PerSubRunInfo& lastRun = cacheBlob->fRuns[run - 1].fSubRunInfo.back();
504
505 newRun.fVertexStartIndex = lastRun.fVertexEndIndex;
506 newRun.fVertexEndIndex = lastRun.fVertexEndIndex;
507
508 newRun.fGlyphStartIndex = lastRun.fGlyphEndIndex;
509 newRun.fGlyphEndIndex = lastRun.fGlyphEndIndex;
510 }
511
512 if (this->canDrawAsDistanceFields(runPaint, viewMatrix)) {
513 cacheBlob->setHasDistanceField();
514 SkPaint dfPaint = runPaint;
515 SkScalar textRatio;
516 this->initDistanceFieldPaint(cacheBlob, &dfPaint, &textRatio, viewMatrix);
517 Run& runIdx = cacheBlob->fRuns[run];
518 PerSubRunInfo& subRun = runIdx.fSubRunInfo.back();
519 subRun.fUseLCDText = runPaint.isLCDRenderText();
520 subRun.fDrawAsDistanceFields = true;
521
522 SkGlyphCache* cache = this->setupCache(&cacheBlob->fRuns[run], dfPaint, NULL, true);
523
524 SkTDArray<char> fallbackTxt;
525 SkTDArray<SkScalar> fallbackPos;
526 SkPoint dfOffset;
527 int scalarsPerPosition = 2;
528 switch (it.positioning()) {
529 case SkTextBlob::kDefault_Positioning: {
530 this->internalDrawDFText(cacheBlob, run, cache, dfPaint, color, viewMatrix,
531 (const char *)it.glyphs(), textLen,
532 x + offset.x(), y + offset.y(), clipRect, textRatio,
533 &fallbackTxt, &fallbackPos, &dfOffset, runPaint);
534 break;
535 }
536 case SkTextBlob::kHorizontal_Positioning: {
537 scalarsPerPosition = 1;
538 dfOffset = SkPoint::Make(x, y + offset.y());
539 this->internalDrawDFPosText(cacheBlob, run, cache, dfPaint, color, viewMatrix,
540 (const char*)it.glyphs(), textLen, it.pos(),
541 scalarsPerPosition, dfOffset, clipRect, textRatio,
542 &fallbackTxt, &fallbackPos);
543 break;
544 }
545 case SkTextBlob::kFull_Positioning: {
546 dfOffset = SkPoint::Make(x, y);
547 this->internalDrawDFPosText(cacheBlob, run, cache, dfPaint, color, viewMatrix,
548 (const char*)it.glyphs(), textLen, it.pos(),
549 scalarsPerPosition, dfOffset, clipRect, textRatio,
550 &fallbackTxt, &fallbackPos);
551 break;
552 }
553 }
554 if (fallbackTxt.count()) {
555 this->fallbackDrawPosText(cacheBlob, run, rt, clip, paint, runPaint, viewMatrix,
556 fallbackTxt, fallbackPos, scalarsPerPosition, dfOffset,
557 clipRect);
558 }
559
560 SkGlyphCache::AttachCache(cache);
561 } else if (SkDraw::ShouldDrawTextAsPaths(runPaint, viewMatrix)) {
562 cacheBlob->fRuns[run].fDrawAsPaths = true;
563 } else {
564 cacheBlob->setHasBitmap();
565 SkGlyphCache* cache = this->setupCache(&cacheBlob->fRuns[run], runPaint, &viewMatrix,
566 false);
567 switch (it.positioning()) {
568 case SkTextBlob::kDefault_Positioning:
569 this->internalDrawBMPText(cacheBlob, run, cache, runPaint, color, viewMatrix,
570 (const char *)it.glyphs(), textLen,
571 x + offset.x(), y + offset.y(), clipRect);
572 break;
573 case SkTextBlob::kHorizontal_Positioning:
574 this->internalDrawBMPPosText(cacheBlob, run, cache, runPaint, color, viewMatrix,
575 (const char*)it.glyphs(), textLen, it.pos(), 1,
576 SkPoint::Make(x, y + offset.y()), clipRect);
577 break;
578 case SkTextBlob::kFull_Positioning:
579 this->internalDrawBMPPosText(cacheBlob, run, cache, runPaint, color, viewMatrix,
580 (const char*)it.glyphs(), textLen, it.pos(), 2,
581 SkPoint::Make(x, y), clipRect);
582 break;
583 }
584 SkGlyphCache::AttachCache(cache);
585 }
586
587 if (drawFilter) {
588 // A draw filter may change the paint arbitrarily, so we must re-seed in this case.
589 runPaint = skPaint;
590 }
591 }
592 }
593
initDistanceFieldPaint(BitmapTextBlob * blob,SkPaint * skPaint,SkScalar * textRatio,const SkMatrix & viewMatrix)594 inline void GrAtlasTextContext::initDistanceFieldPaint(BitmapTextBlob* blob,
595 SkPaint* skPaint,
596 SkScalar* textRatio,
597 const SkMatrix& viewMatrix) {
598 // getMaxScale doesn't support perspective, so neither do we at the moment
599 SkASSERT(!viewMatrix.hasPerspective());
600 SkScalar maxScale = viewMatrix.getMaxScale();
601 SkScalar textSize = skPaint->getTextSize();
602 SkScalar scaledTextSize = textSize;
603 // if we have non-unity scale, we need to choose our base text size
604 // based on the SkPaint's text size multiplied by the max scale factor
605 // TODO: do we need to do this if we're scaling down (i.e. maxScale < 1)?
606 if (maxScale > 0 && !SkScalarNearlyEqual(maxScale, SK_Scalar1)) {
607 scaledTextSize *= maxScale;
608 }
609
610 // We have three sizes of distance field text, and within each size 'bucket' there is a floor
611 // and ceiling. A scale outside of this range would require regenerating the distance fields
612 SkScalar dfMaskScaleFloor;
613 SkScalar dfMaskScaleCeil;
614 if (scaledTextSize <= kSmallDFFontLimit) {
615 dfMaskScaleFloor = kMinDFFontSize;
616 dfMaskScaleCeil = kSmallDFFontLimit;
617 *textRatio = textSize / kSmallDFFontSize;
618 skPaint->setTextSize(SkIntToScalar(kSmallDFFontSize));
619 } else if (scaledTextSize <= kMediumDFFontLimit) {
620 dfMaskScaleFloor = kSmallDFFontLimit;
621 dfMaskScaleCeil = kMediumDFFontLimit;
622 *textRatio = textSize / kMediumDFFontSize;
623 skPaint->setTextSize(SkIntToScalar(kMediumDFFontSize));
624 } else {
625 dfMaskScaleFloor = kMediumDFFontLimit;
626 dfMaskScaleCeil = kLargeDFFontLimit;
627 *textRatio = textSize / kLargeDFFontSize;
628 skPaint->setTextSize(SkIntToScalar(kLargeDFFontSize));
629 }
630
631 // Because there can be multiple runs in the blob, we want the overall maxMinScale, and
632 // minMaxScale to make regeneration decisions. Specifically, we want the maximum minimum scale
633 // we can tolerate before we'd drop to a lower mip size, and the minimum maximum scale we can
634 // tolerate before we'd have to move to a large mip size. When we actually test these values
635 // we look at the delta in scale between the new viewmatrix and the old viewmatrix, and test
636 // against these values to decide if we can reuse or not(ie, will a given scale change our mip
637 // level)
638 SkASSERT(dfMaskScaleFloor <= scaledTextSize && scaledTextSize <= dfMaskScaleCeil);
639 blob->fMaxMinScale = SkMaxScalar(dfMaskScaleFloor / scaledTextSize, blob->fMaxMinScale);
640 blob->fMinMaxScale = SkMinScalar(dfMaskScaleCeil / scaledTextSize, blob->fMinMaxScale);
641
642 skPaint->setLCDRenderText(false);
643 skPaint->setAutohinted(false);
644 skPaint->setHinting(SkPaint::kNormal_Hinting);
645 skPaint->setSubpixelText(true);
646 }
647
fallbackDrawPosText(BitmapTextBlob * blob,int runIndex,GrRenderTarget * rt,const GrClip & clip,const GrPaint & paint,const SkPaint & skPaint,const SkMatrix & viewMatrix,const SkTDArray<char> & fallbackTxt,const SkTDArray<SkScalar> & fallbackPos,int scalarsPerPosition,const SkPoint & offset,const SkIRect & clipRect)648 inline void GrAtlasTextContext::fallbackDrawPosText(BitmapTextBlob* blob,
649 int runIndex,
650 GrRenderTarget* rt, const GrClip& clip,
651 const GrPaint& paint,
652 const SkPaint& skPaint,
653 const SkMatrix& viewMatrix,
654 const SkTDArray<char>& fallbackTxt,
655 const SkTDArray<SkScalar>& fallbackPos,
656 int scalarsPerPosition,
657 const SkPoint& offset,
658 const SkIRect& clipRect) {
659 SkASSERT(fallbackTxt.count());
660 blob->setHasBitmap();
661 Run& run = blob->fRuns[runIndex];
662 // Push back a new subrun to fill and set the override descriptor
663 run.push_back();
664 run.fOverrideDescriptor.reset(SkNEW(SkAutoDescriptor));
665 skPaint.getScalerContextDescriptor(run.fOverrideDescriptor,
666 &fDeviceProperties, &viewMatrix, false);
667 SkGlyphCache* cache = SkGlyphCache::DetachCache(run.fTypeface,
668 run.fOverrideDescriptor->getDesc());
669 this->internalDrawBMPPosText(blob, runIndex, cache, skPaint, paint.getColor(), viewMatrix,
670 fallbackTxt.begin(), fallbackTxt.count(),
671 fallbackPos.begin(), scalarsPerPosition, offset, clipRect);
672 SkGlyphCache::AttachCache(cache);
673 }
674
675 inline GrAtlasTextContext::BitmapTextBlob*
setupDFBlob(int glyphCount,const SkPaint & origPaint,const SkMatrix & viewMatrix,SkGlyphCache ** cache,SkPaint * dfPaint,SkScalar * textRatio)676 GrAtlasTextContext::setupDFBlob(int glyphCount, const SkPaint& origPaint,
677 const SkMatrix& viewMatrix, SkGlyphCache** cache,
678 SkPaint* dfPaint, SkScalar* textRatio) {
679 BitmapTextBlob* blob = fCache->createBlob(glyphCount, 1, kGrayTextVASize);
680
681 *dfPaint = origPaint;
682 this->initDistanceFieldPaint(blob, dfPaint, textRatio, viewMatrix);
683 blob->fViewMatrix = viewMatrix;
684 Run& run = blob->fRuns[0];
685 PerSubRunInfo& subRun = run.fSubRunInfo.back();
686 subRun.fUseLCDText = origPaint.isLCDRenderText();
687 subRun.fDrawAsDistanceFields = true;
688
689 *cache = this->setupCache(&blob->fRuns[0], *dfPaint, NULL, true);
690 return blob;
691 }
692
693 inline GrAtlasTextContext::BitmapTextBlob*
createDrawTextBlob(GrRenderTarget * rt,const GrClip & clip,const GrPaint & paint,const SkPaint & skPaint,const SkMatrix & viewMatrix,const char text[],size_t byteLength,SkScalar x,SkScalar y,const SkIRect & regionClipBounds)694 GrAtlasTextContext::createDrawTextBlob(GrRenderTarget* rt, const GrClip& clip,
695 const GrPaint& paint, const SkPaint& skPaint,
696 const SkMatrix& viewMatrix,
697 const char text[], size_t byteLength,
698 SkScalar x, SkScalar y, const SkIRect& regionClipBounds) {
699 int glyphCount = skPaint.countText(text, byteLength);
700 SkIRect clipRect;
701 clip.getConservativeBounds(rt->width(), rt->height(), &clipRect);
702
703 BitmapTextBlob* blob;
704 if (this->canDrawAsDistanceFields(skPaint, viewMatrix)) {
705 SkPaint dfPaint;
706 SkScalar textRatio;
707 SkGlyphCache* cache;
708 blob = this->setupDFBlob(glyphCount, skPaint, viewMatrix, &cache, &dfPaint, &textRatio);
709
710 SkTDArray<char> fallbackTxt;
711 SkTDArray<SkScalar> fallbackPos;
712 SkPoint offset;
713 this->internalDrawDFText(blob, 0, cache, dfPaint, paint.getColor(), viewMatrix, text,
714 byteLength, x, y, clipRect, textRatio, &fallbackTxt, &fallbackPos,
715 &offset, skPaint);
716 SkGlyphCache::AttachCache(cache);
717 if (fallbackTxt.count()) {
718 this->fallbackDrawPosText(blob, 0, rt, clip, paint, skPaint, viewMatrix, fallbackTxt,
719 fallbackPos, 2, offset, clipRect);
720 }
721 } else {
722 blob = fCache->createBlob(glyphCount, 1, kGrayTextVASize);
723 blob->fViewMatrix = viewMatrix;
724
725 SkGlyphCache* cache = this->setupCache(&blob->fRuns[0], skPaint, &viewMatrix, false);
726 this->internalDrawBMPText(blob, 0, cache, skPaint, paint.getColor(), viewMatrix, text,
727 byteLength, x, y, clipRect);
728 SkGlyphCache::AttachCache(cache);
729 }
730 return blob;
731 }
732
733 inline GrAtlasTextContext::BitmapTextBlob*
createDrawPosTextBlob(GrRenderTarget * rt,const GrClip & clip,const GrPaint & paint,const SkPaint & skPaint,const SkMatrix & viewMatrix,const char text[],size_t byteLength,const SkScalar pos[],int scalarsPerPosition,const SkPoint & offset,const SkIRect & regionClipBounds)734 GrAtlasTextContext::createDrawPosTextBlob(GrRenderTarget* rt, const GrClip& clip,
735 const GrPaint& paint, const SkPaint& skPaint,
736 const SkMatrix& viewMatrix,
737 const char text[], size_t byteLength,
738 const SkScalar pos[], int scalarsPerPosition,
739 const SkPoint& offset, const SkIRect& regionClipBounds) {
740 int glyphCount = skPaint.countText(text, byteLength);
741
742 SkIRect clipRect;
743 clip.getConservativeBounds(rt->width(), rt->height(), &clipRect);
744
745 BitmapTextBlob* blob;
746 if (this->canDrawAsDistanceFields(skPaint, viewMatrix)) {
747 SkPaint dfPaint;
748 SkScalar textRatio;
749 SkGlyphCache* cache;
750 blob = this->setupDFBlob(glyphCount, skPaint, viewMatrix, &cache, &dfPaint, &textRatio);
751
752 SkTDArray<char> fallbackTxt;
753 SkTDArray<SkScalar> fallbackPos;
754 this->internalDrawDFPosText(blob, 0, cache, dfPaint, paint.getColor(), viewMatrix, text,
755 byteLength, pos, scalarsPerPosition, offset, clipRect,
756 textRatio, &fallbackTxt, &fallbackPos);
757 SkGlyphCache::AttachCache(cache);
758 if (fallbackTxt.count()) {
759 this->fallbackDrawPosText(blob, 0, rt, clip, paint, skPaint, viewMatrix, fallbackTxt,
760 fallbackPos, scalarsPerPosition, offset, clipRect);
761 }
762 } else {
763 blob = fCache->createBlob(glyphCount, 1, kGrayTextVASize);
764 blob->fViewMatrix = viewMatrix;
765 SkGlyphCache* cache = this->setupCache(&blob->fRuns[0], skPaint, &viewMatrix, false);
766 this->internalDrawBMPPosText(blob, 0, cache, skPaint, paint.getColor(), viewMatrix, text,
767 byteLength, pos, scalarsPerPosition, offset, clipRect);
768 SkGlyphCache::AttachCache(cache);
769 }
770 return blob;
771 }
772
onDrawText(GrRenderTarget * rt,const GrClip & clip,const GrPaint & paint,const SkPaint & skPaint,const SkMatrix & viewMatrix,const char text[],size_t byteLength,SkScalar x,SkScalar y,const SkIRect & regionClipBounds)773 void GrAtlasTextContext::onDrawText(GrRenderTarget* rt, const GrClip& clip,
774 const GrPaint& paint, const SkPaint& skPaint,
775 const SkMatrix& viewMatrix,
776 const char text[], size_t byteLength,
777 SkScalar x, SkScalar y, const SkIRect& regionClipBounds) {
778 SkAutoTUnref<BitmapTextBlob> blob(
779 this->createDrawTextBlob(rt, clip, paint, skPaint, viewMatrix,
780 text, byteLength, x, y, regionClipBounds));
781 this->flush(fContext->getTextTarget(), blob, rt, skPaint, paint, clip, regionClipBounds);
782 }
783
onDrawPosText(GrRenderTarget * rt,const GrClip & clip,const GrPaint & paint,const SkPaint & skPaint,const SkMatrix & viewMatrix,const char text[],size_t byteLength,const SkScalar pos[],int scalarsPerPosition,const SkPoint & offset,const SkIRect & regionClipBounds)784 void GrAtlasTextContext::onDrawPosText(GrRenderTarget* rt, const GrClip& clip,
785 const GrPaint& paint, const SkPaint& skPaint,
786 const SkMatrix& viewMatrix,
787 const char text[], size_t byteLength,
788 const SkScalar pos[], int scalarsPerPosition,
789 const SkPoint& offset, const SkIRect& regionClipBounds) {
790 SkAutoTUnref<BitmapTextBlob> blob(
791 this->createDrawPosTextBlob(rt, clip, paint, skPaint, viewMatrix,
792 text, byteLength,
793 pos, scalarsPerPosition,
794 offset, regionClipBounds));
795
796 this->flush(fContext->getTextTarget(), blob, rt, skPaint, paint, clip, regionClipBounds);
797 }
798
internalDrawBMPText(BitmapTextBlob * blob,int runIndex,SkGlyphCache * cache,const SkPaint & skPaint,GrColor color,const SkMatrix & viewMatrix,const char text[],size_t byteLength,SkScalar x,SkScalar y,const SkIRect & clipRect)799 void GrAtlasTextContext::internalDrawBMPText(BitmapTextBlob* blob, int runIndex,
800 SkGlyphCache* cache, const SkPaint& skPaint,
801 GrColor color,
802 const SkMatrix& viewMatrix,
803 const char text[], size_t byteLength,
804 SkScalar x, SkScalar y, const SkIRect& clipRect) {
805 SkASSERT(byteLength == 0 || text != NULL);
806
807 // nothing to draw
808 if (text == NULL || byteLength == 0) {
809 return;
810 }
811
812 fCurrStrike = NULL;
813 SkDrawCacheProc glyphCacheProc = skPaint.getDrawCacheProc();
814
815 // Get GrFontScaler from cache
816 GrFontScaler* fontScaler = GetGrFontScaler(cache);
817
818 // transform our starting point
819 {
820 SkPoint loc;
821 viewMatrix.mapXY(x, y, &loc);
822 x = loc.fX;
823 y = loc.fY;
824 }
825
826 // need to measure first
827 if (skPaint.getTextAlign() != SkPaint::kLeft_Align) {
828 SkVector stopVector;
829 MeasureText(cache, glyphCacheProc, text, byteLength, &stopVector);
830
831 SkScalar stopX = stopVector.fX;
832 SkScalar stopY = stopVector.fY;
833
834 if (skPaint.getTextAlign() == SkPaint::kCenter_Align) {
835 stopX = SkScalarHalf(stopX);
836 stopY = SkScalarHalf(stopY);
837 }
838 x -= stopX;
839 y -= stopY;
840 }
841
842 const char* stop = text + byteLength;
843
844 SkAutoKern autokern;
845
846 SkFixed fxMask = ~0;
847 SkFixed fyMask = ~0;
848 SkScalar halfSampleX, halfSampleY;
849 if (cache->isSubpixel()) {
850 halfSampleX = halfSampleY = SkFixedToScalar(SkGlyph::kSubpixelRound);
851 SkAxisAlignment baseline = SkComputeAxisAlignmentForHText(viewMatrix);
852 if (kX_SkAxisAlignment == baseline) {
853 fyMask = 0;
854 halfSampleY = SK_ScalarHalf;
855 } else if (kY_SkAxisAlignment == baseline) {
856 fxMask = 0;
857 halfSampleX = SK_ScalarHalf;
858 }
859 } else {
860 halfSampleX = halfSampleY = SK_ScalarHalf;
861 }
862
863 Sk48Dot16 fx = SkScalarTo48Dot16(x + halfSampleX);
864 Sk48Dot16 fy = SkScalarTo48Dot16(y + halfSampleY);
865
866 while (text < stop) {
867 const SkGlyph& glyph = glyphCacheProc(cache, &text, fx & fxMask, fy & fyMask);
868
869 fx += autokern.adjust(glyph);
870
871 if (glyph.fWidth) {
872 this->bmpAppendGlyph(blob,
873 runIndex,
874 GrGlyph::Pack(glyph.getGlyphID(),
875 glyph.getSubXFixed(),
876 glyph.getSubYFixed(),
877 GrGlyph::kCoverage_MaskStyle),
878 Sk48Dot16FloorToInt(fx),
879 Sk48Dot16FloorToInt(fy),
880 color,
881 fontScaler,
882 clipRect);
883 }
884
885 fx += glyph.fAdvanceX;
886 fy += glyph.fAdvanceY;
887 }
888 }
889
internalDrawBMPPosText(BitmapTextBlob * blob,int runIndex,SkGlyphCache * cache,const SkPaint & skPaint,GrColor color,const SkMatrix & viewMatrix,const char text[],size_t byteLength,const SkScalar pos[],int scalarsPerPosition,const SkPoint & offset,const SkIRect & clipRect)890 void GrAtlasTextContext::internalDrawBMPPosText(BitmapTextBlob* blob, int runIndex,
891 SkGlyphCache* cache, const SkPaint& skPaint,
892 GrColor color,
893 const SkMatrix& viewMatrix,
894 const char text[], size_t byteLength,
895 const SkScalar pos[], int scalarsPerPosition,
896 const SkPoint& offset, const SkIRect& clipRect) {
897 SkASSERT(byteLength == 0 || text != NULL);
898 SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition);
899
900 // nothing to draw
901 if (text == NULL || byteLength == 0) {
902 return;
903 }
904
905 fCurrStrike = NULL;
906 SkDrawCacheProc glyphCacheProc = skPaint.getDrawCacheProc();
907
908 // Get GrFontScaler from cache
909 GrFontScaler* fontScaler = GetGrFontScaler(cache);
910
911 const char* stop = text + byteLength;
912 SkTextAlignProc alignProc(skPaint.getTextAlign());
913 SkTextMapStateProc tmsProc(viewMatrix, offset, scalarsPerPosition);
914
915 if (cache->isSubpixel()) {
916 // maybe we should skip the rounding if linearText is set
917 SkAxisAlignment baseline = SkComputeAxisAlignmentForHText(viewMatrix);
918
919 SkFixed fxMask = ~0;
920 SkFixed fyMask = ~0;
921 SkScalar halfSampleX = SkFixedToScalar(SkGlyph::kSubpixelRound);
922 SkScalar halfSampleY = SkFixedToScalar(SkGlyph::kSubpixelRound);
923 if (kX_SkAxisAlignment == baseline) {
924 fyMask = 0;
925 halfSampleY = SK_ScalarHalf;
926 } else if (kY_SkAxisAlignment == baseline) {
927 fxMask = 0;
928 halfSampleX = SK_ScalarHalf;
929 }
930
931 if (SkPaint::kLeft_Align == skPaint.getTextAlign()) {
932 while (text < stop) {
933 SkPoint tmsLoc;
934 tmsProc(pos, &tmsLoc);
935 Sk48Dot16 fx = SkScalarTo48Dot16(tmsLoc.fX + halfSampleX);
936 Sk48Dot16 fy = SkScalarTo48Dot16(tmsLoc.fY + halfSampleY);
937
938 const SkGlyph& glyph = glyphCacheProc(cache, &text,
939 fx & fxMask, fy & fyMask);
940
941 if (glyph.fWidth) {
942 this->bmpAppendGlyph(blob,
943 runIndex,
944 GrGlyph::Pack(glyph.getGlyphID(),
945 glyph.getSubXFixed(),
946 glyph.getSubYFixed(),
947 GrGlyph::kCoverage_MaskStyle),
948 Sk48Dot16FloorToInt(fx),
949 Sk48Dot16FloorToInt(fy),
950 color,
951 fontScaler,
952 clipRect);
953 }
954 pos += scalarsPerPosition;
955 }
956 } else {
957 while (text < stop) {
958 const char* currentText = text;
959 const SkGlyph& metricGlyph = glyphCacheProc(cache, &text, 0, 0);
960
961 if (metricGlyph.fWidth) {
962 SkDEBUGCODE(SkFixed prevAdvX = metricGlyph.fAdvanceX;)
963 SkDEBUGCODE(SkFixed prevAdvY = metricGlyph.fAdvanceY;)
964 SkPoint tmsLoc;
965 tmsProc(pos, &tmsLoc);
966 SkPoint alignLoc;
967 alignProc(tmsLoc, metricGlyph, &alignLoc);
968
969 Sk48Dot16 fx = SkScalarTo48Dot16(alignLoc.fX + halfSampleX);
970 Sk48Dot16 fy = SkScalarTo48Dot16(alignLoc.fY + halfSampleY);
971
972 // have to call again, now that we've been "aligned"
973 const SkGlyph& glyph = glyphCacheProc(cache, ¤tText,
974 fx & fxMask, fy & fyMask);
975 // the assumption is that the metrics haven't changed
976 SkASSERT(prevAdvX == glyph.fAdvanceX);
977 SkASSERT(prevAdvY == glyph.fAdvanceY);
978 SkASSERT(glyph.fWidth);
979
980 this->bmpAppendGlyph(blob,
981 runIndex,
982 GrGlyph::Pack(glyph.getGlyphID(),
983 glyph.getSubXFixed(),
984 glyph.getSubYFixed(),
985 GrGlyph::kCoverage_MaskStyle),
986 Sk48Dot16FloorToInt(fx),
987 Sk48Dot16FloorToInt(fy),
988 color,
989 fontScaler,
990 clipRect);
991 }
992 pos += scalarsPerPosition;
993 }
994 }
995 } else { // not subpixel
996
997 if (SkPaint::kLeft_Align == skPaint.getTextAlign()) {
998 while (text < stop) {
999 // the last 2 parameters are ignored
1000 const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
1001
1002 if (glyph.fWidth) {
1003 SkPoint tmsLoc;
1004 tmsProc(pos, &tmsLoc);
1005
1006 Sk48Dot16 fx = SkScalarTo48Dot16(tmsLoc.fX + SK_ScalarHalf); //halfSampleX;
1007 Sk48Dot16 fy = SkScalarTo48Dot16(tmsLoc.fY + SK_ScalarHalf); //halfSampleY;
1008 this->bmpAppendGlyph(blob,
1009 runIndex,
1010 GrGlyph::Pack(glyph.getGlyphID(),
1011 glyph.getSubXFixed(),
1012 glyph.getSubYFixed(),
1013 GrGlyph::kCoverage_MaskStyle),
1014 Sk48Dot16FloorToInt(fx),
1015 Sk48Dot16FloorToInt(fy),
1016 color,
1017 fontScaler,
1018 clipRect);
1019 }
1020 pos += scalarsPerPosition;
1021 }
1022 } else {
1023 while (text < stop) {
1024 // the last 2 parameters are ignored
1025 const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
1026
1027 if (glyph.fWidth) {
1028 SkPoint tmsLoc;
1029 tmsProc(pos, &tmsLoc);
1030
1031 SkPoint alignLoc;
1032 alignProc(tmsLoc, glyph, &alignLoc);
1033
1034 Sk48Dot16 fx = SkScalarTo48Dot16(alignLoc.fX + SK_ScalarHalf); //halfSampleX;
1035 Sk48Dot16 fy = SkScalarTo48Dot16(alignLoc.fY + SK_ScalarHalf); //halfSampleY;
1036 this->bmpAppendGlyph(blob,
1037 runIndex,
1038 GrGlyph::Pack(glyph.getGlyphID(),
1039 glyph.getSubXFixed(),
1040 glyph.getSubYFixed(),
1041 GrGlyph::kCoverage_MaskStyle),
1042 Sk48Dot16FloorToInt(fx),
1043 Sk48Dot16FloorToInt(fy),
1044 color,
1045 fontScaler,
1046 clipRect);
1047 }
1048 pos += scalarsPerPosition;
1049 }
1050 }
1051 }
1052 }
1053
1054
internalDrawDFText(BitmapTextBlob * blob,int runIndex,SkGlyphCache * cache,const SkPaint & skPaint,GrColor color,const SkMatrix & viewMatrix,const char text[],size_t byteLength,SkScalar x,SkScalar y,const SkIRect & clipRect,SkScalar textRatio,SkTDArray<char> * fallbackTxt,SkTDArray<SkScalar> * fallbackPos,SkPoint * offset,const SkPaint & origPaint)1055 void GrAtlasTextContext::internalDrawDFText(BitmapTextBlob* blob, int runIndex,
1056 SkGlyphCache* cache, const SkPaint& skPaint,
1057 GrColor color,
1058 const SkMatrix& viewMatrix,
1059 const char text[], size_t byteLength,
1060 SkScalar x, SkScalar y, const SkIRect& clipRect,
1061 SkScalar textRatio,
1062 SkTDArray<char>* fallbackTxt,
1063 SkTDArray<SkScalar>* fallbackPos,
1064 SkPoint* offset,
1065 const SkPaint& origPaint) {
1066 SkASSERT(byteLength == 0 || text != NULL);
1067
1068 // nothing to draw
1069 if (text == NULL || byteLength == 0) {
1070 return;
1071 }
1072
1073 SkDrawCacheProc glyphCacheProc = origPaint.getDrawCacheProc();
1074 SkAutoDescriptor desc;
1075 origPaint.getScalerContextDescriptor(&desc, &fDeviceProperties, NULL, true);
1076 SkGlyphCache* origPaintCache = SkGlyphCache::DetachCache(origPaint.getTypeface(),
1077 desc.getDesc());
1078
1079 SkTArray<SkScalar> positions;
1080
1081 const char* textPtr = text;
1082 SkFixed stopX = 0;
1083 SkFixed stopY = 0;
1084 SkFixed origin = 0;
1085 switch (origPaint.getTextAlign()) {
1086 case SkPaint::kRight_Align: origin = SK_Fixed1; break;
1087 case SkPaint::kCenter_Align: origin = SK_FixedHalf; break;
1088 case SkPaint::kLeft_Align: origin = 0; break;
1089 }
1090
1091 SkAutoKern autokern;
1092 const char* stop = text + byteLength;
1093 while (textPtr < stop) {
1094 // don't need x, y here, since all subpixel variants will have the
1095 // same advance
1096 const SkGlyph& glyph = glyphCacheProc(origPaintCache, &textPtr, 0, 0);
1097
1098 SkFixed width = glyph.fAdvanceX + autokern.adjust(glyph);
1099 positions.push_back(SkFixedToScalar(stopX + SkFixedMul(origin, width)));
1100
1101 SkFixed height = glyph.fAdvanceY;
1102 positions.push_back(SkFixedToScalar(stopY + SkFixedMul(origin, height)));
1103
1104 stopX += width;
1105 stopY += height;
1106 }
1107 SkASSERT(textPtr == stop);
1108
1109 // now adjust starting point depending on alignment
1110 SkScalar alignX = SkFixedToScalar(stopX);
1111 SkScalar alignY = SkFixedToScalar(stopY);
1112 if (origPaint.getTextAlign() == SkPaint::kCenter_Align) {
1113 alignX = SkScalarHalf(alignX);
1114 alignY = SkScalarHalf(alignY);
1115 } else if (origPaint.getTextAlign() == SkPaint::kLeft_Align) {
1116 alignX = 0;
1117 alignY = 0;
1118 }
1119 x -= alignX;
1120 y -= alignY;
1121 *offset = SkPoint::Make(x, y);
1122
1123 this->internalDrawDFPosText(blob, runIndex, cache, skPaint, color, viewMatrix, text, byteLength,
1124 positions.begin(), 2, *offset, clipRect, textRatio, fallbackTxt,
1125 fallbackPos);
1126 SkGlyphCache::AttachCache(origPaintCache);
1127 }
1128
internalDrawDFPosText(BitmapTextBlob * blob,int runIndex,SkGlyphCache * cache,const SkPaint & skPaint,GrColor color,const SkMatrix & viewMatrix,const char text[],size_t byteLength,const SkScalar pos[],int scalarsPerPosition,const SkPoint & offset,const SkIRect & clipRect,SkScalar textRatio,SkTDArray<char> * fallbackTxt,SkTDArray<SkScalar> * fallbackPos)1129 void GrAtlasTextContext::internalDrawDFPosText(BitmapTextBlob* blob, int runIndex,
1130 SkGlyphCache* cache, const SkPaint& skPaint,
1131 GrColor color,
1132 const SkMatrix& viewMatrix,
1133 const char text[], size_t byteLength,
1134 const SkScalar pos[], int scalarsPerPosition,
1135 const SkPoint& offset, const SkIRect& clipRect,
1136 SkScalar textRatio,
1137 SkTDArray<char>* fallbackTxt,
1138 SkTDArray<SkScalar>* fallbackPos) {
1139
1140 SkASSERT(byteLength == 0 || text != NULL);
1141 SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition);
1142
1143 // nothing to draw
1144 if (text == NULL || byteLength == 0) {
1145 return;
1146 }
1147
1148 fCurrStrike = NULL;
1149
1150 SkDrawCacheProc glyphCacheProc = skPaint.getDrawCacheProc();
1151 GrFontScaler* fontScaler = GetGrFontScaler(cache);
1152
1153 const char* stop = text + byteLength;
1154
1155 if (SkPaint::kLeft_Align == skPaint.getTextAlign()) {
1156 while (text < stop) {
1157 const char* lastText = text;
1158 // the last 2 parameters are ignored
1159 const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
1160
1161 if (glyph.fWidth) {
1162 SkScalar x = offset.x() + pos[0];
1163 SkScalar y = offset.y() + (2 == scalarsPerPosition ? pos[1] : 0);
1164
1165 if (!this->dfAppendGlyph(blob,
1166 runIndex,
1167 GrGlyph::Pack(glyph.getGlyphID(),
1168 glyph.getSubXFixed(),
1169 glyph.getSubYFixed(),
1170 GrGlyph::kDistance_MaskStyle),
1171 x, y, color, fontScaler, clipRect,
1172 textRatio, viewMatrix)) {
1173 // couldn't append, send to fallback
1174 fallbackTxt->append(SkToInt(text-lastText), lastText);
1175 *fallbackPos->append() = pos[0];
1176 if (2 == scalarsPerPosition) {
1177 *fallbackPos->append() = pos[1];
1178 }
1179 }
1180 }
1181 pos += scalarsPerPosition;
1182 }
1183 } else {
1184 SkScalar alignMul = SkPaint::kCenter_Align == skPaint.getTextAlign() ? SK_ScalarHalf
1185 : SK_Scalar1;
1186 while (text < stop) {
1187 const char* lastText = text;
1188 // the last 2 parameters are ignored
1189 const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
1190
1191 if (glyph.fWidth) {
1192 SkScalar x = offset.x() + pos[0];
1193 SkScalar y = offset.y() + (2 == scalarsPerPosition ? pos[1] : 0);
1194
1195 SkScalar advanceX = SkFixedToScalar(glyph.fAdvanceX) * alignMul * textRatio;
1196 SkScalar advanceY = SkFixedToScalar(glyph.fAdvanceY) * alignMul * textRatio;
1197
1198 if (!this->dfAppendGlyph(blob,
1199 runIndex,
1200 GrGlyph::Pack(glyph.getGlyphID(),
1201 glyph.getSubXFixed(),
1202 glyph.getSubYFixed(),
1203 GrGlyph::kDistance_MaskStyle),
1204 x - advanceX, y - advanceY, color,
1205 fontScaler,
1206 clipRect,
1207 textRatio,
1208 viewMatrix)) {
1209 // couldn't append, send to fallback
1210 fallbackTxt->append(SkToInt(text-lastText), lastText);
1211 *fallbackPos->append() = pos[0];
1212 if (2 == scalarsPerPosition) {
1213 *fallbackPos->append() = pos[1];
1214 }
1215 }
1216 }
1217 pos += scalarsPerPosition;
1218 }
1219 }
1220 }
1221
bmpAppendGlyph(BitmapTextBlob * blob,int runIndex,GrGlyph::PackedID packed,int vx,int vy,GrColor color,GrFontScaler * scaler,const SkIRect & clipRect)1222 void GrAtlasTextContext::bmpAppendGlyph(BitmapTextBlob* blob, int runIndex,
1223 GrGlyph::PackedID packed,
1224 int vx, int vy, GrColor color, GrFontScaler* scaler,
1225 const SkIRect& clipRect) {
1226 Run& run = blob->fRuns[runIndex];
1227 if (!fCurrStrike) {
1228 fCurrStrike = fContext->getBatchFontCache()->getStrike(scaler);
1229 run.fStrike.reset(SkRef(fCurrStrike));
1230 }
1231
1232 GrGlyph* glyph = fCurrStrike->getGlyph(packed, scaler);
1233 if (!glyph) {
1234 return;
1235 }
1236
1237 int x = vx + glyph->fBounds.fLeft;
1238 int y = vy + glyph->fBounds.fTop;
1239
1240 // keep them as ints until we've done the clip-test
1241 int width = glyph->fBounds.width();
1242 int height = glyph->fBounds.height();
1243
1244 #if 0
1245 // Not checking the clip bounds might introduce a performance regression. However, its not
1246 // clear if this is still true today with the larger tiles we use in Chrome. For repositionable
1247 // blobs, we want to make sure we have all of the glyphs, so clipping them out is not ideal.
1248 // We could store the cliprect in the key, but then we'd lose the ability to do integer scrolls
1249 // TODO verify this
1250 // check if we clipped out
1251 if (clipRect.quickReject(x, y, x + width, y + height)) {
1252 return;
1253 }
1254 #endif
1255
1256 // If the glyph is too large we fall back to paths
1257 if (glyph->fTooLargeForAtlas) {
1258 this->appendGlyphPath(blob, glyph, scaler, SkIntToScalar(vx), SkIntToScalar(vy));
1259 return;
1260 }
1261
1262 GrMaskFormat format = glyph->fMaskFormat;
1263
1264 PerSubRunInfo* subRun = &run.fSubRunInfo.back();
1265 if (run.fInitialized && subRun->fMaskFormat != format) {
1266 subRun = &run.fSubRunInfo.push_back();
1267 }
1268
1269 run.fInitialized = true;
1270
1271 size_t vertexStride = get_vertex_stride(format);
1272
1273 SkRect r;
1274 r.fLeft = SkIntToScalar(x);
1275 r.fTop = SkIntToScalar(y);
1276 r.fRight = r.fLeft + SkIntToScalar(width);
1277 r.fBottom = r.fTop + SkIntToScalar(height);
1278 subRun->fMaskFormat = format;
1279 this->appendGlyphCommon(blob, &run, subRun, r, color, vertexStride, kA8_GrMaskFormat == format,
1280 glyph);
1281 }
1282
dfAppendGlyph(BitmapTextBlob * blob,int runIndex,GrGlyph::PackedID packed,SkScalar sx,SkScalar sy,GrColor color,GrFontScaler * scaler,const SkIRect & clipRect,SkScalar textRatio,const SkMatrix & viewMatrix)1283 bool GrAtlasTextContext::dfAppendGlyph(BitmapTextBlob* blob, int runIndex,
1284 GrGlyph::PackedID packed,
1285 SkScalar sx, SkScalar sy, GrColor color,
1286 GrFontScaler* scaler,
1287 const SkIRect& clipRect,
1288 SkScalar textRatio, const SkMatrix& viewMatrix) {
1289 Run& run = blob->fRuns[runIndex];
1290 if (!fCurrStrike) {
1291 fCurrStrike = fContext->getBatchFontCache()->getStrike(scaler);
1292 run.fStrike.reset(SkRef(fCurrStrike));
1293 }
1294
1295 GrGlyph* glyph = fCurrStrike->getGlyph(packed, scaler);
1296 if (!glyph) {
1297 return true;
1298 }
1299
1300 // fallback to color glyph support
1301 if (kA8_GrMaskFormat != glyph->fMaskFormat) {
1302 return false;
1303 }
1304
1305 SkScalar dx = SkIntToScalar(glyph->fBounds.fLeft + SK_DistanceFieldInset);
1306 SkScalar dy = SkIntToScalar(glyph->fBounds.fTop + SK_DistanceFieldInset);
1307 SkScalar width = SkIntToScalar(glyph->fBounds.width() - 2 * SK_DistanceFieldInset);
1308 SkScalar height = SkIntToScalar(glyph->fBounds.height() - 2 * SK_DistanceFieldInset);
1309
1310 SkScalar scale = textRatio;
1311 dx *= scale;
1312 dy *= scale;
1313 width *= scale;
1314 height *= scale;
1315 sx += dx;
1316 sy += dy;
1317 SkRect glyphRect = SkRect::MakeXYWH(sx, sy, width, height);
1318
1319 #if 0
1320 // check if we clipped out
1321 SkRect dstRect;
1322 viewMatrix.mapRect(&dstRect, glyphRect);
1323 if (clipRect.quickReject(SkScalarTruncToInt(dstRect.left()),
1324 SkScalarTruncToInt(dstRect.top()),
1325 SkScalarTruncToInt(dstRect.right()),
1326 SkScalarTruncToInt(dstRect.bottom()))) {
1327 return true;
1328 }
1329 #endif
1330
1331 // TODO combine with the above
1332 // If the glyph is too large we fall back to paths
1333 if (glyph->fTooLargeForAtlas) {
1334 this->appendGlyphPath(blob, glyph, scaler, sx - dx, sy - dy);
1335 return true;
1336 }
1337
1338 PerSubRunInfo* subRun = &run.fSubRunInfo.back();
1339 SkASSERT(glyph->fMaskFormat == kA8_GrMaskFormat);
1340 subRun->fMaskFormat = kA8_GrMaskFormat;
1341
1342 size_t vertexStride = get_vertex_stride_df(kA8_GrMaskFormat, subRun->fUseLCDText);
1343
1344 bool useColorVerts = !subRun->fUseLCDText;
1345 this->appendGlyphCommon(blob, &run, subRun, glyphRect, color, vertexStride, useColorVerts,
1346 glyph);
1347 return true;
1348 }
1349
appendGlyphPath(BitmapTextBlob * blob,GrGlyph * glyph,GrFontScaler * scaler,SkScalar x,SkScalar y)1350 inline void GrAtlasTextContext::appendGlyphPath(BitmapTextBlob* blob, GrGlyph* glyph,
1351 GrFontScaler* scaler, SkScalar x, SkScalar y) {
1352 if (NULL == glyph->fPath) {
1353 SkPath* path = SkNEW(SkPath);
1354 if (!scaler->getGlyphPath(glyph->glyphID(), path)) {
1355 // flag the glyph as being dead?
1356 SkDELETE(path);
1357 return;
1358 }
1359 glyph->fPath = path;
1360 }
1361 SkASSERT(glyph->fPath);
1362 blob->fBigGlyphs.push_back(BitmapTextBlob::BigGlyph(*glyph->fPath, x, y));
1363 }
1364
appendGlyphCommon(BitmapTextBlob * blob,Run * run,Run::SubRunInfo * subRun,const SkRect & positions,GrColor color,size_t vertexStride,bool useVertexColor,GrGlyph * glyph)1365 inline void GrAtlasTextContext::appendGlyphCommon(BitmapTextBlob* blob, Run* run,
1366 Run::SubRunInfo* subRun,
1367 const SkRect& positions, GrColor color,
1368 size_t vertexStride, bool useVertexColor,
1369 GrGlyph* glyph) {
1370 blob->fGlyphs[subRun->fGlyphEndIndex] = glyph;
1371 run->fVertexBounds.joinNonEmptyArg(positions);
1372 run->fColor = color;
1373
1374 intptr_t vertex = reinterpret_cast<intptr_t>(blob->fVertices + subRun->fVertexEndIndex);
1375
1376 if (useVertexColor) {
1377 // V0
1378 SkPoint* position = reinterpret_cast<SkPoint*>(vertex);
1379 position->set(positions.fLeft, positions.fTop);
1380 SkColor* colorPtr = reinterpret_cast<SkColor*>(vertex + sizeof(SkPoint));
1381 *colorPtr = color;
1382 vertex += vertexStride;
1383
1384 // V1
1385 position = reinterpret_cast<SkPoint*>(vertex);
1386 position->set(positions.fLeft, positions.fBottom);
1387 colorPtr = reinterpret_cast<SkColor*>(vertex + sizeof(SkPoint));
1388 *colorPtr = color;
1389 vertex += vertexStride;
1390
1391 // V2
1392 position = reinterpret_cast<SkPoint*>(vertex);
1393 position->set(positions.fRight, positions.fBottom);
1394 colorPtr = reinterpret_cast<SkColor*>(vertex + sizeof(SkPoint));
1395 *colorPtr = color;
1396 vertex += vertexStride;
1397
1398 // V3
1399 position = reinterpret_cast<SkPoint*>(vertex);
1400 position->set(positions.fRight, positions.fTop);
1401 colorPtr = reinterpret_cast<SkColor*>(vertex + sizeof(SkPoint));
1402 *colorPtr = color;
1403 } else {
1404 // V0
1405 SkPoint* position = reinterpret_cast<SkPoint*>(vertex);
1406 position->set(positions.fLeft, positions.fTop);
1407 vertex += vertexStride;
1408
1409 // V1
1410 position = reinterpret_cast<SkPoint*>(vertex);
1411 position->set(positions.fLeft, positions.fBottom);
1412 vertex += vertexStride;
1413
1414 // V2
1415 position = reinterpret_cast<SkPoint*>(vertex);
1416 position->set(positions.fRight, positions.fBottom);
1417 vertex += vertexStride;
1418
1419 // V3
1420 position = reinterpret_cast<SkPoint*>(vertex);
1421 position->set(positions.fRight, positions.fTop);
1422 }
1423
1424 subRun->fGlyphEndIndex++;
1425 subRun->fVertexEndIndex += vertexStride * kVerticesPerGlyph;
1426 }
1427
1428 class BitmapTextBatch : public GrBatch {
1429 public:
1430 typedef GrAtlasTextContext::DistanceAdjustTable DistanceAdjustTable;
1431 typedef GrAtlasTextContext::BitmapTextBlob Blob;
1432 typedef Blob::Run Run;
1433 typedef Run::SubRunInfo TextInfo;
1434 struct Geometry {
1435 Blob* fBlob;
1436 int fRun;
1437 int fSubRun;
1438 GrColor fColor;
1439 SkScalar fTransX;
1440 SkScalar fTransY;
1441 };
1442
Create(GrMaskFormat maskFormat,int glyphCount,GrBatchFontCache * fontCache)1443 static BitmapTextBatch* Create(GrMaskFormat maskFormat, int glyphCount,
1444 GrBatchFontCache* fontCache) {
1445 return SkNEW_ARGS(BitmapTextBatch, (maskFormat, glyphCount, fontCache));
1446 }
1447
Create(GrMaskFormat maskFormat,int glyphCount,GrBatchFontCache * fontCache,DistanceAdjustTable * distanceAdjustTable,SkColor filteredColor,bool useLCDText,bool useBGR,float gamma)1448 static BitmapTextBatch* Create(GrMaskFormat maskFormat, int glyphCount,
1449 GrBatchFontCache* fontCache,
1450 DistanceAdjustTable* distanceAdjustTable,
1451 SkColor filteredColor, bool useLCDText,
1452 bool useBGR, float gamma) {
1453 return SkNEW_ARGS(BitmapTextBatch, (maskFormat, glyphCount, fontCache, distanceAdjustTable,
1454 filteredColor, useLCDText, useBGR, gamma));
1455 }
1456
name() const1457 const char* name() const override { return "BitmapTextBatch"; }
1458
getInvariantOutputColor(GrInitInvariantOutput * out) const1459 void getInvariantOutputColor(GrInitInvariantOutput* out) const override {
1460 if (kARGB_GrMaskFormat == fMaskFormat) {
1461 out->setUnknownFourComponents();
1462 } else {
1463 out->setKnownFourComponents(fBatch.fColor);
1464 }
1465 }
1466
getInvariantOutputCoverage(GrInitInvariantOutput * out) const1467 void getInvariantOutputCoverage(GrInitInvariantOutput* out) const override {
1468 if (!fUseDistanceFields) {
1469 // Bitmap Text
1470 if (kARGB_GrMaskFormat != fMaskFormat) {
1471 if (GrPixelConfigIsAlphaOnly(fPixelConfig)) {
1472 out->setUnknownSingleComponent();
1473 } else if (GrPixelConfigIsOpaque(fPixelConfig)) {
1474 out->setUnknownOpaqueFourComponents();
1475 out->setUsingLCDCoverage();
1476 } else {
1477 out->setUnknownFourComponents();
1478 out->setUsingLCDCoverage();
1479 }
1480 } else {
1481 out->setKnownSingleComponent(0xff);
1482 }
1483 } else {
1484 // Distance fields
1485 if (!fUseLCDText) {
1486 out->setUnknownSingleComponent();
1487 } else {
1488 out->setUnknownFourComponents();
1489 out->setUsingLCDCoverage();
1490 }
1491 }
1492 }
1493
initBatchTracker(const GrPipelineInfo & init)1494 void initBatchTracker(const GrPipelineInfo& init) override {
1495 // Handle any color overrides
1496 if (init.fColorIgnored) {
1497 fBatch.fColor = GrColor_ILLEGAL;
1498 } else if (GrColor_ILLEGAL != init.fOverrideColor) {
1499 fBatch.fColor = init.fOverrideColor;
1500 }
1501
1502 // setup batch properties
1503 fBatch.fColorIgnored = init.fColorIgnored;
1504 fBatch.fUsesLocalCoords = init.fUsesLocalCoords;
1505 fBatch.fCoverageIgnored = init.fCoverageIgnored;
1506 }
1507
1508 struct FlushInfo {
1509 SkAutoTUnref<const GrVertexBuffer> fVertexBuffer;
1510 SkAutoTUnref<const GrIndexBuffer> fIndexBuffer;
1511 int fGlyphsToFlush;
1512 int fVertexOffset;
1513 };
1514
generateGeometry(GrBatchTarget * batchTarget,const GrPipeline * pipeline)1515 void generateGeometry(GrBatchTarget* batchTarget, const GrPipeline* pipeline) override {
1516 // if we have RGB, then we won't have any SkShaders so no need to use a localmatrix.
1517 // TODO actually only invert if we don't have RGBA
1518 SkMatrix localMatrix;
1519 if (this->usesLocalCoords() && !this->viewMatrix().invert(&localMatrix)) {
1520 SkDebugf("Cannot invert viewmatrix\n");
1521 return;
1522 }
1523
1524 GrTexture* texture = fFontCache->getTexture(fMaskFormat);
1525 if (!texture) {
1526 SkDebugf("Could not allocate backing texture for atlas\n");
1527 return;
1528 }
1529
1530 SkAutoTUnref<const GrGeometryProcessor> gp;
1531 if (fUseDistanceFields) {
1532 gp.reset(this->setupDfProcessor(this->viewMatrix(), fFilteredColor, this->color(),
1533 texture));
1534 } else {
1535 GrTextureParams params(SkShader::kClamp_TileMode, GrTextureParams::kNone_FilterMode);
1536 gp.reset(GrBitmapTextGeoProc::Create(this->color(),
1537 texture,
1538 params,
1539 fMaskFormat,
1540 localMatrix));
1541 }
1542
1543 FlushInfo flushInfo;
1544 flushInfo.fGlyphsToFlush = 0;
1545 size_t vertexStride = gp->getVertexStride();
1546 SkASSERT(vertexStride == (fUseDistanceFields ?
1547 get_vertex_stride_df(fMaskFormat, fUseLCDText) :
1548 get_vertex_stride(fMaskFormat)));
1549
1550 this->initDraw(batchTarget, gp, pipeline);
1551
1552 int glyphCount = this->numGlyphs();
1553 int instanceCount = fInstanceCount;
1554 const GrVertexBuffer* vertexBuffer;
1555
1556 void* vertices = batchTarget->makeVertSpace(vertexStride,
1557 glyphCount * kVerticesPerGlyph,
1558 &vertexBuffer,
1559 &flushInfo.fVertexOffset);
1560 flushInfo.fVertexBuffer.reset(SkRef(vertexBuffer));
1561 flushInfo.fIndexBuffer.reset(batchTarget->resourceProvider()->refQuadIndexBuffer());
1562 if (!vertices || !flushInfo.fVertexBuffer) {
1563 SkDebugf("Could not allocate vertices\n");
1564 return;
1565 }
1566
1567 unsigned char* currVertex = reinterpret_cast<unsigned char*>(vertices);
1568
1569 // We cache some values to avoid going to the glyphcache for the same fontScaler twice
1570 // in a row
1571 const SkDescriptor* desc = NULL;
1572 SkGlyphCache* cache = NULL;
1573 GrFontScaler* scaler = NULL;
1574 SkTypeface* typeface = NULL;
1575
1576 for (int i = 0; i < instanceCount; i++) {
1577 Geometry& args = fGeoData[i];
1578 Blob* blob = args.fBlob;
1579 Run& run = blob->fRuns[args.fRun];
1580 TextInfo& info = run.fSubRunInfo[args.fSubRun];
1581
1582 uint64_t currentAtlasGen = fFontCache->atlasGeneration(fMaskFormat);
1583 bool regenerateTextureCoords = info.fAtlasGeneration != currentAtlasGen;
1584 bool regenerateColors;
1585 if (fUseDistanceFields) {
1586 regenerateColors = !fUseLCDText && run.fColor != args.fColor;
1587 } else {
1588 regenerateColors = kA8_GrMaskFormat == fMaskFormat && run.fColor != args.fColor;
1589 }
1590 bool regeneratePositions = args.fTransX != 0.f || args.fTransY != 0.f;
1591 int glyphCount = info.fGlyphEndIndex - info.fGlyphStartIndex;
1592
1593 // We regenerate both texture coords and colors in the blob itself, and update the
1594 // atlas generation. If we don't end up purging any unused plots, we can avoid
1595 // regenerating the coords. We could take a finer grained approach to updating texture
1596 // coords but its not clear if the extra bookkeeping would offset any gains.
1597 // To avoid looping over the glyphs twice, we do one loop and conditionally update color
1598 // or coords as needed. One final note, if we have to break a run for an atlas eviction
1599 // then we can't really trust the atlas has all of the correct data. Atlas evictions
1600 // should be pretty rare, so we just always regenerate in those cases
1601 if (regenerateTextureCoords || regenerateColors || regeneratePositions) {
1602 // first regenerate texture coordinates / colors if need be
1603 bool brokenRun = false;
1604
1605 // Because the GrBatchFontCache may evict the strike a blob depends on using for
1606 // generating its texture coords, we have to track whether or not the strike has
1607 // been abandoned. If it hasn't been abandoned, then we can use the GrGlyph*s as is
1608 // otherwise we have to get the new strike, and use that to get the correct glyphs.
1609 // Because we do not have the packed ids, and thus can't look up our glyphs in the
1610 // new strike, we instead keep our ref to the old strike and use the packed ids from
1611 // it. These ids will still be valid as long as we hold the ref. When we are done
1612 // updating our cache of the GrGlyph*s, we drop our ref on the old strike
1613 bool regenerateGlyphs = false;
1614 GrBatchTextStrike* strike = NULL;
1615 if (regenerateTextureCoords) {
1616 info.fBulkUseToken.reset();
1617
1618 // We can reuse if we have a valid strike and our descriptors / typeface are the
1619 // same
1620 const SkDescriptor* newDesc = run.fOverrideDescriptor ?
1621 run.fOverrideDescriptor->getDesc() :
1622 run.fDescriptor.getDesc();
1623 if (!cache || !SkTypeface::Equal(typeface, run.fTypeface) ||
1624 !(desc->equals(*newDesc))) {
1625 if (cache) {
1626 SkGlyphCache::AttachCache(cache);
1627 }
1628 desc = newDesc;
1629 cache = SkGlyphCache::DetachCache(run.fTypeface, desc);
1630 scaler = GrTextContext::GetGrFontScaler(cache);
1631 strike = run.fStrike;
1632 typeface = run.fTypeface;
1633 }
1634
1635 if (run.fStrike->isAbandoned()) {
1636 regenerateGlyphs = true;
1637 strike = fFontCache->getStrike(scaler);
1638 } else {
1639 strike = run.fStrike;
1640 }
1641 }
1642
1643 for (int glyphIdx = 0; glyphIdx < glyphCount; glyphIdx++) {
1644 if (regenerateTextureCoords) {
1645 size_t glyphOffset = glyphIdx + info.fGlyphStartIndex;
1646 GrGlyph* glyph;
1647 if (regenerateGlyphs) {
1648 // Get the id from the old glyph, and use the new strike to lookup
1649 // the glyph.
1650 glyph = blob->fGlyphs[glyphOffset];
1651 blob->fGlyphs[glyphOffset] = strike->getGlyph(glyph->fPackedID,
1652 scaler);
1653 }
1654 glyph = blob->fGlyphs[glyphOffset];
1655 SkASSERT(glyph);
1656
1657 if (!fFontCache->hasGlyph(glyph) &&
1658 !strike->addGlyphToAtlas(batchTarget, glyph, scaler)) {
1659 this->flush(batchTarget, &flushInfo);
1660 this->initDraw(batchTarget, gp, pipeline);
1661 brokenRun = glyphIdx > 0;
1662
1663 SkDEBUGCODE(bool success =) strike->addGlyphToAtlas(batchTarget,
1664 glyph,
1665 scaler);
1666 SkASSERT(success);
1667 }
1668 fFontCache->addGlyphToBulkAndSetUseToken(&info.fBulkUseToken, glyph,
1669 batchTarget->currentToken());
1670
1671 // Texture coords are the last vertex attribute so we get a pointer to the
1672 // first one and then map with stride in regenerateTextureCoords
1673 intptr_t vertex = reinterpret_cast<intptr_t>(blob->fVertices);
1674 vertex += info.fVertexStartIndex;
1675 vertex += vertexStride * glyphIdx * kVerticesPerGlyph;
1676 vertex += vertexStride - sizeof(SkIPoint16);
1677
1678 this->regenerateTextureCoords(glyph, vertex, vertexStride);
1679 }
1680
1681 if (regenerateColors) {
1682 intptr_t vertex = reinterpret_cast<intptr_t>(blob->fVertices);
1683 vertex += info.fVertexStartIndex;
1684 vertex += vertexStride * glyphIdx * kVerticesPerGlyph + sizeof(SkPoint);
1685 this->regenerateColors(vertex, vertexStride, args.fColor);
1686 }
1687
1688 if (regeneratePositions) {
1689 intptr_t vertex = reinterpret_cast<intptr_t>(blob->fVertices);
1690 vertex += info.fVertexStartIndex;
1691 vertex += vertexStride * glyphIdx * kVerticesPerGlyph;
1692 SkScalar transX = args.fTransX;
1693 SkScalar transY = args.fTransY;
1694 this->regeneratePositions(vertex, vertexStride, transX, transY);
1695 }
1696 flushInfo.fGlyphsToFlush++;
1697 }
1698
1699 // We my have changed the color so update it here
1700 run.fColor = args.fColor;
1701 if (regenerateTextureCoords) {
1702 if (regenerateGlyphs) {
1703 run.fStrike.reset(SkRef(strike));
1704 }
1705 info.fAtlasGeneration = brokenRun ? GrBatchAtlas::kInvalidAtlasGeneration :
1706 fFontCache->atlasGeneration(fMaskFormat);
1707 }
1708 } else {
1709 flushInfo.fGlyphsToFlush += glyphCount;
1710
1711 // set use tokens for all of the glyphs in our subrun. This is only valid if we
1712 // have a valid atlas generation
1713 fFontCache->setUseTokenBulk(info.fBulkUseToken,
1714 batchTarget->currentToken(),
1715 fMaskFormat);
1716 }
1717
1718 // now copy all vertices
1719 size_t byteCount = info.fVertexEndIndex - info.fVertexStartIndex;
1720 memcpy(currVertex, blob->fVertices + info.fVertexStartIndex, byteCount);
1721
1722 currVertex += byteCount;
1723 }
1724 // Make sure to attach the last cache if applicable
1725 if (cache) {
1726 SkGlyphCache::AttachCache(cache);
1727 }
1728 this->flush(batchTarget, &flushInfo);
1729 }
1730
1731 // The minimum number of Geometry we will try to allocate.
1732 static const int kMinAllocated = 32;
1733
1734 // Total number of Geometry this Batch owns
instanceCount() const1735 int instanceCount() const { return fInstanceCount; }
geoData()1736 SkAutoSTMalloc<kMinAllocated, Geometry>* geoData() { return &fGeoData; }
1737
1738 // to avoid even the initial copy of the struct, we have a getter for the first item which
1739 // is used to seed the batch with its initial geometry. After seeding, the client should call
1740 // init() so the Batch can initialize itself
geometry()1741 Geometry& geometry() { return fGeoData[0]; }
init()1742 void init() {
1743 const Geometry& geo = fGeoData[0];
1744 fBatch.fColor = geo.fColor;
1745 fBatch.fViewMatrix = geo.fBlob->fViewMatrix;
1746
1747 // We don't yet position distance field text on the cpu, so we have to map the vertex bounds
1748 // into device space
1749 const Run& run = geo.fBlob->fRuns[geo.fRun];
1750 if (run.fSubRunInfo[geo.fSubRun].fDrawAsDistanceFields) {
1751 SkRect bounds = run.fVertexBounds;
1752 fBatch.fViewMatrix.mapRect(&bounds);
1753 this->setBounds(bounds);
1754 } else {
1755 this->setBounds(run.fVertexBounds);
1756 }
1757 }
1758
1759 private:
BitmapTextBatch(GrMaskFormat maskFormat,int glyphCount,GrBatchFontCache * fontCache)1760 BitmapTextBatch(GrMaskFormat maskFormat, int glyphCount, GrBatchFontCache* fontCache)
1761 : fMaskFormat(maskFormat)
1762 , fPixelConfig(fontCache->getPixelConfig(maskFormat))
1763 , fFontCache(fontCache)
1764 , fUseDistanceFields(false) {
1765 this->initClassID<BitmapTextBatch>();
1766 fBatch.fNumGlyphs = glyphCount;
1767 fInstanceCount = 1;
1768 fAllocatedCount = kMinAllocated;
1769 }
1770
BitmapTextBatch(GrMaskFormat maskFormat,int glyphCount,GrBatchFontCache * fontCache,DistanceAdjustTable * distanceAdjustTable,SkColor filteredColor,bool useLCDText,bool useBGR,float gamma)1771 BitmapTextBatch(GrMaskFormat maskFormat, int glyphCount, GrBatchFontCache* fontCache,
1772 DistanceAdjustTable* distanceAdjustTable, SkColor filteredColor,
1773 bool useLCDText, bool useBGR, float gamma)
1774 : fMaskFormat(maskFormat)
1775 , fPixelConfig(fontCache->getPixelConfig(maskFormat))
1776 , fFontCache(fontCache)
1777 , fDistanceAdjustTable(SkRef(distanceAdjustTable))
1778 , fFilteredColor(filteredColor)
1779 , fUseDistanceFields(true)
1780 , fUseLCDText(useLCDText)
1781 , fUseBGR(useBGR)
1782 , fGamma(gamma) {
1783 this->initClassID<BitmapTextBatch>();
1784 fBatch.fNumGlyphs = glyphCount;
1785 fInstanceCount = 1;
1786 fAllocatedCount = kMinAllocated;
1787 SkASSERT(fMaskFormat == kA8_GrMaskFormat);
1788 }
1789
~BitmapTextBatch()1790 ~BitmapTextBatch() {
1791 for (int i = 0; i < fInstanceCount; i++) {
1792 fGeoData[i].fBlob->unref();
1793 }
1794 }
1795
regenerateTextureCoords(GrGlyph * glyph,intptr_t vertex,size_t vertexStride)1796 void regenerateTextureCoords(GrGlyph* glyph, intptr_t vertex, size_t vertexStride) {
1797 int width = glyph->fBounds.width();
1798 int height = glyph->fBounds.height();
1799
1800 int u0, v0, u1, v1;
1801 if (fUseDistanceFields) {
1802 u0 = glyph->fAtlasLocation.fX + SK_DistanceFieldInset;
1803 v0 = glyph->fAtlasLocation.fY + SK_DistanceFieldInset;
1804 u1 = u0 + width - 2 * SK_DistanceFieldInset;
1805 v1 = v0 + height - 2 * SK_DistanceFieldInset;
1806 } else {
1807 u0 = glyph->fAtlasLocation.fX;
1808 v0 = glyph->fAtlasLocation.fY;
1809 u1 = u0 + width;
1810 v1 = v0 + height;
1811 }
1812
1813 SkIPoint16* textureCoords;
1814 // V0
1815 textureCoords = reinterpret_cast<SkIPoint16*>(vertex);
1816 textureCoords->set(u0, v0);
1817 vertex += vertexStride;
1818
1819 // V1
1820 textureCoords = reinterpret_cast<SkIPoint16*>(vertex);
1821 textureCoords->set(u0, v1);
1822 vertex += vertexStride;
1823
1824 // V2
1825 textureCoords = reinterpret_cast<SkIPoint16*>(vertex);
1826 textureCoords->set(u1, v1);
1827 vertex += vertexStride;
1828
1829 // V3
1830 textureCoords = reinterpret_cast<SkIPoint16*>(vertex);
1831 textureCoords->set(u1, v0);
1832 }
1833
regenerateColors(intptr_t vertex,size_t vertexStride,GrColor color)1834 void regenerateColors(intptr_t vertex, size_t vertexStride, GrColor color) {
1835 for (int i = 0; i < kVerticesPerGlyph; i++) {
1836 SkColor* vcolor = reinterpret_cast<SkColor*>(vertex);
1837 *vcolor = color;
1838 vertex += vertexStride;
1839 }
1840 }
1841
regeneratePositions(intptr_t vertex,size_t vertexStride,SkScalar transX,SkScalar transY)1842 void regeneratePositions(intptr_t vertex, size_t vertexStride, SkScalar transX,
1843 SkScalar transY) {
1844 for (int i = 0; i < kVerticesPerGlyph; i++) {
1845 SkPoint* point = reinterpret_cast<SkPoint*>(vertex);
1846 point->fX += transX;
1847 point->fY += transY;
1848 vertex += vertexStride;
1849 }
1850 }
1851
initDraw(GrBatchTarget * batchTarget,const GrGeometryProcessor * gp,const GrPipeline * pipeline)1852 void initDraw(GrBatchTarget* batchTarget,
1853 const GrGeometryProcessor* gp,
1854 const GrPipeline* pipeline) {
1855 batchTarget->initDraw(gp, pipeline);
1856
1857 // TODO remove this when batch is everywhere
1858 GrPipelineInfo init;
1859 init.fColorIgnored = fBatch.fColorIgnored;
1860 init.fOverrideColor = GrColor_ILLEGAL;
1861 init.fCoverageIgnored = fBatch.fCoverageIgnored;
1862 init.fUsesLocalCoords = this->usesLocalCoords();
1863 gp->initBatchTracker(batchTarget->currentBatchTracker(), init);
1864 }
1865
flush(GrBatchTarget * batchTarget,FlushInfo * flushInfo)1866 void flush(GrBatchTarget* batchTarget, FlushInfo* flushInfo) {
1867 GrVertices vertices;
1868 int maxGlyphsPerDraw = flushInfo->fIndexBuffer->maxQuads();
1869 vertices.initInstanced(kTriangles_GrPrimitiveType, flushInfo->fVertexBuffer,
1870 flushInfo->fIndexBuffer, flushInfo->fVertexOffset,
1871 kVerticesPerGlyph, kIndicesPerGlyph, flushInfo->fGlyphsToFlush,
1872 maxGlyphsPerDraw);
1873 batchTarget->draw(vertices);
1874 flushInfo->fVertexOffset += kVerticesPerGlyph * flushInfo->fGlyphsToFlush;
1875 flushInfo->fGlyphsToFlush = 0;
1876 }
1877
color() const1878 GrColor color() const { return fBatch.fColor; }
viewMatrix() const1879 const SkMatrix& viewMatrix() const { return fBatch.fViewMatrix; }
usesLocalCoords() const1880 bool usesLocalCoords() const { return fBatch.fUsesLocalCoords; }
numGlyphs() const1881 int numGlyphs() const { return fBatch.fNumGlyphs; }
1882
onCombineIfPossible(GrBatch * t)1883 bool onCombineIfPossible(GrBatch* t) override {
1884 BitmapTextBatch* that = t->cast<BitmapTextBatch>();
1885
1886 if (fUseDistanceFields != that->fUseDistanceFields) {
1887 return false;
1888 }
1889
1890 if (!fUseDistanceFields) {
1891 // Bitmap Text
1892 if (fMaskFormat != that->fMaskFormat) {
1893 return false;
1894 }
1895
1896 // TODO we can often batch across LCD text if we have dual source blending and don't
1897 // have to use the blend constant
1898 if (fMaskFormat != kA8_GrMaskFormat && this->color() != that->color()) {
1899 return false;
1900 }
1901
1902 if (this->usesLocalCoords() && !this->viewMatrix().cheapEqualTo(that->viewMatrix())) {
1903 return false;
1904 }
1905 } else {
1906 // Distance Fields
1907 SkASSERT(this->fMaskFormat == that->fMaskFormat &&
1908 this->fMaskFormat == kA8_GrMaskFormat);
1909
1910 if (!this->viewMatrix().cheapEqualTo(that->viewMatrix())) {
1911 return false;
1912 }
1913
1914 if (fFilteredColor != that->fFilteredColor) {
1915 return false;
1916 }
1917
1918 if (fUseLCDText != that->fUseLCDText) {
1919 return false;
1920 }
1921
1922 if (fUseBGR != that->fUseBGR) {
1923 return false;
1924 }
1925
1926 if (fGamma != that->fGamma) {
1927 return false;
1928 }
1929
1930 // TODO see note above
1931 if (fUseLCDText && this->color() != that->color()) {
1932 return false;
1933 }
1934 }
1935
1936 fBatch.fNumGlyphs += that->numGlyphs();
1937
1938 // copy that->geoData(). We do this manually for performance reasons
1939 SkAutoSTMalloc<kMinAllocated, Geometry>* otherGeoData = that->geoData();
1940 int otherInstanceCount = that->instanceCount();
1941 int allocSize = otherInstanceCount + fInstanceCount;
1942 if (allocSize > fAllocatedCount) {
1943 while (allocSize > fAllocatedCount) {
1944 fAllocatedCount = fAllocatedCount << 1;
1945 }
1946 fGeoData.realloc(fAllocatedCount);
1947 }
1948
1949 memcpy(&fGeoData[fInstanceCount], otherGeoData->get(),
1950 otherInstanceCount * sizeof(Geometry));
1951 int total = fInstanceCount + otherInstanceCount;
1952 for (int i = fInstanceCount; i < total; i++) {
1953 fGeoData[i].fBlob->ref();
1954 }
1955 fInstanceCount = total;
1956
1957 this->joinBounds(that->bounds());
1958 return true;
1959 }
1960
1961 // TODO just use class params
1962 // TODO trying to figure out why lcd is so whack
setupDfProcessor(const SkMatrix & viewMatrix,SkColor filteredColor,GrColor color,GrTexture * texture)1963 GrGeometryProcessor* setupDfProcessor(const SkMatrix& viewMatrix, SkColor filteredColor,
1964 GrColor color, GrTexture* texture) {
1965 GrTextureParams params(SkShader::kClamp_TileMode, GrTextureParams::kBilerp_FilterMode);
1966
1967 // set up any flags
1968 uint32_t flags = 0;
1969 flags |= viewMatrix.isSimilarity() ? kSimilarity_DistanceFieldEffectFlag : 0;
1970 flags |= fUseLCDText ? kUseLCD_DistanceFieldEffectFlag : 0;
1971 flags |= fUseLCDText && viewMatrix.rectStaysRect() ?
1972 kRectToRect_DistanceFieldEffectFlag : 0;
1973 flags |= fUseLCDText && fUseBGR ? kBGR_DistanceFieldEffectFlag : 0;
1974
1975 // see if we need to create a new effect
1976 if (fUseLCDText) {
1977 GrColor colorNoPreMul = skcolor_to_grcolor_nopremultiply(filteredColor);
1978
1979 float redCorrection =
1980 (*fDistanceAdjustTable)[GrColorUnpackR(colorNoPreMul) >> kDistanceAdjustLumShift];
1981 float greenCorrection =
1982 (*fDistanceAdjustTable)[GrColorUnpackG(colorNoPreMul) >> kDistanceAdjustLumShift];
1983 float blueCorrection =
1984 (*fDistanceAdjustTable)[GrColorUnpackB(colorNoPreMul) >> kDistanceAdjustLumShift];
1985 GrDistanceFieldLCDTextGeoProc::DistanceAdjust widthAdjust =
1986 GrDistanceFieldLCDTextGeoProc::DistanceAdjust::Make(redCorrection,
1987 greenCorrection,
1988 blueCorrection);
1989
1990 return GrDistanceFieldLCDTextGeoProc::Create(color,
1991 viewMatrix,
1992 texture,
1993 params,
1994 widthAdjust,
1995 flags);
1996 } else {
1997 flags |= kColorAttr_DistanceFieldEffectFlag;
1998 #ifdef SK_GAMMA_APPLY_TO_A8
1999 U8CPU lum = SkColorSpaceLuminance::computeLuminance(fGamma, filteredColor);
2000 float correction = (*fDistanceAdjustTable)[lum >> kDistanceAdjustLumShift];
2001 return GrDistanceFieldA8TextGeoProc::Create(color,
2002 viewMatrix,
2003 texture,
2004 params,
2005 correction,
2006 flags);
2007 #else
2008 return GrDistanceFieldA8TextGeoProc::Create(color,
2009 viewMatrix,
2010 texture,
2011 params,
2012 flags);
2013 #endif
2014 }
2015
2016 }
2017
2018 struct BatchTracker {
2019 GrColor fColor;
2020 SkMatrix fViewMatrix;
2021 bool fUsesLocalCoords;
2022 bool fColorIgnored;
2023 bool fCoverageIgnored;
2024 int fNumGlyphs;
2025 };
2026
2027 BatchTracker fBatch;
2028 SkAutoSTMalloc<kMinAllocated, Geometry> fGeoData;
2029 int fInstanceCount;
2030 int fAllocatedCount;
2031 GrMaskFormat fMaskFormat;
2032 GrPixelConfig fPixelConfig;
2033 GrBatchFontCache* fFontCache;
2034
2035 // Distance field properties
2036 SkAutoTUnref<DistanceAdjustTable> fDistanceAdjustTable;
2037 SkColor fFilteredColor;
2038 bool fUseDistanceFields;
2039 bool fUseLCDText;
2040 bool fUseBGR;
2041 float fGamma;
2042 };
2043
flushRunAsPaths(const SkTextBlob::RunIterator & it,const SkPaint & skPaint,SkDrawFilter * drawFilter,const SkMatrix & viewMatrix,const SkIRect & clipBounds,SkScalar x,SkScalar y)2044 void GrAtlasTextContext::flushRunAsPaths(const SkTextBlob::RunIterator& it, const SkPaint& skPaint,
2045 SkDrawFilter* drawFilter, const SkMatrix& viewMatrix,
2046 const SkIRect& clipBounds, SkScalar x, SkScalar y) {
2047 SkPaint runPaint = skPaint;
2048
2049 size_t textLen = it.glyphCount() * sizeof(uint16_t);
2050 const SkPoint& offset = it.offset();
2051
2052 it.applyFontToPaint(&runPaint);
2053
2054 if (drawFilter && !drawFilter->filter(&runPaint, SkDrawFilter::kText_Type)) {
2055 return;
2056 }
2057
2058 runPaint.setFlags(fGpuDevice->filterTextFlags(runPaint));
2059
2060 switch (it.positioning()) {
2061 case SkTextBlob::kDefault_Positioning:
2062 this->drawTextAsPath(runPaint, viewMatrix, (const char *)it.glyphs(),
2063 textLen, x + offset.x(), y + offset.y(), clipBounds);
2064 break;
2065 case SkTextBlob::kHorizontal_Positioning:
2066 this->drawPosTextAsPath(runPaint, viewMatrix, (const char*)it.glyphs(),
2067 textLen, it.pos(), 1, SkPoint::Make(x, y + offset.y()),
2068 clipBounds);
2069 break;
2070 case SkTextBlob::kFull_Positioning:
2071 this->drawPosTextAsPath(runPaint, viewMatrix, (const char*)it.glyphs(),
2072 textLen, it.pos(), 2, SkPoint::Make(x, y), clipBounds);
2073 break;
2074 }
2075 }
2076
2077
2078 inline BitmapTextBatch*
createBatch(BitmapTextBlob * cacheBlob,const PerSubRunInfo & info,int glyphCount,int run,int subRun,GrColor color,SkScalar transX,SkScalar transY,const SkPaint & skPaint)2079 GrAtlasTextContext::createBatch(BitmapTextBlob* cacheBlob, const PerSubRunInfo& info,
2080 int glyphCount, int run, int subRun,
2081 GrColor color, SkScalar transX, SkScalar transY,
2082 const SkPaint& skPaint) {
2083 GrMaskFormat format = info.fMaskFormat;
2084 GrColor subRunColor;
2085 if (kARGB_GrMaskFormat == format) {
2086 uint8_t paintAlpha = skPaint.getAlpha();
2087 subRunColor = SkColorSetARGB(paintAlpha, paintAlpha, paintAlpha, paintAlpha);
2088 } else {
2089 subRunColor = color;
2090 }
2091
2092 BitmapTextBatch* batch;
2093 if (info.fDrawAsDistanceFields) {
2094 SkColor filteredColor;
2095 SkColorFilter* colorFilter = skPaint.getColorFilter();
2096 if (colorFilter) {
2097 filteredColor = colorFilter->filterColor(skPaint.getColor());
2098 } else {
2099 filteredColor = skPaint.getColor();
2100 }
2101 bool useBGR = SkPixelGeometryIsBGR(fDeviceProperties.pixelGeometry());
2102 float gamma = fDeviceProperties.gamma();
2103 batch = BitmapTextBatch::Create(format, glyphCount, fContext->getBatchFontCache(),
2104 fDistanceAdjustTable, filteredColor,
2105 info.fUseLCDText, useBGR,
2106 gamma);
2107 } else {
2108 batch = BitmapTextBatch::Create(format, glyphCount, fContext->getBatchFontCache());
2109 }
2110 BitmapTextBatch::Geometry& geometry = batch->geometry();
2111 geometry.fBlob = SkRef(cacheBlob);
2112 geometry.fRun = run;
2113 geometry.fSubRun = subRun;
2114 geometry.fColor = subRunColor;
2115 geometry.fTransX = transX;
2116 geometry.fTransY = transY;
2117 batch->init();
2118
2119 return batch;
2120 }
2121
flushRun(GrDrawTarget * target,GrPipelineBuilder * pipelineBuilder,BitmapTextBlob * cacheBlob,int run,GrColor color,SkScalar transX,SkScalar transY,const SkPaint & skPaint)2122 inline void GrAtlasTextContext::flushRun(GrDrawTarget* target, GrPipelineBuilder* pipelineBuilder,
2123 BitmapTextBlob* cacheBlob, int run, GrColor color,
2124 SkScalar transX, SkScalar transY, const SkPaint& skPaint) {
2125 for (int subRun = 0; subRun < cacheBlob->fRuns[run].fSubRunInfo.count(); subRun++) {
2126 const PerSubRunInfo& info = cacheBlob->fRuns[run].fSubRunInfo[subRun];
2127 int glyphCount = info.fGlyphEndIndex - info.fGlyphStartIndex;
2128 if (0 == glyphCount) {
2129 continue;
2130 }
2131
2132 SkAutoTUnref<BitmapTextBatch> batch(this->createBatch(cacheBlob, info, glyphCount, run,
2133 subRun, color, transX, transY,
2134 skPaint));
2135 target->drawBatch(pipelineBuilder, batch);
2136 }
2137 }
2138
flushBigGlyphs(BitmapTextBlob * cacheBlob,GrRenderTarget * rt,const SkPaint & skPaint,SkScalar transX,SkScalar transY,const SkIRect & clipBounds)2139 inline void GrAtlasTextContext::flushBigGlyphs(BitmapTextBlob* cacheBlob, GrRenderTarget* rt,
2140 const SkPaint& skPaint,
2141 SkScalar transX, SkScalar transY,
2142 const SkIRect& clipBounds) {
2143 if (!cacheBlob->fBigGlyphs.count()) {
2144 return;
2145 }
2146
2147 SkMatrix pathMatrix;
2148 if (!cacheBlob->fViewMatrix.invert(&pathMatrix)) {
2149 SkDebugf("could not invert viewmatrix\n");
2150 return;
2151 }
2152
2153 for (int i = 0; i < cacheBlob->fBigGlyphs.count(); i++) {
2154 BitmapTextBlob::BigGlyph& bigGlyph = cacheBlob->fBigGlyphs[i];
2155 bigGlyph.fVx += transX;
2156 bigGlyph.fVy += transY;
2157 SkMatrix translate = cacheBlob->fViewMatrix;
2158 translate.postTranslate(bigGlyph.fVx, bigGlyph.fVy);
2159
2160 fGpuDevice->internalDrawPath(bigGlyph.fPath, skPaint, translate, &pathMatrix, clipBounds,
2161 false);
2162 }
2163 }
2164
flush(GrDrawTarget * target,const SkTextBlob * blob,BitmapTextBlob * cacheBlob,GrRenderTarget * rt,const SkPaint & skPaint,const GrPaint & grPaint,SkDrawFilter * drawFilter,const GrClip & clip,const SkMatrix & viewMatrix,const SkIRect & clipBounds,SkScalar x,SkScalar y,SkScalar transX,SkScalar transY)2165 void GrAtlasTextContext::flush(GrDrawTarget* target,
2166 const SkTextBlob* blob,
2167 BitmapTextBlob* cacheBlob,
2168 GrRenderTarget* rt,
2169 const SkPaint& skPaint,
2170 const GrPaint& grPaint,
2171 SkDrawFilter* drawFilter,
2172 const GrClip& clip,
2173 const SkMatrix& viewMatrix,
2174 const SkIRect& clipBounds,
2175 SkScalar x, SkScalar y,
2176 SkScalar transX, SkScalar transY) {
2177 // We loop through the runs of the blob, flushing each. If any run is too large, then we flush
2178 // it as paths
2179 GrPipelineBuilder pipelineBuilder;
2180 pipelineBuilder.setFromPaint(grPaint, rt, clip);
2181
2182 GrColor color = grPaint.getColor();
2183
2184 SkTextBlob::RunIterator it(blob);
2185 for (int run = 0; !it.done(); it.next(), run++) {
2186 if (cacheBlob->fRuns[run].fDrawAsPaths) {
2187 this->flushRunAsPaths(it, skPaint, drawFilter, viewMatrix, clipBounds, x, y);
2188 continue;
2189 }
2190 cacheBlob->fRuns[run].fVertexBounds.offset(transX, transY);
2191 this->flushRun(target, &pipelineBuilder, cacheBlob, run, color, transX, transY, skPaint);
2192 }
2193
2194 // Now flush big glyphs
2195 this->flushBigGlyphs(cacheBlob, rt, skPaint, transX, transY, clipBounds);
2196 }
2197
flush(GrDrawTarget * target,BitmapTextBlob * cacheBlob,GrRenderTarget * rt,const SkPaint & skPaint,const GrPaint & grPaint,const GrClip & clip,const SkIRect & clipBounds)2198 void GrAtlasTextContext::flush(GrDrawTarget* target,
2199 BitmapTextBlob* cacheBlob,
2200 GrRenderTarget* rt,
2201 const SkPaint& skPaint,
2202 const GrPaint& grPaint,
2203 const GrClip& clip,
2204 const SkIRect& clipBounds) {
2205 GrPipelineBuilder pipelineBuilder;
2206 pipelineBuilder.setFromPaint(grPaint, rt, clip);
2207
2208 GrColor color = grPaint.getColor();
2209 for (int run = 0; run < cacheBlob->fRunCount; run++) {
2210 this->flushRun(target, &pipelineBuilder, cacheBlob, run, color, 0, 0, skPaint);
2211 }
2212
2213 // Now flush big glyphs
2214 this->flushBigGlyphs(cacheBlob, rt, skPaint, 0, 0, clipBounds);
2215 }
2216
2217 ///////////////////////////////////////////////////////////////////////////////////////////////////
2218
2219 #ifdef GR_TEST_UTILS
2220
BATCH_TEST_DEFINE(TextBlobBatch)2221 BATCH_TEST_DEFINE(TextBlobBatch) {
2222 static uint32_t gContextID = SK_InvalidGenID;
2223 static GrAtlasTextContext* gTextContext = NULL;
2224 static SkDeviceProperties gDeviceProperties(SkDeviceProperties::kLegacyLCD_InitType);
2225
2226 if (context->uniqueID() != gContextID) {
2227 gContextID = context->uniqueID();
2228 SkDELETE(gTextContext);
2229 // We don't yet test the fall back to paths in the GrTextContext base class. This is mostly
2230 // because we don't really want to have a gpu device here.
2231 // We enable distance fields by twiddling a knob on the paint
2232 gTextContext = GrAtlasTextContext::Create(context, NULL, gDeviceProperties, false);
2233 }
2234
2235 // create dummy render target
2236 GrSurfaceDesc desc;
2237 desc.fFlags = kRenderTarget_GrSurfaceFlag;
2238 desc.fWidth = 1024;
2239 desc.fHeight = 1024;
2240 desc.fConfig = kRGBA_8888_GrPixelConfig;
2241 desc.fSampleCnt = 0;
2242 SkAutoTUnref<GrTexture> texture(context->textureProvider()->createTexture(desc, true, NULL, 0));
2243 SkASSERT(texture);
2244 SkASSERT(NULL != texture->asRenderTarget());
2245 GrRenderTarget* rt = texture->asRenderTarget();
2246
2247 // Setup dummy SkPaint / GrPaint
2248 GrColor color = GrRandomColor(random);
2249 SkMatrix viewMatrix = GrTest::TestMatrixInvertible(random);
2250 SkPaint skPaint;
2251 skPaint.setDistanceFieldTextTEMP(random->nextBool());
2252 skPaint.setColor(color);
2253 skPaint.setLCDRenderText(random->nextBool());
2254 skPaint.setAntiAlias(skPaint.isLCDRenderText() ? true : random->nextBool());
2255 skPaint.setSubpixelText(random->nextBool());
2256
2257 GrPaint grPaint;
2258 if (!SkPaint2GrPaint(context, rt, skPaint, viewMatrix, true, &grPaint)) {
2259 SkFAIL("couldn't convert paint\n");
2260 }
2261
2262 const char* text = "The quick brown fox jumps over the lazy dog.";
2263 int textLen = (int)strlen(text);
2264
2265 // Setup clip
2266 GrClip clip;
2267 SkIRect noClip = SkIRect::MakeLargest();
2268
2269 // right now we don't handle textblobs, nor do we handle drawPosText. Since we only
2270 // intend to test the batch with this unit test, that is okay.
2271 SkAutoTUnref<GrAtlasTextContext::BitmapTextBlob> blob(
2272 gTextContext->createDrawTextBlob(rt, clip, grPaint, skPaint, viewMatrix, text,
2273 static_cast<size_t>(textLen), 0, 0, noClip));
2274
2275 SkScalar transX = static_cast<SkScalar>(random->nextU());
2276 SkScalar transY = static_cast<SkScalar>(random->nextU());
2277 const GrAtlasTextContext::BitmapTextBlob::Run::SubRunInfo& info = blob->fRuns[0].fSubRunInfo[0];
2278 return gTextContext->createBatch(blob, info, textLen, 0, 0, color, transX, transY, skPaint);
2279 }
2280
2281 #endif
2282