Deprecate read only depth/stencil Load/StoreOp

In https://github.com/gpuweb/gpuweb/pull/2387 the spec was updated to
require that when depthReadOnly or stencilReadOnly is set the associated
load and store ops must be omitted. This change deprecates setting them
to `Load` and `Store` respectively, while adding an `Undefined` value
which the enums default to.

Bug: dawn:1281
Change-Id: I36474ba67bfb080da8c713d5bb88b8522e4630f3
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/78980
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Brandon Jones <bajones@chromium.org>
diff --git a/dawn.json b/dawn.json
index f0fea53..961e091 100644
--- a/dawn.json
+++ b/dawn.json
@@ -1449,8 +1449,9 @@
         "category": "enum",
         "emscripten_no_enum_table": true,
         "values": [
-            {"value": 0, "name": "clear"},
-            {"value": 1, "name": "load"}
+            {"value": 0, "name": "undefined", "valid": false, "jsrepr": "undefined"},
+            {"value": 1, "name": "clear"},
+            {"value": 2, "name": "load"}
         ]
     },
     "map mode": {
@@ -1464,8 +1465,9 @@
     "store op": {
         "category": "enum",
         "values": [
-            {"value": 0, "name": "store"},
-            {"value": 1, "name": "discard"}
+            {"value": 0, "name": "undefined", "valid": false, "jsrepr": "undefined"},
+            {"value": 1, "name": "store"},
+            {"value": 2, "name": "discard"}
         ]
     },
     "origin 3D": {
@@ -1802,12 +1804,12 @@
         "category": "structure",
         "members": [
             {"name": "view", "type": "texture view"},
-            {"name": "depth load op", "type": "load op"},
-            {"name": "depth store op", "type": "store op"},
-            {"name": "clear depth", "type": "float"},
+            {"name": "depth load op", "type": "load op", "default": "undefined"},
+            {"name": "depth store op", "type": "store op", "default": "undefined"},
+            {"name": "clear depth", "type": "float", "default": "0"},
             {"name": "depth read only", "type": "bool", "default": "false"},
-            {"name": "stencil load op", "type": "load op"},
-            {"name": "stencil store op", "type": "store op"},
+            {"name": "stencil load op", "type": "load op", "default": "undefined"},
+            {"name": "stencil store op", "type": "store op", "default": "undefined"},
             {"name": "clear stencil", "type": "uint32_t", "default": "0"},
             {"name": "stencil read only", "type": "bool", "default": "false"}
         ]
diff --git a/src/dawn_native/CommandBuffer.cpp b/src/dawn_native/CommandBuffer.cpp
index 4370459..43fd478 100644
--- a/src/dawn_native/CommandBuffer.cpp
+++ b/src/dawn_native/CommandBuffer.cpp
@@ -139,6 +139,10 @@
                 case wgpu::StoreOp::Discard:
                     view->GetTexture()->SetIsSubresourceContentInitialized(false, range);
                     break;
+
+                case wgpu::StoreOp::Undefined:
+                    UNREACHABLE();
+                    break;
             }
         }
 
diff --git a/src/dawn_native/CommandEncoder.cpp b/src/dawn_native/CommandEncoder.cpp
index 67a3d4d..119484e 100644
--- a/src/dawn_native/CommandEncoder.cpp
+++ b/src/dawn_native/CommandEncoder.cpp
@@ -280,11 +280,6 @@
                             "The depth stencil attachment %s must encompass all aspects.",
                             attachment);
 
-            DAWN_TRY(ValidateLoadOp(depthStencilAttachment->depthLoadOp));
-            DAWN_TRY(ValidateLoadOp(depthStencilAttachment->stencilLoadOp));
-            DAWN_TRY(ValidateStoreOp(depthStencilAttachment->depthStoreOp));
-            DAWN_TRY(ValidateStoreOp(depthStencilAttachment->stencilStoreOp));
-
             DAWN_INVALID_IF(
                 attachment->GetAspects() == (Aspect::Depth | Aspect::Stencil) &&
                     depthStencilAttachment->depthReadOnly !=
@@ -293,22 +288,48 @@
                 "is 'all'.",
                 depthStencilAttachment->depthReadOnly, depthStencilAttachment->stencilReadOnly);
 
-            DAWN_INVALID_IF(
-                depthStencilAttachment->depthReadOnly &&
-                    (depthStencilAttachment->depthLoadOp != wgpu::LoadOp::Load ||
-                     depthStencilAttachment->depthStoreOp != wgpu::StoreOp::Store),
-                "depthLoadOp (%s) is not %s or depthStoreOp (%s) is not %s when depthReadOnly "
-                "is true.",
-                depthStencilAttachment->depthLoadOp, wgpu::LoadOp::Load,
-                depthStencilAttachment->depthStoreOp, wgpu::StoreOp::Store);
+            if (depthStencilAttachment->depthReadOnly) {
+                if (depthStencilAttachment->depthLoadOp == wgpu::LoadOp::Load &&
+                    depthStencilAttachment->depthStoreOp == wgpu::StoreOp::Store) {
+                    // TODO(dawn:1269): Remove this branch after the deprecation period.
+                    device->EmitDeprecationWarning(
+                        "Setting depthLoadOp and depthStore when "
+                        "depthReadOnly is true is deprecated.");
+                } else {
+                    DAWN_INVALID_IF(depthStencilAttachment->depthLoadOp != wgpu::LoadOp::Undefined,
+                                    "depthLoadOp (%s) must not be set when depthReadOnly is true.",
+                                    depthStencilAttachment->depthLoadOp);
+                    DAWN_INVALID_IF(
+                        depthStencilAttachment->depthStoreOp != wgpu::StoreOp::Undefined,
+                        "depthStoreOp (%s) must not be set when depthReadOnly is true.",
+                        depthStencilAttachment->depthStoreOp);
+                }
+            } else {
+                DAWN_TRY(ValidateLoadOp(depthStencilAttachment->depthLoadOp));
+                DAWN_TRY(ValidateStoreOp(depthStencilAttachment->depthStoreOp));
+            }
 
-            DAWN_INVALID_IF(depthStencilAttachment->stencilReadOnly &&
-                                (depthStencilAttachment->stencilLoadOp != wgpu::LoadOp::Load ||
-                                 depthStencilAttachment->stencilStoreOp != wgpu::StoreOp::Store),
-                            "stencilLoadOp (%s) is not %s or stencilStoreOp (%s) is not %s when "
-                            "stencilReadOnly is true.",
-                            depthStencilAttachment->stencilLoadOp, wgpu::LoadOp::Load,
-                            depthStencilAttachment->stencilStoreOp, wgpu::StoreOp::Store);
+            if (depthStencilAttachment->stencilReadOnly) {
+                if (depthStencilAttachment->stencilLoadOp == wgpu::LoadOp::Load &&
+                    depthStencilAttachment->stencilStoreOp == wgpu::StoreOp::Store) {
+                    // TODO(dawn:1269): Remove this branch after the deprecation period.
+                    device->EmitDeprecationWarning(
+                        "Setting stencilLoadOp and stencilStoreOp when "
+                        "stencilReadOnly is true is deprecated.");
+                } else {
+                    DAWN_INVALID_IF(
+                        depthStencilAttachment->stencilLoadOp != wgpu::LoadOp::Undefined,
+                        "stencilLoadOp (%s) must not be set when stencilReadOnly is true.",
+                        depthStencilAttachment->stencilLoadOp);
+                    DAWN_INVALID_IF(
+                        depthStencilAttachment->stencilStoreOp != wgpu::StoreOp::Undefined,
+                        "stencilStoreOp (%s) must not be set when stencilReadOnly is true.",
+                        depthStencilAttachment->stencilStoreOp);
+                }
+            } else {
+                DAWN_TRY(ValidateLoadOp(depthStencilAttachment->stencilLoadOp));
+                DAWN_TRY(ValidateStoreOp(depthStencilAttachment->stencilStoreOp));
+            }
 
             DAWN_INVALID_IF(depthStencilAttachment->depthLoadOp == wgpu::LoadOp::Clear &&
                                 std::isnan(depthStencilAttachment->clearDepth),
@@ -639,19 +660,31 @@
                         descriptor->depthStencilAttachment->clearDepth;
                     cmd->depthStencilAttachment.clearStencil =
                         descriptor->depthStencilAttachment->clearStencil;
-                    cmd->depthStencilAttachment.depthLoadOp =
-                        descriptor->depthStencilAttachment->depthLoadOp;
-                    cmd->depthStencilAttachment.depthStoreOp =
-                        descriptor->depthStencilAttachment->depthStoreOp;
-                    cmd->depthStencilAttachment.stencilLoadOp =
-                        descriptor->depthStencilAttachment->stencilLoadOp;
-                    cmd->depthStencilAttachment.stencilStoreOp =
-                        descriptor->depthStencilAttachment->stencilStoreOp;
                     cmd->depthStencilAttachment.depthReadOnly =
                         descriptor->depthStencilAttachment->depthReadOnly;
                     cmd->depthStencilAttachment.stencilReadOnly =
                         descriptor->depthStencilAttachment->stencilReadOnly;
 
+                    if (descriptor->depthStencilAttachment->depthReadOnly) {
+                        cmd->depthStencilAttachment.depthLoadOp = wgpu::LoadOp::Load;
+                        cmd->depthStencilAttachment.depthStoreOp = wgpu::StoreOp::Store;
+                    } else {
+                        cmd->depthStencilAttachment.depthLoadOp =
+                            descriptor->depthStencilAttachment->depthLoadOp;
+                        cmd->depthStencilAttachment.depthStoreOp =
+                            descriptor->depthStencilAttachment->depthStoreOp;
+                    }
+
+                    if (descriptor->depthStencilAttachment->stencilReadOnly) {
+                        cmd->depthStencilAttachment.stencilLoadOp = wgpu::LoadOp::Load;
+                        cmd->depthStencilAttachment.stencilStoreOp = wgpu::StoreOp::Store;
+                    } else {
+                        cmd->depthStencilAttachment.stencilLoadOp =
+                            descriptor->depthStencilAttachment->stencilLoadOp;
+                        cmd->depthStencilAttachment.stencilStoreOp =
+                            descriptor->depthStencilAttachment->stencilStoreOp;
+                    }
+
                     if (IsReadOnlyDepthStencilAttachment(descriptor->depthStencilAttachment)) {
                         usageTracker.TextureViewUsedAs(view, kReadOnlyRenderAttachment);
                     } else {
diff --git a/src/dawn_native/d3d12/RenderPassBuilderD3D12.cpp b/src/dawn_native/d3d12/RenderPassBuilderD3D12.cpp
index f86f525..8f04315 100644
--- a/src/dawn_native/d3d12/RenderPassBuilderD3D12.cpp
+++ b/src/dawn_native/d3d12/RenderPassBuilderD3D12.cpp
@@ -30,6 +30,9 @@
                     return D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR;
                 case wgpu::LoadOp::Load:
                     return D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_PRESERVE;
+                case wgpu::LoadOp::Undefined:
+                    UNREACHABLE();
+                    break;
             }
         }
 
@@ -39,6 +42,9 @@
                     return D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_DISCARD;
                 case wgpu::StoreOp::Store:
                     return D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE;
+                case wgpu::StoreOp::Undefined:
+                    UNREACHABLE();
+                    break;
             }
         }
 
diff --git a/src/dawn_native/metal/CommandBufferMTL.mm b/src/dawn_native/metal/CommandBufferMTL.mm
index 59716b7..feef33a 100644
--- a/src/dawn_native/metal/CommandBufferMTL.mm
+++ b/src/dawn_native/metal/CommandBufferMTL.mm
@@ -81,6 +81,10 @@
                     case wgpu::LoadOp::Load:
                         descriptor.colorAttachments[i].loadAction = MTLLoadActionLoad;
                         break;
+
+                    case wgpu::LoadOp::Undefined:
+                        UNREACHABLE();
+                        break;
                 }
 
                 descriptor.colorAttachments[i].texture =
@@ -106,6 +110,9 @@
                             descriptor.colorAttachments[i].storeAction =
                                 MTLStoreActionMultisampleResolve;
                             break;
+                        case wgpu::StoreOp::Undefined:
+                            UNREACHABLE();
+                            break;
                     }
                 } else {
                     switch (attachmentInfo.storeOp) {
@@ -115,6 +122,9 @@
                         case wgpu::StoreOp::Discard:
                             descriptor.colorAttachments[i].storeAction = MTLStoreActionDontCare;
                             break;
+                        case wgpu::StoreOp::Undefined:
+                            UNREACHABLE();
+                            break;
                     }
                 }
             }
@@ -139,6 +149,10 @@
                         case wgpu::StoreOp::Discard:
                             descriptor.depthAttachment.storeAction = MTLStoreActionDontCare;
                             break;
+
+                        case wgpu::StoreOp::Undefined:
+                            UNREACHABLE();
+                            break;
                     }
 
                     switch (attachmentInfo.depthLoadOp) {
@@ -150,6 +164,10 @@
                         case wgpu::LoadOp::Load:
                             descriptor.depthAttachment.loadAction = MTLLoadActionLoad;
                             break;
+
+                        case wgpu::LoadOp::Undefined:
+                            UNREACHABLE();
+                            break;
                     }
                 }
 
@@ -166,6 +184,10 @@
                         case wgpu::StoreOp::Discard:
                             descriptor.stencilAttachment.storeAction = MTLStoreActionDontCare;
                             break;
+
+                        case wgpu::StoreOp::Undefined:
+                            UNREACHABLE();
+                            break;
                     }
 
                     switch (attachmentInfo.stencilLoadOp) {
@@ -177,6 +199,10 @@
                         case wgpu::LoadOp::Load:
                             descriptor.stencilAttachment.loadAction = MTLLoadActionLoad;
                             break;
+
+                        case wgpu::LoadOp::Undefined:
+                            UNREACHABLE();
+                            break;
                     }
                 }
             }
diff --git a/src/dawn_native/vulkan/RenderPassCache.cpp b/src/dawn_native/vulkan/RenderPassCache.cpp
index 20fefe8..095380f 100644
--- a/src/dawn_native/vulkan/RenderPassCache.cpp
+++ b/src/dawn_native/vulkan/RenderPassCache.cpp
@@ -29,6 +29,9 @@
                     return VK_ATTACHMENT_LOAD_OP_LOAD;
                 case wgpu::LoadOp::Clear:
                     return VK_ATTACHMENT_LOAD_OP_CLEAR;
+                case wgpu::LoadOp::Undefined:
+                    UNREACHABLE();
+                    break;
             }
             UNREACHABLE();
         }
@@ -41,6 +44,9 @@
                     return VK_ATTACHMENT_STORE_OP_STORE;
                 case wgpu::StoreOp::Discard:
                     return VK_ATTACHMENT_STORE_OP_DONT_CARE;
+                case wgpu::StoreOp::Undefined:
+                    UNREACHABLE();
+                    break;
             }
             UNREACHABLE();
         }
diff --git a/src/tests/end2end/DeprecatedAPITests.cpp b/src/tests/end2end/DeprecatedAPITests.cpp
index 59bab60..8a188473 100644
--- a/src/tests/end2end/DeprecatedAPITests.cpp
+++ b/src/tests/end2end/DeprecatedAPITests.cpp
@@ -34,6 +34,45 @@
     }
 };
 
+// Test that setting attachment rather than view for render pass color and depth/stencil attachments
+// is deprecated.
+TEST_P(DeprecationTests, ReadOnlyDepthStencilStoreLoadOpsAttachment) {
+    utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, 1, 1);
+    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+    wgpu::RenderPassEncoder pass;
+
+    // Check that setting load/store ops with read only depth/stencil attachments gives a warning.
+    wgpu::TextureDescriptor descriptor;
+    descriptor.dimension = wgpu::TextureDimension::e2D;
+    descriptor.size = {1, 1, 1};
+    descriptor.sampleCount = 1;
+    descriptor.format = wgpu::TextureFormat::Depth24PlusStencil8;
+    descriptor.mipLevelCount = 1;
+    descriptor.usage = wgpu::TextureUsage::RenderAttachment;
+    wgpu::Texture depthStencil = device.CreateTexture(&descriptor);
+
+    wgpu::RenderPassDepthStencilAttachment* depthAttachment =
+        &renderPass.renderPassInfo.cDepthStencilAttachmentInfo;
+    renderPass.renderPassInfo.depthStencilAttachment = depthAttachment;
+    depthAttachment->view = depthStencil.CreateView();
+    depthAttachment->depthReadOnly = true;
+    depthAttachment->stencilReadOnly = true;
+
+    depthAttachment->depthLoadOp = wgpu::LoadOp::Load;
+    depthAttachment->depthStoreOp = wgpu::StoreOp::Store;
+
+    EXPECT_DEPRECATION_WARNING(pass = encoder.BeginRenderPass(&renderPass.renderPassInfo));
+
+    depthAttachment->depthLoadOp = wgpu::LoadOp::Undefined;
+    depthAttachment->depthStoreOp = wgpu::StoreOp::Undefined;
+    depthAttachment->stencilLoadOp = wgpu::LoadOp::Load;
+    depthAttachment->stencilStoreOp = wgpu::StoreOp::Store;
+
+    EXPECT_DEPRECATION_WARNING(pass = encoder.BeginRenderPass(&renderPass.renderPassInfo));
+
+    pass.EndPass();
+}
+
 DAWN_INSTANTIATE_TEST(DeprecationTests,
                       D3D12Backend(),
                       MetalBackend(),
diff --git a/src/tests/end2end/ReadOnlyDepthStencilAttachmentTests.cpp b/src/tests/end2end/ReadOnlyDepthStencilAttachmentTests.cpp
index e4a9eb8..ee3e0b6 100644
--- a/src/tests/end2end/ReadOnlyDepthStencilAttachmentTests.cpp
+++ b/src/tests/end2end/ReadOnlyDepthStencilAttachmentTests.cpp
@@ -169,11 +169,11 @@
         // Set both aspects to readonly. We have to do this if the format has both aspects, or
         // it doesn't impact anything if the format has only one aspect.
         passDescriptor.cDepthStencilAttachmentInfo.depthReadOnly = true;
-        passDescriptor.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Load;
-        passDescriptor.cDepthStencilAttachmentInfo.depthStoreOp = wgpu::StoreOp::Store;
+        passDescriptor.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Undefined;
+        passDescriptor.cDepthStencilAttachmentInfo.depthStoreOp = wgpu::StoreOp::Undefined;
         passDescriptor.cDepthStencilAttachmentInfo.stencilReadOnly = true;
-        passDescriptor.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Load;
-        passDescriptor.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Store;
+        passDescriptor.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Undefined;
+        passDescriptor.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Undefined;
 
         // Create a render pass with readonly depth/stencil attachment. The attachment has already
         // been initialized. The pipeline in this render pass will sample from the attachment.
diff --git a/src/tests/unittests/validation/PipelineAndPassCompatibilityTests.cpp b/src/tests/unittests/validation/PipelineAndPassCompatibilityTests.cpp
index 0ab3c0b..b0909c5 100644
--- a/src/tests/unittests/validation/PipelineAndPassCompatibilityTests.cpp
+++ b/src/tests/unittests/validation/PipelineAndPassCompatibilityTests.cpp
@@ -65,14 +65,15 @@
             utils::ComboRenderPassDescriptor passDescriptor({}, depthStencilTexture.CreateView());
             if (depthReadOnly) {
                 passDescriptor.cDepthStencilAttachmentInfo.depthReadOnly = true;
-                passDescriptor.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Load;
-                passDescriptor.cDepthStencilAttachmentInfo.depthStoreOp = wgpu::StoreOp::Store;
+                passDescriptor.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Undefined;
+                passDescriptor.cDepthStencilAttachmentInfo.depthStoreOp = wgpu::StoreOp::Undefined;
             }
 
             if (stencilReadOnly) {
                 passDescriptor.cDepthStencilAttachmentInfo.stencilReadOnly = true;
-                passDescriptor.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Load;
-                passDescriptor.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Store;
+                passDescriptor.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Undefined;
+                passDescriptor.cDepthStencilAttachmentInfo.stencilStoreOp =
+                    wgpu::StoreOp::Undefined;
             }
 
             return passDescriptor;
diff --git a/src/tests/unittests/validation/RenderPassDescriptorValidationTests.cpp b/src/tests/unittests/validation/RenderPassDescriptorValidationTests.cpp
index cdde3a9..703153e 100644
--- a/src/tests/unittests/validation/RenderPassDescriptorValidationTests.cpp
+++ b/src/tests/unittests/validation/RenderPassDescriptorValidationTests.cpp
@@ -809,11 +809,11 @@
         // Tests that a read-only pass with depthReadOnly set to true succeeds.
         {
             utils::ComboRenderPassDescriptor renderPass({colorView}, depthStencilView);
-            renderPass.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Load;
-            renderPass.cDepthStencilAttachmentInfo.depthStoreOp = wgpu::StoreOp::Store;
+            renderPass.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Undefined;
+            renderPass.cDepthStencilAttachmentInfo.depthStoreOp = wgpu::StoreOp::Undefined;
             renderPass.cDepthStencilAttachmentInfo.depthReadOnly = true;
-            renderPass.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Load;
-            renderPass.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Store;
+            renderPass.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Undefined;
+            renderPass.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Undefined;
             renderPass.cDepthStencilAttachmentInfo.stencilReadOnly = true;
             AssertBeginRenderPassSuccess(&renderPass);
         }
@@ -822,8 +822,8 @@
         // there is no stencil component in the format.
         {
             utils::ComboRenderPassDescriptor renderPass({colorView}, depthStencilViewNoStencil);
-            renderPass.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Load;
-            renderPass.cDepthStencilAttachmentInfo.depthStoreOp = wgpu::StoreOp::Store;
+            renderPass.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Undefined;
+            renderPass.cDepthStencilAttachmentInfo.depthStoreOp = wgpu::StoreOp::Undefined;
             renderPass.cDepthStencilAttachmentInfo.depthReadOnly = true;
             renderPass.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Load;
             renderPass.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Store;
@@ -836,11 +836,11 @@
         // depth/stencil attachment in this case.
         {
             utils::ComboRenderPassDescriptor renderPass({colorView}, depthStencilViewNoStencil);
-            renderPass.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Load;
-            renderPass.cDepthStencilAttachmentInfo.depthStoreOp = wgpu::StoreOp::Store;
+            renderPass.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Undefined;
+            renderPass.cDepthStencilAttachmentInfo.depthStoreOp = wgpu::StoreOp::Undefined;
             renderPass.cDepthStencilAttachmentInfo.depthReadOnly = true;
-            renderPass.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Load;
-            renderPass.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Store;
+            renderPass.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Undefined;
+            renderPass.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Undefined;
             renderPass.cDepthStencilAttachmentInfo.stencilReadOnly = true;
             AssertBeginRenderPassSuccess(&renderPass);
         }
@@ -853,8 +853,8 @@
             renderPass.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Load;
             renderPass.cDepthStencilAttachmentInfo.depthStoreOp = wgpu::StoreOp::Store;
             renderPass.cDepthStencilAttachmentInfo.depthReadOnly = false;
-            renderPass.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Load;
-            renderPass.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Store;
+            renderPass.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Undefined;
+            renderPass.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Undefined;
             renderPass.cDepthStencilAttachmentInfo.stencilReadOnly = true;
             AssertBeginRenderPassSuccess(&renderPass);
         }
@@ -866,8 +866,8 @@
         // both depth and stencil components exist.
         {
             utils::ComboRenderPassDescriptor renderPass({colorView}, depthStencilView);
-            renderPass.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Load;
-            renderPass.cDepthStencilAttachmentInfo.depthStoreOp = wgpu::StoreOp::Store;
+            renderPass.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Undefined;
+            renderPass.cDepthStencilAttachmentInfo.depthStoreOp = wgpu::StoreOp::Undefined;
             renderPass.cDepthStencilAttachmentInfo.depthReadOnly = true;
             renderPass.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Load;
             renderPass.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Store;
@@ -898,6 +898,54 @@
             renderPass.cDepthStencilAttachmentInfo.stencilReadOnly = true;
             AssertBeginRenderPassError(&renderPass);
         }
+
+        // Tests that a pass with only depthLoadOp set to load and readOnly set to true fails.
+        {
+            utils::ComboRenderPassDescriptor renderPass({colorView}, depthStencilView);
+            renderPass.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Load;
+            renderPass.cDepthStencilAttachmentInfo.depthStoreOp = wgpu::StoreOp::Undefined;
+            renderPass.cDepthStencilAttachmentInfo.depthReadOnly = true;
+            renderPass.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Undefined;
+            renderPass.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Undefined;
+            renderPass.cDepthStencilAttachmentInfo.stencilReadOnly = true;
+            AssertBeginRenderPassError(&renderPass);
+        }
+
+        // Tests that a pass with only depthStoreOp set to store and readOnly set to true fails.
+        {
+            utils::ComboRenderPassDescriptor renderPass({colorView}, depthStencilView);
+            renderPass.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Undefined;
+            renderPass.cDepthStencilAttachmentInfo.depthStoreOp = wgpu::StoreOp::Store;
+            renderPass.cDepthStencilAttachmentInfo.depthReadOnly = true;
+            renderPass.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Undefined;
+            renderPass.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Undefined;
+            renderPass.cDepthStencilAttachmentInfo.stencilReadOnly = true;
+            AssertBeginRenderPassError(&renderPass);
+        }
+
+        // Tests that a pass with only stencilLoadOp set to load and readOnly set to true fails.
+        {
+            utils::ComboRenderPassDescriptor renderPass({colorView}, depthStencilView);
+            renderPass.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Undefined;
+            renderPass.cDepthStencilAttachmentInfo.depthStoreOp = wgpu::StoreOp::Undefined;
+            renderPass.cDepthStencilAttachmentInfo.depthReadOnly = true;
+            renderPass.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Load;
+            renderPass.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Undefined;
+            renderPass.cDepthStencilAttachmentInfo.stencilReadOnly = true;
+            AssertBeginRenderPassError(&renderPass);
+        }
+
+        // Tests that a pass with only stencilStoreOp set to store and readOnly set to true fails.
+        {
+            utils::ComboRenderPassDescriptor renderPass({colorView}, depthStencilView);
+            renderPass.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Undefined;
+            renderPass.cDepthStencilAttachmentInfo.depthStoreOp = wgpu::StoreOp::Undefined;
+            renderPass.cDepthStencilAttachmentInfo.depthReadOnly = true;
+            renderPass.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Undefined;
+            renderPass.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Store;
+            renderPass.cDepthStencilAttachmentInfo.stencilReadOnly = true;
+            AssertBeginRenderPassError(&renderPass);
+        }
     }
 
     // Check that the depth stencil attachment must use all aspects.
diff --git a/src/tests/unittests/validation/ResourceUsageTrackingTests.cpp b/src/tests/unittests/validation/ResourceUsageTrackingTests.cpp
index 1f7ef54..c0896d1 100644
--- a/src/tests/unittests/validation/ResourceUsageTrackingTests.cpp
+++ b/src/tests/unittests/validation/ResourceUsageTrackingTests.cpp
@@ -917,6 +917,8 @@
         // the same pass
         {
             passDescriptor.cDepthStencilAttachmentInfo.depthReadOnly = true;
+            passDescriptor.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Undefined;
+            passDescriptor.cDepthStencilAttachmentInfo.depthStoreOp = wgpu::StoreOp::Undefined;
 
             wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
             wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor);