Chromium Code Reviews| Index: src/gpu/batches/GrTessellatingPathRenderer.cpp |
| diff --git a/src/gpu/batches/GrTessellatingPathRenderer.cpp b/src/gpu/batches/GrTessellatingPathRenderer.cpp |
| index b2c3138aed438f9e0979826cdacd121dd8bc5da2..667395ecff5dbc0f2823146e2aa7b6bc8da43e49 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,28 @@ 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. |
| + // 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.fAntiAlias) { |
| +#ifdef SK_DISABLE_SCREENSPACE_TESS_AA_PATH_RENDERER |
| + return false; |
| +#else |
| + SkPath path; |
| + args.fShape->asPath(&path); |
|
bsalomon
2016/08/31 14:19:19
If the path has a path effect or is stroked this o
Stephen White
2016/08/31 15:16:38
Good idea; done.
|
| + if (path.countVerbs() > 10) { |
| + return false; |
| + } |
| +#endif |
| + } else if (!args.fShape->hasUnstyledKey()) { |
| + return false; |
| + } |
| return !args.fShape->style().applies() && args.fShape->style().isSimpleFill() && |
|
bsalomon
2016/08/31 14:19:19
I realize that this is pre-existing (probably done
Stephen White
2016/08/31 15:16:38
Done.
|
| - !args.fAntiAlias && args.fShape->hasUnstyledKey() && !args.fShape->knownToBeConvex(); |
| + !args.fShape->knownToBeConvex(); |
| } |
| class TessellatingPathBatch : public GrVertexBatch { |
| @@ -122,8 +168,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 +192,58 @@ 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 { |
| SkPath path; |
|
bsalomon
2016/08/31 14:19:19
I think this can be:
fShape.applyStyle(GrStyle::Ap
Stephen White
2016/08/31 15:16:38
Actually, given that onCanDrawPath() checks that
!
bsalomon
2016/08/31 15:29:48
Agreed.
|
| fShape.asPath(&path); |
| - bool inverseFill = path.isInverseFillType(); |
| + if (fShape.style().applies()) { |
| + SkScalar styleScale = GrStyle::MatrixToScaleFactor(fViewMatrix); |
| + SkStrokeRec::InitStyle fill; |
| + SkAssertResult(fShape.style().applyToPath(&path, &fill, path, styleScale)); |
| + SkASSERT(SkStrokeRec::kFill_InitStyle == fill); |
| + } |
| + 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 +255,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 +286,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 +327,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 +358,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 +383,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 |