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 <enga@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Jiawei Shao <jiawei.shao@intel.com>
This commit is contained in:
Jiawei Shao 2019-08-27 00:07:09 +00:00 committed by Commit Bot service account
parent 27c3fc5bd3
commit b097b3100b
8 changed files with 317 additions and 46 deletions

View File

@ -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

View File

@ -28,6 +28,7 @@ namespace dawn_native {
NonzeroClearResourcesOnCreationForTesting,
AlwaysResolveIntoZeroLevelAndLayer,
LazyClearResourceOnFirstUse,
UseTemporaryBufferInCompressedTextureToTextureCopy,
EnumCount,
InvalidEnum = EnumCount,

View File

@ -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<Buffer> 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<Buffer>, 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,
&region);
@ -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, &region);
// 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,
&region);
} else {
RecordCopyImageWithTemporaryBuffer(recordingContext, src, dst,
copy->copySize);
}
} break;
case Command::BeginRenderPass: {

View File

@ -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;
};

View File

@ -19,6 +19,7 @@
#include <vector>
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<VkSemaphore> waitSemaphores = {};
std::vector<VkSemaphore> signalSemaphores = {};
// The internal buffers used in the workaround of texture-to-texture copies with compressed
// formats.
std::vector<Ref<Buffer>> tempBuffers;
};
}} // namespace dawn_native::vulkan

View File

@ -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<VulkanFunctions*>(&fn);
}

View File

@ -117,6 +117,8 @@ namespace dawn_native { namespace vulkan {
ResultOrError<VulkanDeviceKnobs> 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();

View File

@ -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, &copy);
@ -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<RGBA8> 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<CopyConfig, kTotalCopyCount> srcConfigs;
std::array<CopyConfig, kTotalCopyCount> 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<dawn::Extent3D, kTotalCopyCount> 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<dawn::Texture, kTotalCopyCount> bcSrcTextures;
std::array<dawn::Texture, kTotalCopyCount> 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<RGBA8> 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"}));