diff --git a/gm/pathfill.cpp b/gm/pathfill.cpp index a36ab1f459..d96f356bef 100644 --- a/gm/pathfill.cpp +++ b/gm/pathfill.cpp @@ -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: diff --git a/src/gpu/effects/GrDistanceFieldGeoProc.cpp b/src/gpu/effects/GrDistanceFieldGeoProc.cpp index 89ec238efd..1c2ef6494a 100644 --- a/src/gpu/effects/GrDistanceFieldGeoProc.cpp +++ b/src/gpu/effects/GrDistanceFieldGeoProc.cpp @@ -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); } diff --git a/src/gpu/ops/GrAADistanceFieldPathRenderer.cpp b/src/gpu/ops/GrAADistanceFieldPathRenderer.cpp index 7ed14ba7de..d4b2b16cc9 100644 --- a/src/gpu/ops/GrAADistanceFieldPathRenderer.cpp +++ b/src/gpu/ops/GrAADistanceFieldPathRenderer.cpp @@ -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); + SkASSERT(maxScale <= mipScale); - // 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; + 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 scale = desiredDimension/maxDim; + SkScalar desiredDimension = SkTMin(mipSize, kMaxMIP); - shapeData = new ShapeData; - if (!this->addPathToAtlas(target, - &flushInfo, - atlas, - shapeData, - args.fShape, - SkScalarCeilToInt(desiredDimension), - scale)) { - delete shapeData; - SkDebugf("Can't rasterize path\n"); - continue; + // 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; + } } + } 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, drawMatrix); + 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(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(); @@ -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; diff --git a/src/gpu/ops/GrAADistanceFieldPathRenderer.h b/src/gpu/ops/GrAADistanceFieldPathRenderer.h index 202b114e23..732888e3ba 100644 --- a/src/gpu/ops/GrAADistanceFieldPathRenderer.h +++ b/src/gpu/ops/GrAADistanceFieldPathRenderer.h @@ -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;