Add StoreOp::Clear

When storeOp is clear, texture subresource is set as not initialized

Bug: dawn:145
Change-Id: I364d7239a7ebdb9d5a28a4af559f3212be7ef15a
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/11560
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
diff --git a/dawn.json b/dawn.json
index a71e9d6..ceeba0c 100644
--- a/dawn.json
+++ b/dawn.json
@@ -708,7 +708,8 @@
     "store op": {
         "category": "enum",
         "values": [
-            {"value": 0, "name": "store"}
+            {"value": 0, "name": "store"},
+            {"value": 1, "name": "clear"}
         ]
     },
     "origin 3D": {
diff --git a/src/dawn_native/CommandEncoder.cpp b/src/dawn_native/CommandEncoder.cpp
index 0c5208e..e0a9cd2 100644
--- a/src/dawn_native/CommandEncoder.cpp
+++ b/src/dawn_native/CommandEncoder.cpp
@@ -404,6 +404,12 @@
                     "depth stencil format");
             }
 
+            // This validates that the depth storeOp and stencil storeOps are the same
+            if (depthStencilAttachment->depthStoreOp != depthStencilAttachment->stencilStoreOp) {
+                return DAWN_VALIDATION_ERROR(
+                    "The depth storeOp and stencil storeOp are not the same");
+            }
+
             // *sampleCount == 0 must only happen when there is no color attachment. In that case we
             // do not need to validate the sample count of the depth stencil attachment.
             const uint32_t depthStencilSampleCount = attachment->GetTexture()->GetSampleCount();
diff --git a/src/dawn_native/Texture.cpp b/src/dawn_native/Texture.cpp
index ea626ed..bcaa923 100644
--- a/src/dawn_native/Texture.cpp
+++ b/src/dawn_native/Texture.cpp
@@ -412,7 +412,8 @@
         return true;
     }
 
-    void TextureBase::SetIsSubresourceContentInitialized(uint32_t baseMipLevel,
+    void TextureBase::SetIsSubresourceContentInitialized(bool isInitialized,
+                                                         uint32_t baseMipLevel,
                                                          uint32_t levelCount,
                                                          uint32_t baseArrayLayer,
                                                          uint32_t layerCount) {
@@ -422,7 +423,7 @@
                  ++arrayLayer) {
                 uint32_t subresourceIndex = GetSubresourceIndex(mipLevel, arrayLayer);
                 ASSERT(subresourceIndex < mIsSubresourceContentInitializedAtIndex.size());
-                mIsSubresourceContentInitializedAtIndex[subresourceIndex] = true;
+                mIsSubresourceContentInitializedAtIndex[subresourceIndex] = isInitialized;
             }
         }
     }
diff --git a/src/dawn_native/Texture.h b/src/dawn_native/Texture.h
index c515066..fafd46e 100644
--- a/src/dawn_native/Texture.h
+++ b/src/dawn_native/Texture.h
@@ -62,7 +62,8 @@
                                              uint32_t levelCount,
                                              uint32_t baseArrayLayer,
                                              uint32_t layerCount) const;
-        void SetIsSubresourceContentInitialized(uint32_t baseMipLevel,
+        void SetIsSubresourceContentInitialized(bool isInitialized,
+                                                uint32_t baseMipLevel,
                                                 uint32_t levelCount,
                                                 uint32_t baseArrayLayer,
                                                 uint32_t layerCount);
diff --git a/src/dawn_native/d3d12/CommandBufferD3D12.cpp b/src/dawn_native/d3d12/CommandBufferD3D12.cpp
index f4aa363..09a178b 100644
--- a/src/dawn_native/d3d12/CommandBufferD3D12.cpp
+++ b/src/dawn_native/d3d12/CommandBufferD3D12.cpp
@@ -654,7 +654,7 @@
                     if (IsCompleteSubresourceCopiedTo(texture, copy->copySize,
                                                       copy->destination.mipLevel)) {
                         texture->SetIsSubresourceContentInitialized(
-                            copy->destination.mipLevel, 1, copy->destination.arrayLayer, 1);
+                            true, copy->destination.mipLevel, 1, copy->destination.arrayLayer, 1);
                     } else {
                         texture->EnsureSubresourceContentInitialized(
                             commandList, copy->destination.mipLevel, 1,
@@ -737,7 +737,7 @@
                     if (IsCompleteSubresourceCopiedTo(destination, copy->copySize,
                                                       copy->destination.mipLevel)) {
                         destination->SetIsSubresourceContentInitialized(
-                            copy->destination.mipLevel, 1, copy->destination.arrayLayer, 1);
+                            true, copy->destination.mipLevel, 1, copy->destination.arrayLayer, 1);
                     } else {
                         destination->EnsureSubresourceContentInitialized(
                             commandList, copy->destination.mipLevel, 1,
@@ -907,14 +907,19 @@
                     // color attachment, which will be correctly initialized.
                     ToBackend(resolveView->GetTexture())
                         ->SetIsSubresourceContentInitialized(
-                            resolveView->GetBaseMipLevel(), resolveView->GetLevelCount(),
+                            true, resolveView->GetBaseMipLevel(), resolveView->GetLevelCount(),
                             resolveView->GetBaseArrayLayer(), resolveView->GetLayerCount());
                 }
 
                 switch (attachmentInfo.storeOp) {
                     case dawn::StoreOp::Store: {
                         view->GetTexture()->SetIsSubresourceContentInitialized(
-                            view->GetBaseMipLevel(), 1, view->GetBaseArrayLayer(), 1);
+                            true, view->GetBaseMipLevel(), 1, view->GetBaseArrayLayer(), 1);
+                    } break;
+
+                    case dawn::StoreOp::Clear: {
+                        view->GetTexture()->SetIsSubresourceContentInitialized(
+                            false, view->GetBaseMipLevel(), 1, view->GetBaseArrayLayer(), 1);
                     } break;
 
                     default: { UNREACHABLE(); } break;
@@ -966,12 +971,16 @@
                                                        0, nullptr);
                 }
 
-                // TODO(natlee@microsoft.com): Need to fix when storeop discard is added
                 if (attachmentInfo.depthStoreOp == dawn::StoreOp::Store &&
                     attachmentInfo.stencilStoreOp == dawn::StoreOp::Store) {
                     texture->SetIsSubresourceContentInitialized(
-                        view->GetBaseMipLevel(), view->GetLevelCount(), view->GetBaseArrayLayer(),
-                        view->GetLayerCount());
+                        true, view->GetBaseMipLevel(), view->GetLevelCount(),
+                        view->GetBaseArrayLayer(), view->GetLayerCount());
+                } else if (attachmentInfo.depthStoreOp == dawn::StoreOp::Clear &&
+                           attachmentInfo.stencilStoreOp == dawn::StoreOp::Clear) {
+                    texture->SetIsSubresourceContentInitialized(
+                        false, view->GetBaseMipLevel(), view->GetLevelCount(),
+                        view->GetBaseArrayLayer(), view->GetLayerCount());
                 }
             }
         }
diff --git a/src/dawn_native/d3d12/TextureD3D12.cpp b/src/dawn_native/d3d12/TextureD3D12.cpp
index c18bac9..35ab67c 100644
--- a/src/dawn_native/d3d12/TextureD3D12.cpp
+++ b/src/dawn_native/d3d12/TextureD3D12.cpp
@@ -307,7 +307,7 @@
                      const TextureDescriptor* descriptor,
                      ID3D12Resource* nativeTexture)
         : TextureBase(device, descriptor, TextureState::OwnedExternal), mResource(nativeTexture) {
-        SetIsSubresourceContentInitialized(0, descriptor->mipLevelCount, 0,
+        SetIsSubresourceContentInitialized(true, 0, descriptor->mipLevelCount, 0,
                                            descriptor->arrayLayerCount);
     }
 
@@ -483,7 +483,7 @@
                                      TextureBase::ClearValue clearValue) {
         // TODO(jiawei.shao@intel.com): initialize the textures in compressed formats with copies.
         if (GetFormat().isCompressed) {
-            SetIsSubresourceContentInitialized(baseMipLevel, levelCount, baseArrayLayer,
+            SetIsSubresourceContentInitialized(true, baseMipLevel, levelCount, baseArrayLayer,
                                                layerCount);
             return {};
         }
@@ -579,7 +579,7 @@
             }
         }
         if (clearValue == TextureBase::ClearValue::Zero) {
-            SetIsSubresourceContentInitialized(baseMipLevel, levelCount, baseArrayLayer,
+            SetIsSubresourceContentInitialized(true, baseMipLevel, levelCount, baseArrayLayer,
                                                layerCount);
             GetDevice()->IncrementLazyClearCountForTesting();
         }
diff --git a/src/dawn_native/opengl/CommandBufferGL.cpp b/src/dawn_native/opengl/CommandBufferGL.cpp
index b451039..3a014d1 100644
--- a/src/dawn_native/opengl/CommandBufferGL.cpp
+++ b/src/dawn_native/opengl/CommandBufferGL.cpp
@@ -467,8 +467,8 @@
                     GLenum target = texture->GetGLTarget();
                     const GLFormat& format = texture->GetGLFormat();
                     if (IsCompleteSubresourceCopiedTo(texture, copySize, dst.mipLevel)) {
-                        texture->SetIsSubresourceContentInitialized(dst.mipLevel, 1, dst.arrayLayer,
-                                                                    1);
+                        texture->SetIsSubresourceContentInitialized(true, dst.mipLevel, 1,
+                                                                    dst.arrayLayer, 1);
                     } else {
                         texture->EnsureSubresourceContentInitialized(dst.mipLevel, 1,
                                                                      dst.arrayLayer, 1);
@@ -609,7 +609,7 @@
                     srcTexture->EnsureSubresourceContentInitialized(src.mipLevel, 1, src.arrayLayer,
                                                                     1);
                     if (IsCompleteSubresourceCopiedTo(dstTexture, copySize, dst.mipLevel)) {
-                        dstTexture->SetIsSubresourceContentInitialized(dst.mipLevel, 1,
+                        dstTexture->SetIsSubresourceContentInitialized(true, dst.mipLevel, 1,
                                                                        dst.arrayLayer, 1);
                     } else {
                         dstTexture->EnsureSubresourceContentInitialized(dst.mipLevel, 1,
@@ -783,28 +783,49 @@
         {
             for (uint32_t i :
                  IterateBitSet(renderPass->attachmentState->GetColorAttachmentsMask())) {
-                const auto& attachmentInfo = renderPass->colorAttachments[i];
+                auto* attachmentInfo = &renderPass->colorAttachments[i];
+                TextureView* view = ToBackend(attachmentInfo->view.Get());
 
                 // Load op - color
                 // TODO(cwallez@chromium.org): Choose the clear function depending on the
                 // componentType: things work for now because the clear color is always a float, but
                 // when that's fixed will lose precision on integer formats when converting to
                 // float.
-                if (attachmentInfo.loadOp == dawn::LoadOp::Clear) {
+                if (attachmentInfo->loadOp == dawn::LoadOp::Clear) {
                     gl.ColorMaski(i, true, true, true, true);
-                    gl.ClearBufferfv(GL_COLOR, i, &attachmentInfo.clearColor.r);
+                    gl.ClearBufferfv(GL_COLOR, i, &attachmentInfo->clearColor.r);
+                }
+
+                switch (attachmentInfo->storeOp) {
+                    case dawn::StoreOp::Store: {
+                        view->GetTexture()->SetIsSubresourceContentInitialized(
+                            true, view->GetBaseMipLevel(), view->GetLevelCount(),
+                            view->GetBaseArrayLayer(), view->GetLayerCount());
+                    } break;
+
+                    case dawn::StoreOp::Clear: {
+                        // TODO(natlee@microsoft.com): call glDiscard to do optimization
+                        view->GetTexture()->SetIsSubresourceContentInitialized(
+                            false, view->GetBaseMipLevel(), view->GetLevelCount(),
+                            view->GetBaseArrayLayer(), view->GetLayerCount());
+                    } break;
+
+                    default:
+                        UNREACHABLE();
+                        break;
                 }
             }
 
             if (renderPass->attachmentState->HasDepthStencilAttachment()) {
-                const auto& attachmentInfo = renderPass->depthStencilAttachment;
-                const Format& attachmentFormat = attachmentInfo.view->GetTexture()->GetFormat();
+                auto* attachmentInfo = &renderPass->depthStencilAttachment;
+                const Format& attachmentFormat = attachmentInfo->view->GetTexture()->GetFormat();
+                TextureView* view = ToBackend(attachmentInfo->view.Get());
 
                 // Load op - depth/stencil
                 bool doDepthClear = attachmentFormat.HasDepth() &&
-                                    (attachmentInfo.depthLoadOp == dawn::LoadOp::Clear);
+                                    (attachmentInfo->depthLoadOp == dawn::LoadOp::Clear);
                 bool doStencilClear = attachmentFormat.HasStencil() &&
-                                      (attachmentInfo.stencilLoadOp == dawn::LoadOp::Clear);
+                                      (attachmentInfo->stencilLoadOp == dawn::LoadOp::Clear);
 
                 if (doDepthClear) {
                     gl.DepthMask(GL_TRUE);
@@ -814,14 +835,26 @@
                 }
 
                 if (doDepthClear && doStencilClear) {
-                    gl.ClearBufferfi(GL_DEPTH_STENCIL, 0, attachmentInfo.clearDepth,
-                                     attachmentInfo.clearStencil);
+                    gl.ClearBufferfi(GL_DEPTH_STENCIL, 0, attachmentInfo->clearDepth,
+                                     attachmentInfo->clearStencil);
                 } else if (doDepthClear) {
-                    gl.ClearBufferfv(GL_DEPTH, 0, &attachmentInfo.clearDepth);
+                    gl.ClearBufferfv(GL_DEPTH, 0, &attachmentInfo->clearDepth);
                 } else if (doStencilClear) {
-                    const GLint clearStencil = attachmentInfo.clearStencil;
+                    const GLint clearStencil = attachmentInfo->clearStencil;
                     gl.ClearBufferiv(GL_STENCIL, 0, &clearStencil);
                 }
+
+                if (attachmentInfo->depthStoreOp == dawn::StoreOp::Store &&
+                    attachmentInfo->stencilStoreOp == dawn::StoreOp::Store) {
+                    view->GetTexture()->SetIsSubresourceContentInitialized(
+                        true, view->GetBaseMipLevel(), view->GetLevelCount(),
+                        view->GetBaseArrayLayer(), view->GetLayerCount());
+                } else if (attachmentInfo->depthStoreOp == dawn::StoreOp::Clear &&
+                           attachmentInfo->stencilStoreOp == dawn::StoreOp::Clear) {
+                    view->GetTexture()->SetIsSubresourceContentInitialized(
+                        false, view->GetBaseMipLevel(), view->GetLevelCount(),
+                        view->GetBaseArrayLayer(), view->GetLayerCount());
+                }
             }
         }
 
diff --git a/src/dawn_native/opengl/TextureGL.cpp b/src/dawn_native/opengl/TextureGL.cpp
index 5dce582..4b4fdde 100644
--- a/src/dawn_native/opengl/TextureGL.cpp
+++ b/src/dawn_native/opengl/TextureGL.cpp
@@ -313,7 +313,7 @@
             if (isLazyClear) {
                 GetDevice()->IncrementLazyClearCountForTesting();
             }
-            SetIsSubresourceContentInitialized(baseMipLevel, levelCount, baseArrayLayer,
+            SetIsSubresourceContentInitialized(true, baseMipLevel, levelCount, baseArrayLayer,
                                                layerCount);
         }
     }
diff --git a/src/dawn_native/vulkan/CommandBufferVk.cpp b/src/dawn_native/vulkan/CommandBufferVk.cpp
index 7729286..c01d5fd 100644
--- a/src/dawn_native/vulkan/CommandBufferVk.cpp
+++ b/src/dawn_native/vulkan/CommandBufferVk.cpp
@@ -137,14 +137,19 @@
                         TextureView* resolveView = ToBackend(attachmentInfo.resolveTarget.Get());
                         ToBackend(resolveView->GetTexture())
                             ->SetIsSubresourceContentInitialized(
-                                resolveView->GetBaseMipLevel(), resolveView->GetLevelCount(),
+                                true, resolveView->GetBaseMipLevel(), resolveView->GetLevelCount(),
                                 resolveView->GetBaseArrayLayer(), resolveView->GetLayerCount());
                     }
 
                     switch (attachmentInfo.storeOp) {
                         case dawn::StoreOp::Store: {
                             view->GetTexture()->SetIsSubresourceContentInitialized(
-                                view->GetBaseMipLevel(), 1, view->GetBaseArrayLayer(), 1);
+                                true, view->GetBaseMipLevel(), 1, view->GetBaseArrayLayer(), 1);
+                        } break;
+
+                        case dawn::StoreOp::Clear: {
+                            view->GetTexture()->SetIsSubresourceContentInitialized(
+                                false, view->GetBaseMipLevel(), 1, view->GetBaseArrayLayer(), 1);
                         } break;
 
                         default: { UNREACHABLE(); } break;
@@ -177,11 +182,15 @@
                     query.SetDepthStencil(view->GetTexture()->GetFormat().format,
                                           attachmentInfo.depthLoadOp, attachmentInfo.stencilLoadOp);
 
-                    // TODO(natlee@microsoft.com): Need to fix when storeop discard is added
                     if (attachmentInfo.depthStoreOp == dawn::StoreOp::Store &&
                         attachmentInfo.stencilStoreOp == dawn::StoreOp::Store) {
                         view->GetTexture()->SetIsSubresourceContentInitialized(
-                            view->GetBaseMipLevel(), view->GetLevelCount(),
+                            true, view->GetBaseMipLevel(), view->GetLevelCount(),
+                            view->GetBaseArrayLayer(), view->GetLayerCount());
+                    } else if (attachmentInfo.depthStoreOp == dawn::StoreOp::Clear &&
+                               attachmentInfo.stencilStoreOp == dawn::StoreOp::Clear) {
+                        view->GetTexture()->SetIsSubresourceContentInitialized(
+                            false, view->GetBaseMipLevel(), view->GetLevelCount(),
                             view->GetBaseArrayLayer(), view->GetLayerCount());
                     }
                 }
@@ -400,7 +409,7 @@
                                                       subresource.mipLevel)) {
                         // Since texture has been overwritten, it has been "initialized"
                         dst.texture->SetIsSubresourceContentInitialized(
-                            subresource.mipLevel, 1, subresource.baseArrayLayer, 1);
+                            true, subresource.mipLevel, 1, subresource.baseArrayLayer, 1);
                     } else {
                         ToBackend(dst.texture)
                             ->EnsureSubresourceContentInitialized(recordingContext,
@@ -459,7 +468,7 @@
                     if (IsCompleteSubresourceCopiedTo(dst.texture.Get(), copy->copySize,
                                                       dst.mipLevel)) {
                         // Since destination texture has been overwritten, it has been "initialized"
-                        dst.texture->SetIsSubresourceContentInitialized(dst.mipLevel, 1,
+                        dst.texture->SetIsSubresourceContentInitialized(true, dst.mipLevel, 1,
                                                                         dst.arrayLayer, 1);
                     } else {
                         ToBackend(dst.texture)
diff --git a/src/dawn_native/vulkan/TextureVk.cpp b/src/dawn_native/vulkan/TextureVk.cpp
index 1f29ea0..fc412a9 100644
--- a/src/dawn_native/vulkan/TextureVk.cpp
+++ b/src/dawn_native/vulkan/TextureVk.cpp
@@ -512,7 +512,7 @@
 
         // Don't clear imported texture if already cleared
         if (descriptor->isCleared) {
-            SetIsSubresourceContentInitialized(0, 1, 0, 1);
+            SetIsSubresourceContentInitialized(true, 0, 1, 0, 1);
         }
     }
 
@@ -724,7 +724,7 @@
             }
         }
         if (clearValue == TextureBase::ClearValue::Zero) {
-            SetIsSubresourceContentInitialized(baseMipLevel, levelCount, baseArrayLayer,
+            SetIsSubresourceContentInitialized(true, baseMipLevel, levelCount, baseArrayLayer,
                                                layerCount);
             device->IncrementLazyClearCountForTesting();
         }
diff --git a/src/tests/end2end/TextureZeroInitTests.cpp b/src/tests/end2end/TextureZeroInitTests.cpp
index 64e518b..81dfc47 100644
--- a/src/tests/end2end/TextureZeroInitTests.cpp
+++ b/src/tests/end2end/TextureZeroInitTests.cpp
@@ -52,22 +52,7 @@
     }
     dawn::RenderPipeline CreatePipelineForTest() {
         utils::ComboRenderPipelineDescriptor pipelineDescriptor(device);
-        const char* vs =
-            R"(#version 450
-            const vec2 pos[6] = vec2[6](vec2(-1.0f, -1.0f),
-                                    vec2(-1.0f,  1.0f),
-                                    vec2( 1.0f, -1.0f),
-                                    vec2( 1.0f,  1.0f),
-                                    vec2(-1.0f,  1.0f),
-                                    vec2( 1.0f, -1.0f)
-                                    );
-
-            void main() {
-                gl_Position = vec4(pos[gl_VertexIndex], 0.0, 1.0);
-            })";
-        pipelineDescriptor.vertexStage.module =
-            utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, vs);
-
+        pipelineDescriptor.vertexStage.module = CreateBasicVertexShaderForTest();
         const char* fs =
             R"(#version 450
             layout(location = 0) out vec4 fragColor;
@@ -83,6 +68,30 @@
 
         return device.CreateRenderPipeline(&pipelineDescriptor);
     }
+    dawn::ShaderModule CreateBasicVertexShaderForTest() {
+        return utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"(#version 450
+            const vec2 pos[6] = vec2[6](vec2(-1.0f, -1.0f),
+                                    vec2(-1.0f,  1.0f),
+                                    vec2( 1.0f, -1.0f),
+                                    vec2( 1.0f,  1.0f),
+                                    vec2(-1.0f,  1.0f),
+                                    vec2( 1.0f, -1.0f)
+                                    );
+
+            void main() {
+                gl_Position = vec4(pos[gl_VertexIndex], 0.0, 1.0);
+            })");
+    }
+    dawn::ShaderModule CreateSampledTextureFragmentShaderForTest() {
+        return utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment,
+                                         R"(#version 450
+            layout(set = 0, binding = 0) uniform sampler sampler0;
+            layout(set = 0, binding = 1) uniform texture2D texture0;
+            layout(location = 0) out vec4 fragColor;
+            void main() {
+                fragColor = texelFetch(sampler2D(texture0, sampler0), ivec2(gl_FragCoord), 0);
+            })");
+    }
     constexpr static uint32_t kSize = 128;
     constexpr static uint32_t kUnalignedSize = 127;
     // All three texture formats used (RGBA8Unorm, Depth24PlusStencil8, and RGBA8Snorm) have the
@@ -161,8 +170,6 @@
 }
 
 // This tests CopyBufferToTexture fully overwrites copy so lazy init is not needed.
-// TODO(natlee@microsoft.com): Add backdoor to dawn native to query the number of zero-inited
-// subresources
 TEST_P(TextureZeroInitTest, CopyBufferToTexture) {
     dawn::TextureDescriptor descriptor = CreateTextureDescriptor(
         4, 1,
@@ -452,29 +459,9 @@
     // Create render pipeline
     utils::ComboRenderPipelineDescriptor renderPipelineDescriptor(device);
     renderPipelineDescriptor.layout = utils::MakeBasicPipelineLayout(device, &bindGroupLayout);
-    renderPipelineDescriptor.vertexStage.module =
-        utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"(#version 450
-        const vec2 pos[6] = vec2[6](vec2(-1.0f, -1.0f),
-                                    vec2(-1.0f,  1.0f),
-                                    vec2( 1.0f, -1.0f),
-                                    vec2( 1.0f,  1.0f),
-                                    vec2(-1.0f,  1.0f),
-                                    vec2( 1.0f, -1.0f)
-                                    );
-
-        void main() {
-           gl_Position = vec4(pos[gl_VertexIndex], 0.0, 1.0);
-        })");
-    renderPipelineDescriptor.cFragmentStage.module =
-        utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment,
-                                  R"(#version 450
-        layout(set = 0, binding = 0) uniform sampler sampler0;
-        layout(set = 0, binding = 1) uniform texture2D texture0;
-        layout(location = 0) out vec4 fragColor;
-        void main() {
-           fragColor = texelFetch(sampler2D(texture0, sampler0), ivec2(gl_FragCoord), 0);
-        })");
     renderPipelineDescriptor.cColorStates[0].format = kColorFormat;
+    renderPipelineDescriptor.vertexStage.module = CreateBasicVertexShaderForTest();
+    renderPipelineDescriptor.cFragmentStage.module = CreateSampledTextureFragmentShaderForTest();
     dawn::RenderPipeline renderPipeline = device.CreateRenderPipeline(&renderPipelineDescriptor);
 
     // Create bindgroup
@@ -651,6 +638,144 @@
     EXPECT_BUFFER_U32_RANGE_EQ(expectedWithZeros.data(), bufferDst, 0, 8);
 }
 
+// This tests that storeOp clear resets resource state to uninitialized.
+// Start with a sample texture that is initialized with data.
+// Then expect the render texture to not store the data from sample texture
+// because it will be lazy cleared by the EXPECT_TEXTURE_RGBA8_EQ call.
+TEST_P(TextureZeroInitTest, RenderPassStoreOpClear) {
+    // Create needed resources
+    dawn::TextureDescriptor descriptor = CreateTextureDescriptor(
+        1, 1, dawn::TextureUsage::Sampled | dawn::TextureUsage::CopyDst, kColorFormat);
+    dawn::Texture texture = device.CreateTexture(&descriptor);
+
+    dawn::TextureDescriptor renderTextureDescriptor = CreateTextureDescriptor(
+        1, 1, dawn::TextureUsage::CopySrc | dawn::TextureUsage::OutputAttachment, kColorFormat);
+    dawn::Texture renderTexture = device.CreateTexture(&renderTextureDescriptor);
+
+    dawn::SamplerDescriptor samplerDesc = utils::GetDefaultSamplerDescriptor();
+    dawn::Sampler sampler = device.CreateSampler(&samplerDesc);
+
+    dawn::BindGroupLayout bindGroupLayout = utils::MakeBindGroupLayout(
+        device, {{0, dawn::ShaderStage::Fragment, dawn::BindingType::Sampler},
+                 {1, dawn::ShaderStage::Fragment, dawn::BindingType::SampledTexture}});
+
+    // Fill the sample texture with data
+    std::vector<uint8_t> data(kFormatBlockByteSize * kSize * kSize, 1);
+    dawn::Buffer stagingBuffer = utils::CreateBufferFromData(
+        device, data.data(), static_cast<uint32_t>(data.size()), dawn::BufferUsage::CopySrc);
+    dawn::BufferCopyView bufferCopyView = utils::CreateBufferCopyView(stagingBuffer, 0, 0, 0);
+    dawn::TextureCopyView textureCopyView = utils::CreateTextureCopyView(texture, 0, 0, {0, 0, 0});
+    dawn::Extent3D copySize = {kSize, kSize, 1};
+    dawn::CommandEncoder encoder = device.CreateCommandEncoder();
+    encoder.CopyBufferToTexture(&bufferCopyView, &textureCopyView, &copySize);
+    dawn::CommandBuffer commands = encoder.Finish();
+    // Expect 0 lazy clears because the texture will be completely copied to
+    EXPECT_LAZY_CLEAR(0u, queue.Submit(1, &commands));
+
+    // Create render pipeline
+    utils::ComboRenderPipelineDescriptor renderPipelineDescriptor(device);
+    renderPipelineDescriptor.layout = utils::MakeBasicPipelineLayout(device, &bindGroupLayout);
+    renderPipelineDescriptor.vertexStage.module = CreateBasicVertexShaderForTest();
+    renderPipelineDescriptor.cFragmentStage.module = CreateSampledTextureFragmentShaderForTest();
+    renderPipelineDescriptor.cColorStates[0].format = kColorFormat;
+    dawn::RenderPipeline renderPipeline = device.CreateRenderPipeline(&renderPipelineDescriptor);
+
+    // Create bindgroup
+    dawn::BindGroup bindGroup =
+        utils::MakeBindGroup(device, bindGroupLayout, {{0, sampler}, {1, texture.CreateView()}});
+
+    // Encode pass and submit
+    encoder = device.CreateCommandEncoder();
+    utils::ComboRenderPassDescriptor renderPassDesc({renderTexture.CreateView()});
+    renderPassDesc.cColorAttachments[0].clearColor = {0.0, 0.0, 0.0, 0.0};
+    renderPassDesc.cColorAttachments[0].loadOp = dawn::LoadOp::Clear;
+    renderPassDesc.cColorAttachments[0].storeOp = dawn::StoreOp::Clear;
+    dawn::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDesc);
+    pass.SetPipeline(renderPipeline);
+    pass.SetBindGroup(0, bindGroup, 0, nullptr);
+    pass.Draw(6, 1, 0, 0);
+    pass.EndPass();
+    commands = encoder.Finish();
+    // Expect 0 lazy clears, sample texture is initialized by copyBufferToTexture and render texture
+    // is cleared by loadop
+    EXPECT_LAZY_CLEAR(0u, queue.Submit(1, &commands));
+
+    // Expect the rendered texture to be cleared
+    std::vector<RGBA8> expectedWithZeros(kSize * kSize, {0, 0, 0, 0});
+    EXPECT_LAZY_CLEAR(1u, EXPECT_TEXTURE_RGBA8_EQ(expectedWithZeros.data(), renderTexture, 0, 0,
+                                                  kSize, kSize, 0, 0));
+}
+
+// This tests storeOp Clear on depth and stencil textures.
+// We put the depth stencil texture through 2 passes:
+// 1) LoadOp::Clear and StoreOp::Clear, fail the depth and stencil test set in the render pipeline.
+//      This means nothing is drawn and subresource is set as uninitialized.
+// 2) LoadOp::Load and StoreOp::Clear, pass the depth and stencil test set in the render pipeline.
+//      Because LoadOp is Load and the subresource is uninitialized, the texture will be cleared to
+//      0's This means the depth and stencil test will pass and the red square is drawn.
+TEST_P(TextureZeroInitTest, RenderingLoadingDepthStencilStoreOpClear) {
+    dawn::TextureDescriptor srcDescriptor =
+        CreateTextureDescriptor(1, 1,
+                                dawn::TextureUsage::CopySrc | dawn::TextureUsage::CopyDst |
+                                    dawn::TextureUsage::OutputAttachment,
+                                kColorFormat);
+    dawn::Texture srcTexture = device.CreateTexture(&srcDescriptor);
+
+    dawn::TextureDescriptor depthStencilDescriptor =
+        CreateTextureDescriptor(1, 1,
+                                dawn::TextureUsage::OutputAttachment | dawn::TextureUsage::CopySrc |
+                                    dawn::TextureUsage::CopyDst,
+                                kDepthStencilFormat);
+    dawn::Texture depthStencilTexture = device.CreateTexture(&depthStencilDescriptor);
+
+    // Setup the renderPass for the first pass.
+    // We want to fail the depth and stencil test here so that nothing gets drawn and we can
+    // see that the subresource correctly gets set as unintialized in the second pass
+    utils::ComboRenderPassDescriptor renderPassDescriptor({srcTexture.CreateView()},
+                                                          depthStencilTexture.CreateView());
+    renderPassDescriptor.cDepthStencilAttachmentInfo.depthLoadOp = dawn::LoadOp::Clear;
+    renderPassDescriptor.cDepthStencilAttachmentInfo.stencilLoadOp = dawn::LoadOp::Clear;
+    renderPassDescriptor.cDepthStencilAttachmentInfo.clearDepth = 1.0f;
+    renderPassDescriptor.cDepthStencilAttachmentInfo.clearStencil = 1u;
+    renderPassDescriptor.cDepthStencilAttachmentInfo.depthStoreOp = dawn::StoreOp::Clear;
+    renderPassDescriptor.cDepthStencilAttachmentInfo.stencilStoreOp = dawn::StoreOp::Clear;
+    {
+        dawn::CommandEncoder encoder = device.CreateCommandEncoder();
+        dawn::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDescriptor);
+        pass.SetPipeline(CreatePipelineForTest());
+        pass.Draw(6, 1, 0, 0);
+        pass.EndPass();
+        dawn::CommandBuffer commandBuffer = encoder.Finish();
+        // Expect 0 lazy clears, depth stencil texture will clear using loadop
+        EXPECT_LAZY_CLEAR(0u, queue.Submit(1, &commandBuffer));
+
+        // The depth stencil test should fail and not draw because the depth stencil texture is
+        // cleared to 1's by using loadOp clear and set values from descriptor.
+        std::vector<RGBA8> expectedBlack(kSize * kSize, {0, 0, 0, 0});
+        EXPECT_TEXTURE_RGBA8_EQ(expectedBlack.data(), srcTexture, 0, 0, kSize, kSize, 0, 0);
+    }
+
+    // Now we put the depth stencil texture back into renderpass, it should be cleared by loadop
+    // because storeOp clear sets the subresource as uninitialized
+    {
+        renderPassDescriptor.cDepthStencilAttachmentInfo.depthLoadOp = dawn::LoadOp::Load;
+        renderPassDescriptor.cDepthStencilAttachmentInfo.stencilLoadOp = dawn::LoadOp::Load;
+        dawn::CommandEncoder encoder = device.CreateCommandEncoder();
+        dawn::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDescriptor);
+        pass.SetPipeline(CreatePipelineForTest());
+        pass.Draw(6, 1, 0, 0);
+        pass.EndPass();
+        dawn::CommandBuffer commandBuffer = encoder.Finish();
+        // Expect 0 lazy clears, depth stencil texture will clear using loadop
+        EXPECT_LAZY_CLEAR(0u, queue.Submit(1, &commandBuffer));
+
+        // Now the depth stencil test should pass since depth stencil texture is cleared to 0's by
+        // loadop load and uninitialized subresource, so we should have a red square
+        std::vector<RGBA8> expectedRed(kSize * kSize, {255, 0, 0, 255});
+        EXPECT_TEXTURE_RGBA8_EQ(expectedRed.data(), srcTexture, 0, 0, kSize, kSize, 0, 0);
+    }
+}
+
 DAWN_INSTANTIATE_TEST(
     TextureZeroInitTest,
     ForceWorkarounds(D3D12Backend, {"nonzero_clear_resources_on_creation_for_testing"}),
diff --git a/src/tests/unittests/validation/RenderPassDescriptorValidationTests.cpp b/src/tests/unittests/validation/RenderPassDescriptorValidationTests.cpp
index d3bae71..bf7e67d 100644
--- a/src/tests/unittests/validation/RenderPassDescriptorValidationTests.cpp
+++ b/src/tests/unittests/validation/RenderPassDescriptorValidationTests.cpp
@@ -189,6 +189,54 @@
     }
 }
 
+// Depth and stencil storeOps must match
+TEST_F(RenderPassDescriptorValidationTest, DepthStencilStoreOpMismatch) {
+    constexpr uint32_t kArrayLayers = 1;
+    constexpr uint32_t kLevelCount = 1;
+    constexpr uint32_t kSize = 32;
+    constexpr dawn::TextureFormat kColorFormat = dawn::TextureFormat::RGBA8Unorm;
+    constexpr dawn::TextureFormat kDepthStencilFormat = dawn::TextureFormat::Depth24PlusStencil8;
+
+    dawn::Texture colorTexture = CreateTexture(device, dawn::TextureDimension::e2D, kColorFormat,
+                                               kSize, kSize, kArrayLayers, kLevelCount);
+    dawn::Texture depthStencilTexture =
+        CreateTexture(device, dawn::TextureDimension::e2D, kDepthStencilFormat, kSize, kSize,
+                      kArrayLayers, kLevelCount);
+
+    dawn::TextureViewDescriptor descriptor;
+    descriptor.dimension = dawn::TextureViewDimension::e2D;
+    descriptor.baseArrayLayer = 0;
+    descriptor.arrayLayerCount = kArrayLayers;
+    descriptor.baseMipLevel = 0;
+    descriptor.mipLevelCount = kLevelCount;
+    dawn::TextureView colorTextureView = colorTexture.CreateView(&descriptor);
+    dawn::TextureView depthStencilView = depthStencilTexture.CreateView(&descriptor);
+
+    // StoreOps mismatch causing the render pass to error
+    {
+        utils::ComboRenderPassDescriptor renderPass({}, depthStencilView);
+        renderPass.cDepthStencilAttachmentInfo.stencilStoreOp = dawn::StoreOp::Store;
+        renderPass.cDepthStencilAttachmentInfo.depthStoreOp = dawn::StoreOp::Clear;
+        AssertBeginRenderPassError(&renderPass);
+    }
+
+    // StoreOps match so render pass is a success
+    {
+        utils::ComboRenderPassDescriptor renderPass({}, depthStencilView);
+        renderPass.cDepthStencilAttachmentInfo.stencilStoreOp = dawn::StoreOp::Store;
+        renderPass.cDepthStencilAttachmentInfo.depthStoreOp = dawn::StoreOp::Store;
+        AssertBeginRenderPassSuccess(&renderPass);
+    }
+
+    // StoreOps match so render pass is a success
+    {
+        utils::ComboRenderPassDescriptor renderPass({}, depthStencilView);
+        renderPass.cDepthStencilAttachmentInfo.stencilStoreOp = dawn::StoreOp::Clear;
+        renderPass.cDepthStencilAttachmentInfo.depthStoreOp = dawn::StoreOp::Clear;
+        AssertBeginRenderPassSuccess(&renderPass);
+    }
+}
+
 // Currently only texture views with arrayLayerCount == 1 are allowed to be color and depth stencil
 // attachments
 TEST_F(RenderPassDescriptorValidationTest, TextureViewLayerCountForColorAndDepthStencil) {