blob: f2e993aa7eb04106083709af5b7305567b89a873 [file] [log] [blame]
joshualitt1d89e8d2015-04-01 12:40:54 -07001/*
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 "GrAtlas.h"
10#include "GrBatch.h"
11#include "GrBatchFontCache.h"
12#include "GrBatchTarget.h"
13#include "GrDefaultGeoProcFactory.h"
14#include "GrDrawTarget.h"
15#include "GrFontScaler.h"
16#include "GrIndexBuffer.h"
17#include "GrStrokeInfo.h"
joshualittb7133be2015-04-08 09:08:31 -070018#include "GrTextBlobCache.h"
joshualitt1d89e8d2015-04-01 12:40:54 -070019#include "GrTexturePriv.h"
20
21#include "SkAutoKern.h"
22#include "SkColorPriv.h"
23#include "SkDraw.h"
24#include "SkDrawFilter.h"
25#include "SkDrawProcs.h"
26#include "SkGlyphCache.h"
27#include "SkGpuDevice.h"
28#include "SkGr.h"
29#include "SkPath.h"
30#include "SkRTConf.h"
31#include "SkStrokeRec.h"
32#include "SkTextBlob.h"
33#include "SkTextMapStateProc.h"
34
35#include "effects/GrBitmapTextGeoProc.h"
36#include "effects/GrSimpleTextureEffect.h"
37
38namespace {
39static const size_t kLCDTextVASize = sizeof(SkPoint) + sizeof(SkIPoint16);
40
41// position + local coord
42static const size_t kColorTextVASize = sizeof(SkPoint) + sizeof(SkIPoint16);
43
44static const size_t kGrayTextVASize = sizeof(SkPoint) + sizeof(GrColor) + sizeof(SkIPoint16);
45
46static const int kVerticesPerGlyph = 4;
47static const int kIndicesPerGlyph = 6;
48
49static size_t get_vertex_stride(GrMaskFormat maskFormat) {
50 switch (maskFormat) {
51 case kA8_GrMaskFormat:
52 return kGrayTextVASize;
53 case kARGB_GrMaskFormat:
54 return kColorTextVASize;
55 default:
56 return kLCDTextVASize;
57 }
58}
59
60};
61
62// TODO
joshualitt53b5f442015-04-13 06:33:59 -070063// Gamma slotting to preserve color
64// Better reuse on regeneration
65// Telemetry tests
66// possibly consider having a regeneration ratio on the textblob itself for animated textblobs
joshualitt1d89e8d2015-04-01 12:40:54 -070067
joshualittdbd35932015-04-02 09:19:04 -070068GrAtlasTextContext::GrAtlasTextContext(GrContext* context,
69 SkGpuDevice* gpuDevice,
70 const SkDeviceProperties& properties)
joshualittb7133be2015-04-08 09:08:31 -070071 : INHERITED(context, gpuDevice, properties) {
72 // We overallocate vertices in our textblobs based on the assumption that A8 has the greatest
73 // vertexStride
74 SK_COMPILE_ASSERT(kGrayTextVASize >= kColorTextVASize && kGrayTextVASize >= kLCDTextVASize,
75 vertex_attribute_changed);
joshualitt1d89e8d2015-04-01 12:40:54 -070076 fCurrStrike = NULL;
joshualittb7133be2015-04-08 09:08:31 -070077 fCache = context->getTextBlobCache();
joshualitt1d89e8d2015-04-01 12:40:54 -070078}
79
joshualittdbd35932015-04-02 09:19:04 -070080GrAtlasTextContext* GrAtlasTextContext::Create(GrContext* context,
81 SkGpuDevice* gpuDevice,
82 const SkDeviceProperties& props) {
83 return SkNEW_ARGS(GrAtlasTextContext, (context, gpuDevice, props));
joshualitt1d89e8d2015-04-01 12:40:54 -070084}
85
joshualittdbd35932015-04-02 09:19:04 -070086bool GrAtlasTextContext::canDraw(const GrRenderTarget*,
87 const GrClip&,
88 const GrPaint&,
89 const SkPaint& skPaint,
90 const SkMatrix& viewMatrix) {
joshualitt1d89e8d2015-04-01 12:40:54 -070091 return !SkDraw::ShouldDrawTextAsPaths(skPaint, viewMatrix);
92}
93
joshualitt9e36c1a2015-04-14 12:17:27 -070094GrColor GrAtlasTextContext::ComputeCanonicalColor(const SkPaint& paint, bool lcd) {
95 GrColor canonicalColor = paint.computeLuminanceColor();
96 if (lcd) {
97 // This is the correct computation, but there are tons of cases where LCD can be overridden.
98 // For now we just regenerate if any run in a textblob has LCD.
99 // TODO figure out where all of these overrides are and see if we can incorporate that logic
100 // at a higher level *OR* use sRGB
101 SkASSERT(false);
102 //canonicalColor = SkMaskGamma::CanonicalColor(canonicalColor);
103 } else {
104 // A8, though can have mixed BMP text but it shouldn't matter because BMP text won't have
105 // gamma corrected masks anyways, nor color
106 U8CPU lum = SkComputeLuminance(SkColorGetR(canonicalColor),
107 SkColorGetG(canonicalColor),
108 SkColorGetB(canonicalColor));
109 // reduce to our finite number of bits
110 canonicalColor = SkMaskGamma::CanonicalColor(SkColorSetRGB(lum, lum, lum));
111 }
112 return canonicalColor;
113}
114
115// TODO if this function ever shows up in profiling, then we can compute this value when the
116// textblob is being built and cache it. However, for the time being textblobs mostly only have 1
117// run so this is not a big deal to compute here.
118bool GrAtlasTextContext::HasLCD(const SkTextBlob* blob) {
119 SkTextBlob::RunIterator it(blob);
120 for (; !it.done(); it.next()) {
121 if (it.isLCD()) {
122 return true;
123 }
124 }
125 return false;
126}
127
joshualitt2a0e9f32015-04-13 06:12:21 -0700128bool GrAtlasTextContext::MustRegenerateBlob(SkScalar* outTransX, SkScalar* outTransY,
129 const BitmapTextBlob& blob, const SkPaint& paint,
joshualitt53b5f442015-04-13 06:33:59 -0700130 const SkMaskFilter::BlurRec& blurRec,
joshualittdbd35932015-04-02 09:19:04 -0700131 const SkMatrix& viewMatrix, SkScalar x, SkScalar y) {
joshualitt9e36c1a2015-04-14 12:17:27 -0700132 // If we have LCD text then our canonical color will be set to transparent, in this case we have
133 // to regenerate the blob on any color change
134 if (blob.fKey.fCanonicalColor == SK_ColorTRANSPARENT && blob.fPaintColor != paint.getColor()) {
joshualitt2a0e9f32015-04-13 06:12:21 -0700135 return true;
136 }
137
138 if (blob.fViewMatrix.hasPerspective() != viewMatrix.hasPerspective()) {
139 return true;
140 }
141
142 if (blob.fViewMatrix.hasPerspective() && !blob.fViewMatrix.cheapEqualTo(viewMatrix)) {
143 return true;
144 }
145
146 if (blob.fViewMatrix.getScaleX() != viewMatrix.getScaleX() ||
147 blob.fViewMatrix.getScaleY() != viewMatrix.getScaleY() ||
148 blob.fViewMatrix.getSkewX() != viewMatrix.getSkewX() ||
149 blob.fViewMatrix.getSkewY() != viewMatrix.getSkewY()) {
150 return true;
151 }
152
joshualitt53b5f442015-04-13 06:33:59 -0700153 // We only cache one masked version
154 if (blob.fKey.fHasBlur &&
155 (blob.fBlurRec.fSigma != blurRec.fSigma ||
156 blob.fBlurRec.fStyle != blurRec.fStyle ||
157 blob.fBlurRec.fQuality != blurRec.fQuality)) {
158 return true;
159 }
160
161 // Similarly, we only cache one version for each style
162 if (blob.fKey.fStyle != SkPaint::kFill_Style &&
163 (blob.fStrokeInfo.fFrameWidth != paint.getStrokeWidth() ||
164 blob.fStrokeInfo.fMiterLimit != paint.getStrokeMiter() ||
165 blob.fStrokeInfo.fJoin != paint.getStrokeJoin())) {
166 return true;
167 }
168
joshualitt2a0e9f32015-04-13 06:12:21 -0700169 // We can update the positions in the cachedtextblobs without regenerating the whole blob, but
170 // only for integer translations.
171 // This cool bit of math will determine the necessary translation to apply to the already
172 // generated vertex coordinates to move them to the correct position
173 SkScalar transX = viewMatrix.getTranslateX() +
174 viewMatrix.getScaleX() * (x - blob.fX) +
175 viewMatrix.getSkewX() * (y - blob.fY) -
176 blob.fViewMatrix.getTranslateX();
177 SkScalar transY = viewMatrix.getTranslateY() +
178 viewMatrix.getSkewY() * (x - blob.fX) +
179 viewMatrix.getScaleY() * (y - blob.fY) -
180 blob.fViewMatrix.getTranslateY();
181 if (SkScalarFraction(transX) > SK_ScalarNearlyZero ||
182 SkScalarFraction(transY) > SK_ScalarNearlyZero) {
183 return true;
184 }
185
186#ifdef SK_DEBUG
187 static const SkScalar kMinDiscernableTranslation = 0.0625;
188 // As a safeguard when debugging, we store the total error across all translations and print if
189 // the error becomes discernable. This is pretty unlikely to occur given the tight bounds above
190 // on translation
191 blob.fTotalXError += SkScalarAbs(SkScalarFraction(transX));
192 blob.fTotalYError += SkScalarAbs(SkScalarFraction(transY));
193 if (blob.fTotalXError > kMinDiscernableTranslation ||
194 blob.fTotalYError > kMinDiscernableTranslation) {
195 SkDebugf("Exceeding error threshold for bitmap text translation");
196 }
197#endif
198 (*outTransX) = transX;
199 (*outTransY) = transY;
200 return false;
joshualitt1d89e8d2015-04-01 12:40:54 -0700201}
202
203
joshualittdbd35932015-04-02 09:19:04 -0700204inline SkGlyphCache* GrAtlasTextContext::setupCache(BitmapTextBlob::Run* run,
205 const SkPaint& skPaint,
206 const SkMatrix& viewMatrix) {
joshualitt1d89e8d2015-04-01 12:40:54 -0700207 skPaint.getScalerContextDescriptor(&run->fDescriptor, &fDeviceProperties, &viewMatrix, false);
208 run->fTypeface.reset(SkSafeRef(skPaint.getTypeface()));
209 return SkGlyphCache::DetachCache(run->fTypeface, run->fDescriptor.getDesc());
210}
211
joshualittdbd35932015-04-02 09:19:04 -0700212void GrAtlasTextContext::drawTextBlob(GrRenderTarget* rt, const GrClip& clip,
213 const SkPaint& skPaint, const SkMatrix& viewMatrix,
214 const SkTextBlob* blob, SkScalar x, SkScalar y,
215 SkDrawFilter* drawFilter, const SkIRect& clipBounds) {
joshualitt2a0e9f32015-04-13 06:12:21 -0700216 SkAutoTUnref<BitmapTextBlob> cacheBlob;
joshualitt53b5f442015-04-13 06:33:59 -0700217 SkMaskFilter::BlurRec blurRec;
218 BitmapTextBlob::Key key;
219 // It might be worth caching these things, but its not clear at this time
220 // TODO for animated mask filters, this will fill up our cache. We need a safeguard here
221 const SkMaskFilter* mf = skPaint.getMaskFilter();
joshualitt2a0e9f32015-04-13 06:12:21 -0700222 bool canCache = !(skPaint.getPathEffect() ||
joshualitt53b5f442015-04-13 06:33:59 -0700223 (mf && !mf->asABlur(&blurRec)) ||
joshualitt2a0e9f32015-04-13 06:12:21 -0700224 drawFilter);
225
226 if (canCache) {
joshualitt9e36c1a2015-04-14 12:17:27 -0700227 bool hasLCD = HasLCD(blob);
228 // TODO we want to figure out a way to be able to use the canonical color on LCD text,
229 // see the note on ComputeCanonicalColor above. We pick a dummy value for LCD text to
230 // ensure we always match the same key
231 GrColor canonicalColor = hasLCD ? SK_ColorTRANSPARENT :
232 ComputeCanonicalColor(skPaint, hasLCD);
233
joshualitt53b5f442015-04-13 06:33:59 -0700234 key.fUniqueID = blob->uniqueID();
235 key.fStyle = skPaint.getStyle();
236 key.fHasBlur = SkToBool(mf);
joshualitt9e36c1a2015-04-14 12:17:27 -0700237 key.fCanonicalColor = canonicalColor;
joshualitt53b5f442015-04-13 06:33:59 -0700238 cacheBlob.reset(SkSafeRef(fCache->find(key)));
joshualitt2a0e9f32015-04-13 06:12:21 -0700239 }
240
joshualitt1d89e8d2015-04-01 12:40:54 -0700241 SkIRect clipRect;
242 clip.getConservativeBounds(rt->width(), rt->height(), &clipRect);
243
joshualitt2a0e9f32015-04-13 06:12:21 -0700244 SkScalar transX = 0.f;
245 SkScalar transY = 0.f;
246
joshualitt9e36c1a2015-04-14 12:17:27 -0700247 // Though for the time being runs in the textblob can override the paint, they only touch font
248 // info.
249 GrPaint grPaint;
bsalomonbed83a62015-04-15 14:18:34 -0700250 if (!SkPaint2GrPaint(fContext, rt, skPaint, viewMatrix, true, &grPaint)) {
251 return;
252 }
joshualitt9e36c1a2015-04-14 12:17:27 -0700253
joshualittb7133be2015-04-08 09:08:31 -0700254 if (cacheBlob) {
joshualitt53b5f442015-04-13 06:33:59 -0700255 if (MustRegenerateBlob(&transX, &transY, *cacheBlob, skPaint, blurRec, viewMatrix, x, y)) {
joshualitt1d89e8d2015-04-01 12:40:54 -0700256 // We have to remake the blob because changes may invalidate our masks.
257 // TODO we could probably get away reuse most of the time if the pointer is unique,
258 // but we'd have to clear the subrun information
joshualittb7133be2015-04-08 09:08:31 -0700259 fCache->remove(cacheBlob);
joshualitt53b5f442015-04-13 06:33:59 -0700260 cacheBlob.reset(SkRef(fCache->createCachedBlob(blob, key, blurRec, skPaint,
261 kGrayTextVASize)));
joshualitt9e36c1a2015-04-14 12:17:27 -0700262 this->regenerateTextBlob(cacheBlob, skPaint, grPaint.getColor(), viewMatrix, blob, x, y,
263 drawFilter, clipRect);
joshualittb7133be2015-04-08 09:08:31 -0700264 } else {
joshualitt9e36c1a2015-04-14 12:17:27 -0700265 // If we can reuse the blob, then make sure we update the blob's viewmatrix, and x/y
266 // offsets
joshualitt2a0e9f32015-04-13 06:12:21 -0700267 cacheBlob->fViewMatrix = viewMatrix;
268 cacheBlob->fX = x;
269 cacheBlob->fY = y;
joshualittb7133be2015-04-08 09:08:31 -0700270 fCache->makeMRU(cacheBlob);
joshualitt1d89e8d2015-04-01 12:40:54 -0700271 }
272 } else {
joshualitt2a0e9f32015-04-13 06:12:21 -0700273 if (canCache) {
joshualitt53b5f442015-04-13 06:33:59 -0700274 cacheBlob.reset(SkRef(fCache->createCachedBlob(blob, key, blurRec, skPaint,
275 kGrayTextVASize)));
joshualitt2a0e9f32015-04-13 06:12:21 -0700276 } else {
277 cacheBlob.reset(fCache->createBlob(blob, kGrayTextVASize));
278 }
joshualitt9e36c1a2015-04-14 12:17:27 -0700279 this->regenerateTextBlob(cacheBlob, skPaint, grPaint.getColor(), viewMatrix, blob, x, y,
280 drawFilter, clipRect);
joshualitt1d89e8d2015-04-01 12:40:54 -0700281 }
282
joshualitt9e36c1a2015-04-14 12:17:27 -0700283 cacheBlob->fPaintColor = skPaint.getColor();
joshualitt9a27e632015-04-06 10:53:36 -0700284 this->flush(fContext->getTextTarget(), blob, cacheBlob, rt, skPaint, grPaint, drawFilter,
joshualitt2a0e9f32015-04-13 06:12:21 -0700285 clip, viewMatrix, clipBounds, x, y, transX, transY);
joshualitt1d89e8d2015-04-01 12:40:54 -0700286}
287
joshualittdbd35932015-04-02 09:19:04 -0700288void GrAtlasTextContext::regenerateTextBlob(BitmapTextBlob* cacheBlob,
joshualitt9e36c1a2015-04-14 12:17:27 -0700289 const SkPaint& skPaint, GrColor color,
290 const SkMatrix& viewMatrix,
joshualittdbd35932015-04-02 09:19:04 -0700291 const SkTextBlob* blob, SkScalar x, SkScalar y,
292 SkDrawFilter* drawFilter, const SkIRect& clipRect) {
joshualitt1d89e8d2015-04-01 12:40:54 -0700293 cacheBlob->fViewMatrix = viewMatrix;
294 cacheBlob->fX = x;
295 cacheBlob->fY = y;
joshualitt1d89e8d2015-04-01 12:40:54 -0700296
297 // Regenerate textblob
298 SkPaint runPaint = skPaint;
299 SkTextBlob::RunIterator it(blob);
300 for (int run = 0; !it.done(); it.next(), run++) {
301 int glyphCount = it.glyphCount();
302 size_t textLen = glyphCount * sizeof(uint16_t);
303 const SkPoint& offset = it.offset();
304 // applyFontToPaint() always overwrites the exact same attributes,
305 // so it is safe to not re-seed the paint for this reason.
306 it.applyFontToPaint(&runPaint);
307
308 if (drawFilter && !drawFilter->filter(&runPaint, SkDrawFilter::kText_Type)) {
309 // A false return from filter() means we should abort the current draw.
310 runPaint = skPaint;
311 continue;
312 }
313
314 runPaint.setFlags(fGpuDevice->filterTextFlags(runPaint));
315
316 SkGlyphCache* cache = this->setupCache(&cacheBlob->fRuns[run], runPaint, viewMatrix);
317
318 // setup vertex / glyphIndex for the new run
319 if (run > 0) {
320 PerSubRunInfo& newRun = cacheBlob->fRuns[run].fSubRunInfo.back();
321 PerSubRunInfo& lastRun = cacheBlob->fRuns[run - 1].fSubRunInfo.back();
322
323 newRun.fVertexStartIndex = lastRun.fVertexEndIndex;
324 newRun.fVertexEndIndex = lastRun.fVertexEndIndex;
325
326 newRun.fGlyphStartIndex = lastRun.fGlyphEndIndex;
327 newRun.fGlyphEndIndex = lastRun.fGlyphEndIndex;
328 }
329
joshualitt2a0e9f32015-04-13 06:12:21 -0700330 if (SkDraw::ShouldDrawTextAsPaths(runPaint, viewMatrix)) {
joshualitt9a27e632015-04-06 10:53:36 -0700331 cacheBlob->fRuns[run].fDrawAsPaths = true;
332 continue;
333 }
334 cacheBlob->fRuns[run].fDrawAsPaths = false;
335
joshualitt1d89e8d2015-04-01 12:40:54 -0700336 switch (it.positioning()) {
337 case SkTextBlob::kDefault_Positioning:
joshualitt9e36c1a2015-04-14 12:17:27 -0700338 this->internalDrawText(cacheBlob, run, cache, runPaint, color, viewMatrix,
joshualitt1d89e8d2015-04-01 12:40:54 -0700339 (const char *)it.glyphs(), textLen,
340 x + offset.x(), y + offset.y(), clipRect);
341 break;
342 case SkTextBlob::kHorizontal_Positioning:
joshualitt9e36c1a2015-04-14 12:17:27 -0700343 this->internalDrawPosText(cacheBlob, run, cache, runPaint, color, viewMatrix,
joshualitt1d89e8d2015-04-01 12:40:54 -0700344 (const char*)it.glyphs(), textLen, it.pos(), 1,
345 SkPoint::Make(x, y + offset.y()), clipRect);
346 break;
347 case SkTextBlob::kFull_Positioning:
joshualitt9e36c1a2015-04-14 12:17:27 -0700348 this->internalDrawPosText(cacheBlob, run, cache, runPaint, color, viewMatrix,
joshualitt1d89e8d2015-04-01 12:40:54 -0700349 (const char*)it.glyphs(), textLen, it.pos(), 2,
350 SkPoint::Make(x, y), clipRect);
351 break;
352 }
353
354 if (drawFilter) {
355 // A draw filter may change the paint arbitrarily, so we must re-seed in this case.
356 runPaint = skPaint;
357 }
358
359 SkGlyphCache::AttachCache(cache);
360 }
361}
362
joshualittdbd35932015-04-02 09:19:04 -0700363void GrAtlasTextContext::onDrawText(GrRenderTarget* rt, const GrClip& clip,
364 const GrPaint& paint, const SkPaint& skPaint,
365 const SkMatrix& viewMatrix,
366 const char text[], size_t byteLength,
367 SkScalar x, SkScalar y, const SkIRect& regionClipBounds) {
joshualitt1d89e8d2015-04-01 12:40:54 -0700368 int glyphCount = skPaint.countText(text, byteLength);
joshualittb7133be2015-04-08 09:08:31 -0700369 SkAutoTUnref<BitmapTextBlob> blob(fCache->createBlob(glyphCount, 1, kGrayTextVASize));
joshualitt1d89e8d2015-04-01 12:40:54 -0700370 blob->fViewMatrix = viewMatrix;
371 blob->fX = x;
372 blob->fY = y;
joshualitt1d89e8d2015-04-01 12:40:54 -0700373
374 SkIRect clipRect;
375 clip.getConservativeBounds(rt->width(), rt->height(), &clipRect);
376
377 // setup cache
378 SkGlyphCache* cache = this->setupCache(&blob->fRuns[0], skPaint, viewMatrix);
joshualitt9e36c1a2015-04-14 12:17:27 -0700379 this->internalDrawText(blob, 0, cache, skPaint, paint.getColor(), viewMatrix, text, byteLength,
380 x, y, clipRect);
joshualitt1d89e8d2015-04-01 12:40:54 -0700381 SkGlyphCache::AttachCache(cache);
382
joshualitt9a27e632015-04-06 10:53:36 -0700383 this->flush(fContext->getTextTarget(), blob, rt, skPaint, paint, clip, viewMatrix);
joshualitt1d89e8d2015-04-01 12:40:54 -0700384}
385
joshualittdbd35932015-04-02 09:19:04 -0700386void GrAtlasTextContext::internalDrawText(BitmapTextBlob* blob, int runIndex,
387 SkGlyphCache* cache, const SkPaint& skPaint,
joshualitt9e36c1a2015-04-14 12:17:27 -0700388 GrColor color,
joshualittdbd35932015-04-02 09:19:04 -0700389 const SkMatrix& viewMatrix,
390 const char text[], size_t byteLength,
391 SkScalar x, SkScalar y, const SkIRect& clipRect) {
joshualitt1d89e8d2015-04-01 12:40:54 -0700392 SkASSERT(byteLength == 0 || text != NULL);
393
394 // nothing to draw
395 if (text == NULL || byteLength == 0) {
396 return;
397 }
398
399 fCurrStrike = NULL;
400 SkDrawCacheProc glyphCacheProc = skPaint.getDrawCacheProc();
401
402 // Get GrFontScaler from cache
403 GrFontScaler* fontScaler = GetGrFontScaler(cache);
404
405 // transform our starting point
406 {
407 SkPoint loc;
408 viewMatrix.mapXY(x, y, &loc);
409 x = loc.fX;
410 y = loc.fY;
411 }
412
413 // need to measure first
414 if (skPaint.getTextAlign() != SkPaint::kLeft_Align) {
415 SkVector stopVector;
416 MeasureText(cache, glyphCacheProc, text, byteLength, &stopVector);
417
418 SkScalar stopX = stopVector.fX;
419 SkScalar stopY = stopVector.fY;
420
421 if (skPaint.getTextAlign() == SkPaint::kCenter_Align) {
422 stopX = SkScalarHalf(stopX);
423 stopY = SkScalarHalf(stopY);
424 }
425 x -= stopX;
426 y -= stopY;
427 }
428
429 const char* stop = text + byteLength;
430
431 SkAutoKern autokern;
432
433 SkFixed fxMask = ~0;
434 SkFixed fyMask = ~0;
435 SkScalar halfSampleX, halfSampleY;
436 if (cache->isSubpixel()) {
437 halfSampleX = halfSampleY = SkFixedToScalar(SkGlyph::kSubpixelRound);
438 SkAxisAlignment baseline = SkComputeAxisAlignmentForHText(viewMatrix);
439 if (kX_SkAxisAlignment == baseline) {
440 fyMask = 0;
441 halfSampleY = SK_ScalarHalf;
442 } else if (kY_SkAxisAlignment == baseline) {
443 fxMask = 0;
444 halfSampleX = SK_ScalarHalf;
445 }
446 } else {
447 halfSampleX = halfSampleY = SK_ScalarHalf;
448 }
449
450 Sk48Dot16 fx = SkScalarTo48Dot16(x + halfSampleX);
451 Sk48Dot16 fy = SkScalarTo48Dot16(y + halfSampleY);
452
453 while (text < stop) {
454 const SkGlyph& glyph = glyphCacheProc(cache, &text, fx & fxMask, fy & fyMask);
455
456 fx += autokern.adjust(glyph);
457
458 if (glyph.fWidth) {
459 this->appendGlyph(blob,
460 runIndex,
461 GrGlyph::Pack(glyph.getGlyphID(),
462 glyph.getSubXFixed(),
463 glyph.getSubYFixed(),
464 GrGlyph::kCoverage_MaskStyle),
465 Sk48Dot16FloorToInt(fx),
466 Sk48Dot16FloorToInt(fy),
joshualitt9e36c1a2015-04-14 12:17:27 -0700467 color,
joshualitt1d89e8d2015-04-01 12:40:54 -0700468 fontScaler,
469 clipRect);
470 }
471
472 fx += glyph.fAdvanceX;
473 fy += glyph.fAdvanceY;
474 }
475}
476
joshualittdbd35932015-04-02 09:19:04 -0700477void GrAtlasTextContext::onDrawPosText(GrRenderTarget* rt, const GrClip& clip,
478 const GrPaint& paint, const SkPaint& skPaint,
479 const SkMatrix& viewMatrix,
480 const char text[], size_t byteLength,
481 const SkScalar pos[], int scalarsPerPosition,
482 const SkPoint& offset, const SkIRect& regionClipBounds) {
joshualitt1d89e8d2015-04-01 12:40:54 -0700483 int glyphCount = skPaint.countText(text, byteLength);
joshualittb7133be2015-04-08 09:08:31 -0700484 SkAutoTUnref<BitmapTextBlob> blob(fCache->createBlob(glyphCount, 1, kGrayTextVASize));
joshualitt1d89e8d2015-04-01 12:40:54 -0700485 blob->fViewMatrix = viewMatrix;
486
487 SkIRect clipRect;
488 clip.getConservativeBounds(rt->width(), rt->height(), &clipRect);
489
490 // setup cache
491 SkGlyphCache* cache = this->setupCache(&blob->fRuns[0], skPaint, viewMatrix);
joshualitt9e36c1a2015-04-14 12:17:27 -0700492 this->internalDrawPosText(blob, 0, cache, skPaint, paint.getColor(), viewMatrix, text,
493 byteLength, pos, scalarsPerPosition, offset, clipRect);
joshualitt1d89e8d2015-04-01 12:40:54 -0700494 SkGlyphCache::AttachCache(cache);
495
joshualitt9a27e632015-04-06 10:53:36 -0700496 this->flush(fContext->getTextTarget(), blob, rt, skPaint, paint, clip, viewMatrix);
joshualitt1d89e8d2015-04-01 12:40:54 -0700497}
498
joshualittdbd35932015-04-02 09:19:04 -0700499void GrAtlasTextContext::internalDrawPosText(BitmapTextBlob* blob, int runIndex,
500 SkGlyphCache* cache, const SkPaint& skPaint,
joshualitt9e36c1a2015-04-14 12:17:27 -0700501 GrColor color,
joshualittdbd35932015-04-02 09:19:04 -0700502 const SkMatrix& viewMatrix,
503 const char text[], size_t byteLength,
504 const SkScalar pos[], int scalarsPerPosition,
505 const SkPoint& offset, const SkIRect& clipRect) {
joshualitt1d89e8d2015-04-01 12:40:54 -0700506 SkASSERT(byteLength == 0 || text != NULL);
507 SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition);
508
509 // nothing to draw
510 if (text == NULL || byteLength == 0) {
511 return;
512 }
513
514 fCurrStrike = NULL;
515 SkDrawCacheProc glyphCacheProc = skPaint.getDrawCacheProc();
516
517 // Get GrFontScaler from cache
518 GrFontScaler* fontScaler = GetGrFontScaler(cache);
519
520 const char* stop = text + byteLength;
521 SkTextAlignProc alignProc(skPaint.getTextAlign());
522 SkTextMapStateProc tmsProc(viewMatrix, offset, scalarsPerPosition);
523
524 if (cache->isSubpixel()) {
525 // maybe we should skip the rounding if linearText is set
526 SkAxisAlignment baseline = SkComputeAxisAlignmentForHText(viewMatrix);
527
528 SkFixed fxMask = ~0;
529 SkFixed fyMask = ~0;
530 SkScalar halfSampleX = SkFixedToScalar(SkGlyph::kSubpixelRound);
531 SkScalar halfSampleY = SkFixedToScalar(SkGlyph::kSubpixelRound);
532 if (kX_SkAxisAlignment == baseline) {
533 fyMask = 0;
534 halfSampleY = SK_ScalarHalf;
535 } else if (kY_SkAxisAlignment == baseline) {
536 fxMask = 0;
537 halfSampleX = SK_ScalarHalf;
538 }
539
540 if (SkPaint::kLeft_Align == skPaint.getTextAlign()) {
541 while (text < stop) {
542 SkPoint tmsLoc;
543 tmsProc(pos, &tmsLoc);
544 Sk48Dot16 fx = SkScalarTo48Dot16(tmsLoc.fX + halfSampleX);
545 Sk48Dot16 fy = SkScalarTo48Dot16(tmsLoc.fY + halfSampleY);
546
547 const SkGlyph& glyph = glyphCacheProc(cache, &text,
548 fx & fxMask, fy & fyMask);
549
550 if (glyph.fWidth) {
551 this->appendGlyph(blob,
552 runIndex,
553 GrGlyph::Pack(glyph.getGlyphID(),
554 glyph.getSubXFixed(),
555 glyph.getSubYFixed(),
556 GrGlyph::kCoverage_MaskStyle),
557 Sk48Dot16FloorToInt(fx),
558 Sk48Dot16FloorToInt(fy),
joshualitt9e36c1a2015-04-14 12:17:27 -0700559 color,
joshualitt1d89e8d2015-04-01 12:40:54 -0700560 fontScaler,
561 clipRect);
562 }
563 pos += scalarsPerPosition;
564 }
565 } else {
566 while (text < stop) {
567 const char* currentText = text;
568 const SkGlyph& metricGlyph = glyphCacheProc(cache, &text, 0, 0);
569
570 if (metricGlyph.fWidth) {
571 SkDEBUGCODE(SkFixed prevAdvX = metricGlyph.fAdvanceX;)
572 SkDEBUGCODE(SkFixed prevAdvY = metricGlyph.fAdvanceY;)
573 SkPoint tmsLoc;
574 tmsProc(pos, &tmsLoc);
575 SkPoint alignLoc;
576 alignProc(tmsLoc, metricGlyph, &alignLoc);
577
578 Sk48Dot16 fx = SkScalarTo48Dot16(alignLoc.fX + halfSampleX);
579 Sk48Dot16 fy = SkScalarTo48Dot16(alignLoc.fY + halfSampleY);
580
581 // have to call again, now that we've been "aligned"
582 const SkGlyph& glyph = glyphCacheProc(cache, &currentText,
583 fx & fxMask, fy & fyMask);
584 // the assumption is that the metrics haven't changed
585 SkASSERT(prevAdvX == glyph.fAdvanceX);
586 SkASSERT(prevAdvY == glyph.fAdvanceY);
587 SkASSERT(glyph.fWidth);
588
589 this->appendGlyph(blob,
590 runIndex,
591 GrGlyph::Pack(glyph.getGlyphID(),
592 glyph.getSubXFixed(),
593 glyph.getSubYFixed(),
594 GrGlyph::kCoverage_MaskStyle),
595 Sk48Dot16FloorToInt(fx),
596 Sk48Dot16FloorToInt(fy),
joshualitt9e36c1a2015-04-14 12:17:27 -0700597 color,
joshualitt1d89e8d2015-04-01 12:40:54 -0700598 fontScaler,
599 clipRect);
600 }
601 pos += scalarsPerPosition;
602 }
603 }
604 } else { // not subpixel
605
606 if (SkPaint::kLeft_Align == skPaint.getTextAlign()) {
607 while (text < stop) {
608 // the last 2 parameters are ignored
609 const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
610
611 if (glyph.fWidth) {
612 SkPoint tmsLoc;
613 tmsProc(pos, &tmsLoc);
614
615 Sk48Dot16 fx = SkScalarTo48Dot16(tmsLoc.fX + SK_ScalarHalf); //halfSampleX;
616 Sk48Dot16 fy = SkScalarTo48Dot16(tmsLoc.fY + SK_ScalarHalf); //halfSampleY;
617 this->appendGlyph(blob,
618 runIndex,
619 GrGlyph::Pack(glyph.getGlyphID(),
620 glyph.getSubXFixed(),
621 glyph.getSubYFixed(),
622 GrGlyph::kCoverage_MaskStyle),
623 Sk48Dot16FloorToInt(fx),
624 Sk48Dot16FloorToInt(fy),
joshualitt9e36c1a2015-04-14 12:17:27 -0700625 color,
joshualitt1d89e8d2015-04-01 12:40:54 -0700626 fontScaler,
627 clipRect);
628 }
629 pos += scalarsPerPosition;
630 }
631 } else {
632 while (text < stop) {
633 // the last 2 parameters are ignored
634 const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
635
636 if (glyph.fWidth) {
637 SkPoint tmsLoc;
638 tmsProc(pos, &tmsLoc);
639
640 SkPoint alignLoc;
641 alignProc(tmsLoc, glyph, &alignLoc);
642
643 Sk48Dot16 fx = SkScalarTo48Dot16(alignLoc.fX + SK_ScalarHalf); //halfSampleX;
644 Sk48Dot16 fy = SkScalarTo48Dot16(alignLoc.fY + SK_ScalarHalf); //halfSampleY;
645 this->appendGlyph(blob,
646 runIndex,
647 GrGlyph::Pack(glyph.getGlyphID(),
648 glyph.getSubXFixed(),
649 glyph.getSubYFixed(),
650 GrGlyph::kCoverage_MaskStyle),
651 Sk48Dot16FloorToInt(fx),
652 Sk48Dot16FloorToInt(fy),
joshualitt9e36c1a2015-04-14 12:17:27 -0700653 color,
joshualitt1d89e8d2015-04-01 12:40:54 -0700654 fontScaler,
655 clipRect);
656 }
657 pos += scalarsPerPosition;
658 }
659 }
660 }
661}
662
joshualittdbd35932015-04-02 09:19:04 -0700663void GrAtlasTextContext::appendGlyph(BitmapTextBlob* blob, int runIndex, GrGlyph::PackedID packed,
joshualitteef5b3e2015-04-03 08:07:26 -0700664 int vx, int vy, GrColor color, GrFontScaler* scaler,
joshualittdbd35932015-04-02 09:19:04 -0700665 const SkIRect& clipRect) {
joshualitt1d89e8d2015-04-01 12:40:54 -0700666 if (NULL == fCurrStrike) {
667 fCurrStrike = fContext->getBatchFontCache()->getStrike(scaler);
668 }
669
670 GrGlyph* glyph = fCurrStrike->getGlyph(packed, scaler);
671 if (NULL == glyph || glyph->fBounds.isEmpty()) {
672 return;
673 }
674
675 int x = vx + glyph->fBounds.fLeft;
676 int y = vy + glyph->fBounds.fTop;
677
678 // keep them as ints until we've done the clip-test
679 int width = glyph->fBounds.width();
680 int height = glyph->fBounds.height();
681
joshualitt2a0e9f32015-04-13 06:12:21 -0700682#if 0
683 // Not checking the clip bounds might introduce a performance regression. However, its not
684 // clear if this is still true today with the larger tiles we use in Chrome. For repositionable
685 // blobs, we want to make sure we have all of the glyphs, so clipping them out is not ideal.
686 // We could store the cliprect in the key, but then we'd lose the ability to do integer scrolls
687 // TODO verify this
joshualitt1d89e8d2015-04-01 12:40:54 -0700688 // check if we clipped out
689 if (clipRect.quickReject(x, y, x + width, y + height)) {
690 return;
691 }
joshualitt2a0e9f32015-04-13 06:12:21 -0700692#endif
joshualitt1d89e8d2015-04-01 12:40:54 -0700693
694 // If the glyph is too large we fall back to paths
695 if (fCurrStrike->glyphTooLargeForAtlas(glyph)) {
696 if (NULL == glyph->fPath) {
697 SkPath* path = SkNEW(SkPath);
698 if (!scaler->getGlyphPath(glyph->glyphID(), path)) {
699 // flag the glyph as being dead?
700 SkDELETE(path);
701 return;
702 }
703 glyph->fPath = path;
704 }
705 SkASSERT(glyph->fPath);
706 blob->fBigGlyphs.push_back(BitmapTextBlob::BigGlyph(*glyph->fPath, vx, vy));
707 return;
708 }
709
710 Run& run = blob->fRuns[runIndex];
711
712 GrMaskFormat format = glyph->fMaskFormat;
713
714 PerSubRunInfo* subRun = &run.fSubRunInfo.back();
715 if (run.fInitialized && subRun->fMaskFormat != format) {
716 PerSubRunInfo* newSubRun = &run.fSubRunInfo.push_back();
717 newSubRun->fGlyphStartIndex = subRun->fGlyphEndIndex;
718 newSubRun->fGlyphEndIndex = subRun->fGlyphEndIndex;
719
720 newSubRun->fVertexStartIndex = subRun->fVertexEndIndex;
721 newSubRun->fVertexEndIndex = subRun->fVertexEndIndex;
722
723 subRun = newSubRun;
724 }
725
726 run.fInitialized = true;
727 subRun->fMaskFormat = format;
728 blob->fGlyphIDs[subRun->fGlyphEndIndex] = packed;
729
730 size_t vertexStride = get_vertex_stride(format);
731
732 SkRect r;
733 r.fLeft = SkIntToScalar(x);
734 r.fTop = SkIntToScalar(y);
735 r.fRight = r.fLeft + SkIntToScalar(width);
736 r.fBottom = r.fTop + SkIntToScalar(height);
737
738 run.fVertexBounds.joinNonEmptyArg(r);
joshualitt1d89e8d2015-04-01 12:40:54 -0700739 run.fColor = color;
740
741 intptr_t vertex = reinterpret_cast<intptr_t>(blob->fVertices + subRun->fVertexEndIndex);
742
743 // V0
744 SkPoint* position = reinterpret_cast<SkPoint*>(vertex);
745 position->set(r.fLeft, r.fTop);
746 if (kA8_GrMaskFormat == format) {
747 SkColor* colorPtr = reinterpret_cast<SkColor*>(vertex + sizeof(SkPoint));
748 *colorPtr = color;
749 }
750 vertex += vertexStride;
joshualitt1d89e8d2015-04-01 12:40:54 -0700751 // V1
752 position = reinterpret_cast<SkPoint*>(vertex);
753 position->set(r.fLeft, r.fBottom);
754 if (kA8_GrMaskFormat == format) {
755 SkColor* colorPtr = reinterpret_cast<SkColor*>(vertex + sizeof(SkPoint));
756 *colorPtr = color;
757 }
758 vertex += vertexStride;
759
760 // V2
761 position = reinterpret_cast<SkPoint*>(vertex);
762 position->set(r.fRight, r.fBottom);
763 if (kA8_GrMaskFormat == format) {
764 SkColor* colorPtr = reinterpret_cast<SkColor*>(vertex + sizeof(SkPoint));
765 *colorPtr = color;
766 }
767 vertex += vertexStride;
768
769 // V3
770 position = reinterpret_cast<SkPoint*>(vertex);
771 position->set(r.fRight, r.fTop);
772 if (kA8_GrMaskFormat == format) {
773 SkColor* colorPtr = reinterpret_cast<SkColor*>(vertex + sizeof(SkPoint));
774 *colorPtr = color;
775 }
776
777 subRun->fGlyphEndIndex++;
778 subRun->fVertexEndIndex += vertexStride * kVerticesPerGlyph;
779}
780
781class BitmapTextBatch : public GrBatch {
782public:
joshualittdbd35932015-04-02 09:19:04 -0700783 typedef GrAtlasTextContext::BitmapTextBlob Blob;
joshualitt1d89e8d2015-04-01 12:40:54 -0700784 typedef Blob::Run Run;
785 typedef Run::SubRunInfo TextInfo;
786 struct Geometry {
joshualittad802c62015-04-15 05:31:57 -0700787 Blob* fBlob;
joshualitt1d89e8d2015-04-01 12:40:54 -0700788 int fRun;
789 int fSubRun;
790 GrColor fColor;
joshualitt2a0e9f32015-04-13 06:12:21 -0700791 SkScalar fTransX;
792 SkScalar fTransY;
joshualitt1d89e8d2015-04-01 12:40:54 -0700793 };
794
joshualittad802c62015-04-15 05:31:57 -0700795 static BitmapTextBatch* Create(GrMaskFormat maskFormat, int glyphCount,
796 GrBatchFontCache* fontCache) {
797 return SkNEW_ARGS(BitmapTextBatch, (maskFormat, glyphCount, fontCache));
joshualitt1d89e8d2015-04-01 12:40:54 -0700798 }
799
800 const char* name() const override { return "BitmapTextBatch"; }
801
802 void getInvariantOutputColor(GrInitInvariantOutput* out) const override {
803 if (kARGB_GrMaskFormat == fMaskFormat) {
804 out->setUnknownFourComponents();
805 } else {
806 out->setKnownFourComponents(fBatch.fColor);
807 }
808 }
809
810 void getInvariantOutputCoverage(GrInitInvariantOutput* out) const override {
811 if (kARGB_GrMaskFormat != fMaskFormat) {
812 if (GrPixelConfigIsAlphaOnly(fPixelConfig)) {
813 out->setUnknownSingleComponent();
814 } else if (GrPixelConfigIsOpaque(fPixelConfig)) {
815 out->setUnknownOpaqueFourComponents();
816 out->setUsingLCDCoverage();
817 } else {
818 out->setUnknownFourComponents();
819 out->setUsingLCDCoverage();
820 }
821 } else {
822 out->setKnownSingleComponent(0xff);
823 }
824 }
825
826 void initBatchTracker(const GrPipelineInfo& init) override {
827 // Handle any color overrides
828 if (init.fColorIgnored) {
829 fBatch.fColor = GrColor_ILLEGAL;
830 } else if (GrColor_ILLEGAL != init.fOverrideColor) {
831 fBatch.fColor = init.fOverrideColor;
832 }
833
834 // setup batch properties
835 fBatch.fColorIgnored = init.fColorIgnored;
836 fBatch.fUsesLocalCoords = init.fUsesLocalCoords;
837 fBatch.fCoverageIgnored = init.fCoverageIgnored;
838 }
839
840 void generateGeometry(GrBatchTarget* batchTarget, const GrPipeline* pipeline) override {
841 // if we have RGB, then we won't have any SkShaders so no need to use a localmatrix.
842 // TODO actually only invert if we don't have RGBA
843 SkMatrix localMatrix;
844 if (this->usesLocalCoords() && !this->viewMatrix().invert(&localMatrix)) {
845 SkDebugf("Cannot invert viewmatrix\n");
846 return;
847 }
848
joshualitt62db8ba2015-04-09 08:22:37 -0700849 GrTexture* texture = fFontCache->getTexture(fMaskFormat);
850 if (!texture) {
851 SkDebugf("Could not allocate backing texture for atlas\n");
852 return;
853 }
854
joshualitt1d89e8d2015-04-01 12:40:54 -0700855 GrTextureParams params(SkShader::kClamp_TileMode, GrTextureParams::kNone_FilterMode);
joshualitteef5b3e2015-04-03 08:07:26 -0700856
joshualitt1d89e8d2015-04-01 12:40:54 -0700857 // This will be ignored in the non A8 case
858 bool opaqueVertexColors = GrColorIsOpaque(this->color());
859 SkAutoTUnref<const GrGeometryProcessor> gp(
860 GrBitmapTextGeoProc::Create(this->color(),
joshualitt62db8ba2015-04-09 08:22:37 -0700861 texture,
joshualitt1d89e8d2015-04-01 12:40:54 -0700862 params,
863 fMaskFormat,
864 opaqueVertexColors,
865 localMatrix));
866
867 size_t vertexStride = gp->getVertexStride();
868 SkASSERT(vertexStride == get_vertex_stride(fMaskFormat));
869
870 this->initDraw(batchTarget, gp, pipeline);
871
872 int glyphCount = this->numGlyphs();
joshualittad802c62015-04-15 05:31:57 -0700873 int instanceCount = fInstanceCount;
joshualitt1d89e8d2015-04-01 12:40:54 -0700874 const GrVertexBuffer* vertexBuffer;
875 int firstVertex;
876
877 void* vertices = batchTarget->vertexPool()->makeSpace(vertexStride,
878 glyphCount * kVerticesPerGlyph,
879 &vertexBuffer,
880 &firstVertex);
881 if (!vertices) {
882 SkDebugf("Could not allocate vertices\n");
883 return;
884 }
885
886 unsigned char* currVertex = reinterpret_cast<unsigned char*>(vertices);
887
888 // setup drawinfo
889 const GrIndexBuffer* quadIndexBuffer = batchTarget->quadIndexBuffer();
890 int maxInstancesPerDraw = quadIndexBuffer->maxQuads();
891
892 GrDrawTarget::DrawInfo drawInfo;
893 drawInfo.setPrimitiveType(kTriangles_GrPrimitiveType);
894 drawInfo.setStartVertex(0);
895 drawInfo.setStartIndex(0);
896 drawInfo.setVerticesPerInstance(kVerticesPerGlyph);
897 drawInfo.setIndicesPerInstance(kIndicesPerGlyph);
898 drawInfo.adjustStartVertex(firstVertex);
899 drawInfo.setVertexBuffer(vertexBuffer);
900 drawInfo.setIndexBuffer(quadIndexBuffer);
901
902 int instancesToFlush = 0;
903 for (int i = 0; i < instanceCount; i++) {
904 Geometry& args = fGeoData[i];
905 Blob* blob = args.fBlob;
906 Run& run = blob->fRuns[args.fRun];
907 TextInfo& info = run.fSubRunInfo[args.fSubRun];
908
909 uint64_t currentAtlasGen = fFontCache->atlasGeneration(fMaskFormat);
910 bool regenerateTextureCoords = info.fAtlasGeneration != currentAtlasGen;
911 bool regenerateColors = kA8_GrMaskFormat == fMaskFormat && run.fColor != args.fColor;
joshualitt2a0e9f32015-04-13 06:12:21 -0700912 bool regeneratePositions = args.fTransX != 0.f || args.fTransY != 0.f;
joshualitt1d89e8d2015-04-01 12:40:54 -0700913 int glyphCount = info.fGlyphEndIndex - info.fGlyphStartIndex;
914
915 // We regenerate both texture coords and colors in the blob itself, and update the
916 // atlas generation. If we don't end up purging any unused plots, we can avoid
917 // regenerating the coords. We could take a finer grained approach to updating texture
918 // coords but its not clear if the extra bookkeeping would offset any gains.
919 // To avoid looping over the glyphs twice, we do one loop and conditionally update color
920 // or coords as needed. One final note, if we have to break a run for an atlas eviction
921 // then we can't really trust the atlas has all of the correct data. Atlas evictions
922 // should be pretty rare, so we just always regenerate in those cases
joshualitt2a0e9f32015-04-13 06:12:21 -0700923 if (regenerateTextureCoords || regenerateColors || regeneratePositions) {
joshualitt1d89e8d2015-04-01 12:40:54 -0700924 // first regenerate texture coordinates / colors if need be
925 const SkDescriptor* desc = NULL;
926 SkGlyphCache* cache = NULL;
927 GrFontScaler* scaler = NULL;
928 GrBatchTextStrike* strike = NULL;
929 bool brokenRun = false;
930 if (regenerateTextureCoords) {
joshualittb4c507e2015-04-08 08:07:59 -0700931 info.fBulkUseToken.reset();
joshualitt1d89e8d2015-04-01 12:40:54 -0700932 desc = run.fDescriptor.getDesc();
933 cache = SkGlyphCache::DetachCache(run.fTypeface, desc);
934 scaler = GrTextContext::GetGrFontScaler(cache);
935 strike = fFontCache->getStrike(scaler);
936 }
937 for (int glyphIdx = 0; glyphIdx < glyphCount; glyphIdx++) {
938 GrGlyph::PackedID glyphID = blob->fGlyphIDs[glyphIdx + info.fGlyphStartIndex];
939
940 if (regenerateTextureCoords) {
941 // Upload the glyph only if needed
942 GrGlyph* glyph = strike->getGlyph(glyphID, scaler);
943 SkASSERT(glyph);
944
945 if (!fFontCache->hasGlyph(glyph) &&
946 !strike->addGlyphToAtlas(batchTarget, glyph, scaler)) {
947 this->flush(batchTarget, &drawInfo, instancesToFlush,
948 maxInstancesPerDraw);
949 this->initDraw(batchTarget, gp, pipeline);
950 instancesToFlush = 0;
951 brokenRun = glyphIdx > 0;
952
953 SkDEBUGCODE(bool success =) strike->addGlyphToAtlas(batchTarget, glyph,
954 scaler);
955 SkASSERT(success);
956 }
joshualittb4c507e2015-04-08 08:07:59 -0700957 fFontCache->addGlyphToBulkAndSetUseToken(&info.fBulkUseToken, glyph,
958 batchTarget->currentToken());
joshualitt1d89e8d2015-04-01 12:40:54 -0700959
960 // Texture coords are the last vertex attribute so we get a pointer to the
961 // first one and then map with stride in regenerateTextureCoords
962 intptr_t vertex = reinterpret_cast<intptr_t>(blob->fVertices);
963 vertex += info.fVertexStartIndex;
964 vertex += vertexStride * glyphIdx * kVerticesPerGlyph;
965 vertex += vertexStride - sizeof(SkIPoint16);
966
967 this->regenerateTextureCoords(glyph, vertex, vertexStride);
968 }
969
970 if (regenerateColors) {
971 intptr_t vertex = reinterpret_cast<intptr_t>(blob->fVertices);
972 vertex += info.fVertexStartIndex;
973 vertex += vertexStride * glyphIdx * kVerticesPerGlyph + sizeof(SkPoint);
974 this->regenerateColors(vertex, vertexStride, args.fColor);
975 }
976
joshualitt2a0e9f32015-04-13 06:12:21 -0700977 if (regeneratePositions) {
978 intptr_t vertex = reinterpret_cast<intptr_t>(blob->fVertices);
979 vertex += info.fVertexStartIndex;
980 vertex += vertexStride * glyphIdx * kVerticesPerGlyph;
981 SkScalar transX = args.fTransX;
982 SkScalar transY = args.fTransY;
983 this->regeneratePositions(vertex, vertexStride, transX, transY);
984 }
joshualitt1d89e8d2015-04-01 12:40:54 -0700985 instancesToFlush++;
986 }
987
joshualitt2a0e9f32015-04-13 06:12:21 -0700988 // We my have changed the color so update it here
989 run.fColor = args.fColor;
joshualitt1d89e8d2015-04-01 12:40:54 -0700990 if (regenerateTextureCoords) {
991 SkGlyphCache::AttachCache(cache);
992 info.fAtlasGeneration = brokenRun ? GrBatchAtlas::kInvalidAtlasGeneration :
993 fFontCache->atlasGeneration(fMaskFormat);
994 }
995 } else {
996 instancesToFlush += glyphCount;
joshualittb4c507e2015-04-08 08:07:59 -0700997
998 // set use tokens for all of the glyphs in our subrun. This is only valid if we
999 // have a valid atlas generation
1000 fFontCache->setUseTokenBulk(info.fBulkUseToken,
1001 batchTarget->currentToken(),
1002 fMaskFormat);
joshualitt1d89e8d2015-04-01 12:40:54 -07001003 }
1004
1005 // now copy all vertices
1006 size_t byteCount = info.fVertexEndIndex - info.fVertexStartIndex;
1007 memcpy(currVertex, blob->fVertices + info.fVertexStartIndex, byteCount);
1008
1009 currVertex += byteCount;
1010 }
1011
1012 this->flush(batchTarget, &drawInfo, instancesToFlush, maxInstancesPerDraw);
1013 }
1014
joshualittad802c62015-04-15 05:31:57 -07001015 // The minimum number of Geometry we will try to allocate.
1016 static const int kMinAllocated = 32;
1017
1018 // Total number of Geometry this Batch owns
1019 int instanceCount() const { return fInstanceCount; }
1020 SkAutoSTMalloc<kMinAllocated, Geometry>* geoData() { return &fGeoData; }
1021
1022 // to avoid even the initial copy of the struct, we have a getter for the first item which
1023 // is used to seed the batch with its initial geometry. After seeding, the client should call
1024 // init() so the Batch can initialize itself
1025 Geometry& geometry() { return fGeoData[0]; }
1026 void init() {
1027 fBatch.fColor = fGeoData[0].fColor;
1028 fBatch.fViewMatrix = fGeoData[0].fBlob->fViewMatrix;
1029 }
joshualitt1d89e8d2015-04-01 12:40:54 -07001030
1031private:
joshualittad802c62015-04-15 05:31:57 -07001032 BitmapTextBatch(GrMaskFormat maskFormat,
joshualitt1d89e8d2015-04-01 12:40:54 -07001033 int glyphCount, GrBatchFontCache* fontCache)
1034 : fMaskFormat(maskFormat)
1035 , fPixelConfig(fontCache->getPixelConfig(maskFormat))
1036 , fFontCache(fontCache) {
1037 this->initClassID<BitmapTextBatch>();
joshualitt1d89e8d2015-04-01 12:40:54 -07001038 fBatch.fNumGlyphs = glyphCount;
joshualittad802c62015-04-15 05:31:57 -07001039 fInstanceCount = 1;
1040 fAllocatedCount = kMinAllocated;
1041 }
1042
1043 ~BitmapTextBatch() {
1044 for (int i = 0; i < fInstanceCount; i++) {
1045 fGeoData[i].fBlob->unref();
1046 }
joshualitt1d89e8d2015-04-01 12:40:54 -07001047 }
1048
1049 void regenerateTextureCoords(GrGlyph* glyph, intptr_t vertex, size_t vertexStride) {
1050 int width = glyph->fBounds.width();
1051 int height = glyph->fBounds.height();
1052 int u0 = glyph->fAtlasLocation.fX;
1053 int v0 = glyph->fAtlasLocation.fY;
1054 int u1 = u0 + width;
1055 int v1 = v0 + height;
1056
1057 // we assume texture coords are the last vertex attribute, this is a bit fragile.
1058 // TODO pass in this offset or something
1059 SkIPoint16* textureCoords;
1060 // V0
1061 textureCoords = reinterpret_cast<SkIPoint16*>(vertex);
1062 textureCoords->set(u0, v0);
1063 vertex += vertexStride;
1064
1065 // V1
1066 textureCoords = reinterpret_cast<SkIPoint16*>(vertex);
1067 textureCoords->set(u0, v1);
1068 vertex += vertexStride;
1069
1070 // V2
1071 textureCoords = reinterpret_cast<SkIPoint16*>(vertex);
1072 textureCoords->set(u1, v1);
1073 vertex += vertexStride;
1074
1075 // V3
1076 textureCoords = reinterpret_cast<SkIPoint16*>(vertex);
1077 textureCoords->set(u1, v0);
1078 }
1079
1080 void regenerateColors(intptr_t vertex, size_t vertexStride, GrColor color) {
1081 for (int i = 0; i < kVerticesPerGlyph; i++) {
1082 SkColor* vcolor = reinterpret_cast<SkColor*>(vertex);
1083 *vcolor = color;
1084 vertex += vertexStride;
1085 }
1086 }
1087
joshualitt2a0e9f32015-04-13 06:12:21 -07001088 void regeneratePositions(intptr_t vertex, size_t vertexStride, SkScalar transX,
1089 SkScalar transY) {
1090 for (int i = 0; i < kVerticesPerGlyph; i++) {
1091 SkPoint* point = reinterpret_cast<SkPoint*>(vertex);
1092 point->fX += transX;
1093 point->fY += transY;
1094 vertex += vertexStride;
1095 }
1096 }
1097
joshualitt1d89e8d2015-04-01 12:40:54 -07001098 void initDraw(GrBatchTarget* batchTarget,
1099 const GrGeometryProcessor* gp,
1100 const GrPipeline* pipeline) {
1101 batchTarget->initDraw(gp, pipeline);
1102
1103 // TODO remove this when batch is everywhere
1104 GrPipelineInfo init;
1105 init.fColorIgnored = fBatch.fColorIgnored;
1106 init.fOverrideColor = GrColor_ILLEGAL;
1107 init.fCoverageIgnored = fBatch.fCoverageIgnored;
1108 init.fUsesLocalCoords = this->usesLocalCoords();
1109 gp->initBatchTracker(batchTarget->currentBatchTracker(), init);
1110 }
1111
1112 void flush(GrBatchTarget* batchTarget,
1113 GrDrawTarget::DrawInfo* drawInfo,
1114 int instanceCount,
1115 int maxInstancesPerDraw) {
1116 while (instanceCount) {
1117 drawInfo->setInstanceCount(SkTMin(instanceCount, maxInstancesPerDraw));
1118 drawInfo->setVertexCount(drawInfo->instanceCount() * drawInfo->verticesPerInstance());
1119 drawInfo->setIndexCount(drawInfo->instanceCount() * drawInfo->indicesPerInstance());
1120
1121 batchTarget->draw(*drawInfo);
1122
1123 drawInfo->setStartVertex(drawInfo->startVertex() + drawInfo->vertexCount());
1124 instanceCount -= drawInfo->instanceCount();
1125 }
1126 }
1127
1128 GrColor color() const { return fBatch.fColor; }
1129 const SkMatrix& viewMatrix() const { return fBatch.fViewMatrix; }
1130 bool usesLocalCoords() const { return fBatch.fUsesLocalCoords; }
1131 int numGlyphs() const { return fBatch.fNumGlyphs; }
1132
1133 bool onCombineIfPossible(GrBatch* t) override {
1134 BitmapTextBatch* that = t->cast<BitmapTextBatch>();
1135
1136 if (this->fMaskFormat != that->fMaskFormat) {
1137 return false;
1138 }
1139
1140 if (this->fMaskFormat != kA8_GrMaskFormat && this->color() != that->color()) {
1141 return false;
1142 }
1143
1144 if (this->usesLocalCoords() && !this->viewMatrix().cheapEqualTo(that->viewMatrix())) {
1145 return false;
1146 }
1147
1148 fBatch.fNumGlyphs += that->numGlyphs();
joshualittad802c62015-04-15 05:31:57 -07001149
1150 // copy that->geoData(). We do this manually for performance reasons
1151 SkAutoSTMalloc<kMinAllocated, Geometry>* otherGeoData = that->geoData();
1152 int otherInstanceCount = that->instanceCount();
1153 int allocSize = otherInstanceCount + fInstanceCount;
1154 if (allocSize > fAllocatedCount) {
1155 while (allocSize > fAllocatedCount) {
1156 fAllocatedCount = fAllocatedCount << 1;
1157 }
1158 fGeoData.realloc(fAllocatedCount);
1159 }
1160
1161 memcpy(&fGeoData[fInstanceCount], otherGeoData->get(),
1162 otherInstanceCount * sizeof(Geometry));
1163 int total = fInstanceCount + otherInstanceCount;
1164 for (int i = fInstanceCount; i < total; i++) {
1165 fGeoData[i].fBlob->ref();
1166 }
1167 fInstanceCount = total;
joshualitt1d89e8d2015-04-01 12:40:54 -07001168 return true;
1169 }
1170
1171 struct BatchTracker {
1172 GrColor fColor;
1173 SkMatrix fViewMatrix;
1174 bool fUsesLocalCoords;
1175 bool fColorIgnored;
1176 bool fCoverageIgnored;
1177 int fNumGlyphs;
1178 };
1179
1180 BatchTracker fBatch;
joshualittad802c62015-04-15 05:31:57 -07001181 SkAutoSTMalloc<kMinAllocated, Geometry> fGeoData;
1182 int fInstanceCount;
1183 int fAllocatedCount;
joshualitt1d89e8d2015-04-01 12:40:54 -07001184 GrMaskFormat fMaskFormat;
1185 GrPixelConfig fPixelConfig;
1186 GrBatchFontCache* fFontCache;
1187};
1188
joshualitt9a27e632015-04-06 10:53:36 -07001189void GrAtlasTextContext::flushRunAsPaths(const SkTextBlob::RunIterator& it, const SkPaint& skPaint,
1190 SkDrawFilter* drawFilter, const SkMatrix& viewMatrix,
1191 const SkIRect& clipBounds, SkScalar x, SkScalar y) {
1192 SkPaint runPaint = skPaint;
joshualitt1d89e8d2015-04-01 12:40:54 -07001193
joshualitt9a27e632015-04-06 10:53:36 -07001194 size_t textLen = it.glyphCount() * sizeof(uint16_t);
1195 const SkPoint& offset = it.offset();
joshualitt1d89e8d2015-04-01 12:40:54 -07001196
joshualitt9a27e632015-04-06 10:53:36 -07001197 it.applyFontToPaint(&runPaint);
joshualitt1d89e8d2015-04-01 12:40:54 -07001198
joshualitt9a27e632015-04-06 10:53:36 -07001199 if (drawFilter && !drawFilter->filter(&runPaint, SkDrawFilter::kText_Type)) {
1200 return;
joshualitt1d89e8d2015-04-01 12:40:54 -07001201 }
1202
joshualitt9a27e632015-04-06 10:53:36 -07001203 runPaint.setFlags(fGpuDevice->filterTextFlags(runPaint));
1204
1205 switch (it.positioning()) {
1206 case SkTextBlob::kDefault_Positioning:
1207 this->drawTextAsPath(runPaint, viewMatrix, (const char *)it.glyphs(),
1208 textLen, x + offset.x(), y + offset.y(), clipBounds);
1209 break;
1210 case SkTextBlob::kHorizontal_Positioning:
1211 this->drawPosTextAsPath(runPaint, viewMatrix, (const char*)it.glyphs(),
1212 textLen, it.pos(), 1, SkPoint::Make(x, y + offset.y()),
1213 clipBounds);
1214 break;
1215 case SkTextBlob::kFull_Positioning:
1216 this->drawPosTextAsPath(runPaint, viewMatrix, (const char*)it.glyphs(),
1217 textLen, it.pos(), 2, SkPoint::Make(x, y), clipBounds);
1218 break;
1219 }
1220}
1221
1222inline void GrAtlasTextContext::flushRun(GrDrawTarget* target, GrPipelineBuilder* pipelineBuilder,
1223 BitmapTextBlob* cacheBlob, int run, GrColor color,
joshualitt2a0e9f32015-04-13 06:12:21 -07001224 uint8_t paintAlpha, SkScalar transX, SkScalar transY) {
joshualitt9a27e632015-04-06 10:53:36 -07001225 for (int subRun = 0; subRun < cacheBlob->fRuns[run].fSubRunInfo.count(); subRun++) {
1226 const PerSubRunInfo& info = cacheBlob->fRuns[run].fSubRunInfo[subRun];
1227 int glyphCount = info.fGlyphEndIndex - info.fGlyphStartIndex;
1228 if (0 == glyphCount) {
1229 continue;
1230 }
1231
1232 GrMaskFormat format = info.fMaskFormat;
1233 GrColor subRunColor = kARGB_GrMaskFormat == format ?
1234 SkColorSetARGB(paintAlpha, paintAlpha, paintAlpha, paintAlpha) :
1235 color;
1236
joshualittad802c62015-04-15 05:31:57 -07001237 SkAutoTUnref<BitmapTextBatch> batch(BitmapTextBatch::Create(format, glyphCount,
1238 fContext->getBatchFontCache()));
1239 BitmapTextBatch::Geometry& geometry = batch->geometry();
1240 geometry.fBlob = SkRef(cacheBlob);
joshualitt9a27e632015-04-06 10:53:36 -07001241 geometry.fRun = run;
1242 geometry.fSubRun = subRun;
1243 geometry.fColor = subRunColor;
joshualitt2a0e9f32015-04-13 06:12:21 -07001244 geometry.fTransX = transX;
1245 geometry.fTransY = transY;
joshualittad802c62015-04-15 05:31:57 -07001246 batch->init();
joshualitt9a27e632015-04-06 10:53:36 -07001247
1248 target->drawBatch(pipelineBuilder, batch, &cacheBlob->fRuns[run].fVertexBounds);
1249 }
1250}
1251
1252inline void GrAtlasTextContext::flushBigGlyphs(BitmapTextBlob* cacheBlob, GrRenderTarget* rt,
joshualitt2a0e9f32015-04-13 06:12:21 -07001253 const GrPaint& grPaint, const GrClip& clip,
1254 SkScalar transX, SkScalar transY) {
joshualitt9a27e632015-04-06 10:53:36 -07001255 for (int i = 0; i < cacheBlob->fBigGlyphs.count(); i++) {
joshualitt2a0e9f32015-04-13 06:12:21 -07001256 BitmapTextBlob::BigGlyph& bigGlyph = cacheBlob->fBigGlyphs[i];
1257 bigGlyph.fVx += SkScalarTruncToInt(transX);
1258 bigGlyph.fVy += SkScalarTruncToInt(transY);
joshualitt1d89e8d2015-04-01 12:40:54 -07001259 SkMatrix translate;
joshualitt2a0e9f32015-04-13 06:12:21 -07001260 translate.setTranslate(SkIntToScalar(bigGlyph.fVx),
1261 SkIntToScalar(bigGlyph.fVy));
joshualitt1d89e8d2015-04-01 12:40:54 -07001262 SkPath tmpPath(bigGlyph.fPath);
1263 tmpPath.transform(translate);
1264 GrStrokeInfo strokeInfo(SkStrokeRec::kFill_InitStyle);
joshualitt9a27e632015-04-06 10:53:36 -07001265 fContext->drawPath(rt, clip, grPaint, SkMatrix::I(), tmpPath, strokeInfo);
joshualitt1d89e8d2015-04-01 12:40:54 -07001266 }
1267}
joshualitt9a27e632015-04-06 10:53:36 -07001268
1269void GrAtlasTextContext::flush(GrDrawTarget* target,
1270 const SkTextBlob* blob,
1271 BitmapTextBlob* cacheBlob,
1272 GrRenderTarget* rt,
1273 const SkPaint& skPaint,
1274 const GrPaint& grPaint,
1275 SkDrawFilter* drawFilter,
1276 const GrClip& clip,
1277 const SkMatrix& viewMatrix,
1278 const SkIRect& clipBounds,
joshualitt2a0e9f32015-04-13 06:12:21 -07001279 SkScalar x, SkScalar y,
1280 SkScalar transX, SkScalar transY) {
joshualitt9a27e632015-04-06 10:53:36 -07001281 // We loop through the runs of the blob, flushing each. If any run is too large, then we flush
1282 // it as paths
1283 GrPipelineBuilder pipelineBuilder;
1284 pipelineBuilder.setFromPaint(grPaint, rt, clip);
1285
1286 GrColor color = grPaint.getColor();
1287 uint8_t paintAlpha = skPaint.getAlpha();
1288
1289 SkTextBlob::RunIterator it(blob);
1290 for (int run = 0; !it.done(); it.next(), run++) {
1291 if (cacheBlob->fRuns[run].fDrawAsPaths) {
1292 this->flushRunAsPaths(it, skPaint, drawFilter, viewMatrix, clipBounds, x, y);
1293 continue;
1294 }
joshualitt2a0e9f32015-04-13 06:12:21 -07001295 cacheBlob->fRuns[run].fVertexBounds.offset(transX, transY);
1296 this->flushRun(target, &pipelineBuilder, cacheBlob, run, color, paintAlpha, transX, transY);
joshualitt9a27e632015-04-06 10:53:36 -07001297 }
1298
1299 // Now flush big glyphs
joshualitt2a0e9f32015-04-13 06:12:21 -07001300 this->flushBigGlyphs(cacheBlob, rt, grPaint, clip, transX, transY);
joshualitt9a27e632015-04-06 10:53:36 -07001301}
1302
1303void GrAtlasTextContext::flush(GrDrawTarget* target,
1304 BitmapTextBlob* cacheBlob,
1305 GrRenderTarget* rt,
1306 const SkPaint& skPaint,
1307 const GrPaint& grPaint,
1308 const GrClip& clip,
1309 const SkMatrix& viewMatrix) {
1310 GrPipelineBuilder pipelineBuilder;
1311 pipelineBuilder.setFromPaint(grPaint, rt, clip);
1312
1313 GrColor color = grPaint.getColor();
1314 uint8_t paintAlpha = skPaint.getAlpha();
1315 for (int run = 0; run < cacheBlob->fRunCount; run++) {
joshualitt2a0e9f32015-04-13 06:12:21 -07001316 this->flushRun(target, &pipelineBuilder, cacheBlob, run, color, paintAlpha, 0, 0);
joshualitt9a27e632015-04-06 10:53:36 -07001317 }
1318
1319 // Now flush big glyphs
joshualitt2a0e9f32015-04-13 06:12:21 -07001320 this->flushBigGlyphs(cacheBlob, rt, grPaint, clip, 0, 0);
joshualitt9a27e632015-04-06 10:53:36 -07001321}