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 <enga@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Ken Rockot <rockot@google.com>
This commit is contained in:
Ken Rockot 2021-09-20 19:25:05 +00:00 committed by Dawn LUCI CQ
parent 1a965ab7f8
commit 6237c8a121
16 changed files with 361 additions and 32 deletions

View File

@ -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": [

View File

@ -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<WriteBufferCmd>(Command::WriteBuffer);
cmd->buffer = buffer;
cmd->offset = bufferOffset;
cmd->size = size;
uint8_t* inlinedData = allocator->AllocateData<uint8_t>(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()) {

View File

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

View File

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

View File

@ -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<uint64_t> ComputeRequiredBytesInCopy(const TexelBlockInfo& blockInfo,
const Extent3D& copySize,
uint32_t bytesPerRow,

View File

@ -191,6 +191,12 @@ namespace dawn_native {
cmd->~SetVertexBufferCmd();
break;
}
case Command::WriteBuffer: {
WriteBufferCmd* write = commands->NextCommand<WriteBufferCmd>();
commands->NextData<uint8_t>(write->size);
write->~WriteBufferCmd();
break;
}
case Command::WriteTimestamp: {
WriteTimestampCmd* cmd = commands->NextCommand<WriteTimestampCmd>();
cmd->~WriteTimestampCmd();
@ -336,6 +342,10 @@ namespace dawn_native {
break;
}
case Command::WriteBuffer:
commands->NextCommand<WriteBufferCmd>();
break;
case Command::WriteTimestamp: {
commands->NextCommand<WriteTimestampCmd>();
break;

View File

@ -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<BufferBase> buffer;
uint64_t offset;
uint64_t size;
};
struct WriteTimestampCmd {
Ref<QuerySetBase> querySet;
uint32_t queryIndex;

View File

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

View File

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

View File

@ -981,6 +981,36 @@ namespace dawn_native { namespace d3d12 {
break;
}
case Command::WriteBuffer: {
WriteBufferCmd* write = mCommands.NextCommand<WriteBufferCmd>();
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<uint8_t>(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();
}

View File

@ -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<WriteBufferCmd>();
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<uint8_t>(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();
}

View File

@ -843,6 +843,23 @@ namespace dawn_native { namespace opengl {
break;
}
case Command::WriteBuffer: {
WriteBufferCmd* write = mCommands.NextCommand<WriteBufferCmd>();
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<uint8_t>(size);
dstBuffer->EnsureDataInitializedAsDestination(offset, size);
gl.BindBuffer(GL_ARRAY_BUFFER, dstBuffer->GetHandle());
gl.BufferSubData(GL_ARRAY_BUFFER, offset, size, data);
break;
}
default:
UNREACHABLE();
}

View File

@ -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<WriteBufferCmd>();
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<uint8_t>(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, &copy);
break;
}
default:
break;
}

View File

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

View File

@ -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<const uint8_t*>(&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<const uint8_t*>(&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());

View File

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