Add a single-pass tessellation Op for convex fills
Bug: skia:10419
Change-Id: I0592adc43c1e426795fce133c37844ea7530058b
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/416979
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Greg Daniel <egdaniel@google.com>
diff --git a/gn/gpu.gni b/gn/gpu.gni
index 3337a09..7133664 100644
--- a/gn/gpu.gni
+++ b/gn/gpu.gni
@@ -458,6 +458,8 @@
"$_src/gpu/tessellate/GrPathInnerTriangulateOp.h",
"$_src/gpu/tessellate/GrPathStencilCoverOp.cpp",
"$_src/gpu/tessellate/GrPathStencilCoverOp.h",
+ "$_src/gpu/tessellate/GrPathTessellateOp.cpp",
+ "$_src/gpu/tessellate/GrPathTessellateOp.h",
"$_src/gpu/tessellate/GrPathTessellator.h",
"$_src/gpu/tessellate/GrPathWedgeTessellator.cpp",
"$_src/gpu/tessellate/GrPathWedgeTessellator.h",
diff --git a/src/gpu/tessellate/GrPathTessellateOp.cpp b/src/gpu/tessellate/GrPathTessellateOp.cpp
new file mode 100644
index 0000000..283706e
--- /dev/null
+++ b/src/gpu/tessellate/GrPathTessellateOp.cpp
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2021 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/gpu/tessellate/GrPathTessellateOp.h"
+
+#include "src/gpu/tessellate/GrPathWedgeTessellator.h"
+#include "src/gpu/tessellate/shaders/GrPathTessellationShader.h"
+
+void GrPathTessellateOp::visitProxies(const VisitProxyFunc& fn) const {
+ if (fTessellationProgram) {
+ fTessellationProgram->pipeline().visitProxies(fn);
+ } else {
+ fProcessors.visitProxies(fn);
+ }
+}
+
+GrProcessorSet::Analysis GrPathTessellateOp::finalize(const GrCaps& caps,
+ const GrAppliedClip* clip,
+ GrClampType clampType) {
+ return fProcessors.finalize(fColor, GrProcessorAnalysisCoverage::kNone, clip, nullptr, caps,
+ clampType, &fColor);
+}
+
+void GrPathTessellateOp::prepareTessellator(const GrTessellationShader::ProgramArgs& args,
+ GrAppliedClip&& appliedClip) {
+ SkASSERT(!fTessellator);
+ SkASSERT(!fTessellationProgram);
+ auto* pipeline = GrTessellationShader::MakePipeline(args, fAAType, std::move(appliedClip),
+ std::move(fProcessors));
+ fTessellator = GrPathWedgeTessellator::Make(args.fArena, fViewMatrix, fColor,
+ fPath.countVerbs(), *pipeline, *args.fCaps);
+ fTessellationProgram = GrTessellationShader::MakeProgram(args, fTessellator->shader(), pipeline,
+ fStencil);
+}
+
+void GrPathTessellateOp::onPrePrepare(GrRecordingContext* context,
+ const GrSurfaceProxyView& writeView, GrAppliedClip* clip,
+ const GrDstProxyView& dstProxyView,
+ GrXferBarrierFlags renderPassXferBarriers,
+ GrLoadOp colorLoadOp) {
+ this->prepareTessellator({context->priv().recordTimeAllocator(), writeView, &dstProxyView,
+ renderPassXferBarriers, colorLoadOp, context->priv().caps()},
+ (clip) ? std::move(*clip) : GrAppliedClip::Disabled());
+ SkASSERT(fTessellationProgram);
+ context->priv().recordProgramInfo(fTessellationProgram);
+}
+
+void GrPathTessellateOp::onPrepare(GrOpFlushState* flushState) {
+ if (!fTessellator) {
+ this->prepareTessellator({flushState->allocator(), flushState->writeView(),
+ &flushState->dstProxyView(), flushState->renderPassBarriers(),
+ flushState->colorLoadOp(), &flushState->caps()},
+ flushState->detachAppliedClip());
+ SkASSERT(fTessellator);
+ }
+ fTessellator->prepare(flushState, this->bounds(), fPath);
+}
+
+void GrPathTessellateOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) {
+ SkASSERT(fTessellator);
+ SkASSERT(fTessellationProgram);
+ flushState->bindPipelineAndScissorClip(*fTessellationProgram, this->bounds());
+ flushState->bindTextures(fTessellationProgram->geomProc(), nullptr,
+ fTessellationProgram->pipeline());
+ fTessellator->draw(flushState);
+}
diff --git a/src/gpu/tessellate/GrPathTessellateOp.h b/src/gpu/tessellate/GrPathTessellateOp.h
new file mode 100644
index 0000000..9520406
--- /dev/null
+++ b/src/gpu/tessellate/GrPathTessellateOp.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2021 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrPathTessellateOp_DEFINED
+#define GrPathTessellateOp_DEFINED
+
+#include "src/gpu/ops/GrDrawOp.h"
+#include "src/gpu/tessellate/shaders/GrTessellationShader.h"
+
+class GrPathTessellator;
+
+// Tessellates a path directly to the color buffer, using one single render pass. This currently
+// only works for convex paths.
+class GrPathTessellateOp : public GrDrawOp {
+private:
+ DEFINE_OP_CLASS_ID
+
+ GrPathTessellateOp(const SkMatrix& viewMatrix, const SkPath& path, GrPaint&& paint,
+ GrAAType aaType, const GrUserStencilSettings* stencil,
+ const SkRect& devBounds)
+ : GrDrawOp(ClassID())
+ , fViewMatrix(viewMatrix)
+ , fPath(path)
+ , fAAType(aaType)
+ , fStencil(stencil)
+ , fColor(paint.getColor4f())
+ , fProcessors(std::move(paint)) {
+ this->setBounds(devBounds, HasAABloat::kNo, IsHairline::kNo);
+ }
+
+ const char* name() const override { return "GrPathTessellateOp"; }
+ bool usesMSAA() const override { return fAAType == GrAAType::kMSAA; }
+ void visitProxies(const VisitProxyFunc& fn) const override;
+ GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*, GrClampType) override;
+ bool usesStencil() const override { return !fStencil->isUnused(); }
+
+ void prepareTessellator(const GrTessellationShader::ProgramArgs&, GrAppliedClip&& clip);
+
+ void onPrePrepare(GrRecordingContext*, const GrSurfaceProxyView&, GrAppliedClip*,
+ const GrDstProxyView&, GrXferBarrierFlags, GrLoadOp colorLoadOp) override;
+ void onPrepare(GrOpFlushState*) override;
+ void onExecute(GrOpFlushState*, const SkRect& chainBounds) override;
+
+ const SkMatrix fViewMatrix;
+ const SkPath fPath;
+ const GrAAType fAAType;
+ const GrUserStencilSettings* const fStencil;
+ SkPMColor4f fColor;
+ GrProcessorSet fProcessors;
+
+ // Decided during prepareTessellator.
+ GrPathTessellator* fTessellator = nullptr;
+ const GrProgramInfo* fTessellationProgram = nullptr;
+
+ friend class GrOp; // For ctor.
+};
+
+#endif
diff --git a/src/gpu/tessellate/GrTessellationPathRenderer.cpp b/src/gpu/tessellate/GrTessellationPathRenderer.cpp
index e0edb7c..84093d3 100644
--- a/src/gpu/tessellate/GrTessellationPathRenderer.cpp
+++ b/src/gpu/tessellate/GrTessellationPathRenderer.cpp
@@ -19,6 +19,7 @@
#include "src/gpu/tessellate/GrDrawAtlasPathOp.h"
#include "src/gpu/tessellate/GrPathInnerTriangulateOp.h"
#include "src/gpu/tessellate/GrPathStencilCoverOp.h"
+#include "src/gpu/tessellate/GrPathTessellateOp.h"
#include "src/gpu/tessellate/GrStrokeTessellateOp.h"
constexpr static SkISize kAtlasInitialSize{512, 512};
@@ -53,6 +54,16 @@
}
}
+GrPathRenderer::StencilSupport GrTessellationPathRenderer::onGetStencilSupport(
+ const GrStyledShape& shape) const {
+ if (!shape.style().isSimpleFill()) {
+ // Don't bother with stroke stencilling yet. Skia probably shouldn't support this at all
+ // since you can't clip by a stroke.
+ return kNoSupport_StencilSupport;
+ }
+ return shape.knownToBeConvex() ? kNoRestriction_StencilSupport : kStencilOnly_StencilSupport;
+}
+
GrPathRenderer::CanDrawPath GrTessellationPathRenderer::onCanDrawPath(
const CanDrawPathArgs& args) const {
const GrStyledShape& shape = *args.fShape;
@@ -61,7 +72,6 @@
args.fViewMatrix->hasPerspective() ||
shape.style().strokeRec().getStyle() == SkStrokeRec::kStrokeAndFill_Style ||
shape.inverseFilled() ||
- args.fHasUserStencilSettings ||
!args.fProxy->canUseStencil(*args.fCaps)) {
return CanDrawPath::kNo;
}
@@ -77,49 +87,61 @@
return CanDrawPath::kNo;
}
}
+ if (args.fHasUserStencilSettings) {
+ // Non-convex paths and strokes use the stencil buffer internally, so they can't support
+ // draws with stencil settings.
+ if (!shape.style().isSimpleFill() || !shape.knownToBeConvex()) {
+ return CanDrawPath::kNo;
+ }
+ }
return CanDrawPath::kYes;
}
-static GrOp::Owner make_op(GrRecordingContext* rContext, const GrSurfaceContext* surfaceContext,
- GrTessellationPathRenderer::PathFlags pathFlags, GrAAType aaType,
- const SkRect& shapeDevBounds, const SkMatrix& viewMatrix,
- const GrStyledShape& shape, GrPaint&& paint) {
- SkPath path;
- shape.asPath(&path);
- if (!shape.style().isSimpleFill()) {
- const SkStrokeRec& stroke = shape.style().strokeRec();
- SkASSERT(stroke.getStyle() != SkStrokeRec::kStrokeAndFill_Style);
- return GrOp::Make<GrStrokeTessellateOp>(rContext, aaType, viewMatrix, path, stroke,
- std::move(paint));
- } else {
- SkRect devBounds;
- viewMatrix.mapRect(&devBounds, path.getBounds());
- int numVerbs = path.countVerbs();
- if (numVerbs > 0) {
- // Check if the path is large and/or simple enough that we can triangulate the inner fan
- // on the CPU. This is our fastest approach. It allows us to stencil only the curves,
- // and then fill the inner fan directly to the final render target, thus drawing the
- // majority of pixels in a single render pass.
- float gpuFragmentWork = devBounds.height() * devBounds.width();
- float cpuTessellationWork = numVerbs * SkNextLog2(numVerbs); // N log N.
- constexpr static float kCpuWeight = 512;
- constexpr static float kMinNumPixelsToTriangulate = 256 * 256;
- if (cpuTessellationWork * kCpuWeight + kMinNumPixelsToTriangulate < gpuFragmentWork) {
- return GrOp::Make<GrPathInnerTriangulateOp>(rContext, viewMatrix, path,
- std::move(paint), aaType, pathFlags,
- devBounds);
- }
+static GrOp::Owner make_non_convex_fill_op(GrRecordingContext* rContext,
+ GrTessellationPathRenderer::PathFlags pathFlags,
+ GrAAType aaType, const SkRect& pathDevBounds,
+ const SkMatrix& viewMatrix, const SkPath& path,
+ GrPaint&& paint) {
+ SkASSERT(!path.isConvex());
+ int numVerbs = path.countVerbs();
+ if (numVerbs > 0) {
+ // Check if the path is large and/or simple enough that we can triangulate the inner fan
+ // on the CPU. This is our fastest approach. It allows us to stencil only the curves,
+ // and then fill the inner fan directly to the final render target, thus drawing the
+ // majority of pixels in a single render pass.
+ float gpuFragmentWork = pathDevBounds.height() * pathDevBounds.width();
+ float cpuTessellationWork = numVerbs * SkNextLog2(numVerbs); // N log N.
+ constexpr static float kCpuWeight = 512;
+ constexpr static float kMinNumPixelsToTriangulate = 256 * 256;
+ if (cpuTessellationWork * kCpuWeight + kMinNumPixelsToTriangulate < gpuFragmentWork) {
+ return GrOp::Make<GrPathInnerTriangulateOp>(rContext, viewMatrix, path,
+ std::move(paint), aaType, pathFlags,
+ pathDevBounds);
}
- return GrOp::Make<GrPathStencilCoverOp>(rContext, viewMatrix, path, std::move(paint),
- aaType, pathFlags, devBounds);
}
+ return GrOp::Make<GrPathStencilCoverOp>(rContext, viewMatrix, path, std::move(paint), aaType,
+ pathFlags, pathDevBounds);
}
bool GrTessellationPathRenderer::onDrawPath(const DrawPathArgs& args) {
GrSurfaceDrawContext* surfaceDrawContext = args.fSurfaceDrawContext;
- SkRect devBounds;
- args.fViewMatrix->mapRect(&devBounds, args.fShape->bounds());
+ SkPath path;
+ args.fShape->asPath(&path);
+
+ // Handle strokes first.
+ if (!args.fShape->style().isSimpleFill()) {
+ SkASSERT(args.fUserStencilSettings->isUnused());
+ const SkStrokeRec& stroke = args.fShape->style().strokeRec();
+ SkASSERT(stroke.getStyle() != SkStrokeRec::kStrokeAndFill_Style);
+ auto op = GrOp::Make<GrStrokeTessellateOp>(args.fContext, args.fAAType, *args.fViewMatrix,
+ path, stroke, std::move(args.fPaint));
+ surfaceDrawContext->addDrawOp(args.fClip, std::move(op));
+ return true;
+ }
+
+ SkRect pathDevBounds;
+ args.fViewMatrix->mapRect(&pathDevBounds, args.fShape->bounds());
// See if the path is small and simple enough to atlas instead of drawing directly.
//
@@ -129,8 +151,9 @@
SkIRect devIBounds;
SkIPoint16 locationInAtlas;
bool transposedInAtlas;
- if (this->tryAddPathToAtlas(*args.fContext->priv().caps(), *args.fViewMatrix, *args.fShape,
- devBounds, args.fAAType, &devIBounds, &locationInAtlas,
+ if (args.fUserStencilSettings->isUnused() &&
+ this->tryAddPathToAtlas(*args.fContext->priv().caps(), *args.fViewMatrix, path,
+ pathDevBounds, args.fAAType, &devIBounds, &locationInAtlas,
&transposedInAtlas)) {
// The atlas is not compatible with DDL. We should only be using it on direct contexts.
SkASSERT(args.fContext->asDirectContext());
@@ -142,21 +165,64 @@
return true;
}
- if (auto op = make_op(args.fContext, surfaceDrawContext, PathFlags::kNone, args.fAAType,
- devBounds, *args.fViewMatrix, *args.fShape, std::move(args.fPaint))) {
+ // Handle convex paths only if we couldn't fit them in the atlas. We give the atlas priority in
+ // an effort to reduce DMSAA triggers.
+ if (args.fShape->knownToBeConvex()) {
+ auto op = GrOp::Make<GrPathTessellateOp>(args.fContext, *args.fViewMatrix, path,
+ std::move(args.fPaint), args.fAAType,
+ args.fUserStencilSettings, pathDevBounds);
surfaceDrawContext->addDrawOp(args.fClip, std::move(op));
+ return true;
}
+
+ SkASSERT(args.fUserStencilSettings->isUnused()); // See onGetStencilSupport().
+ auto op = make_non_convex_fill_op(args.fContext, PathFlags::kNone, args.fAAType, pathDevBounds,
+ *args.fViewMatrix, path, std::move(args.fPaint));
+ surfaceDrawContext->addDrawOp(args.fClip, std::move(op));
return true;
}
-bool GrTessellationPathRenderer::tryAddPathToAtlas(
- const GrCaps& caps, const SkMatrix& viewMatrix, const GrStyledShape& shape,
- const SkRect& devBounds, GrAAType aaType, SkIRect* devIBounds, SkIPoint16* locationInAtlas,
- bool* transposedInAtlas) {
- if (!shape.style().isSimpleFill()) {
- return false;
+void GrTessellationPathRenderer::onStencilPath(const StencilPathArgs& args) {
+ SkASSERT(args.fShape->style().isSimpleFill()); // See onGetStencilSupport().
+
+ GrSurfaceDrawContext* surfaceDrawContext = args.fSurfaceDrawContext;
+ GrAAType aaType = (GrAA::kYes == args.fDoStencilMSAA) ? GrAAType::kMSAA : GrAAType::kNone;
+
+ SkRect pathDevBounds;
+ args.fViewMatrix->mapRect(&pathDevBounds, args.fShape->bounds());
+
+ SkPath path;
+ args.fShape->asPath(&path);
+
+ if (args.fShape->knownToBeConvex()) {
+ constexpr static GrUserStencilSettings kMarkStencil(
+ GrUserStencilSettings::StaticInit<
+ 0x0001,
+ GrUserStencilTest::kAlways,
+ 0xffff,
+ GrUserStencilOp::kReplace,
+ GrUserStencilOp::kKeep,
+ 0xffff>());
+
+ GrPaint stencilPaint;
+ stencilPaint.setXPFactory(GrDisableColorXPFactory::Get());
+ auto op = GrOp::Make<GrPathTessellateOp>(args.fContext, *args.fViewMatrix, path,
+ std::move(stencilPaint), aaType, &kMarkStencil,
+ pathDevBounds);
+ surfaceDrawContext->addDrawOp(args.fClip, std::move(op));
+ return;
}
+ auto op = make_non_convex_fill_op(args.fContext, PathFlags::kStencilOnly, aaType, pathDevBounds,
+ *args.fViewMatrix, path, GrPaint());
+ surfaceDrawContext->addDrawOp(args.fClip, std::move(op));
+}
+
+bool GrTessellationPathRenderer::tryAddPathToAtlas(const GrCaps& caps, const SkMatrix& viewMatrix,
+ const SkPath& path, const SkRect& pathDevBounds,
+ GrAAType aaType, SkIRect* devIBounds,
+ SkIPoint16* locationInAtlas,
+ bool* transposedInAtlas) {
if (!fMaxAtlasPathWidth) {
return false;
}
@@ -168,7 +234,7 @@
// Transpose tall paths in the atlas. Since we limit ourselves to small-area paths, this
// guarantees that every atlas entry has a small height, which lends very well to efficient pow2
// atlas packing.
- devBounds.roundOut(devIBounds);
+ pathDevBounds.roundOut(devIBounds);
int maxDimenstion = devIBounds->width();
int minDimension = devIBounds->height();
*transposedInAtlas = minDimension > maxDimenstion;
@@ -201,25 +267,12 @@
}
// Concatenate this path onto our uber path that matches its fill and AA types.
- SkPath path;
- shape.asPath(&path);
SkPath* uberPath = this->getAtlasUberPath(path.getFillType(), GrAAType::kNone != aaType);
uberPath->moveTo(locationInAtlas->x(), locationInAtlas->y()); // Implicit moveTo(0,0).
uberPath->addPath(path, atlasMatrix);
return true;
}
-void GrTessellationPathRenderer::onStencilPath(const StencilPathArgs& args) {
- GrSurfaceDrawContext* surfaceDrawContext = args.fSurfaceDrawContext;
- GrAAType aaType = (GrAA::kYes == args.fDoStencilMSAA) ? GrAAType::kMSAA : GrAAType::kNone;
- SkRect devBounds;
- args.fViewMatrix->mapRect(&devBounds, args.fShape->bounds());
- if (auto op = make_op(args.fContext, surfaceDrawContext, PathFlags::kStencilOnly, aaType,
- devBounds, *args.fViewMatrix, *args.fShape, GrPaint())) {
- surfaceDrawContext->addDrawOp(args.fClip, std::move(op));
- }
-}
-
void GrTessellationPathRenderer::preFlush(GrOnFlushResourceProvider* onFlushRP,
SkSpan<const uint32_t> /* taskIDs */) {
if (!fAtlas.drawBounds().isEmpty()) {
diff --git a/src/gpu/tessellate/GrTessellationPathRenderer.h b/src/gpu/tessellate/GrTessellationPathRenderer.h
index d934e8a..6b6ff1e 100644
--- a/src/gpu/tessellate/GrTessellationPathRenderer.h
+++ b/src/gpu/tessellate/GrTessellationPathRenderer.h
@@ -33,13 +33,13 @@
GrTessellationPathRenderer(GrRecordingContext*);
const char* name() const final { return "GrTessellationPathRenderer"; }
- StencilSupport onGetStencilSupport(const GrStyledShape& shape) const override {
- // TODO: Single-pass (e.g., convex) paths can have full support.
- return kStencilOnly_StencilSupport;
- }
+
+ StencilSupport onGetStencilSupport(const GrStyledShape&) const override;
CanDrawPath onCanDrawPath(const CanDrawPathArgs&) const override;
+
bool onDrawPath(const DrawPathArgs&) override;
void onStencilPath(const StencilPathArgs&) override;
+
void preFlush(GrOnFlushResourceProvider*, SkSpan<const uint32_t> taskIDs) override;
private:
@@ -48,9 +48,11 @@
idx |= (int)fillType & 1;
return &fAtlasUberPaths[idx];
}
- // Allocates space in fAtlas if the path is small and simple enough, and if there is room.
- bool tryAddPathToAtlas(const GrCaps&, const SkMatrix&, const GrStyledShape&,
- const SkRect& devBounds, GrAAType, SkIRect* devIBounds,
+ // Adds the filled path to fAtlas if the path is small enough, and if the atlas isn't full.
+ // Currently, "small enough" means 128*128 total pixels or less, and no larger than half the
+ // atlas size in either dimension.
+ bool tryAddPathToAtlas(const GrCaps&, const SkMatrix&, const SkPath&,
+ const SkRect& pathDevBounds, GrAAType, SkIRect* devIBounds,
SkIPoint16* locationInAtlas, bool* transposedInAtlas);
void renderAtlas(GrOnFlushResourceProvider*);