Implement readonly depth/stencil attachment on Vulkan

It requires to revise quite a few flags in order to implement
readonly depth/stencil attachment on Vulkan. For example:
  - VkAccessFlags,
  - VkPipelineStageFlags,
  - VkImageUsageFlags,
  - and the most important: VkImageLayout.

These revised flags need to be applied to many Vulkan objects.
For examples:
  - VkImageMemoryBarriers,
  - IMAGE_LAYOUT revisions in descriptor set (bindings),
    render pass, and subpass,
  - and loadOp/storeOp revisions in render pass and subpass.

This change also does some workarounds in order to make Vulkan
validation layers happy. For example:
  - use DEPTH_STENCIL_READ_ONLY image layout for binding a
    depth/stencil texture,
  - add DEPTH_STENCIL_ATTACHMENT_BIT image usage for binding a
    depth/stencil texture.

Note that STORE_OP_STORE is used for depth/stencil's storeOp for
readonly attachment. It leads to Vulkan validation error. This
change igores that error and let it proceeds and it works well.

Bug: dawn:485
Change-Id: Ie247c9cbffbd837984b0933a905632ab5ad8862d
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/70280
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Yunchao He <yunchao.he@intel.com>
diff --git a/src/dawn_native/vulkan/BackendVk.cpp b/src/dawn_native/vulkan/BackendVk.cpp
index 1c4c1b9..7fd6462 100644
--- a/src/dawn_native/vulkan/BackendVk.cpp
+++ b/src/dawn_native/vulkan/BackendVk.cpp
@@ -52,6 +52,31 @@
 #    error "Unimplemented Vulkan backend platform"
 #endif
 
+struct SkippedMessage {
+    const char* messageId;
+    const char* messageContents;
+};
+
+// Array of Validation error/warning messages that will be ignored, should include bugID
+constexpr SkippedMessage kSkippedMessages[] = {
+    // These errors are generated when simultaneously using a read-only depth/stencil attachment as
+    // a texture binding. This is valid Vulkan.
+    //
+    // When storeOp=NONE is not present, Dawn uses storeOp=STORE, but Vulkan validation layer
+    // considers the image read-only and produces a hazard. Dawn can't rely on storeOp=NONE and
+    // so this is not expected to be worked around.
+    // See http://crbug.com/dawn/1225 for more details.
+    {"SYNC-HAZARD-WRITE_AFTER_READ",
+     "depth aspect during store with storeOp VK_ATTACHMENT_STORE_OP_STORE. Access info (usage: "
+     "SYNC_LATE_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE, prior_usage: "
+     "SYNC_FRAGMENT_SHADER_SHADER_STORAGE_READ, read_barriers: VK_PIPELINE_STAGE_2_NONE_KHR, "},
+
+    {"SYNC-HAZARD-WRITE_AFTER_READ",
+     "stencil aspect during store with stencilStoreOp VK_ATTACHMENT_STORE_OP_STORE. Access info "
+     "(usage: SYNC_LATE_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE, prior_usage: "
+     "SYNC_FRAGMENT_SHADER_SHADER_STORAGE_READ, read_barriers: VK_PIPELINE_STAGE_2_NONE_KHR, "},
+};
+
 namespace dawn_native { namespace vulkan {
 
     namespace {
@@ -63,14 +88,26 @@
 #endif  // defined(DAWN_ENABLE_SWIFTSHADER)
         };
 
+        // Suppress validation errors that are known. Returns false in that case.
+        bool ShouldReportDebugMessage(const char* messageId, const char* message) {
+            for (const SkippedMessage& msg : kSkippedMessages) {
+                if (strstr(messageId, msg.messageId) != nullptr &&
+                    strstr(message, msg.messageContents) != nullptr) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
         VKAPI_ATTR VkBool32 VKAPI_CALL
         OnDebugUtilsCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
                              VkDebugUtilsMessageTypeFlagsEXT /* messageTypes */,
                              const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
                              void* /* pUserData */) {
-            dawn::WarningLog() << pCallbackData->pMessage;
-            ASSERT((messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) == 0);
-
+            if (ShouldReportDebugMessage(pCallbackData->pMessageIdName, pCallbackData->pMessage)) {
+                dawn::WarningLog() << pCallbackData->pMessage;
+                ASSERT((messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) == 0);
+            }
             return VK_FALSE;
         }
 
diff --git a/src/dawn_native/vulkan/CommandBufferVk.cpp b/src/dawn_native/vulkan/CommandBufferVk.cpp
index 62a5cd2..d89688c 100644
--- a/src/dawn_native/vulkan/CommandBufferVk.cpp
+++ b/src/dawn_native/vulkan/CommandBufferVk.cpp
@@ -217,10 +217,11 @@
                 if (renderPass->attachmentState->HasDepthStencilAttachment()) {
                     const auto& attachmentInfo = renderPass->depthStencilAttachment;
 
-                    query.SetDepthStencil(attachmentInfo.view->GetTexture()->GetFormat().format,
-                                          attachmentInfo.depthLoadOp, attachmentInfo.depthStoreOp,
-                                          attachmentInfo.stencilLoadOp,
-                                          attachmentInfo.stencilStoreOp);
+                    query.SetDepthStencil(
+                        attachmentInfo.view->GetTexture()->GetFormat().format,
+                        attachmentInfo.depthLoadOp, attachmentInfo.depthStoreOp,
+                        attachmentInfo.stencilLoadOp, attachmentInfo.stencilStoreOp,
+                        attachmentInfo.depthReadOnly || attachmentInfo.stencilReadOnly);
                 }
 
                 query.SetSampleCount(renderPass->attachmentState->GetSampleCount());
diff --git a/src/dawn_native/vulkan/RenderPassCache.cpp b/src/dawn_native/vulkan/RenderPassCache.cpp
index b91e46f..2cc815b 100644
--- a/src/dawn_native/vulkan/RenderPassCache.cpp
+++ b/src/dawn_native/vulkan/RenderPassCache.cpp
@@ -34,6 +34,8 @@
         }
 
         VkAttachmentStoreOp VulkanAttachmentStoreOp(wgpu::StoreOp op) {
+            // TODO(crbug.com/dawn/485): return STORE_OP_STORE_NONE_QCOM if the device has required
+            // extension.
             switch (op) {
                 case wgpu::StoreOp::Store:
                     return VK_ATTACHMENT_STORE_OP_STORE;
@@ -62,13 +64,15 @@
                                                wgpu::LoadOp depthLoadOpIn,
                                                wgpu::StoreOp depthStoreOpIn,
                                                wgpu::LoadOp stencilLoadOpIn,
-                                               wgpu::StoreOp stencilStoreOpIn) {
+                                               wgpu::StoreOp stencilStoreOpIn,
+                                               bool readOnly) {
         hasDepthStencil = true;
         depthStencilFormat = format;
         depthLoadOp = depthLoadOpIn;
         depthStoreOp = depthStoreOpIn;
         stencilLoadOp = stencilLoadOpIn;
         stencilStoreOp = stencilStoreOpIn;
+        readOnlyDepthStencil = readOnly;
     }
 
     void RenderPassCacheQuery::SetSampleCount(uint32_t sampleCount) {
@@ -144,17 +148,23 @@
             depthStencilAttachment = &depthStencilAttachmentRef;
 
             depthStencilAttachmentRef.attachment = attachmentCount;
-            depthStencilAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+            depthStencilAttachmentRef.layout =
+                query.readOnlyDepthStencil ? VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL
+                                           : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
 
             attachmentDesc.flags = 0;
             attachmentDesc.format = VulkanImageFormat(mDevice, query.depthStencilFormat);
             attachmentDesc.samples = vkSampleCount;
+
             attachmentDesc.loadOp = VulkanAttachmentLoadOp(query.depthLoadOp);
             attachmentDesc.storeOp = VulkanAttachmentStoreOp(query.depthStoreOp);
             attachmentDesc.stencilLoadOp = VulkanAttachmentLoadOp(query.stencilLoadOp);
             attachmentDesc.stencilStoreOp = VulkanAttachmentStoreOp(query.stencilStoreOp);
-            attachmentDesc.initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
-            attachmentDesc.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+            // There is only one subpass, so it is safe to set both initialLayout and finalLayout to
+            // the only subpass's layout.
+            attachmentDesc.initialLayout = depthStencilAttachmentRef.layout;
+            attachmentDesc.finalLayout = depthStencilAttachmentRef.layout;
 
             ++attachmentCount;
         }
@@ -235,7 +245,8 @@
 
         HashCombine(&hash, query.hasDepthStencil);
         if (query.hasDepthStencil) {
-            HashCombine(&hash, query.depthStencilFormat, query.depthLoadOp, query.stencilLoadOp);
+            HashCombine(&hash, query.depthStencilFormat, query.depthLoadOp, query.stencilLoadOp,
+                        query.readOnlyDepthStencil);
         }
 
         HashCombine(&hash, query.sampleCount);
@@ -270,7 +281,8 @@
 
         if (a.hasDepthStencil) {
             if ((a.depthStencilFormat != b.depthStencilFormat) ||
-                (a.depthLoadOp != b.depthLoadOp) || (a.stencilLoadOp != b.stencilLoadOp)) {
+                (a.depthLoadOp != b.depthLoadOp) || (a.stencilLoadOp != b.stencilLoadOp) ||
+                (a.readOnlyDepthStencil != b.readOnlyDepthStencil)) {
                 return false;
             }
         }
diff --git a/src/dawn_native/vulkan/RenderPassCache.h b/src/dawn_native/vulkan/RenderPassCache.h
index 4503e1f..29bcfd0 100644
--- a/src/dawn_native/vulkan/RenderPassCache.h
+++ b/src/dawn_native/vulkan/RenderPassCache.h
@@ -47,7 +47,8 @@
                              wgpu::LoadOp depthLoadOp,
                              wgpu::StoreOp depthStoreOp,
                              wgpu::LoadOp stencilLoadOp,
-                             wgpu::StoreOp stencilStoreOp);
+                             wgpu::StoreOp stencilStoreOp,
+                             bool readOnly);
         void SetSampleCount(uint32_t sampleCount);
 
         ityp::bitset<ColorAttachmentIndex, kMaxColorAttachments> colorMask;
@@ -62,6 +63,7 @@
         wgpu::StoreOp depthStoreOp;
         wgpu::LoadOp stencilLoadOp;
         wgpu::StoreOp stencilStoreOp;
+        bool readOnlyDepthStencil;
 
         uint32_t sampleCount;
     };
diff --git a/src/dawn_native/vulkan/RenderPipelineVk.cpp b/src/dawn_native/vulkan/RenderPipelineVk.cpp
index 556b582..d0072df 100644
--- a/src/dawn_native/vulkan/RenderPipelineVk.cpp
+++ b/src/dawn_native/vulkan/RenderPipelineVk.cpp
@@ -497,7 +497,9 @@
         dynamic.pDynamicStates = dynamicStates;
 
         // Get a VkRenderPass that matches the attachment formats for this pipeline, load/store ops
-        // don't matter so set them all to LoadOp::Load / StoreOp::Store
+        // don't matter so set them all to LoadOp::Load / StoreOp::Store. Whether the render pass
+        // has resolve target and whether depth/stencil attachment is read-only also don't matter,
+        // so set them both to false.
         VkRenderPass renderPass = VK_NULL_HANDLE;
         {
             RenderPassCacheQuery query;
@@ -510,7 +512,7 @@
             if (HasDepthStencilAttachment()) {
                 query.SetDepthStencil(GetDepthStencilFormat(), wgpu::LoadOp::Load,
                                       wgpu::StoreOp::Store, wgpu::LoadOp::Load,
-                                      wgpu::StoreOp::Store);
+                                      wgpu::StoreOp::Store, false);
             }
 
             query.SetSampleCount(GetSampleCount());
diff --git a/src/dawn_native/vulkan/TextureVk.cpp b/src/dawn_native/vulkan/TextureVk.cpp
index 2edf724..73519c8 100644
--- a/src/dawn_native/vulkan/TextureVk.cpp
+++ b/src/dawn_native/vulkan/TextureVk.cpp
@@ -83,6 +83,9 @@
                         VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
                 }
             }
+            if (usage & kReadOnlyRenderAttachment) {
+                flags |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT;
+            }
             if (usage & kPresentTextureUsage) {
                 // The present usage is only used internally by the swapchain and is never used in
                 // combination with other usages.
@@ -129,7 +132,7 @@
                 flags |=
                     VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
             }
-            if (usage & wgpu::TextureUsage::RenderAttachment) {
+            if (usage & (wgpu::TextureUsage::RenderAttachment | kReadOnlyRenderAttachment)) {
                 if (format.HasDepthOrStencil()) {
                     flags |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
                              VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
@@ -442,6 +445,12 @@
         }
         if (usage & wgpu::TextureUsage::TextureBinding) {
             flags |= VK_IMAGE_USAGE_SAMPLED_BIT;
+            // If the sampled texture is a depth/stencil texture, its image layout will be set
+            // to DEPTH_STENCIL_READ_ONLY_OPTIMAL in order to support readonly depth/stencil
+            // attachment. That layout requires DEPTH_STENCIL_ATTACHMENT_BIT image usage.
+            if (format.HasDepthOrStencil() && format.isRenderable) {
+                flags |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
+            }
         }
         if (usage & wgpu::TextureUsage::StorageBinding) {
             flags |= VK_IMAGE_USAGE_STORAGE_BIT;
@@ -453,6 +462,9 @@
                 flags |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
             }
         }
+        if (usage & kReadOnlyRenderAttachment) {
+            flags |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
+        }
 
         return flags;
     }
@@ -466,10 +478,17 @@
         }
 
         if (!wgpu::HasZeroOrOneBits(usage)) {
-            // Sampled | ReadOnlyStorage is the only possible multi-bit usage, if more appear  we
-            // might need additional special-casing.
-            ASSERT(usage == wgpu::TextureUsage::TextureBinding);
-            return VK_IMAGE_LAYOUT_GENERAL;
+            // Sampled | kReadOnlyRenderAttachment is the only possible multi-bit usage, if more
+            // appear we might need additional special-casing.
+            ASSERT(usage == (wgpu::TextureUsage::TextureBinding | kReadOnlyRenderAttachment));
+
+            // WebGPU requires both aspects to be readonly if the attachment's format does have
+            // both depth and stencil aspects. Vulkan 1.0 supports readonly for both aspects too
+            // via DEPTH_STENCIL_READ_ONLY image layout. Vulkan 1.1 and above can support separate
+            // readonly for a single aspect via DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL and
+            // DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL layouts. But Vulkan 1.0 cannot support
+            // it, and WebGPU doesn't need that currently.
+            return VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL;
         }
 
         // Usage has a single bit so we can switch on its value directly.
@@ -477,17 +496,23 @@
             case wgpu::TextureUsage::CopyDst:
                 return VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
 
-                // A texture that's sampled and storage may be used as both usages in the same pass.
-                // When that happens, the layout must be GENERAL because that's a requirement for
-                // the storage usage. We can't know at bindgroup creation time if that case will
-                // happen so we must prepare for the pessimistic case and always use the GENERAL
-                // layout.
+                // The layout returned here is the one that will be used at bindgroup creation time.
+                // The bindgrpup's layout must match the runtime layout of the image when it is
+                // used via the bindgroup, but we don't know exactly what it will be yet. So we
+                // have to prepare for the pessimistic case.
             case wgpu::TextureUsage::TextureBinding:
+                // Only VK_IMAGE_LAYOUT_GENERAL can do sampling and storage access of texture at the
+                // same time.
                 if (texture->GetInternalUsage() & wgpu::TextureUsage::StorageBinding) {
                     return VK_IMAGE_LAYOUT_GENERAL;
-                } else {
-                    return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
                 }
+                // The sampled image can be used as a readonly depth/stencil attachment at the same
+                // time if it is a depth/stencil renderable format, so the image layout need to be
+                // VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL.
+                if (texture->GetFormat().HasDepthOrStencil() && texture->GetFormat().isRenderable) {
+                    return VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL;
+                }
+                return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
 
                 // Vulkan texture copy functions require the image to be in _one_  known layout.
                 // Depending on whether parts of the texture have been transitioned to only CopySrc
diff --git a/src/tests/end2end/ReadOnlyDepthStencilAttachmentTests.cpp b/src/tests/end2end/ReadOnlyDepthStencilAttachmentTests.cpp
index 0096dbe..97a7637 100644
--- a/src/tests/end2end/ReadOnlyDepthStencilAttachmentTests.cpp
+++ b/src/tests/end2end/ReadOnlyDepthStencilAttachmentTests.cpp
@@ -254,10 +254,10 @@
 }
 
 DAWN_INSTANTIATE_TEST_P(ReadOnlyDepthAttachmentTests,
-                        {D3D12Backend()},
+                        {D3D12Backend(), VulkanBackend()},
                         std::vector<wgpu::TextureFormat>(utils::kDepthFormats.begin(),
                                                          utils::kDepthFormats.end()));
 DAWN_INSTANTIATE_TEST_P(ReadOnlyStencilAttachmentTests,
-                        {D3D12Backend()},
+                        {D3D12Backend(), VulkanBackend()},
                         std::vector<wgpu::TextureFormat>(utils::kStencilFormats.begin(),
                                                          utils::kStencilFormats.end()));