aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Jim Van Verth <jvanverth@google.com>2017-02-27 18:21:16 -0500
committerGravatar Skia Commit-Bot <skia-commit-bot@chromium.org>2017-02-28 13:35:16 +0000
commitc0bc1bb8690e5ce489394112b0cf4fe4601c1f2c (patch)
tree75a48114f3ffb20cba2cfc887fffb949f015f338
parentedbeb8b842aa86d18388f75a1dbf1e470a565680 (diff)
Allow distance field path renderer to store bitmaps at low resolutions
BUG=chromium:682918 Change-Id: I1a0608f7e6394ab05eebc4b78fb7087ca718f617 Reviewed-on: https://skia-review.googlesource.com/8971 Commit-Queue: Jim Van Verth <jvanverth@google.com> Reviewed-by: Robert Phillips <robertphillips@google.com>
-rw-r--r--gm/pathfill.cpp54
-rw-r--r--src/gpu/effects/GrDistanceFieldGeoProc.cpp4
-rw-r--r--src/gpu/ops/GrAADistanceFieldPathRenderer.cpp381
-rw-r--r--src/gpu/ops/GrAADistanceFieldPathRenderer.h37
4 files changed, 376 insertions, 100 deletions
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);
-
- // 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, 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<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;
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;