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