diff options
author | 2016-08-31 08:41:57 -0700 | |
---|---|---|
committer | 2016-08-31 08:41:57 -0700 | |
commit | 9992bdef8ae97b3e5b109d278ccfab84c66bcbf0 (patch) | |
tree | 65fb6c71cdbb90c72e13ab89709ca6aa5a43daf2 /src/gpu/batches | |
parent | 4bcd62e3313da60c3aa96ccd12b7ea440c7266d4 (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.cpp | 223 |
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 |