Add copyBufferToBuffer encoder command
Bug: 413053623
Change-Id: I6a6a696485f0fe1d20ec0b67072ce9e4a6f54e06
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/266274
Reviewed-by: Loko Kung <lokokung@google.com>
Auto-Submit: Gregg Tavares <gman@chromium.org>
Commit-Queue: Gregg Tavares <gman@chromium.org>
diff --git a/src/dawn/native/webgpu/CaptureContext.cpp b/src/dawn/native/webgpu/CaptureContext.cpp
index f35c11e..0b87118 100644
--- a/src/dawn/native/webgpu/CaptureContext.cpp
+++ b/src/dawn/native/webgpu/CaptureContext.cpp
@@ -42,8 +42,6 @@
namespace dawn::native::webgpu {
-void AddReferenced(CaptureContext& captureContext, const BufferBase* buffer) {}
-
MaybeError CaptureContext::CaptureCreation(schema::ObjectId id,
const std::string& label,
RecordableObject* object) {
diff --git a/src/dawn/native/webgpu/CommandBufferWGPU.cpp b/src/dawn/native/webgpu/CommandBufferWGPU.cpp
index 9c3b674..9bdee3b 100644
--- a/src/dawn/native/webgpu/CommandBufferWGPU.cpp
+++ b/src/dawn/native/webgpu/CommandBufferWGPU.cpp
@@ -49,7 +49,7 @@
}
CommandBuffer::CommandBuffer(CommandEncoder* encoder, const CommandBufferDescriptor* descriptor)
- : CommandBufferBase(encoder, descriptor) {}
+ : CommandBufferBase(encoder, descriptor), RecordableObject(schema::ObjectType::CommandBuffer) {}
namespace {
@@ -399,7 +399,7 @@
} // anonymous namespace
-MaybeError CommandBuffer::Capture(CaptureContext& captureContext) {
+MaybeError CommandBuffer::AddReferenced(CaptureContext& captureContext) const {
const auto& resourceUsages = GetResourceUsages();
for (auto buffer : resourceUsages.topLevelBuffers) {
DAWN_TRY(captureContext.AddResource(buffer));
@@ -417,6 +417,34 @@
return {};
}
+MaybeError CommandBuffer::CaptureCreationParameters(CaptureContext& captureContext) {
+ CommandIterator& commands = *GetCommandIteratorForTesting();
+
+ Command type;
+ while (commands.NextCommandId(&type)) {
+ switch (type) {
+ case Command::CopyBufferToBuffer: {
+ const auto& cmd = *commands.NextCommand<CopyBufferToBufferCmd>();
+ schema::EncoderCommandCopyBufferToBufferCmd data{{
+ .data = {{
+ .srcBufferId = captureContext.GetId(cmd.source.Get()),
+ .srcOffset = cmd.sourceOffset,
+ .dstBufferId = captureContext.GetId(cmd.destination.Get()),
+ .dstOffset = cmd.destinationOffset,
+ .size = cmd.size,
+ }},
+ }};
+ Serialize(captureContext, data);
+ break;
+ }
+ default:
+ DAWN_UNREACHABLE();
+ }
+ }
+ Serialize(captureContext, schema::EncoderCommand::End);
+ return {};
+}
+
WGPUCommandBuffer CommandBuffer::Encode() {
auto& wgpu = ToBackend(GetDevice())->wgpu;
diff --git a/src/dawn/native/webgpu/CommandBufferWGPU.h b/src/dawn/native/webgpu/CommandBufferWGPU.h
index e97861b..fd9ff59 100644
--- a/src/dawn/native/webgpu/CommandBufferWGPU.h
+++ b/src/dawn/native/webgpu/CommandBufferWGPU.h
@@ -31,11 +31,12 @@
#include "dawn/native/CommandBuffer.h"
#include "dawn/native/webgpu/Forward.h"
+#include "dawn/native/webgpu/RecordableObject.h"
namespace dawn::native::webgpu {
class CaptureContext;
-class CommandBuffer final : public CommandBufferBase {
+class CommandBuffer final : public CommandBufferBase, public RecordableObject {
public:
static Ref<CommandBuffer> Create(CommandEncoder* encoder,
const CommandBufferDescriptor* descriptor);
@@ -43,6 +44,9 @@
WGPUCommandBuffer Encode();
MaybeError Capture(CaptureContext& captureContext);
+ MaybeError AddReferenced(CaptureContext& captureContext) const override;
+ MaybeError CaptureCreationParameters(CaptureContext& context) override;
+
private:
CommandBuffer(CommandEncoder* encoder, const CommandBufferDescriptor* descriptor);
};
diff --git a/src/dawn/native/webgpu/QueueWGPU.cpp b/src/dawn/native/webgpu/QueueWGPU.cpp
index f68f5e8..fb3f068 100644
--- a/src/dawn/native/webgpu/QueueWGPU.cpp
+++ b/src/dawn/native/webgpu/QueueWGPU.cpp
@@ -55,13 +55,30 @@
return {};
}
- auto& wgpu = ToBackend(GetDevice())->wgpu;
+ if (IsCapturing()) {
+ std::vector<schema::ObjectId> commandBufferIds;
+ commandBufferIds.reserve(commandCount);
+
+ for (uint32_t i = 0; i < commandCount; ++i) {
+ schema::ObjectId id;
+ DAWN_TRY_ASSIGN(id, mCaptureContext->AddResourceAndGetId(commands[i]));
+ commandBufferIds.emplace_back(id);
+ }
+
+ schema::RootCommandQueueSubmitCmd cmd{{
+ .data = {{
+ .commandBuffers = commandBufferIds,
+ }},
+ }};
+ Serialize(*mCaptureContext, cmd);
+ }
std::vector<WGPUCommandBuffer> innerCommandBuffers(commandCount);
for (uint32_t i = 0; i < commandCount; ++i) {
innerCommandBuffers[i] = ToBackend(commands[i])->Encode();
}
+ auto& wgpu = ToBackend(GetDevice())->wgpu;
wgpu.queueSubmit(mInnerHandle, commandCount, innerCommandBuffers.data());
for (uint32_t i = 0; i < commandCount; ++i) {
diff --git a/src/dawn/native/webgpu/Serialization.h b/src/dawn/native/webgpu/Serialization.h
index de5a1ab..3b254bf 100644
--- a/src/dawn/native/webgpu/Serialization.h
+++ b/src/dawn/native/webgpu/Serialization.h
@@ -20,6 +20,7 @@
#include <cstdint>
#include <string>
#include <type_traits>
+#include <vector>
#include "dawn/native/Error.h"
@@ -42,6 +43,14 @@
WriteBytes(context, reinterpret_cast<const char*>(&v), sizeof(v));
}
+template <typename T>
+void Serialize(CaptureContext& context, const std::vector<T>& v) {
+ Serialize(context, v.size());
+ for (const auto& elem : v) {
+ Serialize(context, elem);
+ }
+}
+
// Serialize for enum types with uint32_t or uint64_t underlying type.
template <typename T>
void Serialize(CaptureContext& s,
@@ -140,6 +149,10 @@
struct CmdType##CmdName##Cmd : CmdType##CmdName##Cmd##__Contents, \
public Serializable<CmdType##CmdName##Cmd>
+// Makes both a CmdData and a Cmd struct for a given encoder command name.
+#define DAWN_REPLAY_MAKE_ENCODER_CMD_AND_CMD_DATA(CmdName, CMD_MEMBERS) \
+ DAWN_REPLAY_MAKE_CMD_AND_CMD_DATA(EncoderCommand, CmdName, CMD_MEMBERS)
+
// Makes both a CmdData and a Cmd struct for a given root command name.
#define DAWN_REPLAY_MAKE_ROOT_CMD_AND_CMD_DATA(CmdName, CMD_MEMBERS) \
DAWN_REPLAY_MAKE_CMD_AND_CMD_DATA(RootCommand, CmdName, CMD_MEMBERS)
diff --git a/src/dawn/replay/Deserialization.h b/src/dawn/replay/Deserialization.h
index 2a32043..51d1fda 100644
--- a/src/dawn/replay/Deserialization.h
+++ b/src/dawn/replay/Deserialization.h
@@ -20,6 +20,7 @@
#include <cstdint>
#include <string>
#include <type_traits>
+#include <vector>
#include "dawn/replay/Error.h"
@@ -43,6 +44,17 @@
return ReadBytes(s, reinterpret_cast<char*>(v), sizeof(*v));
}
+template <typename T>
+MaybeError Deserialize(ReadHead& s, std::vector<T>* v) {
+ size_t size = 0;
+ DAWN_TRY(Deserialize(s, &size));
+ v->resize(size);
+ for (size_t i = 0; i < size; ++i) {
+ DAWN_TRY(Deserialize(s, &(*v)[i]));
+ }
+ return {};
+}
+
// Deserialize for enum types with uint32_t or uint64_t underlying type.
template <typename T>
MaybeError Deserialize(
@@ -146,6 +158,10 @@
struct CmdType##CmdName##Cmd : CmdType##CmdName##Cmd##__Contents, \
public ::dawn::replay::Deserializable<CmdType##CmdName##Cmd>
+// Makes both a CmdData and a Cmd struct for a given encoder command name.
+#define DAWN_REPLAY_MAKE_ENCODER_CMD_AND_CMD_DATA(CmdName, CMD_MEMBERS) \
+ DAWN_REPLAY_MAKE_CMD_AND_CMD_DATA(EncoderCommand, CmdName, CMD_MEMBERS)
+
// Makes both a CmdData and a Cmd struct for a given root command name.
#define DAWN_REPLAY_MAKE_ROOT_CMD_AND_CMD_DATA(CmdName, CMD_MEMBERS) \
DAWN_REPLAY_MAKE_CMD_AND_CMD_DATA(RootCommand, CmdName, CMD_MEMBERS)
diff --git a/src/dawn/replay/Replay.cpp b/src/dawn/replay/Replay.cpp
index ebfbe48..0ff049c 100644
--- a/src/dawn/replay/Replay.cpp
+++ b/src/dawn/replay/Replay.cpp
@@ -27,6 +27,8 @@
#include "dawn/replay/Replay.h"
+#include <algorithm>
+
#include "dawn/replay/Deserialization.h"
namespace dawn::replay {
@@ -85,6 +87,47 @@
return {buffer};
}
+MaybeError ProcessEncoderCommands(const Replay& replay,
+ ReadHead& readHead,
+ wgpu::Device device,
+ wgpu::CommandEncoder encoder) {
+ schema::EncoderCommand cmd;
+
+ while (!readHead.IsDone()) {
+ DAWN_TRY(Deserialize(readHead, &cmd));
+ switch (cmd) {
+ case schema::EncoderCommand::CopyBufferToBuffer: {
+ schema::EncoderCommandCopyBufferToBufferCmdData data;
+ DAWN_TRY(Deserialize(readHead, &data));
+ encoder.CopyBufferToBuffer(replay.GetObjectById<wgpu::Buffer>(data.srcBufferId),
+ data.srcOffset,
+ replay.GetObjectById<wgpu::Buffer>(data.dstBufferId),
+ data.dstOffset, data.size);
+ break;
+ }
+ case schema::EncoderCommand::End: {
+ return {};
+ }
+ default:
+ // UNIMPLEMENTED();
+ break;
+ }
+ }
+ return DAWN_INTERNAL_ERROR("Missing End command");
+}
+
+ResultOrError<wgpu::CommandBuffer> CreateCommandBuffer(const Replay& replay,
+ wgpu::Device device,
+ ReadHead& readHead,
+ const std::string& label) {
+ wgpu::CommandEncoderDescriptor desc{
+ .label = wgpu::StringView(label),
+ };
+ wgpu::CommandEncoder encoder = device.CreateCommandEncoder(&desc);
+ DAWN_TRY(ProcessEncoderCommands(replay, readHead, device, encoder));
+ return {encoder.Finish()};
+}
+
} // anonymous namespace
std::unique_ptr<Replay> Replay::Create(wgpu::Device device, const Capture* capture) {
@@ -104,7 +147,15 @@
mResources.insert({resource.id, {resource.label, buffer}});
return {};
}
-
+ case schema::ObjectType::CommandBuffer: {
+ // Command buffers are special and don't have any resources to create.
+ // They are just a sequence of commands.
+ wgpu::CommandBuffer commandBuffer;
+ DAWN_TRY_ASSIGN(commandBuffer,
+ CreateCommandBuffer(*this, device, readHead, resource.label));
+ mResources.insert({resource.id, {resource.label, commandBuffer}});
+ return {};
+ }
default:
// UNIMPLEMENTED();
break;
@@ -132,6 +183,19 @@
data.size));
break;
}
+ case schema::RootCommand::QueueSubmit: {
+ schema::RootCommandQueueSubmitCmdData data;
+ DAWN_TRY(Deserialize(readHead, &data));
+
+ std::vector<wgpu::CommandBuffer> commandBuffers;
+ std::transform(data.commandBuffers.begin(), data.commandBuffers.end(),
+ std::back_inserter(commandBuffers), [&](const auto id) {
+ return GetObjectById<wgpu::CommandBuffer>(id);
+ });
+
+ mDevice.GetQueue().Submit(commandBuffers.size(), commandBuffers.data());
+ break;
+ }
case schema::RootCommand::UnmapBuffer: {
schema::RootCommandUnmapBufferCmdData data;
DAWN_TRY(Deserialize(readHead, &data));
diff --git a/src/dawn/replay/Replay.h b/src/dawn/replay/Replay.h
index caa5f1a..2edbd5f 100644
--- a/src/dawn/replay/Replay.h
+++ b/src/dawn/replay/Replay.h
@@ -42,7 +42,7 @@
namespace dawn::replay {
-typedef std::variant<wgpu::Buffer, wgpu::Texture> Resource;
+typedef std::variant<wgpu::Buffer, wgpu::Texture, wgpu::CommandBuffer> Resource;
struct LabeledResource {
std::string label;
Resource resource;
diff --git a/src/dawn/serialization/Schema.h b/src/dawn/serialization/Schema.h
index 6de1f27..332df1c 100644
--- a/src/dawn/serialization/Schema.h
+++ b/src/dawn/serialization/Schema.h
@@ -17,6 +17,7 @@
#include <cstdint>
#include <string>
+#include <vector>
namespace schema {
// NOTE: This file must be included after files that define
@@ -46,6 +47,24 @@
TextureView,
};
+enum class EncoderCommand : uint32_t {
+ Invalid = 0, // 0 is invalid at it's more likely to catch bugs.
+ BeginComputePass,
+ BeginRenderPass,
+ CopyBufferToBuffer,
+ CopyBufferToTexture,
+ CopyTextureToBuffer,
+ CopyTextureToTexture,
+ ClearBuffer,
+ ResolveQuerySet,
+ WriteTimestamp,
+ InsertDebugMarker,
+ PopDebugGroup,
+ PushDebugGroup,
+ WriteBuffer,
+ End,
+};
+
enum class RootCommand : uint32_t {
Invalid = 0, // 0 is invalid at it's more likely to catch bugs.
CreateResource,
@@ -88,6 +107,20 @@
DAWN_REPLAY_MAKE_ROOT_CMD_AND_CMD_DATA(UnmapBuffer, UNMAP_BUFFER_CMD_DATA_MEMBER){};
+#define QUEUE_SUBMIT_CMD_DATA_MEMBER(X) X(std::vector<ObjectId>, commandBuffers)
+
+DAWN_REPLAY_MAKE_ROOT_CMD_AND_CMD_DATA(QueueSubmit, QUEUE_SUBMIT_CMD_DATA_MEMBER){};
+
+#define COPY_BUFFER_TO_BUFFER_CMD_DATA_MEMBER(X) \
+ X(ObjectId, srcBufferId) \
+ X(uint64_t, srcOffset) \
+ X(ObjectId, dstBufferId) \
+ X(uint64_t, dstOffset) \
+ X(uint64_t, size)
+
+DAWN_REPLAY_MAKE_ENCODER_CMD_AND_CMD_DATA(CopyBufferToBuffer,
+ COPY_BUFFER_TO_BUFFER_CMD_DATA_MEMBER){};
+
} // namespace schema
#endif // SRC_DAWN_SERIALIZATION_SCHEMA_H_
diff --git a/src/dawn/tests/white_box/CaptureAndReplayTests.cpp b/src/dawn/tests/white_box/CaptureAndReplayTests.cpp
index ff5313c..fa90344 100644
--- a/src/dawn/tests/white_box/CaptureAndReplayTests.cpp
+++ b/src/dawn/tests/white_box/CaptureAndReplayTests.cpp
@@ -396,6 +396,91 @@
}
}
+// We make 2 buffers before capture. During capture we map one buffer
+// put some data it in via map/unmap. We then copy from that buffer to the other buffer.
+// On replay check the data is correct.
+TEST_P(CaptureAndReplayTests, CaptureWithMapWriteDuringCapture) {
+ const char* srcLabel = "srcBuffer";
+ const char* dstLabel = "dstBuffer";
+ const uint8_t myData1[] = {0x11, 0x22, 0x33, 0x44};
+ const uint8_t myData2[] = {0x55, 0x66, 0x77, 0x88};
+
+ wgpu::BufferDescriptor descriptor;
+ descriptor.label = dstLabel;
+ descriptor.size = 8;
+ descriptor.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc;
+ wgpu::Buffer dstBuffer = device.CreateBuffer(&descriptor);
+ queue.WriteBuffer(dstBuffer, 0, &myData1, sizeof(myData1));
+
+ descriptor.label = srcLabel;
+ descriptor.size = 4;
+ descriptor.usage = wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc;
+ wgpu::Buffer srcBuffer = device.CreateBuffer(&descriptor);
+
+ auto recorder = Recorder::CreateAndStart(device);
+
+ MapAsyncAndWait(srcBuffer, wgpu::MapMode::Write, 0, 4);
+ srcBuffer.WriteMappedRange(0, &myData2, sizeof(myData2));
+ srcBuffer.Unmap();
+
+ wgpu::CommandBuffer commands;
+ {
+ wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+ encoder.CopyBufferToBuffer(srcBuffer, 0, dstBuffer, 4, 4);
+ commands = encoder.Finish();
+ }
+
+ queue.Submit(1, &commands);
+
+ auto capture = recorder.Finish();
+ auto replay = capture.Replay(device);
+
+ {
+ wgpu::Buffer buffer = replay->GetObjectByLabel<wgpu::Buffer>(dstLabel);
+ ASSERT_NE(buffer, nullptr);
+
+ uint8_t expected[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
+ EXPECT_BUFFER_U8_RANGE_EQ(expected, buffer, 0, sizeof(expected));
+ }
+}
+
+TEST_P(CaptureAndReplayTests, CaptureCopyBufferToBuffer) {
+ const uint8_t myData[] = {0x11, 0x22, 0x33, 0x44};
+
+ wgpu::BufferDescriptor descriptor;
+ descriptor.label = "srcBuffer";
+ descriptor.size = 4;
+ descriptor.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc;
+ wgpu::Buffer srcBuffer = device.CreateBuffer(&descriptor);
+
+ queue.WriteBuffer(srcBuffer, 0, &myData, sizeof(myData));
+
+ descriptor.label = "dstBuffer";
+ descriptor.usage = wgpu::BufferUsage::CopyDst;
+ wgpu::Buffer dstBuffer = device.CreateBuffer(&descriptor);
+
+ wgpu::CommandBuffer commands;
+ {
+ wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+ encoder.CopyBufferToBuffer(srcBuffer, 0, dstBuffer, 0, 4);
+ commands = encoder.Finish();
+ }
+
+ auto recorder = Recorder::CreateAndStart(device);
+
+ queue.Submit(1, &commands);
+
+ auto capture = recorder.Finish();
+ auto replay = capture.Replay(device);
+
+ {
+ wgpu::Buffer buffer = replay->GetObjectByLabel<wgpu::Buffer>("dstBuffer");
+ ASSERT_NE(buffer, nullptr);
+
+ EXPECT_BUFFER_U8_RANGE_EQ(myData, buffer, 0, sizeof(myData));
+ }
+}
+
DAWN_INSTANTIATE_TEST(CaptureAndReplayTests, WebGPUBackend());
} // anonymous namespace