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;
 }