Add a cap for when dual sided stencil refs and masks must match

Change-Id: Ice00a00cd7185131578669e9c1ca865341f0ed0f
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/283274
Reviewed-by: Greg Daniel <egdaniel@google.com>
Commit-Queue: Chris Dalton <csmartdalton@google.com>
diff --git a/src/gpu/GrCaps.cpp b/src/gpu/GrCaps.cpp
index 530e384..43b8996 100644
--- a/src/gpu/GrCaps.cpp
+++ b/src/gpu/GrCaps.cpp
@@ -33,6 +33,7 @@
     fUsePrimitiveRestart = false;
     fPreferClientSideDynamicBuffers = false;
     fPreferFullscreenClears = false;
+    fTwoSidedStencilRefsAndMasksMustMatch = false;
     fMustClearUploadedBufferData = false;
     fShouldInitializeTextures = false;
     fSupportsAHardwareBufferImages = false;
@@ -206,6 +207,8 @@
     writer->appendBool("Use primitive restart", fUsePrimitiveRestart);
     writer->appendBool("Prefer client-side dynamic buffers", fPreferClientSideDynamicBuffers);
     writer->appendBool("Prefer fullscreen clears (and stencil discard)", fPreferFullscreenClears);
+    writer->appendBool("Two-sided Stencil Refs And Masks Must Match",
+                       fTwoSidedStencilRefsAndMasksMustMatch);
     writer->appendBool("Must clear buffer memory", fMustClearUploadedBufferData);
     writer->appendBool("Should initialize textures", fShouldInitializeTextures);
     writer->appendBool("Supports importing AHardwareBuffers", fSupportsAHardwareBufferImages);
diff --git a/src/gpu/GrCaps.h b/src/gpu/GrCaps.h
index 5ddd661..3ef0c61 100644
--- a/src/gpu/GrCaps.h
+++ b/src/gpu/GrCaps.h
@@ -83,6 +83,11 @@
         return this->preferFullscreenClears();
     }
 
+    // D3D does not allow the refs or masks to differ on a two-sided stencil draw.
+    bool twoSidedStencilRefsAndMasksMustMatch() const {
+        return fTwoSidedStencilRefsAndMasksMustMatch;
+    }
+
     bool preferVRAMUseOverFlushes() const { return fPreferVRAMUseOverFlushes; }
 
     bool preferTrianglesOverSampleMask() const { return fPreferTrianglesOverSampleMask; }
@@ -473,6 +478,7 @@
     bool fUsePrimitiveRestart                        : 1;
     bool fPreferClientSideDynamicBuffers             : 1;
     bool fPreferFullscreenClears                     : 1;
+    bool fTwoSidedStencilRefsAndMasksMustMatch       : 1;
     bool fMustClearUploadedBufferData                : 1;
     bool fShouldInitializeTextures                   : 1;
     bool fSupportsAHardwareBufferImages              : 1;
diff --git a/src/gpu/GrOpsRenderPass.cpp b/src/gpu/GrOpsRenderPass.cpp
index 060b9fc..66e346b 100644
--- a/src/gpu/GrOpsRenderPass.cpp
+++ b/src/gpu/GrOpsRenderPass.cpp
@@ -77,6 +77,15 @@
     if (programInfo.pipeline().isWireframe()) {
          SkASSERT(this->gpu()->caps()->wireframeSupport());
     }
+    if (this->gpu()->caps()->twoSidedStencilRefsAndMasksMustMatch() &&
+        programInfo.pipeline().isStencilEnabled()) {
+        const GrUserStencilSettings* stencil = programInfo.pipeline().getUserStencil();
+        if (stencil->isTwoSided(programInfo.pipeline().hasStencilClip())) {
+            SkASSERT(stencil->fCCWFace.fRef == stencil->fCWFace.fRef);
+            SkASSERT(stencil->fCCWFace.fTestMask == stencil->fCWFace.fTestMask);
+            SkASSERT(stencil->fCCWFace.fWriteMask == stencil->fCWFace.fWriteMask);
+        }
+    }
     if (GrPrimitiveType::kPatches == programInfo.primitiveType()) {
         SkASSERT(this->gpu()->caps()->shaderCaps()->tessellationSupport());
     }
diff --git a/src/gpu/ccpr/GrStencilAtlasOp.cpp b/src/gpu/ccpr/GrStencilAtlasOp.cpp
index 1f8f777..3422592 100644
--- a/src/gpu/ccpr/GrStencilAtlasOp.cpp
+++ b/src/gpu/ccpr/GrStencilAtlasOp.cpp
@@ -116,6 +116,28 @@
         0xffff,                           0xffff>()
 );
 
+// Same as above, but done in two passes for D3D, which doesn't support mismatched refs or masks on
+// dual sided stencil settings.
+static constexpr GrUserStencilSettings kResolveWindingCoverageAndReset(
+    GrUserStencilSettings::StaticInitSeparate<
+        0x0000,                           0x0000,
+        GrUserStencilTest::kNotEqual,     GrUserStencilTest::kNever,
+        0xffff,                           0xffff,
+        GrUserStencilOp::kZero,           GrUserStencilOp::kKeep,
+        GrUserStencilOp::kKeep,           GrUserStencilOp::kKeep,
+        0xffff,                           0xffff>()
+);
+static constexpr GrUserStencilSettings kResolveEvenOddCoverageAndReset(
+    GrUserStencilSettings::StaticInitSeparate<
+        0x0000,                           0x0000,
+        GrUserStencilTest::kNever,        GrUserStencilTest::kNotEqual,
+        0x0001,                           0x0001,
+        GrUserStencilOp::kKeep,           GrUserStencilOp::kZero,
+        GrUserStencilOp::kKeep,           GrUserStencilOp::kZero,
+        0xffff,                           0xffff>()
+);
+
+
 void GrStencilAtlasOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) {
     SkIRect drawBoundsRect = SkIRect::MakeWH(fDrawBounds.width(), fDrawBounds.height());
 
@@ -135,27 +157,41 @@
     // not necessary, and will even cause artifacts if using mixed samples.
     constexpr auto noHWAA = GrPipeline::InputFlags::kNone;
 
-    const auto* stencilResolveSettings = (flushState->caps().discardStencilValuesAfterRenderPass())
-            // The next draw will be the final op in the renderTargetContext. So if Ganesh is
-            // planning to discard the stencil values anyway, we don't actually need to reset them
-            // back to zero.
-            ? &kResolveStencilCoverage
-            : &kResolveStencilCoverageAndReset;
-
     GrPipeline resolvePipeline(GrScissorTest::kEnabled, SkBlendMode::kSrc,
-                               flushState->drawOpArgs().writeSwizzle(), noHWAA,
-                               stencilResolveSettings);
-
+                               flushState->drawOpArgs().writeSwizzle(), noHWAA);
     StencilResolveProcessor primProc;
 
+    if (!flushState->caps().twoSidedStencilRefsAndMasksMustMatch()) {
+        if (flushState->caps().discardStencilValuesAfterRenderPass()) {
+            resolvePipeline.setUserStencil(&kResolveStencilCoverage);
+        } else {
+            resolvePipeline.setUserStencil(&kResolveStencilCoverageAndReset);
+        }
+        this->drawResolve(flushState, resolvePipeline, primProc, drawBoundsRect);
+        return;
+    }
+
+    // If this ever becomes true then we should add new per-fill-type stencil settings that also
+    // don't reset back to zero.
+    SkASSERT(!flushState->caps().discardStencilValuesAfterRenderPass());
+
+    resolvePipeline.setUserStencil(&kResolveWindingCoverageAndReset);
+    this->drawResolve(flushState, resolvePipeline, primProc, drawBoundsRect);
+
+    resolvePipeline.setUserStencil(&kResolveEvenOddCoverageAndReset);
+    this->drawResolve(flushState, resolvePipeline, primProc, drawBoundsRect);
+}
+
+void GrStencilAtlasOp::drawResolve(GrOpFlushState* flushState, const GrPipeline& resolvePipeline,
+                                   const GrPrimitiveProcessor& primProc,
+                                   const SkIRect& drawBounds) const {
     GrProgramInfo programInfo(flushState->proxy()->numSamples(),
                               flushState->proxy()->numStencilSamples(),
                               flushState->proxy()->backendFormat(),
                               flushState->writeView()->origin(), &resolvePipeline, &primProc,
                               GrPrimitiveType::kTriangleStrip);
-
-    flushState->bindPipeline(programInfo, SkRect::Make(drawBoundsRect));
-    flushState->setScissorRect(drawBoundsRect);
+    flushState->bindPipeline(programInfo, SkRect::Make(drawBounds));
+    flushState->setScissorRect(drawBounds);
     flushState->bindBuffers(nullptr, fResources->stencilResolveBuffer(), nullptr);
     flushState->drawInstanced(fEndStencilResolveInstance - fBaseStencilResolveInstance,
                               fBaseStencilResolveInstance, 4, 0);
diff --git a/src/gpu/ccpr/GrStencilAtlasOp.h b/src/gpu/ccpr/GrStencilAtlasOp.h
index 6981fed..7cce205 100644
--- a/src/gpu/ccpr/GrStencilAtlasOp.h
+++ b/src/gpu/ccpr/GrStencilAtlasOp.h
@@ -59,6 +59,8 @@
                       const GrXferProcessor::DstProxyView&) override {}
     void onPrepare(GrOpFlushState*) override {}
     void onExecute(GrOpFlushState*, const SkRect& chainBounds) override;
+    void drawResolve(GrOpFlushState*, const GrPipeline&, const GrPrimitiveProcessor&,
+                     const SkIRect& drawBounds) const;
 
     friend class ::GrOpMemoryPool; // for ctor