Add validation rule for depth/stencil between pipeline and pass

depthWrite/stencilWrite in DepthStencilState in RenderPipeline
should be compatible with depthReadOnly/stencilReadOnly in
DepthStencilAttachment in RenderPass. Otherwise, you may need
to generate validation errors.

Bug: dawn:485

Change-Id: I7b541056dafc4dee4eb31f4cefbac48c0ffc4b18
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/66240
Commit-Queue: Yunchao He <yunchao.he@intel.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/dawn_native/CommandEncoder.cpp b/src/dawn_native/CommandEncoder.cpp
index 223cbb6..ab0857f 100644
--- a/src/dawn_native/CommandEncoder.cpp
+++ b/src/dawn_native/CommandEncoder.cpp
@@ -532,6 +532,8 @@
 
         uint32_t width = 0;
         uint32_t height = 0;
+        bool depthReadOnly = false;
+        bool stencilReadOnly = false;
         Ref<AttachmentState> attachmentState;
         bool success = mEncodingContext.TryEncode(
             this,
@@ -600,6 +602,9 @@
                     } else {
                         usageTracker.TextureViewUsedAs(view, wgpu::TextureUsage::RenderAttachment);
                     }
+
+                    depthReadOnly = descriptor->depthStencilAttachment->depthReadOnly;
+                    stencilReadOnly = descriptor->depthStencilAttachment->stencilReadOnly;
                 }
 
                 cmd->width = width;
@@ -612,9 +617,10 @@
             "encoding BeginRenderPass(%s).", descriptor);
 
         if (success) {
-            RenderPassEncoder* passEncoder = new RenderPassEncoder(
-                device, this, &mEncodingContext, std::move(usageTracker),
-                std::move(attachmentState), descriptor->occlusionQuerySet, width, height);
+            RenderPassEncoder* passEncoder =
+                new RenderPassEncoder(device, this, &mEncodingContext, std::move(usageTracker),
+                                      std::move(attachmentState), descriptor->occlusionQuerySet,
+                                      width, height, depthReadOnly, stencilReadOnly);
             mEncodingContext.EnterPass(passEncoder);
             return passEncoder;
         }
diff --git a/src/dawn_native/RenderBundleEncoder.cpp b/src/dawn_native/RenderBundleEncoder.cpp
index 7a0fc5d..568f941 100644
--- a/src/dawn_native/RenderBundleEncoder.cpp
+++ b/src/dawn_native/RenderBundleEncoder.cpp
@@ -79,7 +79,9 @@
                                              const RenderBundleEncoderDescriptor* descriptor)
         : RenderEncoderBase(device,
                             &mBundleEncodingContext,
-                            device->GetOrCreateAttachmentState(descriptor)),
+                            device->GetOrCreateAttachmentState(descriptor),
+                            false,
+                            false),
           mBundleEncodingContext(device, this) {
     }
 
diff --git a/src/dawn_native/RenderEncoderBase.cpp b/src/dawn_native/RenderEncoderBase.cpp
index a2e99a8..c4ff78c 100644
--- a/src/dawn_native/RenderEncoderBase.cpp
+++ b/src/dawn_native/RenderEncoderBase.cpp
@@ -32,12 +32,16 @@
 
     RenderEncoderBase::RenderEncoderBase(DeviceBase* device,
                                          EncodingContext* encodingContext,
-                                         Ref<AttachmentState> attachmentState)
+                                         Ref<AttachmentState> attachmentState,
+                                         bool depthReadOnly,
+                                         bool stencilReadOnly)
         : ProgrammablePassEncoder(device, encodingContext),
           mIndirectDrawMetadata(device->GetLimits()),
           mAttachmentState(std::move(attachmentState)),
           mDisableBaseVertex(device->IsToggleEnabled(Toggle::DisableBaseVertex)),
           mDisableBaseInstance(device->IsToggleEnabled(Toggle::DisableBaseInstance)) {
+        mDepthReadOnly = depthReadOnly;
+        mStencilReadOnly = stencilReadOnly;
     }
 
     RenderEncoderBase::RenderEncoderBase(DeviceBase* device,
@@ -222,6 +226,14 @@
                         pipeline->GetAttachmentState() != mAttachmentState.Get(),
                         "Attachment state of %s is not compatible with the attachment state of %s",
                         pipeline, this);
+
+                    DAWN_INVALID_IF(pipeline->WritesDepth() && mDepthReadOnly,
+                                    "%s writes depth while %s's depthReadOnly is true", pipeline,
+                                    this);
+
+                    DAWN_INVALID_IF(pipeline->WritesStencil() && mStencilReadOnly,
+                                    "%s writes stencil while %s's stencilReadOnly is true",
+                                    pipeline, this);
                 }
 
                 mCommandBufferState.SetRenderPipeline(pipeline);
diff --git a/src/dawn_native/RenderEncoderBase.h b/src/dawn_native/RenderEncoderBase.h
index 30b7a3c..bc17ba7 100644
--- a/src/dawn_native/RenderEncoderBase.h
+++ b/src/dawn_native/RenderEncoderBase.h
@@ -28,7 +28,9 @@
       public:
         RenderEncoderBase(DeviceBase* device,
                           EncodingContext* encodingContext,
-                          Ref<AttachmentState> attachmentState);
+                          Ref<AttachmentState> attachmentState,
+                          bool depthReadOnly,
+                          bool stencilReadOnly);
 
         void APIDraw(uint32_t vertexCount,
                      uint32_t instanceCount = 1,
@@ -71,6 +73,8 @@
         Ref<AttachmentState> mAttachmentState;
         const bool mDisableBaseVertex;
         const bool mDisableBaseInstance;
+        bool mDepthReadOnly = false;
+        bool mStencilReadOnly = false;
     };
 
 }  // namespace dawn_native
diff --git a/src/dawn_native/RenderPassEncoder.cpp b/src/dawn_native/RenderPassEncoder.cpp
index aa86702..fccf652 100644
--- a/src/dawn_native/RenderPassEncoder.cpp
+++ b/src/dawn_native/RenderPassEncoder.cpp
@@ -55,8 +55,14 @@
                                          Ref<AttachmentState> attachmentState,
                                          QuerySetBase* occlusionQuerySet,
                                          uint32_t renderTargetWidth,
-                                         uint32_t renderTargetHeight)
-        : RenderEncoderBase(device, encodingContext, std::move(attachmentState)),
+                                         uint32_t renderTargetHeight,
+                                         bool depthReadOnly,
+                                         bool stencilReadOnly)
+        : RenderEncoderBase(device,
+                            encodingContext,
+                            std::move(attachmentState),
+                            depthReadOnly,
+                            stencilReadOnly),
           mCommandEncoder(commandEncoder),
           mRenderTargetWidth(renderTargetWidth),
           mRenderTargetHeight(renderTargetHeight),
diff --git a/src/dawn_native/RenderPassEncoder.h b/src/dawn_native/RenderPassEncoder.h
index 5aaf32e..ae9ccbb 100644
--- a/src/dawn_native/RenderPassEncoder.h
+++ b/src/dawn_native/RenderPassEncoder.h
@@ -32,7 +32,9 @@
                           Ref<AttachmentState> attachmentState,
                           QuerySetBase* occlusionQuerySet,
                           uint32_t renderTargetWidth,
-                          uint32_t renderTargetHeight);
+                          uint32_t renderTargetHeight,
+                          bool depthReadOnly,
+                          bool stencilReadOnly);
 
         static RenderPassEncoder* MakeError(DeviceBase* device,
                                             CommandEncoder* commandEncoder,
diff --git a/src/dawn_native/RenderPipeline.cpp b/src/dawn_native/RenderPipeline.cpp
index 2ad7a22..82fdde8 100644
--- a/src/dawn_native/RenderPipeline.cpp
+++ b/src/dawn_native/RenderPipeline.cpp
@@ -619,6 +619,19 @@
 
         if (mAttachmentState->HasDepthStencilAttachment()) {
             mDepthStencil = *descriptor->depthStencil;
+            mWritesDepth = mDepthStencil.depthWriteEnabled;
+            if (mDepthStencil.stencilWriteMask) {
+                if ((mPrimitive.cullMode != wgpu::CullMode::Front &&
+                     (mDepthStencil.stencilFront.failOp != wgpu::StencilOperation::Keep ||
+                      mDepthStencil.stencilFront.depthFailOp != wgpu::StencilOperation::Keep ||
+                      mDepthStencil.stencilFront.passOp != wgpu::StencilOperation::Keep)) ||
+                    (mPrimitive.cullMode != wgpu::CullMode::Back &&
+                     (mDepthStencil.stencilBack.failOp != wgpu::StencilOperation::Keep ||
+                      mDepthStencil.stencilBack.depthFailOp != wgpu::StencilOperation::Keep ||
+                      mDepthStencil.stencilBack.passOp != wgpu::StencilOperation::Keep))) {
+                    mWritesStencil = true;
+                }
+            }
         } else {
             // These default values below are useful for backends to fill information.
             // The values indicate that depth and stencil test are disabled when backends
@@ -833,6 +846,18 @@
         return mAttachmentState.Get();
     }
 
+    bool RenderPipelineBase::WritesDepth() const {
+        ASSERT(!IsError());
+
+        return mWritesDepth;
+    }
+
+    bool RenderPipelineBase::WritesStencil() const {
+        ASSERT(!IsError());
+
+        return mWritesStencil;
+    }
+
     size_t RenderPipelineBase::ComputeContentHash() {
         ObjectContentHasher recorder;
 
diff --git a/src/dawn_native/RenderPipeline.h b/src/dawn_native/RenderPipeline.h
index b38ec97..4020bcc 100644
--- a/src/dawn_native/RenderPipeline.h
+++ b/src/dawn_native/RenderPipeline.h
@@ -95,6 +95,8 @@
         uint32_t GetSampleCount() const;
         uint32_t GetSampleMask() const;
         bool IsAlphaToCoverageEnabled() const;
+        bool WritesDepth() const;
+        bool WritesStencil() const;
 
         const AttachmentState* GetAttachmentState() const;
 
@@ -128,6 +130,8 @@
         DepthStencilState mDepthStencil;
         MultisampleState mMultisample;
         bool mClampDepth = false;
+        bool mWritesDepth = false;
+        bool mWritesStencil = false;
     };
 
 }  // namespace dawn_native
diff --git a/src/tests/BUILD.gn b/src/tests/BUILD.gn
index 6105fb1..3f236fe 100644
--- a/src/tests/BUILD.gn
+++ b/src/tests/BUILD.gn
@@ -230,6 +230,7 @@
     "unittests/validation/MinimumBufferSizeValidationTests.cpp",
     "unittests/validation/MultipleDeviceTests.cpp",
     "unittests/validation/OverridableConstantsValidationTests.cpp",
+    "unittests/validation/PipelineAndPassCompatibilityTests.cpp",
     "unittests/validation/QueryValidationTests.cpp",
     "unittests/validation/QueueOnSubmittedWorkDoneValidationTests.cpp",
     "unittests/validation/QueueSubmitValidationTests.cpp",
diff --git a/src/tests/unittests/validation/PipelineAndPassCompatibilityTests.cpp b/src/tests/unittests/validation/PipelineAndPassCompatibilityTests.cpp
new file mode 100644
index 0000000..a71afa2
--- /dev/null
+++ b/src/tests/unittests/validation/PipelineAndPassCompatibilityTests.cpp
@@ -0,0 +1,114 @@
+// Copyright 2021 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "utils/ComboRenderPipelineDescriptor.h"
+#include "utils/WGPUHelpers.h"
+
+#include "tests/unittests/validation/ValidationTest.h"
+
+constexpr static uint32_t kSize = 4;
+
+namespace {
+
+    class RenderPipelineAndPassCompatibilityTests : public ValidationTest {
+      public:
+        wgpu::RenderPipeline CreatePipeline(wgpu::TextureFormat format,
+                                            bool enableDepthWrite,
+                                            bool enableStencilWrite) {
+            // Create a NoOp pipeline
+            utils::ComboRenderPipelineDescriptor pipelineDescriptor;
+            pipelineDescriptor.vertex.module = utils::CreateShaderModule(device, R"(
+                [[stage(vertex)]] fn main() -> [[builtin(position)]] vec4<f32> {
+                    return vec4<f32>();
+                })");
+            pipelineDescriptor.cFragment.module = utils::CreateShaderModule(device, R"(
+                [[stage(fragment)]] fn main() {
+                })");
+            pipelineDescriptor.cFragment.targets = nullptr;
+            pipelineDescriptor.cFragment.targetCount = 0;
+
+            // Enable depth/stencil write if needed
+            wgpu::DepthStencilState* depthStencil = pipelineDescriptor.EnableDepthStencil(format);
+            if (enableDepthWrite) {
+                depthStencil->depthWriteEnabled = true;
+            }
+            if (enableStencilWrite) {
+                depthStencil->stencilFront.failOp = wgpu::StencilOperation::Replace;
+            }
+            return device.CreateRenderPipeline(&pipelineDescriptor);
+        }
+
+        utils::ComboRenderPassDescriptor CreateRenderPassDescriptor(wgpu::TextureFormat format,
+                                                                    bool depthReadOnly,
+                                                                    bool stencilReadOnly) {
+            wgpu::TextureDescriptor textureDescriptor = {};
+            textureDescriptor.size = {kSize, kSize, 1};
+            textureDescriptor.format = format;
+            textureDescriptor.usage = wgpu::TextureUsage::RenderAttachment;
+            wgpu::Texture depthStencilTexture = device.CreateTexture(&textureDescriptor);
+
+            utils::ComboRenderPassDescriptor passDescriptor({}, depthStencilTexture.CreateView());
+            if (depthReadOnly) {
+                passDescriptor.cDepthStencilAttachmentInfo.depthReadOnly = true;
+                passDescriptor.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Load;
+                passDescriptor.cDepthStencilAttachmentInfo.depthStoreOp = wgpu::StoreOp::Store;
+            }
+
+            if (stencilReadOnly) {
+                passDescriptor.cDepthStencilAttachmentInfo.stencilReadOnly = true;
+                passDescriptor.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Load;
+                passDescriptor.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Store;
+            }
+
+            return passDescriptor;
+        }
+    };
+
+    // Test depthWrite/stencilWrite in DepthStencilState in pipeline vs
+    // depthReadOnly/stencilReadOnly in DepthStencilAttachment in pass
+    TEST_F(RenderPipelineAndPassCompatibilityTests, WriteAndReadOnlyConflictForDepthStencil) {
+        wgpu::TextureFormat kFormat = wgpu::TextureFormat::Depth24PlusStencil8;
+        // If the format has both depth and stencil aspects, depthReadOnly and stencilReadOnly
+        // should be the same. So it is not necessary to set two separate booleans like
+        // depthReadOnlyInPass and stencilReadOnlyInPass.
+        for (bool depthStencilReadOnlyInPass : {true, false}) {
+            for (bool depthWriteInPipeline : {true, false}) {
+                for (bool stencilWriteInPipeline : {true, false}) {
+                    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+                    utils::ComboRenderPassDescriptor passDescriptor = CreateRenderPassDescriptor(
+                        kFormat, depthStencilReadOnlyInPass, depthStencilReadOnlyInPass);
+                    wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor);
+                    wgpu::RenderPipeline pipeline =
+                        CreatePipeline(kFormat, depthWriteInPipeline, stencilWriteInPipeline);
+                    pass.SetPipeline(pipeline);
+                    pass.Draw(3);
+                    pass.EndPass();
+                    if (depthStencilReadOnlyInPass &&
+                        (depthWriteInPipeline || stencilWriteInPipeline)) {
+                        ASSERT_DEVICE_ERROR(encoder.Finish());
+                    } else {
+                        encoder.Finish();
+                    }
+                }
+            }
+        }
+    }
+
+    // TODO(dawn:485): add more tests. For example:
+    //   - readOnly vs write for depth/stencil with renderbundle.
+    //   - depth/stencil attachment should be designated if depth/stencil test is enabled.
+    //   - pipeline and pass compatibility tests for color attachment(s).
+    //   - pipeline and pass compatibility tests for compute.
+
+}  // anonymous namespace