From 6237c8a1211d61ee8c1f3390ad1e37eb08d72c8b Mon Sep 17 00:00:00 2001 From: Ken Rockot Date: Mon, 20 Sep 2021 19:25:05 +0000 Subject: [PATCH] Introduce WriteBuffer command This command copies data from host memory into a GPU buffer. It's analogous to Queue::WriteBuffer, but executed in the context of a command buffer, sequenced with other encoded commands. This is useful for supporting a notion of a shared scratch buffer, with a single allocation whose contents may need to be overwritten with new data before each pass that uses it. Bug: dawn:809 Change-Id: If58d49c52a41127e2980dd626fd687eb1c91fe28 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/64001 Reviewed-by: Austin Eng Reviewed-by: Corentin Wallez Commit-Queue: Ken Rockot --- dawn.json | 9 ++ src/dawn_native/CommandEncoder.cpp | 23 ++++ src/dawn_native/CommandEncoder.h | 4 + src/dawn_native/CommandValidation.cpp | 25 +++++ src/dawn_native/CommandValidation.h | 5 + src/dawn_native/Commands.cpp | 10 ++ src/dawn_native/Commands.h | 7 ++ src/dawn_native/Queue.cpp | 33 +----- src/dawn_native/Queue.h | 3 - src/dawn_native/d3d12/CommandBufferD3D12.cpp | 30 +++++ src/dawn_native/metal/CommandBufferMTL.mm | 32 ++++++ src/dawn_native/opengl/CommandBufferGL.cpp | 17 +++ src/dawn_native/vulkan/CommandBufferVk.cpp | 36 ++++++ src/tests/BUILD.gn | 2 + src/tests/end2end/CommandEncoderTests.cpp | 53 +++++++++ .../unittests/validation/WriteBufferTests.cpp | 104 ++++++++++++++++++ 16 files changed, 361 insertions(+), 32 deletions(-) create mode 100644 src/tests/end2end/CommandEncoderTests.cpp create mode 100644 src/tests/unittests/validation/WriteBufferTests.cpp diff --git a/dawn.json b/dawn.json index 2d1ab5047c..60caac3117 100644 --- a/dawn.json +++ b/dawn.json @@ -522,6 +522,15 @@ {"name": "destination offset", "type": "uint64_t"} ] }, + { + "name": "write buffer", + "args": [ + {"name": "buffer", "type": "buffer"}, + {"name": "buffer offset", "type": "uint64_t"}, + {"name": "data", "type": "uint8_t", "annotation": "const*", "length": "size"}, + {"name": "size", "type": "uint64_t"} + ] + }, { "name": "write timestamp", "args": [ diff --git a/src/dawn_native/CommandEncoder.cpp b/src/dawn_native/CommandEncoder.cpp index 74a931446a..f90e525315 100644 --- a/src/dawn_native/CommandEncoder.cpp +++ b/src/dawn_native/CommandEncoder.cpp @@ -873,6 +873,29 @@ namespace dawn_native { }); } + void CommandEncoder::APIWriteBuffer(BufferBase* buffer, + uint64_t bufferOffset, + const uint8_t* data, + uint64_t size) { + mEncodingContext.TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError { + if (GetDevice()->IsValidationEnabled()) { + DAWN_TRY(ValidateWriteBuffer(GetDevice(), buffer, bufferOffset, size)); + } + + WriteBufferCmd* cmd = allocator->Allocate(Command::WriteBuffer); + cmd->buffer = buffer; + cmd->offset = bufferOffset; + cmd->size = size; + + uint8_t* inlinedData = allocator->AllocateData(size); + memcpy(inlinedData, data, size); + + mTopLevelBuffers.insert(buffer); + + return {}; + }); + } + void CommandEncoder::APIWriteTimestamp(QuerySetBase* querySet, uint32_t queryIndex) { mEncodingContext.TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError { if (GetDevice()->IsValidationEnabled()) { diff --git a/src/dawn_native/CommandEncoder.h b/src/dawn_native/CommandEncoder.h index 776e1d2831..7eaca04dca 100644 --- a/src/dawn_native/CommandEncoder.h +++ b/src/dawn_native/CommandEncoder.h @@ -68,6 +68,10 @@ namespace dawn_native { uint32_t queryCount, BufferBase* destination, uint64_t destinationOffset); + void APIWriteBuffer(BufferBase* buffer, + uint64_t bufferOffset, + const uint8_t* data, + uint64_t size); void APIWriteTimestamp(QuerySetBase* querySet, uint32_t queryIndex); CommandBufferBase* APIFinish(const CommandBufferDescriptor* descriptor = nullptr); diff --git a/src/dawn_native/CommandValidation.cpp b/src/dawn_native/CommandValidation.cpp index 7e3b8ee0ea..93f4278202 100644 --- a/src/dawn_native/CommandValidation.cpp +++ b/src/dawn_native/CommandValidation.cpp @@ -72,6 +72,31 @@ namespace dawn_native { return {}; } + MaybeError ValidateWriteBuffer(const DeviceBase* device, + const BufferBase* buffer, + uint64_t bufferOffset, + uint64_t size) { + DAWN_TRY(device->ValidateObject(buffer)); + + if (bufferOffset % 4 != 0) { + return DAWN_VALIDATION_ERROR("WriteBuffer bufferOffset must be a multiple of 4"); + } + if (size % 4 != 0) { + return DAWN_VALIDATION_ERROR("WriteBuffer size must be a multiple of 4"); + } + + uint64_t bufferSize = buffer->GetSize(); + if (bufferOffset > bufferSize || size > (bufferSize - bufferOffset)) { + return DAWN_VALIDATION_ERROR("WriteBuffer out of range"); + } + + if (!(buffer->GetUsage() & wgpu::BufferUsage::CopyDst)) { + return DAWN_VALIDATION_ERROR("Buffer needs the CopyDst usage bit"); + } + + return {}; + } + bool IsRangeOverlapped(uint32_t startA, uint32_t startB, uint32_t length) { uint32_t maxStart = std::max(startA, startB); uint32_t minStart = std::min(startA, startB); diff --git a/src/dawn_native/CommandValidation.h b/src/dawn_native/CommandValidation.h index a87e956cbb..85f39df7aa 100644 --- a/src/dawn_native/CommandValidation.h +++ b/src/dawn_native/CommandValidation.h @@ -31,6 +31,11 @@ namespace dawn_native { MaybeError ValidateTimestampQuery(QuerySetBase* querySet, uint32_t queryIndex); + MaybeError ValidateWriteBuffer(const DeviceBase* device, + const BufferBase* buffer, + uint64_t bufferOffset, + uint64_t size); + ResultOrError ComputeRequiredBytesInCopy(const TexelBlockInfo& blockInfo, const Extent3D& copySize, uint32_t bytesPerRow, diff --git a/src/dawn_native/Commands.cpp b/src/dawn_native/Commands.cpp index e3f852e2a3..c2bd0dde6d 100644 --- a/src/dawn_native/Commands.cpp +++ b/src/dawn_native/Commands.cpp @@ -191,6 +191,12 @@ namespace dawn_native { cmd->~SetVertexBufferCmd(); break; } + case Command::WriteBuffer: { + WriteBufferCmd* write = commands->NextCommand(); + commands->NextData(write->size); + write->~WriteBufferCmd(); + break; + } case Command::WriteTimestamp: { WriteTimestampCmd* cmd = commands->NextCommand(); cmd->~WriteTimestampCmd(); @@ -336,6 +342,10 @@ namespace dawn_native { break; } + case Command::WriteBuffer: + commands->NextCommand(); + break; + case Command::WriteTimestamp: { commands->NextCommand(); break; diff --git a/src/dawn_native/Commands.h b/src/dawn_native/Commands.h index 3c958fa03a..cee9bbd52e 100644 --- a/src/dawn_native/Commands.h +++ b/src/dawn_native/Commands.h @@ -63,6 +63,7 @@ namespace dawn_native { SetBindGroup, SetIndexBuffer, SetVertexBuffer, + WriteBuffer, WriteTimestamp, }; @@ -255,6 +256,12 @@ namespace dawn_native { uint64_t size; }; + struct WriteBufferCmd { + Ref buffer; + uint64_t offset; + uint64_t size; + }; + struct WriteTimestampCmd { Ref querySet; uint32_t queryIndex; diff --git a/src/dawn_native/Queue.cpp b/src/dawn_native/Queue.cpp index 5c5aa3502c..562fad689c 100644 --- a/src/dawn_native/Queue.cpp +++ b/src/dawn_native/Queue.cpp @@ -244,7 +244,10 @@ namespace dawn_native { uint64_t bufferOffset, const void* data, size_t size) { - DAWN_TRY(ValidateWriteBuffer(buffer, bufferOffset, size)); + DAWN_TRY(GetDevice()->ValidateIsAlive()); + DAWN_TRY(GetDevice()->ValidateObject(this)); + DAWN_TRY(ValidateWriteBuffer(GetDevice(), buffer, bufferOffset, size)); + DAWN_TRY(buffer->ValidateCanUseOnQueueNow()); return WriteBufferImpl(buffer, bufferOffset, data, size); } @@ -430,34 +433,6 @@ namespace dawn_native { return {}; } - MaybeError QueueBase::ValidateWriteBuffer(const BufferBase* buffer, - uint64_t bufferOffset, - size_t size) const { - DAWN_TRY(GetDevice()->ValidateIsAlive()); - DAWN_TRY(GetDevice()->ValidateObject(this)); - DAWN_TRY(GetDevice()->ValidateObject(buffer)); - - if (bufferOffset % 4 != 0) { - return DAWN_VALIDATION_ERROR("Queue::WriteBuffer bufferOffset must be a multiple of 4"); - } - if (size % 4 != 0) { - return DAWN_VALIDATION_ERROR("Queue::WriteBuffer size must be a multiple of 4"); - } - - uint64_t bufferSize = buffer->GetSize(); - if (bufferOffset > bufferSize || size > (bufferSize - bufferOffset)) { - return DAWN_VALIDATION_ERROR("Queue::WriteBuffer out of range"); - } - - if (!(buffer->GetUsage() & wgpu::BufferUsage::CopyDst)) { - return DAWN_VALIDATION_ERROR("Buffer needs the CopyDst usage bit"); - } - - DAWN_TRY(buffer->ValidateCanUseOnQueueNow()); - - return {}; - } - MaybeError QueueBase::ValidateWriteTexture(const ImageCopyTexture* destination, size_t dataSize, const TextureDataLayout& dataLayout, diff --git a/src/dawn_native/Queue.h b/src/dawn_native/Queue.h index f0178d2738..7dc460ddcf 100644 --- a/src/dawn_native/Queue.h +++ b/src/dawn_native/Queue.h @@ -92,9 +92,6 @@ namespace dawn_native { MaybeError ValidateSubmit(uint32_t commandCount, CommandBufferBase* const* commands) const; MaybeError ValidateOnSubmittedWorkDone(uint64_t signalValue, WGPUQueueWorkDoneStatus* status) const; - MaybeError ValidateWriteBuffer(const BufferBase* buffer, - uint64_t bufferOffset, - size_t size) const; MaybeError ValidateWriteTexture(const ImageCopyTexture* destination, size_t dataSize, const TextureDataLayout& dataLayout, diff --git a/src/dawn_native/d3d12/CommandBufferD3D12.cpp b/src/dawn_native/d3d12/CommandBufferD3D12.cpp index 638f17d9ea..72e9191172 100644 --- a/src/dawn_native/d3d12/CommandBufferD3D12.cpp +++ b/src/dawn_native/d3d12/CommandBufferD3D12.cpp @@ -981,6 +981,36 @@ namespace dawn_native { namespace d3d12 { break; } + case Command::WriteBuffer: { + WriteBufferCmd* write = mCommands.NextCommand(); + const uint64_t offset = write->offset; + const uint64_t size = write->size; + if (size == 0) { + continue; + } + + Buffer* dstBuffer = ToBackend(write->buffer.Get()); + uint8_t* data = mCommands.NextData(size); + Device* device = ToBackend(GetDevice()); + + UploadHandle uploadHandle; + DAWN_TRY_ASSIGN(uploadHandle, device->GetDynamicUploader()->Allocate( + size, device->GetPendingCommandSerial(), + kCopyBufferToBufferOffsetAlignment)); + ASSERT(uploadHandle.mappedBuffer != nullptr); + memcpy(uploadHandle.mappedBuffer, data, size); + + DAWN_TRY(dstBuffer->EnsureDataInitializedAsDestination(commandContext, offset, + size)); + dstBuffer->TrackUsageAndTransitionNow(commandContext, + wgpu::BufferUsage::CopyDst); + commandList->CopyBufferRegion( + dstBuffer->GetD3D12Resource(), offset, + ToBackend(uploadHandle.stagingBuffer)->GetResource(), + uploadHandle.startOffset, size); + break; + } + default: UNREACHABLE(); } diff --git a/src/dawn_native/metal/CommandBufferMTL.mm b/src/dawn_native/metal/CommandBufferMTL.mm index 45d1ed1fc4..e9825a824f 100644 --- a/src/dawn_native/metal/CommandBufferMTL.mm +++ b/src/dawn_native/metal/CommandBufferMTL.mm @@ -17,6 +17,7 @@ #include "dawn_native/BindGroupTracker.h" #include "dawn_native/CommandEncoder.h" #include "dawn_native/Commands.h" +#include "dawn_native/DynamicUploader.h" #include "dawn_native/ExternalTexture.h" #include "dawn_native/RenderBundle.h" #include "dawn_native/metal/BindGroupMTL.h" @@ -27,6 +28,7 @@ #include "dawn_native/metal/QuerySetMTL.h" #include "dawn_native/metal/RenderPipelineMTL.h" #include "dawn_native/metal/SamplerMTL.h" +#include "dawn_native/metal/StagingBufferMTL.h" #include "dawn_native/metal/TextureMTL.h" #include "dawn_native/metal/UtilsMetal.h" @@ -985,6 +987,36 @@ namespace dawn_native { namespace metal { break; } + case Command::WriteBuffer: { + WriteBufferCmd* write = mCommands.NextCommand(); + const uint64_t offset = write->offset; + const uint64_t size = write->size; + if (size == 0) { + continue; + } + + Buffer* dstBuffer = ToBackend(write->buffer.Get()); + uint8_t* data = mCommands.NextData(size); + Device* device = ToBackend(GetDevice()); + + UploadHandle uploadHandle; + DAWN_TRY_ASSIGN(uploadHandle, device->GetDynamicUploader()->Allocate( + size, device->GetPendingCommandSerial(), + kCopyBufferToBufferOffsetAlignment)); + ASSERT(uploadHandle.mappedBuffer != nullptr); + memcpy(uploadHandle.mappedBuffer, data, size); + + dstBuffer->EnsureDataInitializedAsDestination(commandContext, offset, size); + + [commandContext->EnsureBlit() + copyFromBuffer:ToBackend(uploadHandle.stagingBuffer)->GetBufferHandle() + sourceOffset:uploadHandle.startOffset + toBuffer:dstBuffer->GetMTLBuffer() + destinationOffset:offset + size:size]; + break; + } + default: UNREACHABLE(); } diff --git a/src/dawn_native/opengl/CommandBufferGL.cpp b/src/dawn_native/opengl/CommandBufferGL.cpp index 2253247327..7d2014db1b 100644 --- a/src/dawn_native/opengl/CommandBufferGL.cpp +++ b/src/dawn_native/opengl/CommandBufferGL.cpp @@ -843,6 +843,23 @@ namespace dawn_native { namespace opengl { break; } + case Command::WriteBuffer: { + WriteBufferCmd* write = mCommands.NextCommand(); + uint64_t offset = write->offset; + uint64_t size = write->size; + if (size == 0) { + continue; + } + + Buffer* dstBuffer = ToBackend(write->buffer.Get()); + uint8_t* data = mCommands.NextData(size); + dstBuffer->EnsureDataInitializedAsDestination(offset, size); + + gl.BindBuffer(GL_ARRAY_BUFFER, dstBuffer->GetHandle()); + gl.BufferSubData(GL_ARRAY_BUFFER, offset, size, data); + break; + } + default: UNREACHABLE(); } diff --git a/src/dawn_native/vulkan/CommandBufferVk.cpp b/src/dawn_native/vulkan/CommandBufferVk.cpp index e5ca8c61cb..72a7f8b336 100644 --- a/src/dawn_native/vulkan/CommandBufferVk.cpp +++ b/src/dawn_native/vulkan/CommandBufferVk.cpp @@ -18,6 +18,7 @@ #include "dawn_native/CommandEncoder.h" #include "dawn_native/CommandValidation.h" #include "dawn_native/Commands.h" +#include "dawn_native/DynamicUploader.h" #include "dawn_native/EnumMaskIterator.h" #include "dawn_native/RenderBundle.h" #include "dawn_native/vulkan/BindGroupVk.h" @@ -30,6 +31,7 @@ #include "dawn_native/vulkan/QuerySetVk.h" #include "dawn_native/vulkan/RenderPassCache.h" #include "dawn_native/vulkan/RenderPipelineVk.h" +#include "dawn_native/vulkan/StagingBufferVk.h" #include "dawn_native/vulkan/TextureVk.h" #include "dawn_native/vulkan/UtilsVulkan.h" #include "dawn_native/vulkan/VulkanError.h" @@ -822,6 +824,40 @@ namespace dawn_native { namespace vulkan { break; } + case Command::WriteBuffer: { + WriteBufferCmd* write = mCommands.NextCommand(); + const uint64_t offset = write->offset; + const uint64_t size = write->size; + if (size == 0) { + continue; + } + + Buffer* dstBuffer = ToBackend(write->buffer.Get()); + uint8_t* data = mCommands.NextData(size); + Device* device = ToBackend(GetDevice()); + + UploadHandle uploadHandle; + DAWN_TRY_ASSIGN(uploadHandle, device->GetDynamicUploader()->Allocate( + size, device->GetPendingCommandSerial(), + kCopyBufferToBufferOffsetAlignment)); + ASSERT(uploadHandle.mappedBuffer != nullptr); + memcpy(uploadHandle.mappedBuffer, data, size); + + dstBuffer->EnsureDataInitializedAsDestination(recordingContext, offset, size); + + dstBuffer->TransitionUsageNow(recordingContext, wgpu::BufferUsage::CopyDst); + + VkBufferCopy copy; + copy.srcOffset = uploadHandle.startOffset; + copy.dstOffset = offset; + copy.size = size; + + device->fn.CmdCopyBuffer( + commands, ToBackend(uploadHandle.stagingBuffer)->GetBufferHandle(), + dstBuffer->GetHandle(), 1, ©); + break; + } + default: break; } diff --git a/src/tests/BUILD.gn b/src/tests/BUILD.gn index b79c5b6b74..82d40ddbce 100644 --- a/src/tests/BUILD.gn +++ b/src/tests/BUILD.gn @@ -229,6 +229,7 @@ test("dawn_unittests") { "unittests/validation/VertexBufferValidationTests.cpp", "unittests/validation/VertexStateValidationTests.cpp", "unittests/validation/VideoViewsValidationTests.cpp", + "unittests/validation/WriteBufferTests.cpp", "unittests/wire/WireArgumentTests.cpp", "unittests/wire/WireBasicTests.cpp", "unittests/wire/WireBufferMappingTests.cpp", @@ -298,6 +299,7 @@ source_set("dawn_end2end_tests_sources") { "end2end/BufferZeroInitTests.cpp", "end2end/ClipSpaceTests.cpp", "end2end/ColorStateTests.cpp", + "end2end/CommandEncoderTests.cpp", "end2end/CompressedTextureFormatTests.cpp", "end2end/ComputeCopyStorageBufferTests.cpp", "end2end/ComputeDispatchTests.cpp", diff --git a/src/tests/end2end/CommandEncoderTests.cpp b/src/tests/end2end/CommandEncoderTests.cpp new file mode 100644 index 0000000000..589f314ed6 --- /dev/null +++ b/src/tests/end2end/CommandEncoderTests.cpp @@ -0,0 +1,53 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "tests/DawnTest.h" +#include "utils/WGPUHelpers.h" + +class CommandEncoderTests : public DawnTest {}; + +// Tests WriteBuffer commands on CommandEncoder. +TEST_P(CommandEncoderTests, WriteBuffer) { + wgpu::Buffer bufferA = utils::CreateBufferFromData( + device, wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc, {0, 0, 0, 0}); + wgpu::Buffer bufferB = utils::CreateBufferFromData( + device, wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc, {0, 0, 0, 0}); + wgpu::Buffer bufferC = utils::CreateBufferFromData( + device, wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc, {0, 0, 0, 0}); + + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + + const uint32_t kData1 = 1; + encoder.WriteBuffer(bufferA, 0, reinterpret_cast(&kData1), sizeof(kData1)); + encoder.CopyBufferToBuffer(bufferA, 0, bufferB, sizeof(uint32_t), 3 * sizeof(uint32_t)); + + const uint32_t kData2 = 2; + encoder.WriteBuffer(bufferB, 0, reinterpret_cast(&kData2), sizeof(kData2)); + encoder.CopyBufferToBuffer(bufferB, 0, bufferC, sizeof(uint32_t), 3 * sizeof(uint32_t)); + + wgpu::CommandBuffer commands = encoder.Finish(); + queue.Submit(1, &commands); + + EXPECT_BUFFER_U32_EQ(0, bufferC, 0); + EXPECT_BUFFER_U32_EQ(2, bufferC, sizeof(uint32_t)); + EXPECT_BUFFER_U32_EQ(1, bufferC, 2 * sizeof(uint32_t)); + EXPECT_BUFFER_U32_EQ(0, bufferC, 3 * sizeof(uint32_t)); +} + +DAWN_INSTANTIATE_TEST(CommandEncoderTests, + D3D12Backend(), + MetalBackend(), + OpenGLBackend(), + OpenGLESBackend(), + VulkanBackend()); diff --git a/src/tests/unittests/validation/WriteBufferTests.cpp b/src/tests/unittests/validation/WriteBufferTests.cpp new file mode 100644 index 0000000000..dc224bf3a9 --- /dev/null +++ b/src/tests/unittests/validation/WriteBufferTests.cpp @@ -0,0 +1,104 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "tests/unittests/validation/ValidationTest.h" + +#include "utils/ComboRenderPipelineDescriptor.h" +#include "utils/WGPUHelpers.h" + +namespace { + + class WriteBufferTest : public ValidationTest { + public: + wgpu::Buffer CreateWritableBuffer(uint64_t size) { + wgpu::BufferDescriptor desc; + desc.usage = wgpu::BufferUsage::CopyDst; + desc.size = size; + return device.CreateBuffer(&desc); + } + + wgpu::CommandBuffer EncodeWriteBuffer(wgpu::Buffer buffer, + uint64_t bufferOffset, + uint64_t size) { + std::vector data(size); + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + encoder.WriteBuffer(buffer, bufferOffset, data.data(), size); + return encoder.Finish(); + } + }; + + // Tests that the buffer offset is validated to be a multiple of 4 bytes. + TEST_F(WriteBufferTest, OffsetAlignment) { + wgpu::Buffer buffer = CreateWritableBuffer(64); + EncodeWriteBuffer(buffer, 0, 4); + EncodeWriteBuffer(buffer, 4, 4); + EncodeWriteBuffer(buffer, 60, 4); + ASSERT_DEVICE_ERROR(EncodeWriteBuffer(buffer, 1, 4)); + ASSERT_DEVICE_ERROR(EncodeWriteBuffer(buffer, 2, 4)); + ASSERT_DEVICE_ERROR(EncodeWriteBuffer(buffer, 3, 4)); + ASSERT_DEVICE_ERROR(EncodeWriteBuffer(buffer, 5, 4)); + ASSERT_DEVICE_ERROR(EncodeWriteBuffer(buffer, 11, 4)); + } + + // Tests that the buffer size is validated to be a multiple of 4 bytes. + TEST_F(WriteBufferTest, SizeAlignment) { + wgpu::Buffer buffer = CreateWritableBuffer(64); + EncodeWriteBuffer(buffer, 0, 64); + EncodeWriteBuffer(buffer, 4, 60); + EncodeWriteBuffer(buffer, 40, 24); + ASSERT_DEVICE_ERROR(EncodeWriteBuffer(buffer, 0, 63)); + ASSERT_DEVICE_ERROR(EncodeWriteBuffer(buffer, 4, 1)); + ASSERT_DEVICE_ERROR(EncodeWriteBuffer(buffer, 4, 2)); + ASSERT_DEVICE_ERROR(EncodeWriteBuffer(buffer, 40, 23)); + } + + // Tests that the buffer size and offset are validated to fit within the bounds of the buffer. + TEST_F(WriteBufferTest, BufferBounds) { + wgpu::Buffer buffer = CreateWritableBuffer(64); + EncodeWriteBuffer(buffer, 0, 64); + EncodeWriteBuffer(buffer, 4, 60); + EncodeWriteBuffer(buffer, 40, 24); + ASSERT_DEVICE_ERROR(EncodeWriteBuffer(buffer, 0, 68)); + ASSERT_DEVICE_ERROR(EncodeWriteBuffer(buffer, 4, 64)); + ASSERT_DEVICE_ERROR(EncodeWriteBuffer(buffer, 60, 8)); + ASSERT_DEVICE_ERROR(EncodeWriteBuffer(buffer, 64, 4)); + } + + // Tests that the destination buffer's usage is validated to contain CopyDst. + TEST_F(WriteBufferTest, RequireCopyDstUsage) { + wgpu::BufferDescriptor desc; + desc.usage = wgpu::BufferUsage::CopySrc; + desc.size = 64; + wgpu::Buffer buffer = device.CreateBuffer(&desc); + + ASSERT_DEVICE_ERROR(EncodeWriteBuffer(buffer, 0, 64)); + } + + // Tests that the destination buffer's state is validated at submission. + TEST_F(WriteBufferTest, ValidBufferState) { + wgpu::BufferDescriptor desc; + desc.usage = wgpu::BufferUsage::CopyDst; + desc.size = 64; + desc.mappedAtCreation = true; + wgpu::Buffer buffer = device.CreateBuffer(&desc); + + wgpu::CommandBuffer commands = EncodeWriteBuffer(buffer, 0, 64); + ASSERT_DEVICE_ERROR(device.GetQueue().Submit(1, &commands)); + + commands = EncodeWriteBuffer(buffer, 0, 64); + buffer.Unmap(); + device.GetQueue().Submit(1, &commands); + } + +} // namespace