blob: 95e773ec651f86e4c54be3d285f5c767311eeaab [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"
11#include "GrFontScaler.h"
commit-bot@chromium.org8128d8c2013-12-19 16:12:25 +000012#include "SkGlyphCache.h"
jvanverth@google.comd830d132013-11-11 20:54:09 +000013#include "GrIndexBuffer.h"
14#include "GrTextStrike.h"
15#include "GrTextStrike_impl.h"
16#include "SkPath.h"
17#include "SkRTConf.h"
18#include "SkStrokeRec.h"
19#include "effects/GrDistanceFieldTextureEffect.h"
20
21static const int kGlyphCoordsAttributeIndex = 1;
22
commit-bot@chromium.org8128d8c2013-12-19 16:12:25 +000023static const int kBaseDFFontSize = 32;
24
jvanverth@google.comd830d132013-11-11 20:54:09 +000025SK_CONF_DECLARE(bool, c_DumpFontCache, "gpu.dumpFontCache", false,
26 "Dump the contents of the font cache before every purge.");
27
skia.committer@gmail.com11a253b2013-11-12 07:02:05 +000028GrDistanceFieldTextContext::GrDistanceFieldTextContext(GrContext* context,
commit-bot@chromium.org8128d8c2013-12-19 16:12:25 +000029 const GrPaint& grPaint,
30 const SkPaint& skPaint)
31 : GrTextContext(context, grPaint),
32 fSkPaint(skPaint) {
jvanverth@google.comd830d132013-11-11 20:54:09 +000033 fStrike = NULL;
34
35 fCurrTexture = NULL;
36 fCurrVertex = 0;
37
38 fVertices = NULL;
39 fMaxVertices = 0;
commit-bot@chromium.org8128d8c2013-12-19 16:12:25 +000040
41 fTextRatio = fSkPaint.getTextSize()/kBaseDFFontSize;
42
43 fSkPaint.setTextSize(SkIntToScalar(kBaseDFFontSize));
44 fSkPaint.setLCDRenderText(false);
45 fSkPaint.setAutohinted(false);
46 fSkPaint.setSubpixelText(false);
jvanverth@google.comd830d132013-11-11 20:54:09 +000047}
48
49GrDistanceFieldTextContext::~GrDistanceFieldTextContext() {
50 this->flushGlyphs();
51}
52
53static inline GrColor skcolor_to_grcolor_nopremultiply(SkColor c) {
54 unsigned r = SkColorGetR(c);
55 unsigned g = SkColorGetG(c);
56 unsigned b = SkColorGetB(c);
57 return GrColorPackRGBA(r, g, b, 0xff);
58}
59
60void GrDistanceFieldTextContext::flushGlyphs() {
61 if (NULL == fDrawTarget) {
62 return;
63 }
64
65 GrDrawState* drawState = fDrawTarget->drawState();
66 GrDrawState::AutoRestoreEffects are(drawState);
67 drawState->setFromPaint(fPaint, fContext->getMatrix(), fContext->getRenderTarget());
68
69 if (fCurrVertex > 0) {
70 // setup our sampler state for our text texture/atlas
71 SkASSERT(GrIsALIGN4(fCurrVertex));
72 SkASSERT(fCurrTexture);
73 GrTextureParams params(SkShader::kRepeat_TileMode, GrTextureParams::kBilerp_FilterMode);
74
75 // This effect could be stored with one of the cache objects (atlas?)
76 drawState->addCoverageEffect(
77 GrDistanceFieldTextureEffect::Create(fCurrTexture, params),
78 kGlyphCoordsAttributeIndex)->unref();
79
80 if (!GrPixelConfigIsAlphaOnly(fCurrTexture->config())) {
81 if (kOne_GrBlendCoeff != fPaint.getSrcBlendCoeff() ||
82 kISA_GrBlendCoeff != fPaint.getDstBlendCoeff() ||
83 fPaint.numColorStages()) {
84 GrPrintf("LCD Text will not draw correctly.\n");
85 }
86 // We don't use the GrPaint's color in this case because it's been premultiplied by
87 // alpha. Instead we feed in a non-premultiplied color, and multiply its alpha by
88 // the mask texture color. The end result is that we get
89 // mask*paintAlpha*paintColor + (1-mask*paintAlpha)*dstColor
commit-bot@chromium.org8128d8c2013-12-19 16:12:25 +000090 int a = SkColorGetA(fSkPaint.getColor());
jvanverth@google.comd830d132013-11-11 20:54:09 +000091 // paintAlpha
92 drawState->setColor(SkColorSetARGB(a, a, a, a));
93 // paintColor
commit-bot@chromium.org8128d8c2013-12-19 16:12:25 +000094 drawState->setBlendConstant(skcolor_to_grcolor_nopremultiply(fSkPaint.getColor()));
jvanverth@google.comd830d132013-11-11 20:54:09 +000095 drawState->setBlendFunc(kConstC_GrBlendCoeff, kISC_GrBlendCoeff);
96 } else {
97 // set back to normal in case we took LCD path previously.
98 drawState->setBlendFunc(fPaint.getSrcBlendCoeff(), fPaint.getDstBlendCoeff());
99 drawState->setColor(fPaint.getColor());
100 }
101
102 int nGlyphs = fCurrVertex / 4;
103 fDrawTarget->setIndexSourceToBuffer(fContext->getQuadIndexBuffer());
104 fDrawTarget->drawIndexedInstances(kTriangles_GrPrimitiveType,
105 nGlyphs,
106 4, 6);
107 fDrawTarget->resetVertexSource();
108 fVertices = NULL;
109 fMaxVertices = 0;
110 fCurrVertex = 0;
111 SkSafeSetNull(fCurrTexture);
112 }
113}
114
115namespace {
116
117// position + texture coord
118extern const GrVertexAttrib gTextVertexAttribs[] = {
119 {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding},
120 {kVec2f_GrVertexAttribType, sizeof(GrPoint), kEffect_GrVertexAttribBinding}
121};
122
123};
124
125void GrDistanceFieldTextContext::drawPackedGlyph(GrGlyph::PackedID packed,
126 GrFixed vx, GrFixed vy,
127 GrFontScaler* scaler) {
128 if (NULL == fDrawTarget) {
129 return;
130 }
131 if (NULL == fStrike) {
132 fStrike = fContext->getFontCache()->getStrike(scaler, true);
133 }
134
135 GrGlyph* glyph = fStrike->getGlyph(packed, scaler);
136 if (NULL == glyph || glyph->fBounds.isEmpty()) {
137 return;
138 }
139
140 SkScalar sx = SkFixedToScalar(vx);
141 SkScalar sy = SkFixedToScalar(vy);
142/*
143 // not valid, need to find a different solution for this
144 vx += SkIntToFixed(glyph->fBounds.fLeft);
145 vy += SkIntToFixed(glyph->fBounds.fTop);
skia.committer@gmail.com11a253b2013-11-12 07:02:05 +0000146
jvanverth@google.comd830d132013-11-11 20:54:09 +0000147 // keep them as ints until we've done the clip-test
148 GrFixed width = glyph->fBounds.width();
149 GrFixed height = glyph->fBounds.height();
150
151 // check if we clipped out
152 if (true || NULL == glyph->fPlot) {
153 int x = vx >> 16;
154 int y = vy >> 16;
155 if (fClipRect.quickReject(x, y, x + width, y + height)) {
156// SkCLZ(3); // so we can set a break-point in the debugger
157 return;
158 }
159 }
160*/
161 if (NULL == glyph->fPlot) {
162 if (fStrike->getGlyphAtlas(glyph, scaler)) {
163 goto HAS_ATLAS;
164 }
165
166 // try to clear out an unused plot before we flush
167 fContext->getFontCache()->freePlotExceptFor(fStrike);
168 if (fStrike->getGlyphAtlas(glyph, scaler)) {
169 goto HAS_ATLAS;
170 }
171
172 if (c_DumpFontCache) {
173#ifdef SK_DEVELOPER
174 fContext->getFontCache()->dump();
175#endif
176 }
177
178 // before we purge the cache, we must flush any accumulated draws
179 this->flushGlyphs();
180 fContext->flush();
181
182 // try to purge
183 fContext->getFontCache()->purgeExceptFor(fStrike);
184 // need to use new flush count here
185 if (fStrike->getGlyphAtlas(glyph, scaler)) {
186 goto HAS_ATLAS;
187 }
188
189 if (NULL == glyph->fPath) {
190 SkPath* path = SkNEW(SkPath);
191 if (!scaler->getGlyphPath(glyph->glyphID(), path)) {
192 // flag the glyph as being dead?
193 delete path;
194 return;
195 }
196 glyph->fPath = path;
197 }
198
199 GrContext::AutoMatrix am;
200 SkMatrix translate;
201 translate.setTranslate(sx, sy);
202 GrPaint tmpPaint(fPaint);
203 am.setPreConcat(fContext, translate, &tmpPaint);
204 SkStrokeRec stroke(SkStrokeRec::kFill_InitStyle);
205 fContext->drawPath(tmpPaint, *glyph->fPath, stroke);
206 return;
207 }
208
209HAS_ATLAS:
210 SkASSERT(glyph->fPlot);
211 GrDrawTarget::DrawToken drawToken = fDrawTarget->getCurrentDrawToken();
212 glyph->fPlot->setDrawToken(drawToken);
213
214 GrTexture* texture = glyph->fPlot->texture();
215 SkASSERT(texture);
216
217 if (fCurrTexture != texture || fCurrVertex + 4 > fMaxVertices) {
218 this->flushGlyphs();
219 fCurrTexture = texture;
220 fCurrTexture->ref();
221 }
222
223 if (NULL == fVertices) {
224 // If we need to reserve vertices allow the draw target to suggest
225 // a number of verts to reserve and whether to perform a flush.
226 fMaxVertices = kMinRequestedVerts;
227 fDrawTarget->drawState()->setVertexAttribs<gTextVertexAttribs>(
228 SK_ARRAY_COUNT(gTextVertexAttribs));
229 bool flush = fDrawTarget->geometryHints(&fMaxVertices, NULL);
230 if (flush) {
231 this->flushGlyphs();
232 fContext->flush();
233 fDrawTarget->drawState()->setVertexAttribs<gTextVertexAttribs>(
234 SK_ARRAY_COUNT(gTextVertexAttribs));
235 }
236 fMaxVertices = kDefaultRequestedVerts;
237 // ignore return, no point in flushing again.
238 fDrawTarget->geometryHints(&fMaxVertices, NULL);
239
240 int maxQuadVertices = 4 * fContext->getQuadIndexBuffer()->maxQuads();
241 if (fMaxVertices < kMinRequestedVerts) {
242 fMaxVertices = kDefaultRequestedVerts;
243 } else if (fMaxVertices > maxQuadVertices) {
244 // don't exceed the limit of the index buffer
245 fMaxVertices = maxQuadVertices;
246 }
247 bool success = fDrawTarget->reserveVertexAndIndexSpace(fMaxVertices,
248 0,
249 GrTCast<void**>(&fVertices),
250 NULL);
251 GrAlwaysAssert(success);
252 SkASSERT(2*sizeof(GrPoint) == fDrawTarget->getDrawState().getVertexSize());
253 }
254
255 SkScalar dx = SkIntToScalar(glyph->fBounds.fLeft);
256 SkScalar dy = SkIntToScalar(glyph->fBounds.fTop);
257 SkScalar width = SkIntToScalar(glyph->fBounds.width());
258 SkScalar height = SkIntToScalar(glyph->fBounds.height());
259
260 SkScalar scale = fTextRatio;
261 dx *= scale;
262 dy *= scale;
263 sx += dx;
264 sy += dy;
265 width *= scale;
266 height *= scale;
267
268 GrFixed tx = SkIntToFixed(glyph->fAtlasLocation.fX);
269 GrFixed ty = SkIntToFixed(glyph->fAtlasLocation.fY);
270 GrFixed tw = SkIntToFixed(glyph->fBounds.width());
271 GrFixed th = SkIntToFixed(glyph->fBounds.height());
272
273 fVertices[2*fCurrVertex].setRectFan(sx,
274 sy,
275 sx + width,
276 sy + height,
277 2 * sizeof(SkPoint));
278 fVertices[2*fCurrVertex+1].setRectFan(SkFixedToFloat(texture->normalizeFixedX(tx)),
279 SkFixedToFloat(texture->normalizeFixedY(ty)),
280 SkFixedToFloat(texture->normalizeFixedX(tx + tw)),
281 SkFixedToFloat(texture->normalizeFixedY(ty + th)),
282 2 * sizeof(SkPoint));
283 fCurrVertex += 4;
284}
commit-bot@chromium.org8128d8c2013-12-19 16:12:25 +0000285
286void GrDistanceFieldTextContext::drawText(const char text[], size_t byteLength,
287 SkScalar x, SkScalar y, SkGlyphCache* cache,
288 GrFontScaler* fontScaler) {
289 SkASSERT(byteLength == 0 || text != NULL);
290
291 // nothing to draw
292 if (text == NULL || byteLength == 0 /* no raster clip? || fRC->isEmpty()*/) {
293 return;
294 }
295
296 SkScalar sizeRatio = fTextRatio;
297
298 SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
299
300 // need to measure first
301 // TODO - generate positions and pre-load cache as well?
302 const char* stop = text + byteLength;
303 if (fSkPaint.getTextAlign() != SkPaint::kLeft_Align) {
304 SkFixed stopX = 0;
305 SkFixed stopY = 0;
306
307 const char* textPtr = text;
308 while (textPtr < stop) {
309 // don't need x, y here, since all subpixel variants will have the
310 // same advance
311 const SkGlyph& glyph = glyphCacheProc(cache, &textPtr, 0, 0);
312
313 stopX += glyph.fAdvanceX;
314 stopY += glyph.fAdvanceY;
315 }
316 SkASSERT(textPtr == stop);
317
318 SkScalar alignX = SkFixedToScalar(stopX)*sizeRatio;
319 SkScalar alignY = SkFixedToScalar(stopY)*sizeRatio;
320
321 if (fSkPaint.getTextAlign() == SkPaint::kCenter_Align) {
322 alignX = SkScalarHalf(alignX);
323 alignY = SkScalarHalf(alignY);
324 }
325
326 x -= alignX;
327 y -= alignY;
328 }
329
330 SkFixed fx = SkScalarToFixed(x) + SK_FixedHalf;
331 SkFixed fy = SkScalarToFixed(y) + SK_FixedHalf;
332 SkFixed fixedScale = SkScalarToFixed(sizeRatio);
333 while (text < stop) {
334 const SkGlyph& glyph = glyphCacheProc(cache, &text, fx, fy);
335
336 if (glyph.fWidth) {
337 this->drawPackedGlyph(GrGlyph::Pack(glyph.getGlyphID(),
338 glyph.getSubXFixed(),
339 glyph.getSubYFixed()),
340 SkFixedFloorToFixed(fx),
341 SkFixedFloorToFixed(fy),
342 fontScaler);
343 }
344
345 fx += SkFixedMul_portable(glyph.fAdvanceX, fixedScale);
346 fy += SkFixedMul_portable(glyph.fAdvanceY, fixedScale);
347 }
348}
349
350void GrDistanceFieldTextContext::drawPosText(const char text[], size_t byteLength,
351 const SkScalar pos[], SkScalar constY,
352 int scalarsPerPosition,
353 SkGlyphCache* cache, GrFontScaler* fontScaler) {
354
355 SkASSERT(byteLength == 0 || text != NULL);
356 SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition);
357
358 // nothing to draw
359 if (text == NULL || byteLength == 0 /* no raster clip? || fRC->isEmpty()*/) {
360 return;
361 }
362
363 SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
364
365 const char* stop = text + byteLength;
366
367 if (SkPaint::kLeft_Align == fSkPaint.getTextAlign()) {
368 while (text < stop) {
369 // the last 2 parameters are ignored
370 const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
371
372 if (glyph.fWidth) {
373 SkScalar x = pos[0];
374 SkScalar y = scalarsPerPosition == 1 ? constY : pos[1];
375
376 this->drawPackedGlyph(GrGlyph::Pack(glyph.getGlyphID(),
377 glyph.getSubXFixed(),
378 glyph.getSubYFixed()),
379 SkScalarToFixed(x) + SK_FixedHalf, //d1g.fHalfSampleX,
380 SkScalarToFixed(y) + SK_FixedHalf, //d1g.fHalfSampleY,
381 fontScaler);
382 }
383 pos += scalarsPerPosition;
384 }
385 } else {
386 int alignShift = SkPaint::kCenter_Align == fSkPaint.getTextAlign() ? 1 : 0;
387 while (text < stop) {
388 // the last 2 parameters are ignored
389 const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
390
391 if (glyph.fWidth) {
392 SkScalar x = pos[0];
393 SkScalar y = scalarsPerPosition == 1 ? constY : pos[1];
skia.committer@gmail.com22e96722013-12-20 07:01:36 +0000394
commit-bot@chromium.org8128d8c2013-12-19 16:12:25 +0000395 this->drawPackedGlyph(GrGlyph::Pack(glyph.getGlyphID(),
396 glyph.getSubXFixed(),
397 glyph.getSubYFixed()),
398 SkScalarToFixed(x) - (glyph.fAdvanceX >> alignShift)
399 + SK_FixedHalf, //d1g.fHalfSampleX,
400 SkScalarToFixed(y) - (glyph.fAdvanceY >> alignShift)
401 + SK_FixedHalf, //d1g.fHalfSampleY,
402 fontScaler);
403 }
404 pos += scalarsPerPosition;
405 }
406 }
407}