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 ©Size,
- 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, ®ion);
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 ©Size,
- VkImageAspectFlags aspectMask,
+ const VkImageSubresourceLayers &srcSubresources,
+ const VkImageSubresourceLayers &dstSubresources,
CommandBuffer *commandBuffer);
angle::Result generateMipmapsWithBlit(ContextVk *contextVk, GLuint maxLevel);