blob: e372b6cb1bcdf5f2bc83d0252c86c287084f5d13 [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) {
commit-bot@chromium.orgc9b2c882014-03-03 14:30:25 +0000164 if (fStrike->addGlyphToAtlas(glyph, scaler)) {
jvanverth@google.comd830d132013-11-11 20:54:09 +0000165 goto HAS_ATLAS;
166 }
167
168 // try to clear out an unused plot before we flush
commit-bot@chromium.orgc9b2c882014-03-03 14:30:25 +0000169 if (fContext->getFontCache()->freeUnusedPlot(fStrike) &&
170 fStrike->addGlyphToAtlas(glyph, scaler)) {
jvanverth@google.comd830d132013-11-11 20:54:09 +0000171 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
commit-bot@chromium.orgc9b2c882014-03-03 14:30:25 +0000184 // we should have an unused plot now
185 if (fContext->getFontCache()->freeUnusedPlot(fStrike) &&
186 fStrike->addGlyphToAtlas(glyph, scaler)) {
jvanverth@google.comd830d132013-11-11 20:54:09 +0000187 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;
skia.committer@gmail.coma3b53272014-02-15 03:02:15 +0000268
jvanverth@google.comd830d132013-11-11 20:54:09 +0000269 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
commit-bot@chromium.org6c89c342014-02-14 21:48:29 +0000274 static const size_t kVertexSize = 2 * sizeof(SkPoint);
jvanverth@google.comd830d132013-11-11 20:54:09 +0000275 fVertices[2*fCurrVertex].setRectFan(sx,
276 sy,
277 sx + width,
278 sy + height,
commit-bot@chromium.org6c89c342014-02-14 21:48:29 +0000279 kVertexSize);
jvanverth@google.comd830d132013-11-11 20:54:09 +0000280 fVertices[2*fCurrVertex+1].setRectFan(SkFixedToFloat(texture->normalizeFixedX(tx)),
281 SkFixedToFloat(texture->normalizeFixedY(ty)),
282 SkFixedToFloat(texture->normalizeFixedX(tx + tw)),
283 SkFixedToFloat(texture->normalizeFixedY(ty + th)),
commit-bot@chromium.org6c89c342014-02-14 21:48:29 +0000284 kVertexSize);
jvanverth@google.comd830d132013-11-11 20:54:09 +0000285 fCurrVertex += 4;
286}
commit-bot@chromium.org8128d8c2013-12-19 16:12:25 +0000287
commit-bot@chromium.orgcbbc4812014-01-30 22:05:47 +0000288inline void GrDistanceFieldTextContext::init(const GrPaint& paint, const SkPaint& skPaint) {
289 GrTextContext::init(paint, skPaint);
290
291 fStrike = NULL;
292
293 fCurrTexture = NULL;
294 fCurrVertex = 0;
295
296 fVertices = NULL;
297 fMaxVertices = 0;
298
299 fTextRatio = fSkPaint.getTextSize()/kBaseDFFontSize;
skia.committer@gmail.com4c18e9f2014-01-31 03:01:59 +0000300
commit-bot@chromium.orgcbbc4812014-01-30 22:05:47 +0000301 fSkPaint.setTextSize(SkIntToScalar(kBaseDFFontSize));
302 fSkPaint.setLCDRenderText(false);
303 fSkPaint.setAutohinted(false);
304 fSkPaint.setSubpixelText(false);
305}
306
307inline void GrDistanceFieldTextContext::finish() {
308 flushGlyphs();
309
310 GrTextContext::finish();
311}
312
313void GrDistanceFieldTextContext::drawText(const GrPaint& paint, const SkPaint& skPaint,
314 const char text[], size_t byteLength,
commit-bot@chromium.orge8612d92014-01-28 22:02:07 +0000315 SkScalar x, SkScalar y) {
commit-bot@chromium.org8128d8c2013-12-19 16:12:25 +0000316 SkASSERT(byteLength == 0 || text != NULL);
317
commit-bot@chromium.orge8612d92014-01-28 22:02:07 +0000318 // nothing to draw or can't draw
319 if (text == NULL || byteLength == 0 /* no raster clip? || fRC->isEmpty()*/
320 || fSkPaint.getRasterizer()) {
commit-bot@chromium.org8128d8c2013-12-19 16:12:25 +0000321 return;
322 }
skia.committer@gmail.come5d70152014-01-29 07:01:48 +0000323
commit-bot@chromium.orgcbbc4812014-01-30 22:05:47 +0000324 this->init(paint, skPaint);
325
commit-bot@chromium.org8128d8c2013-12-19 16:12:25 +0000326 SkScalar sizeRatio = fTextRatio;
327
328 SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
329
commit-bot@chromium.orge8612d92014-01-28 22:02:07 +0000330 SkAutoGlyphCache autoCache(fSkPaint, &fDeviceProperties, NULL);
331 SkGlyphCache* cache = autoCache.getCache();
332 GrFontScaler* fontScaler = GetGrFontScaler(cache);
skia.committer@gmail.come5d70152014-01-29 07:01:48 +0000333
commit-bot@chromium.org8128d8c2013-12-19 16:12:25 +0000334 // need to measure first
335 // TODO - generate positions and pre-load cache as well?
336 const char* stop = text + byteLength;
337 if (fSkPaint.getTextAlign() != SkPaint::kLeft_Align) {
338 SkFixed stopX = 0;
339 SkFixed stopY = 0;
340
341 const char* textPtr = text;
342 while (textPtr < stop) {
343 // don't need x, y here, since all subpixel variants will have the
344 // same advance
345 const SkGlyph& glyph = glyphCacheProc(cache, &textPtr, 0, 0);
346
347 stopX += glyph.fAdvanceX;
348 stopY += glyph.fAdvanceY;
349 }
350 SkASSERT(textPtr == stop);
351
352 SkScalar alignX = SkFixedToScalar(stopX)*sizeRatio;
353 SkScalar alignY = SkFixedToScalar(stopY)*sizeRatio;
354
355 if (fSkPaint.getTextAlign() == SkPaint::kCenter_Align) {
356 alignX = SkScalarHalf(alignX);
357 alignY = SkScalarHalf(alignY);
358 }
359
360 x -= alignX;
361 y -= alignY;
362 }
363
364 SkFixed fx = SkScalarToFixed(x) + SK_FixedHalf;
365 SkFixed fy = SkScalarToFixed(y) + SK_FixedHalf;
366 SkFixed fixedScale = SkScalarToFixed(sizeRatio);
367 while (text < stop) {
368 const SkGlyph& glyph = glyphCacheProc(cache, &text, fx, fy);
369
370 if (glyph.fWidth) {
371 this->drawPackedGlyph(GrGlyph::Pack(glyph.getGlyphID(),
372 glyph.getSubXFixed(),
373 glyph.getSubYFixed()),
374 SkFixedFloorToFixed(fx),
375 SkFixedFloorToFixed(fy),
376 fontScaler);
377 }
378
379 fx += SkFixedMul_portable(glyph.fAdvanceX, fixedScale);
380 fy += SkFixedMul_portable(glyph.fAdvanceY, fixedScale);
381 }
commit-bot@chromium.orgcbbc4812014-01-30 22:05:47 +0000382
383 this->finish();
commit-bot@chromium.org8128d8c2013-12-19 16:12:25 +0000384}
385
commit-bot@chromium.orgcbbc4812014-01-30 22:05:47 +0000386void GrDistanceFieldTextContext::drawPosText(const GrPaint& paint, const SkPaint& skPaint,
387 const char text[], size_t byteLength,
commit-bot@chromium.org8128d8c2013-12-19 16:12:25 +0000388 const SkScalar pos[], SkScalar constY,
commit-bot@chromium.orge8612d92014-01-28 22:02:07 +0000389 int scalarsPerPosition) {
commit-bot@chromium.org8128d8c2013-12-19 16:12:25 +0000390
391 SkASSERT(byteLength == 0 || text != NULL);
392 SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition);
393
394 // nothing to draw
commit-bot@chromium.orgcbbc4812014-01-30 22:05:47 +0000395 if (text == NULL || byteLength == 0 /* no raster clip? || fRC->isEmpty()*/) {
commit-bot@chromium.org8128d8c2013-12-19 16:12:25 +0000396 return;
397 }
398
commit-bot@chromium.orgcbbc4812014-01-30 22:05:47 +0000399 this->init(paint, skPaint);
400
commit-bot@chromium.org8128d8c2013-12-19 16:12:25 +0000401 SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
402
commit-bot@chromium.orge8612d92014-01-28 22:02:07 +0000403 SkAutoGlyphCache autoCache(fSkPaint, &fDeviceProperties, NULL);
404 SkGlyphCache* cache = autoCache.getCache();
405 GrFontScaler* fontScaler = GetGrFontScaler(cache);
skia.committer@gmail.come5d70152014-01-29 07:01:48 +0000406
commit-bot@chromium.org8128d8c2013-12-19 16:12:25 +0000407 const char* stop = text + byteLength;
408
409 if (SkPaint::kLeft_Align == fSkPaint.getTextAlign()) {
410 while (text < stop) {
411 // the last 2 parameters are ignored
412 const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
413
414 if (glyph.fWidth) {
415 SkScalar x = pos[0];
416 SkScalar y = scalarsPerPosition == 1 ? constY : pos[1];
417
418 this->drawPackedGlyph(GrGlyph::Pack(glyph.getGlyphID(),
419 glyph.getSubXFixed(),
420 glyph.getSubYFixed()),
421 SkScalarToFixed(x) + SK_FixedHalf, //d1g.fHalfSampleX,
422 SkScalarToFixed(y) + SK_FixedHalf, //d1g.fHalfSampleY,
423 fontScaler);
424 }
425 pos += scalarsPerPosition;
426 }
427 } else {
428 int alignShift = SkPaint::kCenter_Align == fSkPaint.getTextAlign() ? 1 : 0;
429 while (text < stop) {
430 // the last 2 parameters are ignored
431 const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
432
433 if (glyph.fWidth) {
434 SkScalar x = pos[0];
435 SkScalar y = scalarsPerPosition == 1 ? constY : pos[1];
skia.committer@gmail.com22e96722013-12-20 07:01:36 +0000436
commit-bot@chromium.org8128d8c2013-12-19 16:12:25 +0000437 this->drawPackedGlyph(GrGlyph::Pack(glyph.getGlyphID(),
438 glyph.getSubXFixed(),
439 glyph.getSubYFixed()),
440 SkScalarToFixed(x) - (glyph.fAdvanceX >> alignShift)
441 + SK_FixedHalf, //d1g.fHalfSampleX,
442 SkScalarToFixed(y) - (glyph.fAdvanceY >> alignShift)
443 + SK_FixedHalf, //d1g.fHalfSampleY,
444 fontScaler);
445 }
446 pos += scalarsPerPosition;
447 }
448 }
commit-bot@chromium.orgcbbc4812014-01-30 22:05:47 +0000449
450 this->finish();
commit-bot@chromium.org8128d8c2013-12-19 16:12:25 +0000451}