From dab10eae8a8257b24d7475eaf473b0158b01ea24 Mon Sep 17 00:00:00 2001 From: Jiawei Shao Date: Thu, 9 Jul 2020 09:15:22 +0000 Subject: [PATCH] Implement buffer lazy initialization before CopyBufferToBuffer This patch implements buffer lazy initialization before CopyBufferToBuffer() behind the toggle LazyClearBufferOnFirstUse. - If the source buffer is not initialized, it will be cleared to 0 before CopyBufferToBuffer(). - If the destination buffer is not initialized and the copy doesn't overwrite the whole buffer, it will be cleared to 0 before CopyBufferToBuffer(), otherwise the buffer shouldn't be cleared. BUG=dawn:414 TEST=dawn_end2end_tests Change-Id: I3d0512c6376a1ed8928e86f8e56fefebc16910fa Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/24360 Commit-Queue: Jiawei Shao Reviewed-by: Corentin Wallez Reviewed-by: Austin Eng --- src/dawn_native/d3d12/BufferD3D12.cpp | 36 +++- src/dawn_native/d3d12/BufferD3D12.h | 6 +- src/dawn_native/d3d12/CommandBufferD3D12.cpp | 4 + src/dawn_native/d3d12/DeviceD3D12.cpp | 11 +- src/dawn_native/metal/BufferMTL.h | 6 +- src/dawn_native/metal/BufferMTL.mm | 30 +++- src/dawn_native/metal/CommandBufferMTL.mm | 5 + src/dawn_native/metal/DeviceMTL.mm | 13 +- src/dawn_native/opengl/BufferGL.cpp | 30 +++- src/dawn_native/opengl/BufferGL.h | 5 +- src/dawn_native/opengl/CommandBufferGL.cpp | 4 + src/dawn_native/opengl/QueueGL.cpp | 11 +- src/dawn_native/vulkan/BufferVk.cpp | 33 +++- src/dawn_native/vulkan/BufferVk.h | 6 +- src/dawn_native/vulkan/CommandBufferVk.cpp | 4 + src/dawn_native/vulkan/DeviceVk.cpp | 12 +- src/tests/end2end/BufferZeroInitTests.cpp | 173 +++++++++++++++++++ 17 files changed, 339 insertions(+), 50 deletions(-) diff --git a/src/dawn_native/d3d12/BufferD3D12.cpp b/src/dawn_native/d3d12/BufferD3D12.cpp index 6ff9f0b550..2521e93431 100644 --- a/src/dawn_native/d3d12/BufferD3D12.cpp +++ b/src/dawn_native/d3d12/BufferD3D12.cpp @@ -313,10 +313,44 @@ namespace dawn_native { namespace d3d12 { return mResourceAllocation.GetInfo().mMethod == allocationMethod; } - MaybeError Buffer::ClearBufferContentsToZero(CommandRecordingContext* commandContext) { + MaybeError Buffer::EnsureDataInitialized(CommandRecordingContext* commandContext) { + // TODO(jiawei.shao@intel.com): check Toggle::LazyClearResourceOnFirstUse + // instead when buffer lazy initialization is completely supported. + if (IsDataInitialized() || + !GetDevice()->IsToggleEnabled(Toggle::LazyClearBufferOnFirstUse)) { + return {}; + } + + DAWN_TRY(InitializeToZero(commandContext)); + + return {}; + } + + MaybeError Buffer::EnsureDataInitializedAsDestination(CommandRecordingContext* commandContext, + uint64_t offset, + uint64_t size) { + // TODO(jiawei.shao@intel.com): check Toggle::LazyClearResourceOnFirstUse + // instead when buffer lazy initialization is completely supported. + if (IsDataInitialized() || + !GetDevice()->IsToggleEnabled(Toggle::LazyClearBufferOnFirstUse)) { + return {}; + } + + if (IsFullBufferRange(offset, size)) { + SetIsDataInitialized(); + } else { + DAWN_TRY(InitializeToZero(commandContext)); + } + + return {}; + } + + MaybeError Buffer::InitializeToZero(CommandRecordingContext* commandContext) { ASSERT(GetDevice()->IsToggleEnabled(Toggle::LazyClearBufferOnFirstUse)); ASSERT(!IsDataInitialized()); + // TODO(jiawei.shao@intel.com): skip initializing the buffer when it is created on a heap + // that has already been zero initialized. DAWN_TRY(ClearBuffer(commandContext, uint8_t(0u))); SetIsDataInitialized(); GetDevice()->IncrementLazyClearCountForTesting(); diff --git a/src/dawn_native/d3d12/BufferD3D12.h b/src/dawn_native/d3d12/BufferD3D12.h index 33e0580a0f..bb255587d6 100644 --- a/src/dawn_native/d3d12/BufferD3D12.h +++ b/src/dawn_native/d3d12/BufferD3D12.h @@ -44,7 +44,10 @@ namespace dawn_native { namespace d3d12 { bool CheckAllocationMethodForTesting(AllocationMethod allocationMethod) const; bool CheckIsResidentForTesting() const; - MaybeError ClearBufferContentsToZero(CommandRecordingContext* commandContext); + MaybeError EnsureDataInitialized(CommandRecordingContext* commandContext); + MaybeError EnsureDataInitializedAsDestination(CommandRecordingContext* commandContext, + uint64_t offset, + uint64_t size); private: ~Buffer() override; @@ -63,6 +66,7 @@ namespace dawn_native { namespace d3d12 { D3D12_RESOURCE_BARRIER* barrier, wgpu::BufferUsage newUsage); + MaybeError InitializeToZero(CommandRecordingContext* commandContext); MaybeError ClearBuffer(CommandRecordingContext* commandContext, uint8_t clearValue); ResourceHeapAllocation mResourceAllocation; diff --git a/src/dawn_native/d3d12/CommandBufferD3D12.cpp b/src/dawn_native/d3d12/CommandBufferD3D12.cpp index 0b8b124788..cd30dc53ab 100644 --- a/src/dawn_native/d3d12/CommandBufferD3D12.cpp +++ b/src/dawn_native/d3d12/CommandBufferD3D12.cpp @@ -582,6 +582,10 @@ namespace dawn_native { namespace d3d12 { Buffer* srcBuffer = ToBackend(copy->source.Get()); Buffer* dstBuffer = ToBackend(copy->destination.Get()); + DAWN_TRY(srcBuffer->EnsureDataInitialized(commandContext)); + DAWN_TRY(dstBuffer->EnsureDataInitializedAsDestination( + commandContext, copy->destinationOffset, copy->size)); + srcBuffer->TrackUsageAndTransitionNow(commandContext, wgpu::BufferUsage::CopySrc); dstBuffer->TrackUsageAndTransitionNow(commandContext, diff --git a/src/dawn_native/d3d12/DeviceD3D12.cpp b/src/dawn_native/d3d12/DeviceD3D12.cpp index 0a647ad24f..01e7ead5b8 100644 --- a/src/dawn_native/d3d12/DeviceD3D12.cpp +++ b/src/dawn_native/d3d12/DeviceD3D12.cpp @@ -337,15 +337,8 @@ namespace dawn_native { namespace d3d12 { Buffer* dstBuffer = ToBackend(destination); - // TODO(jiawei.shao@intel.com): use Toggle::LazyClearResourceOnFirstUse when the support of - // buffer lazy initialization is completed. - if (IsToggleEnabled(Toggle::LazyClearBufferOnFirstUse) && !dstBuffer->IsDataInitialized()) { - if (dstBuffer->IsFullBufferRange(destinationOffset, size)) { - dstBuffer->SetIsDataInitialized(); - } else { - DAWN_TRY(dstBuffer->ClearBufferContentsToZero(commandRecordingContext)); - } - } + DAWN_TRY(dstBuffer->EnsureDataInitializedAsDestination(commandRecordingContext, + destinationOffset, size)); CopyFromStagingToBufferImpl(commandRecordingContext, source, sourceOffset, destination, destinationOffset, size); diff --git a/src/dawn_native/metal/BufferMTL.h b/src/dawn_native/metal/BufferMTL.h index 74d2cdb126..38c403c7f3 100644 --- a/src/dawn_native/metal/BufferMTL.h +++ b/src/dawn_native/metal/BufferMTL.h @@ -31,7 +31,10 @@ namespace dawn_native { namespace metal { const BufferDescriptor* descriptor); id GetMTLBuffer() const; - void ClearBufferContentsToZero(CommandRecordingContext* commandContext); + void EnsureDataInitialized(CommandRecordingContext* commandContext); + void EnsureDataInitializedAsDestination(CommandRecordingContext* commandContext, + uint64_t offset, + uint64_t size); private: using BufferBase::BufferBase; @@ -47,6 +50,7 @@ namespace dawn_native { namespace metal { bool IsMapWritable() const override; MaybeError MapAtCreationImpl() override; + void InitializeToZero(CommandRecordingContext* commandContext); void ClearBuffer(CommandRecordingContext* commandContext, uint8_t clearValue); id mMtlBuffer = nil; diff --git a/src/dawn_native/metal/BufferMTL.mm b/src/dawn_native/metal/BufferMTL.mm index c14a5a2a3e..5b10a8dd03 100644 --- a/src/dawn_native/metal/BufferMTL.mm +++ b/src/dawn_native/metal/BufferMTL.mm @@ -134,7 +134,35 @@ namespace dawn_native { namespace metal { mMtlBuffer = nil; } - void Buffer::ClearBufferContentsToZero(CommandRecordingContext* commandContext) { + void Buffer::EnsureDataInitialized(CommandRecordingContext* commandContext) { + // TODO(jiawei.shao@intel.com): check Toggle::LazyClearResourceOnFirstUse + // instead when buffer lazy initialization is completely supported. + if (IsDataInitialized() || + !GetDevice()->IsToggleEnabled(Toggle::LazyClearBufferOnFirstUse)) { + return; + } + + InitializeToZero(commandContext); + } + + void Buffer::EnsureDataInitializedAsDestination(CommandRecordingContext* commandContext, + uint64_t offset, + uint64_t size) { + // TODO(jiawei.shao@intel.com): check Toggle::LazyClearResourceOnFirstUse + // instead when buffer lazy initialization is completely supported. + if (IsDataInitialized() || + !GetDevice()->IsToggleEnabled(Toggle::LazyClearBufferOnFirstUse)) { + return; + } + + if (IsFullBufferRange(offset, size)) { + 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 4efc7a6df3..0f923f4116 100644 --- a/src/dawn_native/metal/CommandBufferMTL.mm +++ b/src/dawn_native/metal/CommandBufferMTL.mm @@ -721,6 +721,11 @@ namespace dawn_native { namespace metal { case Command::CopyBufferToBuffer: { CopyBufferToBufferCmd* copy = mCommands.NextCommand(); + ToBackend(copy->source)->EnsureDataInitialized(commandContext); + ToBackend(copy->destination) + ->EnsureDataInitializedAsDestination(commandContext, + copy->destinationOffset, copy->size); + [commandContext->EnsureBlit() copyFromBuffer:ToBackend(copy->source)->GetMTLBuffer() sourceOffset:copy->sourceOffset diff --git a/src/dawn_native/metal/DeviceMTL.mm b/src/dawn_native/metal/DeviceMTL.mm index 01b63b2ba9..07f11cd27a 100644 --- a/src/dawn_native/metal/DeviceMTL.mm +++ b/src/dawn_native/metal/DeviceMTL.mm @@ -254,16 +254,9 @@ namespace dawn_native { namespace metal { // this function. ASSERT(size != 0); - // TODO(jiawei.shao@intel.com): use Toggle::LazyClearResourceOnFirstUse when the support of - // buffer lazy initialization is completed. - if (IsToggleEnabled(Toggle::LazyClearBufferOnFirstUse) && - !destination->IsDataInitialized()) { - if (destination->IsFullBufferRange(destinationOffset, size)) { - destination->SetIsDataInitialized(); - } else { - ToBackend(destination)->ClearBufferContentsToZero(GetPendingCommandContext()); - } - } + ToBackend(destination) + ->EnsureDataInitializedAsDestination(GetPendingCommandContext(), destinationOffset, + size); id uploadBuffer = ToBackend(source)->GetBufferHandle(); id buffer = ToBackend(destination)->GetMTLBuffer(); diff --git a/src/dawn_native/opengl/BufferGL.cpp b/src/dawn_native/opengl/BufferGL.cpp index c56cc55945..c0bd7f3405 100644 --- a/src/dawn_native/opengl/BufferGL.cpp +++ b/src/dawn_native/opengl/BufferGL.cpp @@ -51,7 +51,33 @@ namespace dawn_native { namespace opengl { return std::max(GetSize(), uint64_t(4u)); } - void Buffer::ClearBufferContentsToZero() { + void Buffer::EnsureDataInitialized() { + // TODO(jiawei.shao@intel.com): check Toggle::LazyClearResourceOnFirstUse + // instead when buffer lazy initialization is completely supported. + if (IsDataInitialized() || + !GetDevice()->IsToggleEnabled(Toggle::LazyClearBufferOnFirstUse)) { + return; + } + + InitializeToZero(); + } + + void Buffer::EnsureDataInitializedAsDestination(uint64_t offset, uint64_t size) { + // TODO(jiawei.shao@intel.com): check Toggle::LazyClearResourceOnFirstUse + // instead when buffer lazy initialization is completely supported. + if (IsDataInitialized() || + !GetDevice()->IsToggleEnabled(Toggle::LazyClearBufferOnFirstUse)) { + return; + } + + if (IsFullBufferRange(offset, size)) { + SetIsDataInitialized(); + } else { + InitializeToZero(); + } + } + + void Buffer::InitializeToZero() { ASSERT(GetDevice()->IsToggleEnabled(Toggle::LazyClearBufferOnFirstUse)); ASSERT(!IsDataInitialized()); @@ -61,9 +87,9 @@ namespace dawn_native { namespace opengl { const std::vector clearValues(size, 0u); device->gl.BindBuffer(GL_ARRAY_BUFFER, mBuffer); device->gl.BufferSubData(GL_ARRAY_BUFFER, 0, size, clearValues.data()); + device->IncrementLazyClearCountForTesting(); SetIsDataInitialized(); - device->IncrementLazyClearCountForTesting(); } bool Buffer::IsMapWritable() const { diff --git a/src/dawn_native/opengl/BufferGL.h b/src/dawn_native/opengl/BufferGL.h index 121b1d6a39..9bc870e381 100644 --- a/src/dawn_native/opengl/BufferGL.h +++ b/src/dawn_native/opengl/BufferGL.h @@ -29,7 +29,8 @@ namespace dawn_native { namespace opengl { GLuint GetHandle() const; - void ClearBufferContentsToZero(); + void EnsureDataInitialized(); + void EnsureDataInitializedAsDestination(uint64_t offset, uint64_t size); private: ~Buffer() override; @@ -44,6 +45,8 @@ namespace dawn_native { namespace opengl { void* GetMappedPointerImpl() override; uint64_t GetAppliedSize() const; + void InitializeToZero(); + GLuint mBuffer = 0; void* mMappedData = nullptr; }; diff --git a/src/dawn_native/opengl/CommandBufferGL.cpp b/src/dawn_native/opengl/CommandBufferGL.cpp index 4180eb5a04..3689277efa 100644 --- a/src/dawn_native/opengl/CommandBufferGL.cpp +++ b/src/dawn_native/opengl/CommandBufferGL.cpp @@ -493,6 +493,10 @@ namespace dawn_native { namespace opengl { case Command::CopyBufferToBuffer: { CopyBufferToBufferCmd* copy = mCommands.NextCommand(); + ToBackend(copy->source)->EnsureDataInitialized(); + ToBackend(copy->destination) + ->EnsureDataInitializedAsDestination(copy->destinationOffset, copy->size); + gl.BindBuffer(GL_PIXEL_PACK_BUFFER, ToBackend(copy->source)->GetHandle()); gl.BindBuffer(GL_PIXEL_UNPACK_BUFFER, ToBackend(copy->destination)->GetHandle()); diff --git a/src/dawn_native/opengl/QueueGL.cpp b/src/dawn_native/opengl/QueueGL.cpp index 2547f17950..88d1575cf4 100644 --- a/src/dawn_native/opengl/QueueGL.cpp +++ b/src/dawn_native/opengl/QueueGL.cpp @@ -44,16 +44,7 @@ namespace dawn_native { namespace opengl { size_t size) { const OpenGLFunctions& gl = ToBackend(GetDevice())->gl; - // TODO(jiawei.shao@intel.com): use Toggle::LazyClearResourceOnFirstUse when the support of - // buffer lazy initialization is completed. - if (GetDevice()->IsToggleEnabled(Toggle::LazyClearBufferOnFirstUse) && - !buffer->IsDataInitialized()) { - if (buffer->IsFullBufferRange(bufferOffset, size)) { - buffer->SetIsDataInitialized(); - } else { - ToBackend(buffer)->ClearBufferContentsToZero(); - } - } + ToBackend(buffer)->EnsureDataInitializedAsDestination(bufferOffset, size); gl.BindBuffer(GL_ARRAY_BUFFER, ToBackend(buffer)->GetHandle()); gl.BufferSubData(GL_ARRAY_BUFFER, bufferOffset, size, data); diff --git a/src/dawn_native/vulkan/BufferVk.cpp b/src/dawn_native/vulkan/BufferVk.cpp index 11f56f5133..417532451d 100644 --- a/src/dawn_native/vulkan/BufferVk.cpp +++ b/src/dawn_native/vulkan/BufferVk.cpp @@ -278,14 +278,41 @@ namespace dawn_native { namespace vulkan { } } - void Buffer::ClearBufferContentsToZero(CommandRecordingContext* recordingContext) { + void Buffer::EnsureDataInitialized(CommandRecordingContext* recordingContext) { + // TODO(jiawei.shao@intel.com): check Toggle::LazyClearResourceOnFirstUse + // instead when buffer lazy initialization is completely supported. + if (IsDataInitialized() || + !GetDevice()->IsToggleEnabled(Toggle::LazyClearBufferOnFirstUse)) { + return; + } + + InitializeToZero(recordingContext); + } + + void Buffer::EnsureDataInitializedAsDestination(CommandRecordingContext* recordingContext, + uint64_t offset, + uint64_t size) { + // TODO(jiawei.shao@intel.com): check Toggle::LazyClearResourceOnFirstUse + // instead when buffer lazy initialization is completely supported. + if (IsDataInitialized() || + !GetDevice()->IsToggleEnabled(Toggle::LazyClearBufferOnFirstUse)) { + return; + } + + if (IsFullBufferRange(offset, size)) { + SetIsDataInitialized(); + } else { + InitializeToZero(recordingContext); + } + } + + void Buffer::InitializeToZero(CommandRecordingContext* recordingContext) { ASSERT(GetDevice()->IsToggleEnabled(Toggle::LazyClearBufferOnFirstUse)); ASSERT(!IsDataInitialized()); ClearBuffer(recordingContext, 0u); - - SetIsDataInitialized(); GetDevice()->IncrementLazyClearCountForTesting(); + SetIsDataInitialized(); } void Buffer::ClearBuffer(CommandRecordingContext* recordingContext, uint32_t clearValue) { diff --git a/src/dawn_native/vulkan/BufferVk.h b/src/dawn_native/vulkan/BufferVk.h index 6fb8592730..14be75742d 100644 --- a/src/dawn_native/vulkan/BufferVk.h +++ b/src/dawn_native/vulkan/BufferVk.h @@ -43,12 +43,16 @@ namespace dawn_native { namespace vulkan { VkPipelineStageFlags* srcStages, VkPipelineStageFlags* dstStages); - void ClearBufferContentsToZero(CommandRecordingContext* recordingContext); + void EnsureDataInitialized(CommandRecordingContext* recordingContext); + void EnsureDataInitializedAsDestination(CommandRecordingContext* recordingContext, + uint64_t offset, + uint64_t size); private: ~Buffer() override; using BufferBase::BufferBase; MaybeError Initialize(); + void InitializeToZero(CommandRecordingContext* recordingContext); void ClearBuffer(CommandRecordingContext* recordingContext, uint32_t clearValue); // Dawn API diff --git a/src/dawn_native/vulkan/CommandBufferVk.cpp b/src/dawn_native/vulkan/CommandBufferVk.cpp index 3a2bec9049..dcbbf0dd7e 100644 --- a/src/dawn_native/vulkan/CommandBufferVk.cpp +++ b/src/dawn_native/vulkan/CommandBufferVk.cpp @@ -424,6 +424,10 @@ namespace dawn_native { namespace vulkan { Buffer* srcBuffer = ToBackend(copy->source.Get()); Buffer* dstBuffer = ToBackend(copy->destination.Get()); + srcBuffer->EnsureDataInitialized(recordingContext); + dstBuffer->EnsureDataInitializedAsDestination( + recordingContext, copy->destinationOffset, copy->size); + srcBuffer->TransitionUsageNow(recordingContext, wgpu::BufferUsage::CopySrc); dstBuffer->TransitionUsageNow(recordingContext, wgpu::BufferUsage::CopyDst); diff --git a/src/dawn_native/vulkan/DeviceVk.cpp b/src/dawn_native/vulkan/DeviceVk.cpp index db135771d1..da17b6ceb5 100644 --- a/src/dawn_native/vulkan/DeviceVk.cpp +++ b/src/dawn_native/vulkan/DeviceVk.cpp @@ -592,16 +592,8 @@ namespace dawn_native { namespace vulkan { CommandRecordingContext* recordingContext = GetPendingRecordingContext(); - // TODO(jiawei.shao@intel.com): use Toggle::LazyClearResourceOnFirstUse when the support of - // buffer lazy initialization is completed. - if (IsToggleEnabled(Toggle::LazyClearBufferOnFirstUse) && - !destination->IsDataInitialized()) { - if (destination->IsFullBufferRange(destinationOffset, size)) { - destination->SetIsDataInitialized(); - } else { - ToBackend(destination)->ClearBufferContentsToZero(recordingContext); - } - } + ToBackend(destination) + ->EnsureDataInitializedAsDestination(recordingContext, destinationOffset, size); // Insert memory barrier to ensure host write operations are made visible before // copying from the staging buffer. However, this barrier can be removed (see note below). diff --git a/src/tests/end2end/BufferZeroInitTests.cpp b/src/tests/end2end/BufferZeroInitTests.cpp index 5392e49364..28e43f5c10 100644 --- a/src/tests/end2end/BufferZeroInitTests.cpp +++ b/src/tests/end2end/BufferZeroInitTests.cpp @@ -87,6 +87,179 @@ TEST_P(BufferZeroInitTest, WriteBufferToSubBuffer) { } } +// Test that the code path of CopyBufferToBuffer clears the source buffer correctly when it is the +// first use of the source buffer. +TEST_P(BufferZeroInitTest, CopyBufferToBufferSource) { + constexpr uint64_t kBufferSize = 16u; + constexpr wgpu::BufferUsage kBufferUsage = + wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst; + wgpu::BufferDescriptor bufferDescriptor; + bufferDescriptor.size = kBufferSize; + bufferDescriptor.usage = kBufferUsage; + + constexpr std::array kInitialData = { + {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}}; + + wgpu::Buffer dstBuffer = + utils::CreateBufferFromData(device, kInitialData.data(), kBufferSize, kBufferUsage); + + constexpr std::array kExpectedData = {{0, 0, 0, 0}}; + + // Full copy from the source buffer + { + wgpu::Buffer srcBuffer = device.CreateBuffer(&bufferDescriptor); + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + encoder.CopyBufferToBuffer(srcBuffer, 0, dstBuffer, 0, kBufferSize); + wgpu::CommandBuffer commandBuffer = encoder.Finish(); + + EXPECT_LAZY_CLEAR(1u, queue.Submit(1, &commandBuffer)); + EXPECT_BUFFER_U32_RANGE_EQ(kExpectedData.data(), srcBuffer, 0, + kBufferSize / sizeof(uint32_t)); + } + + // Partial copy from the source buffer + // srcOffset == 0 + { + constexpr uint64_t kSrcOffset = 0; + constexpr uint64_t kCopySize = kBufferSize / 2; + + wgpu::Buffer srcBuffer = device.CreateBuffer(&bufferDescriptor); + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + encoder.CopyBufferToBuffer(srcBuffer, kSrcOffset, dstBuffer, 0, kCopySize); + wgpu::CommandBuffer commandBuffer = encoder.Finish(); + + EXPECT_LAZY_CLEAR(1u, queue.Submit(1, &commandBuffer)); + EXPECT_BUFFER_U32_RANGE_EQ(kExpectedData.data(), srcBuffer, 0, + kBufferSize / sizeof(uint32_t)); + } + + // srcOffset > 0 and srcOffset + copySize == srcBufferSize + { + constexpr uint64_t kSrcOffset = kBufferSize / 2; + constexpr uint64_t kCopySize = kBufferSize - kSrcOffset; + + wgpu::Buffer srcBuffer = device.CreateBuffer(&bufferDescriptor); + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + encoder.CopyBufferToBuffer(srcBuffer, kSrcOffset, dstBuffer, 0, kCopySize); + wgpu::CommandBuffer commandBuffer = encoder.Finish(); + + EXPECT_LAZY_CLEAR(1u, queue.Submit(1, &commandBuffer)); + EXPECT_BUFFER_U32_RANGE_EQ(kExpectedData.data(), srcBuffer, 0, + kBufferSize / sizeof(uint32_t)); + } + + // srcOffset > 0 and srcOffset + copySize < srcBufferSize + { + constexpr uint64_t kSrcOffset = kBufferSize / 4; + constexpr uint64_t kCopySize = kBufferSize / 2; + + wgpu::Buffer srcBuffer = device.CreateBuffer(&bufferDescriptor); + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + encoder.CopyBufferToBuffer(srcBuffer, kSrcOffset, dstBuffer, 0, kCopySize); + wgpu::CommandBuffer commandBuffer = encoder.Finish(); + + EXPECT_LAZY_CLEAR(1u, queue.Submit(1, &commandBuffer)); + EXPECT_BUFFER_U32_RANGE_EQ(kExpectedData.data(), srcBuffer, 0, + kBufferSize / sizeof(uint32_t)); + } +} + +// Test that the code path of CopyBufferToBuffer clears the destination buffer correctly when it is +// the first use of the destination buffer. +TEST_P(BufferZeroInitTest, CopyBufferToBufferDestination) { + constexpr uint64_t kBufferSize = 16u; + constexpr wgpu::BufferUsage kBufferUsage = + wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst; + wgpu::BufferDescriptor bufferDescriptor; + bufferDescriptor.size = kBufferSize; + bufferDescriptor.usage = kBufferUsage; + + const std::array kInitialData = { + {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}}; + wgpu::Buffer srcBuffer = + utils::CreateBufferFromData(device, kInitialData.data(), kBufferSize, kBufferUsage); + + // Full copy from the source buffer doesn't need lazy initialization at all. + { + wgpu::Buffer dstBuffer = device.CreateBuffer(&bufferDescriptor); + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + encoder.CopyBufferToBuffer(srcBuffer, 0, dstBuffer, 0, kBufferSize); + wgpu::CommandBuffer commandBuffer = encoder.Finish(); + + EXPECT_LAZY_CLEAR(0u, queue.Submit(1, &commandBuffer)); + + EXPECT_BUFFER_U32_RANGE_EQ(reinterpret_cast(kInitialData.data()), + dstBuffer, 0, kBufferSize / sizeof(uint32_t)); + } + + // Partial copy from the source buffer needs lazy initialization. + // offset == 0 + { + constexpr uint32_t kDstOffset = 0; + constexpr uint32_t kCopySize = kBufferSize / 2; + + wgpu::Buffer dstBuffer = device.CreateBuffer(&bufferDescriptor); + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + encoder.CopyBufferToBuffer(srcBuffer, 0, dstBuffer, kDstOffset, kCopySize); + wgpu::CommandBuffer commandBuffer = encoder.Finish(); + + EXPECT_LAZY_CLEAR(1u, queue.Submit(1, &commandBuffer)); + + std::array expectedData; + expectedData.fill(0); + for (uint32_t index = kDstOffset; index < kDstOffset + kCopySize; ++index) { + expectedData[index] = kInitialData[index - kDstOffset]; + } + + EXPECT_BUFFER_U32_RANGE_EQ(reinterpret_cast(expectedData.data()), dstBuffer, 0, + kBufferSize / sizeof(uint32_t)); + } + + // offset > 0 and dstOffset + CopySize == kBufferSize + { + constexpr uint32_t kDstOffset = kBufferSize / 2; + constexpr uint32_t kCopySize = kBufferSize - kDstOffset; + + wgpu::Buffer dstBuffer = device.CreateBuffer(&bufferDescriptor); + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + encoder.CopyBufferToBuffer(srcBuffer, 0, dstBuffer, kDstOffset, kCopySize); + wgpu::CommandBuffer commandBuffer = encoder.Finish(); + + EXPECT_LAZY_CLEAR(1u, queue.Submit(1, &commandBuffer)); + + std::array expectedData; + expectedData.fill(0); + for (uint32_t index = kDstOffset; index < kDstOffset + kCopySize; ++index) { + expectedData[index] = kInitialData[index - kDstOffset]; + } + + EXPECT_BUFFER_U32_RANGE_EQ(reinterpret_cast(expectedData.data()), dstBuffer, 0, + kBufferSize / sizeof(uint32_t)); + } + + // offset > 0 and dstOffset + CopySize < kBufferSize + { + constexpr uint32_t kDstOffset = kBufferSize / 4; + constexpr uint32_t kCopySize = kBufferSize / 2; + + wgpu::Buffer dstBuffer = device.CreateBuffer(&bufferDescriptor); + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + encoder.CopyBufferToBuffer(srcBuffer, 0, dstBuffer, kDstOffset, kCopySize); + wgpu::CommandBuffer commandBuffer = encoder.Finish(); + + EXPECT_LAZY_CLEAR(1u, queue.Submit(1, &commandBuffer)); + + std::array expectedData; + expectedData.fill(0); + for (uint32_t index = kDstOffset; index < kDstOffset + kCopySize; ++index) { + expectedData[index] = kInitialData[index - kDstOffset]; + } + + EXPECT_BUFFER_U32_RANGE_EQ(reinterpret_cast(expectedData.data()), dstBuffer, 0, + kBufferSize / sizeof(uint32_t)); + } +} + DAWN_INSTANTIATE_TEST(BufferZeroInitTest, D3D12Backend({"nonzero_clear_resources_on_creation_for_testing", "lazy_clear_buffer_on_first_use"}),