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 "SkColorFilter.h"
11#include "GrDrawTarget.h"
12#include "GrDrawTargetCaps.h"
13#include "GrFontScaler.h"
14#include "SkGlyphCache.h"
15#include "GrGpu.h"
16#include "GrIndexBuffer.h"
17#include "GrStrokeInfo.h"
18#include "GrTextStrike.h"
19#include "GrTextStrike_impl.h"
20#include "SkDistanceFieldGen.h"
21#include "SkDraw.h"
22#include "SkGpuDevice.h"
23#include "SkPath.h"
24#include "SkRTConf.h"
25#include "SkStrokeRec.h"
26#include "effects/GrDistanceFieldTextureEffect.h"
27
28static const int kGlyphCoordsAttributeIndex = 1;
29
30static const int kSmallDFFontSize = 32;
31static const int kSmallDFFontLimit = 32;
32static const int kMediumDFFontSize = 64;
33static const int kMediumDFFontLimit = 64;
34static const int kLargeDFFontSize = 128;
35
36SK_CONF_DECLARE(bool, c_DumpFontCache, "gpu.dumpFontCache", false,
37                "Dump the contents of the font cache before every purge.");
38
39GrDistanceFieldTextContext::GrDistanceFieldTextContext(GrContext* context,
40                                                       const SkDeviceProperties& properties,
41                                                       bool enable)
42                                                    : GrTextContext(context, properties) {
43#if SK_FORCE_DISTANCEFIELD_FONTS
44    fEnableDFRendering = true;
45#else
46    fEnableDFRendering = enable;
47#endif
48    fStrike = NULL;
49    fGammaTexture = NULL;
50
51    fCurrTexture = NULL;
52    fCurrVertex = 0;
53
54    fVertices = NULL;
55    fMaxVertices = 0;
56}
57
58GrDistanceFieldTextContext::~GrDistanceFieldTextContext() {
59    this->flushGlyphs();
60    SkSafeSetNull(fGammaTexture);
61}
62
63bool GrDistanceFieldTextContext::canDraw(const SkPaint& paint) {
64    if (!fEnableDFRendering && !paint.isDistanceFieldTextTEMP()) {
65        return false;
66    }
67
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()) {
72        return false;
73    }
74
75    // TODO: add some stroking support
76    if (paint.getStyle() != SkPaint::kFill_Style) {
77        return false;
78    }
79
80    // TODO: choose an appropriate maximum scale for distance fields and
81    //       enable perspective
82    if (SkDraw::ShouldDrawTextAsPaths(paint, fContext->getMatrix())) {
83        return false;
84    }
85
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;
90}
91
92static 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);
97}
98
99void GrDistanceFieldTextContext::flushGlyphs() {
100    if (NULL == fDrawTarget) {
101        return;
102    }
103
104    GrDrawState* drawState = fDrawTarget->drawState();
105    GrDrawState::AutoRestoreEffects are(drawState);
106    drawState->setFromPaint(fPaint, fContext->getMatrix(), fContext->getRenderTarget());
107
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);
114
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());
120        } else {
121            filteredColor = fSkPaint.getColor();
122        }
123        if (fUseLCDText) {
124            GrColor colorNoPreMul = skcolor_to_grcolor_nopremultiply(filteredColor);
125            bool useBGR = SkDeviceProperties::Geometry::kBGR_Layout ==
126                                                            fDeviceProperties.fGeometry.getLayout();
127            drawState->addCoverageEffect(GrDistanceFieldLCDTextureEffect::Create(
128                                                            fCurrTexture,
129                                                            params,
130                                                            fGammaTexture,
131                                                            gammaParams,
132                                                            colorNoPreMul,
133                                                            fContext->getMatrix().rectStaysRect() &&
134                                                            fContext->getMatrix().isSimilarity(),
135                                                            useBGR),
136                                         kGlyphCoordsAttributeIndex)->unref();
137
138            if (kOne_GrBlendCoeff != fPaint.getSrcBlendCoeff() ||
139                kISA_GrBlendCoeff != fPaint.getDstBlendCoeff() ||
140                fPaint.numColorStages()) {
141                GrPrintf("LCD Text will not draw correctly.\n");
142            }
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());
148            // paintAlpha
149            drawState->setColor(SkColorSetARGB(a, a, a, a));
150            // paintColor
151            drawState->setBlendConstant(colorNoPreMul);
152            drawState->setBlendFunc(kConstC_GrBlendCoeff, kISC_GrBlendCoeff);
153        } else {
154#ifdef SK_GAMMA_APPLY_TO_A8
155            U8CPU lum = SkColorSpaceLuminance::computeLuminance(fDeviceProperties.fGamma,
156                                                                filteredColor);
157            drawState->addCoverageEffect(GrDistanceFieldTextureEffect::Create(
158                                                              fCurrTexture, params,
159                                                              fGammaTexture, gammaParams,
160                                                              lum/255.f,
161                                                              fContext->getMatrix().isSimilarity()),
162                                         kGlyphCoordsAttributeIndex)->unref();
163#else
164            drawState->addCoverageEffect(GrDistanceFieldTextureEffect::Create(
165                                                              fCurrTexture, params,
166                                                              fContext->getMatrix().isSimilarity()),
167                                         kGlyphCoordsAttributeIndex)->unref();
168#endif
169            // set back to normal in case we took LCD path previously.
170            drawState->setBlendFunc(fPaint.getSrcBlendCoeff(), fPaint.getDstBlendCoeff());
171            drawState->setColor(fPaint.getColor());
172        }
173
174        int nGlyphs = fCurrVertex / 4;
175        fDrawTarget->setIndexSourceToBuffer(fContext->getQuadIndexBuffer());
176        fDrawTarget->drawIndexedInstances(kTriangles_GrPrimitiveType,
177                                          nGlyphs,
178                                          4, 6);
179        fDrawTarget->resetVertexSource();
180        fVertices = NULL;
181        fMaxVertices = 0;
182        fCurrVertex = 0;
183        SkSafeSetNull(fCurrTexture);
184    }
185}
186
187namespace {
188
189// position + texture coord
190extern const GrVertexAttrib gTextVertexAttribs[] = {
191    {kVec2f_GrVertexAttribType, 0,               kPosition_GrVertexAttribBinding},
192    {kVec2f_GrVertexAttribType, sizeof(SkPoint), kEffect_GrVertexAttribBinding}
193};
194
195};
196
197void GrDistanceFieldTextContext::drawPackedGlyph(GrGlyph::PackedID packed,
198                                                 SkFixed vx, SkFixed vy,
199                                                 GrFontScaler* scaler) {
200    if (NULL == fDrawTarget) {
201        return;
202    }
203    if (NULL == fStrike) {
204        fStrike = fContext->getFontCache()->getStrike(scaler, true);
205    }
206
207    GrGlyph* glyph = fStrike->getGlyph(packed, scaler);
208    if (NULL == glyph || glyph->fBounds.isEmpty()) {
209        return;
210    }
211
212    SkScalar sx = SkFixedToScalar(vx);
213    SkScalar sy = SkFixedToScalar(vy);
214/*
215    // not valid, need to find a different solution for this
216    vx += SkIntToFixed(glyph->fBounds.fLeft);
217    vy += SkIntToFixed(glyph->fBounds.fTop);
218
219    // keep them as ints until we've done the clip-test
220    GrFixed width = glyph->fBounds.width();
221    GrFixed height = glyph->fBounds.height();
222
223    // check if we clipped out
224    if (true || NULL == glyph->fPlot) {
225        int x = vx >> 16;
226        int y = vy >> 16;
227        if (fClipRect.quickReject(x, y, x + width, y + height)) {
228//            SkCLZ(3);    // so we can set a break-point in the debugger
229            return;
230        }
231    }
232*/
233    if (NULL == glyph->fPlot) {
234        if (fStrike->addGlyphToAtlas(glyph, scaler)) {
235            goto HAS_ATLAS;
236        }
237
238        // try to clear out an unused plot before we flush
239        if (fContext->getFontCache()->freeUnusedPlot(fStrike) &&
240            fStrike->addGlyphToAtlas(glyph, scaler)) {
241            goto HAS_ATLAS;
242        }
243
244        if (c_DumpFontCache) {
245#ifdef SK_DEVELOPER
246            fContext->getFontCache()->dump();
247#endif
248        }
249
250        // before we purge the cache, we must flush any accumulated draws
251        this->flushGlyphs();
252        fContext->flush();
253
254        // we should have an unused plot now
255        if (fContext->getFontCache()->freeUnusedPlot(fStrike) &&
256            fStrike->addGlyphToAtlas(glyph, scaler)) {
257            goto HAS_ATLAS;
258        }
259
260        if (NULL == glyph->fPath) {
261            SkPath* path = SkNEW(SkPath);
262            if (!scaler->getGlyphPath(glyph->glyphID(), path)) {
263                // flag the glyph as being dead?
264                delete path;
265                return;
266            }
267            glyph->fPath = path;
268        }
269
270        GrContext::AutoMatrix am;
271        SkMatrix ctm;
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);
278        return;
279    }
280
281HAS_ATLAS:
282    SkASSERT(glyph->fPlot);
283    GrDrawTarget::DrawToken drawToken = fDrawTarget->getCurrentDrawToken();
284    glyph->fPlot->setDrawToken(drawToken);
285
286    GrTexture* texture = glyph->fPlot->texture();
287    SkASSERT(texture);
288
289    if (fCurrTexture != texture || fCurrVertex + 4 > fMaxVertices) {
290        this->flushGlyphs();
291        fCurrTexture = texture;
292        fCurrTexture->ref();
293    }
294
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);
302        if (flush) {
303            this->flushGlyphs();
304            fContext->flush();
305            fDrawTarget->drawState()->setVertexAttribs<gTextVertexAttribs>(
306                SK_ARRAY_COUNT(gTextVertexAttribs));
307        }
308        fMaxVertices = kDefaultRequestedVerts;
309        // ignore return, no point in flushing again.
310        fDrawTarget->geometryHints(&fMaxVertices, NULL);
311
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;
318        }
319        bool success = fDrawTarget->reserveVertexAndIndexSpace(fMaxVertices,
320                                                               0,
321                                                               GrTCast<void**>(&fVertices),
322                                                               NULL);
323        GrAlwaysAssert(success);
324        SkASSERT(2*sizeof(SkPoint) == fDrawTarget->getDrawState().getVertexSize());
325    }
326
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);
331
332    SkScalar scale = fTextRatio;
333    dx *= scale;
334    dy *= scale;
335    sx += dx;
336    sy += dy;
337    width *= scale;
338    height *= scale;
339
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);
344
345    static const size_t kVertexSize = 2 * sizeof(SkPoint);
346    fVertices[2*fCurrVertex].setRectFan(sx,
347                                        sy,
348                                        sx + width,
349                                        sy + height,
350                                        kVertexSize);
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)),
355                                          kVertexSize);
356    fCurrVertex += 4;
357}
358
359inline void GrDistanceFieldTextContext::init(const GrPaint& paint, const SkPaint& skPaint) {
360    GrTextContext::init(paint, skPaint);
361
362    fStrike = NULL;
363
364    fCurrTexture = NULL;
365    fCurrVertex = 0;
366
367    fVertices = NULL;
368    fMaxVertices = 0;
369
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));
376    } else {
377        fTextRatio = fSkPaint.getTextSize()/kLargeDFFontSize;
378        fSkPaint.setTextSize(SkIntToScalar(kLargeDFFontSize));
379    }
380
381    fUseLCDText = fSkPaint.isLCDRenderText();
382
383    fSkPaint.setLCDRenderText(false);
384    fSkPaint.setAutohinted(false);
385    fSkPaint.setHinting(SkPaint::kNormal_Hinting);
386    fSkPaint.setSubpixelText(true);
387
388}
389
390inline void GrDistanceFieldTextContext::finish() {
391    flushGlyphs();
392
393    GrTextContext::finish();
394}
395
396static void setup_gamma_texture(GrContext* context, const SkGlyphCache* cache,
397                                const SkDeviceProperties& deviceProperties,
398                                GrTexture** gammaTexture) {
399    if (NULL == *gammaTexture) {
400        int width, height;
401        size_t size;
402
403#ifdef SK_GAMMA_CONTRAST
404        SkScalar contrast = SK_GAMMA_CONTRAST;
405#else
406        SkScalar contrast = 0.5f;
407#endif
408        SkScalar paintGamma = deviceProperties.fGamma;
409        SkScalar deviceGamma = deviceProperties.fGamma;
410
411        size = SkScalerContext::GetGammaLUTSize(contrast, paintGamma, deviceGamma,
412                                                &width, &height);
413
414        SkAutoTArray<uint8_t> data((int)size);
415        SkScalerContext::GetGammaLUTData(contrast, paintGamma, deviceGamma, data.get());
416
417        // TODO: Update this to use the cache rather than directly creating a texture.
418        GrTextureDesc desc;
419        desc.fFlags = kDynamicUpdate_GrTextureFlagBit;
420        desc.fWidth = width;
421        desc.fHeight = height;
422        desc.fConfig = kAlpha_8_GrPixelConfig;
423
424        *gammaTexture = context->getGpu()->createTexture(desc, NULL, 0);
425        if (NULL == *gammaTexture) {
426            return;
427        }
428
429        context->writeTexturePixels(*gammaTexture,
430                                    0, 0, width, height,
431                                    (*gammaTexture)->config(), data.get(), 0,
432                                    GrContext::kDontFlush_PixelOpsFlag);
433    }
434}
435
436void 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);
440
441    // nothing to draw or can't draw
442    if (text == NULL || byteLength == 0 /* no raster clip? || fRC->isEmpty()*/
443        || fSkPaint.getRasterizer()) {
444        return;
445    }
446
447    this->init(paint, skPaint);
448
449    SkScalar sizeRatio = fTextRatio;
450
451    SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
452
453    SkAutoGlyphCacheNoGamma    autoCache(fSkPaint, &fDeviceProperties, NULL);
454    SkGlyphCache*              cache = autoCache.getCache();
455    GrFontScaler*              fontScaler = GetGrFontScaler(cache);
456
457    setup_gamma_texture(fContext, cache, fDeviceProperties, &fGammaTexture);
458
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) {
463        SkFixed    stopX = 0;
464        SkFixed    stopY = 0;
465
466        const char* textPtr = text;
467        while (textPtr < stop) {
468            // don't need x, y here, since all subpixel variants will have the
469            // same advance
470            const SkGlyph& glyph = glyphCacheProc(cache, &textPtr, 0, 0);
471
472            stopX += glyph.fAdvanceX;
473            stopY += glyph.fAdvanceY;
474        }
475        SkASSERT(textPtr == stop);
476
477        SkScalar alignX = SkFixedToScalar(stopX)*sizeRatio;
478        SkScalar alignY = SkFixedToScalar(stopY)*sizeRatio;
479
480        if (fSkPaint.getTextAlign() == SkPaint::kCenter_Align) {
481            alignX = SkScalarHalf(alignX);
482            alignY = SkScalarHalf(alignY);
483        }
484
485        x -= alignX;
486        y -= alignY;
487    }
488
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);
494
495        if (glyph.fWidth) {
496            this->drawPackedGlyph(GrGlyph::Pack(glyph.getGlyphID(),
497                                                glyph.getSubXFixed(),
498                                                glyph.getSubYFixed()),
499                                  fx,
500                                  fy,
501                                  fontScaler);
502        }
503
504        fx += SkFixedMul_portable(glyph.fAdvanceX, fixedScale);
505        fy += SkFixedMul_portable(glyph.fAdvanceY, fixedScale);
506    }
507
508    this->finish();
509}
510
511void GrDistanceFieldTextContext::drawPosText(const GrPaint& paint, const SkPaint& skPaint,
512                                             const char text[], size_t byteLength,
513                                             const SkScalar pos[], SkScalar constY,
514                                             int scalarsPerPosition) {
515
516    SkASSERT(byteLength == 0 || text != NULL);
517    SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition);
518
519    // nothing to draw
520    if (text == NULL || byteLength == 0 /* no raster clip? || fRC->isEmpty()*/) {
521        return;
522    }
523
524    this->init(paint, skPaint);
525
526    SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
527
528    SkAutoGlyphCacheNoGamma    autoCache(fSkPaint, &fDeviceProperties, NULL);
529    SkGlyphCache*              cache = autoCache.getCache();
530    GrFontScaler*              fontScaler = GetGrFontScaler(cache);
531
532    setup_gamma_texture(fContext, cache, fDeviceProperties, &fGammaTexture);
533
534    const char*        stop = text + byteLength;
535
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);
540
541            if (glyph.fWidth) {
542                SkScalar x = pos[0];
543                SkScalar y = scalarsPerPosition == 1 ? constY : pos[1];
544
545                this->drawPackedGlyph(GrGlyph::Pack(glyph.getGlyphID(),
546                                                    glyph.getSubXFixed(),
547                                                    glyph.getSubYFixed()),
548                                      SkScalarToFixed(x),
549                                      SkScalarToFixed(y),
550                                      fontScaler);
551            }
552            pos += scalarsPerPosition;
553        }
554    } else {
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);
559
560            if (glyph.fWidth) {
561                SkScalar x = pos[0];
562                SkScalar y = scalarsPerPosition == 1 ? constY : pos[1];
563
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),
569                                      fontScaler);
570            }
571            pos += scalarsPerPosition;
572        }
573    }
574
575    this->finish();
576}
577