aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/gpu/batches
diff options
context:
space:
mode:
authorGravatar senorblanco <senorblanco@chromium.org>2016-08-31 08:41:57 -0700
committerGravatar Commit bot <commit-bot@chromium.org>2016-08-31 08:41:57 -0700
commit9992bdef8ae97b3e5b109d278ccfab84c66bcbf0 (patch)
tree65fb6c71cdbb90c72e13ab89709ca6aa5a43daf2 /src/gpu/batches
parent4bcd62e3313da60c3aa96ccd12b7ea440c7266d4 (diff)
Screenspace AA tessellated GPU path rendering.
This is an approach to antialiased concave path rendering on the GPU without using MSAA. It uses GrTessellator to extract boundary contours from the given path, then inflates by half a pixel in screen space each direction, then renders the result with zero alpha on the outer contour and one alpha on in the inner contour. This requires two passes through the tessellation code: one to extract the boundaries, then one to tessellate the result. This gives approximately a 3X improvement on the IE chalkboard demo in non-MSAA mode, a 30-40% improvement on MotionMark's "Fill Paths", and a ~3X improvement on MotionMark's "canvas arcTo segments". It works best for large, simple paths, so there's currently a limit of 10 verbs in the onCanDrawPath() check. This dovetails nicely with the distance field path renderer's support for small, detailed (and cached) paths. BUG=skia: GOLD_TRYBOT_URL= https://gold.skia.org/search2?unt=true&query=source_type%3Dgm&master=false&issue=1152733009 Review-Url: https://codereview.chromium.org/1152733009
Diffstat (limited to 'src/gpu/batches')
-rw-r--r--src/gpu/batches/GrTessellatingPathRenderer.cpp223
1 files changed, 156 insertions, 67 deletions
diff --git a/src/gpu/batches/GrTessellatingPathRenderer.cpp b/src/gpu/batches/GrTessellatingPathRenderer.cpp
index b2c3138aed..3aa7ce8338 100644
--- a/src/gpu/batches/GrTessellatingPathRenderer.cpp
+++ b/src/gpu/batches/GrTessellatingPathRenderer.cpp
@@ -12,6 +12,7 @@
#include "GrBatchTest.h"
#include "GrClip.h"
#include "GrDefaultGeoProcFactory.h"
+#include "GrDrawTarget.h"
#include "GrMesh.h"
#include "GrPathUtils.h"
#include "GrPipelineBuilder.h"
@@ -24,10 +25,12 @@
#include <stdio.h>
+#define SK_DISABLE_SCREENSPACE_TESS_AA_PATH_RENDERER
+
/*
- * This path renderer tessellates the path into triangles using GrTessellator, uploads the triangles
- * to a vertex buffer, and renders them with a single draw call. It does not currently do
- * antialiasing, so it must be used in conjunction with multisampling.
+ * This path renderer tessellates the path into triangles using GrTessellator, uploads the
+ * triangles to a vertex buffer, and renders them with a single draw call. It can do screenspace
+ * antialiasing with a one-pixel coverage ramp.
*/
namespace {
@@ -64,22 +67,23 @@ bool cache_match(GrBuffer* vertexBuffer, SkScalar tol, int* actualCount) {
class StaticVertexAllocator : public GrTessellator::VertexAllocator {
public:
- StaticVertexAllocator(GrResourceProvider* resourceProvider, bool canMapVB)
- : fResourceProvider(resourceProvider)
+ StaticVertexAllocator(size_t stride, GrResourceProvider* resourceProvider, bool canMapVB)
+ : VertexAllocator(stride)
+ , fResourceProvider(resourceProvider)
, fCanMapVB(canMapVB)
, fVertices(nullptr) {
}
- SkPoint* lock(int vertexCount) override {
- size_t size = vertexCount * sizeof(SkPoint);
+ void* lock(int vertexCount) override {
+ size_t size = vertexCount * stride();
fVertexBuffer.reset(fResourceProvider->createBuffer(
size, kVertex_GrBufferType, kStatic_GrAccessPattern, 0));
if (!fVertexBuffer.get()) {
return nullptr;
}
if (fCanMapVB) {
- fVertices = static_cast<SkPoint*>(fVertexBuffer->map());
+ fVertices = fVertexBuffer->map();
} else {
- fVertices = new SkPoint[vertexCount];
+ fVertices = sk_malloc_throw(vertexCount * stride());
}
return fVertices;
}
@@ -87,8 +91,8 @@ public:
if (fCanMapVB) {
fVertexBuffer->unmap();
} else {
- fVertexBuffer->updateData(fVertices, actualCount * sizeof(SkPoint));
- delete[] fVertices;
+ fVertexBuffer->updateData(fVertices, actualCount * stride());
+ sk_free(fVertices);
}
fVertices = nullptr;
}
@@ -97,7 +101,34 @@ private:
SkAutoTUnref<GrBuffer> fVertexBuffer;
GrResourceProvider* fResourceProvider;
bool fCanMapVB;
- SkPoint* fVertices;
+ void* fVertices;
+};
+
+class DynamicVertexAllocator : public GrTessellator::VertexAllocator {
+public:
+ DynamicVertexAllocator(size_t stride, GrVertexBatch::Target* target)
+ : VertexAllocator(stride)
+ , fTarget(target)
+ , fVertexBuffer(nullptr)
+ , fVertices(nullptr) {
+ }
+ void* lock(int vertexCount) override {
+ fVertexCount = vertexCount;
+ fVertices = fTarget->makeVertexSpace(stride(), vertexCount, &fVertexBuffer, &fFirstVertex);
+ return fVertices;
+ }
+ void unlock(int actualCount) override {
+ fTarget->putBackVertices(fVertexCount - actualCount, stride());
+ fVertices = nullptr;
+ }
+ const GrBuffer* vertexBuffer() const { return fVertexBuffer; }
+ int firstVertex() const { return fFirstVertex; }
+private:
+ GrVertexBatch::Target* fTarget;
+ const GrBuffer* fVertexBuffer;
+ int fVertexCount;
+ int fFirstVertex;
+ void* fVertices;
};
} // namespace
@@ -106,13 +137,30 @@ GrTessellatingPathRenderer::GrTessellatingPathRenderer() {
}
bool GrTessellatingPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const {
- // This path renderer can draw fill styles but does not do antialiasing. It can do convex and
- // concave paths, but we'll leave the convex ones to simpler algorithms. We pass on paths that
- // have styles, though they may come back around after applying the styling information to the
- // geometry to create a filled path. We also skip paths that don't have a key since the real
- // advantage of this path renderer comes from caching the tessellated geometry.
- return !args.fShape->style().applies() && args.fShape->style().isSimpleFill() &&
- !args.fAntiAlias && args.fShape->hasUnstyledKey() && !args.fShape->knownToBeConvex();
+ // This path renderer can draw fill styles, and can do screenspace antialiasing via a
+ // one-pixel coverage ramp. It can do convex and concave paths, but we'll leave the convex
+ // ones to simpler algorithms. We pass on paths that have styles, though they may come back
+ // around after applying the styling information to the geometry to create a filled path. In
+ // the non-AA case, We skip paths thta don't have a key since the real advantage of this path
+ // renderer comes from caching the tessellated geometry. In the AA case, we do not cache, so we
+ // accept paths without keys.
+ if (!args.fShape->style().isSimpleFill() || args.fShape->knownToBeConvex()) {
+ return false;
+ }
+ if (args.fAntiAlias) {
+#ifdef SK_DISABLE_SCREENSPACE_TESS_AA_PATH_RENDERER
+ return false;
+#else
+ SkPath path;
+ args.fShape->asPath(&path);
+ if (path.countVerbs() > 10) {
+ return false;
+ }
+#endif
+ } else if (!args.fShape->hasUnstyledKey()) {
+ return false;
+ }
+ return true;
}
class TessellatingPathBatch : public GrVertexBatch {
@@ -122,8 +170,9 @@ public:
static GrDrawBatch* Create(const GrColor& color,
const GrShape& shape,
const SkMatrix& viewMatrix,
- SkRect clipBounds) {
- return new TessellatingPathBatch(color, shape, viewMatrix, clipBounds);
+ SkIRect devClipBounds,
+ bool antiAlias) {
+ return new TessellatingPathBatch(color, shape, viewMatrix, devClipBounds, antiAlias);
}
const char* name() const override { return "TessellatingPathBatch"; }
@@ -145,41 +194,53 @@ private:
fPipelineInfo = overrides;
}
- void draw(Target* target, const GrGeometryProcessor* gp) const {
- GrResourceProvider* rp = target->resourceProvider();
- SkScalar screenSpaceTol = GrPathUtils::kDefaultTolerance;
- SkScalar tol = GrPathUtils::scaleToleranceToSrc(screenSpaceTol, fViewMatrix,
- fShape.bounds());
-
+ SkPath getPath() const {
+ SkASSERT(!fShape.style().applies());
SkPath path;
fShape.asPath(&path);
- bool inverseFill = path.isInverseFillType();
+ return path;
+ }
+
+ void draw(Target* target, const GrGeometryProcessor* gp) const {
+ SkASSERT(!fAntiAlias);
+ GrResourceProvider* rp = target->resourceProvider();
+ bool inverseFill = fShape.inverseFilled();
// construct a cache key from the path's genID and the view matrix
static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
GrUniqueKey key;
- static constexpr int kClipBoundsCnt = sizeof(fClipBounds) / sizeof(uint32_t);
+ static constexpr int kClipBoundsCnt = sizeof(fDevClipBounds) / sizeof(uint32_t);
int shapeKeyDataCnt = fShape.unstyledKeySize();
SkASSERT(shapeKeyDataCnt >= 0);
GrUniqueKey::Builder builder(&key, kDomain, shapeKeyDataCnt + kClipBoundsCnt);
fShape.writeUnstyledKey(&builder[0]);
// For inverse fills, the tessellation is dependent on clip bounds.
if (inverseFill) {
- memcpy(&builder[shapeKeyDataCnt], &fClipBounds, sizeof(fClipBounds));
+ memcpy(&builder[shapeKeyDataCnt], &fDevClipBounds, sizeof(fDevClipBounds));
} else {
- memset(&builder[shapeKeyDataCnt], 0, sizeof(fClipBounds));
+ memset(&builder[shapeKeyDataCnt], 0, sizeof(fDevClipBounds));
}
builder.finish();
SkAutoTUnref<GrBuffer> cachedVertexBuffer(rp->findAndRefTByUniqueKey<GrBuffer>(key));
int actualCount;
+ SkScalar tol = GrPathUtils::kDefaultTolerance;
+ tol = GrPathUtils::scaleToleranceToSrc(tol, fViewMatrix, fShape.bounds());
if (cache_match(cachedVertexBuffer.get(), tol, &actualCount)) {
this->drawVertices(target, gp, cachedVertexBuffer.get(), 0, actualCount);
return;
}
+ SkRect clipBounds = SkRect::Make(fDevClipBounds);
+
+ SkMatrix vmi;
+ if (!fViewMatrix.invert(&vmi)) {
+ return;
+ }
+ vmi.mapRect(&clipBounds);
bool isLinear;
bool canMapVB = GrCaps::kNone_MapFlags != target->caps().mapBufferFlags();
- StaticVertexAllocator allocator(rp, canMapVB);
- int count = GrTessellator::PathToTriangles(path, tol, fClipBounds, &allocator, &isLinear);
+ StaticVertexAllocator allocator(gp->getVertexStride(), rp, canMapVB);
+ int count = GrTessellator::PathToTriangles(getPath(), tol, clipBounds, &allocator,
+ false, GrColor(), false, &isLinear);
if (count == 0) {
return;
}
@@ -191,6 +252,27 @@ private:
rp->assignUniqueKeyToResource(key, allocator.vertexBuffer());
}
+ void drawAA(Target* target, const GrGeometryProcessor* gp) const {
+ SkASSERT(fAntiAlias);
+ SkPath path = getPath();
+ if (path.isEmpty()) {
+ return;
+ }
+ SkRect clipBounds = SkRect::Make(fDevClipBounds);
+ path.transform(fViewMatrix);
+ SkScalar tol = GrPathUtils::kDefaultTolerance;
+ bool isLinear;
+ DynamicVertexAllocator allocator(gp->getVertexStride(), target);
+ bool canTweakAlphaForCoverage = fPipelineInfo.canTweakAlphaForCoverage();
+ int count = GrTessellator::PathToTriangles(path, tol, clipBounds, &allocator,
+ true, fColor, canTweakAlphaForCoverage,
+ &isLinear);
+ if (count == 0) {
+ return;
+ }
+ drawVertices(target, gp, allocator.vertexBuffer(), allocator.firstVertex(), count);
+ }
+
void onPrepareDraws(Target* target) const override {
sk_sp<GrGeometryProcessor> gp;
{
@@ -201,21 +283,35 @@ private:
LocalCoords::kUsePosition_Type :
LocalCoords::kUnused_Type);
Coverage::Type coverageType;
- if (fPipelineInfo.readsCoverage()) {
+ if (fAntiAlias) {
+ color = Color(Color::kAttribute_Type);
+ if (fPipelineInfo.canTweakAlphaForCoverage()) {
+ coverageType = Coverage::kSolid_Type;
+ } else {
+ coverageType = Coverage::kAttribute_Type;
+ }
+ } else if (fPipelineInfo.readsCoverage()) {
coverageType = Coverage::kSolid_Type;
} else {
coverageType = Coverage::kNone_Type;
}
Coverage coverage(coverageType);
- gp = GrDefaultGeoProcFactory::Make(color, coverage, localCoords, fViewMatrix);
+ if (fAntiAlias) {
+ gp = GrDefaultGeoProcFactory::MakeForDeviceSpace(color, coverage, localCoords,
+ fViewMatrix);
+ } else {
+ gp = GrDefaultGeoProcFactory::Make(color, coverage, localCoords, fViewMatrix);
+ }
+ }
+ if (fAntiAlias) {
+ this->drawAA(target, gp.get());
+ } else {
+ this->draw(target, gp.get());
}
- this->draw(target, gp.get());
}
void drawVertices(Target* target, const GrGeometryProcessor* gp, const GrBuffer* vb,
int firstVertex, int count) const {
- SkASSERT(gp->getVertexStride() == sizeof(SkPoint));
-
GrPrimitiveType primitiveType = TESSELLATOR_WIREFRAME ? kLines_GrPrimitiveType
: kTriangles_GrPrimitiveType;
GrMesh mesh;
@@ -228,24 +324,29 @@ private:
TessellatingPathBatch(const GrColor& color,
const GrShape& shape,
const SkMatrix& viewMatrix,
- const SkRect& clipBounds)
+ const SkIRect& devClipBounds,
+ bool antiAlias)
: INHERITED(ClassID())
, fColor(color)
, fShape(shape)
- , fViewMatrix(viewMatrix) {
- const SkRect& pathBounds = shape.bounds();
- fClipBounds = clipBounds;
- // Because the clip bounds are used to add a contour for inverse fills, they must also
- // include the path bounds.
- fClipBounds.join(pathBounds);
- const SkRect& srcBounds = shape.inverseFilled() ? fClipBounds : pathBounds;
- this->setTransformedBounds(srcBounds, viewMatrix, HasAABloat::kNo, IsZeroArea::kNo);
+ , fViewMatrix(viewMatrix)
+ , fDevClipBounds(devClipBounds)
+ , fAntiAlias(antiAlias) {
+ SkRect devBounds;
+ viewMatrix.mapRect(&devBounds, shape.bounds());
+ if (shape.inverseFilled()) {
+ // Because the clip bounds are used to add a contour for inverse fills, they must also
+ // include the path bounds.
+ devBounds.join(SkRect::Make(fDevClipBounds));
+ }
+ this->setBounds(devBounds, HasAABloat::kNo, IsZeroArea::kNo);
}
GrColor fColor;
GrShape fShape;
SkMatrix fViewMatrix;
- SkRect fClipBounds; // in source space
+ SkIRect fDevClipBounds;
+ bool fAntiAlias;
GrXPOverridesForBatch fPipelineInfo;
typedef GrVertexBatch INHERITED;
@@ -254,22 +355,14 @@ private:
bool GrTessellatingPathRenderer::onDrawPath(const DrawPathArgs& args) {
GR_AUDIT_TRAIL_AUTO_FRAME(args.fDrawContext->auditTrail(),
"GrTessellatingPathRenderer::onDrawPath");
- SkASSERT(!args.fAntiAlias);
-
SkIRect clipBoundsI;
args.fClip->getConservativeBounds(args.fDrawContext->width(), args.fDrawContext->height(),
&clipBoundsI);
- SkRect clipBounds = SkRect::Make(clipBoundsI);
- SkMatrix vmi;
- if (!args.fViewMatrix->invert(&vmi)) {
- return false;
- }
- vmi.mapRect(&clipBounds);
- SkPath path;
- args.fShape->asPath(&path);
SkAutoTUnref<GrDrawBatch> batch(TessellatingPathBatch::Create(args.fPaint->getColor(),
*args.fShape,
- *args.fViewMatrix, clipBounds));
+ *args.fViewMatrix,
+ clipBoundsI,
+ args.fAntiAlias));
GrPipelineBuilder pipelineBuilder(*args.fPaint, args.fDrawContext->mustUseHWAA(*args.fPaint));
pipelineBuilder.setUserStencil(args.fUserStencilSettings);
@@ -287,19 +380,15 @@ DRAW_BATCH_TEST_DEFINE(TesselatingPathBatch) {
GrColor color = GrRandomColor(random);
SkMatrix viewMatrix = GrTest::TestMatrixInvertible(random);
SkPath path = GrTest::TestPath(random);
- SkRect clipBounds = GrTest::TestRect(random);
- SkMatrix vmi;
- bool result = viewMatrix.invert(&vmi);
- if (!result) {
- SkFAIL("Cannot invert matrix\n");
- }
- vmi.mapRect(&clipBounds);
+ SkIRect devClipBounds = SkIRect::MakeXYWH(
+ random->nextU(), random->nextU(), random->nextU(), random->nextU());
+ bool antiAlias = random->nextBool();
GrStyle style;
do {
GrTest::TestStyle(random, &style);
} while (style.strokeRec().isHairlineStyle());
GrShape shape(path, style);
- return TessellatingPathBatch::Create(color, shape, viewMatrix, clipBounds);
+ return TessellatingPathBatch::Create(color, shape, viewMatrix, devClipBounds, antiAlias);
}
#endif