blob: c22807d396390f344272fefb828ffb81b0d6ec8b [file] [log] [blame]
jvanverth@google.comd830d132013-11-11 20:54:09 +00001/*
2 * Copyright 2013 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "GrDistanceFieldTextContext.h"
9#include "GrAtlas.h"
10#include "GrDrawTarget.h"
commit-bot@chromium.org6c89c342014-02-14 21:48:29 +000011#include "GrDrawTargetCaps.h"
jvanverth@google.comd830d132013-11-11 20:54:09 +000012#include "GrFontScaler.h"
commit-bot@chromium.org8128d8c2013-12-19 16:12:25 +000013#include "SkGlyphCache.h"
jvanverth@google.comd830d132013-11-11 20:54:09 +000014#include "GrIndexBuffer.h"
15#include "GrTextStrike.h"
16#include "GrTextStrike_impl.h"
commit-bot@chromium.org9f94b912014-01-30 15:22:54 +000017#include "SkDraw.h"
commit-bot@chromium.orge8612d92014-01-28 22:02:07 +000018#include "SkGpuDevice.h"
jvanverth@google.comd830d132013-11-11 20:54:09 +000019#include "SkPath.h"
20#include "SkRTConf.h"
21#include "SkStrokeRec.h"
22#include "effects/GrDistanceFieldTextureEffect.h"
23
24static const int kGlyphCoordsAttributeIndex = 1;
25
commit-bot@chromium.org8128d8c2013-12-19 16:12:25 +000026static const int kBaseDFFontSize = 32;
27
jvanverth@google.comd830d132013-11-11 20:54:09 +000028SK_CONF_DECLARE(bool, c_DumpFontCache, "gpu.dumpFontCache", false,
29 "Dump the contents of the font cache before every purge.");
30
skia.committer@gmail.come5d70152014-01-29 07:01:48 +000031GrDistanceFieldTextContext::GrDistanceFieldTextContext(GrContext* context,
commit-bot@chromium.orge8612d92014-01-28 22:02:07 +000032 const SkDeviceProperties& properties)
commit-bot@chromium.orgcbbc4812014-01-30 22:05:47 +000033 : GrTextContext(context, properties) {
jvanverth@google.comd830d132013-11-11 20:54:09 +000034 fStrike = NULL;
35
36 fCurrTexture = NULL;
37 fCurrVertex = 0;
38
39 fVertices = NULL;
40 fMaxVertices = 0;
41}
42
43GrDistanceFieldTextContext::~GrDistanceFieldTextContext() {
44 this->flushGlyphs();
45}
46
commit-bot@chromium.orgcbbc4812014-01-30 22:05:47 +000047bool GrDistanceFieldTextContext::canDraw(const SkPaint& paint) {
48 return !paint.getRasterizer() && !paint.getMaskFilter() &&
49 paint.getStyle() == SkPaint::kFill_Style &&
commit-bot@chromium.org6c89c342014-02-14 21:48:29 +000050 fContext->getTextTarget()->caps()->shaderDerivativeSupport() &&
commit-bot@chromium.orgcbbc4812014-01-30 22:05:47 +000051 !SkDraw::ShouldDrawTextAsPaths(paint, fContext->getMatrix());
52}
53
jvanverth@google.comd830d132013-11-11 20:54:09 +000054static inline GrColor skcolor_to_grcolor_nopremultiply(SkColor c) {
55 unsigned r = SkColorGetR(c);
56 unsigned g = SkColorGetG(c);
57 unsigned b = SkColorGetB(c);
58 return GrColorPackRGBA(r, g, b, 0xff);
59}
60
61void GrDistanceFieldTextContext::flushGlyphs() {
62 if (NULL == fDrawTarget) {
63 return;
64 }
65
66 GrDrawState* drawState = fDrawTarget->drawState();
67 GrDrawState::AutoRestoreEffects are(drawState);
68 drawState->setFromPaint(fPaint, fContext->getMatrix(), fContext->getRenderTarget());
69
70 if (fCurrVertex > 0) {
71 // setup our sampler state for our text texture/atlas
72 SkASSERT(GrIsALIGN4(fCurrVertex));
73 SkASSERT(fCurrTexture);
74 GrTextureParams params(SkShader::kRepeat_TileMode, GrTextureParams::kBilerp_FilterMode);
75
76 // This effect could be stored with one of the cache objects (atlas?)
commit-bot@chromium.org6c89c342014-02-14 21:48:29 +000077 SkISize size = fStrike->getAtlasSize();
jvanverth@google.comd830d132013-11-11 20:54:09 +000078 drawState->addCoverageEffect(
commit-bot@chromium.org6c89c342014-02-14 21:48:29 +000079 GrDistanceFieldTextureEffect::Create(fCurrTexture, params, size),
jvanverth@google.comd830d132013-11-11 20:54:09 +000080 kGlyphCoordsAttributeIndex)->unref();
81
82 if (!GrPixelConfigIsAlphaOnly(fCurrTexture->config())) {
83 if (kOne_GrBlendCoeff != fPaint.getSrcBlendCoeff() ||
84 kISA_GrBlendCoeff != fPaint.getDstBlendCoeff() ||
85 fPaint.numColorStages()) {
86 GrPrintf("LCD Text will not draw correctly.\n");
87 }
88 // We don't use the GrPaint's color in this case because it's been premultiplied by
89 // alpha. Instead we feed in a non-premultiplied color, and multiply its alpha by
90 // the mask texture color. The end result is that we get
91 // mask*paintAlpha*paintColor + (1-mask*paintAlpha)*dstColor
commit-bot@chromium.org8128d8c2013-12-19 16:12:25 +000092 int a = SkColorGetA(fSkPaint.getColor());
jvanverth@google.comd830d132013-11-11 20:54:09 +000093 // paintAlpha
94 drawState->setColor(SkColorSetARGB(a, a, a, a));
95 // paintColor
commit-bot@chromium.org8128d8c2013-12-19 16:12:25 +000096 drawState->setBlendConstant(skcolor_to_grcolor_nopremultiply(fSkPaint.getColor()));
jvanverth@google.comd830d132013-11-11 20:54:09 +000097 drawState->setBlendFunc(kConstC_GrBlendCoeff, kISC_GrBlendCoeff);
98 } else {
99 // set back to normal in case we took LCD path previously.
100 drawState->setBlendFunc(fPaint.getSrcBlendCoeff(), fPaint.getDstBlendCoeff());
101 drawState->setColor(fPaint.getColor());
102 }
103
104 int nGlyphs = fCurrVertex / 4;
105 fDrawTarget->setIndexSourceToBuffer(fContext->getQuadIndexBuffer());
106 fDrawTarget->drawIndexedInstances(kTriangles_GrPrimitiveType,
107 nGlyphs,
108 4, 6);
109 fDrawTarget->resetVertexSource();
110 fVertices = NULL;
111 fMaxVertices = 0;
112 fCurrVertex = 0;
113 SkSafeSetNull(fCurrTexture);
114 }
115}
116
117namespace {
118
119// position + texture coord
120extern const GrVertexAttrib gTextVertexAttribs[] = {
121 {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding},
122 {kVec2f_GrVertexAttribType, sizeof(GrPoint), kEffect_GrVertexAttribBinding}
123};
124
125};
126
127void GrDistanceFieldTextContext::drawPackedGlyph(GrGlyph::PackedID packed,
128 GrFixed vx, GrFixed vy,
129 GrFontScaler* scaler) {
130 if (NULL == fDrawTarget) {
131 return;
132 }
133 if (NULL == fStrike) {
134 fStrike = fContext->getFontCache()->getStrike(scaler, true);
135 }
136
137 GrGlyph* glyph = fStrike->getGlyph(packed, scaler);
138 if (NULL == glyph || glyph->fBounds.isEmpty()) {
139 return;
140 }
141
142 SkScalar sx = SkFixedToScalar(vx);
143 SkScalar sy = SkFixedToScalar(vy);
144/*
145 // not valid, need to find a different solution for this
146 vx += SkIntToFixed(glyph->fBounds.fLeft);
147 vy += SkIntToFixed(glyph->fBounds.fTop);
skia.committer@gmail.com11a253b2013-11-12 07:02:05 +0000148
jvanverth@google.comd830d132013-11-11 20:54:09 +0000149 // keep them as ints until we've done the clip-test
150 GrFixed width = glyph->fBounds.width();
151 GrFixed height = glyph->fBounds.height();
152
153 // check if we clipped out
154 if (true || NULL == glyph->fPlot) {
155 int x = vx >> 16;
156 int y = vy >> 16;
157 if (fClipRect.quickReject(x, y, x + width, y + height)) {
158// SkCLZ(3); // so we can set a break-point in the debugger
159 return;
160 }
161 }
162*/
163 if (NULL == glyph->fPlot) {
164 if (fStrike->getGlyphAtlas(glyph, scaler)) {
165 goto HAS_ATLAS;
166 }
167
168 // try to clear out an unused plot before we flush
169 fContext->getFontCache()->freePlotExceptFor(fStrike);
170 if (fStrike->getGlyphAtlas(glyph, scaler)) {
171 goto HAS_ATLAS;
172 }
173
174 if (c_DumpFontCache) {
175#ifdef SK_DEVELOPER
176 fContext->getFontCache()->dump();
177#endif
178 }
179
180 // before we purge the cache, we must flush any accumulated draws
181 this->flushGlyphs();
182 fContext->flush();
183
184 // try to purge
185 fContext->getFontCache()->purgeExceptFor(fStrike);
186 // need to use new flush count here
187 if (fStrike->getGlyphAtlas(glyph, scaler)) {
188 goto HAS_ATLAS;
189 }
190
191 if (NULL == glyph->fPath) {
192 SkPath* path = SkNEW(SkPath);
193 if (!scaler->getGlyphPath(glyph->glyphID(), path)) {
194 // flag the glyph as being dead?
195 delete path;
196 return;
197 }
198 glyph->fPath = path;
199 }
200
201 GrContext::AutoMatrix am;
202 SkMatrix translate;
203 translate.setTranslate(sx, sy);
204 GrPaint tmpPaint(fPaint);
205 am.setPreConcat(fContext, translate, &tmpPaint);
206 SkStrokeRec stroke(SkStrokeRec::kFill_InitStyle);
207 fContext->drawPath(tmpPaint, *glyph->fPath, stroke);
208 return;
209 }
210
211HAS_ATLAS:
212 SkASSERT(glyph->fPlot);
213 GrDrawTarget::DrawToken drawToken = fDrawTarget->getCurrentDrawToken();
214 glyph->fPlot->setDrawToken(drawToken);
215
216 GrTexture* texture = glyph->fPlot->texture();
217 SkASSERT(texture);
218
219 if (fCurrTexture != texture || fCurrVertex + 4 > fMaxVertices) {
220 this->flushGlyphs();
221 fCurrTexture = texture;
222 fCurrTexture->ref();
223 }
224
225 if (NULL == fVertices) {
226 // If we need to reserve vertices allow the draw target to suggest
227 // a number of verts to reserve and whether to perform a flush.
228 fMaxVertices = kMinRequestedVerts;
229 fDrawTarget->drawState()->setVertexAttribs<gTextVertexAttribs>(
230 SK_ARRAY_COUNT(gTextVertexAttribs));
231 bool flush = fDrawTarget->geometryHints(&fMaxVertices, NULL);
232 if (flush) {
233 this->flushGlyphs();
234 fContext->flush();
235 fDrawTarget->drawState()->setVertexAttribs<gTextVertexAttribs>(
236 SK_ARRAY_COUNT(gTextVertexAttribs));
237 }
238 fMaxVertices = kDefaultRequestedVerts;
239 // ignore return, no point in flushing again.
240 fDrawTarget->geometryHints(&fMaxVertices, NULL);
241
242 int maxQuadVertices = 4 * fContext->getQuadIndexBuffer()->maxQuads();
243 if (fMaxVertices < kMinRequestedVerts) {
244 fMaxVertices = kDefaultRequestedVerts;
245 } else if (fMaxVertices > maxQuadVertices) {
246 // don't exceed the limit of the index buffer
247 fMaxVertices = maxQuadVertices;
248 }
249 bool success = fDrawTarget->reserveVertexAndIndexSpace(fMaxVertices,
250 0,
251 GrTCast<void**>(&fVertices),
252 NULL);
253 GrAlwaysAssert(success);
254 SkASSERT(2*sizeof(GrPoint) == fDrawTarget->getDrawState().getVertexSize());
255 }
256
257 SkScalar dx = SkIntToScalar(glyph->fBounds.fLeft);
258 SkScalar dy = SkIntToScalar(glyph->fBounds.fTop);
259 SkScalar width = SkIntToScalar(glyph->fBounds.width());
260 SkScalar height = SkIntToScalar(glyph->fBounds.height());
261
262 SkScalar scale = fTextRatio;
263 dx *= scale;
264 dy *= scale;
265 sx += dx;
266 sy += dy;
267 width *= scale;
268 height *= scale;
skia.committer@gmail.coma3b53272014-02-15 03:02:15 +0000269
jvanverth@google.comd830d132013-11-11 20:54:09 +0000270 GrFixed tx = SkIntToFixed(glyph->fAtlasLocation.fX);
271 GrFixed ty = SkIntToFixed(glyph->fAtlasLocation.fY);
272 GrFixed tw = SkIntToFixed(glyph->fBounds.width());
273 GrFixed th = SkIntToFixed(glyph->fBounds.height());
274
commit-bot@chromium.org6c89c342014-02-14 21:48:29 +0000275 static const size_t kVertexSize = 2 * sizeof(SkPoint);
jvanverth@google.comd830d132013-11-11 20:54:09 +0000276 fVertices[2*fCurrVertex].setRectFan(sx,
277 sy,
278 sx + width,
279 sy + height,
commit-bot@chromium.org6c89c342014-02-14 21:48:29 +0000280 kVertexSize);
jvanverth@google.comd830d132013-11-11 20:54:09 +0000281 fVertices[2*fCurrVertex+1].setRectFan(SkFixedToFloat(texture->normalizeFixedX(tx)),
282 SkFixedToFloat(texture->normalizeFixedY(ty)),
283 SkFixedToFloat(texture->normalizeFixedX(tx + tw)),
284 SkFixedToFloat(texture->normalizeFixedY(ty + th)),
commit-bot@chromium.org6c89c342014-02-14 21:48:29 +0000285 kVertexSize);
jvanverth@google.comd830d132013-11-11 20:54:09 +0000286 fCurrVertex += 4;
287}
commit-bot@chromium.org8128d8c2013-12-19 16:12:25 +0000288
commit-bot@chromium.orgcbbc4812014-01-30 22:05:47 +0000289inline void GrDistanceFieldTextContext::init(const GrPaint& paint, const SkPaint& skPaint) {
290 GrTextContext::init(paint, skPaint);
291
292 fStrike = NULL;
293
294 fCurrTexture = NULL;
295 fCurrVertex = 0;
296
297 fVertices = NULL;
298 fMaxVertices = 0;
299
300 fTextRatio = fSkPaint.getTextSize()/kBaseDFFontSize;
skia.committer@gmail.com4c18e9f2014-01-31 03:01:59 +0000301
commit-bot@chromium.orgcbbc4812014-01-30 22:05:47 +0000302 fSkPaint.setTextSize(SkIntToScalar(kBaseDFFontSize));
303 fSkPaint.setLCDRenderText(false);
304 fSkPaint.setAutohinted(false);
305 fSkPaint.setSubpixelText(false);
306}
307
308inline void GrDistanceFieldTextContext::finish() {
309 flushGlyphs();
310
311 GrTextContext::finish();
312}
313
314void GrDistanceFieldTextContext::drawText(const GrPaint& paint, const SkPaint& skPaint,
315 const char text[], size_t byteLength,
commit-bot@chromium.orge8612d92014-01-28 22:02:07 +0000316 SkScalar x, SkScalar y) {
commit-bot@chromium.org8128d8c2013-12-19 16:12:25 +0000317 SkASSERT(byteLength == 0 || text != NULL);
318
commit-bot@chromium.orge8612d92014-01-28 22:02:07 +0000319 // nothing to draw or can't draw
320 if (text == NULL || byteLength == 0 /* no raster clip? || fRC->isEmpty()*/
321 || fSkPaint.getRasterizer()) {
commit-bot@chromium.org8128d8c2013-12-19 16:12:25 +0000322 return;
323 }
skia.committer@gmail.come5d70152014-01-29 07:01:48 +0000324
commit-bot@chromium.orgcbbc4812014-01-30 22:05:47 +0000325 this->init(paint, skPaint);
326
commit-bot@chromium.org8128d8c2013-12-19 16:12:25 +0000327 SkScalar sizeRatio = fTextRatio;
328
329 SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
330
commit-bot@chromium.orge8612d92014-01-28 22:02:07 +0000331 SkAutoGlyphCache autoCache(fSkPaint, &fDeviceProperties, NULL);
332 SkGlyphCache* cache = autoCache.getCache();
333 GrFontScaler* fontScaler = GetGrFontScaler(cache);
skia.committer@gmail.come5d70152014-01-29 07:01:48 +0000334
commit-bot@chromium.org8128d8c2013-12-19 16:12:25 +0000335 // need to measure first
336 // TODO - generate positions and pre-load cache as well?
337 const char* stop = text + byteLength;
338 if (fSkPaint.getTextAlign() != SkPaint::kLeft_Align) {
339 SkFixed stopX = 0;
340 SkFixed stopY = 0;
341
342 const char* textPtr = text;
343 while (textPtr < stop) {
344 // don't need x, y here, since all subpixel variants will have the
345 // same advance
346 const SkGlyph& glyph = glyphCacheProc(cache, &textPtr, 0, 0);
347
348 stopX += glyph.fAdvanceX;
349 stopY += glyph.fAdvanceY;
350 }
351 SkASSERT(textPtr == stop);
352
353 SkScalar alignX = SkFixedToScalar(stopX)*sizeRatio;
354 SkScalar alignY = SkFixedToScalar(stopY)*sizeRatio;
355
356 if (fSkPaint.getTextAlign() == SkPaint::kCenter_Align) {
357 alignX = SkScalarHalf(alignX);
358 alignY = SkScalarHalf(alignY);
359 }
360
361 x -= alignX;
362 y -= alignY;
363 }
364
365 SkFixed fx = SkScalarToFixed(x) + SK_FixedHalf;
366 SkFixed fy = SkScalarToFixed(y) + SK_FixedHalf;
367 SkFixed fixedScale = SkScalarToFixed(sizeRatio);
368 while (text < stop) {
369 const SkGlyph& glyph = glyphCacheProc(cache, &text, fx, fy);
370
371 if (glyph.fWidth) {
372 this->drawPackedGlyph(GrGlyph::Pack(glyph.getGlyphID(),
373 glyph.getSubXFixed(),
374 glyph.getSubYFixed()),
375 SkFixedFloorToFixed(fx),
376 SkFixedFloorToFixed(fy),
377 fontScaler);
378 }
379
380 fx += SkFixedMul_portable(glyph.fAdvanceX, fixedScale);
381 fy += SkFixedMul_portable(glyph.fAdvanceY, fixedScale);
382 }
commit-bot@chromium.orgcbbc4812014-01-30 22:05:47 +0000383
384 this->finish();
commit-bot@chromium.org8128d8c2013-12-19 16:12:25 +0000385}
386
commit-bot@chromium.orgcbbc4812014-01-30 22:05:47 +0000387void GrDistanceFieldTextContext::drawPosText(const GrPaint& paint, const SkPaint& skPaint,
388 const char text[], size_t byteLength,
commit-bot@chromium.org8128d8c2013-12-19 16:12:25 +0000389 const SkScalar pos[], SkScalar constY,
commit-bot@chromium.orge8612d92014-01-28 22:02:07 +0000390 int scalarsPerPosition) {
commit-bot@chromium.org8128d8c2013-12-19 16:12:25 +0000391
392 SkASSERT(byteLength == 0 || text != NULL);
393 SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition);
394
395 // nothing to draw
commit-bot@chromium.orgcbbc4812014-01-30 22:05:47 +0000396 if (text == NULL || byteLength == 0 /* no raster clip? || fRC->isEmpty()*/) {
commit-bot@chromium.org8128d8c2013-12-19 16:12:25 +0000397 return;
398 }
399
commit-bot@chromium.orgcbbc4812014-01-30 22:05:47 +0000400 this->init(paint, skPaint);
401
commit-bot@chromium.org8128d8c2013-12-19 16:12:25 +0000402 SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
403
commit-bot@chromium.orge8612d92014-01-28 22:02:07 +0000404 SkAutoGlyphCache autoCache(fSkPaint, &fDeviceProperties, NULL);
405 SkGlyphCache* cache = autoCache.getCache();
406 GrFontScaler* fontScaler = GetGrFontScaler(cache);
skia.committer@gmail.come5d70152014-01-29 07:01:48 +0000407
commit-bot@chromium.org8128d8c2013-12-19 16:12:25 +0000408 const char* stop = text + byteLength;
409
410 if (SkPaint::kLeft_Align == fSkPaint.getTextAlign()) {
411 while (text < stop) {
412 // the last 2 parameters are ignored
413 const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
414
415 if (glyph.fWidth) {
416 SkScalar x = pos[0];
417 SkScalar y = scalarsPerPosition == 1 ? constY : pos[1];
418
419 this->drawPackedGlyph(GrGlyph::Pack(glyph.getGlyphID(),
420 glyph.getSubXFixed(),
421 glyph.getSubYFixed()),
422 SkScalarToFixed(x) + SK_FixedHalf, //d1g.fHalfSampleX,
423 SkScalarToFixed(y) + SK_FixedHalf, //d1g.fHalfSampleY,
424 fontScaler);
425 }
426 pos += scalarsPerPosition;
427 }
428 } else {
429 int alignShift = SkPaint::kCenter_Align == fSkPaint.getTextAlign() ? 1 : 0;
430 while (text < stop) {
431 // the last 2 parameters are ignored
432 const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
433
434 if (glyph.fWidth) {
435 SkScalar x = pos[0];
436 SkScalar y = scalarsPerPosition == 1 ? constY : pos[1];
skia.committer@gmail.com22e96722013-12-20 07:01:36 +0000437
commit-bot@chromium.org8128d8c2013-12-19 16:12:25 +0000438 this->drawPackedGlyph(GrGlyph::Pack(glyph.getGlyphID(),
439 glyph.getSubXFixed(),
440 glyph.getSubYFixed()),
441 SkScalarToFixed(x) - (glyph.fAdvanceX >> alignShift)
442 + SK_FixedHalf, //d1g.fHalfSampleX,
443 SkScalarToFixed(y) - (glyph.fAdvanceY >> alignShift)
444 + SK_FixedHalf, //d1g.fHalfSampleY,
445 fontScaler);
446 }
447 pos += scalarsPerPosition;
448 }
449 }
commit-bot@chromium.orgcbbc4812014-01-30 22:05:47 +0000450
451 this->finish();
commit-bot@chromium.org8128d8c2013-12-19 16:12:25 +0000452}