blob: 567d9863db61bae36b8beac8d2e6bafd4d714a83 [file] [log] [blame]
// 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 "dawn/native/WebGPUBackend.h"
#include "dawn/replay/Capture.h"
#include "dawn/replay/Replay.h"
#include "dawn/tests/DawnTest.h"
#include "dawn/tests/MockCallback.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();
// 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);
std::unique_ptr<replay::Capture> capture = replay::Capture::Create(
commandIStream, mCommandData.size(), contentIStream, mContentData.size());
std::unique_ptr<replay::Replay> replay = replay::Replay::Create(device, capture.get());
auto result = replay->Play();
EXPECT_TRUE(result.IsSuccess());
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;
};
};
// 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::BufferDescriptor descriptor;
descriptor.label = label;
descriptor.size = 4;
descriptor.usage = wgpu::BufferUsage::CopyDst;
wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
queue.WriteBuffer(buffer, 0, &myData, sizeof(myData));
}
auto capture = recorder.Finish();
auto replay = capture.Replay(device);
{
wgpu::Buffer buffer = replay->GetObjectByLabel<wgpu::Buffer>(label);
ASSERT_NE(buffer, nullptr);
EXPECT_BUFFER_U8_RANGE_EQ(myData, buffer, 0, sizeof(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::BufferDescriptor descriptor;
descriptor.label = label;
descriptor.size = 4;
descriptor.usage = wgpu::BufferUsage::CopyDst;
wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
queue.WriteBuffer(buffer, 0, &myData, sizeof(myData));
}
// --- replay ---
auto capture = recorder.Finish();
auto replay = capture.Replay(device);
{
wgpu::Buffer buffer = replay->GetObjectByLabel<wgpu::Buffer>(label);
ASSERT_NE(buffer, nullptr);
EXPECT_BUFFER_U8_RANGE_EQ(myData, buffer, 0, sizeof(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::BufferDescriptor descriptor;
descriptor.label = label;
descriptor.size = 8;
descriptor.usage = wgpu::BufferUsage::CopyDst;
wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
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);
{
wgpu::Buffer buffer = replay->GetObjectByLabel<wgpu::Buffer>(label);
ASSERT_NE(buffer, nullptr);
uint8_t expected[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
EXPECT_BUFFER_U8_RANGE_EQ(expected, buffer, 0, sizeof(expected));
}
}
// 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::BufferDescriptor descriptor;
descriptor.label = label;
descriptor.size = 8;
descriptor.usage = wgpu::BufferUsage::CopyDst;
descriptor.mappedAtCreation = true;
wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
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);
{
wgpu::Buffer buffer = replay->GetObjectByLabel<wgpu::Buffer>(label);
ASSERT_NE(buffer, nullptr);
uint8_t expected[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
EXPECT_BUFFER_U8_RANGE_EQ(expected, buffer, 0, sizeof(expected));
}
}
// 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::BufferDescriptor descriptor;
descriptor.label = label;
descriptor.size = 8;
descriptor.usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst;
wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
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);
{
wgpu::Buffer buffer = replay->GetObjectByLabel<wgpu::Buffer>(label);
ASSERT_NE(buffer, nullptr);
uint8_t expected[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
EXPECT_BUFFER_U8_RANGE_EQ(expected, buffer, 0, sizeof(expected));
}
}
// 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::BufferDescriptor descriptor;
descriptor.label = dstLabel;
descriptor.size = 8;
descriptor.usage = wgpu::BufferUsage::CopyDst;
wgpu::Buffer dstBuffer = device.CreateBuffer(&descriptor);
descriptor.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst;
descriptor.label = srcLabel;
wgpu::Buffer srcBuffer = device.CreateBuffer(&descriptor);
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);
{
wgpu::Buffer buffer = replay->GetObjectByLabel<wgpu::Buffer>(dstLabel);
ASSERT_NE(buffer, nullptr);
uint8_t expected[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
EXPECT_BUFFER_U8_RANGE_EQ(expected, buffer, 0, sizeof(expected));
}
}
// 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);
wgpu::BufferDescriptor descriptor;
descriptor.label = label;
descriptor.size = 8;
descriptor.usage = wgpu::BufferUsage::CopyDst;
buffer = device.CreateBuffer(&descriptor);
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);
{
wgpu::Buffer buffer = replay->GetObjectByLabel<wgpu::Buffer>(label);
ASSERT_NE(buffer, nullptr);
uint8_t expected[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
EXPECT_BUFFER_U8_RANGE_EQ(expected, buffer, 0, sizeof(expected));
}
}
}
// 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::BufferDescriptor descriptor;
descriptor.label = label;
descriptor.size = 4;
descriptor.usage = wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc;
wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
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);
{
wgpu::Buffer buffer = replay->GetObjectByLabel<wgpu::Buffer>(label);
ASSERT_NE(buffer, nullptr);
EXPECT_BUFFER_U8_RANGE_EQ(myData, buffer, 0, sizeof(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::BufferDescriptor descriptor;
descriptor.label = dstLabel;
descriptor.size = 8;
descriptor.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc;
wgpu::Buffer dstBuffer = device.CreateBuffer(&descriptor);
queue.WriteBuffer(dstBuffer, 0, &myData1, sizeof(myData1));
descriptor.label = srcLabel;
descriptor.size = 4;
descriptor.usage = wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc;
wgpu::Buffer srcBuffer = device.CreateBuffer(&descriptor);
auto recorder = Recorder::CreateAndStart(device);
MapAsyncAndWait(srcBuffer, wgpu::MapMode::Write, 0, 4);
srcBuffer.WriteMappedRange(0, &myData2, sizeof(myData2));
srcBuffer.Unmap();
wgpu::CommandBuffer commands;
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
encoder.CopyBufferToBuffer(srcBuffer, 0, dstBuffer, 4, 4);
commands = encoder.Finish();
}
queue.Submit(1, &commands);
auto capture = recorder.Finish();
auto replay = capture.Replay(device);
{
wgpu::Buffer buffer = replay->GetObjectByLabel<wgpu::Buffer>(dstLabel);
ASSERT_NE(buffer, nullptr);
uint8_t expected[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
EXPECT_BUFFER_U8_RANGE_EQ(expected, buffer, 0, sizeof(expected));
}
}
TEST_P(CaptureAndReplayTests, CaptureCopyBufferToBuffer) {
const uint8_t myData[] = {0x11, 0x22, 0x33, 0x44};
wgpu::BufferDescriptor descriptor;
descriptor.label = "srcBuffer";
descriptor.size = 4;
descriptor.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc;
wgpu::Buffer srcBuffer = device.CreateBuffer(&descriptor);
queue.WriteBuffer(srcBuffer, 0, &myData, sizeof(myData));
descriptor.label = "dstBuffer";
descriptor.usage = wgpu::BufferUsage::CopyDst;
wgpu::Buffer dstBuffer = device.CreateBuffer(&descriptor);
wgpu::CommandBuffer commands;
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
encoder.CopyBufferToBuffer(srcBuffer, 0, dstBuffer, 0, 4);
commands = encoder.Finish();
}
auto recorder = Recorder::CreateAndStart(device);
queue.Submit(1, &commands);
auto capture = recorder.Finish();
auto replay = capture.Replay(device);
{
wgpu::Buffer buffer = replay->GetObjectByLabel<wgpu::Buffer>("dstBuffer");
ASSERT_NE(buffer, nullptr);
EXPECT_BUFFER_U8_RANGE_EQ(myData, buffer, 0, sizeof(myData));
}
}
TEST_P(CaptureAndReplayTests, WriteTexture) {
const uint8_t myData[] = {0x11, 0x22, 0x33, 0x44};
wgpu::TextureDescriptor textureDesc;
textureDesc.label = "myTexture";
textureDesc.size = {4, 1, 1};
textureDesc.format = wgpu::TextureFormat::R8Unorm;
textureDesc.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc;
wgpu::Texture texture = device.CreateTexture(&textureDesc);
auto recorder = Recorder::CreateAndStart(device);
{
wgpu::TexelCopyBufferLayout texelCopyBufferLayout =
utils::CreateTexelCopyBufferLayout(0, 4);
wgpu::TexelCopyTextureInfo texelCopyTextureInfo =
utils::CreateTexelCopyTextureInfo(texture, 0, {0, 0, 0}, wgpu::TextureAspect::All);
wgpu::Extent3D extent = {4, 1, 1};
queue.WriteTexture(&texelCopyTextureInfo, myData, sizeof(myData), &texelCopyBufferLayout,
&extent);
}
auto capture = recorder.Finish();
auto replay = capture.Replay(device);
{
wgpu::Texture texture = replay->GetObjectByLabel<wgpu::Texture>("myTexture");
ASSERT_NE(texture, nullptr);
EXPECT_TEXTURE_EQ(&myData[0], texture, {0, 0}, {4, 1}, 0, wgpu::TextureAspect::All);
}
}
TEST_P(CaptureAndReplayTests, CaptureCopyBufferToTexture) {
const uint8_t myData[] = {0x11, 0x22, 0x33, 0x44};
wgpu::BufferDescriptor descriptor;
descriptor.label = "srcBuffer";
descriptor.size = 4;
descriptor.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc;
wgpu::Buffer srcBuffer = device.CreateBuffer(&descriptor);
queue.WriteBuffer(srcBuffer, 0, &myData, sizeof(myData));
wgpu::TextureDescriptor textureDesc;
textureDesc.label = "dstTexture";
textureDesc.size = {4, 1, 1};
textureDesc.format = wgpu::TextureFormat::R8Unorm;
textureDesc.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc;
wgpu::Texture dstTexture = device.CreateTexture(&textureDesc);
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);
{
wgpu::Texture texture = replay->GetObjectByLabel<wgpu::Texture>("dstTexture");
ASSERT_NE(texture, nullptr);
EXPECT_TEXTURE_EQ(&myData[0], texture, {0, 0}, {4, 1}, 0, wgpu::TextureAspect::All);
}
}
TEST_P(CaptureAndReplayTests, CaptureCopyTextureToBuffer) {
const uint8_t myData[] = {0x11, 0x22, 0x33, 0x44};
wgpu::TextureDescriptor textureDesc;
textureDesc.label = "srcTexture";
textureDesc.size = {4, 1, 1};
textureDesc.format = wgpu::TextureFormat::R8Unorm;
textureDesc.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc;
wgpu::Texture srcTexture = device.CreateTexture(&textureDesc);
// Put data in source texture
{
wgpu::TexelCopyBufferLayout texelCopyBufferLayout =
utils::CreateTexelCopyBufferLayout(0, 4);
wgpu::TexelCopyTextureInfo texelCopyTextureInfo =
utils::CreateTexelCopyTextureInfo(srcTexture, 0, {0, 0, 0}, wgpu::TextureAspect::All);
wgpu::Extent3D extent = {4, 1, 1};
queue.WriteTexture(&texelCopyTextureInfo, myData, sizeof(myData), &texelCopyBufferLayout,
&extent);
}
wgpu::BufferDescriptor descriptor;
descriptor.label = "dstBuffer";
descriptor.size = 4;
descriptor.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc;
wgpu::Buffer dstBuffer = device.CreateBuffer(&descriptor);
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);
{
wgpu::Buffer buffer = replay->GetObjectByLabel<wgpu::Buffer>("dstBuffer");
ASSERT_NE(buffer, nullptr);
EXPECT_BUFFER_U8_RANGE_EQ(myData, buffer, 0, sizeof(myData));
}
}
TEST_P(CaptureAndReplayTests, CaptureCopyTextureToTexture) {
const uint8_t myData[] = {0x11, 0x22, 0x33, 0x44};
wgpu::TextureDescriptor textureDesc;
textureDesc.label = "srcTexture";
textureDesc.size = {4, 1, 1};
textureDesc.format = wgpu::TextureFormat::R8Unorm;
textureDesc.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc;
wgpu::Texture srcTexture = device.CreateTexture(&textureDesc);
// Put data in source texture
{
wgpu::TexelCopyBufferLayout texelCopyBufferLayout =
utils::CreateTexelCopyBufferLayout(0, 4);
wgpu::TexelCopyTextureInfo texelCopyTextureInfo =
utils::CreateTexelCopyTextureInfo(srcTexture, 0, {0, 0, 0}, wgpu::TextureAspect::All);
wgpu::Extent3D extent = {4, 1, 1};
queue.WriteTexture(&texelCopyTextureInfo, myData, sizeof(myData), &texelCopyBufferLayout,
&extent);
}
textureDesc.label = "dstTexture";
wgpu::Texture dstTexture = device.CreateTexture(&textureDesc);
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);
{
wgpu::Texture texture = replay->GetObjectByLabel<wgpu::Texture>("dstTexture");
ASSERT_NE(texture, nullptr);
EXPECT_TEXTURE_EQ(&myData[0], texture, {0, 0}, {4, 1}, 0, wgpu::TextureAspect::All);
}
}
// 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) {
const uint8_t myData[] = {0x11, 0x22, 0x33, 0x44};
wgpu::TextureDescriptor textureDesc;
textureDesc.label = "dataTexture";
textureDesc.size = {4, 1, 1};
textureDesc.format = wgpu::TextureFormat::R8Unorm;
textureDesc.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc;
wgpu::Texture dataTexture = device.CreateTexture(&textureDesc);
// Put data in data texture
{
wgpu::TexelCopyBufferLayout texelCopyBufferLayout =
utils::CreateTexelCopyBufferLayout(0, 4);
wgpu::TexelCopyTextureInfo texelCopyTextureInfo =
utils::CreateTexelCopyTextureInfo(dataTexture, 0, {0, 0, 0}, wgpu::TextureAspect::All);
wgpu::Extent3D extent = {4, 1, 1};
queue.WriteTexture(&texelCopyTextureInfo, myData, sizeof(myData), &texelCopyBufferLayout,
&extent);
}
textureDesc.label = "srcTexture";
wgpu::Texture srcTexture = device.CreateTexture(&textureDesc);
// 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);
}
textureDesc.label = "dstTexture";
wgpu::Texture dstTexture = device.CreateTexture(&textureDesc);
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);
{
wgpu::Texture texture = replay->GetObjectByLabel<wgpu::Texture>("dstTexture");
ASSERT_NE(texture, nullptr);
EXPECT_TEXTURE_EQ(&myData[0], texture, {0, 0}, {4, 1}, 0, wgpu::TextureAspect::All);
}
}
// 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::TextureDescriptor textureDesc;
textureDesc.label = "srcTexture";
textureDesc.size = {1, 1, 1};
textureDesc.format = wgpu::TextureFormat::RGBA8Uint;
textureDesc.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::StorageBinding;
wgpu::Texture srcTexture = device.CreateTexture(&textureDesc);
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);
}
textureDesc.label = "dstTexture";
textureDesc.usage = wgpu::TextureUsage::CopyDst;
wgpu::Texture dstTexture = device.CreateTexture(&textureDesc);
// --- 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);
{
wgpu::Texture texture = replay->GetObjectByLabel<wgpu::Texture>("dstTexture");
ASSERT_NE(texture, nullptr);
uint8_t expected[] = {0x11, 0x22, 0x33, 0x44};
EXPECT_TEXTURE_EQ(&expected[0], texture, {0, 0}, {1, 1}, 0, wgpu::TextureAspect::All);
}
}
// 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::TextureDescriptor textureDesc;
textureDesc.label = "srcTexture";
textureDesc.size = {1, 1, 1};
textureDesc.format = wgpu::TextureFormat::RGBA8Uint;
textureDesc.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment;
wgpu::Texture srcTexture = device.CreateTexture(&textureDesc);
{
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);
}
textureDesc.label = "dstTexture";
textureDesc.usage = wgpu::TextureUsage::CopyDst;
wgpu::Texture dstTexture = device.CreateTexture(&textureDesc);
// --- 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);
{
wgpu::Texture texture = replay->GetObjectByLabel<wgpu::Texture>("dstTexture");
ASSERT_NE(texture, nullptr);
uint8_t expected[] = {0x11, 0x22, 0x33, 0x44};
EXPECT_TEXTURE_EQ(&expected[0], texture, {0, 0}, {1, 1}, 0, wgpu::TextureAspect::All);
}
}
// Capture and replay the simplest compute shader.
TEST_P(CaptureAndReplayTests, CaptureComputeShaderBasic) {
const char* label = "MyBuffer";
wgpu::BufferDescriptor descriptor;
descriptor.label = label;
descriptor.size = 4;
descriptor.usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst;
wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
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);
{
wgpu::Buffer buffer = replay->GetObjectByLabel<wgpu::Buffer>(label);
ASSERT_NE(buffer, nullptr);
uint8_t expected[] = {0x11, 0x22, 0x33, 0x44};
EXPECT_BUFFER_U8_RANGE_EQ(expected, buffer, 0, sizeof(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::BufferDescriptor descriptor;
descriptor.label = label;
descriptor.size = 4;
descriptor.usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst;
wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
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);
{
wgpu::Buffer buffer = replay->GetObjectByLabel<wgpu::Buffer>(label);
ASSERT_NE(buffer, nullptr);
uint8_t expected[] = {0x11, 0x22, 0x33, 0x44};
EXPECT_BUFFER_U8_RANGE_EQ(expected, buffer, 0, sizeof(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::BufferDescriptor descriptor;
descriptor.size = 4;
descriptor.usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst;
descriptor.label = "buffer1";
wgpu::Buffer buffer1 = device.CreateBuffer(&descriptor);
descriptor.label = "buffer2";
wgpu::Buffer buffer2 = device.CreateBuffer(&descriptor);
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);
{
wgpu::Buffer buffer = replay->GetObjectByLabel<wgpu::Buffer>("buffer1");
ASSERT_NE(buffer, nullptr);
uint8_t expected[] = {0x11, 0x22, 0x33, 0x44};
EXPECT_BUFFER_U8_RANGE_EQ(expected, buffer, 0, sizeof(expected));
}
{
wgpu::Buffer buffer = replay->GetObjectByLabel<wgpu::Buffer>("buffer2");
ASSERT_NE(buffer, nullptr);
uint8_t expected[] = {0x55, 0x66, 0x77, 0x88};
EXPECT_BUFFER_U8_RANGE_EQ(expected, buffer, 0, sizeof(expected));
}
}
// 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::BufferDescriptor descriptor;
descriptor.size = 4;
descriptor.usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst;
descriptor.label = "buffer";
wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
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);
{
wgpu::Buffer buffer = replay->GetObjectByLabel<wgpu::Buffer>("buffer");
ASSERT_NE(buffer, nullptr);
uint8_t expected[] = {0x55, 0x66, 0x77, 0x88};
EXPECT_BUFFER_U8_RANGE_EQ(expected, buffer, 0, sizeof(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::TextureDescriptor textureDesc;
textureDesc.label = "myTexture";
textureDesc.size = {1, 1, 1};
textureDesc.format = wgpu::TextureFormat::RGBA8Uint;
textureDesc.usage = wgpu::TextureUsage::RenderAttachment;
wgpu::Texture texture = device.CreateTexture(&textureDesc);
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);
{
wgpu::Texture texture = replay->GetObjectByLabel<wgpu::Texture>("myTexture");
ASSERT_NE(texture, nullptr);
uint8_t expected[] = {0x11, 0x22, 0x33, 0x44};
EXPECT_TEXTURE_EQ(&expected[0], texture, {0, 0}, {1, 1}, 0, wgpu::TextureAspect::All);
}
}
// Capture and replay the a render pass where a texture is rendered into another.
TEST_P(CaptureAndReplayTests, CaptureRenderPassBasicWithBindGroup) {
const uint8_t myData[] = {0x11, 0x22, 0x33, 0x44};
wgpu::TextureDescriptor textureDesc;
textureDesc.label = "srcTexture";
textureDesc.size = {1, 1, 1};
textureDesc.format = wgpu::TextureFormat::RGBA8Uint;
textureDesc.usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopyDst;
wgpu::Texture srcTexture = device.CreateTexture(&textureDesc);
{
wgpu::TexelCopyBufferLayout texelCopyBufferLayout =
utils::CreateTexelCopyBufferLayout(0, 4);
wgpu::TexelCopyTextureInfo texelCopyTextureInfo =
utils::CreateTexelCopyTextureInfo(srcTexture, 0, {0, 0, 0}, wgpu::TextureAspect::All);
wgpu::Extent3D extent = {1, 1, 1};
queue.WriteTexture(&texelCopyTextureInfo, myData, sizeof(myData), &texelCopyBufferLayout,
&extent);
}
textureDesc.usage = wgpu::TextureUsage::RenderAttachment;
textureDesc.label = "dstTexture";
wgpu::Texture dstTexture = device.CreateTexture(&textureDesc);
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);
{
wgpu::Texture texture = replay->GetObjectByLabel<wgpu::Texture>("dstTexture");
ASSERT_NE(texture, nullptr);
uint8_t expected[] = {0x11, 0x22, 0x33, 0x44};
EXPECT_TEXTURE_EQ(&expected[0], texture, {0, 0}, {1, 1}, 0, wgpu::TextureAspect::All);
}
}
TEST_P(CaptureAndReplayTests, CaptureRenderPassBasicWithAttributes) {
const float myVertices[] = {
-1, -1, 3, -1, -1, 3,
};
wgpu::BufferDescriptor descriptor;
descriptor.label = "vertexBuffer";
descriptor.size = sizeof(myVertices);
descriptor.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Vertex;
wgpu::Buffer vertexBuffer = device.CreateBuffer(&descriptor);
queue.WriteBuffer(vertexBuffer, 0, &myVertices, sizeof(myVertices));
wgpu::TextureDescriptor textureDesc;
textureDesc.label = "dstTexture";
textureDesc.size = {1, 1, 1};
textureDesc.format = wgpu::TextureFormat::RGBA8Uint;
textureDesc.usage = wgpu::TextureUsage::RenderAttachment;
wgpu::Texture dstTexture = device.CreateTexture(&textureDesc);
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);
{
wgpu::Texture texture = replay->GetObjectByLabel<wgpu::Texture>("dstTexture");
ASSERT_NE(texture, nullptr);
uint8_t expected[] = {0x11, 0x22, 0x33, 0x44};
EXPECT_TEXTURE_EQ(&expected[0], texture, {0, 0}, {1, 1}, 0, wgpu::TextureAspect::All);
}
}
// 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::BufferDescriptor descriptor;
descriptor.label = label;
descriptor.size = 4;
descriptor.usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst;
wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
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);
{
wgpu::Buffer buffer = replay->GetObjectByLabel<wgpu::Buffer>(label);
ASSERT_NE(buffer, nullptr);
uint8_t expected[] = {0x11, 0x22, 0x33, 0x44};
EXPECT_BUFFER_U8_RANGE_EQ(expected, buffer, 0, sizeof(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::TextureDescriptor textureDesc;
textureDesc.label = "myTexture";
textureDesc.size = {1, 1, 1};
textureDesc.format = wgpu::TextureFormat::RGBA8Uint;
textureDesc.usage = wgpu::TextureUsage::StorageBinding | wgpu::TextureUsage::CopySrc;
wgpu::Texture texture = device.CreateTexture(&textureDesc);
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);
{
wgpu::Texture texture = replay->GetObjectByLabel<wgpu::Texture>("myTexture");
ASSERT_NE(texture, nullptr);
uint8_t expected[] = {0x11, 0x22, 0x33, 0x44};
EXPECT_TEXTURE_EQ(&expected[0], texture, {0, 0}, {1, 1}, 0, wgpu::TextureAspect::All);
}
}
// 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::TextureDescriptor textureDesc;
textureDesc.label = "myTexture";
textureDesc.size = {1, 1, 1};
textureDesc.format = wgpu::TextureFormat::RGBA8Uint;
textureDesc.usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopyDst;
wgpu::Texture texture = device.CreateTexture(&textureDesc);
{
wgpu::TexelCopyBufferLayout texelCopyBufferLayout =
utils::CreateTexelCopyBufferLayout(0, 4);
wgpu::TexelCopyTextureInfo texelCopyTextureInfo =
utils::CreateTexelCopyTextureInfo(texture, 0, {0, 0, 0}, wgpu::TextureAspect::All);
wgpu::Extent3D extent = {1, 1, 1};
const uint8_t myData[] = {0x11, 0x22, 0x33, 0x44};
queue.WriteTexture(&texelCopyTextureInfo, myData, sizeof(myData), &texelCopyBufferLayout,
&extent);
}
wgpu::BufferDescriptor descriptor;
descriptor.label = "myBuffer";
descriptor.size = 4;
descriptor.usage =
wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc;
wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
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);
{
wgpu::Buffer buffer = replay->GetObjectByLabel<wgpu::Buffer>("myBuffer");
ASSERT_NE(buffer, nullptr);
uint8_t expected[] = {0x11, 0x22, 0x33, 0x44};
EXPECT_BUFFER_U8_RANGE_EQ(expected, buffer, 0, sizeof(expected));
}
}
DAWN_INSTANTIATE_TEST(CaptureAndReplayTests, WebGPUBackend());
} // anonymous namespace
} // namespace dawn