From c11a19145f1f5a8846d0123e22c261966949466c Mon Sep 17 00:00:00 2001 From: Jiawei Shao Date: Tue, 28 Jul 2020 01:58:50 +0000 Subject: [PATCH] Support buffer lazy initialization before CopyTextureToBuffer BUG=dawn:414 TEST=dawn_end2end_tests Change-Id: I5bdd6333029170d47ea240388e7b7d80750ae5d9 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/25643 Reviewed-by: Corentin Wallez Reviewed-by: Bryan Bernhart Reviewed-by: Austin Eng Commit-Queue: Jiawei Shao --- src/dawn_native/Buffer.cpp | 2 +- src/dawn_native/Buffer.h | 2 + src/dawn_native/CommandBuffer.cpp | 30 ++++ src/dawn_native/CommandBuffer.h | 3 + src/dawn_native/CommandValidation.h | 2 +- src/dawn_native/d3d12/BufferD3D12.cpp | 19 +++ src/dawn_native/d3d12/BufferD3D12.h | 2 + src/dawn_native/d3d12/CommandBufferD3D12.cpp | 2 + src/dawn_native/metal/BufferMTL.h | 2 + src/dawn_native/metal/BufferMTL.mm | 17 +++ src/dawn_native/metal/CommandBufferMTL.mm | 2 + src/dawn_native/opengl/BufferGL.cpp | 16 ++ src/dawn_native/opengl/BufferGL.h | 1 + src/dawn_native/opengl/CommandBufferGL.cpp | 2 + src/dawn_native/vulkan/BufferVk.cpp | 17 +++ src/dawn_native/vulkan/BufferVk.h | 2 + src/dawn_native/vulkan/CommandBufferVk.cpp | 3 + src/tests/end2end/BufferZeroInitTests.cpp | 149 ++++++++++++++++++- 18 files changed, 265 insertions(+), 8 deletions(-) diff --git a/src/dawn_native/Buffer.cpp b/src/dawn_native/Buffer.cpp index d1d360017d..0cd6eae175 100644 --- a/src/dawn_native/Buffer.cpp +++ b/src/dawn_native/Buffer.cpp @@ -15,6 +15,7 @@ #include "dawn_native/Buffer.h" #include "common/Assert.h" +#include "dawn_native/Commands.h" #include "dawn_native/Device.h" #include "dawn_native/DynamicUploader.h" #include "dawn_native/ErrorData.h" @@ -661,5 +662,4 @@ namespace dawn_native { bool BufferBase::IsFullBufferRange(uint64_t offset, uint64_t size) const { return offset == 0 && size == GetSize(); } - } // namespace dawn_native diff --git a/src/dawn_native/Buffer.h b/src/dawn_native/Buffer.h index 4d7e91b72e..12aaadeff4 100644 --- a/src/dawn_native/Buffer.h +++ b/src/dawn_native/Buffer.h @@ -25,6 +25,8 @@ namespace dawn_native { + struct CopyTextureToBufferCmd; + enum class MapType : uint32_t; MaybeError ValidateBufferDescriptor(DeviceBase* device, const BufferDescriptor* descriptor); diff --git a/src/dawn_native/CommandBuffer.cpp b/src/dawn_native/CommandBuffer.cpp index 177e93e1db..e1446ab756 100644 --- a/src/dawn_native/CommandBuffer.cpp +++ b/src/dawn_native/CommandBuffer.cpp @@ -15,6 +15,7 @@ #include "dawn_native/CommandBuffer.h" #include "common/BitSetIterator.h" +#include "dawn_native/Buffer.h" #include "dawn_native/CommandEncoder.h" #include "dawn_native/Commands.h" #include "dawn_native/Format.h" @@ -145,4 +146,33 @@ namespace dawn_native { } } + // TODO(jiawei.shao@intel.com): support copying with depth stencil textures + bool IsFullBufferOverwrittenInTextureToBufferCopy(const CopyTextureToBufferCmd* copy) { + ASSERT(copy != nullptr); + + if (copy->destination.offset > 0) { + return false; + } + + if (copy->destination.rowsPerImage > copy->copySize.height) { + return false; + } + + const TextureBase* texture = copy->source.texture.Get(); + const uint64_t copyTextureDataSizePerRow = copy->copySize.width / + texture->GetFormat().blockWidth * + texture->GetFormat().blockByteSize; + if (copy->destination.bytesPerRow > copyTextureDataSizePerRow) { + return false; + } + + const uint64_t overwrittenRangeSize = + copyTextureDataSizePerRow * (copy->copySize.height / texture->GetFormat().blockHeight) * + copy->copySize.depth; + if (copy->destination.buffer->GetSize() > overwrittenRangeSize) { + return false; + } + + return true; + } } // namespace dawn_native diff --git a/src/dawn_native/CommandBuffer.h b/src/dawn_native/CommandBuffer.h index ba9e40997f..375f102810 100644 --- a/src/dawn_native/CommandBuffer.h +++ b/src/dawn_native/CommandBuffer.h @@ -25,6 +25,7 @@ namespace dawn_native { struct BeginRenderPassCmd; + struct CopyTextureToBufferCmd; struct TextureCopy; class CommandBufferBase : public ObjectBase { @@ -48,6 +49,8 @@ namespace dawn_native { void LazyClearRenderPassAttachments(BeginRenderPassCmd* renderPass); + bool IsFullBufferOverwrittenInTextureToBufferCopy(const CopyTextureToBufferCmd* copy); + } // namespace dawn_native #endif // DAWNNATIVE_COMMANDBUFFER_H_ diff --git a/src/dawn_native/CommandValidation.h b/src/dawn_native/CommandValidation.h index 72d876d164..ae0464c0ac 100644 --- a/src/dawn_native/CommandValidation.h +++ b/src/dawn_native/CommandValidation.h @@ -53,7 +53,7 @@ namespace dawn_native { const Extent3D& copySize); MaybeError ValidateBufferCopyView(DeviceBase const* device, - const BufferCopyView& bufferCopyViewt); + const BufferCopyView& bufferCopyView); MaybeError ValidateTextureCopyView(DeviceBase const* device, const TextureCopyView& textureCopyView); diff --git a/src/dawn_native/d3d12/BufferD3D12.cpp b/src/dawn_native/d3d12/BufferD3D12.cpp index 878cea4950..877069b29f 100644 --- a/src/dawn_native/d3d12/BufferD3D12.cpp +++ b/src/dawn_native/d3d12/BufferD3D12.cpp @@ -17,6 +17,7 @@ #include "common/Assert.h" #include "common/Constants.h" #include "common/Math.h" +#include "dawn_native/CommandBuffer.h" #include "dawn_native/DynamicUploader.h" #include "dawn_native/d3d12/CommandRecordingContext.h" #include "dawn_native/d3d12/D3D12Error.h" @@ -371,6 +372,24 @@ namespace dawn_native { namespace d3d12 { return {}; } + MaybeError Buffer::EnsureDataInitializedAsDestination(CommandRecordingContext* commandContext, + const CopyTextureToBufferCmd* copy) { + // TODO(jiawei.shao@intel.com): check Toggle::LazyClearResourceOnFirstUse + // instead when buffer lazy initialization is completely supported. + if (IsDataInitialized() || + !GetDevice()->IsToggleEnabled(Toggle::LazyClearBufferOnFirstUse)) { + return {}; + } + + if (IsFullBufferOverwrittenInTextureToBufferCopy(copy)) { + SetIsDataInitialized(); + } else { + DAWN_TRY(InitializeToZero(commandContext)); + } + + return {}; + } + MaybeError Buffer::InitializeToZero(CommandRecordingContext* commandContext) { ASSERT(GetDevice()->IsToggleEnabled(Toggle::LazyClearBufferOnFirstUse)); ASSERT(!IsDataInitialized()); diff --git a/src/dawn_native/d3d12/BufferD3D12.h b/src/dawn_native/d3d12/BufferD3D12.h index ea1cde1de4..a2af099a79 100644 --- a/src/dawn_native/d3d12/BufferD3D12.h +++ b/src/dawn_native/d3d12/BufferD3D12.h @@ -48,6 +48,8 @@ namespace dawn_native { namespace d3d12 { MaybeError EnsureDataInitializedAsDestination(CommandRecordingContext* commandContext, uint64_t offset, uint64_t size); + MaybeError EnsureDataInitializedAsDestination(CommandRecordingContext* commandContext, + const CopyTextureToBufferCmd* copy); private: ~Buffer() override; diff --git a/src/dawn_native/d3d12/CommandBufferD3D12.cpp b/src/dawn_native/d3d12/CommandBufferD3D12.cpp index 5fbe2c62c2..8ee31ade37 100644 --- a/src/dawn_native/d3d12/CommandBufferD3D12.cpp +++ b/src/dawn_native/d3d12/CommandBufferD3D12.cpp @@ -724,6 +724,8 @@ namespace dawn_native { namespace d3d12 { Texture* texture = ToBackend(copy->source.texture.Get()); Buffer* buffer = ToBackend(copy->destination.buffer.Get()); + DAWN_TRY(buffer->EnsureDataInitializedAsDestination(commandContext, copy)); + ASSERT(texture->GetDimension() == wgpu::TextureDimension::e2D); SubresourceRange subresources = {copy->source.mipLevel, 1, copy->source.origin.z, copy->copySize.depth}; diff --git a/src/dawn_native/metal/BufferMTL.h b/src/dawn_native/metal/BufferMTL.h index 76c9e4c1c2..ad6ed5faac 100644 --- a/src/dawn_native/metal/BufferMTL.h +++ b/src/dawn_native/metal/BufferMTL.h @@ -35,6 +35,8 @@ namespace dawn_native { namespace metal { void EnsureDataInitializedAsDestination(CommandRecordingContext* commandContext, uint64_t offset, uint64_t size); + void EnsureDataInitializedAsDestination(CommandRecordingContext* commandContext, + const CopyTextureToBufferCmd* copy); private: using BufferBase::BufferBase; diff --git a/src/dawn_native/metal/BufferMTL.mm b/src/dawn_native/metal/BufferMTL.mm index e9f131ec3f..2c82a7cf72 100644 --- a/src/dawn_native/metal/BufferMTL.mm +++ b/src/dawn_native/metal/BufferMTL.mm @@ -15,6 +15,7 @@ #include "dawn_native/metal/BufferMTL.h" #include "common/Math.h" +#include "dawn_native/CommandBuffer.h" #include "dawn_native/metal/CommandRecordingContext.h" #include "dawn_native/metal/DeviceMTL.h" @@ -176,6 +177,22 @@ namespace dawn_native { namespace metal { } } + void Buffer::EnsureDataInitializedAsDestination(CommandRecordingContext* commandContext, + const CopyTextureToBufferCmd* copy) { + // TODO(jiawei.shao@intel.com): check Toggle::LazyClearResourceOnFirstUse + // instead when buffer lazy initialization is completely supported. + if (IsDataInitialized() || + !GetDevice()->IsToggleEnabled(Toggle::LazyClearBufferOnFirstUse)) { + return; + } + + if (IsFullBufferOverwrittenInTextureToBufferCopy(copy)) { + SetIsDataInitialized(); + } else { + InitializeToZero(commandContext); + } + } + void Buffer::InitializeToZero(CommandRecordingContext* commandContext) { ASSERT(GetDevice()->IsToggleEnabled(Toggle::LazyClearBufferOnFirstUse)); ASSERT(!IsDataInitialized()); diff --git a/src/dawn_native/metal/CommandBufferMTL.mm b/src/dawn_native/metal/CommandBufferMTL.mm index 517caf9688..74b64612ec 100644 --- a/src/dawn_native/metal/CommandBufferMTL.mm +++ b/src/dawn_native/metal/CommandBufferMTL.mm @@ -645,6 +645,8 @@ namespace dawn_native { namespace metal { Texture* texture = ToBackend(src.texture.Get()); Buffer* buffer = ToBackend(dst.buffer.Get()); + buffer->EnsureDataInitializedAsDestination(commandContext, copy); + texture->EnsureSubresourceContentInitialized( GetSubresourcesAffectedByCopy(src, copySize)); diff --git a/src/dawn_native/opengl/BufferGL.cpp b/src/dawn_native/opengl/BufferGL.cpp index 265989404e..6289c6edb0 100644 --- a/src/dawn_native/opengl/BufferGL.cpp +++ b/src/dawn_native/opengl/BufferGL.cpp @@ -14,6 +14,7 @@ #include "dawn_native/opengl/BufferGL.h" +#include "dawn_native/CommandBuffer.h" #include "dawn_native/opengl/DeviceGL.h" namespace dawn_native { namespace opengl { @@ -77,6 +78,21 @@ namespace dawn_native { namespace opengl { } } + void Buffer::EnsureDataInitializedAsDestination(const CopyTextureToBufferCmd* copy) { + // TODO(jiawei.shao@intel.com): check Toggle::LazyClearResourceOnFirstUse + // instead when buffer lazy initialization is completely supported. + if (IsDataInitialized() || + !GetDevice()->IsToggleEnabled(Toggle::LazyClearBufferOnFirstUse)) { + return; + } + + if (IsFullBufferOverwrittenInTextureToBufferCopy(copy)) { + SetIsDataInitialized(); + } else { + InitializeToZero(); + } + } + void Buffer::InitializeToZero() { ASSERT(GetDevice()->IsToggleEnabled(Toggle::LazyClearBufferOnFirstUse)); ASSERT(!IsDataInitialized()); diff --git a/src/dawn_native/opengl/BufferGL.h b/src/dawn_native/opengl/BufferGL.h index c1ba068029..8fcc7fcc34 100644 --- a/src/dawn_native/opengl/BufferGL.h +++ b/src/dawn_native/opengl/BufferGL.h @@ -31,6 +31,7 @@ namespace dawn_native { namespace opengl { void EnsureDataInitialized(); void EnsureDataInitializedAsDestination(uint64_t offset, uint64_t size); + void EnsureDataInitializedAsDestination(const CopyTextureToBufferCmd* copy); private: ~Buffer() override; diff --git a/src/dawn_native/opengl/CommandBufferGL.cpp b/src/dawn_native/opengl/CommandBufferGL.cpp index 252fbe621d..51b9e7c1ec 100644 --- a/src/dawn_native/opengl/CommandBufferGL.cpp +++ b/src/dawn_native/opengl/CommandBufferGL.cpp @@ -611,6 +611,8 @@ namespace dawn_native { namespace opengl { UNREACHABLE(); } + buffer->EnsureDataInitializedAsDestination(copy); + ASSERT(texture->GetDimension() == wgpu::TextureDimension::e2D); SubresourceRange subresources = {src.mipLevel, 1, src.origin.z, copy->copySize.depth}; diff --git a/src/dawn_native/vulkan/BufferVk.cpp b/src/dawn_native/vulkan/BufferVk.cpp index b26ee12fe1..e011455348 100644 --- a/src/dawn_native/vulkan/BufferVk.cpp +++ b/src/dawn_native/vulkan/BufferVk.cpp @@ -14,6 +14,7 @@ #include "dawn_native/vulkan/BufferVk.h" +#include "dawn_native/CommandBuffer.h" #include "dawn_native/vulkan/DeviceVk.h" #include "dawn_native/vulkan/FencedDeleter.h" #include "dawn_native/vulkan/ResourceHeapVk.h" @@ -329,6 +330,22 @@ namespace dawn_native { namespace vulkan { } } + void Buffer::EnsureDataInitializedAsDestination(CommandRecordingContext* recordingContext, + const CopyTextureToBufferCmd* copy) { + // TODO(jiawei.shao@intel.com): check Toggle::LazyClearResourceOnFirstUse + // instead when buffer lazy initialization is completely supported. + if (IsDataInitialized() || + !GetDevice()->IsToggleEnabled(Toggle::LazyClearBufferOnFirstUse)) { + return; + } + + if (IsFullBufferOverwrittenInTextureToBufferCopy(copy)) { + SetIsDataInitialized(); + } else { + InitializeToZero(recordingContext); + } + } + void Buffer::InitializeToZero(CommandRecordingContext* recordingContext) { ASSERT(GetDevice()->IsToggleEnabled(Toggle::LazyClearBufferOnFirstUse)); ASSERT(!IsDataInitialized()); diff --git a/src/dawn_native/vulkan/BufferVk.h b/src/dawn_native/vulkan/BufferVk.h index 062ff25960..14495d19d4 100644 --- a/src/dawn_native/vulkan/BufferVk.h +++ b/src/dawn_native/vulkan/BufferVk.h @@ -47,6 +47,8 @@ namespace dawn_native { namespace vulkan { void EnsureDataInitializedAsDestination(CommandRecordingContext* recordingContext, uint64_t offset, uint64_t size); + void EnsureDataInitializedAsDestination(CommandRecordingContext* recordingContext, + const CopyTextureToBufferCmd* copy); private: ~Buffer() override; diff --git a/src/dawn_native/vulkan/CommandBufferVk.cpp b/src/dawn_native/vulkan/CommandBufferVk.cpp index 1379221b50..ba48a35c21 100644 --- a/src/dawn_native/vulkan/CommandBufferVk.cpp +++ b/src/dawn_native/vulkan/CommandBufferVk.cpp @@ -484,6 +484,9 @@ namespace dawn_native { namespace vulkan { auto& src = copy->source; auto& dst = copy->destination; + ToBackend(dst.buffer) + ->EnsureDataInitializedAsDestination(recordingContext, copy); + VkBufferImageCopy region = ComputeBufferImageCopyRegion(dst, src, copy->copySize); VkImageSubresourceLayers subresource = region.imageSubresource; diff --git a/src/tests/end2end/BufferZeroInitTests.cpp b/src/tests/end2end/BufferZeroInitTests.cpp index c4a3809ac1..0f685e59ed 100644 --- a/src/tests/end2end/BufferZeroInitTests.cpp +++ b/src/tests/end2end/BufferZeroInitTests.cpp @@ -28,6 +28,19 @@ } \ } while (0) +namespace { + + struct BufferZeroInitInCopyT2BSpec { + wgpu::Extent3D textureSize; + uint64_t bufferOffset; + uint64_t extraBytes; + uint32_t bytesPerRow; + uint32_t rowsPerImage; + uint32_t lazyClearCount; + }; + +} // anonymous namespace + class BufferZeroInitTest : public DawnTest { public: wgpu::Buffer CreateBuffer(uint64_t size, @@ -60,8 +73,9 @@ class BufferZeroInitTest : public DawnTest { } } - wgpu::Texture CreateAndInitialize2DTexture(const wgpu::Extent3D& size, - wgpu::TextureFormat format) { + wgpu::Texture CreateAndInitializeTexture(const wgpu::Extent3D& size, + wgpu::TextureFormat format, + wgpu::Color color = {0.f, 0.f, 0.f, 0.f}) { wgpu::TextureDescriptor descriptor; descriptor.size = size; descriptor.format = format; @@ -70,14 +84,73 @@ class BufferZeroInitTest : public DawnTest { wgpu::Texture texture = device.CreateTexture(&descriptor); wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); - utils::ComboRenderPassDescriptor renderPassDescriptor({texture.CreateView()}); - wgpu::RenderPassEncoder renderPass = encoder.BeginRenderPass(&renderPassDescriptor); - renderPass.EndPass(); + + for (uint32_t arrayLayer = 0; arrayLayer < size.depth; ++arrayLayer) { + wgpu::TextureViewDescriptor viewDescriptor; + viewDescriptor.format = format; + viewDescriptor.dimension = wgpu::TextureViewDimension::e2D; + viewDescriptor.baseArrayLayer = arrayLayer; + viewDescriptor.arrayLayerCount = 1u; + + utils::ComboRenderPassDescriptor renderPassDescriptor( + {texture.CreateView(&viewDescriptor)}); + renderPassDescriptor.cColorAttachments[0].clearColor = color; + wgpu::RenderPassEncoder renderPass = encoder.BeginRenderPass(&renderPassDescriptor); + renderPass.EndPass(); + } + wgpu::CommandBuffer commandBuffer = encoder.Finish(); queue.Submit(1, &commandBuffer); return texture; } + + void TestBufferZeroInitInCopyTextureToBuffer(const BufferZeroInitInCopyT2BSpec& spec) { + constexpr wgpu::TextureFormat kTextureFormat = wgpu::TextureFormat::R32Float; + ASSERT(utils::GetTexelBlockSizeInBytes(kTextureFormat) * spec.textureSize.width % + kTextureBytesPerRowAlignment == + 0); + + constexpr wgpu::Color kClearColor = {0.5f, 0.5f, 0.5f, 0.5f}; + wgpu::Texture texture = + CreateAndInitializeTexture(spec.textureSize, kTextureFormat, kClearColor); + + const wgpu::TextureCopyView textureCopyView = + utils::CreateTextureCopyView(texture, 0, {0, 0, 0}); + + const uint64_t bufferSize = spec.bufferOffset + spec.extraBytes + + utils::RequiredBytesInCopy(spec.bytesPerRow, spec.rowsPerImage, + spec.textureSize, kTextureFormat); + wgpu::BufferDescriptor bufferDescriptor; + bufferDescriptor.size = bufferSize; + bufferDescriptor.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst; + wgpu::Buffer buffer = device.CreateBuffer(&bufferDescriptor); + const wgpu::BufferCopyView bufferCopyView = utils::CreateBufferCopyView( + buffer, spec.bufferOffset, spec.bytesPerRow, spec.rowsPerImage); + + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + encoder.CopyTextureToBuffer(&textureCopyView, &bufferCopyView, &spec.textureSize); + wgpu::CommandBuffer commandBuffer = encoder.Finish(); + EXPECT_LAZY_CLEAR(spec.lazyClearCount, queue.Submit(1, &commandBuffer)); + + const uint64_t expectedValueCount = bufferSize / sizeof(float); + std::vector expectedValues(expectedValueCount, 0.f); + + for (uint32_t slice = 0; slice < spec.textureSize.depth; ++slice) { + const uint64_t baseOffsetBytesPerSlice = + spec.bufferOffset + spec.bytesPerRow * spec.rowsPerImage * slice; + for (uint32_t y = 0; y < spec.textureSize.height; ++y) { + const uint64_t baseOffsetBytesPerRow = + baseOffsetBytesPerSlice + spec.bytesPerRow * y; + const uint64_t baseOffsetFloatCountPerRow = baseOffsetBytesPerRow / sizeof(float); + for (uint32_t x = 0; x < spec.textureSize.width; ++x) { + expectedValues[baseOffsetFloatCountPerRow + x] = 0.5f; + } + } + } + + EXPECT_BUFFER_FLOAT_RANGE_EQ(expectedValues.data(), buffer, 0, expectedValues.size()); + } }; // Test that calling writeBuffer to overwrite the entire buffer doesn't need to lazily initialize @@ -405,7 +478,7 @@ TEST_P(BufferZeroInitTest, CopyBufferToTexture) { constexpr wgpu::TextureFormat kTextureFormat = wgpu::TextureFormat::R32Uint; - wgpu::Texture texture = CreateAndInitialize2DTexture(kTextureSize, kTextureFormat); + wgpu::Texture texture = CreateAndInitializeTexture(kTextureSize, kTextureFormat); const wgpu::TextureCopyView textureCopyView = utils::CreateTextureCopyView(texture, 0, {0, 0, 0}); @@ -461,6 +534,70 @@ TEST_P(BufferZeroInitTest, CopyBufferToTexture) { } } +// Test that the code path of CopyTextureToBuffer clears the destination buffer correctly when it is +// the first use of the buffer and the texture is a 2D non-array texture. +TEST_P(BufferZeroInitTest, Copy2DTextureToBuffer) { + constexpr wgpu::Extent3D kTextureSize = {64u, 8u, 1u}; + + // bytesPerRow == texelBlockSizeInBytes * copySize.width && bytesPerRow * copySize.height == + // buffer.size + { + TestBufferZeroInitInCopyTextureToBuffer( + {kTextureSize, 0u, 0u, kTextureBytesPerRowAlignment, kTextureSize.height, 0u}); + } + + // bytesPerRow > texelBlockSizeInBytes * copySize.width + { + constexpr uint64_t kBytesPerRow = kTextureBytesPerRowAlignment * 2; + TestBufferZeroInitInCopyTextureToBuffer( + {kTextureSize, 0u, 0u, kBytesPerRow, kTextureSize.height, 1u}); + } + + // bufferOffset > 0 + { + constexpr uint64_t kBufferOffset = 16u; + TestBufferZeroInitInCopyTextureToBuffer({kTextureSize, kBufferOffset, 0u, + kTextureBytesPerRowAlignment, kTextureSize.height, + 1u}); + } + + // bytesPerRow * copySize.height < buffer.size + { + constexpr uint64_t kExtraBufferSize = 16u; + TestBufferZeroInitInCopyTextureToBuffer({kTextureSize, 0u, kExtraBufferSize, + kTextureBytesPerRowAlignment, kTextureSize.height, + 1u}); + } +} + +// Test that the code path of CopyTextureToBuffer clears the destination buffer correctly when it is +// the first use of the buffer and the texture is a 2D array texture. +TEST_P(BufferZeroInitTest, Copy2DArrayTextureToBuffer) { + constexpr wgpu::Extent3D kTextureSize = {64u, 4u, 3u}; + + // bytesPerRow == texelBlockSizeInBytes * copySize.width && rowsPerImage == copySize.height && + // bytesPerRow * (rowsPerImage * (copySize.depth - 1) + copySize.height) == buffer.size + { + TestBufferZeroInitInCopyTextureToBuffer( + {kTextureSize, 0u, 0u, kTextureBytesPerRowAlignment, kTextureSize.height, 0u}); + } + + // rowsPerImage > copySize.height + { + constexpr uint64_t kRowsPerImage = kTextureSize.height + 1u; + TestBufferZeroInitInCopyTextureToBuffer( + {kTextureSize, 0u, 0u, kTextureBytesPerRowAlignment, kRowsPerImage, 1u}); + } + + // bytesPerRow * rowsPerImage * copySize.depth < buffer.size + { + constexpr uint64_t kExtraBufferSize = 16u; + TestBufferZeroInitInCopyTextureToBuffer({kTextureSize, 0u, kExtraBufferSize, + kTextureBytesPerRowAlignment, kTextureSize.height, + 1u}); + } +} + DAWN_INSTANTIATE_TEST(BufferZeroInitTest, D3D12Backend({"nonzero_clear_resources_on_creation_for_testing", "lazy_clear_buffer_on_first_use"}),