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 kSmallDFFontSize = 32;
32 static const int kSmallDFFontLimit = 32;
33 static const int kMediumDFFontSize = 64;
34 static const int kMediumDFFontLimit = 64;
35 static const int kLargeDFFontSize = 128;
38 // position + texture coord
39 extern const GrVertexAttrib gTextVertexAttribs[] = {
40 {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding},
41 {kVec2f_GrVertexAttribType, sizeof(SkPoint) , kGeometryProcessor_GrVertexAttribBinding}
44 static const size_t kTextVASize = 2 * sizeof(SkPoint);
46 // position + color + texture coord
47 extern const GrVertexAttrib gTextVertexWithColorAttribs[] = {
48 {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding},
49 {kVec4ub_GrVertexAttribType, sizeof(SkPoint), kColor_GrVertexAttribBinding},
50 {kVec2f_GrVertexAttribType, sizeof(SkPoint) + sizeof(GrColor), kGeometryProcessor_GrVertexAttribBinding}
53 static const size_t kTextVAColorSize = 2 * sizeof(SkPoint) + sizeof(GrColor);
57 GrDistanceFieldTextContext::GrDistanceFieldTextContext(GrContext* context,
58 const SkDeviceProperties& properties,
60 : GrTextContext(context, properties) {
61 #if SK_FORCE_DISTANCEFIELD_FONTS
62 fEnableDFRendering = true;
64 fEnableDFRendering = enable;
71 fEffectTextureUniqueID = SK_InvalidUniqueID;
72 fEffectColor = GrColor_ILLEGAL;
78 fVertexBounds.setLargestInverted();
81 GrDistanceFieldTextContext::~GrDistanceFieldTextContext() {
83 SkSafeSetNull(fGammaTexture);
86 bool GrDistanceFieldTextContext::canDraw(const SkPaint& paint) {
87 if (!fEnableDFRendering && !paint.isDistanceFieldTextTEMP()) {
91 // rasterizers and mask filters modify alpha, which doesn't
92 // translate well to distance
93 if (paint.getRasterizer() || paint.getMaskFilter() ||
94 !fContext->getTextTarget()->caps()->shaderDerivativeSupport()) {
98 // TODO: add some stroking support
99 if (paint.getStyle() != SkPaint::kFill_Style) {
103 // TODO: choose an appropriate maximum scale for distance fields and
104 // enable perspective
105 if (SkDraw::ShouldDrawTextAsPaths(paint, fContext->getMatrix())) {
109 // distance fields cannot represent color fonts
110 SkScalerContext::Rec rec;
111 SkScalerContext::MakeRec(paint, &fDeviceProperties, NULL, &rec);
112 return rec.getFormat() != SkMask::kARGB32_Format;
115 static inline GrColor skcolor_to_grcolor_nopremultiply(SkColor c) {
116 unsigned r = SkColorGetR(c);
117 unsigned g = SkColorGetG(c);
118 unsigned b = SkColorGetB(c);
119 return GrColorPackRGBA(r, g, b, 0xff);
122 void GrDistanceFieldTextContext::setupCoverageEffect(const SkColor& filteredColor) {
123 GrTextureParams params(SkShader::kRepeat_TileMode, GrTextureParams::kBilerp_FilterMode);
124 GrTextureParams gammaParams(SkShader::kClamp_TileMode, GrTextureParams::kNone_FilterMode);
126 uint32_t textureUniqueID = fCurrTexture->getUniqueID();
127 const SkMatrix& ctm = fContext->getMatrix();
131 flags |= ctm.isSimilarity() ? kSimilarity_DistanceFieldEffectFlag : 0;
132 flags |= fUseLCDText ? kUseLCD_DistanceFieldEffectFlag : 0;
133 flags |= fUseLCDText && ctm.rectStaysRect() ?
134 kRectToRect_DistanceFieldEffectFlag : 0;
135 bool useBGR = SkPixelGeometryIsBGR(fDeviceProperties.fPixelGeometry);
136 flags |= fUseLCDText && useBGR ? kBGR_DistanceFieldEffectFlag : 0;
138 // see if we need to create a new effect
139 if (textureUniqueID != fEffectTextureUniqueID ||
140 filteredColor != fEffectColor ||
141 flags != fEffectFlags) {
143 GrColor colorNoPreMul = skcolor_to_grcolor_nopremultiply(filteredColor);
144 fCachedGeometryProcessor.reset(
145 GrDistanceFieldLCDTextureEffect::Create(fCurrTexture,
152 #ifdef SK_GAMMA_APPLY_TO_A8
153 U8CPU lum = SkColorSpaceLuminance::computeLuminance(fDeviceProperties.getGamma(),
155 fCachedGeometryProcessor.reset(
156 GrDistanceFieldTextureEffect::Create(fCurrTexture,
163 fCachedGeometryProcessor.reset(GrDistanceFieldTextureEffect::Create(fCurrTexture,
167 fEffectTextureUniqueID = textureUniqueID;
168 fEffectColor = filteredColor;
169 fEffectFlags = flags;
174 void GrDistanceFieldTextContext::flushGlyphs() {
175 if (NULL == fDrawTarget) {
179 GrDrawState* drawState = fDrawTarget->drawState();
180 GrDrawState::AutoRestoreEffects are(drawState);
182 drawState->setFromPaint(fPaint, fContext->getMatrix(), fContext->getRenderTarget());
184 if (fCurrVertex > 0) {
185 // setup our sampler state for our text texture/atlas
186 SkASSERT(SkIsAlign4(fCurrVertex));
188 // get our current color
189 SkColor filteredColor;
190 SkColorFilter* colorFilter = fSkPaint.getColorFilter();
192 filteredColor = colorFilter->filterColor(fSkPaint.getColor());
194 filteredColor = fSkPaint.getColor();
196 this->setupCoverageEffect(filteredColor);
198 // Effects could be stored with one of the cache objects (atlas?)
199 drawState->setGeometryProcessor(fCachedGeometryProcessor.get());
203 GrColor colorNoPreMul = skcolor_to_grcolor_nopremultiply(filteredColor);
204 if (kOne_GrBlendCoeff != fPaint.getSrcBlendCoeff() ||
205 kISA_GrBlendCoeff != fPaint.getDstBlendCoeff() ||
206 fPaint.numColorStages()) {
207 GrPrintf("LCD Text will not draw correctly.\n");
209 SkASSERT(!drawState->hasColorVertexAttribute());
210 // We don't use the GrPaint's color in this case because it's been premultiplied by
211 // alpha. Instead we feed in a non-premultiplied color, and multiply its alpha by
212 // the mask texture color. The end result is that we get
213 // mask*paintAlpha*paintColor + (1-mask*paintAlpha)*dstColor
214 int a = SkColorGetA(fSkPaint.getColor());
216 drawState->setColor(SkColorSetARGB(a, a, a, a));
218 drawState->setBlendConstant(colorNoPreMul);
219 drawState->setBlendFunc(kConstC_GrBlendCoeff, kISC_GrBlendCoeff);
221 // set back to normal in case we took LCD path previously.
222 drawState->setBlendFunc(fPaint.getSrcBlendCoeff(), fPaint.getDstBlendCoeff());
223 // We're using per-vertex color.
224 SkASSERT(drawState->hasColorVertexAttribute());
226 int nGlyphs = fCurrVertex / 4;
227 fDrawTarget->setIndexSourceToBuffer(fContext->getQuadIndexBuffer());
228 fDrawTarget->drawIndexedInstances(kTriangles_GrPrimitiveType,
230 4, 6, &fVertexBounds);
231 fDrawTarget->resetVertexSource();
235 SkSafeSetNull(fCurrTexture);
236 fVertexBounds.setLargestInverted();
240 void GrDistanceFieldTextContext::drawPackedGlyph(GrGlyph::PackedID packed,
241 SkFixed vx, SkFixed vy,
242 GrFontScaler* scaler) {
243 if (NULL == fDrawTarget) {
247 if (NULL == fStrike) {
248 fStrike = fContext->getFontCache()->getStrike(scaler, true);
251 GrGlyph* glyph = fStrike->getGlyph(packed, scaler);
252 if (NULL == glyph || glyph->fBounds.isEmpty()) {
256 SkScalar sx = SkFixedToScalar(vx);
257 SkScalar sy = SkFixedToScalar(vy);
259 // not valid, need to find a different solution for this
260 vx += SkIntToFixed(glyph->fBounds.fLeft);
261 vy += SkIntToFixed(glyph->fBounds.fTop);
263 // keep them as ints until we've done the clip-test
264 GrFixed width = glyph->fBounds.width();
265 GrFixed height = glyph->fBounds.height();
267 // check if we clipped out
268 if (true || NULL == glyph->fPlot) {
271 if (fClipRect.quickReject(x, y, x + width, y + height)) {
272 // SkCLZ(3); // so we can set a break-point in the debugger
277 if (NULL == glyph->fPlot) {
278 if (!fStrike->glyphTooLargeForAtlas(glyph)) {
279 if (fStrike->addGlyphToAtlas(glyph, scaler)) {
283 // try to clear out an unused plot before we flush
284 if (fContext->getFontCache()->freeUnusedPlot(fStrike) &&
285 fStrike->addGlyphToAtlas(glyph, scaler)) {
289 if (c_DumpFontCache) {
291 fContext->getFontCache()->dump();
295 // before we purge the cache, we must flush any accumulated draws
299 // we should have an unused plot now
300 if (fContext->getFontCache()->freeUnusedPlot(fStrike) &&
301 fStrike->addGlyphToAtlas(glyph, scaler)) {
306 if (NULL == glyph->fPath) {
307 SkPath* path = SkNEW(SkPath);
308 if (!scaler->getGlyphPath(glyph->glyphID(), path)) {
309 // flag the glyph as being dead?
316 GrContext::AutoMatrix am;
318 ctm.setScale(fTextRatio, fTextRatio);
319 ctm.postTranslate(sx, sy);
320 GrPaint tmpPaint(fPaint);
321 am.setPreConcat(fContext, ctm, &tmpPaint);
322 GrStrokeInfo strokeInfo(SkStrokeRec::kFill_InitStyle);
323 fContext->drawPath(tmpPaint, *glyph->fPath, strokeInfo);
328 SkASSERT(glyph->fPlot);
329 GrDrawTarget::DrawToken drawToken = fDrawTarget->getCurrentDrawToken();
330 glyph->fPlot->setDrawToken(drawToken);
332 GrTexture* texture = glyph->fPlot->texture();
335 if (fCurrTexture != texture || fCurrVertex + 4 > fMaxVertices) {
337 fCurrTexture = texture;
341 bool useColorVerts = !fUseLCDText;
343 if (NULL == fVertices) {
344 // If we need to reserve vertices allow the draw target to suggest
345 // a number of verts to reserve and whether to perform a flush.
346 fMaxVertices = kMinRequestedVerts;
348 fDrawTarget->drawState()->setVertexAttribs<gTextVertexWithColorAttribs>(
349 SK_ARRAY_COUNT(gTextVertexWithColorAttribs),
352 fDrawTarget->drawState()->setVertexAttribs<gTextVertexAttribs>(
353 SK_ARRAY_COUNT(gTextVertexAttribs),
356 bool flush = fDrawTarget->geometryHints(&fMaxVertices, NULL);
361 fDrawTarget->drawState()->setVertexAttribs<gTextVertexWithColorAttribs>(
362 SK_ARRAY_COUNT(gTextVertexWithColorAttribs),
365 fDrawTarget->drawState()->setVertexAttribs<gTextVertexAttribs>(
366 SK_ARRAY_COUNT(gTextVertexAttribs),
370 fMaxVertices = kDefaultRequestedVerts;
371 // ignore return, no point in flushing again.
372 fDrawTarget->geometryHints(&fMaxVertices, NULL);
374 int maxQuadVertices = 4 * fContext->getQuadIndexBuffer()->maxQuads();
375 if (fMaxVertices < kMinRequestedVerts) {
376 fMaxVertices = kDefaultRequestedVerts;
377 } else if (fMaxVertices > maxQuadVertices) {
378 // don't exceed the limit of the index buffer
379 fMaxVertices = maxQuadVertices;
381 bool success = fDrawTarget->reserveVertexAndIndexSpace(fMaxVertices,
385 GrAlwaysAssert(success);
388 SkScalar dx = SkIntToScalar(glyph->fBounds.fLeft + SK_DistanceFieldInset);
389 SkScalar dy = SkIntToScalar(glyph->fBounds.fTop + SK_DistanceFieldInset);
390 SkScalar width = SkIntToScalar(glyph->fBounds.width() - 2*SK_DistanceFieldInset);
391 SkScalar height = SkIntToScalar(glyph->fBounds.height() - 2*SK_DistanceFieldInset);
393 SkScalar scale = fTextRatio;
401 SkFixed tx = SkIntToFixed(glyph->fAtlasLocation.fX + SK_DistanceFieldInset);
402 SkFixed ty = SkIntToFixed(glyph->fAtlasLocation.fY + SK_DistanceFieldInset);
403 SkFixed tw = SkIntToFixed(glyph->fBounds.width() - 2*SK_DistanceFieldInset);
404 SkFixed th = SkIntToFixed(glyph->fBounds.height() - 2*SK_DistanceFieldInset);
409 r.fRight = sx + width;
410 r.fBottom = sy + height;
412 fVertexBounds.growToInclude(r);
414 size_t vertSize = fUseLCDText ? (2 * sizeof(SkPoint))
415 : (2 * sizeof(SkPoint) + sizeof(GrColor));
417 SkASSERT(vertSize == fDrawTarget->getDrawState().getVertexStride());
419 SkPoint* positions = reinterpret_cast<SkPoint*>(
420 reinterpret_cast<intptr_t>(fVertices) + vertSize * fCurrVertex);
421 positions->setRectFan(r.fLeft, r.fTop, r.fRight, r.fBottom, vertSize);
423 // The texture coords are last in both the with and without color vertex layouts.
424 SkPoint* textureCoords = reinterpret_cast<SkPoint*>(
425 reinterpret_cast<intptr_t>(positions) + vertSize - sizeof(SkPoint));
426 textureCoords->setRectFan(SkFixedToFloat(texture->normalizeFixedX(tx)),
427 SkFixedToFloat(texture->normalizeFixedY(ty)),
428 SkFixedToFloat(texture->normalizeFixedX(tx + tw)),
429 SkFixedToFloat(texture->normalizeFixedY(ty + th)),
432 if (0xFF == GrColorUnpackA(fPaint.getColor())) {
433 fDrawTarget->drawState()->setHint(GrDrawState::kVertexColorsAreOpaque_Hint, true);
435 // color comes after position.
436 GrColor* colors = reinterpret_cast<GrColor*>(positions + 1);
437 for (int i = 0; i < 4; ++i) {
438 *colors = fPaint.getColor();
439 colors = reinterpret_cast<GrColor*>(reinterpret_cast<intptr_t>(colors) + vertSize);
446 inline void GrDistanceFieldTextContext::init(const GrPaint& paint, const SkPaint& skPaint) {
447 GrTextContext::init(paint, skPaint);
451 const SkMatrix& ctm = fContext->getMatrix();
453 // getMaxScale doesn't support perspective, so neither do we at the moment
454 SkASSERT(!ctm.hasPerspective());
455 SkScalar maxScale = ctm.getMaxScale();
456 SkScalar textSize = fSkPaint.getTextSize();
457 SkScalar scaledTextSize = textSize;
458 // if we have non-unity scale, we need to choose our base text size
459 // based on the SkPaint's text size multiplied by the max scale factor
460 // TODO: do we need to do this if we're scaling down (i.e. maxScale < 1)?
461 if (maxScale > 0 && !SkScalarNearlyEqual(maxScale, SK_Scalar1)) {
462 scaledTextSize *= maxScale;
469 if (scaledTextSize <= kSmallDFFontLimit) {
470 fTextRatio = textSize / kSmallDFFontSize;
471 fSkPaint.setTextSize(SkIntToScalar(kSmallDFFontSize));
472 } else if (scaledTextSize <= kMediumDFFontLimit) {
473 fTextRatio = textSize / kMediumDFFontSize;
474 fSkPaint.setTextSize(SkIntToScalar(kMediumDFFontSize));
476 fTextRatio = textSize / kLargeDFFontSize;
477 fSkPaint.setTextSize(SkIntToScalar(kLargeDFFontSize));
480 fUseLCDText = fSkPaint.isLCDRenderText();
482 fSkPaint.setLCDRenderText(false);
483 fSkPaint.setAutohinted(false);
484 fSkPaint.setHinting(SkPaint::kNormal_Hinting);
485 fSkPaint.setSubpixelText(true);
489 inline void GrDistanceFieldTextContext::finish() {
492 GrTextContext::finish();
495 static void setup_gamma_texture(GrContext* context, const SkGlyphCache* cache,
496 const SkDeviceProperties& deviceProperties,
497 GrTexture** gammaTexture) {
498 if (NULL == *gammaTexture) {
502 #ifdef SK_GAMMA_CONTRAST
503 SkScalar contrast = SK_GAMMA_CONTRAST;
505 SkScalar contrast = 0.5f;
507 SkScalar paintGamma = deviceProperties.getGamma();
508 SkScalar deviceGamma = deviceProperties.getGamma();
510 size = SkScalerContext::GetGammaLUTSize(contrast, paintGamma, deviceGamma,
513 SkAutoTArray<uint8_t> data((int)size);
514 SkScalerContext::GetGammaLUTData(contrast, paintGamma, deviceGamma, data.get());
516 // TODO: Update this to use the cache rather than directly creating a texture.
518 desc.fFlags = kDynamicUpdate_GrTextureFlagBit;
520 desc.fHeight = height;
521 desc.fConfig = kAlpha_8_GrPixelConfig;
523 *gammaTexture = context->getGpu()->createTexture(desc, NULL, 0);
524 if (NULL == *gammaTexture) {
528 context->writeTexturePixels(*gammaTexture,
530 (*gammaTexture)->config(), data.get(), 0,
531 GrContext::kDontFlush_PixelOpsFlag);
535 void GrDistanceFieldTextContext::drawText(const GrPaint& paint, const SkPaint& skPaint,
536 const char text[], size_t byteLength,
537 SkScalar x, SkScalar y) {
538 SkASSERT(byteLength == 0 || text != NULL);
540 // nothing to draw or can't draw
541 if (text == NULL || byteLength == 0 /* no raster clip? || fRC->isEmpty()*/
542 || fSkPaint.getRasterizer()) {
546 this->init(paint, skPaint);
548 SkScalar sizeRatio = fTextRatio;
550 SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
552 SkAutoGlyphCacheNoGamma autoCache(fSkPaint, &fDeviceProperties, NULL);
553 SkGlyphCache* cache = autoCache.getCache();
554 GrFontScaler* fontScaler = GetGrFontScaler(cache);
556 setup_gamma_texture(fContext, cache, fDeviceProperties, &fGammaTexture);
558 // need to measure first
559 // TODO - generate positions and pre-load cache as well?
560 const char* stop = text + byteLength;
561 if (fSkPaint.getTextAlign() != SkPaint::kLeft_Align) {
565 const char* textPtr = text;
566 while (textPtr < stop) {
567 // don't need x, y here, since all subpixel variants will have the
569 const SkGlyph& glyph = glyphCacheProc(cache, &textPtr, 0, 0);
571 stopX += glyph.fAdvanceX;
572 stopY += glyph.fAdvanceY;
574 SkASSERT(textPtr == stop);
576 SkScalar alignX = SkFixedToScalar(stopX)*sizeRatio;
577 SkScalar alignY = SkFixedToScalar(stopY)*sizeRatio;
579 if (fSkPaint.getTextAlign() == SkPaint::kCenter_Align) {
580 alignX = SkScalarHalf(alignX);
581 alignY = SkScalarHalf(alignY);
588 SkFixed fx = SkScalarToFixed(x);
589 SkFixed fy = SkScalarToFixed(y);
590 SkFixed fixedScale = SkScalarToFixed(sizeRatio);
591 while (text < stop) {
592 const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
595 this->drawPackedGlyph(GrGlyph::Pack(glyph.getGlyphID(),
596 glyph.getSubXFixed(),
597 glyph.getSubYFixed()),
603 fx += SkFixedMul_portable(glyph.fAdvanceX, fixedScale);
604 fy += SkFixedMul_portable(glyph.fAdvanceY, fixedScale);
610 void GrDistanceFieldTextContext::drawPosText(const GrPaint& paint, const SkPaint& skPaint,
611 const char text[], size_t byteLength,
612 const SkScalar pos[], SkScalar constY,
613 int scalarsPerPosition) {
615 SkASSERT(byteLength == 0 || text != NULL);
616 SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition);
619 if (text == NULL || byteLength == 0 /* no raster clip? || fRC->isEmpty()*/) {
623 this->init(paint, skPaint);
625 SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
627 SkAutoGlyphCacheNoGamma autoCache(fSkPaint, &fDeviceProperties, NULL);
628 SkGlyphCache* cache = autoCache.getCache();
629 GrFontScaler* fontScaler = GetGrFontScaler(cache);
631 setup_gamma_texture(fContext, cache, fDeviceProperties, &fGammaTexture);
633 const char* stop = text + byteLength;
635 if (SkPaint::kLeft_Align == fSkPaint.getTextAlign()) {
636 while (text < stop) {
637 // the last 2 parameters are ignored
638 const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
642 SkScalar y = scalarsPerPosition == 1 ? constY : pos[1];
644 this->drawPackedGlyph(GrGlyph::Pack(glyph.getGlyphID(),
645 glyph.getSubXFixed(),
646 glyph.getSubYFixed()),
651 pos += scalarsPerPosition;
654 int alignShift = SkPaint::kCenter_Align == fSkPaint.getTextAlign() ? 1 : 0;
655 while (text < stop) {
656 // the last 2 parameters are ignored
657 const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
661 SkScalar y = scalarsPerPosition == 1 ? constY : pos[1];
663 this->drawPackedGlyph(GrGlyph::Pack(glyph.getGlyphID(),
664 glyph.getSubXFixed(),
665 glyph.getSubYFixed()),
666 SkScalarToFixed(x) - (glyph.fAdvanceX >> alignShift),
667 SkScalarToFixed(y) - (glyph.fAdvanceY >> alignShift),
670 pos += scalarsPerPosition;