|
1 /* |
|
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 "GrDrawTargetCaps.h" |
|
12 #include "GrFontScaler.h" |
|
13 #include "SkGlyphCache.h" |
|
14 #include "GrIndexBuffer.h" |
|
15 #include "GrTextStrike.h" |
|
16 #include "GrTextStrike_impl.h" |
|
17 #include "SkDraw.h" |
|
18 #include "SkGpuDevice.h" |
|
19 #include "SkPath.h" |
|
20 #include "SkRTConf.h" |
|
21 #include "SkStrokeRec.h" |
|
22 #include "effects/GrDistanceFieldTextureEffect.h" |
|
23 |
|
24 static const int kGlyphCoordsAttributeIndex = 1; |
|
25 |
|
26 static const int kBaseDFFontSize = 32; |
|
27 |
|
28 SK_CONF_DECLARE(bool, c_DumpFontCache, "gpu.dumpFontCache", false, |
|
29 "Dump the contents of the font cache before every purge."); |
|
30 |
|
31 #if SK_FORCE_DISTANCEFIELD_FONTS |
|
32 static const bool kForceDistanceFieldFonts = true; |
|
33 #else |
|
34 static const bool kForceDistanceFieldFonts = false; |
|
35 #endif |
|
36 |
|
37 GrDistanceFieldTextContext::GrDistanceFieldTextContext(GrContext* context, |
|
38 const SkDeviceProperties& properties) |
|
39 : GrTextContext(context, properties) { |
|
40 fStrike = NULL; |
|
41 |
|
42 fCurrTexture = NULL; |
|
43 fCurrVertex = 0; |
|
44 |
|
45 fVertices = NULL; |
|
46 fMaxVertices = 0; |
|
47 } |
|
48 |
|
49 GrDistanceFieldTextContext::~GrDistanceFieldTextContext() { |
|
50 this->flushGlyphs(); |
|
51 } |
|
52 |
|
53 bool GrDistanceFieldTextContext::canDraw(const SkPaint& paint) { |
|
54 return (kForceDistanceFieldFonts || paint.isDistanceFieldTextTEMP()) && |
|
55 !paint.getRasterizer() && !paint.getMaskFilter() && |
|
56 paint.getStyle() == SkPaint::kFill_Style && |
|
57 fContext->getTextTarget()->caps()->shaderDerivativeSupport() && |
|
58 !SkDraw::ShouldDrawTextAsPaths(paint, fContext->getMatrix()); |
|
59 } |
|
60 |
|
61 static 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 |
|
68 void 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 SkISize size = fStrike->getAtlasSize(); |
|
85 drawState->addCoverageEffect( |
|
86 GrDistanceFieldTextureEffect::Create(fCurrTexture, params, size), |
|
87 kGlyphCoordsAttributeIndex)->unref(); |
|
88 |
|
89 if (!GrPixelConfigIsAlphaOnly(fCurrTexture->config())) { |
|
90 if (kOne_GrBlendCoeff != fPaint.getSrcBlendCoeff() || |
|
91 kISA_GrBlendCoeff != fPaint.getDstBlendCoeff() || |
|
92 fPaint.numColorStages()) { |
|
93 GrPrintf("LCD Text will not draw correctly.\n"); |
|
94 } |
|
95 // We don't use the GrPaint's color in this case because it's been premultiplied by |
|
96 // alpha. Instead we feed in a non-premultiplied color, and multiply its alpha by |
|
97 // the mask texture color. The end result is that we get |
|
98 // mask*paintAlpha*paintColor + (1-mask*paintAlpha)*dstColor |
|
99 int a = SkColorGetA(fSkPaint.getColor()); |
|
100 // paintAlpha |
|
101 drawState->setColor(SkColorSetARGB(a, a, a, a)); |
|
102 // paintColor |
|
103 drawState->setBlendConstant(skcolor_to_grcolor_nopremultiply(fSkPaint.getColor())); |
|
104 drawState->setBlendFunc(kConstC_GrBlendCoeff, kISC_GrBlendCoeff); |
|
105 } else { |
|
106 // set back to normal in case we took LCD path previously. |
|
107 drawState->setBlendFunc(fPaint.getSrcBlendCoeff(), fPaint.getDstBlendCoeff()); |
|
108 drawState->setColor(fPaint.getColor()); |
|
109 } |
|
110 |
|
111 int nGlyphs = fCurrVertex / 4; |
|
112 fDrawTarget->setIndexSourceToBuffer(fContext->getQuadIndexBuffer()); |
|
113 fDrawTarget->drawIndexedInstances(kTriangles_GrPrimitiveType, |
|
114 nGlyphs, |
|
115 4, 6); |
|
116 fDrawTarget->resetVertexSource(); |
|
117 fVertices = NULL; |
|
118 fMaxVertices = 0; |
|
119 fCurrVertex = 0; |
|
120 SkSafeSetNull(fCurrTexture); |
|
121 } |
|
122 } |
|
123 |
|
124 namespace { |
|
125 |
|
126 // position + texture coord |
|
127 extern const GrVertexAttrib gTextVertexAttribs[] = { |
|
128 {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding}, |
|
129 {kVec2f_GrVertexAttribType, sizeof(GrPoint), kEffect_GrVertexAttribBinding} |
|
130 }; |
|
131 |
|
132 }; |
|
133 |
|
134 void GrDistanceFieldTextContext::drawPackedGlyph(GrGlyph::PackedID packed, |
|
135 GrFixed vx, GrFixed vy, |
|
136 GrFontScaler* scaler) { |
|
137 if (NULL == fDrawTarget) { |
|
138 return; |
|
139 } |
|
140 if (NULL == fStrike) { |
|
141 fStrike = fContext->getFontCache()->getStrike(scaler, true); |
|
142 } |
|
143 |
|
144 GrGlyph* glyph = fStrike->getGlyph(packed, scaler); |
|
145 if (NULL == glyph || glyph->fBounds.isEmpty()) { |
|
146 return; |
|
147 } |
|
148 |
|
149 SkScalar sx = SkFixedToScalar(vx); |
|
150 SkScalar sy = SkFixedToScalar(vy); |
|
151 /* |
|
152 // not valid, need to find a different solution for this |
|
153 vx += SkIntToFixed(glyph->fBounds.fLeft); |
|
154 vy += SkIntToFixed(glyph->fBounds.fTop); |
|
155 |
|
156 // keep them as ints until we've done the clip-test |
|
157 GrFixed width = glyph->fBounds.width(); |
|
158 GrFixed height = glyph->fBounds.height(); |
|
159 |
|
160 // check if we clipped out |
|
161 if (true || NULL == glyph->fPlot) { |
|
162 int x = vx >> 16; |
|
163 int y = vy >> 16; |
|
164 if (fClipRect.quickReject(x, y, x + width, y + height)) { |
|
165 // SkCLZ(3); // so we can set a break-point in the debugger |
|
166 return; |
|
167 } |
|
168 } |
|
169 */ |
|
170 if (NULL == glyph->fPlot) { |
|
171 if (fStrike->addGlyphToAtlas(glyph, scaler)) { |
|
172 goto HAS_ATLAS; |
|
173 } |
|
174 |
|
175 // try to clear out an unused plot before we flush |
|
176 if (fContext->getFontCache()->freeUnusedPlot(fStrike) && |
|
177 fStrike->addGlyphToAtlas(glyph, scaler)) { |
|
178 goto HAS_ATLAS; |
|
179 } |
|
180 |
|
181 if (c_DumpFontCache) { |
|
182 #ifdef SK_DEVELOPER |
|
183 fContext->getFontCache()->dump(); |
|
184 #endif |
|
185 } |
|
186 |
|
187 // before we purge the cache, we must flush any accumulated draws |
|
188 this->flushGlyphs(); |
|
189 fContext->flush(); |
|
190 |
|
191 // we should have an unused plot now |
|
192 if (fContext->getFontCache()->freeUnusedPlot(fStrike) && |
|
193 fStrike->addGlyphToAtlas(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 |
|
217 HAS_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 static const size_t kVertexSize = 2 * sizeof(SkPoint); |
|
282 fVertices[2*fCurrVertex].setRectFan(sx, |
|
283 sy, |
|
284 sx + width, |
|
285 sy + height, |
|
286 kVertexSize); |
|
287 fVertices[2*fCurrVertex+1].setRectFan(SkFixedToFloat(texture->normalizeFixedX(tx)), |
|
288 SkFixedToFloat(texture->normalizeFixedY(ty)), |
|
289 SkFixedToFloat(texture->normalizeFixedX(tx + tw)), |
|
290 SkFixedToFloat(texture->normalizeFixedY(ty + th)), |
|
291 kVertexSize); |
|
292 fCurrVertex += 4; |
|
293 } |
|
294 |
|
295 inline void GrDistanceFieldTextContext::init(const GrPaint& paint, const SkPaint& skPaint) { |
|
296 GrTextContext::init(paint, skPaint); |
|
297 |
|
298 fStrike = NULL; |
|
299 |
|
300 fCurrTexture = NULL; |
|
301 fCurrVertex = 0; |
|
302 |
|
303 fVertices = NULL; |
|
304 fMaxVertices = 0; |
|
305 |
|
306 fTextRatio = fSkPaint.getTextSize()/kBaseDFFontSize; |
|
307 |
|
308 fSkPaint.setTextSize(SkIntToScalar(kBaseDFFontSize)); |
|
309 fSkPaint.setLCDRenderText(false); |
|
310 fSkPaint.setAutohinted(false); |
|
311 fSkPaint.setSubpixelText(true); |
|
312 } |
|
313 |
|
314 inline void GrDistanceFieldTextContext::finish() { |
|
315 flushGlyphs(); |
|
316 |
|
317 GrTextContext::finish(); |
|
318 } |
|
319 |
|
320 void GrDistanceFieldTextContext::drawText(const GrPaint& paint, const SkPaint& skPaint, |
|
321 const char text[], size_t byteLength, |
|
322 SkScalar x, SkScalar y) { |
|
323 SkASSERT(byteLength == 0 || text != NULL); |
|
324 |
|
325 // nothing to draw or can't draw |
|
326 if (text == NULL || byteLength == 0 /* no raster clip? || fRC->isEmpty()*/ |
|
327 || fSkPaint.getRasterizer()) { |
|
328 return; |
|
329 } |
|
330 |
|
331 this->init(paint, skPaint); |
|
332 |
|
333 SkScalar sizeRatio = fTextRatio; |
|
334 |
|
335 SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc(); |
|
336 |
|
337 SkAutoGlyphCache autoCache(fSkPaint, &fDeviceProperties, NULL); |
|
338 SkGlyphCache* cache = autoCache.getCache(); |
|
339 GrFontScaler* fontScaler = GetGrFontScaler(cache); |
|
340 |
|
341 // need to measure first |
|
342 // TODO - generate positions and pre-load cache as well? |
|
343 const char* stop = text + byteLength; |
|
344 if (fSkPaint.getTextAlign() != SkPaint::kLeft_Align) { |
|
345 SkFixed stopX = 0; |
|
346 SkFixed stopY = 0; |
|
347 |
|
348 const char* textPtr = text; |
|
349 while (textPtr < stop) { |
|
350 // don't need x, y here, since all subpixel variants will have the |
|
351 // same advance |
|
352 const SkGlyph& glyph = glyphCacheProc(cache, &textPtr, 0, 0); |
|
353 |
|
354 stopX += glyph.fAdvanceX; |
|
355 stopY += glyph.fAdvanceY; |
|
356 } |
|
357 SkASSERT(textPtr == stop); |
|
358 |
|
359 SkScalar alignX = SkFixedToScalar(stopX)*sizeRatio; |
|
360 SkScalar alignY = SkFixedToScalar(stopY)*sizeRatio; |
|
361 |
|
362 if (fSkPaint.getTextAlign() == SkPaint::kCenter_Align) { |
|
363 alignX = SkScalarHalf(alignX); |
|
364 alignY = SkScalarHalf(alignY); |
|
365 } |
|
366 |
|
367 x -= alignX; |
|
368 y -= alignY; |
|
369 } |
|
370 |
|
371 SkFixed fx = SkScalarToFixed(x) + SK_FixedHalf; |
|
372 SkFixed fy = SkScalarToFixed(y) + SK_FixedHalf; |
|
373 SkFixed fixedScale = SkScalarToFixed(sizeRatio); |
|
374 while (text < stop) { |
|
375 const SkGlyph& glyph = glyphCacheProc(cache, &text, fx, fy); |
|
376 |
|
377 if (glyph.fWidth) { |
|
378 this->drawPackedGlyph(GrGlyph::Pack(glyph.getGlyphID(), |
|
379 glyph.getSubXFixed(), |
|
380 glyph.getSubYFixed()), |
|
381 SkFixedFloorToFixed(fx), |
|
382 SkFixedFloorToFixed(fy), |
|
383 fontScaler); |
|
384 } |
|
385 |
|
386 fx += SkFixedMul_portable(glyph.fAdvanceX, fixedScale); |
|
387 fy += SkFixedMul_portable(glyph.fAdvanceY, fixedScale); |
|
388 } |
|
389 |
|
390 this->finish(); |
|
391 } |
|
392 |
|
393 void GrDistanceFieldTextContext::drawPosText(const GrPaint& paint, const SkPaint& skPaint, |
|
394 const char text[], size_t byteLength, |
|
395 const SkScalar pos[], SkScalar constY, |
|
396 int scalarsPerPosition) { |
|
397 |
|
398 SkASSERT(byteLength == 0 || text != NULL); |
|
399 SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition); |
|
400 |
|
401 // nothing to draw |
|
402 if (text == NULL || byteLength == 0 /* no raster clip? || fRC->isEmpty()*/) { |
|
403 return; |
|
404 } |
|
405 |
|
406 this->init(paint, skPaint); |
|
407 |
|
408 SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc(); |
|
409 |
|
410 SkAutoGlyphCache autoCache(fSkPaint, &fDeviceProperties, NULL); |
|
411 SkGlyphCache* cache = autoCache.getCache(); |
|
412 GrFontScaler* fontScaler = GetGrFontScaler(cache); |
|
413 |
|
414 const char* stop = text + byteLength; |
|
415 |
|
416 if (SkPaint::kLeft_Align == fSkPaint.getTextAlign()) { |
|
417 while (text < stop) { |
|
418 // the last 2 parameters are ignored |
|
419 const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0); |
|
420 |
|
421 if (glyph.fWidth) { |
|
422 SkScalar x = pos[0]; |
|
423 SkScalar y = scalarsPerPosition == 1 ? constY : pos[1]; |
|
424 |
|
425 this->drawPackedGlyph(GrGlyph::Pack(glyph.getGlyphID(), |
|
426 glyph.getSubXFixed(), |
|
427 glyph.getSubYFixed()), |
|
428 SkScalarToFixed(x) + SK_FixedHalf, //d1g.fHalfSampleX, |
|
429 SkScalarToFixed(y) + SK_FixedHalf, //d1g.fHalfSampleY, |
|
430 fontScaler); |
|
431 } |
|
432 pos += scalarsPerPosition; |
|
433 } |
|
434 } else { |
|
435 int alignShift = SkPaint::kCenter_Align == fSkPaint.getTextAlign() ? 1 : 0; |
|
436 while (text < stop) { |
|
437 // the last 2 parameters are ignored |
|
438 const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0); |
|
439 |
|
440 if (glyph.fWidth) { |
|
441 SkScalar x = pos[0]; |
|
442 SkScalar y = scalarsPerPosition == 1 ? constY : pos[1]; |
|
443 |
|
444 this->drawPackedGlyph(GrGlyph::Pack(glyph.getGlyphID(), |
|
445 glyph.getSubXFixed(), |
|
446 glyph.getSubYFixed()), |
|
447 SkScalarToFixed(x) - (glyph.fAdvanceX >> alignShift) |
|
448 + SK_FixedHalf, //d1g.fHalfSampleX, |
|
449 SkScalarToFixed(y) - (glyph.fAdvanceY >> alignShift) |
|
450 + SK_FixedHalf, //d1g.fHalfSampleY, |
|
451 fontScaler); |
|
452 } |
|
453 pos += scalarsPerPosition; |
|
454 } |
|
455 } |
|
456 |
|
457 this->finish(); |
|
458 } |