2 * Copyright 2013 Google Inc.
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
8 #include "GrDistanceFieldTextContext.h"
10 #include "SkColorFilter.h"
11 #include "GrDrawTarget.h"
12 #include "GrDrawTargetCaps.h"
13 #include "GrFontScaler.h"
14 #include "SkGlyphCache.h"
16 #include "GrIndexBuffer.h"
17 #include "GrStrokeInfo.h"
18 #include "GrTextStrike.h"
19 #include "GrTextStrike_impl.h"
20 #include "SkDistanceFieldGen.h"
22 #include "SkGpuDevice.h"
25 #include "SkStrokeRec.h"
26 #include "effects/GrDistanceFieldTextureEffect.h"
28 static const int kGlyphCoordsAttributeIndex = 1;
30 static const int kSmallDFFontSize = 32;
31 static const int kSmallDFFontLimit = 32;
32 static const int kMediumDFFontSize = 64;
33 static const int kMediumDFFontLimit = 64;
34 static const int kLargeDFFontSize = 128;
36 SK_CONF_DECLARE(bool, c_DumpFontCache, "gpu.dumpFontCache", false,
37 "Dump the contents of the font cache before every purge.");
39 GrDistanceFieldTextContext::GrDistanceFieldTextContext(GrContext* context,
40 const SkDeviceProperties& properties,
42 : GrTextContext(context, properties) {
43 #if SK_FORCE_DISTANCEFIELD_FONTS
44 fEnableDFRendering = true;
46 fEnableDFRendering = enable;
58 GrDistanceFieldTextContext::~GrDistanceFieldTextContext() {
60 SkSafeSetNull(fGammaTexture);
63 bool GrDistanceFieldTextContext::canDraw(const SkPaint& paint) {
64 if (!fEnableDFRendering && !paint.isDistanceFieldTextTEMP()) {
68 // rasterizers and mask filters modify alpha, which doesn't
69 // translate well to distance
70 if (paint.getRasterizer() || paint.getMaskFilter() ||
71 !fContext->getTextTarget()->caps()->shaderDerivativeSupport()) {
75 // TODO: add some stroking support
76 if (paint.getStyle() != SkPaint::kFill_Style) {
80 // TODO: choose an appropriate maximum scale for distance fields and
82 if (SkDraw::ShouldDrawTextAsPaths(paint, fContext->getMatrix())) {
86 // distance fields cannot represent color fonts
87 SkScalerContext::Rec rec;
88 SkScalerContext::MakeRec(paint, &fDeviceProperties, NULL, &rec);
89 return rec.getFormat() != SkMask::kARGB32_Format;
92 static inline GrColor skcolor_to_grcolor_nopremultiply(SkColor c) {
93 unsigned r = SkColorGetR(c);
94 unsigned g = SkColorGetG(c);
95 unsigned b = SkColorGetB(c);
96 return GrColorPackRGBA(r, g, b, 0xff);
99 void GrDistanceFieldTextContext::flushGlyphs() {
100 if (NULL == fDrawTarget) {
104 GrDrawState* drawState = fDrawTarget->drawState();
105 GrDrawState::AutoRestoreEffects are(drawState);
106 drawState->setFromPaint(fPaint, fContext->getMatrix(), fContext->getRenderTarget());
108 if (fCurrVertex > 0) {
109 // setup our sampler state for our text texture/atlas
110 SkASSERT(SkIsAlign4(fCurrVertex));
111 SkASSERT(fCurrTexture);
112 GrTextureParams params(SkShader::kRepeat_TileMode, GrTextureParams::kBilerp_FilterMode);
113 GrTextureParams gammaParams(SkShader::kClamp_TileMode, GrTextureParams::kNone_FilterMode);
115 // Effects could be stored with one of the cache objects (atlas?)
116 SkColor filteredColor;
117 SkColorFilter* colorFilter = fSkPaint.getColorFilter();
118 if (NULL != colorFilter) {
119 filteredColor = colorFilter->filterColor(fSkPaint.getColor());
121 filteredColor = fSkPaint.getColor();
124 GrColor colorNoPreMul = skcolor_to_grcolor_nopremultiply(filteredColor);
125 bool useBGR = SkDeviceProperties::Geometry::kBGR_Layout ==
126 fDeviceProperties.fGeometry.getLayout();
127 drawState->addCoverageEffect(GrDistanceFieldLCDTextureEffect::Create(
133 fContext->getMatrix().rectStaysRect() &&
134 fContext->getMatrix().isSimilarity(),
136 kGlyphCoordsAttributeIndex)->unref();
138 if (kOne_GrBlendCoeff != fPaint.getSrcBlendCoeff() ||
139 kISA_GrBlendCoeff != fPaint.getDstBlendCoeff() ||
140 fPaint.numColorStages()) {
141 GrPrintf("LCD Text will not draw correctly.\n");
143 // We don't use the GrPaint's color in this case because it's been premultiplied by
144 // alpha. Instead we feed in a non-premultiplied color, and multiply its alpha by
145 // the mask texture color. The end result is that we get
146 // mask*paintAlpha*paintColor + (1-mask*paintAlpha)*dstColor
147 int a = SkColorGetA(fSkPaint.getColor());
149 drawState->setColor(SkColorSetARGB(a, a, a, a));
151 drawState->setBlendConstant(colorNoPreMul);
152 drawState->setBlendFunc(kConstC_GrBlendCoeff, kISC_GrBlendCoeff);
154 #ifdef SK_GAMMA_APPLY_TO_A8
155 U8CPU lum = SkColorSpaceLuminance::computeLuminance(fDeviceProperties.fGamma,
157 drawState->addCoverageEffect(GrDistanceFieldTextureEffect::Create(
158 fCurrTexture, params,
159 fGammaTexture, gammaParams,
161 fContext->getMatrix().isSimilarity()),
162 kGlyphCoordsAttributeIndex)->unref();
164 drawState->addCoverageEffect(GrDistanceFieldTextureEffect::Create(
165 fCurrTexture, params,
166 fContext->getMatrix().isSimilarity()),
167 kGlyphCoordsAttributeIndex)->unref();
169 // set back to normal in case we took LCD path previously.
170 drawState->setBlendFunc(fPaint.getSrcBlendCoeff(), fPaint.getDstBlendCoeff());
171 drawState->setColor(fPaint.getColor());
174 int nGlyphs = fCurrVertex / 4;
175 fDrawTarget->setIndexSourceToBuffer(fContext->getQuadIndexBuffer());
176 fDrawTarget->drawIndexedInstances(kTriangles_GrPrimitiveType,
179 fDrawTarget->resetVertexSource();
183 SkSafeSetNull(fCurrTexture);
189 // position + texture coord
190 extern const GrVertexAttrib gTextVertexAttribs[] = {
191 {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding},
192 {kVec2f_GrVertexAttribType, sizeof(SkPoint), kEffect_GrVertexAttribBinding}
197 void GrDistanceFieldTextContext::drawPackedGlyph(GrGlyph::PackedID packed,
198 SkFixed vx, SkFixed vy,
199 GrFontScaler* scaler) {
200 if (NULL == fDrawTarget) {
203 if (NULL == fStrike) {
204 fStrike = fContext->getFontCache()->getStrike(scaler, true);
207 GrGlyph* glyph = fStrike->getGlyph(packed, scaler);
208 if (NULL == glyph || glyph->fBounds.isEmpty()) {
212 SkScalar sx = SkFixedToScalar(vx);
213 SkScalar sy = SkFixedToScalar(vy);
215 // not valid, need to find a different solution for this
216 vx += SkIntToFixed(glyph->fBounds.fLeft);
217 vy += SkIntToFixed(glyph->fBounds.fTop);
219 // keep them as ints until we've done the clip-test
220 GrFixed width = glyph->fBounds.width();
221 GrFixed height = glyph->fBounds.height();
223 // check if we clipped out
224 if (true || NULL == glyph->fPlot) {
227 if (fClipRect.quickReject(x, y, x + width, y + height)) {
228 // SkCLZ(3); // so we can set a break-point in the debugger
233 if (NULL == glyph->fPlot) {
234 if (fStrike->addGlyphToAtlas(glyph, scaler)) {
238 // try to clear out an unused plot before we flush
239 if (fContext->getFontCache()->freeUnusedPlot(fStrike) &&
240 fStrike->addGlyphToAtlas(glyph, scaler)) {
244 if (c_DumpFontCache) {
246 fContext->getFontCache()->dump();
250 // before we purge the cache, we must flush any accumulated draws
254 // we should have an unused plot now
255 if (fContext->getFontCache()->freeUnusedPlot(fStrike) &&
256 fStrike->addGlyphToAtlas(glyph, scaler)) {
260 if (NULL == glyph->fPath) {
261 SkPath* path = SkNEW(SkPath);
262 if (!scaler->getGlyphPath(glyph->glyphID(), path)) {
263 // flag the glyph as being dead?
270 GrContext::AutoMatrix am;
272 ctm.setScale(fTextRatio, fTextRatio);
273 ctm.postTranslate(sx, sy);
274 GrPaint tmpPaint(fPaint);
275 am.setPreConcat(fContext, ctm, &tmpPaint);
276 GrStrokeInfo strokeInfo(SkStrokeRec::kFill_InitStyle);
277 fContext->drawPath(tmpPaint, *glyph->fPath, strokeInfo);
282 SkASSERT(glyph->fPlot);
283 GrDrawTarget::DrawToken drawToken = fDrawTarget->getCurrentDrawToken();
284 glyph->fPlot->setDrawToken(drawToken);
286 GrTexture* texture = glyph->fPlot->texture();
289 if (fCurrTexture != texture || fCurrVertex + 4 > fMaxVertices) {
291 fCurrTexture = texture;
295 if (NULL == fVertices) {
296 // If we need to reserve vertices allow the draw target to suggest
297 // a number of verts to reserve and whether to perform a flush.
298 fMaxVertices = kMinRequestedVerts;
299 fDrawTarget->drawState()->setVertexAttribs<gTextVertexAttribs>(
300 SK_ARRAY_COUNT(gTextVertexAttribs));
301 bool flush = fDrawTarget->geometryHints(&fMaxVertices, NULL);
305 fDrawTarget->drawState()->setVertexAttribs<gTextVertexAttribs>(
306 SK_ARRAY_COUNT(gTextVertexAttribs));
308 fMaxVertices = kDefaultRequestedVerts;
309 // ignore return, no point in flushing again.
310 fDrawTarget->geometryHints(&fMaxVertices, NULL);
312 int maxQuadVertices = 4 * fContext->getQuadIndexBuffer()->maxQuads();
313 if (fMaxVertices < kMinRequestedVerts) {
314 fMaxVertices = kDefaultRequestedVerts;
315 } else if (fMaxVertices > maxQuadVertices) {
316 // don't exceed the limit of the index buffer
317 fMaxVertices = maxQuadVertices;
319 bool success = fDrawTarget->reserveVertexAndIndexSpace(fMaxVertices,
321 GrTCast<void**>(&fVertices),
323 GrAlwaysAssert(success);
324 SkASSERT(2*sizeof(SkPoint) == fDrawTarget->getDrawState().getVertexSize());
327 SkScalar dx = SkIntToScalar(glyph->fBounds.fLeft + SK_DistanceFieldInset);
328 SkScalar dy = SkIntToScalar(glyph->fBounds.fTop + SK_DistanceFieldInset);
329 SkScalar width = SkIntToScalar(glyph->fBounds.width() - 2*SK_DistanceFieldInset);
330 SkScalar height = SkIntToScalar(glyph->fBounds.height() - 2*SK_DistanceFieldInset);
332 SkScalar scale = fTextRatio;
340 SkFixed tx = SkIntToFixed(glyph->fAtlasLocation.fX + SK_DistanceFieldInset);
341 SkFixed ty = SkIntToFixed(glyph->fAtlasLocation.fY + SK_DistanceFieldInset);
342 SkFixed tw = SkIntToFixed(glyph->fBounds.width() - 2*SK_DistanceFieldInset);
343 SkFixed th = SkIntToFixed(glyph->fBounds.height() - 2*SK_DistanceFieldInset);
345 static const size_t kVertexSize = 2 * sizeof(SkPoint);
346 fVertices[2*fCurrVertex].setRectFan(sx,
351 fVertices[2*fCurrVertex+1].setRectFan(SkFixedToFloat(texture->normalizeFixedX(tx)),
352 SkFixedToFloat(texture->normalizeFixedY(ty)),
353 SkFixedToFloat(texture->normalizeFixedX(tx + tw)),
354 SkFixedToFloat(texture->normalizeFixedY(ty + th)),
359 inline void GrDistanceFieldTextContext::init(const GrPaint& paint, const SkPaint& skPaint) {
360 GrTextContext::init(paint, skPaint);
370 if (fSkPaint.getTextSize() <= kSmallDFFontLimit) {
371 fTextRatio = fSkPaint.getTextSize()/kSmallDFFontSize;
372 fSkPaint.setTextSize(SkIntToScalar(kSmallDFFontSize));
373 } else if (fSkPaint.getTextSize() <= kMediumDFFontLimit) {
374 fTextRatio = fSkPaint.getTextSize()/kMediumDFFontSize;
375 fSkPaint.setTextSize(SkIntToScalar(kMediumDFFontSize));
377 fTextRatio = fSkPaint.getTextSize()/kLargeDFFontSize;
378 fSkPaint.setTextSize(SkIntToScalar(kLargeDFFontSize));
381 fUseLCDText = fSkPaint.isLCDRenderText();
383 fSkPaint.setLCDRenderText(false);
384 fSkPaint.setAutohinted(false);
385 fSkPaint.setHinting(SkPaint::kNormal_Hinting);
386 fSkPaint.setSubpixelText(true);
390 inline void GrDistanceFieldTextContext::finish() {
393 GrTextContext::finish();
396 static void setup_gamma_texture(GrContext* context, const SkGlyphCache* cache,
397 const SkDeviceProperties& deviceProperties,
398 GrTexture** gammaTexture) {
399 if (NULL == *gammaTexture) {
403 #ifdef SK_GAMMA_CONTRAST
404 SkScalar contrast = SK_GAMMA_CONTRAST;
406 SkScalar contrast = 0.5f;
408 SkScalar paintGamma = deviceProperties.fGamma;
409 SkScalar deviceGamma = deviceProperties.fGamma;
411 size = SkScalerContext::GetGammaLUTSize(contrast, paintGamma, deviceGamma,
414 SkAutoTArray<uint8_t> data((int)size);
415 SkScalerContext::GetGammaLUTData(contrast, paintGamma, deviceGamma, data.get());
417 // TODO: Update this to use the cache rather than directly creating a texture.
419 desc.fFlags = kDynamicUpdate_GrTextureFlagBit;
421 desc.fHeight = height;
422 desc.fConfig = kAlpha_8_GrPixelConfig;
424 *gammaTexture = context->getGpu()->createTexture(desc, NULL, 0);
425 if (NULL == *gammaTexture) {
429 context->writeTexturePixels(*gammaTexture,
431 (*gammaTexture)->config(), data.get(), 0,
432 GrContext::kDontFlush_PixelOpsFlag);
436 void GrDistanceFieldTextContext::drawText(const GrPaint& paint, const SkPaint& skPaint,
437 const char text[], size_t byteLength,
438 SkScalar x, SkScalar y) {
439 SkASSERT(byteLength == 0 || text != NULL);
441 // nothing to draw or can't draw
442 if (text == NULL || byteLength == 0 /* no raster clip? || fRC->isEmpty()*/
443 || fSkPaint.getRasterizer()) {
447 this->init(paint, skPaint);
449 SkScalar sizeRatio = fTextRatio;
451 SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
453 SkAutoGlyphCacheNoGamma autoCache(fSkPaint, &fDeviceProperties, NULL);
454 SkGlyphCache* cache = autoCache.getCache();
455 GrFontScaler* fontScaler = GetGrFontScaler(cache);
457 setup_gamma_texture(fContext, cache, fDeviceProperties, &fGammaTexture);
459 // need to measure first
460 // TODO - generate positions and pre-load cache as well?
461 const char* stop = text + byteLength;
462 if (fSkPaint.getTextAlign() != SkPaint::kLeft_Align) {
466 const char* textPtr = text;
467 while (textPtr < stop) {
468 // don't need x, y here, since all subpixel variants will have the
470 const SkGlyph& glyph = glyphCacheProc(cache, &textPtr, 0, 0);
472 stopX += glyph.fAdvanceX;
473 stopY += glyph.fAdvanceY;
475 SkASSERT(textPtr == stop);
477 SkScalar alignX = SkFixedToScalar(stopX)*sizeRatio;
478 SkScalar alignY = SkFixedToScalar(stopY)*sizeRatio;
480 if (fSkPaint.getTextAlign() == SkPaint::kCenter_Align) {
481 alignX = SkScalarHalf(alignX);
482 alignY = SkScalarHalf(alignY);
489 SkFixed fx = SkScalarToFixed(x);
490 SkFixed fy = SkScalarToFixed(y);
491 SkFixed fixedScale = SkScalarToFixed(sizeRatio);
492 while (text < stop) {
493 const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
496 this->drawPackedGlyph(GrGlyph::Pack(glyph.getGlyphID(),
497 glyph.getSubXFixed(),
498 glyph.getSubYFixed()),
504 fx += SkFixedMul_portable(glyph.fAdvanceX, fixedScale);
505 fy += SkFixedMul_portable(glyph.fAdvanceY, fixedScale);
511 void GrDistanceFieldTextContext::drawPosText(const GrPaint& paint, const SkPaint& skPaint,
512 const char text[], size_t byteLength,
513 const SkScalar pos[], SkScalar constY,
514 int scalarsPerPosition) {
516 SkASSERT(byteLength == 0 || text != NULL);
517 SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition);
520 if (text == NULL || byteLength == 0 /* no raster clip? || fRC->isEmpty()*/) {
524 this->init(paint, skPaint);
526 SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
528 SkAutoGlyphCacheNoGamma autoCache(fSkPaint, &fDeviceProperties, NULL);
529 SkGlyphCache* cache = autoCache.getCache();
530 GrFontScaler* fontScaler = GetGrFontScaler(cache);
532 setup_gamma_texture(fContext, cache, fDeviceProperties, &fGammaTexture);
534 const char* stop = text + byteLength;
536 if (SkPaint::kLeft_Align == fSkPaint.getTextAlign()) {
537 while (text < stop) {
538 // the last 2 parameters are ignored
539 const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
543 SkScalar y = scalarsPerPosition == 1 ? constY : pos[1];
545 this->drawPackedGlyph(GrGlyph::Pack(glyph.getGlyphID(),
546 glyph.getSubXFixed(),
547 glyph.getSubYFixed()),
552 pos += scalarsPerPosition;
555 int alignShift = SkPaint::kCenter_Align == fSkPaint.getTextAlign() ? 1 : 0;
556 while (text < stop) {
557 // the last 2 parameters are ignored
558 const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
562 SkScalar y = scalarsPerPosition == 1 ? constY : pos[1];
564 this->drawPackedGlyph(GrGlyph::Pack(glyph.getGlyphID(),
565 glyph.getSubXFixed(),
566 glyph.getSubYFixed()),
567 SkScalarToFixed(x) - (glyph.fAdvanceX >> alignShift),
568 SkScalarToFixed(y) - (glyph.fAdvanceY >> alignShift),
571 pos += scalarsPerPosition;