Add a stencil/tessellation hybrid mode for path rendering

This is a hybrid approach where we stencil only the curves using GPU
tessellation shaders, and then tessellate the path's inner polygon on
CPU and draw it directly to the final render target, stencilled
against the curves. Tessellating just the inner polygon is more than
fast enough to do in real time, and still allows us fill in the
majority of pixels in a single render pass. It gives us most the
benefit of CPU tessellation, but at a fraction of the cost.
Furthermore, the tessellated inner polygon can potentially be cached
and reused independent of the view matrix.

Change-Id: Id45adc643b51ab80a0c655dd2045a5314bdc7507
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/266456
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Greg Daniel <egdaniel@google.com>
This commit is contained in:
Chris Dalton 2020-01-29 13:16:14 -07:00 committed by Skia Commit-Bot
parent 1a049bb043
commit 4328e928ef
13 changed files with 460 additions and 190 deletions

View File

@ -418,12 +418,13 @@ skia_gpu_sources = [
"$_src/gpu/gradients/generated/GrTiledGradientEffect.cpp",
"$_src/gpu/gradients/generated/GrTiledGradientEffect.h",
"$_src/gpu/tessellate/GrCoverShader.cpp",
"$_src/gpu/tessellate/GrCoverShader.h",
"$_src/gpu/tessellate/GrFillPathShader.cpp",
"$_src/gpu/tessellate/GrFillPathShader.h",
"$_src/gpu/tessellate/GrGpuTessellationPathRenderer.cpp",
"$_src/gpu/tessellate/GrGpuTessellationPathRenderer.h",
"$_src/gpu/tessellate/GrPathParser.cpp",
"$_src/gpu/tessellate/GrPathParser.h",
"$_src/gpu/tessellate/GrPathShader.h",
"$_src/gpu/tessellate/GrStencilPathShader.cpp",
"$_src/gpu/tessellate/GrStencilPathShader.h",
"$_src/gpu/tessellate/GrTessellatePathOp.cpp",

View File

@ -27,11 +27,7 @@ GrPipeline::GrPipeline(const InitArgs& args, sk_sp<const GrXferProcessor> xferPr
}
fWindowRectsState = hardClip.windowRectsState();
if (!args.fUserStencil->isDisabled(fFlags & Flags::kHasStencilClip)) {
fFlags |= Flags::kStencilEnabled;
}
fUserStencilSettings = args.fUserStencil;
this->setUserStencil(args.fUserStencil);
fXferProcessor = std::move(xferProcessor);
@ -76,16 +72,13 @@ GrPipeline::GrPipeline(GrScissorTest scissorTest, sk_sp<const GrXferProcessor> x
const GrSwizzle& outputSwizzle, InputFlags inputFlags,
const GrUserStencilSettings* userStencil)
: fWindowRectsState()
, fUserStencilSettings(userStencil)
, fFlags((Flags)inputFlags)
, fXferProcessor(std::move(xp))
, fOutputSwizzle(outputSwizzle) {
if (GrScissorTest::kEnabled == scissorTest) {
fFlags |= Flags::kScissorEnabled;
}
if (!userStencil->isDisabled(false)) {
fFlags |= Flags::kStencilEnabled;
}
this->setUserStencil(userStencil);
}
void GrPipeline::genKey(GrProcessorKeyBuilder* b, const GrCaps& caps) const {

View File

@ -187,6 +187,12 @@ public:
/// @}
const GrUserStencilSettings* getUserStencil() const { return fUserStencilSettings; }
void setUserStencil(const GrUserStencilSettings* stencil) {
fUserStencilSettings = stencil;
if (!fUserStencilSettings->isDisabled(fFlags & Flags::kHasStencilClip)) {
fFlags |= Flags::kStencilEnabled;
}
}
bool isScissorEnabled() const {
return SkToBool(fFlags & Flags::kScissorEnabled);

View File

@ -100,7 +100,6 @@ public:
kGrConicEffect_ClassID,
kGrConstColorProcessor_ClassID,
kGrConvexPolyEffect_ClassID,
kGrCoverShader_ClassID,
kGrDeviceSpaceTextureDecalFragmentProcessor_ClassID,
kGrDiffuseLightingEffect_ClassID,
kGrDisplacementMapEffect_ClassID,
@ -160,6 +159,9 @@ public:
kSwizzleFragmentProcessor_ClassID,
kTessellationTestTriShader_ClassID,
kTessellationTestRectShader_ClassID,
kTessellate_GrFillBoundingBoxShader_ClassID,
kTessellate_GrFillCubicHullShader_ClassID,
kTessellate_GrFillTriangleShader_ClassID,
kTessellate_GrStencilCubicShader_ClassID,
kTessellate_GrStencilTriangleShader_ClassID,
kTessellate_GrStencilWedgeShader_ClassID,

View File

@ -1,74 +0,0 @@
/*
* Copyright 2019 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/GrCoverShader.h"
#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
#include "src/gpu/glsl/GrGLSLGeometryProcessor.h"
#include "src/gpu/glsl/GrGLSLVarying.h"
#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
class GrCoverShader::Impl : public GrGLSLGeometryProcessor {
void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) final {
const char* viewMatrix;
fViewMatrixUniform = args.fUniformHandler->addUniform(
kVertex_GrShaderFlag, kFloat3x3_GrSLType, "view_matrix", &viewMatrix);
const char* pathBounds;
fPathBoundsUniform = args.fUniformHandler->addUniform(
kVertex_GrShaderFlag, kFloat4_GrSLType, "path_bounds", &pathBounds);
const char* color;
fColorUniform = args.fUniformHandler->addUniform(
kFragment_GrShaderFlag, kHalf4_GrSLType, "color", &color);
args.fVaryingHandler->emitAttributes(args.fGP.cast<GrCoverShader>());
args.fVertBuilder->codeAppendf(R"(
// Use sk_VertexID and uniforms (instead of vertex data) to find vertex positions.
float2 T = float2(sk_VertexID & 1, sk_VertexID >> 1);
float2 localcoord = mix(%s.xy, %s.zw, T);
float2 vertexpos = (%s * float3(localcoord, 1)).xy;
// Outset to avoid possible T-junctions with extreme edges of the path.
float2x2 M2 = float2x2(%s);
float2 devoutset = .25 * sign(M2 * (T - .5));
vertexpos += devoutset;
localcoord += inverse(M2) * devoutset;)",
pathBounds, pathBounds, viewMatrix, viewMatrix);
this->emitTransforms(args.fVertBuilder, args.fVaryingHandler, args.fUniformHandler,
GrShaderVar("localcoord", kFloat2_GrSLType),
args.fFPCoordTransformHandler);
gpArgs->fPositionVar.set(kFloat2_GrSLType, "vertexpos");
args.fFragBuilder->codeAppendf(R"(
%s = %s;
%s = half4(1);)",
args.fOutputColor, color, args.fOutputCoverage);
}
void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& primProc,
const CoordTransformRange& transformRange) override {
const GrCoverShader& shader = primProc.cast<GrCoverShader>();
const SkRect& b = shader.fPathBounds;
const SkPMColor4f& color = shader.fColor;
pdman.setSkMatrix(fViewMatrixUniform, shader.fViewMatrix);
pdman.set4f(fPathBoundsUniform, b.left(), b.top(), b.right(), b.bottom());
pdman.set4f(fColorUniform, color.fR, color.fG, color.fB, color.fA);
this->setTransformDataHelper(SkMatrix::I(), pdman, transformRange);
}
GrGLSLUniformHandler::UniformHandle fViewMatrixUniform;
GrGLSLUniformHandler::UniformHandle fPathBoundsUniform;
GrGLSLUniformHandler::UniformHandle fColorUniform;
};
GrGLSLPrimitiveProcessor* GrCoverShader::createGLSLInstance(const GrShaderCaps&) const {
return new Impl;
}

View File

@ -1,36 +0,0 @@
/*
* Copyright 2019 Google LLC.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef GrCoverShader_DEFINED
#define GrCoverShader_DEFINED
#include "src/gpu/GrGeometryProcessor.h"
// Draws a path's bounding box, with a subpixel outset to avoid possible T-junctions with extreme
// edges of the path. This class is used for the "cover" pass of stencil-then-cover path rendering.
// NOTE: The emitted geometry may not be axis-aligned, depending on the view matrix.
class GrCoverShader : public GrGeometryProcessor {
public:
GrCoverShader(const SkMatrix& viewMatrix, const SkRect& pathBounds, const SkPMColor4f& color)
: GrGeometryProcessor(kGrCoverShader_ClassID)
, fViewMatrix(viewMatrix)
, fPathBounds(pathBounds)
, fColor(color) {}
const char* name() const override { return "GrCoverShader"; }
void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const final {}
GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const final;
private:
const SkMatrix fViewMatrix;
const SkRect fPathBounds;
const SkPMColor4f fColor;
class Impl;
};
#endif

View File

@ -0,0 +1,136 @@
/*
* Copyright 2020 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/GrFillPathShader.h"
#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
#include "src/gpu/glsl/GrGLSLGeometryProcessor.h"
#include "src/gpu/glsl/GrGLSLVarying.h"
#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
class GrFillPathShader::Impl : public GrGLSLGeometryProcessor {
public:
void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
auto& shader = args.fGP.cast<GrFillPathShader>();
const char* viewMatrix;
fViewMatrixUniform = args.fUniformHandler->addUniform(
kVertex_GrShaderFlag, kFloat3x3_GrSLType, "view_matrix", &viewMatrix);
args.fVaryingHandler->emitAttributes(shader);
args.fVertBuilder->codeAppend("float2 localcoord, vertexpos;");
shader.emitVertexCode(this, args.fVertBuilder, viewMatrix, args.fUniformHandler);
this->emitTransforms(args.fVertBuilder, args.fVaryingHandler, args.fUniformHandler,
GrShaderVar("localcoord", kFloat2_GrSLType),
args.fFPCoordTransformHandler);
gpArgs->fPositionVar.set(kFloat2_GrSLType, "vertexpos");
const char* color;
fColorUniform = args.fUniformHandler->addUniform(
kFragment_GrShaderFlag, kHalf4_GrSLType, "color", &color);
args.fFragBuilder->codeAppendf("%s = %s;", args.fOutputColor, color);
args.fFragBuilder->codeAppendf("%s = half4(1);", args.fOutputCoverage);
}
void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& primProc,
const CoordTransformRange& transformRange) override {
const GrFillPathShader& shader = primProc.cast<GrFillPathShader>();
pdman.setSkMatrix(fViewMatrixUniform, shader.viewMatrix());
const SkPMColor4f& color = shader.fColor;
pdman.set4f(fColorUniform, color.fR, color.fG, color.fB, color.fA);
if (fPathBoundsUniform.isValid()) {
const SkRect& b = primProc.cast<GrFillBoundingBoxShader>().pathBounds();
pdman.set4f(fPathBoundsUniform, b.left(), b.top(), b.right(), b.bottom());
}
this->setTransformDataHelper(SkMatrix::I(), pdman, transformRange);
}
GrGLSLUniformHandler::UniformHandle fViewMatrixUniform;
GrGLSLUniformHandler::UniformHandle fColorUniform;
GrGLSLUniformHandler::UniformHandle fPathBoundsUniform;
};
GrGLSLPrimitiveProcessor* GrFillPathShader::createGLSLInstance(const GrShaderCaps&) const {
return new Impl;
}
void GrFillTriangleShader::emitVertexCode(Impl*, GrGLSLVertexBuilder* v, const char* viewMatrix,
GrGLSLUniformHandler* uniformHandler) const {
v->codeAppendf(R"(
localcoord = input_point;
vertexpos = (%s * float3(localcoord, 1)).xy;)", viewMatrix);
}
void GrFillCubicHullShader::emitVertexCode(Impl*, GrGLSLVertexBuilder* v, const char* viewMatrix,
GrGLSLUniformHandler* uniformHandler) const {
v->codeAppend(R"(
float4x2 P = float4x2(input_points_0_1, input_points_2_3);
// Translate the points to v0..3 where v0=0.
float2 v1 = P[1] - P[0], v2 = P[2] - P[0], v3 = P[3] - P[0];
// Reorder the points so v2 bisects v1 and v3.
if (sign(determinant(float2x2(v2,v1))) == sign(determinant(float2x2(v2,v3)))) {
float2 tmp = P[2];
if (sign(determinant(float2x2(v1,v2))) != sign(determinant(float2x2(v1,v3)))) {
P[2] = P[1]; // swap(P2, P1)
P[1] = tmp;
} else {
P[2] = P[3]; // swap(P2, P3)
P[3] = tmp;
}
}
// Find the "turn direction" of each corner and net turn direction.
float4 dir;
float netdir = 0.0;
for (int i = 0; i < 4; ++i) {
float2 prev = P[i] - P[(i + 3) & 3], next = P[(i + 1) & 3] - P[i];
dir[i] = sign(determinant(float2x2(prev, next)));
netdir += dir[i];
}
// sk_VertexID comes in fan order. Convert to strip order.
int vertexidx = sk_VertexID;
vertexidx ^= vertexidx >> 1;
// Remove the non-convex vertex, if any.
if (dir[vertexidx] != sign(netdir)) {
vertexidx = (vertexidx + 1) & 3;
}
localcoord = P[vertexidx];)");
v->codeAppendf("vertexpos = (%s * float3(localcoord, 1)).xy;", viewMatrix);
}
void GrFillBoundingBoxShader::emitVertexCode(Impl* impl, GrGLSLVertexBuilder* v,
const char* viewMatrix,
GrGLSLUniformHandler* uniformHandler) const {
const char* pathBounds;
impl->fPathBoundsUniform = uniformHandler->addUniform(
kVertex_GrShaderFlag, kFloat4_GrSLType, "path_bounds", &pathBounds);
v->codeAppendf(R"(
// Use sk_VertexID and uniforms (instead of vertex data) to find vertex positions.
float2 T = float2(sk_VertexID & 1, sk_VertexID >> 1);
localcoord = mix(%s.xy, %s.zw, T);
vertexpos = (%s * float3(localcoord, 1)).xy;
// Outset to avoid possible T-junctions with extreme edges of the path.
float2x2 M2 = float2x2(%s);
float2 devoutset = .25 * sign(M2 * (T - .5));
localcoord += inverse(M2) * devoutset;
vertexpos += devoutset;)", pathBounds, pathBounds, viewMatrix, viewMatrix);
}

View File

@ -0,0 +1,94 @@
/*
* Copyright 2020 Google LLC.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef GrFillPathShader_DEFINED
#define GrFillPathShader_DEFINED
#include "src/gpu/tessellate/GrPathShader.h"
class GrGLSLUniformHandler;
class GrGLSLVertexBuilder;
// This is the base class for shaders that fill a path's pixels in the final render target.
class GrFillPathShader : public GrPathShader {
public:
GrFillPathShader(ClassID classID, const SkMatrix& viewMatrix, SkPMColor4f color,
GrPrimitiveType primitiveType)
: GrPathShader(classID, viewMatrix, primitiveType, 0)
, fColor(color) {
}
void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override {}
GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const final;
protected:
class Impl;
virtual void emitVertexCode(Impl*, GrGLSLVertexBuilder*, const char* viewMatrix,
GrGLSLUniformHandler*) const = 0;
private:
const SkPMColor4f fColor;
};
// Fills a simple array of triangles.
class GrFillTriangleShader : public GrFillPathShader {
public:
GrFillTriangleShader(const SkMatrix& viewMatrix, SkPMColor4f color)
: GrFillPathShader(kTessellate_GrFillTriangleShader_ClassID, viewMatrix, color,
GrPrimitiveType::kTriangles) {
static constexpr Attribute kPtAttrib = {
"input_point", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
this->setVertexAttributes(&kPtAttrib, 1);
}
private:
const char* name() const override { return "GrFillTriangleShader"; }
void emitVertexCode(Impl*, GrGLSLVertexBuilder*, const char* viewMatrix,
GrGLSLUniformHandler*) const override;
};
// Fills an array of convex hulls surrounding 4-point cubic instances.
class GrFillCubicHullShader : public GrFillPathShader {
public:
GrFillCubicHullShader(const SkMatrix& viewMatrix, SkPMColor4f color)
: GrFillPathShader(kTessellate_GrFillCubicHullShader_ClassID, viewMatrix, color,
GrPrimitiveType::kTriangleStrip) {
static constexpr Attribute kPtsAttribs[] = {
{"input_points_0_1", kFloat4_GrVertexAttribType, kFloat4_GrSLType},
{"input_points_2_3", kFloat4_GrVertexAttribType, kFloat4_GrSLType}};
this->setInstanceAttributes(kPtsAttribs, SK_ARRAY_COUNT(kPtsAttribs));
}
private:
const char* name() const override { return "GrFillCubicHullShader"; }
void emitVertexCode(Impl*, GrGLSLVertexBuilder*, const char* viewMatrix,
GrGLSLUniformHandler*) const override;
};
// Fills a path's bounding box, with subpixel outset to avoid possible T-junctions with extreme
// edges of the path.
// NOTE: The emitted geometry may not be axis-aligned, depending on the view matrix.
class GrFillBoundingBoxShader : public GrFillPathShader {
public:
GrFillBoundingBoxShader(const SkMatrix& viewMatrix, SkPMColor4f color, const SkRect& pathBounds)
: GrFillPathShader(kTessellate_GrFillBoundingBoxShader_ClassID, viewMatrix, color,
GrPrimitiveType::kTriangleStrip)
, fPathBounds(pathBounds) {
}
const SkRect& pathBounds() const { return fPathBounds; }
private:
const char* name() const override { return "GrFillBoundingBoxShader"; }
void emitVertexCode(Impl*, GrGLSLVertexBuilder*, const char* viewMatrix,
GrGLSLUniformHandler*) const override;
const SkRect fPathBounds;
};
#endif

View File

@ -0,0 +1,64 @@
/*
* Copyright 2020 Google LLC.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef GrPathShader_DEFINED
#define GrPathShader_DEFINED
#include "src/gpu/GrGeometryProcessor.h"
#include "src/gpu/GrOpFlushState.h"
#include "src/gpu/GrOpsRenderPass.h"
#include "src/gpu/GrProgramInfo.h"
// This is a common base class for shaders in the GPU tessellator.
class GrPathShader : public GrGeometryProcessor {
public:
GrPathShader(ClassID classID, const SkMatrix& viewMatrix, GrPrimitiveType primitiveType,
int tessellationPatchVertexCount)
: GrGeometryProcessor(classID)
, fViewMatrix(viewMatrix)
, fPrimitiveType(primitiveType)
, fTessellationPatchVertexCount(tessellationPatchVertexCount) {
if (fTessellationPatchVertexCount) {
this->setWillUseTessellationShaders();
}
}
const SkMatrix& viewMatrix() const { return fViewMatrix; }
GrPrimitiveType primitiveType() const { return fPrimitiveType; }
int tessellationPatchVertexCount() const { return fTessellationPatchVertexCount; }
void issueDraw(GrOpFlushState* state, const GrPipeline* pipeline,
const GrPipeline::FixedDynamicState* fixedDynamicState,
sk_sp<const GrBuffer> vertexBuffer, int vertexCount, int baseVertex,
const SkRect& bounds) {
GrMesh mesh(fPrimitiveType, fTessellationPatchVertexCount);
mesh.setNonIndexedNonInstanced(vertexCount);
mesh.setVertexData(std::move(vertexBuffer), baseVertex);
this->issueDraw(state, pipeline, fixedDynamicState, mesh, bounds);
}
void issueDraw(GrOpFlushState* state, const GrPipeline* pipeline,
const GrPipeline::FixedDynamicState* fixedDynamicState, const GrMesh& mesh,
const SkRect& bounds) {
SkASSERT(mesh.primitiveType() == fPrimitiveType);
SkASSERT(mesh.tessellationPatchVertexCount() == fTessellationPatchVertexCount);
GrProgramInfo programInfo(state->proxy()->numSamples(), state->proxy()->numStencilSamples(),
state->proxy()->backendFormat(), state->view()->origin(),
pipeline, this, fixedDynamicState, nullptr, 0,
fPrimitiveType, fTessellationPatchVertexCount);
state->opsRenderPass()->draw(programInfo, &mesh, 1, bounds);
}
private:
const SkMatrix fViewMatrix;
const GrPrimitiveType fPrimitiveType;
const int fTessellationPatchVertexCount;
class Impl;
};
#endif

View File

@ -40,7 +40,7 @@ class GrStencilPathShader::Impl : public GrGLSLGeometryProcessor {
args.fVaryingHandler->emitAttributes(shader);
GrShaderVar vertexPos = (*shader.vertexAttributes().begin()).asShaderVar();
if (!shader.fViewMatrix.isIdentity()) {
if (!shader.viewMatrix().isIdentity()) {
const char* viewMatrix;
fViewMatrixUniform = args.fUniformHandler->addUniform(
kVertex_GrShaderFlag, kFloat3x3_GrSLType, "view_matrix", &viewMatrix);
@ -63,8 +63,8 @@ class GrStencilPathShader::Impl : public GrGLSLGeometryProcessor {
void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& primProc,
const CoordTransformRange& transformRange) override {
const auto& shader = primProc.cast<GrStencilPathShader>();
if (!shader.fViewMatrix.isIdentity()) {
pdman.setSkMatrix(fViewMatrixUniform, shader.fViewMatrix);
if (!shader.viewMatrix().isIdentity()) {
pdman.setSkMatrix(fViewMatrixUniform, shader.viewMatrix());
}
}

View File

@ -8,36 +8,25 @@
#ifndef GrStencilPathShader_DEFINED
#define GrStencilPathShader_DEFINED
#include "src/gpu/GrGeometryProcessor.h"
#include "src/gpu/tessellate/GrPathShader.h"
// This is the base class for shaders that stencil path elements, namely, triangles, standalone
// cubics, and wedges.
class GrStencilPathShader : public GrGeometryProcessor {
class GrStencilPathShader : public GrPathShader {
public:
GrStencilPathShader(ClassID classID, const SkMatrix& viewMatrix, GrPrimitiveType primitiveType,
int tessellationPatchVertexCount = 0)
: GrGeometryProcessor(classID)
, fViewMatrix(viewMatrix)
, fPrimitiveType(primitiveType)
, fTessellationPatchVertexCount(tessellationPatchVertexCount) {
: GrPathShader(classID, viewMatrix, primitiveType, tessellationPatchVertexCount) {
constexpr static Attribute kPointAttrib = {
"point", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
this->setVertexAttributes(&kPointAttrib, 1);
if (fTessellationPatchVertexCount) {
this->setWillUseTessellationShaders();
}
}
GrPrimitiveType primitiveType() const { return fPrimitiveType; }
int tessellationPatchVertexCount() const { return fTessellationPatchVertexCount; }
void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const final {
b->add32(fViewMatrix.isIdentity());
}
GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const final;
private:
const SkMatrix fViewMatrix;
const GrPrimitiveType fPrimitiveType;
const int fTessellationPatchVertexCount;
void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const final {
b->add32(this->viewMatrix().isIdentity());
}
GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const final;
class Impl;
};

View File

@ -10,9 +10,8 @@
#include "src/gpu/GrEagerVertexAllocator.h"
#include "src/gpu/GrGpu.h"
#include "src/gpu/GrOpFlushState.h"
#include "src/gpu/GrOpsRenderPass.h"
#include "src/gpu/GrProgramInfo.h"
#include "src/gpu/tessellate/GrCoverShader.h"
#include "src/gpu/GrTessellator.h"
#include "src/gpu/tessellate/GrFillPathShader.h"
#include "src/gpu/tessellate/GrPathParser.h"
#include "src/gpu/tessellate/GrStencilPathShader.h"
@ -29,24 +28,66 @@ void GrTessellatePathOp::onPrepare(GrOpFlushState* state) {
GrEagerDynamicVertexAllocator cubicInstanceAllocator(state, &fCubicInstanceBuffer,
&fBaseCubicInstance);
// First see if we should split up inner polygon triangles and curves, and triangulate the inner
// First check if the path is large and/or simple enough that we can actually tessellate the
// inner polygon(s) on the CPU. This is our fastest approach. It allows us to stencil only the
// curves, and then draw the internal polygons directly to the final render target, thus filling
// in the majority of pixels in a single render pass.
SkScalar scales[2];
SkAssertResult(fViewMatrix.getMinMaxScales(scales)); // Will fail if perspective.
const SkRect& bounds = fPath.getBounds();
int numVerbs = fPath.countVerbs();
if (numVerbs <= 0) {
return;
}
float gpuFragmentWork = bounds.height() * scales[0] * bounds.width() * scales[1];
float cpuTessellationWork = (float)numVerbs * SkNextLog2(numVerbs); // N log N.
if (cpuTessellationWork * 500 + (256 * 256) < gpuFragmentWork) { // Don't try below 256x256.
bool pathIsLinear;
// PathToTriangles(..kSimpleInnerPolygon..) will fail if the inner polygon is not simple.
if ((fPathVertexCount = GrTessellator::PathToTriangles(
fPath, 0, SkRect::MakeEmpty(), &pathVertexAllocator,
GrTessellator::Mode::kSimpleInnerPolygons, &pathIsLinear))) {
if (((Flags::kStencilOnly | Flags::kWireframe) & fFlags) ||
GrAAType::kCoverage == fAAType ||
(state->appliedClip() && state->appliedClip()->hasStencilClip())) {
// If we have certain flags, mixed samples, or a stencil clip then we unfortunately
// can't fill the inner polygon directly. Create a stencil shader here to ensure we
// still stencil the entire path.
fStencilPathShader = state->allocator()->make<GrStencilTriangleShader>(fViewMatrix);
}
if (!(Flags::kStencilOnly & fFlags)) {
fFillPathShader = state->allocator()->make<GrFillTriangleShader>(
fViewMatrix, fColor);
}
if (!pathIsLinear) {
fCubicInstanceCount = GrPathParser::EmitCubicInstances(
fPath, &cubicInstanceAllocator);
SkASSERT(fCubicInstanceCount);
}
return;
}
}
// Next see if we can split up inner polygon triangles and curves, and triangulate the inner
// polygon(s) more efficiently. This causes greater CPU overhead due to the extra shaders and
// draw calls, but the better triangulation can reduce the rasterizer load by a great deal on
// complex paths.
const SkRect& bounds = fPath.getBounds();
float scale = fViewMatrix.getMaxScale();
// Raster-edge work is 1-dimensional, so we sum height and width rather than multiplying them.
float rasterEdgeWork = (bounds.height() + bounds.width()) * scale * fPath.countVerbs();
// NOTE: Raster-edge work is 1-dimensional, so we sum height and width instead of multiplying.
float rasterEdgeWork = (bounds.height() + bounds.width()) * scales[1] * fPath.countVerbs();
if (rasterEdgeWork > 1000 * 1000) {
fPathShader = state->allocator()->make<GrStencilTriangleShader>(fViewMatrix);
fPathVertexCount = GrPathParser::EmitInnerPolygonTriangles(fPath, &pathVertexAllocator);
if ((fPathVertexCount =
GrPathParser::EmitInnerPolygonTriangles(fPath, &pathVertexAllocator))) {
fStencilPathShader = state->allocator()->make<GrStencilTriangleShader>(fViewMatrix);
}
fCubicInstanceCount = GrPathParser::EmitCubicInstances(fPath, &cubicInstanceAllocator);
return;
}
// Fastest CPU approach: emit one cubic wedge per verb, fanning out from the center.
fPathShader = state->allocator()->make<GrStencilWedgeShader>(fViewMatrix);
fPathVertexCount = GrPathParser::EmitCenterWedgePatches(fPath, &pathVertexAllocator);
if ((fPathVertexCount = GrPathParser::EmitCenterWedgePatches(fPath, &pathVertexAllocator))) {
fStencilPathShader = state->allocator()->make<GrStencilWedgeShader>(fViewMatrix);
}
}
void GrTessellatePathOp::onExecute(GrOpFlushState* state, const SkRect& chainBounds) {
@ -100,33 +141,19 @@ void GrTessellatePathOp::drawStencilPass(GrOpFlushState* state, const GrAppliedH
GrPipeline pipeline(initArgs, GrDisableColorXPFactory::MakeXferProcessor(), hardClip);
if (fPathVertexBuffer) {
GrProgramInfo programInfo(state->proxy()->numSamples(), state->proxy()->numStencilSamples(),
state->proxy()->backendFormat(), state->view()->origin(),
&pipeline, fPathShader, fixedDynamicState, nullptr, 0,
fPathShader->primitiveType(),
fPathShader->tessellationPatchVertexCount());
GrMesh mesh(fPathShader->primitiveType(), fPathShader->tessellationPatchVertexCount());
mesh.setNonIndexedNonInstanced(fPathVertexCount);
mesh.setVertexData(fPathVertexBuffer, fBasePathVertex);
state->opsRenderPass()->draw(programInfo, &mesh, 1, this->bounds());
if (fStencilPathShader) {
SkASSERT(fPathVertexBuffer);
fStencilPathShader->issueDraw(state, &pipeline, fixedDynamicState, fPathVertexBuffer,
fPathVertexCount, fBasePathVertex, this->bounds());
}
if (fCubicInstanceBuffer) {
// Here we treat the cubic instance buffer as tessellation patches.
GrStencilCubicShader shader(fViewMatrix);
GrProgramInfo programInfo(state->proxy()->numSamples(), state->proxy()->numStencilSamples(),
state->proxy()->backendFormat(), state->view()->origin(),
&pipeline, &shader, fixedDynamicState, nullptr, 0,
GrPrimitiveType::kPatches, 4);
// Here we treat the cubic instance buffer as tessellation patches to stencil the curves.
GrMesh mesh(GrPrimitiveType::kPatches, 4);
mesh.setNonIndexedNonInstanced(fCubicInstanceCount * 4);
mesh.setVertexData(fCubicInstanceBuffer, fBaseCubicInstance * 4);
state->opsRenderPass()->draw(programInfo, &mesh, 1, this->bounds());
GrStencilCubicShader(fViewMatrix).issueDraw(
state, &pipeline, fixedDynamicState, mesh, this->bounds());
}
// http://skbug.com/9739
@ -162,20 +189,74 @@ void GrTessellatePathOp::drawCoverPass(GrOpFlushState* state, GrAppliedClip&& cl
initArgs.fInputFlags |= GrPipeline::InputFlags::kConservativeRaster;
}
}
initArgs.fUserStencil = &kTestAndResetStencil;
initArgs.fCaps = &state->caps();
initArgs.fDstProxyView = state->drawOpArgs().dstProxyView();
initArgs.fOutputSwizzle = state->drawOpArgs().outputSwizzle();
GrPipeline pipeline(initArgs, std::move(fProcessors), std::move(clip));
GrCoverShader shader(fViewMatrix, fPath.getBounds(), fColor);
GrProgramInfo programInfo(state->proxy()->numSamples(), state->proxy()->numStencilSamples(),
state->proxy()->backendFormat(), state->view()->origin(), &pipeline,
&shader, fixedDynamicState, nullptr, 0,
GrPrimitiveType::kTriangleStrip);
GrMesh mesh(GrPrimitiveType::kTriangleStrip);
mesh.setNonIndexedNonInstanced(4);
if (fFillPathShader) {
SkASSERT(fPathVertexBuffer);
state->opsRenderPass()->draw(programInfo, &mesh, 1, this->bounds());
// These are a twist on the standard red book stencil settings that allow us to draw the
// inner polygon directly to the final render target. At this point, the curves are already
// stencilled in. So if the stencil value is zero, then it means the path at our sample is
// not affected by any curves and we fill the path in directly. If the stencil value is
// nonzero, then we don't fill and instead continue the standard red book stencil process.
//
// NOTE: These settings are currently incompatible with a stencil clip.
constexpr static GrUserStencilSettings kFillOrIncrDecrStencil(
GrUserStencilSettings::StaticInitSeparate<
0x0000, 0x0000,
GrUserStencilTest::kEqual, GrUserStencilTest::kEqual,
0xffff, 0xffff,
GrUserStencilOp::kKeep, GrUserStencilOp::kKeep,
GrUserStencilOp::kIncWrap, GrUserStencilOp::kDecWrap,
0xffff, 0xffff>());
constexpr static GrUserStencilSettings kFillOrInvertStencil(
GrUserStencilSettings::StaticInit<
0x0000,
GrUserStencilTest::kEqual,
0xffff,
GrUserStencilOp::kKeep,
GrUserStencilOp::kZero,
0xffff>());
if (fStencilPathShader) {
// The path was already stencilled. Here we just need to do a cover pass.
pipeline.setUserStencil(&kTestAndResetStencil);
} else if (!fCubicInstanceBuffer) {
// There are no curves, so we can just ignore stencil and fill the path directly.
pipeline.setUserStencil(&GrUserStencilSettings::kUnused);
} else if (SkPathFillType::kWinding == fPath.getFillType()) {
// Fill in the path pixels not touched by curves, incr/decr stencil otherwise.
SkASSERT(!pipeline.hasStencilClip());
pipeline.setUserStencil(&kFillOrIncrDecrStencil);
} else {
// Fill in the path pixels not touched by curves, invert stencil otherwise.
SkASSERT(!pipeline.hasStencilClip());
pipeline.setUserStencil(&kFillOrInvertStencil);
}
fFillPathShader->issueDraw(state, &pipeline, fixedDynamicState, fPathVertexBuffer,
fPathVertexCount, fBasePathVertex, this->bounds());
if (fCubicInstanceBuffer) {
// At this point, every pixel is filled in except the ones touched by curves. Issue a
// final cover pass over the curves by drawing their convex hulls. This will fill in any
// remaining samples and reset the stencil buffer.
GrMesh mesh(GrPrimitiveType::kTriangleStrip);
mesh.setInstanced(fCubicInstanceBuffer, fCubicInstanceCount, fBaseCubicInstance, 4);
pipeline.setUserStencil(&kTestAndResetStencil);
GrFillCubicHullShader(fViewMatrix, fColor).issueDraw(
state, &pipeline, fixedDynamicState, mesh, this->bounds());
}
} else {
// There is not a fill shader for the path. Just draw a bounding box.
GrMesh mesh(GrPrimitiveType::kTriangleStrip);
mesh.setNonIndexedNonInstanced(4);
pipeline.setUserStencil(&kTestAndResetStencil);
GrFillBoundingBoxShader(fViewMatrix, fColor, fPath.getBounds()).issueDraw(
state, &pipeline, fixedDynamicState, mesh, this->bounds());
}
}

View File

@ -11,6 +11,7 @@
#include "src/gpu/ops/GrDrawOp.h"
class GrAppliedHardClip;
class GrFillPathShader;
class GrStencilPathShader;
// Renders paths using the classic Red Book "stencil, then cover" method. Curves get linearized by
@ -67,12 +68,25 @@ private:
SkPMColor4f fColor;
GrProcessorSet fProcessors;
// The "path shader" draws the below path geometry.
GrStencilPathShader* fPathShader;
// These path shaders get created during onPrepare for drawing the below path vertex data.
//
// If fFillPathShader is null, then we just stencil the full path using fStencilPathShader and
// fCubicInstanceBuffer, and then fill it using a simple bounding box.
//
// If fFillPathShader is not null, then we fill the path using it plus cubic hulls from
// fCubicInstanceBuffer instead of a bounding box.
//
// If fFillPathShader is not null and fStencilPathShader *is* null, then the vertex data
// contains non-overlapping path geometry that can be drawn directly to the final render target.
// We only need to stencil curves from fCubicInstanceBuffer, and then draw the rest of the path
// directly.
GrStencilPathShader* fStencilPathShader = nullptr;
GrFillPathShader* fFillPathShader = nullptr;
// The "path vertex buffer" is made up of either inner polygon triangles (see
// GrPathParser::EmitInnerPolygonTriangles) or cubic wedge patches (see
// GrPathParser::EmitCenterWedgePatches).
// The "path vertex data" is made up of cubic wedges or inner polygon triangles (either red book
// style or fully tessellated). The geometry is generated by
// GrPathParser::EmitCenterWedgePatches, GrPathParser::EmitInnerPolygonTriangles,
// or GrTessellator::PathToTriangles.
sk_sp<const GrBuffer> fPathVertexBuffer;
int fBasePathVertex;
int fPathVertexCount;