From b097b3100bb7adacbc4b6c490e3c8f4b320427d1 Mon Sep 17 00:00:00 2001 From: Jiawei Shao Date: Tue, 27 Aug 2019 00:07:09 +0000 Subject: [PATCH] Vulkan: workaround texture-to-texture copy issue with compressed formats This patch adds the workaround for the Vulkan SPEC issue in the T2T copies with compressed formats when the parameter "extent" fitting in one subresource but not fitting in another. You can get the detail of the issue through the following link: https://github.com/KhronosGroup/Vulkan-Docs/issues/1005 This patch implements the workaround for this issue by splitting the affected T2T copy into a T2B and a B2T copy with an internal buffer. BUG=dawn:42 TEST=dawn_end2end_tests Change-Id: I29c48da0b5ff85f9860839a82733e8c1c43acfc6 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/10020 Reviewed-by: Austin Eng Reviewed-by: Corentin Wallez Commit-Queue: Jiawei Shao --- src/dawn_native/Toggles.cpp | 11 +- src/dawn_native/Toggles.h | 1 + src/dawn_native/vulkan/CommandBufferVk.cpp | 138 ++++++++++--- src/dawn_native/vulkan/CommandBufferVk.h | 5 + .../vulkan/CommandRecordingContext.h | 5 + src/dawn_native/vulkan/DeviceVk.cpp | 7 + src/dawn_native/vulkan/DeviceVk.h | 2 + .../end2end/CompressedTextureFormatTests.cpp | 194 ++++++++++++++++-- 8 files changed, 317 insertions(+), 46 deletions(-) diff --git a/src/dawn_native/Toggles.cpp b/src/dawn_native/Toggles.cpp index 382f77bc28..bbb69a328a 100644 --- a/src/dawn_native/Toggles.cpp +++ b/src/dawn_native/Toggles.cpp @@ -56,7 +56,16 @@ namespace dawn_native { {"lazy_clear_resource_on_first_use", "Clears resource to zero on first usage. This initializes the resource " "so that no dirty bits from recycled memory is present in the new resource.", - "https://bugs.chromium.org/p/dawn/issues/detail?id=145"}}}}; + "https://bugs.chromium.org/p/dawn/issues/detail?id=145"}}, + {Toggle::UseTemporaryBufferInCompressedTextureToTextureCopy, + {"use_temporary_buffer_in_texture_to_texture_copy", + "Split texture-to-texture copy into two copies: copy from source texture into a " + "temporary buffer, and copy from the temporary buffer into the destination texture " + "when copying between compressed textures that don't have block-aligned sizes. This " + "workaround is enabled by default on all Vulkan drivers to solve an issue in the " + "Vulkan SPEC about the texture-to-texture copies with compressed formats. See #1005 " + "(https://github.com/KhronosGroup/Vulkan-Docs/issues/1005) for more details.", + "https://bugs.chromium.org/p/dawn/issues/detail?id=42"}}}}; } // anonymous namespace diff --git a/src/dawn_native/Toggles.h b/src/dawn_native/Toggles.h index 080bf2efe2..73da01eacd 100644 --- a/src/dawn_native/Toggles.h +++ b/src/dawn_native/Toggles.h @@ -28,6 +28,7 @@ namespace dawn_native { NonzeroClearResourcesOnCreationForTesting, AlwaysResolveIntoZeroLevelAndLayer, LazyClearResourceOnFirstUse, + UseTemporaryBufferInCompressedTextureToTextureCopy, EnumCount, InvalidEnum = EnumCount, diff --git a/src/dawn_native/vulkan/CommandBufferVk.cpp b/src/dawn_native/vulkan/CommandBufferVk.cpp index bc25ea240b..636f44ea88 100644 --- a/src/dawn_native/vulkan/CommandBufferVk.cpp +++ b/src/dawn_native/vulkan/CommandBufferVk.cpp @@ -44,6 +44,16 @@ namespace dawn_native { namespace vulkan { } } + bool HasSameTextureCopyExtent(const TextureCopy& srcCopy, + const TextureCopy& dstCopy, + const Extent3D& copySize) { + Extent3D imageExtentSrc = ComputeTextureCopyExtent(srcCopy, copySize); + Extent3D imageExtentDst = ComputeTextureCopyExtent(dstCopy, copySize); + return imageExtentSrc.width == imageExtentDst.width && + imageExtentSrc.height == imageExtentDst.height && + imageExtentSrc.depth == imageExtentDst.depth; + } + VkImageCopy ComputeImageCopyRegion(const TextureCopy& srcCopy, const TextureCopy& dstCopy, const Extent3D& copySize) { @@ -70,11 +80,8 @@ namespace dawn_native { namespace vulkan { region.dstOffset.y = dstCopy.origin.y; region.dstOffset.z = dstCopy.origin.z; - Extent3D imageExtentDst = ComputeTextureCopyExtent(dstCopy, copySize); - // TODO(jiawei.shao@intel.com): add workaround for the case that imageExtentSrc is not - // equal to imageExtentDst. For example when copySize fits in the virtual size of the - // source image but does not fit in the one of the destination image. - Extent3D imageExtent = imageExtentDst; + ASSERT(HasSameTextureCopyExtent(srcCopy, dstCopy, copySize)); + Extent3D imageExtent = ComputeTextureCopyExtent(dstCopy, copySize); region.extent.width = imageExtent.width; region.extent.height = imageExtent.height; region.extent.depth = imageExtent.depth; @@ -303,6 +310,64 @@ namespace dawn_native { namespace vulkan { FreeCommands(&mCommands); } + void CommandBuffer::RecordCopyImageWithTemporaryBuffer( + CommandRecordingContext* recordingContext, + const TextureCopy& srcCopy, + const TextureCopy& dstCopy, + const Extent3D& copySize) { + ASSERT(srcCopy.texture->GetFormat().format == dstCopy.texture->GetFormat().format); + dawn_native::Format format = srcCopy.texture->GetFormat(); + ASSERT(copySize.width % format.blockWidth == 0); + ASSERT(copySize.height % format.blockHeight == 0); + + // Create the temporary buffer. Note that We don't need to respect WebGPU's 256 alignment + // because it isn't a hard constraint in Vulkan. + uint64_t tempBufferSize = + (copySize.width / format.blockWidth * copySize.height / format.blockHeight) * + format.blockByteSize; + BufferDescriptor tempBufferDescriptor; + tempBufferDescriptor.size = tempBufferSize; + tempBufferDescriptor.usage = dawn::BufferUsageBit::CopySrc | dawn::BufferUsageBit::CopyDst; + + Device* device = ToBackend(GetDevice()); + Ref tempBuffer = ToBackend(device->CreateBuffer(&tempBufferDescriptor)); + // After device->CreateBuffer(&tempBufferDescriptor) is called, the ref count of the buffer + // object is 1, and after assigning it to a Ref, the ref count of it will be 2. To + // prevent memory leak, we must reduce the ref count here to ensure the ref count of this + // object to be 0 after all the Ref<> objects that contain the buffer object are released. + tempBuffer->Release(); + + BufferCopy tempBufferCopy; + tempBufferCopy.buffer = tempBuffer.Get(); + tempBufferCopy.imageHeight = copySize.height; + tempBufferCopy.offset = 0; + tempBufferCopy.rowPitch = copySize.width / format.blockWidth * format.blockByteSize; + + VkCommandBuffer commands = recordingContext->commandBuffer; + VkImage srcImage = ToBackend(srcCopy.texture)->GetHandle(); + VkImage dstImage = ToBackend(dstCopy.texture)->GetHandle(); + + tempBuffer->TransitionUsageNow(recordingContext, dawn::BufferUsageBit::CopyDst); + VkBufferImageCopy srcToTempBufferRegion = + ComputeBufferImageCopyRegion(tempBufferCopy, srcCopy, copySize); + + // The Dawn CopySrc usage is always mapped to GENERAL + device->fn.CmdCopyImageToBuffer(commands, srcImage, VK_IMAGE_LAYOUT_GENERAL, + tempBuffer->GetHandle(), 1, &srcToTempBufferRegion); + + tempBuffer->TransitionUsageNow(recordingContext, dawn::BufferUsageBit::CopySrc); + VkBufferImageCopy tempBufferToDstRegion = + ComputeBufferImageCopyRegion(tempBufferCopy, dstCopy, copySize); + + // Dawn guarantees dstImage be in the TRANSFER_DST_OPTIMAL layout after the + // copy command. + device->fn.CmdCopyBufferToImage(commands, tempBuffer->GetHandle(), dstImage, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, + &tempBufferToDstRegion); + + recordingContext->tempBuffers.emplace_back(tempBuffer); + } + void CommandBuffer::RecordCommands(CommandRecordingContext* recordingContext) { Device* device = ToBackend(GetDevice()); VkCommandBuffer commands = recordingContext->commandBuffer; @@ -378,8 +443,8 @@ namespace dawn_native { namespace vulkan { VkBuffer srcBuffer = ToBackend(src.buffer)->GetHandle(); VkImage dstImage = ToBackend(dst.texture)->GetHandle(); - // The image is written to so the Dawn guarantees make sure it is in the - // TRANSFER_DST_OPTIMAL layout + // Dawn guarantees dstImage be in the TRANSFER_DST_OPTIMAL layout after the + // copy command. device->fn.CmdCopyBufferToImage(commands, srcBuffer, dstImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); @@ -417,36 +482,57 @@ namespace dawn_native { namespace vulkan { TextureCopy& src = copy->source; TextureCopy& dst = copy->destination; - VkImageCopy region = ComputeImageCopyRegion(src, dst, copy->copySize); - VkImageSubresourceLayers dstSubresource = region.dstSubresource; - VkImageSubresourceLayers srcSubresource = region.srcSubresource; - ToBackend(src.texture) - ->EnsureSubresourceContentInitialized(recordingContext, - srcSubresource.mipLevel, 1, - srcSubresource.baseArrayLayer, 1); + ->EnsureSubresourceContentInitialized(recordingContext, src.mipLevel, 1, + src.arrayLayer, 1); if (IsCompleteSubresourceCopiedTo(dst.texture.Get(), copy->copySize, - dstSubresource.mipLevel)) { + dst.mipLevel)) { // Since destination texture has been overwritten, it has been "initialized" - dst.texture->SetIsSubresourceContentInitialized( - dstSubresource.mipLevel, 1, dstSubresource.baseArrayLayer, 1); + dst.texture->SetIsSubresourceContentInitialized(dst.mipLevel, 1, + dst.arrayLayer, 1); } else { ToBackend(dst.texture) - ->EnsureSubresourceContentInitialized(recordingContext, - dstSubresource.mipLevel, 1, - dstSubresource.baseArrayLayer, 1); + ->EnsureSubresourceContentInitialized(recordingContext, dst.mipLevel, 1, + dst.arrayLayer, 1); } + ToBackend(src.texture) ->TransitionUsageNow(recordingContext, dawn::TextureUsageBit::CopySrc); ToBackend(dst.texture) ->TransitionUsageNow(recordingContext, dawn::TextureUsageBit::CopyDst); - VkImage srcImage = ToBackend(src.texture)->GetHandle(); - VkImage dstImage = ToBackend(dst.texture)->GetHandle(); - // The dstImage is written to so the Dawn guarantees make sure it is in the - // TRANSFER_DST_OPTIMAL layout - device->fn.CmdCopyImage(commands, srcImage, VK_IMAGE_LAYOUT_GENERAL, dstImage, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + // 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 + // the image subresource, when the extent that fits in the copy region of one + // subresource but does not fit in the one of another subresource, we will fail + // to find a valid extent to satisfy the requirements on both source and + // destination image subresource. For example, when the source is the first + // level of a 16x16 texture in BC format, and the destination is the third level + // of a 60x60 texture in the same format, neither 16x16 nor 15x15 is valid as + // the extent of vkCmdCopyImage. + // Our workaround for this issue is replacing the texture-to-texture copy with + // one texture-to-buffer copy and one buffer-to-texture copy. + bool copyUsingTemporaryBuffer = + device->IsToggleEnabled( + Toggle::UseTemporaryBufferInCompressedTextureToTextureCopy) && + src.texture->GetFormat().isCompressed && + !HasSameTextureCopyExtent(src, dst, copy->copySize); + + if (!copyUsingTemporaryBuffer) { + VkImage srcImage = ToBackend(src.texture)->GetHandle(); + VkImage dstImage = ToBackend(dst.texture)->GetHandle(); + VkImageCopy region = ComputeImageCopyRegion(src, dst, copy->copySize); + + // Dawn guarantees dstImage be in the TRANSFER_DST_OPTIMAL layout after the + // copy command. + device->fn.CmdCopyImage(commands, srcImage, VK_IMAGE_LAYOUT_GENERAL, + dstImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, + ®ion); + } else { + RecordCopyImageWithTemporaryBuffer(recordingContext, src, dst, + copy->copySize); + } + } break; case Command::BeginRenderPass: { diff --git a/src/dawn_native/vulkan/CommandBufferVk.h b/src/dawn_native/vulkan/CommandBufferVk.h index 6025eef04b..c6d15c2289 100644 --- a/src/dawn_native/vulkan/CommandBufferVk.h +++ b/src/dawn_native/vulkan/CommandBufferVk.h @@ -22,6 +22,7 @@ namespace dawn_native { struct BeginRenderPassCmd; + struct TextureCopy; } // namespace dawn_native namespace dawn_native { namespace vulkan { @@ -40,6 +41,10 @@ namespace dawn_native { namespace vulkan { void RecordComputePass(CommandRecordingContext* recordingContext); void RecordRenderPass(CommandRecordingContext* recordingContext, BeginRenderPassCmd* renderPass); + void RecordCopyImageWithTemporaryBuffer(CommandRecordingContext* recordingContext, + const TextureCopy& srcCopy, + const TextureCopy& dstCopy, + const Extent3D& copySize); CommandIterator mCommands; }; diff --git a/src/dawn_native/vulkan/CommandRecordingContext.h b/src/dawn_native/vulkan/CommandRecordingContext.h index 5de9087397..025de69469 100644 --- a/src/dawn_native/vulkan/CommandRecordingContext.h +++ b/src/dawn_native/vulkan/CommandRecordingContext.h @@ -19,6 +19,7 @@ #include namespace dawn_native { namespace vulkan { + class Buffer; // Used to track operations that are handled after recording. // Currently only tracks semaphores, but may be used to do barrier coalescing in the future. @@ -26,6 +27,10 @@ namespace dawn_native { namespace vulkan { VkCommandBuffer commandBuffer = VK_NULL_HANDLE; std::vector waitSemaphores = {}; std::vector signalSemaphores = {}; + + // The internal buffers used in the workaround of texture-to-texture copies with compressed + // formats. + std::vector> tempBuffers; }; }} // namespace dawn_native::vulkan diff --git a/src/dawn_native/vulkan/DeviceVk.cpp b/src/dawn_native/vulkan/DeviceVk.cpp index 128cfdd52e..f5774f6640 100644 --- a/src/dawn_native/vulkan/DeviceVk.cpp +++ b/src/dawn_native/vulkan/DeviceVk.cpp @@ -44,6 +44,7 @@ namespace dawn_native { namespace vulkan { Device::Device(Adapter* adapter, const DeviceDescriptor* descriptor) : DeviceBase(adapter, descriptor) { + InitTogglesFromDriver(); if (descriptor != nullptr) { ApplyToggleOverrides(descriptor); } @@ -436,6 +437,12 @@ namespace dawn_native { namespace vulkan { fn.GetDeviceQueue(mVkDevice, mQueueFamily, 0, &mQueue); } + void Device::InitTogglesFromDriver() { + // TODO(jiawei.shao@intel.com): tighten this workaround when this issue is fixed in both + // Vulkan SPEC and drivers. + SetToggle(Toggle::UseTemporaryBufferInCompressedTextureToTextureCopy, true); + } + VulkanFunctions* Device::GetMutableFunctions() { return const_cast(&fn); } diff --git a/src/dawn_native/vulkan/DeviceVk.h b/src/dawn_native/vulkan/DeviceVk.h index 2aba4289df..c3ff3522e2 100644 --- a/src/dawn_native/vulkan/DeviceVk.h +++ b/src/dawn_native/vulkan/DeviceVk.h @@ -117,6 +117,8 @@ namespace dawn_native { namespace vulkan { ResultOrError CreateDevice(VkPhysicalDevice physicalDevice); void GatherQueueFromDevice(); + void InitTogglesFromDriver(); + // To make it easier to use fn it is a public const member. However // the Device is allowed to mutate them through these private methods. VulkanFunctions* GetMutableFunctions(); diff --git a/src/tests/end2end/CompressedTextureFormatTests.cpp b/src/tests/end2end/CompressedTextureFormatTests.cpp index f8b8a4d18f..d13f6168dd 100644 --- a/src/tests/end2end/CompressedTextureFormatTests.cpp +++ b/src/tests/end2end/CompressedTextureFormatTests.cpp @@ -239,21 +239,29 @@ class CompressedTextureBCFormatTest : public DawnTest { return bcTexture; } - dawn::Texture CreateTextureFromTexture(dawn::Texture srcTexture, - CopyConfig srcConfig, - CopyConfig dstConfig) { - dawn::Texture dstTexture = device.CreateTexture(&dstConfig.textureDescriptor); - + // Record a texture-to-texture copy command into command encoder without finishing the encoding. + void RecordTextureToTextureCopy(dawn::CommandEncoder encoder, + dawn::Texture srcTexture, + dawn::Texture dstTexture, + CopyConfig srcConfig, + CopyConfig dstConfig) { dawn::TextureCopyView textureCopyViewSrc = utils::CreateTextureCopyView(srcTexture, srcConfig.viewMipmapLevel, srcConfig.viewArrayLayer, srcConfig.copyOrigin3D); dawn::TextureCopyView textureCopyViewDst = utils::CreateTextureCopyView(dstTexture, dstConfig.viewMipmapLevel, dstConfig.viewArrayLayer, dstConfig.copyOrigin3D); - - dawn::CommandEncoder encoder = device.CreateCommandEncoder(); encoder.CopyTextureToTexture(&textureCopyViewSrc, &textureCopyViewDst, &dstConfig.copyExtent3D); + } + + dawn::Texture CreateTextureFromTexture(dawn::Texture srcTexture, + CopyConfig srcConfig, + CopyConfig dstConfig) { + dawn::Texture dstTexture = device.CreateTexture(&dstConfig.textureDescriptor); + + dawn::CommandEncoder encoder = device.CreateCommandEncoder(); + RecordTextureToTextureCopy(encoder, srcTexture, dstTexture, srcConfig, dstConfig); dawn::CommandBuffer copy = encoder.Finish(); queue.Submit(1, ©); @@ -615,8 +623,8 @@ TEST_P(CompressedTextureBCFormatTest, CopyWholeTextureSubResourceIntoNonZeroMipm // required in the copies. const dawn::Extent3D kVirtualSize = GetVirtualSizeAtLevel(config); const dawn::Extent3D kPhysicalSize = GetPhysicalSizeAtLevel(config); - ASSERT(kVirtualSize.width % kBCBlockWidthInTexels != 0); - ASSERT(kVirtualSize.height % kBCBlockHeightInTexels != 0); + ASSERT_NE(0u, kVirtualSize.width % kBCBlockWidthInTexels); + ASSERT_NE(0u, kVirtualSize.height % kBCBlockHeightInTexels); config.copyExtent3D = kPhysicalSize; for (dawn::TextureFormat format : kBCFormats) { @@ -645,14 +653,18 @@ TEST_P(CompressedTextureBCFormatTest, CopyWholeTextureSubResourceIntoNonZeroMipm } } -// Test BC format texture-to-texture partial copies. -TEST_P(CompressedTextureBCFormatTest, CopyPartofTextureSubResourceIntoNonZeroMipmapLevel) { +// Test BC format texture-to-texture partial copies where the physical size of the destination +// subresource is different from its virtual size. +TEST_P(CompressedTextureBCFormatTest, CopyIntoSubresourceWithPhysicalSizeNotEqualToVirtualSize) { DAWN_SKIP_TEST_IF(!IsBCFormatSupported()); // TODO(jiawei.shao@intel.com): add workaround on the T2T copies where Extent3D fits in one - // subresource and does not fit in another one on Vulkan and OpenGL. Currently this test causes - // an error if Vulkan validation layer is enabled. - DAWN_SKIP_TEST_IF(IsVulkan() || IsOpenGL()); + // subresource and does not fit in another one on OpenGL. + DAWN_SKIP_TEST_IF(IsOpenGL()); + + // TODO(jiawei.shao@intel.com): find out why this test is flaky on Windows Intel Vulkan wire + // bots. + DAWN_SKIP_TEST_IF(IsIntel() && IsVulkan() && IsWindows() && UsesWire()); CopyConfig srcConfig; srcConfig.textureDescriptor.size = {60, 60, 1}; @@ -669,14 +681,14 @@ TEST_P(CompressedTextureBCFormatTest, CopyPartofTextureSubResourceIntoNonZeroMip // The actual size of the texture at mipmap level == 2 is not a multiple of 4, paddings are // required in the copies. const dawn::Extent3D kDstVirtualSize = GetVirtualSizeAtLevel(dstConfig); - ASSERT(kDstVirtualSize.width % kBCBlockWidthInTexels != 0); - ASSERT(kDstVirtualSize.height % kBCBlockHeightInTexels != 0); + ASSERT_NE(0u, kDstVirtualSize.width % kBCBlockWidthInTexels); + ASSERT_NE(0u, kDstVirtualSize.height % kBCBlockHeightInTexels); const dawn::Extent3D kDstPhysicalSize = GetPhysicalSizeAtLevel(dstConfig); srcConfig.copyExtent3D = dstConfig.copyExtent3D = kDstPhysicalSize; - ASSERT(srcConfig.copyOrigin3D.x + srcConfig.copyExtent3D.width < kSrcVirtualSize.width); - ASSERT(srcConfig.copyOrigin3D.y + srcConfig.copyExtent3D.height < kSrcVirtualSize.height); + ASSERT_LT(srcConfig.copyOrigin3D.x + srcConfig.copyExtent3D.width, kSrcVirtualSize.width); + ASSERT_LT(srcConfig.copyOrigin3D.y + srcConfig.copyExtent3D.height, kSrcVirtualSize.height); for (dawn::TextureFormat format : kBCFormats) { // Create bcTextureSrc as the source texture and initialize it with pre-prepared BC @@ -705,6 +717,148 @@ TEST_P(CompressedTextureBCFormatTest, CopyPartofTextureSubResourceIntoNonZeroMip } } +// Test BC format texture-to-texture partial copies where the physical size of the source +// subresource is different from its virtual size. +TEST_P(CompressedTextureBCFormatTest, CopyFromSubresourceWithPhysicalSizeNotEqualToVirtualSize) { + DAWN_SKIP_TEST_IF(!IsBCFormatSupported()); + + // TODO(jiawei.shao@intel.com): add workaround on the T2T copies where Extent3D fits in one + // subresource and does not fit in another one on OpenGL. + DAWN_SKIP_TEST_IF(IsOpenGL()); + + // TODO(jiawei.shao@intel.com): find out why this test is flaky on Windows Intel Vulkan wire + // bots. + DAWN_SKIP_TEST_IF(IsIntel() && IsVulkan() && IsWindows() && UsesWire()); + + CopyConfig srcConfig; + srcConfig.textureDescriptor.size = {60, 60, 1}; + constexpr uint32_t kMipmapLevelCount = 3; + srcConfig.textureDescriptor.mipLevelCount = kMipmapLevelCount; + srcConfig.viewMipmapLevel = srcConfig.textureDescriptor.mipLevelCount - 1; + + // The actual size of the texture at mipmap level == 2 is not a multiple of 4, paddings are + // required in the copies. + const dawn::Extent3D kSrcVirtualSize = GetVirtualSizeAtLevel(srcConfig); + ASSERT_NE(0u, kSrcVirtualSize.width % kBCBlockWidthInTexels); + ASSERT_NE(0u, kSrcVirtualSize.height % kBCBlockHeightInTexels); + + CopyConfig dstConfig; + dstConfig.textureDescriptor.size = {16, 16, 1}; + dstConfig.viewMipmapLevel = dstConfig.textureDescriptor.mipLevelCount - 1; + + const dawn::Extent3D kDstVirtualSize = GetVirtualSizeAtLevel(dstConfig); + srcConfig.copyExtent3D = dstConfig.copyExtent3D = kDstVirtualSize; + + ASSERT_GT(srcConfig.copyOrigin3D.x + srcConfig.copyExtent3D.width, kSrcVirtualSize.width); + ASSERT_GT(srcConfig.copyOrigin3D.y + srcConfig.copyExtent3D.height, kSrcVirtualSize.height); + + for (dawn::TextureFormat format : kBCFormats) { + srcConfig.textureDescriptor.format = dstConfig.textureDescriptor.format = format; + srcConfig.textureDescriptor.usage = + dawn::TextureUsageBit::CopySrc | dawn::TextureUsageBit::CopyDst; + dstConfig.textureDescriptor.usage = kDefaultBCFormatTextureUsage; + + // Create bcTextureSrc as the source texture and initialize it with pre-prepared BC + // compressed data. + dawn::Texture bcTextureSrc = CreateTextureWithCompressedData(srcConfig); + + // Create bcTexture and copy from the content in bcTextureSrc into it. + dawn::Texture bcTextureDst = CreateTextureFromTexture(bcTextureSrc, srcConfig, dstConfig); + + // Verify if we can use bcTexture as sampled textures correctly. + dawn::BindGroup bindGroup = CreateBindGroupForTest( + bcTextureDst, format, dstConfig.viewArrayLayer, dstConfig.viewMipmapLevel); + dawn::RenderPipeline renderPipeline = CreateRenderPipelineForTest(); + + std::vector expectedData = GetExpectedData(format, kDstVirtualSize); + VerifyCompressedTexturePixelValues(renderPipeline, bindGroup, kDstVirtualSize, + dstConfig.copyOrigin3D, kDstVirtualSize, expectedData); + } +} + +// Test recording two BC format texture-to-texture partial copies where the physical size of the +// source subresource is different from its virtual size into one command buffer. +TEST_P(CompressedTextureBCFormatTest, MultipleCopiesWithPhysicalSizeNotEqualToVirtualSize) { + DAWN_SKIP_TEST_IF(!IsBCFormatSupported()); + + // TODO(jiawei.shao@intel.com): add workaround on the T2T copies where Extent3D fits in one + // subresource and does not fit in another one on OpenGL. + DAWN_SKIP_TEST_IF(IsOpenGL()); + + // TODO(jiawei.shao@intel.com): find out why this test is flaky on Windows Intel Vulkan wire + // bots. + DAWN_SKIP_TEST_IF(IsIntel() && IsVulkan() && IsWindows() && UsesWire()); + + constexpr uint32_t kTotalCopyCount = 2; + std::array srcConfigs; + std::array dstConfigs; + + constexpr uint32_t kSrcMipmapLevelCount0 = 3; + srcConfigs[0].textureDescriptor.size = {60, 60, 1}; + srcConfigs[0].textureDescriptor.mipLevelCount = kSrcMipmapLevelCount0; + srcConfigs[0].viewMipmapLevel = srcConfigs[0].textureDescriptor.mipLevelCount - 1; + dstConfigs[0].textureDescriptor.size = {16, 16, 1}; + dstConfigs[0].viewMipmapLevel = dstConfigs[0].textureDescriptor.mipLevelCount - 1; + srcConfigs[0].copyExtent3D = dstConfigs[0].copyExtent3D = GetVirtualSizeAtLevel(dstConfigs[0]); + const dawn::Extent3D kSrcVirtualSize0 = GetVirtualSizeAtLevel(srcConfigs[0]); + ASSERT_NE(0u, kSrcVirtualSize0.width % kBCBlockWidthInTexels); + ASSERT_NE(0u, kSrcVirtualSize0.height % kBCBlockHeightInTexels); + + constexpr uint32_t kDstMipmapLevelCount1 = 4; + srcConfigs[1].textureDescriptor.size = {8, 8, 1}; + srcConfigs[1].viewMipmapLevel = srcConfigs[1].textureDescriptor.mipLevelCount - 1; + dstConfigs[1].textureDescriptor.size = {56, 56, 1}; + dstConfigs[1].textureDescriptor.mipLevelCount = kDstMipmapLevelCount1; + dstConfigs[1].viewMipmapLevel = dstConfigs[1].textureDescriptor.mipLevelCount - 1; + srcConfigs[1].copyExtent3D = dstConfigs[1].copyExtent3D = GetVirtualSizeAtLevel(srcConfigs[1]); + + std::array dstVirtualSizes; + for (uint32_t i = 0; i < kTotalCopyCount; ++i) { + dstVirtualSizes[i] = GetVirtualSizeAtLevel(dstConfigs[i]); + } + ASSERT_NE(0u, dstVirtualSizes[1].width % kBCBlockWidthInTexels); + ASSERT_NE(0u, dstVirtualSizes[1].height % kBCBlockHeightInTexels); + + for (dawn::TextureFormat format : kBCFormats) { + std::array bcSrcTextures; + std::array bcDstTextures; + + dawn::CommandEncoder encoder = device.CreateCommandEncoder(); + for (uint32_t i = 0; i < kTotalCopyCount; ++i) { + srcConfigs[i].textureDescriptor.format = dstConfigs[i].textureDescriptor.format = + format; + srcConfigs[i].textureDescriptor.usage = + dawn::TextureUsageBit::CopySrc | dawn::TextureUsageBit::CopyDst; + dstConfigs[i].textureDescriptor.usage = kDefaultBCFormatTextureUsage; + + // Create bcSrcTextures as the source textures and initialize them with pre-prepared BC + // compressed data. + bcSrcTextures[i] = CreateTextureWithCompressedData(srcConfigs[i]); + bcDstTextures[i] = device.CreateTexture(&dstConfigs[i].textureDescriptor); + + RecordTextureToTextureCopy(encoder, bcSrcTextures[i], bcDstTextures[i], srcConfigs[i], + dstConfigs[i]); + } + + dawn::CommandBuffer commandBuffer = encoder.Finish(); + queue.Submit(1, &commandBuffer); + + dawn::RenderPipeline renderPipeline = CreateRenderPipelineForTest(); + + for (uint32_t i = 0; i < kTotalCopyCount; ++i) { + // Verify if we can use bcDstTextures as sampled textures correctly. + dawn::BindGroup bindGroup0 = + CreateBindGroupForTest(bcDstTextures[i], format, dstConfigs[i].viewArrayLayer, + dstConfigs[i].viewMipmapLevel); + + std::vector expectedData = GetExpectedData(format, dstVirtualSizes[i]); + VerifyCompressedTexturePixelValues(renderPipeline, bindGroup0, dstVirtualSizes[i], + dstConfigs[i].copyOrigin3D, dstVirtualSizes[i], + expectedData); + } + } +} + // Test the special case of the B2T copies on the D3D12 backend that the buffer offset and texture // extent exactly fit the RowPitch. TEST_P(CompressedTextureBCFormatTest, BufferOffsetAndExtentFitRowPitch) { @@ -911,4 +1065,6 @@ DAWN_INSTANTIATE_TEST(CompressedTextureBCFormatTest, D3D12Backend, MetalBackend, OpenGLBackend, - VulkanBackend); + VulkanBackend, + ForceWorkarounds(VulkanBackend, + {"use_temporary_buffer_in_texture_to_texture_copy"}));