Vulkan: Implement transfer path for texture copy

This is primarily in preparation for compressed texture copy, but has
the following side effect:

- When transfer is possible, it's faster than draw
- When texture format does not support draw (but transfer is possible),
  it will avoid copying through CPU.

Bug: angleproject:2670
Change-Id: I49e1b51e6ccec875db3f971106687c7d48c4916f
Reviewed-on: https://chromium-review.googlesource.com/c/1470595
Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>
Reviewed-by: Yuly Novikov <ynovikov@chromium.org>
diff --git a/src/libANGLE/renderer/vulkan/FramebufferVk.cpp b/src/libANGLE/renderer/vulkan/FramebufferVk.cpp
index 00339a4..b0af474 100644
--- a/src/libANGLE/renderer/vulkan/FramebufferVk.cpp
+++ b/src/libANGLE/renderer/vulkan/FramebufferVk.cpp
@@ -421,19 +421,30 @@
                                           bool blitDepthBuffer,
                                           bool blitStencilBuffer)
 {
-    vk::ImageHelper *writeImage = drawRenderTarget->getImageForWrite(&mFramebuffer);
+    VkImageAspectFlags aspectMask =
+        vk::GetDepthStencilAspectFlagsForCopy(blitDepthBuffer, blitStencilBuffer);
 
     vk::CommandBuffer *commandBuffer;
     ANGLE_TRY(mFramebuffer.recordCommands(contextVk, &commandBuffer));
 
+    vk::ImageHelper *writeImage = drawRenderTarget->getImageForWrite(&mFramebuffer);
+    writeImage->changeLayout(writeImage->getAspectFlags(), vk::ImageLayout::TransferDst,
+                             commandBuffer);
+
     vk::ImageHelper *readImage = readRenderTarget->getImageForRead(
         &mFramebuffer, vk::ImageLayout::TransferSrc, commandBuffer);
 
-    VkImageAspectFlags aspectMask =
-        vk::GetDepthStencilAspectFlagsForCopy(blitDepthBuffer, blitStencilBuffer);
+    VkImageSubresourceLayers readSubresource = {};
+    readSubresource.aspectMask               = aspectMask;
+    readSubresource.mipLevel                 = 0;
+    readSubresource.baseArrayLayer           = 0;
+    readSubresource.layerCount               = 1;
+
+    VkImageSubresourceLayers writeSubresource = readSubresource;
+
     vk::ImageHelper::Copy(readImage, writeImage, gl::Offset(), gl::Offset(),
-                          gl::Extents(copyArea.width, copyArea.height, 1), aspectMask,
-                          commandBuffer);
+                          gl::Extents(copyArea.width, copyArea.height, 1), readSubresource,
+                          writeSubresource, commandBuffer);
     return angle::Result::Continue;
 }
 
diff --git a/src/libANGLE/renderer/vulkan/RenderTargetVk.cpp b/src/libANGLE/renderer/vulkan/RenderTargetVk.cpp
index 1ac608a..8035c8e 100644
--- a/src/libANGLE/renderer/vulkan/RenderTargetVk.cpp
+++ b/src/libANGLE/renderer/vulkan/RenderTargetVk.cpp
@@ -140,6 +140,20 @@
     ASSERT(mImage && mImage->valid());
 
     // TODO(jmadill): Better simultaneous resource access. http://anglebug.com/2679
+    //
+    // A better alternative would be:
+    //
+    // if (mImage->isLayoutChangeNecessary(layout)
+    // {
+    //     vk::CommandBuffer *srcLayoutChange;
+    //     ANGLE_TRY(mImage->recordCommands(contextVk, &srcLayoutChange));
+    //     mImage->changeLayout(mImage->getAspectFlags(), layout, srcLayoutChange);
+    // }
+    // mImage->addReadDependency(readingResource);
+    //
+    // I.e. the transition should happen on a node generated from mImage itself.
+    // However, this needs context to be available here, or all call sites changed
+    // to perform the layout transition and set the dependency.
     mImage->addWriteDependency(readingResource);
 
     mImage->changeLayout(mImage->getAspectFlags(), layout, commandBuffer);
diff --git a/src/libANGLE/renderer/vulkan/TextureVk.cpp b/src/libANGLE/renderer/vulkan/TextureVk.cpp
index 3d0b5ac..e36b273 100644
--- a/src/libANGLE/renderer/vulkan/TextureVk.cpp
+++ b/src/libANGLE/renderer/vulkan/TextureVk.cpp
@@ -27,12 +27,28 @@
 {
 namespace
 {
-constexpr VkImageUsageFlags kStagingImageFlags =
+constexpr VkImageUsageFlags kDrawStagingImageFlags =
     VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
 
+constexpr VkImageUsageFlags kTransferStagingImageFlags =
+    VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+
 constexpr VkFormatFeatureFlags kBlitFeatureFlags =
     VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT;
 
+bool CanCopyWithTransfer(RendererVk *renderer,
+                         const vk::Format &srcFormat,
+                         const vk::Format &destFormat)
+{
+    // NOTE(syoussefi): technically, you can transfer between formats as long as they have the same
+    // size and are compatible, but for now, let's just support same-format copies with transfer.
+    return srcFormat.internalFormat == destFormat.internalFormat &&
+           renderer->hasTextureFormatFeatureBits(srcFormat.vkTextureFormat,
+                                                 VK_FORMAT_FEATURE_TRANSFER_SRC_BIT) &&
+           renderer->hasTextureFormatFeatureBits(destFormat.vkTextureFormat,
+                                                 VK_FORMAT_FEATURE_TRANSFER_DST_BIT);
+}
+
 bool CanCopyWithDraw(RendererVk *renderer,
                      const vk::Format &srcFormat,
                      const vk::Format &destFormat)
@@ -314,23 +330,31 @@
     const vk::Format &srcFormat  = framebufferVk->getColorReadRenderTarget()->getImageFormat();
     const vk::Format &destFormat = renderer->getFormat(internalFormat.sizedInternalFormat);
 
+    bool isViewportFlipY = contextVk->isViewportFlipEnabledForDrawFBO();
+
+    // If it's possible to perform the copy with a transfer, that's the best option.
+    if (!isViewportFlipY && CanCopyWithTransfer(renderer, srcFormat, destFormat))
+    {
+        RenderTargetVk *colorReadRT = framebufferVk->getColorReadRenderTarget();
+
+        return copySubImageImplWithTransfer(contextVk, offsetImageIndex, modifiedDestOffset,
+                                            destFormat, 0, clippedSourceArea,
+                                            &colorReadRT->getImage());
+    }
+
     bool forceCpuPath = ForceCpuPathForCopy(renderer, mImage);
 
     // If it's possible to perform the copy with a draw call, do that.
     if (CanCopyWithDraw(renderer, srcFormat, destFormat) && !forceCpuPath)
     {
         RenderTargetVk *colorReadRT = framebufferVk->getColorReadRenderTarget();
-        bool isViewportFlipY        = contextVk->isViewportFlipEnabledForDrawFBO();
 
         // Layer count can only be 1 as the source is a framebuffer.
-        ASSERT(index.getLayerCount() == 1);
+        ASSERT(offsetImageIndex.getLayerCount() == 1);
 
-        ANGLE_TRY(copySubImageImplWithDraw(contextVk, offsetImageIndex, modifiedDestOffset,
-                                           destFormat, 0, clippedSourceArea, isViewportFlipY, false,
-                                           false, false, &colorReadRT->getImage(),
-                                           colorReadRT->getReadImageView()));
-
-        return angle::Result::Continue;
+        return copySubImageImplWithDraw(contextVk, offsetImageIndex, modifiedDestOffset, destFormat,
+                                        0, clippedSourceArea, isViewportFlipY, false, false, false,
+                                        &colorReadRT->getImage(), colorReadRT->getReadImageView());
     }
 
     // Do a CPU readback that does the conversion, and then stage the change to the pixel buffer.
@@ -364,17 +388,23 @@
 
     const gl::ImageIndex offsetImageIndex = getNativeImageIndex(index);
 
+    // If it's possible to perform the copy with a transfer, that's the best option.
+    if (!unpackFlipY && !unpackPremultiplyAlpha && !unpackUnmultiplyAlpha &&
+        CanCopyWithTransfer(renderer, sourceVkFormat, destVkFormat))
+    {
+        return copySubImageImplWithTransfer(contextVk, offsetImageIndex, destOffset, destVkFormat,
+                                            sourceLevel, sourceArea, &source->getImage());
+    }
+
     bool forceCpuPath = ForceCpuPathForCopy(renderer, mImage);
 
     // If it's possible to perform the copy with a draw call, do that.
     if (CanCopyWithDraw(renderer, sourceVkFormat, destVkFormat) && !forceCpuPath)
     {
-        ANGLE_TRY(copySubImageImplWithDraw(contextVk, offsetImageIndex, destOffset, destVkFormat,
-                                           sourceLevel, sourceArea, false, unpackFlipY,
-                                           unpackPremultiplyAlpha, unpackUnmultiplyAlpha,
-                                           &source->getImage(), &source->getReadImageView()));
-
-        return angle::Result::Continue;
+        return copySubImageImplWithDraw(contextVk, offsetImageIndex, destOffset, destVkFormat,
+                                        sourceLevel, sourceArea, false, unpackFlipY,
+                                        unpackPremultiplyAlpha, unpackUnmultiplyAlpha,
+                                        &source->getImage(), &source->getReadImageView());
     }
 
     if (sourceLevel != 0)
@@ -429,6 +459,94 @@
     return angle::Result::Continue;
 }
 
+angle::Result TextureVk::copySubImageImplWithTransfer(ContextVk *contextVk,
+                                                      const gl::ImageIndex &index,
+                                                      const gl::Offset &destOffset,
+                                                      const vk::Format &destFormat,
+                                                      size_t sourceLevel,
+                                                      const gl::Rectangle &sourceArea,
+                                                      vk::ImageHelper *srcImage)
+{
+    RendererVk *renderer = contextVk->getRenderer();
+
+    uint32_t level       = index.getLevelIndex();
+    uint32_t baseLayer   = index.hasLayer() ? index.getLayerIndex() : 0;
+    uint32_t layerCount  = index.getLayerCount();
+    gl::Offset srcOffset = {sourceArea.x, sourceArea.y, 0};
+    gl::Extents extents  = {sourceArea.width, sourceArea.height, 1};
+
+    // Change source layout if necessary
+    if (srcImage->isLayoutChangeNecessary(vk::ImageLayout::TransferSrc))
+    {
+        vk::CommandBuffer *srcLayoutChange;
+        ANGLE_TRY(srcImage->recordCommands(contextVk, &srcLayoutChange));
+        srcImage->changeLayout(VK_IMAGE_ASPECT_COLOR_BIT, vk::ImageLayout::TransferSrc,
+                               srcLayoutChange);
+    }
+
+    VkImageSubresourceLayers srcSubresource = {};
+    srcSubresource.aspectMask               = VK_IMAGE_ASPECT_COLOR_BIT;
+    srcSubresource.mipLevel                 = sourceLevel;
+    srcSubresource.baseArrayLayer           = 0;
+    srcSubresource.layerCount               = layerCount;
+
+    // If destination is valid, copy the source directly into it.
+    if (mImage->valid())
+    {
+        // Make sure any updates to the image are already flushed.
+        ANGLE_TRY(ensureImageInitialized(contextVk));
+
+        vk::CommandBuffer *commandBuffer;
+        ANGLE_TRY(mImage->recordCommands(contextVk, &commandBuffer));
+
+        // Change the image layout before the transfer
+        mImage->changeLayout(VK_IMAGE_ASPECT_COLOR_BIT, vk::ImageLayout::TransferDst,
+                             commandBuffer);
+
+        // Source's layout change should happen before the copy
+        srcImage->addReadDependency(mImage);
+
+        VkImageSubresourceLayers destSubresource = srcSubresource;
+        destSubresource.mipLevel                 = level;
+        destSubresource.baseArrayLayer           = baseLayer;
+
+        vk::ImageHelper::Copy(srcImage, mImage, srcOffset, destOffset, extents, srcSubresource,
+                              destSubresource, commandBuffer);
+    }
+    else
+    {
+        std::unique_ptr<vk::ImageHelper> stagingImage;
+
+        // Create a temporary image to stage the copy
+        stagingImage = std::make_unique<vk::ImageHelper>();
+
+        ANGLE_TRY(stagingImage->init2DStaging(contextVk, renderer->getMemoryProperties(),
+                                              gl::Extents(sourceArea.width, sourceArea.height, 1),
+                                              destFormat, kTransferStagingImageFlags, layerCount));
+
+        vk::CommandBuffer *commandBuffer;
+        ANGLE_TRY(stagingImage->recordCommands(contextVk, &commandBuffer));
+
+        // Change the image layout before the transfer
+        stagingImage->changeLayout(VK_IMAGE_ASPECT_COLOR_BIT, vk::ImageLayout::TransferDst,
+                                   commandBuffer);
+
+        // Source's layout change should happen before the copy
+        srcImage->addReadDependency(stagingImage.get());
+
+        VkImageSubresourceLayers destSubresource = srcSubresource;
+        destSubresource.mipLevel                 = 0;
+
+        vk::ImageHelper::Copy(srcImage, stagingImage.get(), srcOffset, gl::Offset(), extents,
+                              srcSubresource, destSubresource, commandBuffer);
+
+        // Stage the copy for when the image storage is actually created.
+        mImage->stageSubresourceUpdateFromImage(stagingImage.release(), index, destOffset, extents);
+    }
+
+    return angle::Result::Continue;
+}
+
 angle::Result TextureVk::copySubImageImplWithDraw(ContextVk *contextVk,
                                                   const gl::ImageIndex &index,
                                                   const gl::Offset &destOffset,
@@ -493,7 +611,7 @@
 
         ANGLE_TRY(stagingImage->init2DStaging(contextVk, renderer->getMemoryProperties(),
                                               gl::Extents(sourceArea.width, sourceArea.height, 1),
-                                              destFormat, kStagingImageFlags, layerCount));
+                                              destFormat, kDrawStagingImageFlags, layerCount));
 
         params.destOffset[0] = 0;
         params.destOffset[1] = 0;
@@ -531,8 +649,8 @@
                                     GLenum internalFormat,
                                     const gl::Extents &size)
 {
-    ContextVk *contextVk             = GetAs<ContextVk>(context->getImplementation());
-    RendererVk *renderer             = contextVk->getRenderer();
+    ContextVk *contextVk = GetAs<ContextVk>(context->getImplementation());
+    RendererVk *renderer = contextVk->getRenderer();
 
     if (!mOwnsImage)
     {
diff --git a/src/libANGLE/renderer/vulkan/TextureVk.h b/src/libANGLE/renderer/vulkan/TextureVk.h
index eb1e825..fef1588 100644
--- a/src/libANGLE/renderer/vulkan/TextureVk.h
+++ b/src/libANGLE/renderer/vulkan/TextureVk.h
@@ -207,6 +207,14 @@
                                      bool unpackUnmultiplyAlpha,
                                      TextureVk *source);
 
+    angle::Result copySubImageImplWithTransfer(ContextVk *contextVk,
+                                               const gl::ImageIndex &index,
+                                               const gl::Offset &destOffset,
+                                               const vk::Format &destFormat,
+                                               size_t sourceLevel,
+                                               const gl::Rectangle &sourceArea,
+                                               vk::ImageHelper *srcImage);
+
     angle::Result copySubImageImplWithDraw(ContextVk *contextVk,
                                            const gl::ImageIndex &index,
                                            const gl::Offset &destOffset,
diff --git a/src/libANGLE/renderer/vulkan/vk_helpers.cpp b/src/libANGLE/renderer/vulkan/vk_helpers.cpp
index b41e699..d1ab17f 100644
--- a/src/libANGLE/renderer/vulkan/vk_helpers.cpp
+++ b/src/libANGLE/renderer/vulkan/vk_helpers.cpp
@@ -1401,12 +1401,12 @@
 {
     ASSERT(!valid());
 
-    mExtents    = extents;
-    mFormat     = &format;
-    mSamples    = samples;
+    mExtents       = extents;
+    mFormat        = &format;
+    mSamples       = samples;
     mCurrentLayout = ImageLayout::Undefined;
-    mLayerCount = 1;
-    mLevelCount = 1;
+    mLayerCount    = 1;
+    mLevelCount    = 1;
 
     mImage.setHandle(handle);
 }
@@ -1607,32 +1607,27 @@
                        const gl::Offset &srcOffset,
                        const gl::Offset &dstOffset,
                        const gl::Extents &copySize,
-                       VkImageAspectFlags aspectMask,
+                       const VkImageSubresourceLayers &srcSubresource,
+                       const VkImageSubresourceLayers &dstSubresource,
                        CommandBuffer *commandBuffer)
 {
     ASSERT(commandBuffer->valid() && srcImage->valid() && dstImage->valid());
 
-    srcImage->changeLayout(srcImage->getAspectFlags(), ImageLayout::TransferSrc, commandBuffer);
-    dstImage->changeLayout(dstImage->getAspectFlags(), ImageLayout::TransferDst, commandBuffer);
+    ASSERT(srcImage->getCurrentLayout() == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
+    ASSERT(dstImage->getCurrentLayout() == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
 
-    VkImageCopy region                   = {};
-    region.srcSubresource.aspectMask     = aspectMask;
-    region.srcSubresource.mipLevel       = 0;
-    region.srcSubresource.baseArrayLayer = 0;
-    region.srcSubresource.layerCount     = 1;
-    region.srcOffset.x                   = srcOffset.x;
-    region.srcOffset.y                   = srcOffset.y;
-    region.srcOffset.z                   = srcOffset.z;
-    region.dstSubresource.aspectMask     = aspectMask;
-    region.dstSubresource.mipLevel       = 0;
-    region.dstSubresource.baseArrayLayer = 0;
-    region.dstSubresource.layerCount     = 1;
-    region.dstOffset.x                   = dstOffset.x;
-    region.dstOffset.y                   = dstOffset.y;
-    region.dstOffset.z                   = dstOffset.z;
-    region.extent.width                  = copySize.width;
-    region.extent.height                 = copySize.height;
-    region.extent.depth                  = copySize.depth;
+    VkImageCopy region    = {};
+    region.srcSubresource = srcSubresource;
+    region.srcOffset.x    = srcOffset.x;
+    region.srcOffset.y    = srcOffset.y;
+    region.srcOffset.z    = srcOffset.z;
+    region.dstSubresource = dstSubresource;
+    region.dstOffset.x    = dstOffset.x;
+    region.dstOffset.y    = dstOffset.y;
+    region.dstOffset.z    = dstOffset.z;
+    region.extent.width   = copySize.width;
+    region.extent.height  = copySize.height;
+    region.extent.depth   = copySize.depth;
 
     commandBuffer->copyImage(srcImage->getImage(), srcImage->getCurrentLayout(),
                              dstImage->getImage(), dstImage->getCurrentLayout(), 1, &region);
diff --git a/src/libANGLE/renderer/vulkan/vk_helpers.h b/src/libANGLE/renderer/vulkan/vk_helpers.h
index 750a872..6206595 100644
--- a/src/libANGLE/renderer/vulkan/vk_helpers.h
+++ b/src/libANGLE/renderer/vulkan/vk_helpers.h
@@ -614,7 +614,8 @@
                      const gl::Offset &srcOffset,
                      const gl::Offset &dstOffset,
                      const gl::Extents &copySize,
-                     VkImageAspectFlags aspectMask,
+                     const VkImageSubresourceLayers &srcSubresources,
+                     const VkImageSubresourceLayers &dstSubresources,
                      CommandBuffer *commandBuffer);
 
     angle::Result generateMipmapsWithBlit(ContextVk *contextVk, GLuint maxLevel);