// Copyright 2025 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
//    list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
//    contributors may be used to endorse or promote products derived from
//    this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include <memory>
#include <ostream>
#include <sstream>
#include <string>
#include <utility>
#include <vector>

#include "dawn/native/WebGPUBackend.h"
#include "dawn/replay/Replay.h"
#include "src/dawn/tests/DawnTest.h"
#include "src/dawn/tests/MockCallback.h"
#include "src/dawn/utils/ComboRenderBundleEncoderDescriptor.h"
#include "src/dawn/utils/ComboRenderPipelineDescriptor.h"
#include "src/dawn/utils/TestUtils.h"
#include "src/dawn/utils/WGPUHelpers.h"
#include "src/utils/compiler.h"

#ifdef DAWN_SUPPORTS_GLFW_FOR_WINDOWING
#include "GLFW/glfw3.h"
#include "webgpu/webgpu_glfw.h"
#endif

namespace dawn {
namespace {

class CaptureAndReplayTests : public DawnTest {
  public:
    void SetUp() override {
        DawnTest::SetUp();

        // This test is already testing capture and replay, we don't need to wrap it and test again.
        DAWN_TEST_UNSUPPORTED_IF(IsCaptureReplayCheckingEnabled());

        // StartCapture/EndCapture are dawn::native APIs that require a native
        // WGPUDevice. When the wire is enabled, device.Get() returns a wire
        // client device which cannot be cast to a native device, causing a crash.
        // TODO(crbug.com/452924800): Remove once these tests work properly with
        // the WebGPU on WebGPU backend with wire.
        DAWN_SUPPRESS_TEST_IF(UsesWire());
    }

    class Recorder {
      public:
        static std::unique_ptr<Recorder> CreateAndStart(wgpu::Device device) {
            auto recorder = std::unique_ptr<Recorder>(new Recorder(device));
            native::webgpu::StartCapture(device.Get(), recorder->mCommandStream,
                                         recorder->mContentStream);
            return recorder;
        }

        void SetSurfaces(std::vector<wgpu::Surface> surfaces) { mSurfaces = std::move(surfaces); }

        void EndCapture() {
            if (mCapture) {
                return;
            }

            native::webgpu::EndCapture(mDevice.Get());
            mDevice = nullptr;

            auto commandData = mCommandStream.str();
            auto contentData = mContentStream.str();
            std::istringstream commandIStream(commandData);
            std::istringstream contentIStream(contentData);

            mCapture = replay::Capture::Create(commandIStream, commandData.size(), contentIStream,
                                               contentData.size());
        }

        replay::Replay* Replay(wgpu::Device device) {
            CreateReplay(device);
            bool result = mReplay->Play();
            EXPECT_TRUE(result);
            return mReplay.get();
        }

        replay::Replay* CreateReplay(wgpu::Device device) {
            if (!mCapture) {
                EndCapture();
            }
            mReplay = replay::Replay::Create(device, std::move(mCapture));

            if (!mSurfaces.empty()) {
                mReplay->SetSurfaces(mSurfaces);
            }
            return mReplay.get();
        }

        replay::Capture* GetCapture() {
            if (mReplay) {
                return mReplay->GetCapture();
            }
            if (!mCapture) {
                EndCapture();
            }
            return mCapture.get();
        }

        replay::Replay* GetReplay() const {
            DAWN_ASSERT(mReplay);
            return mReplay.get();
        }

        ~Recorder() {
            if (mDevice != nullptr) {
                native::webgpu::EndCapture(mDevice.Get());
            }
        }

      private:
        explicit Recorder(wgpu::Device device) : mDevice(device) {}

        wgpu::Device mDevice;
        std::vector<wgpu::Surface> mSurfaces;
        std::ostringstream mCommandStream;
        std::ostringstream mContentStream;

        std::unique_ptr<replay::Capture> mCapture;
        std::unique_ptr<replay::Replay> mReplay;
    };

    wgpu::Buffer CreateBuffer(const char* label,
                              uint64_t size,
                              wgpu::BufferUsage usage,
                              bool mappedAtCreation = false) {
        wgpu::BufferDescriptor descriptor;
        descriptor.label = label;
        descriptor.size = size;
        descriptor.usage = usage;
        descriptor.mappedAtCreation = mappedAtCreation;
        return device.CreateBuffer(&descriptor);
    }

    wgpu::Texture CreateTexture(const char* label,
                                const wgpu::Extent3D& size,
                                wgpu::TextureFormat format,
                                wgpu::TextureUsage usage) {
        wgpu::TextureDescriptor textureDesc;
        textureDesc.label = label;
        textureDesc.size = size;
        textureDesc.format = format;
        textureDesc.usage = usage;
        return device.CreateTexture(&textureDesc);
    }

    template <typename T>
    void WriteFullTexture(wgpu::Texture texture,
                          wgpu::TextureFormat format,
                          const wgpu::Extent3D& size,
                          const T& data) {
        ASSERT_TRUE(sizeof(data) > 0);
        uint32_t bytesPerBlock = utils::GetTexelBlockSizeInBytes(format);
        wgpu::TexelCopyBufferLayout texelCopyBufferLayout =
            utils::CreateTexelCopyBufferLayout(0, bytesPerBlock * size.width);
        wgpu::TexelCopyTextureInfo texelCopyTextureInfo =
            utils::CreateTexelCopyTextureInfo(texture, 0, {0, 0, 0}, wgpu::TextureAspect::All);
        queue.WriteTexture(&texelCopyTextureInfo, data, sizeof(data), &texelCopyBufferLayout,
                           &size);
    }

    template <typename T>
    void ExpectBufferEQ(replay::Replay* replay, const char* label, const T& expected) {
        ASSERT_TRUE(sizeof(expected) > 0);
        wgpu::Buffer buffer = replay->GetObjectByLabel<wgpu::Buffer>(label);
        ASSERT_NE(buffer, nullptr);

        EXPECT_BUFFER_U8_RANGE_EQ(expected, buffer, 0, sizeof(expected));
    }

    template <typename T>
    void ExpectTextureEQ(replay::Replay* replay,
                         const char* label,
                         const wgpu::Extent3D& size,
                         const T& expected) {
        wgpu::Texture texture = replay->GetObjectByLabel<wgpu::Texture>(label);
        ASSERT_NE(texture, nullptr);
        ASSERT_TRUE(size.width > 0 && size.height > 0 && size.depthOrArrayLayers > 0);
        EXPECT_TEXTURE_EQ(&expected[0], texture, {0, 0}, size, 0, wgpu::TextureAspect::All);
    }
};

// During capture, makes a buffer, puts data in it.
// Then, replays and checks the data is correct.
TEST_P(CaptureAndReplayTests, Basic) {
    const char* label = "MyBuffer";
    const uint8_t myData[] = {0x11, 0x22, 0x33, 0x44};

    auto recorder = Recorder::CreateAndStart(device);

    wgpu::Buffer buffer = CreateBuffer(label, 4, wgpu::BufferUsage::CopyDst);
    queue.WriteBuffer(buffer, 0, &myData, sizeof(myData));

    auto replay = recorder->Replay(device);

    ExpectBufferEQ(replay, label, myData);
}

// During capture, makes a buffer, puts data in it.
// Then, replays and checks the data is correct.
// It uses on label with 5 characters which may skew alignment.
TEST_P(CaptureAndReplayTests, NonMultipleOf4LabelLength) {
    const char* label = "MyBuf";
    const uint8_t myData[] = {0x11, 0x22, 0x33, 0x44};

    // --- capture ---
    auto recorder = Recorder::CreateAndStart(device);

    wgpu::Buffer buffer = CreateBuffer(label, 4, wgpu::BufferUsage::CopyDst);
    queue.WriteBuffer(buffer, 0, &myData, sizeof(myData));

    // --- replay ---
    auto replay = recorder->Replay(device);

    ExpectBufferEQ(replay, label, myData);
}

// Before capture, creates a buffer and sets half of it with WriteBuffer.
// It then starts a capture and writes the other half with WriteBuffer.
// On replay both halves should have the correct data..
TEST_P(CaptureAndReplayTests, StartCaptureAfterBufferCreationWriteBuffer) {
    const char* label = "MyBuffer";
    const uint8_t myData0[] = {0x11, 0x22, 0x33, 0x44};
    const uint8_t myData1[] = {0x55, 0x66, 0x77, 0x88};

    wgpu::Buffer buffer = CreateBuffer(label, 8, wgpu::BufferUsage::CopyDst);
    queue.WriteBuffer(buffer, 0, &myData0, sizeof(myData0));

    // --- capture ---
    auto recorder = Recorder::CreateAndStart(device);

    queue.WriteBuffer(buffer, 4, &myData1, sizeof(myData1));

    // --- replay ---
    auto replay = recorder->Replay(device);

    uint8_t expected[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
    ExpectBufferEQ(replay, label, expected);
}

// Before capture, creates a buffer and sets half of it with mappedAtCreation.
// It then starts a capture and writes the other half with WriteBuffer.
// On replay both halves should have the correct data..
TEST_P(CaptureAndReplayTests, StartCaptureAfterBufferCreationMappedAtCreation) {
    const char* label = "MyBuffer";
    const uint8_t myData0[] = {0x11, 0x22, 0x33, 0x44};
    const uint8_t myData1[] = {0x55, 0x66, 0x77, 0x88};

    wgpu::Buffer buffer = CreateBuffer(label, 8, wgpu::BufferUsage::CopyDst, true);
    DAWN_UNSAFE_TODO(std::memcpy(buffer.GetMappedRange(), myData0, sizeof(myData0)));
    buffer.Unmap();

    // --- capture ---
    auto recorder = Recorder::CreateAndStart(device);

    queue.WriteBuffer(buffer, 4, &myData1, sizeof(myData1));

    // --- replay ---
    auto replay = recorder->Replay(device);

    uint8_t expected[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
    ExpectBufferEQ(replay, label, expected);
}

// Before capture, creates a buffer and sets half of it with a compute shader.
// It then starts a capture and writes the other half with WriteBuffer.
// On replay both halves should have the correct data..
TEST_P(CaptureAndReplayTests, StartCaptureAfterBufferCreationComputeShader) {
    const char* label = "MyBuffer";

    wgpu::Buffer buffer =
        CreateBuffer(label, 8, wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst);

    const char* shader = R"(
        @group(0) @binding(0) var<storage, read_write> result : u32;

        @compute @workgroup_size(1) fn main() {
            result = 0x44332211;
        }
    )";
    auto module = utils::CreateShaderModule(device, shader);

    wgpu::ComputePipelineDescriptor csDesc;
    csDesc.compute.module = module;
    wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc);

    wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0),
                                                     {
                                                         {0, buffer},
                                                     });

    wgpu::CommandBuffer commands;
    {
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
        wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
        pass.SetPipeline(pipeline);
        pass.SetBindGroup(0, bindGroup);
        pass.DispatchWorkgroups(1);
        pass.End();

        commands = encoder.Finish();
    }
    queue.Submit(1, &commands);

    // --- capture ---
    auto recorder = Recorder::CreateAndStart(device);

    const uint8_t myData[] = {0x55, 0x66, 0x77, 0x88};
    queue.WriteBuffer(buffer, 4, &myData, sizeof(myData));

    // --- replay ---
    auto replay = recorder->Replay(device);

    uint8_t expected[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
    ExpectBufferEQ(replay, label, expected);
}

// Before capture, creates a buffer and sets half of it with copyBufferToBuffer.
// It then starts a capture and writes the other half with WriteBuffer.
// On replay both halves should have the correct data..
TEST_P(CaptureAndReplayTests, StartCaptureAfterBufferCreationCopyB2B) {
    const char* srcLabel = "SrcBuffer";
    const char* dstLabel = "DstBuffer";

    wgpu::Buffer dstBuffer = CreateBuffer(dstLabel, 8, wgpu::BufferUsage::CopyDst);
    wgpu::Buffer srcBuffer =
        CreateBuffer(srcLabel, 8, wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst);
    const uint8_t myData1[] = {0x11, 0x22, 0x33, 0x44};
    queue.WriteBuffer(srcBuffer, 0, &myData1, sizeof(myData1));

    wgpu::CommandBuffer commands;
    {
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
        encoder.CopyBufferToBuffer(srcBuffer, 0, dstBuffer, 0, 4);
        commands = encoder.Finish();
    }
    queue.Submit(1, &commands);

    // --- capture ---
    auto recorder = Recorder::CreateAndStart(device);

    const uint8_t myData2[] = {0x55, 0x66, 0x77, 0x88};
    queue.WriteBuffer(dstBuffer, 4, &myData2, sizeof(myData2));

    // --- replay ---
    auto replay = recorder->Replay(device);

    uint8_t expected[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
    ExpectBufferEQ(replay, dstLabel, expected);
}

// During first capture, makes a buffer, puts data in it.
// During 2nd capture, puts a little data in the same buffer.
// Then, replays the 2nd capture and checks the data is correct.
TEST_P(CaptureAndReplayTests, TwoCaptures) {
    const char* label = "MyBuffer";
    const uint8_t myData1[] = {0x11, 0x22, 0x33, 0x44};
    const uint8_t myData2[] = {0x55, 0x66, 0x77, 0x88};

    wgpu::Buffer buffer;

    {
        auto recorder = Recorder::CreateAndStart(device);

        buffer = CreateBuffer(label, 8, wgpu::BufferUsage::CopyDst);
        queue.WriteBuffer(buffer, 0, &myData1, sizeof(myData1));

        recorder->EndCapture();
    }

    {
        auto recorder = Recorder::CreateAndStart(device);

        queue.WriteBuffer(buffer, 4, &myData2, sizeof(myData2));

        auto replay = recorder->Replay(device);

        uint8_t expected[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
        ExpectBufferEQ(replay, label, expected);
    }
}

// We make a buffer before capture. During capture
// write map it, put data in it, copyB2B from it,
// write map it again, put different data in it, copyB2B from it,
// then check the data is correct on replay.
TEST_P(CaptureAndReplayTests, MapWrite) {
    const uint8_t myData1[] = {0x11, 0x22, 0x33, 0x44};
    const uint8_t myData2[] = {0x55, 0x66, 0x77, 0x88};

    wgpu::Buffer srcBuffer =
        CreateBuffer("srcBuffer", 4, wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc);
    wgpu::Buffer dstBuffer = CreateBuffer("dstBuffer", 4, wgpu::BufferUsage::CopyDst);

    auto recorder = Recorder::CreateAndStart(device);

    MapAsyncAndWait(srcBuffer, wgpu::MapMode::Write, 0, 4);
    srcBuffer.WriteMappedRange(0, &myData1, sizeof(myData1));
    srcBuffer.Unmap();

    {
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
        encoder.CopyBufferToBuffer(srcBuffer, 0, dstBuffer, 0, 4);
        wgpu::CommandBuffer commands = encoder.Finish();
        queue.Submit(1, &commands);
    }

    MapAsyncAndWait(srcBuffer, wgpu::MapMode::Write, 0, 4);
    srcBuffer.WriteMappedRange(0, &myData2, sizeof(myData2));
    srcBuffer.Unmap();

    {
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
        encoder.CopyBufferToBuffer(srcBuffer, 0, dstBuffer, 0, 4);
        wgpu::CommandBuffer commands = encoder.Finish();
        queue.Submit(1, &commands);
    }

    auto replay = recorder->Replay(device);

    ExpectBufferEQ(replay, "srcBuffer", myData2);
    ExpectBufferEQ(replay, "dstBuffer", myData2);
}

// 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::Buffer dstBuffer =
        CreateBuffer(dstLabel, 8, wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc);
    queue.WriteBuffer(dstBuffer, 0, &myData1, sizeof(myData1));

    wgpu::Buffer srcBuffer =
        CreateBuffer(srcLabel, 4, wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc);

    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 replay = recorder->Replay(device);

    uint8_t expected[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
    ExpectBufferEQ(replay, dstLabel, expected);
}

// Capture a buffer with MapRead.
TEST_P(CaptureAndReplayTests, CaptureWithMapRead) {
    const char* srcLabel = "srcBuffer";
    const char* dstLabel = "dstBuffer";
    const uint8_t myData[] = {0x55, 0x66, 0x77, 0x88};

    wgpu::Buffer srcBuffer =
        CreateBuffer(srcLabel, 4, wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc);
    queue.WriteBuffer(srcBuffer, 0, &myData, sizeof(myData));

    wgpu::Buffer dstBuffer =
        CreateBuffer(dstLabel, 4, wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopyDst);

    auto recorder = Recorder::CreateAndStart(device);

    wgpu::CommandBuffer commands;
    {
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
        encoder.CopyBufferToBuffer(srcBuffer, 0, dstBuffer, 0, sizeof(myData));
        commands = encoder.Finish();
    }

    queue.Submit(1, &commands);

    recorder->Replay(device);

    // We can't use ExpectBufferEQ here because it uses a copy and `dstBuffer`
    // does not have CopySrc. So, let's do it ourselves.
    MapAsyncAndWait(dstBuffer, wgpu::MapMode::Read, 0, sizeof(myData));
    auto actual = static_cast<const uint8_t*>(dstBuffer.GetConstMappedRange(0, sizeof(myData)));
    std::span<const uint8_t> actual_span(actual, std::size(myData));
    ASSERT_THAT(actual_span, ::testing::ElementsAreArray(myData));
}

TEST_P(CaptureAndReplayTests, CaptureCopyBufferToBuffer) {
    const uint8_t myData[] = {0x11, 0x22, 0x33, 0x44};

    wgpu::Buffer srcBuffer =
        CreateBuffer("srcBuffer", 4, wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc);
    queue.WriteBuffer(srcBuffer, 0, &myData, sizeof(myData));

    wgpu::Buffer dstBuffer = CreateBuffer("dstBuffer", 4, wgpu::BufferUsage::CopyDst);

    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 replay = recorder->Replay(device);

    ExpectBufferEQ(replay, "dstBuffer", myData);
}

TEST_P(CaptureAndReplayTests, WriteTexture) {
    wgpu::Texture texture =
        CreateTexture("myTexture", {4}, wgpu::TextureFormat::R8Unorm,
                      wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc);

    auto recorder = Recorder::CreateAndStart(device);

    const uint8_t myData[] = {0x11, 0x22, 0x33, 0x44};
    WriteFullTexture(texture, wgpu::TextureFormat::R8Unorm, {4}, myData);

    auto replay = recorder->Replay(device);

    ExpectTextureEQ(replay, "myTexture", {4}, myData);
}

TEST_P(CaptureAndReplayTests, CaptureCopyBufferToTexture) {
    const uint8_t myData[] = {0x11, 0x22, 0x33, 0x44};

    wgpu::Buffer srcBuffer =
        CreateBuffer("srcBuffer", 4, wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc);
    queue.WriteBuffer(srcBuffer, 0, &myData, sizeof(myData));

    wgpu::Texture dstTexture =
        CreateTexture("dstTexture", {4}, wgpu::TextureFormat::R8Unorm,
                      wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc);

    wgpu::CommandBuffer commands;
    {
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
        wgpu::TexelCopyBufferInfo texelCopyBufferInfo =
            utils::CreateTexelCopyBufferInfo(srcBuffer, 0, 256, 1);
        wgpu::TexelCopyTextureInfo texelCopyTextureInfo =
            utils::CreateTexelCopyTextureInfo(dstTexture, 0, {0, 0, 0}, wgpu::TextureAspect::All);
        wgpu::Extent3D extent = {4, 1, 1};

        encoder.CopyBufferToTexture(&texelCopyBufferInfo, &texelCopyTextureInfo, &extent);
        commands = encoder.Finish();
    }

    auto recorder = Recorder::CreateAndStart(device);

    queue.Submit(1, &commands);

    auto replay = recorder->Replay(device);

    ExpectTextureEQ(replay, "dstTexture", {4}, myData);
}

TEST_P(CaptureAndReplayTests, CaptureCopyTextureToBuffer) {
    wgpu::Texture srcTexture =
        CreateTexture("srcTexture", {4}, wgpu::TextureFormat::R8Unorm,
                      wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc);

    // Put data in source texture
    const uint8_t myData[] = {0x11, 0x22, 0x33, 0x44};
    WriteFullTexture(srcTexture, wgpu::TextureFormat::R8Unorm, {4}, myData);

    wgpu::Buffer dstBuffer =
        CreateBuffer("dstBuffer", 4, wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc);

    wgpu::CommandBuffer commands;
    {
        // Copy srcTexture to dstBuffer
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
        wgpu::TexelCopyTextureInfo texelCopyTextureInfo =
            utils::CreateTexelCopyTextureInfo(srcTexture, 0, {0, 0, 0}, wgpu::TextureAspect::All);
        wgpu::TexelCopyBufferInfo texelCopyBufferInfo =
            utils::CreateTexelCopyBufferInfo(dstBuffer, 0, 256, 1);
        wgpu::Extent3D extent = {4, 1, 1};

        encoder.CopyTextureToBuffer(&texelCopyTextureInfo, &texelCopyBufferInfo, &extent);
        commands = encoder.Finish();
    }

    auto recorder = Recorder::CreateAndStart(device);

    queue.Submit(1, &commands);

    auto replay = recorder->Replay(device);

    ExpectBufferEQ(replay, "dstBuffer", myData);
}

TEST_P(CaptureAndReplayTests, CaptureCopyTextureToTexture) {
    wgpu::Texture srcTexture =
        CreateTexture("srcTexture", {4}, wgpu::TextureFormat::R8Unorm,
                      wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc);

    // Put data in source texture
    const uint8_t myData[] = {0x11, 0x22, 0x33, 0x44};
    WriteFullTexture(srcTexture, wgpu::TextureFormat::R8Unorm, {4}, myData);

    wgpu::Texture dstTexture =
        CreateTexture("dstTexture", {4}, wgpu::TextureFormat::R8Unorm,
                      wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc);

    wgpu::CommandBuffer commands;
    {
        // Copy srcTexture to dstBuffer
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
        wgpu::TexelCopyTextureInfo srcTexelCopyTextureInfo =
            utils::CreateTexelCopyTextureInfo(srcTexture, 0, {0, 0, 0}, wgpu::TextureAspect::All);
        wgpu::TexelCopyTextureInfo dstTexelCopyTextureInfo =
            utils::CreateTexelCopyTextureInfo(dstTexture, 0, {0, 0, 0}, wgpu::TextureAspect::All);
        wgpu::Extent3D extent = {4, 1, 1};

        encoder.CopyTextureToTexture(&srcTexelCopyTextureInfo, &dstTexelCopyTextureInfo, &extent);
        commands = encoder.Finish();
    }

    auto recorder = Recorder::CreateAndStart(device);

    queue.Submit(1, &commands);

    auto replay = recorder->Replay(device);

    ExpectTextureEQ(replay, "dstTexture", {4}, myData);
}

// We make 3 textures. Put data in the first one. Copy to the 2nd one.
// Copy tha the 3rd. Check the results. The reason for this test is that
// the first texture is marked as initialized by WriteTexture. The 2nd is
// not. So, if the texture is not marked as initialized by CopyT2T then
// capture will fail as it will not copy the contents of the 2nd texture.
TEST_P(CaptureAndReplayTests, CaptureCopyTextureToTextureFromCopyT2TTexture) {
    wgpu::Texture dataTexture =
        CreateTexture("dataTexture", {4}, wgpu::TextureFormat::R8Unorm,
                      wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc);

    // Put data in data texture
    const uint8_t myData[] = {0x11, 0x22, 0x33, 0x44};
    WriteFullTexture(dataTexture, wgpu::TextureFormat::R8Unorm, {4}, myData);

    wgpu::Texture srcTexture =
        CreateTexture("srcTexture", {4}, wgpu::TextureFormat::R8Unorm,
                      wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc);

    // Copy the data texture ot the src texture
    {
        wgpu::CommandBuffer commands;
        {
            // Copy srcTexture to dstBuffer
            wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
            wgpu::TexelCopyTextureInfo srcTexelCopyTextureInfo = utils::CreateTexelCopyTextureInfo(
                dataTexture, 0, {0, 0, 0}, wgpu::TextureAspect::All);
            wgpu::TexelCopyTextureInfo dstTexelCopyTextureInfo = utils::CreateTexelCopyTextureInfo(
                srcTexture, 0, {0, 0, 0}, wgpu::TextureAspect::All);
            wgpu::Extent3D extent = {4, 1, 1};

            encoder.CopyTextureToTexture(&srcTexelCopyTextureInfo, &dstTexelCopyTextureInfo,
                                         &extent);
            commands = encoder.Finish();
        }
        queue.Submit(1, &commands);
    }

    wgpu::Texture dstTexture =
        CreateTexture("dstTexture", {4}, wgpu::TextureFormat::R8Unorm,
                      wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc);

    wgpu::CommandBuffer commands;
    {
        // Copy srcTexture to dstBuffer
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
        wgpu::TexelCopyTextureInfo srcTexelCopyTextureInfo =
            utils::CreateTexelCopyTextureInfo(srcTexture, 0, {0, 0, 0}, wgpu::TextureAspect::All);
        wgpu::TexelCopyTextureInfo dstTexelCopyTextureInfo =
            utils::CreateTexelCopyTextureInfo(dstTexture, 0, {0, 0, 0}, wgpu::TextureAspect::All);
        wgpu::Extent3D extent = {4, 1, 1};

        encoder.CopyTextureToTexture(&srcTexelCopyTextureInfo, &dstTexelCopyTextureInfo, &extent);
        commands = encoder.Finish();
    }

    auto recorder = Recorder::CreateAndStart(device);

    queue.Submit(1, &commands);

    auto replay = recorder->Replay(device);

    ExpectTextureEQ(replay, "dstTexture", {4}, myData);
}

// Before capture, creates a texture and sets it in a compute pass as a storage texture.
// Then, captures a copyT2T to a 2nd texture. Checks the 2nd texture has the correct data on replay.
TEST_P(CaptureAndReplayTests, CaptureCopyTextureToTextureFromComputeTexture) {
    wgpu::Texture srcTexture =
        CreateTexture("srcTexture", {1}, wgpu::TextureFormat::RGBA8Uint,
                      wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::StorageBinding);

    const char* shader = R"(
        @group(0) @binding(0) var tex: texture_storage_2d<rgba8uint, write>;

        @compute @workgroup_size(1) fn main() {
            textureStore(tex, vec2u(0), vec4<u32>(0x11, 0x22, 0x33, 0x44));
        }
    )";
    auto module = utils::CreateShaderModule(device, shader);

    wgpu::ComputePipelineDescriptor csDesc;
    csDesc.compute.module = module;
    wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc);

    wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0),
                                                     {
                                                         {0, srcTexture.CreateView()},
                                                     });

    {
        wgpu::CommandBuffer commands;
        {
            wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
            wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
            pass.SetPipeline(pipeline);
            pass.SetBindGroup(0, bindGroup);
            pass.DispatchWorkgroups(1);
            pass.End();

            commands = encoder.Finish();
        }
        queue.Submit(1, &commands);
    }

    wgpu::Texture dstTexture = CreateTexture(
        "dstTexture", {1, 1, 1}, wgpu::TextureFormat::RGBA8Uint, wgpu::TextureUsage::CopyDst);

    // --- capture ---
    auto recorder = Recorder::CreateAndStart(device);

    {
        wgpu::CommandBuffer commands;
        {
            // Copy srcTexture to dstBuffer
            wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
            wgpu::TexelCopyTextureInfo srcTexelCopyTextureInfo = utils::CreateTexelCopyTextureInfo(
                srcTexture, 0, {0, 0, 0}, wgpu::TextureAspect::All);
            wgpu::TexelCopyTextureInfo dstTexelCopyTextureInfo = utils::CreateTexelCopyTextureInfo(
                dstTexture, 0, {0, 0, 0}, wgpu::TextureAspect::All);
            wgpu::Extent3D extent = {1, 1, 1};

            encoder.CopyTextureToTexture(&srcTexelCopyTextureInfo, &dstTexelCopyTextureInfo,
                                         &extent);
            commands = encoder.Finish();
        }
        queue.Submit(1, &commands);
    }

    // --- replay ---
    auto replay = recorder->Replay(device);

    utils::RGBA8 expected[] = {{0x11, 0x22, 0x33, 0x44}};
    ExpectTextureEQ(replay, "dstTexture", {1}, expected);
}

// Before capture, creates a texture and sets in a render pass as a render attachment
// Then, captures a copyT2T to a 2nd texture. Checks the 2nd texture has the correct data on replay.
TEST_P(CaptureAndReplayTests, CaptureCopyTextureToTextureFromRenderTexture) {
    wgpu::Texture srcTexture =
        CreateTexture("srcTexture", {1}, wgpu::TextureFormat::RGBA8Uint,
                      wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment);

    {
        wgpu::CommandBuffer commands;
        {
            wgpu::CommandEncoder encoder = device.CreateCommandEncoder();

            utils::ComboRenderPassDescriptor passDescriptor({srcTexture.CreateView()});
            passDescriptor.cColorAttachments[0].clearValue = {0x11, 0x22, 0x33, 0x44};
            wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor);
            pass.End();

            commands = encoder.Finish();
        }
        queue.Submit(1, &commands);
    }

    wgpu::Texture dstTexture = CreateTexture(
        "dstTexture", {1, 1, 1}, wgpu::TextureFormat::RGBA8Uint, wgpu::TextureUsage::CopyDst);

    // --- capture ---
    auto recorder = Recorder::CreateAndStart(device);

    {
        wgpu::CommandBuffer commands;
        {
            // Copy srcTexture to dstBuffer
            wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
            wgpu::TexelCopyTextureInfo srcTexelCopyTextureInfo = utils::CreateTexelCopyTextureInfo(
                srcTexture, 0, {0, 0, 0}, wgpu::TextureAspect::All);
            wgpu::TexelCopyTextureInfo dstTexelCopyTextureInfo = utils::CreateTexelCopyTextureInfo(
                dstTexture, 0, {0, 0, 0}, wgpu::TextureAspect::All);
            wgpu::Extent3D extent = {1, 1, 1};

            encoder.CopyTextureToTexture(&srcTexelCopyTextureInfo, &dstTexelCopyTextureInfo,
                                         &extent);
            commands = encoder.Finish();
        }
        queue.Submit(1, &commands);
    }

    // --- replay ---
    auto replay = recorder->Replay(device);

    utils::RGBA8 expected[] = {{0x11, 0x22, 0x33, 0x44}};
    ExpectTextureEQ(replay, "dstTexture", {1}, expected);
}

// Capture and replay the simplest compute shader.
TEST_P(CaptureAndReplayTests, CaptureComputeShaderBasic) {
    const char* label = "MyBuffer";

    wgpu::Buffer buffer =
        CreateBuffer(label, 4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst);

    const char* shader = R"(
        @group(0) @binding(0) var<storage, read_write> result : u32;

        @compute @workgroup_size(1) fn main() {
            result = 0x44332211;
        }
    )";
    auto module = utils::CreateShaderModule(device, shader);

    wgpu::ComputePipelineDescriptor csDesc;
    csDesc.compute.module = module;
    wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc);

    wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0),
                                                     {
                                                         {0, buffer},
                                                     });

    wgpu::CommandBuffer commands;
    {
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
        wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
        pass.SetPipeline(pipeline);
        pass.SetBindGroup(0, bindGroup);
        pass.DispatchWorkgroups(1);
        pass.End();

        commands = encoder.Finish();
    }

    // --- capture ---
    auto recorder = Recorder::CreateAndStart(device);

    queue.Submit(1, &commands);

    // --- replay ---
    auto replay = recorder->Replay(device);

    uint8_t expected[] = {0x11, 0x22, 0x33, 0x44};
    ExpectBufferEQ(replay, label, expected);
}

// Capture and replay the simplest compute shader but set the bindGroup
// before setting the pipeline.
TEST_P(CaptureAndReplayTests, CaptureComputeShaderBasicSetBindGroupFirst) {
    const char* label = "MyBuffer";

    wgpu::Buffer buffer =
        CreateBuffer(label, 4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst);

    const char* shader = R"(
        @group(0) @binding(0) var<storage, read_write> result : u32;

        @compute @workgroup_size(1) fn main() {
            result = 0x44332211;
        }
    )";
    auto module = utils::CreateShaderModule(device, shader);

    wgpu::ComputePipelineDescriptor csDesc;
    csDesc.compute.module = module;
    wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc);

    wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0),
                                                     {
                                                         {0, buffer},
                                                     });

    wgpu::CommandBuffer commands;
    {
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
        wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
        pass.SetBindGroup(0, bindGroup);
        pass.SetPipeline(pipeline);
        pass.DispatchWorkgroups(1);
        pass.End();

        commands = encoder.Finish();
    }

    // --- capture ---
    auto recorder = Recorder::CreateAndStart(device);

    queue.Submit(1, &commands);

    // --- replay ---
    auto replay = recorder->Replay(device);

    uint8_t expected[] = {0x11, 0x22, 0x33, 0x44};
    ExpectBufferEQ(replay, label, expected);
}

// Capture and replay 2 auto-layout compute pipelines with the same layout
// This is to verify a bug fix. In Dawn, there is BindGroupLayout and
// BindGroupLayoutInternal. In this test, given 2 pipelines with the same
// layout, there will be 2 BindGroupLayout objects pointing to one
// BindGroupLayoutInternal. That means that when serializing, one of them
// will get the wrong Pipeline if the pipeline is incorrectly associated
// with the one BindGroupLayoutInternal instead of each of the 2 pipelines
// being separately associated with one of the 2 BindGroupLayout objects.
// This is a regression test for crbug.com/455605671
TEST_P(CaptureAndReplayTests, CaptureTwoMatchingAutoLayoutComputePipelines) {
    wgpu::Buffer buffer1 =
        CreateBuffer("buffer1", 4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst);
    wgpu::Buffer buffer2 =
        CreateBuffer("buffer2", 4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst);

    const char* shader = R"(
        @group(0) @binding(0) var<storage, read_write> result : u32;

        @compute @workgroup_size(1) fn cs1() {
            result = 0x44332211;
        }

        @compute @workgroup_size(1) fn cs2() {
            result = 0x88776655;
        }
    )";
    auto module = utils::CreateShaderModule(device, shader);

    wgpu::ComputePipelineDescriptor csDesc;
    csDesc.label = "pipeline1";
    csDesc.compute.module = module;
    csDesc.compute.entryPoint = "cs1";
    wgpu::ComputePipeline pipeline1 = device.CreateComputePipeline(&csDesc);
    csDesc.label = "pipeline2";
    csDesc.compute.entryPoint = "cs2";
    wgpu::ComputePipeline pipeline2 = device.CreateComputePipeline(&csDesc);

    wgpu::BindGroup bindGroup1 = utils::MakeBindGroup(device, pipeline1.GetBindGroupLayout(0),
                                                      {
                                                          {0, buffer1},
                                                      });
    wgpu::BindGroup bindGroup2 = utils::MakeBindGroup(device, pipeline2.GetBindGroupLayout(0),
                                                      {
                                                          {0, buffer2},
                                                      });

    wgpu::CommandBuffer commands;
    {
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
        wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
        pass.SetPipeline(pipeline1);
        pass.SetBindGroup(0, bindGroup1);
        pass.DispatchWorkgroups(1);
        pass.SetPipeline(pipeline2);
        pass.SetBindGroup(0, bindGroup2);
        pass.DispatchWorkgroups(1);
        pass.End();

        commands = encoder.Finish();
    }

    // --- capture ---
    auto recorder = Recorder::CreateAndStart(device);

    queue.Submit(1, &commands);

    // --- replay ---
    auto replay = recorder->Replay(device);

    uint8_t expected1[] = {0x11, 0x22, 0x33, 0x44};
    ExpectBufferEQ(replay, "buffer1", expected1);

    uint8_t expected2[] = {0x55, 0x66, 0x77, 0x88};
    ExpectBufferEQ(replay, "buffer2", expected2);
}

// Capture and replay 2 bindGroups that use implicit bindGroupLayouts from
// different pipelines but for 1, never set the pipeline nor dispatch. This effectively
// makes it a no-op. The issue is, we can't easily serialize a bindGroup that uses an
// implicit bindGroupLayout unless the pipeline that created that bindGroupLayout is
// used in the command buffer. So, we just don't serialize those calls to setBindGroup
// since they are effectively no-ops. This test checks things don't crash as if the
// call was actually serialized it would reference a bindGroupLayout that does not
// exist.
TEST_P(CaptureAndReplayTests, CaptureTwoAutoLayoutComputePipelinesOneIsBoundButUnused) {
    wgpu::Buffer buffer =
        CreateBuffer("buffer", 4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst);

    const char* shader = R"(
        @group(0) @binding(0) var<storage, read_write> result : u32;

        @compute @workgroup_size(1) fn cs1() {
            result = 0x44332211;
        }

        @compute @workgroup_size(1) fn cs2() {
            result = 0x88776655;
        }
    )";
    auto module = utils::CreateShaderModule(device, shader);

    wgpu::ComputePipelineDescriptor csDesc;
    csDesc.label = "pipeline1";
    csDesc.compute.module = module;
    csDesc.compute.entryPoint = "cs1";
    wgpu::ComputePipeline pipeline1 = device.CreateComputePipeline(&csDesc);
    csDesc.label = "pipeline2";
    csDesc.compute.entryPoint = "cs2";
    wgpu::ComputePipeline pipeline2 = device.CreateComputePipeline(&csDesc);

    wgpu::BindGroup bindGroup1 = utils::MakeBindGroup(device, pipeline1.GetBindGroupLayout(0),
                                                      {
                                                          {0, buffer},
                                                      });
    wgpu::BindGroup bindGroup2 = utils::MakeBindGroup(device, pipeline2.GetBindGroupLayout(0),
                                                      {
                                                          {0, buffer},
                                                      });

    wgpu::CommandBuffer commands;
    {
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
        wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
        pass.SetBindGroup(0, bindGroup1);
        pass.SetPipeline(pipeline2);
        pass.SetBindGroup(0, bindGroup2);
        pass.DispatchWorkgroups(1);
        pass.End();

        commands = encoder.Finish();
    }

    // --- capture ---
    auto recorder = Recorder::CreateAndStart(device);

    queue.Submit(1, &commands);

    // --- replay ---
    auto replay = recorder->Replay(device);

    uint8_t expected[] = {0x55, 0x66, 0x77, 0x88};
    ExpectBufferEQ(replay, "buffer", expected);
}

// Capture and replay the simplest render pass.
// It just starts and ends a render pass and uses the clearValue to set
// a texture.
TEST_P(CaptureAndReplayTests, CaptureRenderPassBasic) {
    wgpu::Texture texture = CreateTexture("myTexture", {1}, wgpu::TextureFormat::RGBA8Uint,
                                          wgpu::TextureUsage::RenderAttachment);

    wgpu::CommandBuffer commands;
    {
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();

        utils::ComboRenderPassDescriptor passDescriptor({texture.CreateView()});
        passDescriptor.cColorAttachments[0].clearValue = {0x11, 0x22, 0x33, 0x44};
        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor);
        pass.End();

        commands = encoder.Finish();
    }

    // --- capture ---
    auto recorder = Recorder::CreateAndStart(device);

    queue.Submit(1, &commands);

    // --- replay ---
    auto replay = recorder->Replay(device);

    utils::RGBA8 expected[] = {{0x11, 0x22, 0x33, 0x44}};
    ExpectTextureEQ(replay, "myTexture", {1}, expected);
}

// Capture and replay the a render pass where a texture is rendered into another.
TEST_P(CaptureAndReplayTests, CaptureRenderPassBasicWithBindGroup) {
    wgpu::Texture srcTexture =
        CreateTexture("srcTexture", {1}, wgpu::TextureFormat::RGBA8Uint,
                      wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopyDst);

    const uint8_t myData[] = {0x11, 0x22, 0x33, 0x44};
    WriteFullTexture(srcTexture, wgpu::TextureFormat::RGBA8Uint, {1}, myData);

    wgpu::Texture dstTexture = CreateTexture("dstTexture", {1}, wgpu::TextureFormat::RGBA8Uint,
                                             wgpu::TextureUsage::RenderAttachment);

    const char* shader = R"(
        @group(0) @binding(0) var tex: texture_2d<u32>;

        @vertex fn vs() -> @builtin(position) vec4f {
            return vec4f(0.0, 0.0, 0.0, 1.0);
        }

        @fragment fn fs() -> @location(0) vec4u {
            return textureLoad(tex, vec2u(0), 0);
        }
    )";
    auto module = utils::CreateShaderModule(device, shader);

    utils::ComboRenderPipelineDescriptor desc;
    desc.vertex.module = module;
    desc.cFragment.module = module;
    desc.cFragment.targetCount = 1;
    desc.cTargets[0].format = wgpu::TextureFormat::RGBA8Uint;
    desc.primitive.topology = wgpu::PrimitiveTopology::PointList;
    wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&desc);

    wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0),
                                                     {
                                                         {0, srcTexture.CreateView()},
                                                     });
    wgpu::CommandBuffer commands;
    {
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();

        utils::ComboRenderPassDescriptor passDescriptor({dstTexture.CreateView()});
        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor);
        pass.SetPipeline(pipeline);
        pass.SetBindGroup(0, bindGroup);
        pass.Draw(1);
        pass.End();

        commands = encoder.Finish();
    }

    // --- capture ---
    auto recorder = Recorder::CreateAndStart(device);

    queue.Submit(1, &commands);

    // --- replay ---
    auto replay = recorder->Replay(device);

    utils::RGBA8 expected[] = {{0x11, 0x22, 0x33, 0x44}};
    ExpectTextureEQ(replay, "dstTexture", {1}, expected);
}

TEST_P(CaptureAndReplayTests, CaptureRenderPassBasicWithAttributes) {
    const float myVertices[] = {
        -1, -1, 3, -1, -1, 3,
    };

    wgpu::Buffer vertexBuffer = CreateBuffer(
        "vertexBuffer", sizeof(myVertices), wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Vertex);
    queue.WriteBuffer(vertexBuffer, 0, &myVertices, sizeof(myVertices));

    wgpu::Texture dstTexture = CreateTexture("dstTexture", {1}, wgpu::TextureFormat::RGBA8Uint,
                                             wgpu::TextureUsage::RenderAttachment);

    const char* shader = R"(
        @vertex fn vs(@location(0) pos: vec4f) -> @builtin(position) vec4f {
            return pos;
        }

        @fragment fn fs() -> @location(0) vec4u {
            return vec4u(0x11, 0x22, 0x33, 0x44);
        }
    )";
    auto module = utils::CreateShaderModule(device, shader);

    utils::ComboRenderPipelineDescriptor desc;
    desc.vertex.module = module;
    desc.cFragment.module = module;
    desc.cFragment.targetCount = 1;
    desc.cTargets[0].format = wgpu::TextureFormat::RGBA8Uint;
    desc.primitive.topology = wgpu::PrimitiveTopology::TriangleList;
    desc.cBuffers[0].arrayStride = 2 * sizeof(float);
    desc.cBuffers[0].attributeCount = 1;
    desc.cBuffers[0].attributes = &desc.cAttributes[0];
    desc.cAttributes[0].shaderLocation = 0;
    desc.cAttributes[0].format = wgpu::VertexFormat::Float32x2;
    desc.vertex.bufferCount = 1;
    wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&desc);

    wgpu::CommandBuffer commands;
    {
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();

        utils::ComboRenderPassDescriptor passDescriptor({dstTexture.CreateView()});
        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor);
        pass.SetPipeline(pipeline);
        pass.SetVertexBuffer(0, vertexBuffer);
        pass.Draw(3);
        pass.End();

        commands = encoder.Finish();
    }

    // --- capture ---
    auto recorder = Recorder::CreateAndStart(device);

    queue.Submit(1, &commands);

    // --- replay ---
    auto replay = recorder->Replay(device);

    utils::RGBA8 expected[] = {{0x11, 0x22, 0x33, 0x44}};
    ExpectTextureEQ(replay, "dstTexture", {1}, expected);
}

// Capture and replay a compute shader with an explicit bindGroupLayout
TEST_P(CaptureAndReplayTests, CaptureComputeShaderBasicExplicitBindGroup) {
    wgpu::BindGroupLayoutEntry entries[1];
    entries[0].binding = 0;
    entries[0].visibility = wgpu::ShaderStage::Compute;
    entries[0].buffer.type = wgpu::BufferBindingType::Storage;

    wgpu::BindGroupLayoutDescriptor bglDesc;
    bglDesc.entryCount = 1;
    bglDesc.entries = entries;
    wgpu::BindGroupLayout layout = device.CreateBindGroupLayout(&bglDesc);

    wgpu::PipelineLayoutDescriptor plDesc;
    plDesc.bindGroupLayoutCount = 1;
    plDesc.bindGroupLayouts = &layout;
    wgpu::PipelineLayout pipelineLayout = device.CreatePipelineLayout(&plDesc);

    const char* label = "MyBuffer";
    wgpu::Buffer buffer =
        CreateBuffer(label, 4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst);

    const char* shader = R"(
        @group(0) @binding(0) var<storage, read_write> result : u32;

        @compute @workgroup_size(1) fn main() {
            result = 0x44332211;
        }
    )";
    auto module = utils::CreateShaderModule(device, shader);

    wgpu::ComputePipelineDescriptor csDesc;
    csDesc.layout = pipelineLayout;
    csDesc.compute.module = module;
    wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc);

    wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0),
                                                     {
                                                         {0, buffer},
                                                     });

    wgpu::CommandBuffer commands;
    {
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
        wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
        pass.SetBindGroup(0, bindGroup);
        pass.SetPipeline(pipeline);
        pass.DispatchWorkgroups(1);
        pass.End();

        commands = encoder.Finish();
    }

    // --- capture ---
    auto recorder = Recorder::CreateAndStart(device);

    queue.Submit(1, &commands);

    // --- replay ---
    auto replay = recorder->Replay(device);

    uint8_t expected[] = {0x11, 0x22, 0x33, 0x44};
    ExpectBufferEQ(replay, label, expected);
}

// Capture and replay a pass that uses a storage texture
TEST_P(CaptureAndReplayTests, CaptureStorageTextureUsageWithExplicitBindGroupLayout) {
    wgpu::BindGroupLayoutEntry entries[1];
    entries[0].binding = 0;
    entries[0].visibility = wgpu::ShaderStage::Compute;
    entries[0].storageTexture.access = wgpu::StorageTextureAccess::WriteOnly;
    entries[0].storageTexture.format = wgpu::TextureFormat::RGBA8Uint;

    wgpu::BindGroupLayoutDescriptor bglDesc;
    bglDesc.entryCount = 1;
    bglDesc.entries = entries;
    wgpu::BindGroupLayout layout = device.CreateBindGroupLayout(&bglDesc);

    wgpu::PipelineLayoutDescriptor plDesc;
    plDesc.bindGroupLayoutCount = 1;
    plDesc.bindGroupLayouts = &layout;
    wgpu::PipelineLayout pipelineLayout = device.CreatePipelineLayout(&plDesc);

    wgpu::Texture texture =
        CreateTexture("myTexture", {1}, wgpu::TextureFormat::RGBA8Uint,
                      wgpu::TextureUsage::StorageBinding | wgpu::TextureUsage::CopySrc);

    const char* shader = R"(
        @group(0) @binding(0) var tex: texture_storage_2d<rgba8uint, write>;

        @compute @workgroup_size(1) fn main() {
            textureStore(tex, vec2u(0), vec4u(0x11, 0x22, 0x33, 0x44));
        }
    )";
    auto module = utils::CreateShaderModule(device, shader);

    wgpu::ComputePipelineDescriptor csDesc;
    csDesc.layout = pipelineLayout;
    csDesc.compute.module = module;
    wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc);

    wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0),
                                                     {
                                                         {0, texture.CreateView()},
                                                     });

    wgpu::CommandBuffer commands;
    {
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
        wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
        pass.SetBindGroup(0, bindGroup);
        pass.SetPipeline(pipeline);
        pass.DispatchWorkgroups(1);
        pass.End();

        commands = encoder.Finish();
    }

    // --- capture ---
    auto recorder = Recorder::CreateAndStart(device);

    queue.Submit(1, &commands);

    // --- replay ---
    auto replay = recorder->Replay(device);

    utils::RGBA8 expected[] = {{0x11, 0x22, 0x33, 0x44}};
    ExpectTextureEQ(replay, "myTexture", {1}, expected);
}

// Capture and replay a pass that uses a texture binding with explicit bind group layout.
TEST_P(CaptureAndReplayTests, CaptureTextureUsageWithExplicitBindGroupLayout) {
    wgpu::BindGroupLayoutEntry entries[2];
    entries[0].binding = 2;
    entries[0].visibility = wgpu::ShaderStage::Compute;
    entries[0].texture.sampleType = wgpu::TextureSampleType::Uint;
    entries[0].texture.viewDimension = wgpu::TextureViewDimension::e2D;
    entries[1].binding = 4;
    entries[1].visibility = wgpu::ShaderStage::Compute;
    entries[1].buffer.type = wgpu::BufferBindingType::Storage;

    wgpu::BindGroupLayoutDescriptor bglDesc;
    bglDesc.entryCount = 2;
    bglDesc.entries = entries;
    wgpu::BindGroupLayout layout = device.CreateBindGroupLayout(&bglDesc);

    wgpu::PipelineLayoutDescriptor plDesc;
    plDesc.bindGroupLayoutCount = 1;
    plDesc.bindGroupLayouts = &layout;
    wgpu::PipelineLayout pipelineLayout = device.CreatePipelineLayout(&plDesc);

    wgpu::Texture texture =
        CreateTexture("myTexture", {1}, wgpu::TextureFormat::RGBA8Uint,
                      wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopyDst);

    const uint8_t myData[] = {0x11, 0x22, 0x33, 0x44};
    WriteFullTexture(texture, wgpu::TextureFormat::RGBA8Uint, {1}, myData);

    wgpu::Buffer buffer = CreateBuffer(
        "myBuffer", 4,
        wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc);

    const char* shader = R"(
        @group(0) @binding(2) var tex: texture_2d<u32>;
        @group(0) @binding(4) var<storage, read_write> result: u32;

        @compute @workgroup_size(1) fn main() {
            let c = textureLoad(tex, vec2u(0), 0);
            result = pack4xU8Clamp(c);
        }
    )";
    auto module = utils::CreateShaderModule(device, shader);

    wgpu::ComputePipelineDescriptor csDesc;
    csDesc.layout = pipelineLayout;
    csDesc.compute.module = module;
    wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc);

    wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0),
                                                     {
                                                         {2, texture.CreateView()},
                                                         {4, buffer},
                                                     });

    wgpu::CommandBuffer commands;
    {
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
        wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
        pass.SetBindGroup(0, bindGroup);
        pass.SetPipeline(pipeline);
        pass.DispatchWorkgroups(1);
        pass.End();

        commands = encoder.Finish();
    }

    // --- capture ---
    auto recorder = Recorder::CreateAndStart(device);

    queue.Submit(1, &commands);

    // --- replay ---
    auto replay = recorder->Replay(device);

    uint8_t expected[] = {0x11, 0x22, 0x33, 0x44};
    ExpectBufferEQ(replay, "myBuffer", expected);
}

// Capture and replay a pass that uses a sampler binding with explicit bind group layout.
TEST_P(CaptureAndReplayTests, CaptureSamplerUsageWithExplicitBindGroupLayout) {
    wgpu::BindGroupLayoutEntry entries[2];
    entries[0].binding = 2;
    entries[0].visibility = wgpu::ShaderStage::Fragment;
    entries[0].texture.sampleType = wgpu::TextureSampleType::Float;
    entries[0].texture.viewDimension = wgpu::TextureViewDimension::e2D;
    entries[1].binding = 4;
    entries[1].visibility = wgpu::ShaderStage::Fragment;
    entries[1].sampler.type = wgpu::SamplerBindingType::Filtering;

    wgpu::BindGroupLayoutDescriptor bglDesc;
    bglDesc.entryCount = 2;
    bglDesc.entries = entries;
    wgpu::BindGroupLayout layout = device.CreateBindGroupLayout(&bglDesc);

    wgpu::PipelineLayoutDescriptor plDesc;
    plDesc.bindGroupLayoutCount = 1;
    plDesc.bindGroupLayouts = &layout;
    wgpu::PipelineLayout pipelineLayout = device.CreatePipelineLayout(&plDesc);

    wgpu::Texture srcTexture =
        CreateTexture("srcTexture", {1}, wgpu::TextureFormat::RGBA8Unorm,
                      wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopyDst);

    const uint8_t myData[] = {0x11, 0x22, 0x33, 0x44};
    WriteFullTexture(srcTexture, wgpu::TextureFormat::RGBA8Unorm, {1}, myData);

    wgpu::Sampler sampler = device.CreateSampler();

    const char* shader = R"(
        @group(0) @binding(2) var tex: texture_2d<f32>;
        @group(0) @binding(4) var smp: sampler;

        @vertex fn vs() -> @builtin(position) vec4f {
            return vec4f(0, 0, 0, 1);
        }

        @fragment fn fs() -> @location(0) vec4f {
            return textureSample(tex, smp, vec2f(0));
        }
    )";
    auto module = utils::CreateShaderModule(device, shader);

    utils::ComboRenderPipelineDescriptor desc;
    desc.vertex.module = module;
    desc.cFragment.module = module;
    desc.cFragment.targetCount = 1;
    desc.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm;
    desc.primitive.topology = wgpu::PrimitiveTopology::PointList;
    wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&desc);

    wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0),
                                                     {
                                                         {2, srcTexture.CreateView()},
                                                         {4, sampler},
                                                     });

    wgpu::Texture dstTexture =
        CreateTexture("dstTexture", {1}, wgpu::TextureFormat::RGBA8Unorm,
                      wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc);

    wgpu::CommandBuffer commands;
    {
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();

        utils::ComboRenderPassDescriptor passDescriptor({dstTexture.CreateView()});
        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor);
        pass.SetBindGroup(0, bindGroup);
        pass.SetPipeline(pipeline);
        pass.Draw(1);
        pass.End();

        commands = encoder.Finish();
    }

    // --- capture ---
    auto recorder = Recorder::CreateAndStart(device);

    queue.Submit(1, &commands);

    // --- replay ---
    auto replay = recorder->Replay(device);

    utils::RGBA8 expected[] = {{0x11, 0x22, 0x33, 0x44}};
    ExpectTextureEQ(replay, "dstTexture", {1}, expected);
}

// Capture and replay a pass uses a depth attachment.
// This was failing because the front end does not save the user's
// stencilLoadOp and stencilStoreOp as the spec requires "undefined"
TEST_P(CaptureAndReplayTests, CaptureDepthRenderPass) {
    wgpu::Texture texture = CreateTexture("texture", {1}, wgpu::TextureFormat::RGBA8Unorm,
                                          wgpu::TextureUsage::RenderAttachment);

    wgpu::Texture depthTexture =
        CreateTexture("depthTexture", {1}, wgpu::TextureFormat::Depth16Unorm,
                      wgpu::TextureUsage::RenderAttachment);

    wgpu::CommandBuffer commands;
    {
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();

        utils::ComboRenderPassDescriptor passDescriptor({texture.CreateView()},
                                                        depthTexture.CreateView());
        passDescriptor.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Undefined;
        passDescriptor.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Undefined;
        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor);
        pass.End();

        commands = encoder.Finish();
    }

    // --- capture ---
    auto recorder = Recorder::CreateAndStart(device);

    queue.Submit(1, &commands);

    // --- replay ---
    recorder->Replay(device);

    // We just expect no errors.
}

constexpr static uint64_t kSentinelValue = ~uint64_t(0u);
class OcclusionExpectation : public detail::Expectation {
  public:
    enum class Result { Zero, NonZero };

    ~OcclusionExpectation() override = default;

    explicit OcclusionExpectation(const std::vector<Result> expected) { mExpected = expected; }

    testing::AssertionResult Check(const void* data, size_t size) override {
        DAWN_ASSERT(size % sizeof(uint64_t) == 0);
        DAWN_ASSERT(size / sizeof(uint64_t) == mExpected.size());
        const uint64_t* actual = static_cast<const uint64_t*>(data);
        for (size_t i = 0; i < size / sizeof(uint64_t); i++) {
            if (DAWN_UNSAFE_TODO(actual[i]) == kSentinelValue) {
                return testing::AssertionFailure()
                       << "Data[" << i << "] was not written (it kept the sentinel value of "
                       << kSentinelValue << ").\n";
            }
            Result expected = mExpected[i];
            if (expected == Result::Zero && DAWN_UNSAFE_TODO(actual[i]) != 0) {
                return testing::AssertionFailure()
                       << "Expected data[" << i
                       << "] to be zero, actual: " << DAWN_UNSAFE_TODO(actual[i]) << ".\n";
            }
            if (expected == Result::NonZero && DAWN_UNSAFE_TODO(actual[i]) == 0) {
                return testing::AssertionFailure()
                       << "Expected data[" << i << "] to be non-zero.\n";
            }
        }

        return testing::AssertionSuccess();
    }

  private:
    std::vector<Result> mExpected;
};

// Capture and replay a pass that uses a QuerySet.
// We use a point-list vertex shader that we can set the z value by passing a different vertex_index
// via the firstVertex argument to draw.
TEST_P(CaptureAndReplayTests, CaptureQuerySetBasic) {
    const char* shader = R"(
        @vertex fn vs(@builtin(vertex_index) vNdx: u32) -> @builtin(position) vec4f {
            return vec4f(0, 0, f32(vNdx) / 10.0, 1);
        }

        @fragment fn fs() -> @location(0) vec4f {
            return vec4f(0);
        }
    )";
    auto module = utils::CreateShaderModule(device, shader);

    utils::ComboRenderPipelineDescriptor desc;
    desc.vertex.module = module;
    desc.cFragment.module = module;
    desc.cFragment.targetCount = 1;
    desc.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm;
    desc.primitive.topology = wgpu::PrimitiveTopology::PointList;

    wgpu::DepthStencilState* depthStencil =
        desc.EnableDepthStencil(wgpu::TextureFormat::Depth16Unorm);
    depthStencil->depthWriteEnabled = wgpu::OptionalBool::True;
    depthStencil->depthCompare = wgpu::CompareFunction::Less;

    wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&desc);

    wgpu::Texture dstTexture =
        CreateTexture("dstTexture", {1}, wgpu::TextureFormat::RGBA8Unorm,
                      wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc);
    wgpu::Texture depthTexture =
        CreateTexture("depthTexture", {1}, wgpu::TextureFormat::Depth16Unorm,
                      wgpu::TextureUsage::RenderAttachment);

    constexpr uint32_t kNumQueries = 4;
    wgpu::QuerySetDescriptor qsDesc;
    qsDesc.label = "myQuerySet";
    qsDesc.count = kNumQueries;
    qsDesc.type = wgpu::QueryType::Occlusion;
    wgpu::QuerySet querySet = device.CreateQuerySet(&qsDesc);

    wgpu::CommandBuffer commands;
    {
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();

        utils::ComboRenderPassDescriptor passDescriptor({dstTexture.CreateView()},
                                                        depthTexture.CreateView());
        passDescriptor.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Undefined;
        passDescriptor.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Undefined;
        passDescriptor.occlusionQuerySet = querySet;

        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor);
        pass.SetPipeline(pipeline);

        uint32_t nextIndex = 0;
        auto DrawPixelAtDepthWithOcclusionTest = [&](uint32_t depth) {
            pass.BeginOcclusionQuery(nextIndex++);
            pass.Draw(1, 1, depth, 0);
            pass.EndOcclusionQuery();
        };

        DrawPixelAtDepthWithOcclusionTest(5);  // draws at 0.5 (not occluded)
        DrawPixelAtDepthWithOcclusionTest(7);  // draws at 0.7 (occluded)
        DrawPixelAtDepthWithOcclusionTest(2);  // draws at 0.2 (not-occluded)
        DrawPixelAtDepthWithOcclusionTest(5);  // draws at 0.5 (occluded)

        pass.End();

        commands = encoder.Finish();
    }

    // --- capture ---
    auto recorder = Recorder::CreateAndStart(device);

    queue.Submit(1, &commands);

    // --- replay ---
    auto replay = recorder->Replay(device);

    {
        auto qs = replay->GetObjectByLabel<wgpu::QuerySet>("myQuerySet");
        ASSERT_TRUE(qs);

        uint64_t size = sizeof(uint64_t) * kNumQueries;
        auto resolveBuffer =
            CreateBuffer("", size,
                         wgpu::BufferUsage::QueryResolve | wgpu::BufferUsage::CopySrc |
                             wgpu::BufferUsage::CopyDst);
        std::vector<uint64_t> sentinels(kNumQueries, kSentinelValue);
        queue.WriteBuffer(resolveBuffer, 0, sentinels.data(), size);

        {
            wgpu::CommandBuffer commands;
            wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
            encoder.ResolveQuerySet(qs, 0, kNumQueries, resolveBuffer, 0);
            commands = encoder.Finish();
            queue.Submit(1, &commands);
        }

        EXPECT_BUFFER(
            resolveBuffer, 0, size,
            new OcclusionExpectation(
                {OcclusionExpectation::Result::NonZero, OcclusionExpectation::Result::Zero,
                 OcclusionExpectation::Result::NonZero, OcclusionExpectation::Result::Zero}));
    }
}

// Capture and replay a render bundle.
TEST_P(CaptureAndReplayTests, CaptureRenderBundleBasic) {
    const char* shader = R"(
        @vertex fn vs() -> @builtin(position) vec4f {
            return vec4f(0, 0, 0, 1);
        }

        @fragment fn fs() -> @location(0) vec4u {
            return vec4u(0x11, 0x22, 0x33, 0x44);
        }
    )";
    auto module = utils::CreateShaderModule(device, shader);

    utils::ComboRenderPipelineDescriptor passDesc;
    passDesc.vertex.module = module;
    passDesc.cFragment.module = module;
    passDesc.cFragment.targetCount = 1;
    passDesc.cTargets[0].format = wgpu::TextureFormat::RGBA8Uint;
    passDesc.primitive.topology = wgpu::PrimitiveTopology::PointList;
    wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&passDesc);

    utils::ComboRenderBundleEncoderDescriptor desc = {};
    desc.colorFormatCount = 1;
    desc.cColorFormats[0] = wgpu::TextureFormat::RGBA8Uint;

    wgpu::RenderBundleEncoder renderBundleEncoder = device.CreateRenderBundleEncoder(&desc);

    renderBundleEncoder.SetPipeline(pipeline);
    renderBundleEncoder.Draw(1);

    wgpu::RenderBundle renderBundle = renderBundleEncoder.Finish();

    wgpu::Texture dstTexture =
        CreateTexture("dstTexture", {1}, wgpu::TextureFormat::RGBA8Uint,
                      wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc);

    wgpu::CommandBuffer commands;
    {
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();

        utils::ComboRenderPassDescriptor passDescriptor({dstTexture.CreateView()});
        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor);
        pass.ExecuteBundles(1, &renderBundle);
        pass.End();

        commands = encoder.Finish();
    }

    // --- capture ---
    auto recorder = Recorder::CreateAndStart(device);

    queue.Submit(1, &commands);

    // --- replay ---
    auto replay = recorder->Replay(device);

    utils::RGBA8 expected[] = {{0x11, 0x22, 0x33, 0x44}};
    ExpectTextureEQ(replay, "dstTexture", {1}, expected);
}

// Test debug commands don't fail.
TEST_P(CaptureAndReplayTests, PushPopInsertDebug) {
    utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, 4, 4);

    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
    encoder.PushDebugGroup("Event Start");
    encoder.InsertDebugMarker("Marker");
    encoder.PopDebugGroup();
    {
        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
        pass.PushDebugGroup("Event Start");
        pass.InsertDebugMarker("Marker");
        pass.PopDebugGroup();
        pass.End();
    }
    {
        wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
        pass.PushDebugGroup("Event Start");
        pass.InsertDebugMarker("Marker");
        pass.PopDebugGroup();
        pass.End();
    }

    wgpu::CommandBuffer commands = encoder.Finish();

    // --- capture ---
    auto recorder = Recorder::CreateAndStart(device);

    queue.Submit(1, &commands);

    // --- replay ---
    recorder->Replay(device);

    // just expect no errors.
}

// Test capturing setting a label
TEST_P(CaptureAndReplayTests, CaptureSetLabel) {
    wgpu::Buffer buffer = CreateBuffer("buf", 4, wgpu::BufferUsage::CopyDst);
    wgpu::Texture texture =
        CreateTexture("tex", {1}, wgpu::TextureFormat::RGBA8Uint,
                      wgpu::TextureUsage::StorageBinding | wgpu::TextureUsage::CopySrc);
    wgpu::TextureView view = texture.CreateView();
    wgpu::Sampler sampler = device.CreateSampler();

    wgpu::QuerySetDescriptor qsDesc;
    qsDesc.count = 1;
    qsDesc.type = wgpu::QueryType::Occlusion;
    wgpu::QuerySet querySet = device.CreateQuerySet(&qsDesc);

    const char* shader = R"(
        @group(0) @binding(0) var tex: texture_storage_2d<rgba8uint, write>;

        @compute @workgroup_size(1) fn main() {
            textureStore(tex, vec2u(0), vec4<u32>(0));
        }

        @vertex fn vs() -> @builtin(position) vec4f {
            return vec4f(0);
        }

        @fragment fn fs() -> @location(0) vec4u {
            return vec4u(0);
        }
    )";
    auto module = utils::CreateShaderModule(device, shader);

    wgpu::BindGroupLayoutEntry entries[1];
    entries[0].binding = 0;
    entries[0].visibility = wgpu::ShaderStage::Compute;
    entries[0].buffer.type = wgpu::BufferBindingType::Storage;

    wgpu::BindGroupLayoutDescriptor bglDesc;
    bglDesc.entryCount = 1;
    bglDesc.entries = entries;
    wgpu::BindGroupLayout layout = device.CreateBindGroupLayout(&bglDesc);

    wgpu::PipelineLayoutDescriptor plDesc;
    plDesc.bindGroupLayoutCount = 1;
    plDesc.bindGroupLayouts = &layout;
    wgpu::PipelineLayout pipelineLayout = device.CreatePipelineLayout(&plDesc);

    wgpu::ComputePipelineDescriptor csDesc;
    csDesc.compute.module = module;
    wgpu::ComputePipeline cPipeline = device.CreateComputePipeline(&csDesc);

    wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, cPipeline.GetBindGroupLayout(0),
                                                     {
                                                         {0, view},
                                                     });

    utils::ComboRenderPipelineDescriptor desc;
    desc.vertex.module = module;
    desc.cFragment.module = module;
    desc.cFragment.targetCount = 1;
    desc.cTargets[0].format = wgpu::TextureFormat::RGBA8Uint;
    desc.primitive.topology = wgpu::PrimitiveTopology::PointList;
    wgpu::RenderPipeline rPipeline = device.CreateRenderPipeline(&desc);

    utils::ComboRenderBundleEncoderDescriptor rbDesc = {};
    rbDesc.colorFormatCount = 1;
    rbDesc.cColorFormats[0] = wgpu::TextureFormat::RGBA8Uint;

    wgpu::RenderBundleEncoder renderBundleEncoder = device.CreateRenderBundleEncoder(&rbDesc);
    wgpu::RenderBundle renderBundle = renderBundleEncoder.Finish();

    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
    wgpu::CommandBuffer commandBuffer = encoder.Finish();

    auto recorder = Recorder::CreateAndStart(device);

    // Note: the first time capture see's a resource here is
    // on SetLabel. The label is set before the resource is captured
    // so we won't see the original label. So, set them twice so we
    // can verify the label changed.
    bindGroup.SetLabel("bgA");
    bindGroup.SetLabel("bgB");
    buffer.SetLabel("bufA");
    buffer.SetLabel("bufB");
    commandBuffer.SetLabel("cbA");
    commandBuffer.SetLabel("cbB");
    cPipeline.SetLabel("cpA");
    cPipeline.SetLabel("cpB");
    device.SetLabel("devA");
    device.SetLabel("devB");
    layout.SetLabel("loA");
    layout.SetLabel("loB");
    pipelineLayout.SetLabel("plA");
    pipelineLayout.SetLabel("plB");
    querySet.SetLabel("qsA");
    querySet.SetLabel("qsB");
    rPipeline.SetLabel("rpA");
    rPipeline.SetLabel("rpB");
    renderBundle.SetLabel("rbA");
    renderBundle.SetLabel("rbB");
    sampler.SetLabel("smpA");
    sampler.SetLabel("smpB");
    module.SetLabel("modA");
    module.SetLabel("modB");
    texture.SetLabel("texA");
    texture.SetLabel("texB");
    view.SetLabel("viewA");
    view.SetLabel("viewB");

    auto replay = recorder->Replay(device);

    EXPECT_TRUE(replay->GetObjectByLabel<wgpu::BindGroup>("bgA") == nullptr);
    EXPECT_TRUE(replay->GetObjectByLabel<wgpu::BindGroup>("bgB") != nullptr);

    EXPECT_TRUE(replay->GetObjectByLabel<wgpu::Buffer>("buf") == nullptr);
    EXPECT_TRUE(replay->GetObjectByLabel<wgpu::Buffer>("bufA") == nullptr);
    EXPECT_TRUE(replay->GetObjectByLabel<wgpu::Buffer>("bufB") != nullptr);

    EXPECT_TRUE(replay->GetObjectByLabel<wgpu::CommandBuffer>("cbA") == nullptr);
    EXPECT_TRUE(replay->GetObjectByLabel<wgpu::CommandBuffer>("cbB") != nullptr);

    EXPECT_TRUE(replay->GetObjectByLabel<wgpu::ComputePipeline>("cpA") == nullptr);
    EXPECT_TRUE(replay->GetObjectByLabel<wgpu::ComputePipeline>("cpB") != nullptr);

    EXPECT_TRUE(replay->GetObjectByLabel<wgpu::Device>("devA") == nullptr);
    EXPECT_TRUE(replay->GetObjectByLabel<wgpu::Device>("devB") != nullptr);

    EXPECT_TRUE(replay->GetObjectByLabel<wgpu::BindGroupLayout>("loA") == nullptr);
    EXPECT_TRUE(replay->GetObjectByLabel<wgpu::BindGroupLayout>("loB") != nullptr);

    EXPECT_TRUE(replay->GetObjectByLabel<wgpu::PipelineLayout>("plA") == nullptr);
    EXPECT_TRUE(replay->GetObjectByLabel<wgpu::PipelineLayout>("plB") != nullptr);

    EXPECT_TRUE(replay->GetObjectByLabel<wgpu::RenderBundle>("rbA") == nullptr);
    EXPECT_TRUE(replay->GetObjectByLabel<wgpu::RenderBundle>("rbB") != nullptr);

    EXPECT_TRUE(replay->GetObjectByLabel<wgpu::RenderPipeline>("rpA") == nullptr);
    EXPECT_TRUE(replay->GetObjectByLabel<wgpu::RenderPipeline>("rpB") != nullptr);

    EXPECT_TRUE(replay->GetObjectByLabel<wgpu::QuerySet>("qsA") == nullptr);
    EXPECT_TRUE(replay->GetObjectByLabel<wgpu::QuerySet>("qsB") != nullptr);

    EXPECT_TRUE(replay->GetObjectByLabel<wgpu::Sampler>("smpA") == nullptr);
    EXPECT_TRUE(replay->GetObjectByLabel<wgpu::Sampler>("smpB") != nullptr);

    EXPECT_TRUE(replay->GetObjectByLabel<wgpu::ShaderModule>("modA") == nullptr);
    EXPECT_TRUE(replay->GetObjectByLabel<wgpu::ShaderModule>("modB") != nullptr);

    EXPECT_TRUE(replay->GetObjectByLabel<wgpu::Texture>("texA") == nullptr);
    EXPECT_TRUE(replay->GetObjectByLabel<wgpu::Texture>("texB") != nullptr);

    EXPECT_TRUE(replay->GetObjectByLabel<wgpu::TextureView>("viewA") == nullptr);
    EXPECT_TRUE(replay->GetObjectByLabel<wgpu::TextureView>("viewB") != nullptr);
}

// Capture SetBlendConstant.
TEST_P(CaptureAndReplayTests, CaptureSetBlendConstant) {
    wgpu::Texture dstTexture = CreateTexture("dstTexture", {1}, wgpu::TextureFormat::RGBA8Unorm,
                                             wgpu::TextureUsage::RenderAttachment);

    const char* shader = R"(
        @vertex fn vs() -> @builtin(position) vec4f {
            return vec4f(0, 0, 0, 1);
        }

        @fragment fn fs() -> @location(0) vec4f {
            return vec4f(1);
        }
    )";
    auto module = utils::CreateShaderModule(device, shader);

    utils::ComboRenderPipelineDescriptor desc;
    desc.vertex.module = module;
    desc.cFragment.module = module;
    desc.cFragment.targetCount = 1;
    desc.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm;
    desc.cTargets[0].blend = &desc.cBlends[0];
    desc.cBlends[0].color.operation = wgpu::BlendOperation::Add;
    desc.cBlends[0].color.srcFactor = wgpu::BlendFactor::Constant;
    desc.cBlends[0].color.dstFactor = wgpu::BlendFactor::Zero;
    desc.cBlends[0].alpha.operation = wgpu::BlendOperation::Add;
    desc.cBlends[0].alpha.srcFactor = wgpu::BlendFactor::Constant;
    desc.cBlends[0].alpha.dstFactor = wgpu::BlendFactor::Zero;
    desc.primitive.topology = wgpu::PrimitiveTopology::PointList;
    wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&desc);

    wgpu::CommandBuffer commands;
    {
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();

        utils::ComboRenderPassDescriptor passDescriptor({dstTexture.CreateView()});
        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor);
        pass.SetPipeline(pipeline);
        // the +0.1 is to try get the same result on all GPUs.
        wgpu::Color color = {11.1 / 255.0, 22.1 / 255.0, 33.1 / 255.0, 44.1 / 255.0};
        pass.SetBlendConstant(&color);
        pass.Draw(1);
        pass.End();

        commands = encoder.Finish();
    }

    // --- capture ---
    auto recorder = Recorder::CreateAndStart(device);

    queue.Submit(1, &commands);

    // --- replay ---
    auto replay = recorder->Replay(device);

    utils::RGBA8 expected[] = {{11, 22, 33, 44}};
    ExpectTextureEQ(replay, "dstTexture", {1}, expected);
}

// Capture DispatchIndirect.
TEST_P(CaptureAndReplayTests, CaptureDispatchIndirect) {
    const char* shader = R"(
        @group(0) @binding(0) var<storage, read_write> result : u32;

        @compute @workgroup_size(1) fn main() {
            result = 0x44332211;
        }
    )";
    auto module = utils::CreateShaderModule(device, shader);

    const uint32_t myData[] = {0x1, 0x1, 0x1};
    wgpu::Buffer indirectBuffer = CreateBuffer(
        "indirect", sizeof(myData), wgpu::BufferUsage::Indirect | wgpu::BufferUsage::CopyDst);
    queue.WriteBuffer(indirectBuffer, 0, &myData, sizeof(myData));

    wgpu::ComputePipelineDescriptor csDesc;
    csDesc.compute.module = module;
    wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc);

    const char* label = "MyBuffer";
    wgpu::Buffer buffer = CreateBuffer(label, 4, wgpu::BufferUsage::Storage);

    wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0),
                                                     {
                                                         {0, buffer},
                                                     });

    wgpu::CommandBuffer commands;
    {
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
        wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
        pass.SetBindGroup(0, bindGroup);
        pass.SetPipeline(pipeline);
        pass.DispatchWorkgroupsIndirect(indirectBuffer, 0);
        pass.End();

        commands = encoder.Finish();
    }

    // --- capture ---
    auto recorder = Recorder::CreateAndStart(device);

    queue.Submit(1, &commands);

    // --- replay ---
    auto replay = recorder->Replay(device);

    uint8_t expected[] = {0x11, 0x22, 0x33, 0x44};
    ExpectBufferEQ(replay, label, expected);
}

// Capture ClearBuffer.
TEST_P(CaptureAndReplayTests, CaptureClearBuffer) {
    const uint32_t myData[] = {0x11111111, 0x22222222, 0x33333333};
    wgpu::Buffer buffer = CreateBuffer("buf", sizeof(myData),
                                       wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst);
    queue.WriteBuffer(buffer, 0, &myData, sizeof(myData));

    wgpu::CommandBuffer commands;
    {
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
        encoder.ClearBuffer(buffer, 4, 4);
        commands = encoder.Finish();
    }

    // --- capture ---
    auto recorder = Recorder::CreateAndStart(device);

    queue.Submit(1, &commands);

    // --- replay ---
    auto replay = recorder->Replay(device);

    const uint8_t expected[] = {0x11, 0x11, 0x11, 0x11, 0, 0, 0, 0, 0x33, 0x33, 0x33, 0x33};
    ExpectBufferEQ(replay, "buf", expected);
}

TEST_P(CaptureAndReplayTests, SetImmediateComputePass) {
    const char* shader = R"(
        var<immediate> value: u32;
        @group(0) @binding(0) var<storage, read_write> result : u32;

        @compute @workgroup_size(1) fn main() {
            result = value;
        }
    )";
    auto module = utils::CreateShaderModule(device, shader);

    wgpu::BindGroupLayoutEntry entries[1];
    entries[0].binding = 0;
    entries[0].visibility = wgpu::ShaderStage::Compute;
    entries[0].buffer.type = wgpu::BufferBindingType::Storage;

    wgpu::BindGroupLayoutDescriptor bglDesc;
    bglDesc.entryCount = 1;
    bglDesc.entries = entries;
    wgpu::BindGroupLayout layout = device.CreateBindGroupLayout(&bglDesc);

    wgpu::PipelineLayoutDescriptor plDesc;
    plDesc.label = "immediatePipelineLayout";
    plDesc.bindGroupLayoutCount = 1;
    plDesc.bindGroupLayouts = &layout;
    plDesc.immediateSize = 4;
    wgpu::PipelineLayout pipelineLayout = device.CreatePipelineLayout(&plDesc);

    wgpu::ComputePipelineDescriptor csDesc;
    csDesc.label = "immediatePipeline";
    csDesc.layout = pipelineLayout;
    csDesc.compute.module = module;

    wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc);

    const char* label = "MyBuffer";
    wgpu::Buffer buffer = CreateBuffer(label, 4, wgpu::BufferUsage::Storage);

    wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0),
                                                     {
                                                         {0, buffer},
                                                     });

    wgpu::CommandBuffer commands;
    {
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
        wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
        pass.SetBindGroup(0, bindGroup);
        pass.SetPipeline(pipeline);
        const uint8_t data[] = {0x11, 0x22, 0x33, 0x44};
        pass.SetImmediates(0, data, sizeof(data));
        pass.DispatchWorkgroups(1);
        pass.End();

        commands = encoder.Finish();
    }

    // --- capture ---
    auto recorder = Recorder::CreateAndStart(device);

    queue.Submit(1, &commands);

    // --- replay ---
    auto replay = recorder->Replay(device);

    uint8_t expected[] = {0x11, 0x22, 0x33, 0x44};
    ExpectBufferEQ(replay, label, expected);
}

TEST_P(CaptureAndReplayTests, SetImmediateRenderPass) {
    const char* shader = R"(
        var<immediate> value: u32;
        @vertex fn vs() -> @builtin(position) vec4f {
            return vec4f(0, 0, 0, 1);
        }

        @fragment fn fs() -> @location(0) vec4u {
            return vec4u(value);
        }
    )";
    auto module = utils::CreateShaderModule(device, shader);

    wgpu::BindGroupLayoutEntry entries[1];
    entries[0].binding = 0;
    entries[0].visibility = wgpu::ShaderStage::Compute;
    entries[0].buffer.type = wgpu::BufferBindingType::Storage;

    wgpu::BindGroupLayoutDescriptor bglDesc;
    bglDesc.entryCount = 1;
    bglDesc.entries = entries;
    wgpu::BindGroupLayout layout = device.CreateBindGroupLayout(&bglDesc);

    wgpu::PipelineLayoutDescriptor plDesc;
    plDesc.label = "immediatePipelineLayout";
    plDesc.bindGroupLayoutCount = 0;
    plDesc.immediateSize = 4;
    wgpu::PipelineLayout pipelineLayout = device.CreatePipelineLayout(&plDesc);

    utils::ComboRenderPipelineDescriptor desc;
    desc.vertex.module = module;
    desc.cFragment.module = module;
    desc.cFragment.targetCount = 1;
    desc.cTargets[0].format = wgpu::TextureFormat::R32Uint;
    desc.primitive.topology = wgpu::PrimitiveTopology::PointList;
    wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&desc);

    wgpu::Texture dstTexture = CreateTexture("dstTexture", {1}, wgpu::TextureFormat::R32Uint,
                                             wgpu::TextureUsage::RenderAttachment);

    wgpu::CommandBuffer commands;
    {
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
        utils::ComboRenderPassDescriptor passDescriptor({dstTexture.CreateView()});
        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor);
        pass.SetPipeline(pipeline);
        const uint8_t data[] = {0x11, 0x22, 0x33, 0x44};
        pass.SetImmediates(0, data, sizeof(data));
        pass.Draw(1);
        pass.End();

        commands = encoder.Finish();
    }

    // --- capture ---
    auto recorder = Recorder::CreateAndStart(device);

    queue.Submit(1, &commands);

    // --- replay ---
    auto replay = recorder->Replay(device);

    const uint32_t expected[] = {0x44332211};
    ExpectTextureEQ(replay, "dstTexture", {1}, expected);
}

// Make sure it does not capture the unmap of a buffer if that unmap is triggered by the buffer
// destroy.
TEST_P(CaptureAndReplayTests, MappedBufferDestroyed) {
    auto recorder = Recorder::CreateAndStart(device);

    {
        // Create a buffer mapped at creation and then release it.
        wgpu::Buffer buffer = CreateBuffer("MyBuffer", 4, wgpu::BufferUsage::CopyDst, true);
    }

    recorder->Replay(device);

    // just expect no errors
}

// Depth24Plus is not directly copyable as either src or dst so, we make a depth24plus.
// put values in it via render pass. Then capture it in an empty render pass.
// On replay we read the values via a compute shader.
TEST_P(CaptureAndReplayTests, CaptureDepth24Plus) {
    // TODO(477645283): This fails only on WARP and after it fails, all following tests
    // fail to create a device.
    DAWN_SUPPRESS_TEST_IF(IsWARP());

    constexpr uint32_t kNumLayers = 6;
    auto [cPipeline, commands] = [&]() {
        wgpu::Texture texture = CreateTexture(
            "myTexture", {1, 1, kNumLayers}, wgpu::TextureFormat::Depth24Plus,
            wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment);

        const char* shader = R"(
            struct VOut {
              @builtin(position) pos: vec4f,
              @location(0) @interpolate(flat, either) instance_index: u32,
            };

            @vertex fn vs(@builtin(vertex_index) vNdx: u32,
                          @builtin(instance_index) iNdx: u32)
                 -> VOut {
                let pos = array(
                  vec2f(-1, -1),
                  vec2f(-1,  3),
                  vec2f( 3, -1),
                );
                return VOut(vec4f(pos[vNdx], 0, 1), iNdx);
            }

            @fragment fn fs(v: VOut) -> @builtin(frag_depth) f32 {
                return (f32(v.instance_index) + 0.5) / 6.0;
            }

            @group(0) @binding(0) var tex: texture_2d_array<f32>;
            @group(0) @binding(1) var<storage, read_write> result: array<f32>;

            @compute @workgroup_size(1) fn main(@builtin(global_invocation_id) gid: vec3u) {
                result[gid.x] = textureLoad(tex, vec2u(0), gid.x, 0).x;
            }
        )";
        auto module = utils::CreateShaderModule(device, shader);

        // Put values in depth texture. This step is not captured.
        {
            utils::ComboRenderPipelineDescriptor desc;
            desc.vertex.module = module;
            desc.cFragment.module = module;
            desc.cFragment.targetCount = 0;
            desc.primitive.topology = wgpu::PrimitiveTopology::TriangleList;

            wgpu::DepthStencilState* depthStencil =
                desc.EnableDepthStencil(wgpu::TextureFormat::Depth24Plus);
            depthStencil->depthWriteEnabled = wgpu::OptionalBool::True;
            depthStencil->depthCompare = wgpu::CompareFunction::Always;

            wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&desc);

            wgpu::CommandBuffer commands;
            wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
            for (uint32_t layer = 0; layer < kNumLayers; ++layer) {
                wgpu::TextureViewDescriptor viewDesc;
                viewDesc.baseArrayLayer = layer;
                viewDesc.arrayLayerCount = 1;

                utils::ComboRenderPassDescriptor passDescriptor({}, texture.CreateView(&viewDesc));
                passDescriptor.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Load;
                passDescriptor.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Undefined;
                passDescriptor.cDepthStencilAttachmentInfo.stencilStoreOp =
                    wgpu::StoreOp::Undefined;
                wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor);
                pass.SetPipeline(pipeline);
                pass.Draw(3, 1, 0, layer);
                pass.End();
            }
            commands = encoder.Finish();
            queue.Submit(1, &commands);
        }

        wgpu::ComputePipelineDescriptor csDesc;
        csDesc.compute.module = module;
        wgpu::ComputePipeline cPipeline = device.CreateComputePipeline(&csDesc);

        // Copy texture to temp buffer via compute shader during capture.
        // We don't care about the temp buffer. We just care that texture is
        // referenced so it will appear during replay.
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
        {
            wgpu::Buffer tempBuffer =
                CreateBuffer("temp", sizeof(float) * kNumLayers,
                             wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc);
            wgpu::BindGroup bindGroup =
                utils::MakeBindGroup(device, cPipeline.GetBindGroupLayout(0),
                                     {
                                         {0, texture.CreateView()},
                                         {1, tempBuffer},
                                     });

            wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
            pass.SetPipeline(cPipeline);
            pass.SetBindGroup(0, bindGroup);
            pass.DispatchWorkgroups(kNumLayers);
            pass.End();
        }
        return std::make_pair(cPipeline, encoder.Finish());
    }();

    // --- capture ---
    auto recorder = Recorder::CreateAndStart(device);

    queue.Submit(1, &commands);

    // --- replay ---
    auto replay = recorder->Replay(device);

    // Read the replay version of myTexture in result via compute shader.
    wgpu::Buffer result = CreateBuffer("result", sizeof(float) * kNumLayers,
                                       wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc);
    {
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();

        wgpu::Texture texture = replay->GetObjectByLabel<wgpu::Texture>("myTexture");
        wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, cPipeline.GetBindGroupLayout(0),
                                                         {
                                                             {0, texture.CreateView()},
                                                             {1, result},
                                                         });

        wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
        pass.SetPipeline(cPipeline);
        pass.SetBindGroup(0, bindGroup);
        pass.DispatchWorkgroups(kNumLayers);
        pass.End();

        wgpu::CommandBuffer commands = encoder.Finish();
        queue.Submit(1, &commands);
    }

    float expected[kNumLayers];
    for (uint32_t i = 0; i < kNumLayers; ++i) {
        DAWN_UNSAFE_TODO(expected[i]) = (i + 0.5f) / 6.f;
    }
    EXPECT_BUFFER_FLOAT_RANGE_TOLERANCE_EQ(expected, result, 0, 6, 0.05);
}

// Test that you can not copy Depth24plus/Depth24PlusStencil8 depth aspect
// The spec requires this to be validated out but the WGPU backend enables copying
// on the inner device by the use_blit_for_depth24plus_texture_to_buffer_copy
// toggle. Make sure that even when that toggle is enabled copying these formats
// is validated out.
TEST_P(CaptureAndReplayTests, Depth24PlusNotCopyable) {
    // No validation errors if validation is off.
    DAWN_TEST_UNSUPPORTED_IF(HasToggleEnabled("skip_validation"));

    wgpu::Buffer buffer = CreateBuffer("buf", 4, wgpu::BufferUsage::CopyDst);
    for (wgpu::TextureFormat format :
         {wgpu::TextureFormat::Depth24Plus, wgpu::TextureFormat::Depth24PlusStencil8}) {
        wgpu::Texture texture = CreateTexture("tex", {1}, format, wgpu::TextureUsage::CopySrc);

        wgpu::CommandBuffer commands;
        {
            // Copy srcTexture to dstBuffer
            wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
            wgpu::TexelCopyTextureInfo texelCopyTextureInfo = utils::CreateTexelCopyTextureInfo(
                texture, 0, {0, 0, 0}, wgpu::TextureAspect::DepthOnly);
            wgpu::TexelCopyBufferInfo texelCopyBufferInfo =
                utils::CreateTexelCopyBufferInfo(buffer, 0, 256, 1);
            wgpu::Extent3D extent = {1, 1, 1};

            encoder.CopyTextureToBuffer(&texelCopyTextureInfo, &texelCopyBufferInfo, &extent);
            ASSERT_DEVICE_ERROR(encoder.Finish());
        }
    }
}

// Test that capturing and replaying a BindGroup with an ExternalTexture works.
TEST_P(CaptureAndReplayTests, BindGroupWithExternalTexture) {
    wgpu::TextureDescriptor textureDesc;
    textureDesc.size = {1, 1, 1};
    textureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
    textureDesc.usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment;
    wgpu::Texture texture = device.CreateTexture(&textureDesc);

    wgpu::ExternalTexture externalTexture = utils::MakePassthroughExternalTexture(device, texture);

    const char* shader = R"(
        @vertex fn vs() -> @builtin(position) vec4f {
            return vec4f(0, 0, 0, 1);
        }

        @group(0) @binding(0) var s : sampler;
        @group(0) @binding(1) var t : texture_external;

        @fragment fn main(@builtin(position) FragCoord : vec4f)
                                    -> @location(0) vec4f {
            return textureSampleBaseClampToEdge(t, s, FragCoord.xy / vec2f(4.0, 4.0));
        }
    )";
    auto module = utils::CreateShaderModule(device, shader);

    // Create an explicit bind group layout.
    auto bgl = utils::MakeBindGroupLayout(
        device, {{0, wgpu::ShaderStage::Fragment, wgpu::SamplerBindingType::Filtering},
                 {1, wgpu::ShaderStage::Fragment, &utils::kExternalTextureBindingLayout}});

    utils::ComboRenderPipelineDescriptor descriptor;
    descriptor.layout = utils::MakeBasicPipelineLayout(device, &bgl);
    descriptor.vertex.module = module;
    descriptor.cFragment.module = module;
    descriptor.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm;
    auto pipeline = device.CreateRenderPipeline(&descriptor);

    auto sampler = device.CreateSampler();

    auto bg = utils::MakeBindGroup(device, bgl, {{0, sampler}, {1, externalTexture}});
    bg.SetLabel("MyBindGroup");

    auto renderTexture = CreateTexture("renderTexture", {1}, wgpu::TextureFormat::RGBA8Unorm,
                                       wgpu::TextureUsage::RenderAttachment);
    utils::ComboRenderPassDescriptor renderPass({renderTexture.CreateView()});
    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
    wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
    {
        pass.SetPipeline(pipeline);
        pass.SetBindGroup(0, bg);
        pass.Draw(3);
        pass.End();
    }

    wgpu::CommandBuffer commands = encoder.Finish();

    // --- capture ---
    auto recorder = Recorder::CreateAndStart(device);

    queue.Submit(1, &commands);

    // --- replay ---
    auto replay = recorder->Replay(device);

    // Expect no errors.
    // Verify that the bind group exists in the replayed device.
    auto replayedBg = replay->GetObjectByLabel<wgpu::BindGroup>("MyBindGroup");
    EXPECT_NE(replayedBg, nullptr);
}

DAWN_INSTANTIATE_TEST(CaptureAndReplayTests, WebGPUBackend());

class CaptureAndReplayDrawTests : public CaptureAndReplayTests {
  public:
    // Sets up point-list render pipeline to a 1x1 rgba8uint texture
    // and expects texture to be 'expected'
    template <typename Func, typename T>
    void TestDrawCommand(Func fn, const T& expected) {
        wgpu::Texture dstTexture = CreateTexture("dstTexture", {1}, wgpu::TextureFormat::RGBA8Uint,
                                                 wgpu::TextureUsage::RenderAttachment);

        wgpu::Texture depthTexture =
            CreateTexture("depthTexture", {1}, wgpu::TextureFormat::Depth24PlusStencil8,
                          wgpu::TextureUsage::RenderAttachment);

        const char* shader = R"(
            struct VOut {
                @builtin(position) pos: vec4f,
                @location(0) @interpolate(flat, either) params: vec4u,
            };

            @vertex fn vs(
                @builtin(vertex_index) vNdx: u32,
                @builtin(instance_index) iNdx: u32) -> VOut
            {
                return VOut(
                    vec4f(0, 0, 0, 1),
                    vec4u(vNdx, iNdx, 0x33, 0x44));
            }

            @fragment fn fs(v: VOut) -> @location(0) vec4u {
                return v.params;
            }
        )";
        auto module = utils::CreateShaderModule(device, shader);

        utils::ComboRenderPipelineDescriptor desc;
        desc.vertex.module = module;
        desc.cFragment.module = module;
        desc.cFragment.targetCount = 1;
        desc.depthStencil = &desc.cDepthStencil;
        desc.cTargets[0].format = wgpu::TextureFormat::RGBA8Uint;
        desc.cDepthStencil.format = wgpu::TextureFormat::Depth24PlusStencil8;
        desc.cDepthStencil.depthCompare = wgpu::CompareFunction::Always;
        desc.cDepthStencil.stencilFront.compare = wgpu::CompareFunction::Equal;
        desc.primitive.topology = wgpu::PrimitiveTopology::PointList;
        wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&desc);

        wgpu::CommandBuffer commands;
        {
            wgpu::CommandEncoder encoder = device.CreateCommandEncoder();

            utils::ComboRenderPassDescriptor passDescriptor({dstTexture.CreateView()},
                                                            depthTexture.CreateView());
            wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor);
            pass.SetPipeline(pipeline);
            fn(pass);
            pass.End();

            commands = encoder.Finish();
        }

        // --- capture ---
        auto recorder = Recorder::CreateAndStart(device);

        queue.Submit(1, &commands);

        // --- replay ---
        auto replay = recorder->Replay(device);

        ExpectTextureEQ(replay, "dstTexture", {1}, expected);
    }
};

// Capture DrawIndexed
TEST_P(CaptureAndReplayDrawTests, CaptureDrawIndexed) {
    uint32_t indices[] = {0x10, 0x20, 0x30};
    wgpu::Buffer indexBuffer = CreateBuffer("index", sizeof(indices),
                                            wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Index);
    queue.WriteBuffer(indexBuffer, 0, indices, sizeof(indices));

    utils::RGBA8 expected[] = {{0x32, 0x3, 0x33, 0x44}};
    TestDrawCommand(
        [&](wgpu::RenderPassEncoder pass) {
            pass.SetIndexBuffer(indexBuffer, wgpu::IndexFormat::Uint32);
            pass.DrawIndexed(1,   // indexCount
                             1,   // instanceCount
                             2,   // firstIndex,
                             2,   // baseVertex,
                             3);  // firstInstance
        },
        expected);
}

// Capture DrawIndirect
TEST_P(CaptureAndReplayDrawTests, CaptureDrawIndirect) {
    uint32_t indirect[] = {
        0x11,  // vertexCount
        0x22,  // instanceCount
        0,     // firstVertex
        0,     // firstInstance (must be 0 without "indirect-first-instance")
    };
    wgpu::Buffer indirectBuffer = CreateBuffer(
        "indirect", sizeof(indirect), wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Indirect);
    queue.WriteBuffer(indirectBuffer, 0, indirect, sizeof(indirect));

    utils::RGBA8 expected[] = {{0x10, 0x21, 0x33, 0x44}};
    TestDrawCommand([&](wgpu::RenderPassEncoder pass) { pass.DrawIndirect(indirectBuffer, 0); },
                    expected);
}

// Capture DrawIndexedIndirect
TEST_P(CaptureAndReplayDrawTests, CaptureDrawIndexedIndirect) {
    uint32_t indices[] = {10, 20};
    wgpu::Buffer indexBuffer = CreateBuffer("index", sizeof(indices),
                                            wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Index);

    uint32_t indirectIndexed[] = {
        1,   // indexCount
        10,  // instanceCount
        1,   // firstIndex
        3,   // baseVertex
        0,   // firstInstance (must be 0 without "indirect-first-instance")
    };
    wgpu::Buffer indirectIndexedBuffer =
        CreateBuffer("indirectIndexed", sizeof(indirectIndexed),
                     wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Indirect);
    queue.WriteBuffer(indirectIndexedBuffer, 0, indirectIndexed, sizeof(indirectIndexed));

    utils::RGBA8 expected[] = {{0x3, 9, 0x33, 0x44}};
    TestDrawCommand(
        [&](wgpu::RenderPassEncoder pass) {
            pass.SetIndexBuffer(indexBuffer, wgpu::IndexFormat::Uint32);
            pass.DrawIndexedIndirect(indirectIndexedBuffer, 0);
        },
        expected);
}

// Capture SetViewport. Draws twice. The second draw should be ignored because
// its out of the viewport.
TEST_P(CaptureAndReplayDrawTests, CaptureSetViewport) {
    utils::RGBA8 expected[] = {{0x11, 0x22, 0x33, 0x44}};
    TestDrawCommand(
        [&](wgpu::RenderPassEncoder pass) {
            pass.Draw(1, 1, 0x11, 0x22);
            pass.SetViewport(1, 1, 1, 1, 0, 1);
            pass.Draw(1, 1, 0x1, 0x2);
        },
        expected);
}

// Capture SetScissorRect. Draws twice. The second draw should be ignored because
// its out of the scissor rect.
TEST_P(CaptureAndReplayDrawTests, CaptureSetScissorRect) {
    // TODO(464436694): Zero size scissor fails on Intel Mac for this case.
    DAWN_SUPPRESS_TEST_IF(IsWebGPUOn(wgpu::BackendType::Metal) && IsIntel());

    utils::RGBA8 expected[] = {{0x11, 0x22, 0x33, 0x44}};
    TestDrawCommand(
        [&](wgpu::RenderPassEncoder pass) {
            pass.Draw(1, 1, 0x11, 0x22);
            pass.SetScissorRect(0, 0, 0, 0);
            pass.Draw(1, 1, 0x1, 0x2);
        },
        expected);
}

// Capture SetStencilReference. Draws twice. The second draw should be ignored because
// it doesn't match the stencil reference.
TEST_P(CaptureAndReplayDrawTests, CaptureSetStencilReference) {
    utils::RGBA8 expected[] = {{0x11, 0x22, 0x33, 0x44}};
    TestDrawCommand(
        [&](wgpu::RenderPassEncoder pass) {
            pass.Draw(1, 1, 0x11, 0x22);
            pass.SetStencilReference(1);
            pass.Draw(1, 1, 0x1, 0x2);
        },
        expected);
}

DAWN_INSTANTIATE_TEST(CaptureAndReplayDrawTests, WebGPUBackend());

class CaptureAndReplayTimestampTests : public CaptureAndReplayTests {
  protected:
    void SetUp() override {
        CaptureAndReplayTests::SetUp();
        DAWN_TEST_UNSUPPORTED_IF(
            !SupportsFeatures({wgpu::FeatureName::ChromiumExperimentalTimestampQueryInsidePasses}));
    }

    std::vector<wgpu::FeatureName> GetRequiredFeatures() override {
        std::vector<wgpu::FeatureName> requiredFeatures = {};
        if (SupportsFeatures({wgpu::FeatureName::ChromiumExperimentalTimestampQueryInsidePasses})) {
            requiredFeatures.push_back(
                wgpu::FeatureName::ChromiumExperimentalTimestampQueryInsidePasses);
            requiredFeatures.push_back(wgpu::FeatureName::TimestampQuery);
        }
        return requiredFeatures;
    }
};

// Test WriteTimestamp in compute pass, render pass, and
// command buffer. We don't expect any results. We only
// expect it doesn't get any errors.
TEST_P(CaptureAndReplayTimestampTests, WriteTimestamp) {
    wgpu::QuerySetDescriptor qsDesc;
    qsDesc.label = "myQuerySet";
    qsDesc.count = 3;
    qsDesc.type = wgpu::QueryType::Timestamp;
    wgpu::QuerySet querySet = device.CreateQuerySet(&qsDesc);

    wgpu::CommandBuffer commands;
    {
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
        encoder.WriteTimestamp(querySet, 0);
        {
            utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, 1, 1);
            wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
            pass.WriteTimestamp(querySet, 1);
            pass.End();
        }
        {
            wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
            pass.WriteTimestamp(querySet, 2);
            pass.End();
        }
        commands = encoder.Finish();
    }

    // --- capture ---
    auto recorder = Recorder::CreateAndStart(device);

    queue.Submit(1, &commands);

    // --- replay ---
    recorder->Replay(device);

    // just expect no errors.
}

DAWN_INSTANTIATE_TEST(CaptureAndReplayTimestampTests, WebGPUBackend());

#ifdef DAWN_SUPPORTS_GLFW_FOR_WINDOWING
struct GLFWindowDestroyer {
    void operator()(GLFWwindow* ptr) { glfwDestroyWindow(ptr); }
};

class CaptureAndReplaySurfaceTests : public CaptureAndReplayTests {
  protected:
    void SetUp() override {
        CaptureAndReplayTests::SetUp();

        // TODO(crbug.com/dawn/2531): Failing on newer Linux/Intel driver version.
        // However, IsIntel() and IsMesa() don't work with the null backend.
        DAWN_SUPPRESS_TEST_IF(IsLinux() && IsNull());
        DAWN_SUPPRESS_TEST_IF(IsLinux() && IsVulkan() && IsIntel() && IsMesa("23.2"));

        // Causes flaky X11 resource exhaustion when run with SwiftShader.
        DAWN_SUPPRESS_TEST_IF(IsLinux() && IsVulkan() && IsSwiftshader());

        DAWN_TEST_UNSUPPORTED_IF(!glfwInit());
        glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
        mWindow.reset(glfwCreateWindow(1, 1, "CaptureAndReplaySurfaceTests", nullptr, nullptr));
    }

    void TearDown() override {
        CaptureAndReplayTests::TearDown();

        mWindow.reset();
        glfwTerminate();
    }
    std::unique_ptr<GLFWwindow, GLFWindowDestroyer> mWindow;
};

TEST_P(CaptureAndReplaySurfaceTests, TestSurface) {
    DAWN_SUPPRESS_TEST_IF(IsWARP());

    wgpu::Surface surface = wgpu::glfw::CreateSurfaceForWindow(instance, mWindow.get());
    surface.SetLabel("mySurface");

    wgpu::SurfaceConfiguration config = {};
    config.device = device;
    config.format = wgpu::TextureFormat::BGRA8Unorm;
    config.usage = wgpu::TextureUsage::RenderAttachment;
    config.width = 1;
    config.height = 1;
    config.presentMode = wgpu::PresentMode::Fifo;

    // --- capture ---
    auto recorder = Recorder::CreateAndStart(device);

    surface.Configure(&config);

    wgpu::SurfaceTexture surfaceTexture;
    surface.GetCurrentTexture(&surfaceTexture);
    wgpu::Texture texture = surfaceTexture.texture;
    texture.SetLabel("backbuffer");

    surface.Present();

    // --- replay ---
    recorder->EndCapture();

    // Check surface reflection info
    auto surfaceInfos = recorder->GetCapture()->GetSurfaceInfos();
    EXPECT_EQ(surfaceInfos.size(), 1u);
    EXPECT_EQ(surfaceInfos[0].width, 1u);
    EXPECT_EQ(surfaceInfos[0].height, 1u);

    wgpu::Surface replaySurface = wgpu::glfw::CreateSurfaceForWindow(instance, mWindow.get());

    recorder->SetSurfaces({replaySurface});

    auto replay = recorder->Replay(device);

    EXPECT_TRUE(replay->GetObjectByLabel<wgpu::Texture>("backbuffer") != nullptr);

    // Explicitly reset the recorder object to release all replayed resources (like Surface)
    // before the test ends and the device count check is performed.
    recorder.reset();
}

TEST_P(CaptureAndReplaySurfaceTests, MultiFrame) {
    DAWN_SUPPRESS_TEST_IF(IsWARP());

    wgpu::Surface surface = wgpu::glfw::CreateSurfaceForWindow(instance, mWindow.get());
    surface.SetLabel("mySurface");

    wgpu::SurfaceConfiguration config = {};
    config.device = device;
    config.format = wgpu::TextureFormat::BGRA8Unorm;
    config.usage = wgpu::TextureUsage::RenderAttachment;
    config.width = 1;
    config.height = 1;
    config.presentMode = wgpu::PresentMode::Fifo;

    // --- capture ---
    auto recorder = Recorder::CreateAndStart(device);

    surface.Configure(&config);

    // Frame 1
    {
        wgpu::SurfaceTexture surfaceTexture;
        surface.GetCurrentTexture(&surfaceTexture);
        surfaceTexture.texture.SetLabel("backbuffer1");
        surface.Present();
    }

    // Frame 2
    {
        wgpu::SurfaceTexture surfaceTexture;
        surface.GetCurrentTexture(&surfaceTexture);
        surfaceTexture.texture.SetLabel("backbuffer2");
        surface.Present();
    }

    // --- replay ---
    recorder->EndCapture();

    wgpu::Surface replaySurface = wgpu::glfw::CreateSurfaceForWindow(instance, mWindow.get());

    recorder->SetSurfaces({replaySurface});
    auto replay = recorder->CreateReplay(device);

    // Replay Frame 1
    EXPECT_EQ(replay->PlayFrame(), dawn::replay::ReplayStatus::Continuing);
    EXPECT_TRUE(replay->GetObjectByLabel<wgpu::Texture>("backbuffer1") != nullptr);
    EXPECT_TRUE(replay->GetObjectByLabel<wgpu::Texture>("backbuffer2") == nullptr);

    // Replay Frame 2
    EXPECT_EQ(replay->PlayFrame(), dawn::replay::ReplayStatus::Continuing);
    EXPECT_TRUE(replay->GetObjectByLabel<wgpu::Texture>("backbuffer2") != nullptr);

    // No more frames
    EXPECT_EQ(replay->PlayFrame(), dawn::replay::ReplayStatus::Finished);

    // Explicitly reset the recorder object to release all replayed resources (like Surface)
    // before the test ends and the device count check is performed.
    recorder.reset();
}

DAWN_INSTANTIATE_TEST(CaptureAndReplaySurfaceTests, WebGPUBackend());
#endif

}  // anonymous namespace
}  // namespace dawn
