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 SK_CONF_DECLARE(bool, c_DumpFontCache, "gpu.dumpFontCache", false,
29 "Dump the contents of the font cache before every purge.");
31 static const int kGlyphCoordsNoColorAttributeIndex = 1;
32 static const int kGlyphCoordsWithColorAttributeIndex = 2;
34 static const int kSmallDFFontSize = 32;
35 static const int kSmallDFFontLimit = 32;
36 static const int kMediumDFFontSize = 64;
37 static const int kMediumDFFontLimit = 64;
38 static const int kLargeDFFontSize = 128;
41 // position + texture coord
42 extern const GrVertexAttrib gTextVertexAttribs[] = {
43 {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding},
44 {kVec2f_GrVertexAttribType, sizeof(SkPoint) , kEffect_GrVertexAttribBinding}
47 // position + color + texture coord
48 extern const GrVertexAttrib gTextVertexWithColorAttribs[] = {
49 {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding},
50 {kVec4ub_GrVertexAttribType, sizeof(SkPoint), kColor_GrVertexAttribBinding},
51 {kVec2f_GrVertexAttribType, sizeof(SkPoint) + sizeof(GrColor), kEffect_GrVertexAttribBinding}
56 GrDistanceFieldTextContext::GrDistanceFieldTextContext(GrContext* context,
57 const SkDeviceProperties& properties,
59 : GrTextContext(context, properties) {
60 #if SK_FORCE_DISTANCEFIELD_FONTS
61 fEnableDFRendering = true;
63 fEnableDFRendering = enable;
70 fEffectTextureUniqueID = SK_InvalidUniqueID;
71 fEffectColor = GrColor_ILLEGAL;
77 fVertexBounds.setLargestInverted();
80 GrDistanceFieldTextContext::~GrDistanceFieldTextContext() {
82 SkSafeSetNull(fGammaTexture);
85 bool GrDistanceFieldTextContext::canDraw(const SkPaint& paint) {
86 if (!fEnableDFRendering && !paint.isDistanceFieldTextTEMP()) {
90 // rasterizers and mask filters modify alpha, which doesn't
91 // translate well to distance
92 if (paint.getRasterizer() || paint.getMaskFilter() ||
93 !fContext->getTextTarget()->caps()->shaderDerivativeSupport()) {
97 // TODO: add some stroking support
98 if (paint.getStyle() != SkPaint::kFill_Style) {
102 // TODO: choose an appropriate maximum scale for distance fields and
103 // enable perspective
104 if (SkDraw::ShouldDrawTextAsPaths(paint, fContext->getMatrix())) {
108 // distance fields cannot represent color fonts
109 SkScalerContext::Rec rec;
110 SkScalerContext::MakeRec(paint, &fDeviceProperties, NULL, &rec);
111 return rec.getFormat() != SkMask::kARGB32_Format;
114 static inline GrColor skcolor_to_grcolor_nopremultiply(SkColor c) {
115 unsigned r = SkColorGetR(c);
116 unsigned g = SkColorGetG(c);
117 unsigned b = SkColorGetB(c);
118 return GrColorPackRGBA(r, g, b, 0xff);
121 void GrDistanceFieldTextContext::setupCoverageEffect(const SkColor& filteredColor) {
122 GrTextureParams params(SkShader::kRepeat_TileMode, GrTextureParams::kBilerp_FilterMode);
123 GrTextureParams gammaParams(SkShader::kClamp_TileMode, GrTextureParams::kNone_FilterMode);
125 uint32_t textureUniqueID = fCurrTexture->getUniqueID();
129 flags |= fContext->getMatrix().isSimilarity() ? kSimilarity_DistanceFieldEffectFlag : 0;
130 flags |= fUseLCDText ? kUseLCD_DistanceFieldEffectFlag : 0;
131 flags |= fUseLCDText && fContext->getMatrix().rectStaysRect() ?
132 kRectToRect_DistanceFieldEffectFlag : 0;
133 bool useBGR = SkDeviceProperties::Geometry::kBGR_Layout ==
134 fDeviceProperties.fGeometry.getLayout();
135 flags |= fUseLCDText && useBGR ? kBGR_DistanceFieldEffectFlag : 0;
137 // see if we need to create a new effect
138 if (textureUniqueID != fEffectTextureUniqueID ||
139 filteredColor != fEffectColor ||
140 flags != fEffectFlags) {
142 GrColor colorNoPreMul = skcolor_to_grcolor_nopremultiply(filteredColor);
143 fCachedEffect.reset(GrDistanceFieldLCDTextureEffect::Create(fCurrTexture,
150 #ifdef SK_GAMMA_APPLY_TO_A8
151 U8CPU lum = SkColorSpaceLuminance::computeLuminance(fDeviceProperties.fGamma,
153 fCachedEffect.reset(GrDistanceFieldTextureEffect::Create(fCurrTexture,
160 fCachedEffect.reset(GrDistanceFieldTextureEffect::Create(fCurrTexture,
164 fEffectTextureUniqueID = textureUniqueID;
165 fEffectColor = filteredColor;
166 fEffectFlags = flags;
171 void GrDistanceFieldTextContext::flushGlyphs() {
172 if (NULL == fDrawTarget) {
176 GrDrawState* drawState = fDrawTarget->drawState();
177 GrDrawState::AutoRestoreEffects are(drawState);
178 drawState->setFromPaint(fPaint, fContext->getMatrix(), fContext->getRenderTarget());
180 if (fCurrVertex > 0) {
181 // setup our sampler state for our text texture/atlas
182 SkASSERT(SkIsAlign4(fCurrVertex));
184 // get our current color
185 SkColor filteredColor;
186 SkColorFilter* colorFilter = fSkPaint.getColorFilter();
187 if (NULL != colorFilter) {
188 filteredColor = colorFilter->filterColor(fSkPaint.getColor());
190 filteredColor = fSkPaint.getColor();
192 this->setupCoverageEffect(filteredColor);
194 // Effects could be stored with one of the cache objects (atlas?)
195 int coordsIdx = drawState->hasColorVertexAttribute() ? kGlyphCoordsWithColorAttributeIndex :
196 kGlyphCoordsNoColorAttributeIndex;
197 drawState->addCoverageEffect(fCachedEffect.get(), coordsIdx);
201 GrColor colorNoPreMul = skcolor_to_grcolor_nopremultiply(filteredColor);
202 if (kOne_GrBlendCoeff != fPaint.getSrcBlendCoeff() ||
203 kISA_GrBlendCoeff != fPaint.getDstBlendCoeff() ||
204 fPaint.numColorStages()) {
205 GrPrintf("LCD Text will not draw correctly.\n");
207 SkASSERT(!drawState->hasColorVertexAttribute());
208 // We don't use the GrPaint's color in this case because it's been premultiplied by
209 // alpha. Instead we feed in a non-premultiplied color, and multiply its alpha by
210 // the mask texture color. The end result is that we get
211 // mask*paintAlpha*paintColor + (1-mask*paintAlpha)*dstColor
212 int a = SkColorGetA(fSkPaint.getColor());
214 drawState->setColor(SkColorSetARGB(a, a, a, a));
216 drawState->setBlendConstant(colorNoPreMul);
217 drawState->setBlendFunc(kConstC_GrBlendCoeff, kISC_GrBlendCoeff);
219 // set back to normal in case we took LCD path previously.
220 drawState->setBlendFunc(fPaint.getSrcBlendCoeff(), fPaint.getDstBlendCoeff());
221 // We're using per-vertex color.
222 SkASSERT(drawState->hasColorVertexAttribute());
224 int nGlyphs = fCurrVertex / 4;
225 fDrawTarget->setIndexSourceToBuffer(fContext->getQuadIndexBuffer());
226 fDrawTarget->drawIndexedInstances(kTriangles_GrPrimitiveType,
228 4, 6, &fVertexBounds);
229 fDrawTarget->resetVertexSource();
233 SkSafeSetNull(fCurrTexture);
234 fVertexBounds.setLargestInverted();
238 void GrDistanceFieldTextContext::drawPackedGlyph(GrGlyph::PackedID packed,
239 SkFixed vx, SkFixed vy,
240 GrFontScaler* scaler) {
241 if (NULL == fDrawTarget) {
245 if (NULL == fStrike) {
246 fStrike = fContext->getFontCache()->getStrike(scaler, true);
249 GrGlyph* glyph = fStrike->getGlyph(packed, scaler);
250 if (NULL == glyph || glyph->fBounds.isEmpty()) {
254 SkScalar sx = SkFixedToScalar(vx);
255 SkScalar sy = SkFixedToScalar(vy);
257 // not valid, need to find a different solution for this
258 vx += SkIntToFixed(glyph->fBounds.fLeft);
259 vy += SkIntToFixed(glyph->fBounds.fTop);
261 // keep them as ints until we've done the clip-test
262 GrFixed width = glyph->fBounds.width();
263 GrFixed height = glyph->fBounds.height();
265 // check if we clipped out
266 if (true || NULL == glyph->fPlot) {
269 if (fClipRect.quickReject(x, y, x + width, y + height)) {
270 // SkCLZ(3); // so we can set a break-point in the debugger
275 if (NULL == glyph->fPlot) {
276 if (fStrike->addGlyphToAtlas(glyph, scaler)) {
280 // try to clear out an unused plot before we flush
281 if (fContext->getFontCache()->freeUnusedPlot(fStrike) &&
282 fStrike->addGlyphToAtlas(glyph, scaler)) {
286 if (c_DumpFontCache) {
288 fContext->getFontCache()->dump();
292 // before we purge the cache, we must flush any accumulated draws
296 // we should have an unused plot now
297 if (fContext->getFontCache()->freeUnusedPlot(fStrike) &&
298 fStrike->addGlyphToAtlas(glyph, scaler)) {
302 if (NULL == glyph->fPath) {
303 SkPath* path = SkNEW(SkPath);
304 if (!scaler->getGlyphPath(glyph->glyphID(), path)) {
305 // flag the glyph as being dead?
312 GrContext::AutoMatrix am;
314 ctm.setScale(fTextRatio, fTextRatio);
315 ctm.postTranslate(sx, sy);
316 GrPaint tmpPaint(fPaint);
317 am.setPreConcat(fContext, ctm, &tmpPaint);
318 GrStrokeInfo strokeInfo(SkStrokeRec::kFill_InitStyle);
319 fContext->drawPath(tmpPaint, *glyph->fPath, strokeInfo);
324 SkASSERT(glyph->fPlot);
325 GrDrawTarget::DrawToken drawToken = fDrawTarget->getCurrentDrawToken();
326 glyph->fPlot->setDrawToken(drawToken);
328 GrTexture* texture = glyph->fPlot->texture();
331 if (fCurrTexture != texture || fCurrVertex + 4 > fMaxVertices) {
333 fCurrTexture = texture;
337 bool useColorVerts = !fUseLCDText;
339 if (NULL == fVertices) {
340 // If we need to reserve vertices allow the draw target to suggest
341 // a number of verts to reserve and whether to perform a flush.
342 fMaxVertices = kMinRequestedVerts;
344 fDrawTarget->drawState()->setVertexAttribs<gTextVertexWithColorAttribs>(
345 SK_ARRAY_COUNT(gTextVertexWithColorAttribs));
347 fDrawTarget->drawState()->setVertexAttribs<gTextVertexAttribs>(
348 SK_ARRAY_COUNT(gTextVertexAttribs));
350 bool flush = fDrawTarget->geometryHints(&fMaxVertices, NULL);
355 fDrawTarget->drawState()->setVertexAttribs<gTextVertexWithColorAttribs>(
356 SK_ARRAY_COUNT(gTextVertexWithColorAttribs));
358 fDrawTarget->drawState()->setVertexAttribs<gTextVertexAttribs>(
359 SK_ARRAY_COUNT(gTextVertexAttribs));
362 fMaxVertices = kDefaultRequestedVerts;
363 // ignore return, no point in flushing again.
364 fDrawTarget->geometryHints(&fMaxVertices, NULL);
366 int maxQuadVertices = 4 * fContext->getQuadIndexBuffer()->maxQuads();
367 if (fMaxVertices < kMinRequestedVerts) {
368 fMaxVertices = kDefaultRequestedVerts;
369 } else if (fMaxVertices > maxQuadVertices) {
370 // don't exceed the limit of the index buffer
371 fMaxVertices = maxQuadVertices;
373 bool success = fDrawTarget->reserveVertexAndIndexSpace(fMaxVertices,
377 GrAlwaysAssert(success);
380 SkScalar dx = SkIntToScalar(glyph->fBounds.fLeft + SK_DistanceFieldInset);
381 SkScalar dy = SkIntToScalar(glyph->fBounds.fTop + SK_DistanceFieldInset);
382 SkScalar width = SkIntToScalar(glyph->fBounds.width() - 2*SK_DistanceFieldInset);
383 SkScalar height = SkIntToScalar(glyph->fBounds.height() - 2*SK_DistanceFieldInset);
385 SkScalar scale = fTextRatio;
393 SkFixed tx = SkIntToFixed(glyph->fAtlasLocation.fX + SK_DistanceFieldInset);
394 SkFixed ty = SkIntToFixed(glyph->fAtlasLocation.fY + SK_DistanceFieldInset);
395 SkFixed tw = SkIntToFixed(glyph->fBounds.width() - 2*SK_DistanceFieldInset);
396 SkFixed th = SkIntToFixed(glyph->fBounds.height() - 2*SK_DistanceFieldInset);
401 r.fRight = sx + width;
402 r.fBottom = sy + height;
404 fVertexBounds.growToInclude(r);
406 size_t vertSize = fUseLCDText ? (2 * sizeof(SkPoint))
407 : (2 * sizeof(SkPoint) + sizeof(GrColor));
409 SkASSERT(vertSize == fDrawTarget->getDrawState().getVertexSize());
411 SkPoint* positions = reinterpret_cast<SkPoint*>(
412 reinterpret_cast<intptr_t>(fVertices) + vertSize * fCurrVertex);
413 positions->setRectFan(r.fLeft, r.fTop, r.fRight, r.fBottom, vertSize);
415 // The texture coords are last in both the with and without color vertex layouts.
416 SkPoint* textureCoords = reinterpret_cast<SkPoint*>(
417 reinterpret_cast<intptr_t>(positions) + vertSize - sizeof(SkPoint));
418 textureCoords->setRectFan(SkFixedToFloat(texture->normalizeFixedX(tx)),
419 SkFixedToFloat(texture->normalizeFixedY(ty)),
420 SkFixedToFloat(texture->normalizeFixedX(tx + tw)),
421 SkFixedToFloat(texture->normalizeFixedY(ty + th)),
424 if (0xFF == GrColorUnpackA(fPaint.getColor())) {
425 fDrawTarget->drawState()->setHint(GrDrawState::kVertexColorsAreOpaque_Hint, true);
427 // color comes after position.
428 GrColor* colors = reinterpret_cast<GrColor*>(positions + 1);
429 for (int i = 0; i < 4; ++i) {
430 *colors = fPaint.getColor();
431 colors = reinterpret_cast<GrColor*>(reinterpret_cast<intptr_t>(colors) + vertSize);
438 inline void GrDistanceFieldTextContext::init(const GrPaint& paint, const SkPaint& skPaint) {
439 GrTextContext::init(paint, skPaint);
447 if (fSkPaint.getTextSize() <= kSmallDFFontLimit) {
448 fTextRatio = fSkPaint.getTextSize()/kSmallDFFontSize;
449 fSkPaint.setTextSize(SkIntToScalar(kSmallDFFontSize));
450 } else if (fSkPaint.getTextSize() <= kMediumDFFontLimit) {
451 fTextRatio = fSkPaint.getTextSize()/kMediumDFFontSize;
452 fSkPaint.setTextSize(SkIntToScalar(kMediumDFFontSize));
454 fTextRatio = fSkPaint.getTextSize()/kLargeDFFontSize;
455 fSkPaint.setTextSize(SkIntToScalar(kLargeDFFontSize));
458 fUseLCDText = fSkPaint.isLCDRenderText();
460 fSkPaint.setLCDRenderText(false);
461 fSkPaint.setAutohinted(false);
462 fSkPaint.setHinting(SkPaint::kNormal_Hinting);
463 fSkPaint.setSubpixelText(true);
467 inline void GrDistanceFieldTextContext::finish() {
470 GrTextContext::finish();
473 static void setup_gamma_texture(GrContext* context, const SkGlyphCache* cache,
474 const SkDeviceProperties& deviceProperties,
475 GrTexture** gammaTexture) {
476 if (NULL == *gammaTexture) {
480 #ifdef SK_GAMMA_CONTRAST
481 SkScalar contrast = SK_GAMMA_CONTRAST;
483 SkScalar contrast = 0.5f;
485 SkScalar paintGamma = deviceProperties.fGamma;
486 SkScalar deviceGamma = deviceProperties.fGamma;
488 size = SkScalerContext::GetGammaLUTSize(contrast, paintGamma, deviceGamma,
491 SkAutoTArray<uint8_t> data((int)size);
492 SkScalerContext::GetGammaLUTData(contrast, paintGamma, deviceGamma, data.get());
494 // TODO: Update this to use the cache rather than directly creating a texture.
496 desc.fFlags = kDynamicUpdate_GrTextureFlagBit;
498 desc.fHeight = height;
499 desc.fConfig = kAlpha_8_GrPixelConfig;
501 *gammaTexture = context->getGpu()->createTexture(desc, NULL, 0);
502 if (NULL == *gammaTexture) {
506 context->writeTexturePixels(*gammaTexture,
508 (*gammaTexture)->config(), data.get(), 0,
509 GrContext::kDontFlush_PixelOpsFlag);
513 void GrDistanceFieldTextContext::drawText(const GrPaint& paint, const SkPaint& skPaint,
514 const char text[], size_t byteLength,
515 SkScalar x, SkScalar y) {
516 SkASSERT(byteLength == 0 || text != NULL);
518 // nothing to draw or can't draw
519 if (text == NULL || byteLength == 0 /* no raster clip? || fRC->isEmpty()*/
520 || fSkPaint.getRasterizer()) {
524 this->init(paint, skPaint);
526 SkScalar sizeRatio = fTextRatio;
528 SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
530 SkAutoGlyphCacheNoGamma autoCache(fSkPaint, &fDeviceProperties, NULL);
531 SkGlyphCache* cache = autoCache.getCache();
532 GrFontScaler* fontScaler = GetGrFontScaler(cache);
534 setup_gamma_texture(fContext, cache, fDeviceProperties, &fGammaTexture);
536 // need to measure first
537 // TODO - generate positions and pre-load cache as well?
538 const char* stop = text + byteLength;
539 if (fSkPaint.getTextAlign() != SkPaint::kLeft_Align) {
543 const char* textPtr = text;
544 while (textPtr < stop) {
545 // don't need x, y here, since all subpixel variants will have the
547 const SkGlyph& glyph = glyphCacheProc(cache, &textPtr, 0, 0);
549 stopX += glyph.fAdvanceX;
550 stopY += glyph.fAdvanceY;
552 SkASSERT(textPtr == stop);
554 SkScalar alignX = SkFixedToScalar(stopX)*sizeRatio;
555 SkScalar alignY = SkFixedToScalar(stopY)*sizeRatio;
557 if (fSkPaint.getTextAlign() == SkPaint::kCenter_Align) {
558 alignX = SkScalarHalf(alignX);
559 alignY = SkScalarHalf(alignY);
566 SkFixed fx = SkScalarToFixed(x);
567 SkFixed fy = SkScalarToFixed(y);
568 SkFixed fixedScale = SkScalarToFixed(sizeRatio);
569 while (text < stop) {
570 const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
573 this->drawPackedGlyph(GrGlyph::Pack(glyph.getGlyphID(),
574 glyph.getSubXFixed(),
575 glyph.getSubYFixed()),
581 fx += SkFixedMul_portable(glyph.fAdvanceX, fixedScale);
582 fy += SkFixedMul_portable(glyph.fAdvanceY, fixedScale);
588 void GrDistanceFieldTextContext::drawPosText(const GrPaint& paint, const SkPaint& skPaint,
589 const char text[], size_t byteLength,
590 const SkScalar pos[], SkScalar constY,
591 int scalarsPerPosition) {
593 SkASSERT(byteLength == 0 || text != NULL);
594 SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition);
597 if (text == NULL || byteLength == 0 /* no raster clip? || fRC->isEmpty()*/) {
601 this->init(paint, skPaint);
603 SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
605 SkAutoGlyphCacheNoGamma autoCache(fSkPaint, &fDeviceProperties, NULL);
606 SkGlyphCache* cache = autoCache.getCache();
607 GrFontScaler* fontScaler = GetGrFontScaler(cache);
609 setup_gamma_texture(fContext, cache, fDeviceProperties, &fGammaTexture);
611 const char* stop = text + byteLength;
613 if (SkPaint::kLeft_Align == fSkPaint.getTextAlign()) {
614 while (text < stop) {
615 // the last 2 parameters are ignored
616 const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
620 SkScalar y = scalarsPerPosition == 1 ? constY : pos[1];
622 this->drawPackedGlyph(GrGlyph::Pack(glyph.getGlyphID(),
623 glyph.getSubXFixed(),
624 glyph.getSubYFixed()),
629 pos += scalarsPerPosition;
632 int alignShift = SkPaint::kCenter_Align == fSkPaint.getTextAlign() ? 1 : 0;
633 while (text < stop) {
634 // the last 2 parameters are ignored
635 const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
639 SkScalar y = scalarsPerPosition == 1 ? constY : pos[1];
641 this->drawPackedGlyph(GrGlyph::Pack(glyph.getGlyphID(),
642 glyph.getSubXFixed(),
643 glyph.getSubYFixed()),
644 SkScalarToFixed(x) - (glyph.fAdvanceX >> alignShift),
645 SkScalarToFixed(y) - (glyph.fAdvanceY >> alignShift),
648 pos += scalarsPerPosition;