ccpr: Indicate path fill type by winding direction of cover triangles
Draws the cover geometry for "nonzero" paths with clockwise triangles,
and "even/odd" paths with counterclockwise. This will allow us to use
a double-sided stencil function to resolve coverage in MSAA mode.
Bug: skia:
Change-Id: I067acbe9a466f97f67223d27fea28d758173d25f
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/209232
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Chris Dalton <csmartdalton@google.com>
diff --git a/src/gpu/ccpr/GrCCPathProcessor.cpp b/src/gpu/ccpr/GrCCPathProcessor.cpp
index feefd4f..852875e 100644
--- a/src/gpu/ccpr/GrCCPathProcessor.cpp
+++ b/src/gpu/ccpr/GrCCPathProcessor.cpp
@@ -17,19 +17,20 @@
#include "glsl/GrGLSLVarying.h"
// Paths are drawn as octagons. Each point on the octagon is the intersection of two lines: one edge
-// from the path's bounding box and one edge from its 45-degree bounding box. The below inputs
-// define a vertex by the two edges that need to be intersected. Normals point out of the octagon,
-// and the bounding boxes are sent in as instance attribs.
-static constexpr float kOctoEdgeNorms[8 * 4] = {
+// from the path's bounding box and one edge from its 45-degree bounding box. The selectors
+// below indicate one corner from the bounding box, paired with a corner from the 45-degree bounding
+// box. The octagon vertex is the point that lies between these two corners, found by intersecting
+// their edges.
+static constexpr float kOctoEdgeNorms[8*4] = {
// bbox // bbox45
- -1, 0, -1,+1,
- -1, 0, -1,-1,
- 0,-1, -1,-1,
- 0,-1, +1,-1,
- +1, 0, +1,-1,
- +1, 0, +1,+1,
- 0,+1, +1,+1,
- 0,+1, -1,+1,
+ 0,0, 0,0,
+ 0,0, 1,0,
+ 1,0, 1,0,
+ 1,0, 1,1,
+ 1,1, 1,1,
+ 1,1, 0,1,
+ 0,1, 0,1,
+ 0,1, 0,0,
};
GR_DECLARE_STATIC_UNIQUE_KEY(gVertexBufferKey);
@@ -43,26 +44,26 @@
static constexpr uint16_t kRestartStrip = 0xffff;
static constexpr uint16_t kOctoIndicesAsStrips[] = {
- 1, 0, 2, 4, 3, kRestartStrip, // First half.
- 5, 4, 6, 0, 7 // Second half.
+ 3, 4, 2, 0, 1, kRestartStrip, // First half.
+ 7, 0, 6, 4, 5 // Second half.
};
static constexpr uint16_t kOctoIndicesAsTris[] = {
// First half.
- 1, 0, 2,
- 0, 4, 2,
- 2, 4, 3,
+ 3, 4, 2,
+ 4, 0, 2,
+ 2, 0, 1,
// Second half.
- 5, 4, 6,
- 4, 0, 6,
- 6, 0, 7,
+ 7, 0, 6,
+ 0, 4, 6,
+ 6, 4, 5,
};
GR_DECLARE_STATIC_UNIQUE_KEY(gIndexBufferKey);
constexpr GrPrimitiveProcessor::Attribute GrCCPathProcessor::kInstanceAttribs[];
-constexpr GrPrimitiveProcessor::Attribute GrCCPathProcessor::kEdgeNormsAttrib;
+constexpr GrPrimitiveProcessor::Attribute GrCCPathProcessor::kCornersAttrib;
sk_sp<const GrGpuBuffer> GrCCPathProcessor::FindIndexBuffer(GrOnFlushResourceProvider* onFlushRP) {
GR_DEFINE_STATIC_UNIQUE_KEY(gIndexBufferKey);
@@ -85,10 +86,10 @@
, fAtlasSize(atlas->isize())
, fAtlasOrigin(atlas->origin()) {
// TODO: Can we just assert that atlas has GrCCAtlas::kTextureOrigin and remove fAtlasOrigin?
- this->setInstanceAttributes(kInstanceAttribs, kNumInstanceAttribs);
+ this->setInstanceAttributes(kInstanceAttribs, SK_ARRAY_COUNT(kInstanceAttribs));
SkASSERT(this->instanceStride() == sizeof(Instance));
- this->setVertexAttributes(&kEdgeNormsAttrib, 1);
+ this->setVertexAttributes(&kCornersAttrib, 1);
this->setTextureSamplerCnt(1);
if (!viewMatrixIfUsingLocalCoords.invert(&fLocalMatrix)) {
@@ -96,7 +97,7 @@
}
}
-class GLSLPathProcessor : public GrGLSLGeometryProcessor {
+class GrCCPathProcessor::Impl : public GrGLSLGeometryProcessor {
public:
void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override;
@@ -115,7 +116,7 @@
};
GrGLSLPrimitiveProcessor* GrCCPathProcessor::createGLSLInstance(const GrShaderCaps&) const {
- return new GLSLPathProcessor();
+ return new Impl();
}
void GrCCPathProcessor::drawPaths(GrOpFlushState* flushState, const GrPipeline& pipeline,
@@ -141,8 +142,7 @@
bounds);
}
-void GLSLPathProcessor::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) {
- using InstanceAttribs = GrCCPathProcessor::InstanceAttribs;
+void GrCCPathProcessor::Impl::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) {
using Interpolation = GrGLSLVaryingHandler::Interpolation;
const GrCCPathProcessor& proc = args.fGP.cast<GrCCPathProcessor>();
@@ -151,45 +151,38 @@
const char* atlasAdjust;
fAtlasAdjustUniform = uniHandler->addUniform(
- kVertex_GrShaderFlag,
- kFloat2_GrSLType, "atlas_adjust", &atlasAdjust);
+ kVertex_GrShaderFlag, kFloat2_GrSLType, "atlas_adjust", &atlasAdjust);
varyingHandler->emitAttributes(proc);
GrGLSLVarying texcoord(kFloat3_GrSLType);
GrGLSLVarying color(kHalf4_GrSLType);
varyingHandler->addVarying("texcoord", &texcoord);
- varyingHandler->addPassThroughAttribute(proc.getInstanceAttrib(InstanceAttribs::kColor),
- args.fOutputColor, Interpolation::kCanBeFlat);
+ varyingHandler->addPassThroughAttribute(
+ kInstanceAttribs[kColorAttribIdx], args.fOutputColor, Interpolation::kCanBeFlat);
// The vertex shader bloats and intersects the devBounds and devBounds45 rectangles, in order to
// find an octagon that circumscribes the (bloated) path.
GrGLSLVertexBuilder* v = args.fVertBuilder;
- // Each vertex is the intersection of one edge from devBounds and one from devBounds45.
- // 'N' holds the normals to these edges as column vectors.
- //
- // NOTE: "float2x2(float4)" is valid and equivalent to "float2x2(float4.xy, float4.zw)",
- // however Intel compilers crash when we use the former syntax in this shader.
- v->codeAppendf("float2x2 N = float2x2(%s.xy, %s.zw);", proc.getEdgeNormsAttrib().name(),
- proc.getEdgeNormsAttrib().name());
+ // Are we clockwise (positive wind, nonzero fill), or counter-clockwise (negative wind,
+ // even/odd fill)?
+ v->codeAppendf("float wind = sign(devbounds.z - devbounds.x);");
- // N[0] is the normal for the edge we are intersecting from the regular bounding box, pointing
- // out of the octagon.
- v->codeAppendf("float4 devbounds = %s;",
- proc.getInstanceAttrib(InstanceAttribs::kDevBounds).name());
- v->codeAppend ("float2 refpt = (0 == sk_VertexID >> 2)"
- "? float2(min(devbounds.x, devbounds.z), devbounds.y)"
- ": float2(max(devbounds.x, devbounds.z), devbounds.w);");
+ // Find our reference corner from the device-space bounding box.
+ v->codeAppendf("float2 refpt = mix(devbounds.xy, devbounds.zw, corners.xy);");
- // N[1] is the normal for the edge we are intersecting from the 45-degree bounding box, pointing
- // out of the octagon.
- v->codeAppendf("float2 refpt45 = (0 == ((sk_VertexID + 1) & (1 << 2))) ? %s.xy : %s.zw;",
- proc.getInstanceAttrib(InstanceAttribs::kDevBounds45).name(),
- proc.getInstanceAttrib(InstanceAttribs::kDevBounds45).name());
- v->codeAppendf("refpt45 *= float2x2(.5,.5,-.5,.5);"); // transform back to device space.
+ // Find our reference corner from the 45-degree bounding box.
+ v->codeAppendf("float2 refpt45 = mix(devbounds45.xy, devbounds45.zw, corners.zw);");
+ // Transform back to device space.
+ v->codeAppendf("refpt45 *= float2x2(+1, +1, -wind, +wind) * .5;");
- v->codeAppend ("float2 K = float2(dot(N[0], refpt), dot(N[1], refpt45));");
+ // Find the normals to each edge, then intersect them to find our octagon vertex.
+ v->codeAppendf("float2x2 N = float2x2("
+ "corners.z + corners.w - 1, corners.w - corners.z, "
+ "corners.xy*2 - 1);");
+ v->codeAppendf("N = float2x2(wind, 0, 0, 1) * N;");
+ v->codeAppendf("float2 K = float2(dot(N[0], refpt), dot(N[1], refpt45));");
v->codeAppendf("float2 octocoord = K * inverse(N);");
// Round the octagon out to ensure we rasterize every pixel the path might touch. (Positive
@@ -198,16 +191,13 @@
// NOTE: If we were just drawing a rect, ceil/floor would be enough. But since there are also
// diagonals in the octagon that cross through pixel centers, we need to outset by another
// quarter px to ensure those pixels get rasterized.
- v->codeAppend ("half2 bloatdir = (0 != N[0].x) "
- "? half2(half(N[0].x), half(N[1].y))"
- ": half2(half(N[1].x), half(N[0].y));");
- v->codeAppend ("octocoord = (ceil(octocoord * bloatdir - 1e-4) + 0.25) * bloatdir;");
-
- gpArgs->fPositionVar.set(kFloat2_GrSLType, "octocoord");
+ v->codeAppendf("float2 bloatdir = (0 != N[0].x) "
+ "? float2(N[0].x, N[1].y)"
+ ": float2(N[1].x, N[0].y);");
+ v->codeAppendf("octocoord = (ceil(octocoord * bloatdir - 1e-4) + 0.25) * bloatdir;");
// Convert to atlas coordinates in order to do our texture lookup.
- v->codeAppendf("float2 atlascoord = octocoord + float2(%s);",
- proc.getInstanceAttrib(InstanceAttribs::kDevToAtlasOffset).name());
+ v->codeAppendf("float2 atlascoord = octocoord + float2(dev_to_atlas_offset);");
if (kTopLeft_GrSurfaceOrigin == proc.atlasOrigin()) {
v->codeAppendf("%s.xy = atlascoord * %s;", texcoord.vsOut(), atlasAdjust);
} else {
@@ -215,12 +205,11 @@
v->codeAppendf("%s.xy = float2(atlascoord.x * %s.x, 1 - atlascoord.y * %s.y);",
texcoord.vsOut(), atlasAdjust, atlasAdjust);
}
- // The third texture coordinate is -.5 for even-odd paths and +.5 for winding ones.
- // ("right < left" indicates even-odd fill type.)
- v->codeAppendf("%s.z = sign(devbounds.z - devbounds.x) * .5;", texcoord.vsOut());
+ v->codeAppendf("%s.z = wind * .5;", texcoord.vsOut());
- this->emitTransforms(v, varyingHandler, uniHandler, GrShaderVar("octocoord", kFloat2_GrSLType),
- proc.localMatrix(), args.fFPCoordTransformHandler);
+ gpArgs->fPositionVar.set(kFloat2_GrSLType, "octocoord");
+ this->emitTransforms(v, varyingHandler, uniHandler, gpArgs->fPositionVar, proc.localMatrix(),
+ args.fFPCoordTransformHandler);
// Fragment shader.
GrGLSLFPFragmentBuilder* f = args.fFragBuilder;
diff --git a/src/gpu/ccpr/GrCCPathProcessor.h b/src/gpu/ccpr/GrCCPathProcessor.h
index 534f08d..3143819 100644
--- a/src/gpu/ccpr/GrCCPathProcessor.h
+++ b/src/gpu/ccpr/GrCCPathProcessor.h
@@ -31,14 +31,6 @@
*/
class GrCCPathProcessor : public GrGeometryProcessor {
public:
- enum class InstanceAttribs {
- kDevBounds,
- kDevBounds45,
- kDevToAtlasOffset,
- kColor
- };
- static constexpr int kNumInstanceAttribs = 1 + (int)InstanceAttribs::kColor;
-
// Helper to offset the 45-degree bounding box returned by GrCCPathParser::parsePath().
static SkRect MakeOffset45(const SkRect& devBounds45, float dx, float dy) {
// devBounds45 is in "| 1 -1 | * devCoords" space.
@@ -76,12 +68,6 @@
const SkISize& atlasSize() const { return fAtlasSize; }
GrSurfaceOrigin atlasOrigin() const { return fAtlasOrigin; }
const SkMatrix& localMatrix() const { return fLocalMatrix; }
- const Attribute& getInstanceAttrib(InstanceAttribs attribID) const {
- int idx = static_cast<int>(attribID);
- SkASSERT(idx >= 0 && idx < static_cast<int>(SK_ARRAY_COUNT(kInstanceAttribs)));
- return kInstanceAttribs[idx];
- }
- const Attribute& getEdgeNormsAttrib() const { return kEdgeNormsAttrib; }
void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override {}
GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override;
@@ -98,28 +84,36 @@
GrSurfaceOrigin fAtlasOrigin;
SkMatrix fLocalMatrix;
- static constexpr Attribute kInstanceAttribs[kNumInstanceAttribs] = {
+ static constexpr Attribute kInstanceAttribs[] = {
{"devbounds", kFloat4_GrVertexAttribType, kFloat4_GrSLType},
{"devbounds45", kFloat4_GrVertexAttribType, kFloat4_GrSLType},
{"dev_to_atlas_offset", kInt2_GrVertexAttribType, kInt2_GrSLType},
{"color", kHalf4_GrVertexAttribType, kHalf4_GrSLType}
};
- static constexpr Attribute kEdgeNormsAttrib = {"edge_norms", kFloat4_GrVertexAttribType,
- kFloat4_GrSLType};
+ static constexpr int kColorAttribIdx = 3;
+ static constexpr Attribute kCornersAttrib =
+ {"corners", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
+
+ class Impl;
typedef GrGeometryProcessor INHERITED;
};
-inline void GrCCPathProcessor::Instance::set(const SkRect& devBounds, const SkRect& devBounds45,
- const SkIVector& devToAtlasOffset, uint64_t color,
- DoEvenOddFill doEvenOddFill) {
- if (DoEvenOddFill::kYes == doEvenOddFill) {
- // "right < left" indicates even-odd fill type.
- fDevBounds.setLTRB(devBounds.fRight, devBounds.fTop, devBounds.fLeft, devBounds.fBottom);
- } else {
+inline void GrCCPathProcessor::Instance::set(
+ const SkRect& devBounds, const SkRect& devBounds45, const SkIVector& devToAtlasOffset,
+ uint64_t color, DoEvenOddFill doEvenOddFill) {
+ if (DoEvenOddFill::kNo == doEvenOddFill) {
+ // We cover "nonzero" paths with clockwise triangles, which is the default result from
+ // normal bounding boxes.
fDevBounds = devBounds;
+ fDevBounds45 = devBounds45;
+ } else {
+ // We cover "even/odd" paths with counterclockwise triangles. Reorder the bounding box
+ // vertices so the output is flipped horizontally.
+ fDevBounds.setLTRB(devBounds.fRight, devBounds.fTop, devBounds.fLeft, devBounds.fBottom);
+ fDevBounds45.setLTRB(
+ devBounds45.fBottom, devBounds45.fRight, devBounds45.fTop, devBounds45.fLeft);
}
- fDevBounds45 = devBounds45;
fDevToAtlasOffset = devToAtlasOffset;
fColor = color;
}