Reland: Allow DFPathRenderer to store bitmaps at low resolutions
authorJim Van Verth <jvanverth@google.com>
Tue, 28 Feb 2017 15:24:39 +0000 (10:24 -0500)
committerSkia Commit-Bot <skia-commit-bot@chromium.org>
Tue, 28 Feb 2017 16:27:08 +0000 (16:27 +0000)
BUG=chromium:682918

Change-Id: Ieadb41229227a20d41b8e932ba0770fe72479898
Reviewed-on: https://skia-review.googlesource.com/9068
Reviewed-by: Robert Phillips <robertphillips@google.com>
Commit-Queue: Jim Van Verth <jvanverth@google.com>

gm/pathfill.cpp
src/gpu/effects/GrDistanceFieldGeoProc.cpp
src/gpu/ops/GrAADistanceFieldPathRenderer.cpp
src/gpu/ops/GrAADistanceFieldPathRenderer.h

index a36ab1f..d96f356 100644 (file)
@@ -224,6 +224,54 @@ static void make_accessibility(SkPath* path) {
     path->close();
 }
 
+// test case for http://crbug.com/695196
+static void make_visualizer(SkPath* path) {
+    path->moveTo(1.9520f, 2.0000f);
+    path->conicTo(1.5573f, 1.9992f, 1.2782f, 2.2782f, 0.9235f);
+    path->conicTo(0.9992f, 2.5573f, 1.0000f, 2.9520f, 0.9235f);
+    path->lineTo(1.0000f, 5.4300f);
+    path->lineTo(17.0000f, 5.4300f);
+    path->lineTo(17.0000f, 2.9520f);
+    path->conicTo(17.0008f, 2.5573f, 16.7218f, 2.2782f, 0.9235f);
+    path->conicTo(16.4427f, 1.9992f, 16.0480f, 2.0000f, 0.9235f);
+    path->lineTo(1.9520f, 2.0000f);
+    path->close();
+    path->moveTo(2.7140f, 3.1430f);
+    path->conicTo(3.0547f, 3.1287f, 3.2292f, 3.4216f, 0.8590f);
+    path->conicTo(3.4038f, 3.7145f, 3.2292f, 4.0074f, 0.8590f);
+    path->conicTo(3.0547f, 4.3003f, 2.7140f, 4.2860f, 0.8590f);
+    path->conicTo(2.1659f, 4.2631f, 2.1659f, 3.7145f, 0.7217f);
+    path->conicTo(2.1659f, 3.1659f, 2.7140f, 3.1430f, 0.7217f);
+    path->lineTo(2.7140f, 3.1430f);
+    path->close();
+    path->moveTo(5.0000f, 3.1430f);
+    path->conicTo(5.3407f, 3.1287f, 5.5152f, 3.4216f, 0.8590f);
+    path->conicTo(5.6898f, 3.7145f, 5.5152f, 4.0074f, 0.8590f);
+    path->conicTo(5.3407f, 4.3003f, 5.0000f, 4.2860f, 0.8590f);
+    path->conicTo(4.4519f, 4.2631f, 4.4519f, 3.7145f, 0.7217f);
+    path->conicTo(4.4519f, 3.1659f, 5.0000f, 3.1430f, 0.7217f);
+    path->lineTo(5.0000f, 3.1430f);
+    path->close();
+    path->moveTo(7.2860f, 3.1430f);
+    path->conicTo(7.6267f, 3.1287f, 7.8012f, 3.4216f, 0.8590f);
+    path->conicTo(7.9758f, 3.7145f, 7.8012f, 4.0074f, 0.8590f);
+    path->conicTo(7.6267f, 4.3003f, 7.2860f, 4.2860f, 0.8590f);
+    path->conicTo(6.7379f, 4.2631f, 6.7379f, 3.7145f, 0.7217f);
+    path->conicTo(6.7379f, 3.1659f, 7.2860f, 3.1430f, 0.7217f);
+    path->close();
+    path->moveTo(1.0000f, 6.1900f);
+    path->lineTo(1.0000f, 14.3810f);
+    path->conicTo(0.9992f, 14.7757f, 1.2782f, 15.0548f, 0.9235f);
+    path->conicTo(1.5573f, 15.3338f, 1.9520f, 15.3330f, 0.9235f);
+    path->lineTo(16.0480f, 15.3330f);
+    path->conicTo(16.4427f, 15.3338f, 16.7218f, 15.0548f, 0.9235f);
+    path->conicTo(17.0008f, 14.7757f, 17.0000f, 14.3810f, 0.9235f);
+    path->lineTo(17.0000f, 6.1910f);
+    path->lineTo(1.0000f, 6.1910f);
+    path->lineTo(1.0000f, 6.1900f);
+    path->close();
+}
+
 constexpr MakePathProc gProcs[] = {
     make_frame,
     make_triangle,
@@ -244,6 +292,7 @@ class PathFillGM : public skiagm::GM {
     SkScalar fDY[N];
     SkPath  fInfoPath;
     SkPath  fAccessibilityPath;
+    SkPath  fVisualizerPath;
 protected:
     void onOnceBeforeDraw() override {
         for (size_t i = 0; i < N; i++) {
@@ -252,6 +301,7 @@ protected:
 
         make_info(&fInfoPath);
         make_accessibility(&fAccessibilityPath);
+        make_visualizer(&fVisualizerPath);
     }
 
 
@@ -281,6 +331,10 @@ protected:
         canvas->scale(2, 2);
         canvas->translate(5, 15);
         canvas->drawPath(fAccessibilityPath, paint);
+
+        canvas->scale(0.5f, 0.5f);
+        canvas->translate(5, 50);
+        canvas->drawPath(fVisualizerPath, paint);
     }
 
 private:
index 89ec238..1c2ef64 100644 (file)
@@ -518,7 +518,7 @@ GrDistanceFieldPathGeoProc::GrDistanceFieldPathGeoProc(
     fInPosition = &this->addVertexAttrib("inPosition", kVec2f_GrVertexAttribType,
                                          kHigh_GrSLPrecision);
     fInColor = &this->addVertexAttrib("inColor", kVec4ub_GrVertexAttribType);
-    fInTextureCoords = &this->addVertexAttrib("inTextureCoords", kVec2f_GrVertexAttribType);
+    fInTextureCoords = &this->addVertexAttrib("inTextureCoords", kVec2us_GrVertexAttribType);
     this->addTextureSampler(&fTextureSampler);
 }
 
@@ -542,7 +542,7 @@ GrDistanceFieldPathGeoProc::GrDistanceFieldPathGeoProc(
     fInPosition = &this->addVertexAttrib("inPosition", kVec2f_GrVertexAttribType,
                                          kHigh_GrSLPrecision);
     fInColor = &this->addVertexAttrib("inColor", kVec4ub_GrVertexAttribType);
-    fInTextureCoords = &this->addVertexAttrib("inTextureCoords", kVec2f_GrVertexAttribType);
+    fInTextureCoords = &this->addVertexAttrib("inTextureCoords", kVec2us_GrVertexAttribType);
     this->addTextureSampler(&fTextureSampler);
 }
 
index 7ed14ba..2db3e36 100644 (file)
@@ -18,6 +18,7 @@
 #include "GrSWMaskHelper.h"
 #include "GrSurfacePriv.h"
 #include "GrTexturePriv.h"
+#include "effects/GrBitmapTextGeoProc.h"
 #include "effects/GrDistanceFieldGeoProc.h"
 #include "ops/GrMeshDrawOp.h"
 
@@ -165,16 +166,36 @@ private:
                           bool gammaCorrect)
             : INHERITED(ClassID()) {
         SkASSERT(shape.hasUnstyledKey());
+        // Compute bounds
+        this->setTransformedBounds(shape.bounds(), viewMatrix, HasAABloat::kYes, IsZeroArea::kNo);
+
+#ifdef SK_BUILD_FOR_ANDROID
+        fUsesDistanceField = true;
+#else
+        // only use distance fields on desktop to save space in the atlas
+        fUsesDistanceField = this->bounds().width() > kMaxMIP || this->bounds().height() > kMaxMIP;
+#endif
         fViewMatrix = viewMatrix;
-        fShapes.emplace_back(Entry{color, shape});
+        SkVector translate = SkVector::Make(0, 0);
+        if (!fUsesDistanceField) {
+            // In this case we don't apply a view matrix, so we need to remove the non-subpixel
+            // translation and add it back when we generate the quad for the path
+            SkScalar translateX = viewMatrix.getTranslateX();
+            SkScalar translateY = viewMatrix.getTranslateY();
+            translate = SkVector::Make(SkScalarFloorToScalar(translateX),
+                                       SkScalarFloorToScalar(translateY));
+            // Only store the fractional part of the translation in the view matrix
+            fViewMatrix.setTranslateX(translateX - translate.fX);
+            fViewMatrix.setTranslateY(translateY - translate.fY);
+        }
+
+        fShapes.emplace_back(Entry{color, shape, translate});
 
         fAtlas = atlas;
         fShapeCache = shapeCache;
         fShapeList = shapeList;
         fGammaCorrect = gammaCorrect;
 
-        // Compute bounds
-        this->setTransformedBounds(shape.bounds(), viewMatrix, HasAABloat::kYes, IsZeroArea::kNo);
     }
 
     void getFragmentProcessorAnalysisInputs(FragmentProcessorAnalysisInputs* input) const override {
@@ -198,34 +219,45 @@ private:
     void onPrepareDraws(Target* target) const override {
         int instanceCount = fShapes.count();
 
-        SkMatrix invert;
-        if (this->usesLocalCoords() && !this->viewMatrix().invert(&invert)) {
-            SkDebugf("Could not invert viewmatrix\n");
-            return;
-        }
-
         const SkMatrix& ctm = this->viewMatrix();
-        uint32_t flags = 0;
-        flags |= ctm.isScaleTranslate() ? kScaleOnly_DistanceFieldEffectFlag : 0;
-        flags |= ctm.isSimilarity() ? kSimilarity_DistanceFieldEffectFlag : 0;
-        flags |= fGammaCorrect ? kGammaCorrect_DistanceFieldEffectFlag : 0;
-
-        GrSamplerParams params(SkShader::kRepeat_TileMode, GrSamplerParams::kBilerp_FilterMode);
 
         FlushInfo flushInfo;
 
         // Setup GrGeometryProcessor
         GrDrawOpAtlas* atlas = fAtlas;
-        flushInfo.fGeometryProcessor = GrDistanceFieldPathGeoProc::Make(this->color(),
-                                                                        this->viewMatrix(),
-                                                                        atlas->getTexture(),
-                                                                        params,
-                                                                        flags,
-                                                                        this->usesLocalCoords());
+        if (fUsesDistanceField) {
+            GrSamplerParams params(SkShader::kClamp_TileMode, GrSamplerParams::kBilerp_FilterMode);
+
+            uint32_t flags = 0;
+            flags |= ctm.isScaleTranslate() ? kScaleOnly_DistanceFieldEffectFlag : 0;
+            flags |= ctm.isSimilarity() ? kSimilarity_DistanceFieldEffectFlag : 0;
+            flags |= fGammaCorrect ? kGammaCorrect_DistanceFieldEffectFlag : 0;
+
+            flushInfo.fGeometryProcessor = GrDistanceFieldPathGeoProc::Make(
+                this->color(), this->viewMatrix(), atlas->getTexture(), params, flags,
+                this->usesLocalCoords());
+        } else {
+            GrSamplerParams params(SkShader::kClamp_TileMode, GrSamplerParams::kNone_FilterMode);
+
+            SkMatrix invert;
+            if (this->usesLocalCoords()) {
+                if (!this->viewMatrix().invert(&invert)) {
+                    SkDebugf("Could not invert viewmatrix\n");
+                    return;
+                }
+                // for local coords, we need to add the translation back in that we removed
+                // from the stored view matrix
+                invert.preTranslate(-fShapes[0].fTranslate.fX, -fShapes[0].fTranslate.fY);
+            }
+
+            flushInfo.fGeometryProcessor = GrBitmapTextGeoProc::Make(
+                this->color(), atlas->getTexture(), params, kA8_GrMaskFormat, invert,
+                this->usesLocalCoords());
+        }
 
         // allocate vertices
         size_t vertexStride = flushInfo.fGeometryProcessor->getVertexStride();
-        SkASSERT(vertexStride == 2 * sizeof(SkPoint) + sizeof(GrColor));
+        SkASSERT(vertexStride == sizeof(SkPoint) + sizeof(GrColor) + 2*sizeof(uint16_t));
 
         const GrBuffer* vertexBuffer;
         void* vertices = target->makeVertexSpace(vertexStride,
@@ -245,65 +277,94 @@ private:
         for (int i = 0; i < instanceCount; i++) {
             const Entry& args = fShapes[i];
 
-            // get mip level
-            SkScalar maxScale = SkScalarAbs(this->viewMatrix().getMaxScale());
-            const SkRect& bounds = args.fShape.bounds();
-            SkScalar maxDim = SkMaxScalar(bounds.width(), bounds.height());
-            // We try to create the DF at a power of two scaled path resolution (1/2, 1, 2, 4, etc)
-            // In the majority of cases this will yield a crisper rendering.
-            SkScalar mipScale = 1.0f;
-            // Our mipscale is the maxScale clamped to the next highest power of 2
-            if (maxScale <= SK_ScalarHalf) {
-                SkScalar log = SkScalarFloorToScalar(SkScalarLog2(SkScalarInvert(maxScale)));
-                mipScale = SkScalarPow(2, -log);
-            } else if (maxScale > SK_Scalar1) {
-                SkScalar log = SkScalarCeilToScalar(SkScalarLog2(maxScale));
-                mipScale = SkScalarPow(2, log);
-            }
-            SkASSERT(maxScale <= mipScale);
-
-            SkScalar mipSize = mipScale*SkScalarAbs(maxDim);
-            // For sizes less than kIdealMinMIP we want to use as large a distance field as we can
-            // so we can preserve as much detail as possible. However, we can't scale down more
-            // than a 1/4 of the size without artifacts. So the idea is that we pick the mipsize
-            // just bigger than the ideal, and then scale down until we are no more than 4x the
-            // original mipsize.
-            if (mipSize < kIdealMinMIP) {
-                SkScalar newMipSize = mipSize;
-                do {
-                    newMipSize *= 2;
-                } while (newMipSize < kIdealMinMIP);
-                while (newMipSize > 4*mipSize) {
-                    newMipSize *= 0.25f;
+            ShapeData* shapeData;
+            SkScalar maxScale;
+            if (fUsesDistanceField) {
+                // get mip level
+                maxScale = SkScalarAbs(this->viewMatrix().getMaxScale());
+                const SkRect& bounds = args.fShape.bounds();
+                SkScalar maxDim = SkMaxScalar(bounds.width(), bounds.height());
+                // We try to create the DF at a 2^n scaled path resolution (1/2, 1, 2, 4, etc.)
+                // In the majority of cases this will yield a crisper rendering.
+                SkScalar mipScale = 1.0f;
+                // Our mipscale is the maxScale clamped to the next highest power of 2
+                if (maxScale <= SK_ScalarHalf) {
+                    SkScalar log = SkScalarFloorToScalar(SkScalarLog2(SkScalarInvert(maxScale)));
+                    mipScale = SkScalarPow(2, -log);
+                } else if (maxScale > SK_Scalar1) {
+                    SkScalar log = SkScalarCeilToScalar(SkScalarLog2(maxScale));
+                    mipScale = SkScalarPow(2, log);
                 }
-                mipSize = newMipSize;
-            }
-            SkScalar desiredDimension = SkTMin(mipSize, kMaxMIP);
-
-            // check to see if path is cached
-            ShapeData::Key key(args.fShape, SkScalarCeilToInt(desiredDimension));
-            ShapeData* shapeData = fShapeCache->find(key);
-            if (nullptr == shapeData || !atlas->hasID(shapeData->fID)) {
-                // Remove the stale cache entry
-                if (shapeData) {
-                    fShapeCache->remove(shapeData->fKey);
-                    fShapeList->remove(shapeData);
-                    delete shapeData;
+                SkASSERT(maxScale <= mipScale);
+
+                SkScalar mipSize = mipScale*SkScalarAbs(maxDim);
+                // For sizes less than kIdealMinMIP we want to use as large a distance field as we can
+                // so we can preserve as much detail as possible. However, we can't scale down more
+                // than a 1/4 of the size without artifacts. So the idea is that we pick the mipsize
+                // just bigger than the ideal, and then scale down until we are no more than 4x the
+                // original mipsize.
+                if (mipSize < kIdealMinMIP) {
+                    SkScalar newMipSize = mipSize;
+                    do {
+                        newMipSize *= 2;
+                    } while (newMipSize < kIdealMinMIP);
+                    while (newMipSize > 4 * mipSize) {
+                        newMipSize *= 0.25f;
+                    }
+                    mipSize = newMipSize;
+                }
+                SkScalar desiredDimension = SkTMin(mipSize, kMaxMIP);
+
+                // check to see if df path is cached
+                ShapeData::Key key(args.fShape, SkScalarCeilToInt(desiredDimension));
+                shapeData = fShapeCache->find(key);
+                if (nullptr == shapeData || !atlas->hasID(shapeData->fID)) {
+                    // Remove the stale cache entry
+                    if (shapeData) {
+                        fShapeCache->remove(shapeData->fKey);
+                        fShapeList->remove(shapeData);
+                        delete shapeData;
+                    }
+                    SkScalar scale = desiredDimension / maxDim;
+
+                    shapeData = new ShapeData;
+                    if (!this->addDFPathToAtlas(target,
+                                                &flushInfo,
+                                                atlas,
+                                                shapeData,
+                                                args.fShape,
+                                                SkScalarCeilToInt(desiredDimension),
+                                                scale)) {
+                        delete shapeData;
+                        SkDebugf("Can't rasterize path\n");
+                        continue;
+                    }
                 }
-                SkScalar scale = desiredDimension/maxDim;
-
-                shapeData = new ShapeData;
-                if (!this->addPathToAtlas(target,
-                                          &flushInfo,
-                                          atlas,
-                                          shapeData,
-                                          args.fShape,
-                                          SkScalarCeilToInt(desiredDimension),
-                                          scale)) {
-                    delete shapeData;
-                    SkDebugf("Can't rasterize path\n");
-                    continue;
+            } else {
+                // check to see if bitmap path is cached
+                ShapeData::Key key(args.fShape, this->viewMatrix());
+                shapeData = fShapeCache->find(key);
+                if (nullptr == shapeData || !atlas->hasID(shapeData->fID)) {
+                    // Remove the stale cache entry
+                    if (shapeData) {
+                        fShapeCache->remove(shapeData->fKey);
+                        fShapeList->remove(shapeData);
+                        delete shapeData;
+                    }
+
+                    shapeData = new ShapeData;
+                    if (!this->addBMPathToAtlas(target,
+                                              &flushInfo,
+                                              atlas,
+                                              shapeData,
+                                              args.fShape,
+                                              this->viewMatrix())) {
+                        delete shapeData;
+                        SkDebugf("Can't rasterize path\n");
+                        continue;
+                    }
                 }
+                maxScale = 1;
             }
 
             atlas->setLastUseToken(shapeData->fID, target->nextDrawToken());
@@ -314,6 +375,7 @@ private:
                                     args.fColor,
                                     vertexStride,
                                     maxScale,
+                                    args.fTranslate,
                                     shapeData);
             offset += kVerticesPerQuad * vertexStride;
             flushInfo.fInstancesToFlush++;
@@ -322,9 +384,9 @@ private:
         this->flush(target, &flushInfo);
     }
 
-    bool addPathToAtlas(GrMeshDrawOp::Target* target, FlushInfo* flushInfo, GrDrawOpAtlas* atlas,
-                        ShapeData* shapeData, const GrShape& shape, uint32_t dimension,
-                        SkScalar scale) const {
+    bool addDFPathToAtlas(GrMeshDrawOp::Target* target, FlushInfo* flushInfo, GrDrawOpAtlas* atlas,
+                          ShapeData* shapeData, const GrShape& shape, uint32_t dimension,
+                          SkScalar scale) const {
         const SkRect& bounds = shape.bounds();
 
         // generate bounding rect for bitmap draw
@@ -439,23 +501,121 @@ private:
         return true;
     }
 
+    bool addBMPathToAtlas(GrMeshDrawOp::Target* target, FlushInfo* flushInfo, 
+                          GrDrawOpAtlas* atlas, ShapeData* shapeData,
+                          const GrShape& shape, const SkMatrix& ctm) const {
+        const SkRect& bounds = shape.bounds();
+        if (bounds.isEmpty()) {
+            return false;
+        }
+        SkMatrix drawMatrix(ctm);
+        drawMatrix.set(SkMatrix::kMTransX, SkScalarFraction(ctm.get(SkMatrix::kMTransX)));
+        drawMatrix.set(SkMatrix::kMTransY, SkScalarFraction(ctm.get(SkMatrix::kMTransY)));
+        SkRect shapeDevBounds;
+        drawMatrix.mapRect(&shapeDevBounds, bounds);
+        SkScalar dx = SkScalarFloorToScalar(shapeDevBounds.fLeft);
+        SkScalar dy = SkScalarFloorToScalar(shapeDevBounds.fTop);
+
+        // get integer boundary
+        SkIRect devPathBounds;
+        shapeDevBounds.roundOut(&devPathBounds);
+        // pad to allow room for antialiasing
+        const int intPad = SkScalarCeilToInt(kAntiAliasPad);
+        // place devBounds at origin
+        int width = devPathBounds.width() + 2 * intPad;
+        int height = devPathBounds.height() + 2 * intPad;
+        devPathBounds = SkIRect::MakeWH(width, height);
+        SkScalar translateX = intPad - dx;
+        SkScalar translateY = intPad - dy;
+
+        SkASSERT(devPathBounds.fLeft == 0);
+        SkASSERT(devPathBounds.fTop == 0);
+        SkASSERT(devPathBounds.width() > 0);
+        SkASSERT(devPathBounds.height() > 0);
+
+        SkPath path;
+        shape.asPath(&path);
+        // setup bitmap backing
+        SkAutoPixmapStorage dst;
+        if (!dst.tryAlloc(SkImageInfo::MakeA8(devPathBounds.width(),
+                                              devPathBounds.height()))) {
+            return false;
+        }
+        sk_bzero(dst.writable_addr(), dst.getSafeSize());
+
+        // rasterize path
+        SkPaint paint;
+        paint.setStyle(SkPaint::kFill_Style);
+        paint.setAntiAlias(true);
+
+        SkDraw draw;
+        sk_bzero(&draw, sizeof(draw));
+
+        SkRasterClip rasterClip;
+        rasterClip.setRect(devPathBounds);
+        draw.fRC = &rasterClip;
+        drawMatrix.postTranslate(translateX, translateY);
+        draw.fMatrix = &drawMatrix;
+        draw.fDst = dst;
+
+        draw.drawPathCoverage(path, paint);
+
+        // add to atlas
+        SkIPoint16 atlasLocation;
+        GrDrawOpAtlas::AtlasID id;
+        if (!atlas->addToAtlas(&id, target, dst.width(), dst.height(), dst.addr(),
+                               &atlasLocation)) {
+            this->flush(target, flushInfo);
+            if (!atlas->addToAtlas(&id, target, dst.width(), dst.height(), dst.addr(),
+                                   &atlasLocation)) {
+                return false;
+            }
+        }
+
+        // add to cache
+        shapeData->fKey.set(shape, ctm);
+        shapeData->fID = id;
+
+        // set the bounds rect to the original bounds
+        shapeData->fBounds = SkRect::Make(devPathBounds);
+        shapeData->fBounds.offset(-translateX, -translateY);
+
+        // set up path to texture coordinate transform
+        shapeData->fScale = SK_Scalar1;
+        shapeData->fTranslate.fX = atlasLocation.fX + translateX;
+        shapeData->fTranslate.fY = atlasLocation.fY + translateY;
+
+        fShapeCache->add(shapeData);
+        fShapeList->addToTail(shapeData);
+#ifdef DF_PATH_TRACKING
+        ++g_NumCachedPaths;
+#endif
+        return true;
+    }
+
     void writePathVertices(GrDrawOp::Target* target,
                            GrDrawOpAtlas* atlas,
                            intptr_t offset,
                            GrColor color,
                            size_t vertexStride,
                            SkScalar maxScale,
+                           const SkVector& preTranslate,
                            const ShapeData* shapeData) const {
         SkPoint* positions = reinterpret_cast<SkPoint*>(offset);
 
-        // outset bounds to include ~1 pixel of AA in device space
         SkRect bounds = shapeData->fBounds;
-        SkScalar outset = SkScalarInvert(maxScale);
-        bounds.outset(outset, outset);
+        if (fUsesDistanceField) {
+            // outset bounds to include ~1 pixel of AA in device space
+            SkScalar outset = SkScalarInvert(maxScale);
+            bounds.outset(outset, outset);
+        }
 
         // vertex positions
         // TODO make the vertex attributes a struct
-        positions->setRectFan(bounds.left(), bounds.top(), bounds.right(), bounds.bottom(),
+        positions->setRectFan(bounds.left() + preTranslate.fX,
+                              bounds.top() + preTranslate.fY,
+                              bounds.right() + preTranslate.fX,
+                              bounds.bottom() + preTranslate.fY,
                               vertexStride);
 
         // colors
@@ -482,15 +642,32 @@ private:
         texRight += translate.fX;
         texBottom += translate.fY;
 
-        // vertex texture coords
-        // TODO make these int16_t
-        SkPoint* textureCoords = (SkPoint*)(offset + sizeof(SkPoint) + sizeof(GrColor));
+        // convert texcoords to unsigned short format
         GrTexture* texture = atlas->getTexture();
-        textureCoords->setRectFan(texLeft / texture->width(),
-                                  texTop / texture->height(),
-                                  texRight / texture->width(),
-                                  texBottom / texture->height(),
-                                  vertexStride);
+        SkScalar uFactor = 65535.f / texture->width();
+        SkScalar vFactor = 65535.f / texture->height();
+        uint16_t l = (uint16_t)(texLeft*uFactor);
+        uint16_t t = (uint16_t)(texTop*vFactor);
+        uint16_t r = (uint16_t)(texRight*uFactor);
+        uint16_t b = (uint16_t)(texBottom*vFactor);
+
+        // set vertex texture coords
+        intptr_t textureCoordOffset = offset + sizeof(SkPoint) + sizeof(GrColor);
+        uint16_t* textureCoords = (uint16_t*) textureCoordOffset;
+        textureCoords[0] = l;
+        textureCoords[1] = t;
+        textureCoordOffset += vertexStride;
+        textureCoords = (uint16_t*)textureCoordOffset;
+        textureCoords[0] = l;
+        textureCoords[1] = b;
+        textureCoordOffset += vertexStride;
+        textureCoords = (uint16_t*)textureCoordOffset;
+        textureCoords[0] = r;
+        textureCoords[1] = b;
+        textureCoordOffset += vertexStride;
+        textureCoords = (uint16_t*)textureCoordOffset;
+        textureCoords[0] = r;
+        textureCoords[1] = t;
     }
 
     void flush(GrMeshDrawOp::Target* target, FlushInfo* flushInfo) const {
@@ -510,6 +687,7 @@ private:
     GrColor color() const { return fShapes[0].fColor; }
     const SkMatrix& viewMatrix() const { return fViewMatrix; }
     bool usesLocalCoords() const { return fUsesLocalCoords; }
+    bool usesDistanceField() const { return fUsesDistanceField; }
 
     bool onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
         AADistanceFieldPathOp* that = t->cast<AADistanceFieldPathOp>();
@@ -518,11 +696,20 @@ private:
             return false;
         }
 
-        // TODO We can position on the cpu
+        if (this->usesDistanceField() != that->usesDistanceField()) {
+            return false;
+        }
+
+        // TODO We can position on the cpu for distance field paths
         if (!this->viewMatrix().cheapEqualTo(that->viewMatrix())) {
             return false;
         }
 
+        if (!this->usesDistanceField() && this->usesLocalCoords() &&
+            !this->fShapes[0].fTranslate.equalsWithinTolerance(that->fShapes[0].fTranslate)) {
+            return false;
+        }
+
         fShapes.push_back_n(that->fShapes.count(), that->fShapes.begin());
         this->joinBounds(*that);
         return true;
@@ -530,10 +717,12 @@ private:
 
     SkMatrix fViewMatrix;
     bool fUsesLocalCoords;
+    bool fUsesDistanceField;
 
     struct Entry {
-        GrColor fColor;
-        GrShape fShape;
+        GrColor  fColor;
+        GrShape  fShape;
+        SkVector fTranslate;
     };
 
     SkSTArray<1, Entry> fShapes;
index 202b114..732888e 100644 (file)
@@ -38,6 +38,7 @@ private:
             Key() {}
             Key(const Key& that) { *this = that; }
             Key(const GrShape& shape, uint32_t dim) { this->set(shape, dim); }
+            Key(const GrShape& shape, const SkMatrix& ctm) { this->set(shape, ctm); }
 
             Key& operator=(const Key& that) {
                 fKey.reset(that.fKey.count());
@@ -56,6 +57,37 @@ private:
                 shape.writeUnstyledKey(&fKey[1]);
             }
 
+            void set(const GrShape& shape, const SkMatrix& ctm) {
+                GrUniqueKey maskKey;
+                struct KeyData {
+                    SkScalar fFractionalTranslateX;
+                    SkScalar fFractionalTranslateY;
+                };
+
+                // Shapes' keys are for their pre-style geometry, but by now we shouldn't have any
+                // relevant styling information.
+                SkASSERT(shape.style().isSimpleFill());
+                SkASSERT(shape.hasUnstyledKey());
+                // We require the upper left 2x2 of the matrix to match exactly for a cache hit.
+                SkScalar sx = ctm.get(SkMatrix::kMScaleX);
+                SkScalar sy = ctm.get(SkMatrix::kMScaleY);
+                SkScalar kx = ctm.get(SkMatrix::kMSkewX);
+                SkScalar ky = ctm.get(SkMatrix::kMSkewY);
+                SkScalar tx = ctm.get(SkMatrix::kMTransX);
+                SkScalar ty = ctm.get(SkMatrix::kMTransY);
+                // Allow 8 bits each in x and y of subpixel positioning.
+                SkFixed fracX = SkScalarToFixed(SkScalarFraction(tx)) & 0x0000FF00;
+                SkFixed fracY = SkScalarToFixed(SkScalarFraction(ty)) & 0x0000FF00;
+                int shapeKeySize = shape.unstyledKeySize();
+                fKey.reset(5 + shapeKeySize);
+                fKey[0] = SkFloat2Bits(sx);
+                fKey[1] = SkFloat2Bits(sy);
+                fKey[2] = SkFloat2Bits(kx);
+                fKey[3] = SkFloat2Bits(ky);
+                fKey[4] = fracX | (fracY >> 8);
+                shape.writeUnstyledKey(&fKey[5]);
+            }
+
             bool operator==(const Key& that) const {
                 return fKey.count() == that.fKey.count() &&
                         0 == memcmp(fKey.get(), that.fKey.get(), sizeof(uint32_t) * fKey.count());
@@ -65,8 +97,9 @@ private:
             const uint32_t* data() const { return fKey.get(); }
 
         private:
-            // The key is composed of the dimensions of the DF generated for the path (32x32 max,
-            // 64x64 max, 128x128 max) and the GrShape's key.
+            // The key is composed of the GrShape's key, and either the dimensions of the DF
+            // generated for the path (32x32 max, 64x64 max, 128x128 max) if an SDF image or
+            // the matrix for the path with only fractional translation.
             SkAutoSTArray<24, uint32_t> fKey;
         };
         Key fKey;