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