blob: c976d156063f2037f0bfe15e63f6bbe6fa3c757d [file] [log] [blame] [edit]
// 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 "dawn/tests/DawnTest.h"
#include "dawn/tests/MockCallback.h"
#include "dawn/utils/ComboRenderBundleEncoderDescriptor.h"
#include "dawn/utils/ComboRenderPipelineDescriptor.h"
#include "dawn/utils/TestUtils.h"
#include "dawn/utils/WGPUHelpers.h"
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());
// TODO(crbug.com/452924800): Remove once these tests work properly with
// the WebGPU on WebGPU backend with wire.
DAWN_SUPPRESS_TEST_IF(IsWebGPUOn(wgpu::BackendType::Metal) && UsesWire());
DAWN_SUPPRESS_TEST_IF(IsWebGPUOn(wgpu::BackendType::Vulkan) && UsesWire());
}
class Capture {
public:
Capture(const std::string& commandData, const std::string& contentData)
: mCommandData(commandData), mContentData(contentData) {}
std::unique_ptr<replay::Replay> Replay(wgpu::Device device) {
std::istringstream commandIStream(mCommandData);
std::istringstream contentIStream(mContentData);
auto capture = replay::Capture::Create(commandIStream, mCommandData.size(),
contentIStream, mContentData.size());
std::unique_ptr<replay::Replay> replay =
replay::Replay::Create(device, std::move(capture));
bool result = replay->Play();
EXPECT_TRUE(result);
return replay;
}
private:
std::string mCommandData;
std::string mContentData;
};
class Recorder {
public:
static Recorder CreateAndStart(wgpu::Device device) { return Recorder(device); }
Capture Finish() {
native::webgpu::EndCapture(mDevice.Get());
return Capture(mCommandStream.str(), mContentStream.str());
}
private:
explicit Recorder(wgpu::Device device) : mDevice(device) {
native::webgpu::StartCapture(device.Get(), mCommandStream, mContentStream);
}
wgpu::Device mDevice;
std::ostringstream mCommandStream;
std::ostringstream mContentStream;
};
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 capture = recorder.Finish();
auto replay = capture.Replay(device);
ExpectBufferEQ(replay.get(), 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 capture = recorder.Finish();
auto replay = capture.Replay(device);
ExpectBufferEQ(replay.get(), 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 capture = recorder.Finish();
auto replay = capture.Replay(device);
uint8_t expected[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
ExpectBufferEQ(replay.get(), 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);
std::memcpy(buffer.GetMappedRange(), myData0, sizeof(myData0));
buffer.Unmap();
// --- capture ---
auto recorder = Recorder::CreateAndStart(device);
queue.WriteBuffer(buffer, 4, &myData1, sizeof(myData1));
// --- replay ---
auto capture = recorder.Finish();
auto replay = capture.Replay(device);
uint8_t expected[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
ExpectBufferEQ(replay.get(), 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 capture = recorder.Finish();
auto replay = capture.Replay(device);
uint8_t expected[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
ExpectBufferEQ(replay.get(), 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 capture = recorder.Finish();
auto replay = capture.Replay(device);
uint8_t expected[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
ExpectBufferEQ(replay.get(), 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.Finish();
}
{
auto recorder = Recorder::CreateAndStart(device);
queue.WriteBuffer(buffer, 4, &myData2, sizeof(myData2));
auto capture = recorder.Finish();
auto replay = capture.Replay(device);
uint8_t expected[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
ExpectBufferEQ(replay.get(), label, expected);
}
}
// We make a buffer before capture. During capture write map it, put data in it.
// Then check the data is correct on replay.
TEST_P(CaptureAndReplayTests, MapWrite) {
const char* label = "myBuffer";
const uint8_t myData[] = {0x11, 0x22, 0x33, 0x44};
wgpu::Buffer buffer =
CreateBuffer(label, 4, wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc);
auto recorder = Recorder::CreateAndStart(device);
MapAsyncAndWait(buffer, wgpu::MapMode::Write, 0, 4);
buffer.WriteMappedRange(0, &myData, sizeof(myData));
buffer.Unmap();
auto capture = recorder.Finish();
auto replay = capture.Replay(device);
ExpectBufferEQ(replay.get(), label, myData);
}
// 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 capture = recorder.Finish();
auto replay = capture.Replay(device);
uint8_t expected[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
ExpectBufferEQ(replay.get(), 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);
auto capture = recorder.Finish();
auto replay = capture.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 capture = recorder.Finish();
auto replay = capture.Replay(device);
ExpectBufferEQ(replay.get(), "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 capture = recorder.Finish();
auto replay = capture.Replay(device);
ExpectTextureEQ(replay.get(), "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 capture = recorder.Finish();
auto replay = capture.Replay(device);
ExpectTextureEQ(replay.get(), "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 capture = recorder.Finish();
auto replay = capture.Replay(device);
ExpectBufferEQ(replay.get(), "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 capture = recorder.Finish();
auto replay = capture.Replay(device);
ExpectTextureEQ(replay.get(), "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 capture = recorder.Finish();
auto replay = capture.Replay(device);
ExpectTextureEQ(replay.get(), "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 capture = recorder.Finish();
auto replay = capture.Replay(device);
utils::RGBA8 expected[] = {{0x11, 0x22, 0x33, 0x44}};
ExpectTextureEQ(replay.get(), "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 capture = recorder.Finish();
auto replay = capture.Replay(device);
utils::RGBA8 expected[] = {{0x11, 0x22, 0x33, 0x44}};
ExpectTextureEQ(replay.get(), "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 capture = recorder.Finish();
auto replay = capture.Replay(device);
uint8_t expected[] = {0x11, 0x22, 0x33, 0x44};
ExpectBufferEQ(replay.get(), 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 capture = recorder.Finish();
auto replay = capture.Replay(device);
uint8_t expected[] = {0x11, 0x22, 0x33, 0x44};
ExpectBufferEQ(replay.get(), 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 capture = recorder.Finish();
auto replay = capture.Replay(device);
uint8_t expected1[] = {0x11, 0x22, 0x33, 0x44};
ExpectBufferEQ(replay.get(), "buffer1", expected1);
uint8_t expected2[] = {0x55, 0x66, 0x77, 0x88};
ExpectBufferEQ(replay.get(), "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 capture = recorder.Finish();
auto replay = capture.Replay(device);
uint8_t expected[] = {0x55, 0x66, 0x77, 0x88};
ExpectBufferEQ(replay.get(), "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 capture = recorder.Finish();
auto replay = capture.Replay(device);
utils::RGBA8 expected[] = {{0x11, 0x22, 0x33, 0x44}};
ExpectTextureEQ(replay.get(), "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 capture = recorder.Finish();
auto replay = capture.Replay(device);
utils::RGBA8 expected[] = {{0x11, 0x22, 0x33, 0x44}};
ExpectTextureEQ(replay.get(), "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 capture = recorder.Finish();
auto replay = capture.Replay(device);
utils::RGBA8 expected[] = {{0x11, 0x22, 0x33, 0x44}};
ExpectTextureEQ(replay.get(), "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 capture = recorder.Finish();
auto replay = capture.Replay(device);
uint8_t expected[] = {0x11, 0x22, 0x33, 0x44};
ExpectBufferEQ(replay.get(), 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 capture = recorder.Finish();
auto replay = capture.Replay(device);
utils::RGBA8 expected[] = {{0x11, 0x22, 0x33, 0x44}};
ExpectTextureEQ(replay.get(), "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 capture = recorder.Finish();
auto replay = capture.Replay(device);
uint8_t expected[] = {0x11, 0x22, 0x33, 0x44};
ExpectBufferEQ(replay.get(), "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 capture = recorder.Finish();
auto replay = capture.Replay(device);
utils::RGBA8 expected[] = {{0x11, 0x22, 0x33, 0x44}};
ExpectTextureEQ(replay.get(), "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 ---
auto capture = recorder.Finish();
auto replay = capture.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 (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 && actual[i] != 0) {
return testing::AssertionFailure()
<< "Expected data[" << i << "] to be zero, actual: " << actual[i] << ".\n";
}
if (expected == Result::NonZero && 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 capture = recorder.Finish();
auto replay = capture.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 capture = recorder.Finish();
auto replay = capture.Replay(device);
utils::RGBA8 expected[] = {{0x11, 0x22, 0x33, 0x44}};
ExpectTextureEQ(replay.get(), "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 ---
auto capture = recorder.Finish();
auto replay = capture.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 capture = recorder.Finish();
auto replay = capture.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 capture = recorder.Finish();
auto replay = capture.Replay(device);
utils::RGBA8 expected[] = {{11, 22, 33, 44}};
ExpectTextureEQ(replay.get(), "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 capture = recorder.Finish();
auto replay = capture.Replay(device);
uint8_t expected[] = {0x11, 0x22, 0x33, 0x44};
ExpectBufferEQ(replay.get(), 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 capture = recorder.Finish();
auto replay = capture.Replay(device);
const uint8_t expected[] = {0x11, 0x11, 0x11, 0x11, 0, 0, 0, 0, 0x33, 0x33, 0x33, 0x33};
ExpectBufferEQ(replay.get(), "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 capture = recorder.Finish();
auto replay = capture.Replay(device);
uint8_t expected[] = {0x11, 0x22, 0x33, 0x44};
ExpectBufferEQ(replay.get(), 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 capture = recorder.Finish();
auto replay = capture.Replay(device);
const uint32_t expected[] = {0x44332211};
ExpectTextureEQ(replay.get(), "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);
}
auto capture = recorder.Finish();
auto replay = capture.Replay(device);
// just expect no errors
}
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 capture = recorder.Finish();
auto replay = capture.Replay(device);
ExpectTextureEQ(replay.get(), "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 ---
auto capture = recorder.Finish();
auto replay = capture.Replay(device);
// just expect no errors.
}
DAWN_INSTANTIATE_TEST(CaptureAndReplayTimestampTests, WebGPUBackend());
} // anonymous namespace
} // namespace dawn