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