Implement texture subresource on Vulkan

This change implemented texture subresource on Vulkan. It added a new
function to handle barriers for texture subresource for bind groups.
It also simplified barriers which are set for texture clear and copy.

Before this patch, all barriers are done upon all mip levels and all
array layers. With this patch, barriers are done upon particular mip
level(s) and array layer(s).

We may need more texture subresource end2end tests for copy and clear
opterations. I will visit that later.

Bug: dawn:157

Change-Id: Ie2247c6315326494f2d3736334e84b2867a16c17
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/22024
Commit-Queue: Yunchao He <yunchao.he@intel.com>
Reviewed-by: Austin Eng <enga@chromium.org>
diff --git a/src/dawn_native/vulkan/CommandBufferVk.cpp b/src/dawn_native/vulkan/CommandBufferVk.cpp
index 558e576..e5c2106 100644
--- a/src/dawn_native/vulkan/CommandBufferVk.cpp
+++ b/src/dawn_native/vulkan/CommandBufferVk.cpp
@@ -150,11 +150,13 @@
 
                             case wgpu::BindingType::ReadonlyStorageTexture:
                             case wgpu::BindingType::WriteonlyStorageTexture:
+                                // TODO (yunchao.he@intel.com): Do the transition for texture's
+                                // subresource via its view.
                                 ToBackend(
                                     static_cast<TextureViewBase*>(mBindings[index][bindingIndex])
                                         ->GetTexture())
-                                    ->TransitionUsageNow(recordingContext,
-                                                         wgpu::TextureUsage::Storage);
+                                    ->TransitionFullUsage(recordingContext,
+                                                          wgpu::TextureUsage::Storage);
                                 break;
 
                             case wgpu::BindingType::StorageTexture:
@@ -386,7 +388,8 @@
                                                                  texture->GetNumMipLevels(), 0,
                                                                  texture->GetArrayLayers());
                 }
-                texture->TransitionUsageNow(recordingContext, usages.textureUsages[i].usage);
+                texture->TransitionUsageForPass(recordingContext,
+                                                usages.textureUsages[i].subresourceUsages);
             }
         };
         const std::vector<PassResourceUsage>& passResourceUsages = GetResourceUsages().perPass;
@@ -437,7 +440,9 @@
                     ToBackend(src.buffer)
                         ->TransitionUsageNow(recordingContext, wgpu::BufferUsage::CopySrc);
                     ToBackend(dst.texture)
-                        ->TransitionUsageNow(recordingContext, wgpu::TextureUsage::CopyDst);
+                        ->TransitionUsageNow(recordingContext, wgpu::TextureUsage::CopyDst,
+                                             subresource.mipLevel, 1, subresource.baseArrayLayer,
+                                             1);
                     VkBuffer srcBuffer = ToBackend(src.buffer)->GetHandle();
                     VkImage dstImage = ToBackend(dst.texture)->GetHandle();
 
@@ -464,7 +469,9 @@
                                                               subresource.baseArrayLayer, 1);
 
                     ToBackend(src.texture)
-                        ->TransitionUsageNow(recordingContext, wgpu::TextureUsage::CopySrc);
+                        ->TransitionUsageNow(recordingContext, wgpu::TextureUsage::CopySrc,
+                                             subresource.mipLevel, 1, subresource.baseArrayLayer,
+                                             1);
                     ToBackend(dst.buffer)
                         ->TransitionUsageNow(recordingContext, wgpu::BufferUsage::CopyDst);
 
@@ -497,9 +504,11 @@
                     }
 
                     ToBackend(src.texture)
-                        ->TransitionUsageNow(recordingContext, wgpu::TextureUsage::CopySrc);
+                        ->TransitionUsageNow(recordingContext, wgpu::TextureUsage::CopySrc,
+                                             src.mipLevel, 1, src.arrayLayer, 1);
                     ToBackend(dst.texture)
-                        ->TransitionUsageNow(recordingContext, wgpu::TextureUsage::CopyDst);
+                        ->TransitionUsageNow(recordingContext, wgpu::TextureUsage::CopyDst,
+                                             dst.mipLevel, 1, dst.arrayLayer, 1);
 
                     // In some situations we cannot do texture-to-texture copies with vkCmdCopyImage
                     // because as Vulkan SPEC always validates image copies with the virtual size of
diff --git a/src/dawn_native/vulkan/SwapChainVk.cpp b/src/dawn_native/vulkan/SwapChainVk.cpp
index 0c6aad3..99c4775 100644
--- a/src/dawn_native/vulkan/SwapChainVk.cpp
+++ b/src/dawn_native/vulkan/SwapChainVk.cpp
@@ -59,7 +59,7 @@
         // Perform the necessary pipeline barriers for the texture to be used with the usage
         // requested by the implementation.
         CommandRecordingContext* recordingContext = device->GetPendingRecordingContext();
-        ToBackend(texture)->TransitionUsageNow(recordingContext, mTextureUsage);
+        ToBackend(texture)->TransitionFullUsage(recordingContext, mTextureUsage);
 
         DAWN_TRY(device->SubmitPendingCommands());
 
diff --git a/src/dawn_native/vulkan/TextureVk.cpp b/src/dawn_native/vulkan/TextureVk.cpp
index bd036dd..fa3c20b 100644
--- a/src/dawn_native/vulkan/TextureVk.cpp
+++ b/src/dawn_native/vulkan/TextureVk.cpp
@@ -225,6 +225,31 @@
             return {extent.width, extent.height, extent.depth};
         }
 
+        VkImageMemoryBarrier BuildMemoryBarrier(const Format& format,
+                                                const VkImage& image,
+                                                wgpu::TextureUsage lastUsage,
+                                                wgpu::TextureUsage usage,
+                                                uint32_t mipLevel,
+                                                uint32_t arrayLayer) {
+            VkImageMemoryBarrier barrier;
+            barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+            barrier.pNext = nullptr;
+            barrier.srcAccessMask = VulkanAccessFlags(lastUsage, format);
+            barrier.dstAccessMask = VulkanAccessFlags(usage, format);
+            barrier.oldLayout = VulkanImageLayout(lastUsage, format);
+            barrier.newLayout = VulkanImageLayout(usage, format);
+            barrier.image = image;
+            barrier.subresourceRange.aspectMask = VulkanAspectMask(format);
+            barrier.subresourceRange.baseMipLevel = mipLevel;
+            barrier.subresourceRange.levelCount = 1;
+            barrier.subresourceRange.baseArrayLayer = arrayLayer;
+            barrier.subresourceRange.layerCount = 1;
+
+            barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+            barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+            return barrier;
+        }
+
     }  // namespace
 
     // Converts Dawn texture format to Vulkan formats.
@@ -594,7 +619,7 @@
 
         // Release the texture
         mExternalState = ExternalState::PendingRelease;
-        TransitionUsageNow(device->GetPendingRecordingContext(), wgpu::TextureUsage::None);
+        TransitionFullUsage(device->GetPendingRecordingContext(), wgpu::TextureUsage::None);
 
         // Queue submit to signal we are done with the texture
         device->GetPendingRecordingContext()->signalSemaphores.push_back(mSignalSemaphore);
@@ -644,65 +669,141 @@
         return VulkanAspectMask(GetFormat());
     }
 
-    void Texture::TransitionUsageNow(CommandRecordingContext* recordingContext,
-                                     wgpu::TextureUsage usage) {
-        // Avoid encoding barriers when it isn't needed.
-        bool lastReadOnly = (mLastUsage & kReadOnlyTextureUsages) == mLastUsage;
-        if (lastReadOnly && mLastUsage == usage && mLastExternalState == mExternalState) {
-            return;
-        }
-
-        const Format& format = GetFormat();
-
-        VkPipelineStageFlags srcStages = VulkanPipelineStage(mLastUsage, format);
-        VkPipelineStageFlags dstStages = VulkanPipelineStage(usage, format);
-
-        VkImageMemoryBarrier barrier;
-        barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
-        barrier.pNext = nullptr;
-        barrier.srcAccessMask = VulkanAccessFlags(mLastUsage, format);
-        barrier.dstAccessMask = VulkanAccessFlags(usage, format);
-        barrier.oldLayout = VulkanImageLayout(mLastUsage, format);
-        barrier.newLayout = VulkanImageLayout(usage, format);
-        barrier.image = mHandle;
-        // This transitions the whole resource but assumes it is a 2D texture
-        ASSERT(GetDimension() == wgpu::TextureDimension::e2D);
-        barrier.subresourceRange.aspectMask = VulkanAspectMask(format);
-        barrier.subresourceRange.baseMipLevel = 0;
-        barrier.subresourceRange.levelCount = GetNumMipLevels();
-        barrier.subresourceRange.baseArrayLayer = 0;
-        barrier.subresourceRange.layerCount = GetArrayLayers();
-
-        barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
-        barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+    void Texture::TweakTransitionForExternalUsage(CommandRecordingContext* recordingContext,
+                                                  std::vector<VkImageMemoryBarrier>* barriers) {
+        ASSERT(GetNumMipLevels() == 1 && GetArrayLayers() == 1);
+        ASSERT(barriers->size() <= 1);
 
         if (mExternalState == ExternalState::PendingAcquire) {
+            if (!barriers->size()) {
+                barriers->push_back(BuildMemoryBarrier(GetFormat(), mHandle,
+                                                       wgpu::TextureUsage::None,
+                                                       wgpu::TextureUsage::None, 0, 0));
+            }
+
             // Transfer texture from external queue to graphics queue
-            barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL_KHR;
-            barrier.dstQueueFamilyIndex = ToBackend(GetDevice())->GetGraphicsQueueFamily();
+            (*barriers)[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL_KHR;
+            (*barriers)[0].dstQueueFamilyIndex = ToBackend(GetDevice())->GetGraphicsQueueFamily();
             // Don't override oldLayout to leave it as VK_IMAGE_LAYOUT_UNDEFINED
             // TODO(http://crbug.com/dawn/200)
             mExternalState = ExternalState::Acquired;
-
         } else if (mExternalState == ExternalState::PendingRelease) {
+            if (!barriers->size()) {
+                barriers->push_back(BuildMemoryBarrier(GetFormat(), mHandle,
+                                                       wgpu::TextureUsage::None,
+                                                       wgpu::TextureUsage::None, 0, 0));
+            }
+
             // Transfer texture from graphics queue to external queue
-            barrier.srcQueueFamilyIndex = ToBackend(GetDevice())->GetGraphicsQueueFamily();
-            barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL_KHR;
-            barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL;
+            (*barriers)[0].srcQueueFamilyIndex = ToBackend(GetDevice())->GetGraphicsQueueFamily();
+            (*barriers)[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL_KHR;
+            (*barriers)[0].newLayout = VK_IMAGE_LAYOUT_GENERAL;
             mExternalState = ExternalState::Released;
         }
 
-        // Move required semaphores into waitSemaphores
+        mLastExternalState = mExternalState;
+
         recordingContext->waitSemaphores.insert(recordingContext->waitSemaphores.end(),
                                                 mWaitRequirements.begin(), mWaitRequirements.end());
         mWaitRequirements.clear();
+    }
 
+    void Texture::TransitionFullUsage(CommandRecordingContext* recordingContext,
+                                      wgpu::TextureUsage usage) {
+        TransitionUsageNow(recordingContext, usage, 0, GetNumMipLevels(), 0, GetArrayLayers());
+    }
+
+    void Texture::TransitionUsageForPass(CommandRecordingContext* recordingContext,
+                                         const std::vector<wgpu::TextureUsage>& subresourceUsages) {
+        std::vector<VkImageMemoryBarrier> barriers;
+        const Format& format = GetFormat();
+
+        wgpu::TextureUsage allUsages = wgpu::TextureUsage::None;
+        wgpu::TextureUsage allLastUsages = wgpu::TextureUsage::None;
+
+        ASSERT(subresourceUsages.size() == GetSubresourceCount());
+        // This transitions assume it is a 2D texture
+        ASSERT(GetDimension() == wgpu::TextureDimension::e2D);
+
+        for (uint32_t arrayLayer = 0; arrayLayer < GetArrayLayers(); ++arrayLayer) {
+            for (uint32_t mipLevel = 0; mipLevel < GetNumMipLevels(); ++mipLevel) {
+                uint32_t index = GetSubresourceIndex(mipLevel, arrayLayer);
+
+                // Avoid encoding barriers when it isn't needed.
+                if (subresourceUsages[index] == wgpu::TextureUsage::None) {
+                    continue;
+                }
+                bool lastReadOnly = (mLastSubresourceUsages[index] & kReadOnlyTextureUsages) ==
+                                    mLastSubresourceUsages[index];
+                if (lastReadOnly && mLastSubresourceUsages[index] == subresourceUsages[index] &&
+                    mLastExternalState == mExternalState) {
+                    continue;
+                }
+
+                barriers.push_back(
+                    BuildMemoryBarrier(format, mHandle, mLastSubresourceUsages[index],
+                                       subresourceUsages[index], mipLevel, arrayLayer));
+
+                allUsages |= subresourceUsages[index];
+                allLastUsages |= mLastSubresourceUsages[index];
+                mLastSubresourceUsages[index] = subresourceUsages[index];
+            }
+        }
+
+        if (mExternalState != ExternalState::InternalOnly) {
+            TweakTransitionForExternalUsage(recordingContext, &barriers);
+        }
+
+        VkPipelineStageFlags srcStages = VulkanPipelineStage(allLastUsages, format);
+        VkPipelineStageFlags dstStages = VulkanPipelineStage(allUsages, format);
         ToBackend(GetDevice())
             ->fn.CmdPipelineBarrier(recordingContext->commandBuffer, srcStages, dstStages, 0, 0,
-                                    nullptr, 0, nullptr, 1, &barrier);
+                                    nullptr, 0, nullptr, barriers.size(), barriers.data());
+    }
 
-        mLastUsage = usage;
-        mLastExternalState = mExternalState;
+    void Texture::TransitionUsageNow(CommandRecordingContext* recordingContext,
+                                     wgpu::TextureUsage usage,
+                                     uint32_t baseMipLevel,
+                                     uint32_t levelCount,
+                                     uint32_t baseArrayLayer,
+                                     uint32_t layerCount) {
+        std::vector<VkImageMemoryBarrier> barriers;
+        const Format& format = GetFormat();
+
+        wgpu::TextureUsage allLastUsages = wgpu::TextureUsage::None;
+
+        // This transitions assume it is a 2D texture
+        ASSERT(GetDimension() == wgpu::TextureDimension::e2D);
+
+        for (uint32_t arrayLayer = 0; arrayLayer < layerCount; ++arrayLayer) {
+            for (uint32_t mipLevel = 0; mipLevel < levelCount; ++mipLevel) {
+                uint32_t index =
+                    GetSubresourceIndex(baseMipLevel + mipLevel, baseArrayLayer + arrayLayer);
+                wgpu::TextureUsage lastUsage = mLastSubresourceUsages[index];
+
+                // Avoid encoding barriers when it isn't needed.
+                bool lastReadOnly = (lastUsage & kReadOnlyTextureUsages) == lastUsage;
+                if (lastReadOnly && lastUsage == usage && mLastExternalState == mExternalState) {
+                    return;
+                }
+
+                barriers.push_back(BuildMemoryBarrier(format, mHandle, lastUsage, usage,
+                                                      baseMipLevel + mipLevel,
+                                                      baseArrayLayer + arrayLayer));
+                allLastUsages |= lastUsage;
+                mLastSubresourceUsages[index] = usage;
+            }
+        }
+
+        if (mExternalState != ExternalState::InternalOnly) {
+            TweakTransitionForExternalUsage(recordingContext, &barriers);
+        }
+
+        VkPipelineStageFlags srcStages = VulkanPipelineStage(allLastUsages, format);
+        VkPipelineStageFlags dstStages = VulkanPipelineStage(usage, format);
+        ToBackend(GetDevice())
+            ->fn.CmdPipelineBarrier(recordingContext->commandBuffer, srcStages, dstStages, 0, 0,
+                                    nullptr, 0, nullptr, barriers.size(), barriers.data());
     }
 
     MaybeError Texture::ClearTexture(CommandRecordingContext* recordingContext,
@@ -716,7 +817,8 @@
         uint8_t clearColor = (clearValue == TextureBase::ClearValue::Zero) ? 0 : 1;
         float fClearColor = (clearValue == TextureBase::ClearValue::Zero) ? 0.f : 1.f;
 
-        TransitionUsageNow(recordingContext, wgpu::TextureUsage::CopyDst);
+        TransitionUsageNow(recordingContext, wgpu::TextureUsage::CopyDst, baseMipLevel, levelCount,
+                           baseArrayLayer, layerCount);
         if (GetFormat().isRenderable) {
             VkImageSubresourceRange range = {};
             range.aspectMask = GetVkAspectMask();
diff --git a/src/dawn_native/vulkan/TextureVk.h b/src/dawn_native/vulkan/TextureVk.h
index 42c6216..1ac37c4 100644
--- a/src/dawn_native/vulkan/TextureVk.h
+++ b/src/dawn_native/vulkan/TextureVk.h
@@ -63,8 +63,18 @@
         // Transitions the texture to be used as `usage`, recording any necessary barrier in
         // `commands`.
         // TODO(cwallez@chromium.org): coalesce barriers and do them early when possible.
+        void TransitionFullUsage(CommandRecordingContext* recordingContext,
+                                 wgpu::TextureUsage usage);
+
         void TransitionUsageNow(CommandRecordingContext* recordingContext,
-                                wgpu::TextureUsage usage);
+                                wgpu::TextureUsage usage,
+                                uint32_t baseMipLevel,
+                                uint32_t levelCount,
+                                uint32_t baseArrayLayer,
+                                uint32_t layerCount);
+        void TransitionUsageForPass(CommandRecordingContext* recordingContext,
+                                    const std::vector<wgpu::TextureUsage>& subresourceUsages);
+
         void EnsureSubresourceContentInitialized(CommandRecordingContext* recordingContext,
                                                  uint32_t baseMipLevel,
                                                  uint32_t levelCount,
@@ -96,6 +106,9 @@
                                 uint32_t layerCount,
                                 TextureBase::ClearValue);
 
+        void TweakTransitionForExternalUsage(CommandRecordingContext* recordingContext,
+                                             std::vector<VkImageMemoryBarrier>* barriers);
+
         VkImage mHandle = VK_NULL_HANDLE;
         ResourceMemoryAllocation mMemoryAllocation;
         VkDeviceMemory mExternalAllocation = VK_NULL_HANDLE;
@@ -115,7 +128,8 @@
 
         // A usage of none will make sure the texture is transitioned before its first use as
         // required by the Vulkan spec.
-        wgpu::TextureUsage mLastUsage = wgpu::TextureUsage::None;
+        std::vector<wgpu::TextureUsage> mLastSubresourceUsages =
+            std::vector<wgpu::TextureUsage>(GetSubresourceCount(), wgpu::TextureUsage::None);
     };
 
     class TextureView final : public TextureViewBase {
diff --git a/src/tests/end2end/TextureSubresourceTests.cpp b/src/tests/end2end/TextureSubresourceTests.cpp
index fb3b67d..03dd486 100644
--- a/src/tests/end2end/TextureSubresourceTests.cpp
+++ b/src/tests/end2end/TextureSubresourceTests.cpp
@@ -188,7 +188,12 @@
     EXPECT_TEXTURE_RGBA8_EQ(&bottomLeft, texture, 0, kSize - 1, 1, 1, 0, 1);
 }
 
-// TODO (yunchao.he@intel.com): add tests for storage texture and sampler across miplevel or
+// TODO (yunchao.he@intel.com):
+// * add tests for storage texture and sampler across miplevel or
 // arraylayer dimensions in the same texture
+//
+// * add tests for copy operation upon texture subresource if needed
+//
+// * add tests for clear operation upon texture subresource if needed
 
-DAWN_INSTANTIATE_TEST(TextureSubresourceTest, MetalBackend(), OpenGLBackend());
+DAWN_INSTANTIATE_TEST(TextureSubresourceTest, MetalBackend(), OpenGLBackend(), VulkanBackend());