Home | History | Annotate | Download | only in gpu
      1 /*
      2  * Copyright 2014 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 "GrStencilAndCoverTextContext.h"
      9 #include "GrDrawTarget.h"
     10 #include "GrGpu.h"
     11 #include "GrPath.h"
     12 #include "GrPathRange.h"
     13 #include "SkAutoKern.h"
     14 #include "SkDraw.h"
     15 #include "SkDrawProcs.h"
     16 #include "SkGlyphCache.h"
     17 #include "SkGpuDevice.h"
     18 #include "SkPath.h"
     19 #include "SkTextMapStateProc.h"
     20 #include "SkTextFormatParams.h"
     21 
     22 GrStencilAndCoverTextContext::GrStencilAndCoverTextContext(
     23     GrContext* context, const SkDeviceProperties& properties)
     24     : GrTextContext(context, properties)
     25     , fPendingGlyphCount(0) {
     26 }
     27 
     28 GrStencilAndCoverTextContext::~GrStencilAndCoverTextContext() {
     29 }
     30 
     31 void GrStencilAndCoverTextContext::drawText(const GrPaint& paint,
     32                                             const SkPaint& skPaint,
     33                                             const char text[],
     34                                             size_t byteLength,
     35                                             SkScalar x, SkScalar y) {
     36     SkASSERT(byteLength == 0 || text != NULL);
     37 
     38     if (text == NULL || byteLength == 0 /*|| fRC->isEmpty()*/) {
     39         return;
     40     }
     41 
     42     // This is the slow path, mainly used by Skia unit tests.  The other
     43     // backends (8888, gpu, ...) use device-space dependent glyph caches. In
     44     // order to match the glyph positions that the other code paths produce, we
     45     // must also use device-space dependent glyph cache. This has the
     46     // side-effect that the glyph shape outline will be in device-space,
     47     // too. This in turn has the side-effect that NVPR can not stroke the paths,
     48     // as the stroke in NVPR is defined in object-space.
     49     // NOTE: here we have following coincidence that works at the moment:
     50     // - When using the device-space glyphs, the transforms we pass to NVPR
     51     // instanced drawing are the global transforms, and the view transform is
     52     // identity. NVPR can not use non-affine transforms in the instanced
     53     // drawing. This is taken care of by SkDraw::ShouldDrawTextAsPaths since it
     54     // will turn off the use of device-space glyphs when perspective transforms
     55     // are in use.
     56 
     57     this->init(paint, skPaint, byteLength, kMaxAccuracy_RenderMode);
     58 
     59     // Transform our starting point.
     60     if (fNeedsDeviceSpaceGlyphs) {
     61         SkPoint loc;
     62         fContextInitialMatrix.mapXY(x, y, &loc);
     63         x = loc.fX;
     64         y = loc.fY;
     65     }
     66 
     67     SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
     68 
     69     fTransformType = GrPathRendering::kTranslate_PathTransformType;
     70 
     71     const char* stop = text + byteLength;
     72 
     73     // Measure first if needed.
     74     if (fSkPaint.getTextAlign() != SkPaint::kLeft_Align) {
     75         SkFixed    stopX = 0;
     76         SkFixed    stopY = 0;
     77 
     78         const char* textPtr = text;
     79         while (textPtr < stop) {
     80             // We don't need x, y here, since all subpixel variants will have the
     81             // same advance.
     82             const SkGlyph& glyph = glyphCacheProc(fGlyphCache, &textPtr, 0, 0);
     83 
     84             stopX += glyph.fAdvanceX;
     85             stopY += glyph.fAdvanceY;
     86         }
     87         SkASSERT(textPtr == stop);
     88 
     89         SkScalar alignX = SkFixedToScalar(stopX) * fTextRatio;
     90         SkScalar alignY = SkFixedToScalar(stopY) * fTextRatio;
     91 
     92         if (fSkPaint.getTextAlign() == SkPaint::kCenter_Align) {
     93             alignX = SkScalarHalf(alignX);
     94             alignY = SkScalarHalf(alignY);
     95         }
     96 
     97         x -= alignX;
     98         y -= alignY;
     99     }
    100 
    101     SkAutoKern autokern;
    102 
    103     SkFixed fixedSizeRatio = SkScalarToFixed(fTextRatio);
    104 
    105     SkFixed fx = SkScalarToFixed(x);
    106     SkFixed fy = SkScalarToFixed(y);
    107     while (text < stop) {
    108         const SkGlyph& glyph = glyphCacheProc(fGlyphCache, &text, 0, 0);
    109         fx += SkFixedMul_portable(autokern.adjust(glyph), fixedSizeRatio);
    110         if (glyph.fWidth) {
    111             this->appendGlyph(glyph.getGlyphID(), SkFixedToScalar(fx), SkFixedToScalar(fy));
    112         }
    113 
    114         fx += SkFixedMul_portable(glyph.fAdvanceX, fixedSizeRatio);
    115         fy += SkFixedMul_portable(glyph.fAdvanceY, fixedSizeRatio);
    116     }
    117 
    118     this->finish();
    119 }
    120 
    121 void GrStencilAndCoverTextContext::drawPosText(const GrPaint& paint,
    122                                                const SkPaint& skPaint,
    123                                                const char text[],
    124                                                size_t byteLength,
    125                                                const SkScalar pos[],
    126                                                SkScalar constY,
    127                                                int scalarsPerPosition) {
    128     SkASSERT(byteLength == 0 || text != NULL);
    129     SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition);
    130 
    131     // nothing to draw
    132     if (text == NULL || byteLength == 0/* || fRC->isEmpty()*/) {
    133         return;
    134     }
    135 
    136     // This is the fast path.  Here we do not bake in the device-transform to
    137     // the glyph outline or the advances. This is because we do not need to
    138     // position the glyphs at all, since the caller has done the positioning.
    139     // The positioning is based on SkPaint::measureText of individual
    140     // glyphs. That already uses glyph cache without device transforms. Device
    141     // transform is not part of SkPaint::measureText API, and thus we use the
    142     // same glyphs as what were measured.
    143 
    144     const float textTranslateY = (1 == scalarsPerPosition ? constY : 0);
    145     this->init(paint, skPaint, byteLength, kMaxPerformance_RenderMode, textTranslateY);
    146 
    147     SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
    148 
    149     const char* stop = text + byteLength;
    150 
    151     if (SkPaint::kLeft_Align == fSkPaint.getTextAlign()) {
    152         if (1 == scalarsPerPosition) {
    153             fTransformType = GrPathRendering::kTranslateX_PathTransformType;
    154             while (text < stop) {
    155                 const SkGlyph& glyph = glyphCacheProc(fGlyphCache, &text, 0, 0);
    156                 if (glyph.fWidth) {
    157                     this->appendGlyph(glyph.getGlyphID(), *pos);
    158                 }
    159                 pos++;
    160             }
    161         } else {
    162             SkASSERT(2 == scalarsPerPosition);
    163             fTransformType = GrPathRendering::kTranslate_PathTransformType;
    164             while (text < stop) {
    165                 const SkGlyph& glyph = glyphCacheProc(fGlyphCache, &text, 0, 0);
    166                 if (glyph.fWidth) {
    167                     this->appendGlyph(glyph.getGlyphID(), pos[0], pos[1]);
    168                 }
    169                 pos += 2;
    170             }
    171         }
    172     } else {
    173         fTransformType = GrPathRendering::kTranslate_PathTransformType;
    174         SkTextMapStateProc tmsProc(SkMatrix::I(), 0, scalarsPerPosition);
    175         SkTextAlignProcScalar alignProc(fSkPaint.getTextAlign());
    176         while (text < stop) {
    177             const SkGlyph& glyph = glyphCacheProc(fGlyphCache, &text, 0, 0);
    178             if (glyph.fWidth) {
    179                 SkPoint tmsLoc;
    180                 tmsProc(pos, &tmsLoc);
    181                 SkPoint loc;
    182                 alignProc(tmsLoc, glyph, &loc);
    183 
    184                 this->appendGlyph(glyph.getGlyphID(), loc.x(), loc.y());
    185             }
    186             pos += scalarsPerPosition;
    187         }
    188     }
    189 
    190     this->finish();
    191 }
    192 
    193 bool GrStencilAndCoverTextContext::canDraw(const SkPaint& paint) {
    194     if (paint.getRasterizer()) {
    195         return false;
    196     }
    197     if (paint.getMaskFilter()) {
    198         return false;
    199     }
    200     if (paint.getPathEffect()) {
    201         return false;
    202     }
    203 
    204     // No hairlines unless we can map the 1 px width to the object space.
    205     if (paint.getStyle() == SkPaint::kStroke_Style
    206         && paint.getStrokeWidth() == 0
    207         && fContext->getMatrix().hasPerspective()) {
    208         return false;
    209     }
    210 
    211     // No color bitmap fonts.
    212     SkScalerContext::Rec    rec;
    213     SkScalerContext::MakeRec(paint, &fDeviceProperties, NULL, &rec);
    214     return rec.getFormat() != SkMask::kARGB32_Format;
    215 }
    216 
    217 static GrPathRange* get_gr_glyphs(GrContext* ctx,
    218                                   const SkTypeface* typeface,
    219                                   const SkDescriptor* desc,
    220                                   const SkStrokeRec& stroke) {
    221     static const GrCacheID::Domain gGlyphsDomain = GrCacheID::GenerateDomain();
    222 
    223     GrCacheID::Key key;
    224     uint64_t* keyData = key.fData64;
    225     keyData[0] = desc ? desc->getChecksum() : 0;
    226     keyData[0] = (keyData[0] << 32) | (typeface ? typeface->uniqueID() : 0);
    227     keyData[1] = GrPath::ComputeStrokeKey(stroke);
    228     GrResourceKey resourceKey = GrResourceKey(GrCacheID(gGlyphsDomain, key),
    229                                               GrPathRange::resourceType(), 0);
    230 
    231     SkAutoTUnref<GrPathRange> glyphs(
    232         static_cast<GrPathRange*>(ctx->findAndRefCachedResource(resourceKey)));
    233     if (NULL == glyphs || (NULL != desc && !glyphs->isEqualTo(*desc))) {
    234         glyphs.reset(ctx->getGpu()->pathRendering()->createGlyphs(typeface, desc, stroke));
    235         ctx->addResourceToCache(resourceKey, glyphs);
    236     }
    237 
    238     return glyphs.detach();
    239 }
    240 
    241 void GrStencilAndCoverTextContext::init(const GrPaint& paint,
    242                                         const SkPaint& skPaint,
    243                                         size_t textByteLength,
    244                                         RenderMode renderMode,
    245                                         SkScalar textTranslateY) {
    246     GrTextContext::init(paint, skPaint);
    247 
    248     fContextInitialMatrix = fContext->getMatrix();
    249 
    250     const bool otherBackendsWillDrawAsPaths =
    251         SkDraw::ShouldDrawTextAsPaths(skPaint, fContextInitialMatrix);
    252 
    253     fNeedsDeviceSpaceGlyphs = !otherBackendsWillDrawAsPaths &&
    254                               kMaxAccuracy_RenderMode == renderMode &&
    255                               SkToBool(fContextInitialMatrix.getType() &
    256                                        (SkMatrix::kScale_Mask | SkMatrix::kAffine_Mask));
    257 
    258     if (fNeedsDeviceSpaceGlyphs) {
    259         // SkDraw::ShouldDrawTextAsPaths takes care of perspective transforms.
    260         SkASSERT(!fContextInitialMatrix.hasPerspective());
    261         SkASSERT(0 == textTranslateY); // TODO: Handle textTranslateY in device-space usecase.
    262 
    263         fTextRatio = fTextInverseRatio = 1.0f;
    264 
    265         // Glyphs loaded by GPU path rendering have an inverted y-direction.
    266         SkMatrix m;
    267         m.setScale(1, -1);
    268         fContext->setMatrix(m);
    269 
    270         // Post-flip the initial matrix so we're left with just the flip after
    271         // the paint preConcats the inverse.
    272         m = fContextInitialMatrix;
    273         m.postScale(1, -1);
    274         fPaint.localCoordChangeInverse(m);
    275 
    276         // The whole shape (including stroke) will be baked into the glyph outlines. Make
    277         // NVPR just fill the baked shapes.
    278         fGlyphCache = fSkPaint.detachCache(&fDeviceProperties, &fContextInitialMatrix, false);
    279         fGlyphs = get_gr_glyphs(fContext, fGlyphCache->getScalerContext()->getTypeface(),
    280                                 &fGlyphCache->getDescriptor(),
    281                                 SkStrokeRec(SkStrokeRec::kFill_InitStyle));
    282     } else {
    283         // Don't bake strokes into the glyph outlines. We will stroke the glyphs
    284         // using the GPU instead. This is the fast path.
    285         SkStrokeRec gpuStroke = SkStrokeRec(fSkPaint);
    286         fSkPaint.setStyle(SkPaint::kFill_Style);
    287 
    288         if (gpuStroke.isHairlineStyle()) {
    289             // Approximate hairline stroke.
    290             SkScalar strokeWidth = SK_Scalar1 /
    291                 (SkVector::Make(fContextInitialMatrix.getScaleX(),
    292                                 fContextInitialMatrix.getSkewY()).length());
    293             gpuStroke.setStrokeStyle(strokeWidth, false /*strokeAndFill*/);
    294 
    295         } else if (fSkPaint.isFakeBoldText() &&
    296 #ifdef SK_USE_FREETYPE_EMBOLDEN
    297                    kMaxPerformance_RenderMode == renderMode &&
    298 #endif
    299                    SkStrokeRec::kStroke_Style != gpuStroke.getStyle()) {
    300 
    301             // Instead of baking fake bold into the glyph outlines, do it with the GPU stroke.
    302             SkScalar fakeBoldScale = SkScalarInterpFunc(fSkPaint.getTextSize(),
    303                                                         kStdFakeBoldInterpKeys,
    304                                                         kStdFakeBoldInterpValues,
    305                                                         kStdFakeBoldInterpLength);
    306             SkScalar extra = SkScalarMul(fSkPaint.getTextSize(), fakeBoldScale);
    307             gpuStroke.setStrokeStyle(gpuStroke.needToApply() ? gpuStroke.getWidth() + extra : extra,
    308                                      true /*strokeAndFill*/);
    309 
    310             fSkPaint.setFakeBoldText(false);
    311         }
    312 
    313         bool canUseRawPaths;
    314 
    315         if (otherBackendsWillDrawAsPaths || kMaxPerformance_RenderMode == renderMode) {
    316             // We can draw the glyphs from canonically sized paths.
    317             fTextRatio = fSkPaint.getTextSize() / SkPaint::kCanonicalTextSizeForPaths;
    318             fTextInverseRatio = SkPaint::kCanonicalTextSizeForPaths / fSkPaint.getTextSize();
    319 
    320             // Compensate for the glyphs being scaled by fTextRatio.
    321             if (!gpuStroke.isFillStyle()) {
    322                 gpuStroke.setStrokeStyle(gpuStroke.getWidth() / fTextRatio,
    323                                          SkStrokeRec::kStrokeAndFill_Style == gpuStroke.getStyle());
    324             }
    325 
    326             fSkPaint.setLinearText(true);
    327             fSkPaint.setLCDRenderText(false);
    328             fSkPaint.setAutohinted(false);
    329             fSkPaint.setHinting(SkPaint::kNo_Hinting);
    330             fSkPaint.setSubpixelText(true);
    331             fSkPaint.setTextSize(SkIntToScalar(SkPaint::kCanonicalTextSizeForPaths));
    332 
    333             canUseRawPaths = SK_Scalar1 == fSkPaint.getTextScaleX() &&
    334                              0 == fSkPaint.getTextSkewX() &&
    335                              !fSkPaint.isFakeBoldText() &&
    336                              !fSkPaint.isVerticalText();
    337         } else {
    338             fTextRatio = fTextInverseRatio = 1.0f;
    339             canUseRawPaths = false;
    340         }
    341 
    342         SkMatrix textMatrix;
    343         textMatrix.setTranslate(0, textTranslateY);
    344         // Glyphs loaded by GPU path rendering have an inverted y-direction.
    345         textMatrix.preScale(fTextRatio, -fTextRatio);
    346         fPaint.localCoordChange(textMatrix);
    347         fContext->concatMatrix(textMatrix);
    348 
    349         fGlyphCache = fSkPaint.detachCache(&fDeviceProperties, NULL, false);
    350         fGlyphs = canUseRawPaths ?
    351                       get_gr_glyphs(fContext, fSkPaint.getTypeface(), NULL, gpuStroke) :
    352                       get_gr_glyphs(fContext, fGlyphCache->getScalerContext()->getTypeface(),
    353                                     &fGlyphCache->getDescriptor(), gpuStroke);
    354     }
    355 
    356     fStateRestore.set(fDrawTarget->drawState());
    357 
    358     fDrawTarget->drawState()->setFromPaint(fPaint, fContext->getMatrix(),
    359                                            fContext->getRenderTarget());
    360 
    361     GR_STATIC_CONST_SAME_STENCIL(kStencilPass,
    362                                  kZero_StencilOp,
    363                                  kZero_StencilOp,
    364                                  kNotEqual_StencilFunc,
    365                                  0xffff,
    366                                  0x0000,
    367                                  0xffff);
    368 
    369     *fDrawTarget->drawState()->stencil() = kStencilPass;
    370 
    371     SkASSERT(0 == fPendingGlyphCount);
    372 }
    373 
    374 inline void GrStencilAndCoverTextContext::appendGlyph(uint16_t glyphID, float x) {
    375     SkASSERT(GrPathRendering::kTranslateX_PathTransformType == fTransformType);
    376 
    377     if (fPendingGlyphCount >= kGlyphBufferSize) {
    378         this->flush();
    379     }
    380 
    381     fIndexBuffer[fPendingGlyphCount] = glyphID;
    382     fTransformBuffer[fPendingGlyphCount] = fTextInverseRatio * x;
    383 
    384     ++fPendingGlyphCount;
    385 }
    386 
    387 inline void GrStencilAndCoverTextContext::appendGlyph(uint16_t glyphID, float x, float y) {
    388     SkASSERT(GrPathRendering::kTranslate_PathTransformType == fTransformType);
    389 
    390     if (fPendingGlyphCount >= kGlyphBufferSize) {
    391         this->flush();
    392     }
    393 
    394     fIndexBuffer[fPendingGlyphCount] = glyphID;
    395     fTransformBuffer[2 * fPendingGlyphCount] = fTextInverseRatio * x;
    396     fTransformBuffer[2 * fPendingGlyphCount + 1] = -fTextInverseRatio * y;
    397 
    398     ++fPendingGlyphCount;
    399 }
    400 
    401 void GrStencilAndCoverTextContext::flush() {
    402     if (0 == fPendingGlyphCount) {
    403         return;
    404     }
    405 
    406     fDrawTarget->drawPaths(fGlyphs, fIndexBuffer, fPendingGlyphCount,
    407                            fTransformBuffer, fTransformType, SkPath::kWinding_FillType);
    408 
    409     fPendingGlyphCount = 0;
    410 }
    411 
    412 void GrStencilAndCoverTextContext::finish() {
    413     this->flush();
    414 
    415     fGlyphs->unref();
    416     fGlyphs = NULL;
    417 
    418     SkGlyphCache::AttachCache(fGlyphCache);
    419     fGlyphCache = NULL;
    420 
    421     fDrawTarget->drawState()->stencil()->setDisabled();
    422     fStateRestore.set(NULL);
    423     fContext->setMatrix(fContextInitialMatrix);
    424     GrTextContext::finish();
    425 }
    426 
    427