Reland: Allow DFPathRenderer to store bitmaps at low resolutions

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>
This commit is contained in:
Jim Van Verth 2017-02-28 10:24:39 -05:00 committed by Skia Commit-Bot
parent 3c322e23a0
commit 33632d8eda
4 changed files with 374 additions and 98 deletions

View 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:

View 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);
}

View 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,
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,11 +277,14 @@ private:
for (int i = 0; i < instanceCount; i++) {
const Entry& args = fShapes[i];
ShapeData* shapeData;
SkScalar maxScale;
if (fUsesDistanceField) {
// get mip level
SkScalar maxScale = SkScalarAbs(this->viewMatrix().getMaxScale());
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)
// 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
@ -280,9 +315,9 @@ private:
}
SkScalar desiredDimension = SkTMin(mipSize, kMaxMIP);
// check to see if path is cached
// check to see if df path is cached
ShapeData::Key key(args.fShape, SkScalarCeilToInt(desiredDimension));
ShapeData* shapeData = fShapeCache->find(key);
shapeData = fShapeCache->find(key);
if (nullptr == shapeData || !atlas->hasID(shapeData->fID)) {
// Remove the stale cache entry
if (shapeData) {
@ -293,7 +328,7 @@ private:
SkScalar scale = desiredDimension / maxDim;
shapeData = new ShapeData;
if (!this->addPathToAtlas(target,
if (!this->addDFPathToAtlas(target,
&flushInfo,
atlas,
shapeData,
@ -305,6 +340,32 @@ private:
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,7 +384,7 @@ private:
this->flush(target, &flushInfo);
}
bool addPathToAtlas(GrMeshDrawOp::Target* target, FlushInfo* flushInfo, GrDrawOpAtlas* atlas,
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();
@ -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;
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;
SkVector fTranslate;
};
SkSTArray<1, Entry> fShapes;

View 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;