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