| // 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 <array> | |
| #include <memory> | |
| #include <ostream> | |
| #include <sstream> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #include "dawn/native/WebGPUBackend.h" | |
| #include "dawn/replay/Replay.h" | |
| #include "src/dawn/tests/DawnTest.h" | |
| #include "src/dawn/tests/MockCallback.h" | |
| #include "src/dawn/utils/ComboRenderBundleEncoderDescriptor.h" | |
| #include "src/dawn/utils/ComboRenderPipelineDescriptor.h" | |
| #include "src/dawn/utils/TestUtils.h" | |
| #include "src/dawn/utils/WGPUHelpers.h" | |
| #include "src/utils/compiler.h" | |
| #ifdef DAWN_SUPPORTS_GLFW_FOR_WINDOWING | |
| #include "GLFW/glfw3.h" | |
| #include "webgpu/webgpu_glfw.h" | |
| #endif | |
| namespace dawn { | |
| namespace { | |
| class CaptureAndReplayTests : public DawnTest { | |
| public: | |
| void SetUp() override { | |
| DawnTest::SetUp(); | |
| // This test is already testing capture and replay, we don't need to wrap it and test again. | |
| DAWN_TEST_UNSUPPORTED_IF(IsCaptureReplayCheckingEnabled()); | |
| // StartCapture/EndCapture are dawn::native APIs that require a native | |
| // WGPUDevice. When the wire is enabled, device.Get() returns a wire | |
| // client device which cannot be cast to a native device, causing a crash. | |
| // TODO(crbug.com/452924800): Remove once these tests work properly with | |
| // the WebGPU on WebGPU backend with wire. | |
| DAWN_SUPPRESS_TEST_IF(UsesWire()); | |
| } | |
| class Recorder { | |
| public: | |
| static std::unique_ptr<Recorder> CreateAndStart(wgpu::Device device) { | |
| auto recorder = std::unique_ptr<Recorder>(new Recorder(device)); | |
| native::webgpu::StartCapture(device.Get(), recorder->mCommandStream, | |
| recorder->mContentStream); | |
| return recorder; | |
| } | |
| void SetSurfaces(std::vector<wgpu::Surface> surfaces) { mSurfaces = std::move(surfaces); } | |
| void EndCapture() { | |
| if (mCapture) { | |
| return; | |
| } | |
| native::webgpu::EndCapture(mDevice.Get()); | |
| mDevice = nullptr; | |
| auto commandData = mCommandStream.str(); | |
| auto contentData = mContentStream.str(); | |
| std::istringstream commandIStream(commandData); | |
| std::istringstream contentIStream(contentData); | |
| mCapture = replay::Capture::Create(commandIStream, commandData.size(), contentIStream, | |
| contentData.size()); | |
| } | |
| replay::Replay* Replay(wgpu::Device device) { | |
| CreateReplay(device); | |
| bool result = mReplay->Play(); | |
| EXPECT_TRUE(result); | |
| return mReplay.get(); | |
| } | |
| replay::Replay* CreateReplay(wgpu::Device device) { | |
| if (!mCapture) { | |
| EndCapture(); | |
| } | |
| mReplay = replay::Replay::Create(device, std::move(mCapture)); | |
| if (!mSurfaces.empty()) { | |
| mReplay->SetSurfaces(mSurfaces); | |
| } | |
| return mReplay.get(); | |
| } | |
| replay::Capture* GetCapture() { | |
| if (mReplay) { | |
| return mReplay->GetCapture(); | |
| } | |
| if (!mCapture) { | |
| EndCapture(); | |
| } | |
| return mCapture.get(); | |
| } | |
| replay::Replay* GetReplay() const { | |
| DAWN_ASSERT(mReplay); | |
| return mReplay.get(); | |
| } | |
| ~Recorder() { | |
| if (mDevice != nullptr) { | |
| native::webgpu::EndCapture(mDevice.Get()); | |
| } | |
| } | |
| private: | |
| explicit Recorder(wgpu::Device device) : mDevice(device) {} | |
| wgpu::Device mDevice; | |
| std::vector<wgpu::Surface> mSurfaces; | |
| std::ostringstream mCommandStream; | |
| std::ostringstream mContentStream; | |
| std::unique_ptr<replay::Capture> mCapture; | |
| std::unique_ptr<replay::Replay> mReplay; | |
| }; | |
| wgpu::Buffer CreateBuffer(const char* label, | |
| uint64_t size, | |
| wgpu::BufferUsage usage, | |
| bool mappedAtCreation = false) { | |
| wgpu::BufferDescriptor descriptor; | |
| descriptor.label = label; | |
| descriptor.size = size; | |
| descriptor.usage = usage; | |
| descriptor.mappedAtCreation = mappedAtCreation; | |
| return device.CreateBuffer(&descriptor); | |
| } | |
| wgpu::Texture CreateTexture(const char* label, | |
| const wgpu::Extent3D& size, | |
| wgpu::TextureFormat format, | |
| wgpu::TextureUsage usage) { | |
| wgpu::TextureDescriptor textureDesc; | |
| textureDesc.label = label; | |
| textureDesc.size = size; | |
| textureDesc.format = format; | |
| textureDesc.usage = usage; | |
| return device.CreateTexture(&textureDesc); | |
| } | |
| template <typename T> | |
| void WriteFullTexture(wgpu::Texture texture, | |
| wgpu::TextureFormat format, | |
| const wgpu::Extent3D& size, | |
| const T& data) { | |
| ASSERT_TRUE(sizeof(data) > 0); | |
| uint32_t bytesPerBlock = utils::GetTexelBlockSizeInBytes(format); | |
| wgpu::TexelCopyBufferLayout texelCopyBufferLayout = | |
| utils::CreateTexelCopyBufferLayout(0, bytesPerBlock * size.width); | |
| wgpu::TexelCopyTextureInfo texelCopyTextureInfo = | |
| utils::CreateTexelCopyTextureInfo(texture, 0, {0, 0, 0}, wgpu::TextureAspect::All); | |
| queue.WriteTexture(&texelCopyTextureInfo, data, sizeof(data), &texelCopyBufferLayout, | |
| &size); | |
| } | |
| template <typename T> | |
| void ExpectBufferEQ(replay::Replay* replay, const char* label, const T& expected) { | |
| ASSERT_TRUE(sizeof(expected) > 0); | |
| wgpu::Buffer buffer = replay->GetObjectByLabel<wgpu::Buffer>(label); | |
| ASSERT_NE(buffer, nullptr); | |
| EXPECT_BUFFER_U8_RANGE_EQ(expected, buffer, 0, sizeof(expected)); | |
| } | |
| template <typename T> | |
| void ExpectTextureEQ(replay::Replay* replay, | |
| const char* label, | |
| const wgpu::Extent3D& size, | |
| const T& expected) { | |
| wgpu::Texture texture = replay->GetObjectByLabel<wgpu::Texture>(label); | |
| ASSERT_NE(texture, nullptr); | |
| ASSERT_TRUE(size.width > 0 && size.height > 0 && size.depthOrArrayLayers > 0); | |
| EXPECT_TEXTURE_EQ(&expected[0], texture, {0, 0}, size, 0, wgpu::TextureAspect::All); | |
| } | |
| }; | |
| // During capture, makes a buffer, puts data in it. | |
| // Then, replays and checks the data is correct. | |
| TEST_P(CaptureAndReplayTests, Basic) { | |
| const char* label = "MyBuffer"; | |
| const uint8_t myData[] = {0x11, 0x22, 0x33, 0x44}; | |
| auto recorder = Recorder::CreateAndStart(device); | |
| wgpu::Buffer buffer = CreateBuffer(label, 4, wgpu::BufferUsage::CopyDst); | |
| queue.WriteBuffer(buffer, 0, &myData, sizeof(myData)); | |
| auto replay = recorder->Replay(device); | |
| ExpectBufferEQ(replay, label, myData); | |
| } | |
| // During capture, makes a buffer, puts data in it. | |
| // Then, replays and checks the data is correct. | |
| // It uses on label with 5 characters which may skew alignment. | |
| TEST_P(CaptureAndReplayTests, NonMultipleOf4LabelLength) { | |
| const char* label = "MyBuf"; | |
| const uint8_t myData[] = {0x11, 0x22, 0x33, 0x44}; | |
| // --- capture --- | |
| auto recorder = Recorder::CreateAndStart(device); | |
| wgpu::Buffer buffer = CreateBuffer(label, 4, wgpu::BufferUsage::CopyDst); | |
| queue.WriteBuffer(buffer, 0, &myData, sizeof(myData)); | |
| // --- replay --- | |
| auto replay = recorder->Replay(device); | |
| ExpectBufferEQ(replay, label, myData); | |
| } | |
| // Before capture, creates a buffer and sets half of it with WriteBuffer. | |
| // It then starts a capture and writes the other half with WriteBuffer. | |
| // On replay both halves should have the correct data.. | |
| TEST_P(CaptureAndReplayTests, StartCaptureAfterBufferCreationWriteBuffer) { | |
| const char* label = "MyBuffer"; | |
| const uint8_t myData0[] = {0x11, 0x22, 0x33, 0x44}; | |
| const uint8_t myData1[] = {0x55, 0x66, 0x77, 0x88}; | |
| wgpu::Buffer buffer = CreateBuffer(label, 8, wgpu::BufferUsage::CopyDst); | |
| queue.WriteBuffer(buffer, 0, &myData0, sizeof(myData0)); | |
| // --- capture --- | |
| auto recorder = Recorder::CreateAndStart(device); | |
| queue.WriteBuffer(buffer, 4, &myData1, sizeof(myData1)); | |
| // --- replay --- | |
| auto replay = recorder->Replay(device); | |
| uint8_t expected[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}; | |
| ExpectBufferEQ(replay, label, expected); | |
| } | |
| // Before capture, creates a buffer and sets half of it with mappedAtCreation. | |
| // It then starts a capture and writes the other half with WriteBuffer. | |
| // On replay both halves should have the correct data.. | |
| TEST_P(CaptureAndReplayTests, StartCaptureAfterBufferCreationMappedAtCreation) { | |
| const char* label = "MyBuffer"; | |
| const uint8_t myData0[] = {0x11, 0x22, 0x33, 0x44}; | |
| const uint8_t myData1[] = {0x55, 0x66, 0x77, 0x88}; | |
| wgpu::Buffer buffer = CreateBuffer(label, 8, wgpu::BufferUsage::CopyDst, true); | |
| DAWN_UNSAFE_TODO(std::memcpy(buffer.GetMappedRange(), myData0, sizeof(myData0))); | |
| buffer.Unmap(); | |
| // --- capture --- | |
| auto recorder = Recorder::CreateAndStart(device); | |
| queue.WriteBuffer(buffer, 4, &myData1, sizeof(myData1)); | |
| // --- replay --- | |
| auto replay = recorder->Replay(device); | |
| uint8_t expected[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}; | |
| ExpectBufferEQ(replay, label, expected); | |
| } | |
| // Before capture, creates a buffer and sets half of it with a compute shader. | |
| // It then starts a capture and writes the other half with WriteBuffer. | |
| // On replay both halves should have the correct data.. | |
| TEST_P(CaptureAndReplayTests, StartCaptureAfterBufferCreationComputeShader) { | |
| const char* label = "MyBuffer"; | |
| wgpu::Buffer buffer = | |
| CreateBuffer(label, 8, wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst); | |
| const char* shader = R"( | |
| @group(0) @binding(0) var<storage, read_write> result : u32; | |
| @compute @workgroup_size(1) fn main() { | |
| result = 0x44332211; | |
| } | |
| )"; | |
| auto module = utils::CreateShaderModule(device, shader); | |
| wgpu::ComputePipelineDescriptor csDesc; | |
| csDesc.compute.module = module; | |
| wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc); | |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), | |
| { | |
| {0, buffer}, | |
| }); | |
| wgpu::CommandBuffer commands; | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, bindGroup); | |
| pass.DispatchWorkgroups(1); | |
| pass.End(); | |
| commands = encoder.Finish(); | |
| } | |
| queue.Submit(1, &commands); | |
| // --- capture --- | |
| auto recorder = Recorder::CreateAndStart(device); | |
| const uint8_t myData[] = {0x55, 0x66, 0x77, 0x88}; | |
| queue.WriteBuffer(buffer, 4, &myData, sizeof(myData)); | |
| // --- replay --- | |
| auto replay = recorder->Replay(device); | |
| uint8_t expected[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}; | |
| ExpectBufferEQ(replay, label, expected); | |
| } | |
| // Before capture, creates a buffer and sets half of it with copyBufferToBuffer. | |
| // It then starts a capture and writes the other half with WriteBuffer. | |
| // On replay both halves should have the correct data.. | |
| TEST_P(CaptureAndReplayTests, StartCaptureAfterBufferCreationCopyB2B) { | |
| const char* srcLabel = "SrcBuffer"; | |
| const char* dstLabel = "DstBuffer"; | |
| wgpu::Buffer dstBuffer = CreateBuffer(dstLabel, 8, wgpu::BufferUsage::CopyDst); | |
| wgpu::Buffer srcBuffer = | |
| CreateBuffer(srcLabel, 8, wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst); | |
| const uint8_t myData1[] = {0x11, 0x22, 0x33, 0x44}; | |
| queue.WriteBuffer(srcBuffer, 0, &myData1, sizeof(myData1)); | |
| wgpu::CommandBuffer commands; | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| encoder.CopyBufferToBuffer(srcBuffer, 0, dstBuffer, 0, 4); | |
| commands = encoder.Finish(); | |
| } | |
| queue.Submit(1, &commands); | |
| // --- capture --- | |
| auto recorder = Recorder::CreateAndStart(device); | |
| const uint8_t myData2[] = {0x55, 0x66, 0x77, 0x88}; | |
| queue.WriteBuffer(dstBuffer, 4, &myData2, sizeof(myData2)); | |
| // --- replay --- | |
| auto replay = recorder->Replay(device); | |
| uint8_t expected[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}; | |
| ExpectBufferEQ(replay, dstLabel, expected); | |
| } | |
| // During first capture, makes a buffer, puts data in it. | |
| // During 2nd capture, puts a little data in the same buffer. | |
| // Then, replays the 2nd capture and checks the data is correct. | |
| TEST_P(CaptureAndReplayTests, TwoCaptures) { | |
| const char* label = "MyBuffer"; | |
| const uint8_t myData1[] = {0x11, 0x22, 0x33, 0x44}; | |
| const uint8_t myData2[] = {0x55, 0x66, 0x77, 0x88}; | |
| wgpu::Buffer buffer; | |
| { | |
| auto recorder = Recorder::CreateAndStart(device); | |
| buffer = CreateBuffer(label, 8, wgpu::BufferUsage::CopyDst); | |
| queue.WriteBuffer(buffer, 0, &myData1, sizeof(myData1)); | |
| recorder->EndCapture(); | |
| } | |
| { | |
| auto recorder = Recorder::CreateAndStart(device); | |
| queue.WriteBuffer(buffer, 4, &myData2, sizeof(myData2)); | |
| auto replay = recorder->Replay(device); | |
| uint8_t expected[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}; | |
| ExpectBufferEQ(replay, label, expected); | |
| } | |
| } | |
| // We make a buffer before capture. During capture | |
| // write map it, put data in it, copyB2B from it, | |
| // write map it again, put different data in it, copyB2B from it, | |
| // then check the data is correct on replay. | |
| TEST_P(CaptureAndReplayTests, MapWrite) { | |
| const uint8_t myData1[] = {0x11, 0x22, 0x33, 0x44}; | |
| const uint8_t myData2[] = {0x55, 0x66, 0x77, 0x88}; | |
| wgpu::Buffer srcBuffer = | |
| CreateBuffer("srcBuffer", 4, wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc); | |
| wgpu::Buffer dstBuffer = CreateBuffer("dstBuffer", 4, wgpu::BufferUsage::CopyDst); | |
| auto recorder = Recorder::CreateAndStart(device); | |
| MapAsyncAndWait(srcBuffer, wgpu::MapMode::Write, 0, 4); | |
| srcBuffer.WriteMappedRange(0, &myData1, sizeof(myData1)); | |
| srcBuffer.Unmap(); | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| encoder.CopyBufferToBuffer(srcBuffer, 0, dstBuffer, 0, 4); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| } | |
| MapAsyncAndWait(srcBuffer, wgpu::MapMode::Write, 0, 4); | |
| srcBuffer.WriteMappedRange(0, &myData2, sizeof(myData2)); | |
| srcBuffer.Unmap(); | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| encoder.CopyBufferToBuffer(srcBuffer, 0, dstBuffer, 0, 4); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| } | |
| auto replay = recorder->Replay(device); | |
| ExpectBufferEQ(replay, "srcBuffer", myData2); | |
| ExpectBufferEQ(replay, "dstBuffer", myData2); | |
| } | |
| // We make 2 buffers before capture. During capture we map one buffer | |
| // put some data it in via map/unmap. We then copy from that buffer to the other buffer. | |
| // On replay check the data is correct. | |
| TEST_P(CaptureAndReplayTests, CaptureWithMapWriteDuringCapture) { | |
| const char* srcLabel = "srcBuffer"; | |
| const char* dstLabel = "dstBuffer"; | |
| const uint8_t myData1[] = {0x11, 0x22, 0x33, 0x44}; | |
| const uint8_t myData2[] = {0x55, 0x66, 0x77, 0x88}; | |
| wgpu::Buffer dstBuffer = | |
| CreateBuffer(dstLabel, 8, wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc); | |
| queue.WriteBuffer(dstBuffer, 0, &myData1, sizeof(myData1)); | |
| wgpu::Buffer srcBuffer = | |
| CreateBuffer(srcLabel, 4, wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc); | |
| auto recorder = Recorder::CreateAndStart(device); | |
| MapAsyncAndWait(srcBuffer, wgpu::MapMode::Write, 0, 4); | |
| srcBuffer.WriteMappedRange(0, &myData2, sizeof(myData2)); | |
| srcBuffer.Unmap(); | |
| wgpu::CommandBuffer commands; | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| encoder.CopyBufferToBuffer(srcBuffer, 0, dstBuffer, 4, 4); | |
| commands = encoder.Finish(); | |
| } | |
| queue.Submit(1, &commands); | |
| auto replay = recorder->Replay(device); | |
| uint8_t expected[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}; | |
| ExpectBufferEQ(replay, dstLabel, expected); | |
| } | |
| // Capture a buffer with MapRead. | |
| TEST_P(CaptureAndReplayTests, CaptureWithMapRead) { | |
| const char* srcLabel = "srcBuffer"; | |
| const char* dstLabel = "dstBuffer"; | |
| const uint8_t myData[] = {0x55, 0x66, 0x77, 0x88}; | |
| wgpu::Buffer srcBuffer = | |
| CreateBuffer(srcLabel, 4, wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc); | |
| queue.WriteBuffer(srcBuffer, 0, &myData, sizeof(myData)); | |
| wgpu::Buffer dstBuffer = | |
| CreateBuffer(dstLabel, 4, wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopyDst); | |
| auto recorder = Recorder::CreateAndStart(device); | |
| wgpu::CommandBuffer commands; | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| encoder.CopyBufferToBuffer(srcBuffer, 0, dstBuffer, 0, sizeof(myData)); | |
| commands = encoder.Finish(); | |
| } | |
| queue.Submit(1, &commands); | |
| recorder->Replay(device); | |
| // We can't use ExpectBufferEQ here because it uses a copy and `dstBuffer` | |
| // does not have CopySrc. So, let's do it ourselves. | |
| MapAsyncAndWait(dstBuffer, wgpu::MapMode::Read, 0, sizeof(myData)); | |
| auto actual = static_cast<const uint8_t*>(dstBuffer.GetConstMappedRange(0, sizeof(myData))); | |
| std::span<const uint8_t> actual_span(actual, std::size(myData)); | |
| ASSERT_THAT(actual_span, ::testing::ElementsAreArray(myData)); | |
| } | |
| TEST_P(CaptureAndReplayTests, CaptureCopyBufferToBuffer) { | |
| const uint8_t myData[] = {0x11, 0x22, 0x33, 0x44}; | |
| wgpu::Buffer srcBuffer = | |
| CreateBuffer("srcBuffer", 4, wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc); | |
| queue.WriteBuffer(srcBuffer, 0, &myData, sizeof(myData)); | |
| wgpu::Buffer dstBuffer = CreateBuffer("dstBuffer", 4, wgpu::BufferUsage::CopyDst); | |
| wgpu::CommandBuffer commands; | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| encoder.CopyBufferToBuffer(srcBuffer, 0, dstBuffer, 0, 4); | |
| commands = encoder.Finish(); | |
| } | |
| auto recorder = Recorder::CreateAndStart(device); | |
| queue.Submit(1, &commands); | |
| auto replay = recorder->Replay(device); | |
| ExpectBufferEQ(replay, "dstBuffer", myData); | |
| } | |
| TEST_P(CaptureAndReplayTests, WriteTexture) { | |
| wgpu::Texture texture = | |
| CreateTexture("myTexture", {4}, wgpu::TextureFormat::R8Unorm, | |
| wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc); | |
| auto recorder = Recorder::CreateAndStart(device); | |
| const uint8_t myData[] = {0x11, 0x22, 0x33, 0x44}; | |
| WriteFullTexture(texture, wgpu::TextureFormat::R8Unorm, {4}, myData); | |
| auto replay = recorder->Replay(device); | |
| ExpectTextureEQ(replay, "myTexture", {4}, myData); | |
| } | |
| TEST_P(CaptureAndReplayTests, CaptureCopyBufferToTexture) { | |
| const uint8_t myData[] = {0x11, 0x22, 0x33, 0x44}; | |
| wgpu::Buffer srcBuffer = | |
| CreateBuffer("srcBuffer", 4, wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc); | |
| queue.WriteBuffer(srcBuffer, 0, &myData, sizeof(myData)); | |
| wgpu::Texture dstTexture = | |
| CreateTexture("dstTexture", {4}, wgpu::TextureFormat::R8Unorm, | |
| wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc); | |
| wgpu::CommandBuffer commands; | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::TexelCopyBufferInfo texelCopyBufferInfo = | |
| utils::CreateTexelCopyBufferInfo(srcBuffer, 0, 256, 1); | |
| wgpu::TexelCopyTextureInfo texelCopyTextureInfo = | |
| utils::CreateTexelCopyTextureInfo(dstTexture, 0, {0, 0, 0}, wgpu::TextureAspect::All); | |
| wgpu::Extent3D extent = {4, 1, 1}; | |
| encoder.CopyBufferToTexture(&texelCopyBufferInfo, &texelCopyTextureInfo, &extent); | |
| commands = encoder.Finish(); | |
| } | |
| auto recorder = Recorder::CreateAndStart(device); | |
| queue.Submit(1, &commands); | |
| auto replay = recorder->Replay(device); | |
| ExpectTextureEQ(replay, "dstTexture", {4}, myData); | |
| } | |
| TEST_P(CaptureAndReplayTests, CaptureCopyTextureToBuffer) { | |
| wgpu::Texture srcTexture = | |
| CreateTexture("srcTexture", {4}, wgpu::TextureFormat::R8Unorm, | |
| wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc); | |
| // Put data in source texture | |
| const uint8_t myData[] = {0x11, 0x22, 0x33, 0x44}; | |
| WriteFullTexture(srcTexture, wgpu::TextureFormat::R8Unorm, {4}, myData); | |
| wgpu::Buffer dstBuffer = | |
| CreateBuffer("dstBuffer", 4, wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc); | |
| wgpu::CommandBuffer commands; | |
| { | |
| // Copy srcTexture to dstBuffer | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::TexelCopyTextureInfo texelCopyTextureInfo = | |
| utils::CreateTexelCopyTextureInfo(srcTexture, 0, {0, 0, 0}, wgpu::TextureAspect::All); | |
| wgpu::TexelCopyBufferInfo texelCopyBufferInfo = | |
| utils::CreateTexelCopyBufferInfo(dstBuffer, 0, 256, 1); | |
| wgpu::Extent3D extent = {4, 1, 1}; | |
| encoder.CopyTextureToBuffer(&texelCopyTextureInfo, &texelCopyBufferInfo, &extent); | |
| commands = encoder.Finish(); | |
| } | |
| auto recorder = Recorder::CreateAndStart(device); | |
| queue.Submit(1, &commands); | |
| auto replay = recorder->Replay(device); | |
| ExpectBufferEQ(replay, "dstBuffer", myData); | |
| } | |
| TEST_P(CaptureAndReplayTests, CaptureCopyTextureToTexture) { | |
| wgpu::Texture srcTexture = | |
| CreateTexture("srcTexture", {4}, wgpu::TextureFormat::R8Unorm, | |
| wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc); | |
| // Put data in source texture | |
| const uint8_t myData[] = {0x11, 0x22, 0x33, 0x44}; | |
| WriteFullTexture(srcTexture, wgpu::TextureFormat::R8Unorm, {4}, myData); | |
| wgpu::Texture dstTexture = | |
| CreateTexture("dstTexture", {4}, wgpu::TextureFormat::R8Unorm, | |
| wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc); | |
| wgpu::CommandBuffer commands; | |
| { | |
| // Copy srcTexture to dstBuffer | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::TexelCopyTextureInfo srcTexelCopyTextureInfo = | |
| utils::CreateTexelCopyTextureInfo(srcTexture, 0, {0, 0, 0}, wgpu::TextureAspect::All); | |
| wgpu::TexelCopyTextureInfo dstTexelCopyTextureInfo = | |
| utils::CreateTexelCopyTextureInfo(dstTexture, 0, {0, 0, 0}, wgpu::TextureAspect::All); | |
| wgpu::Extent3D extent = {4, 1, 1}; | |
| encoder.CopyTextureToTexture(&srcTexelCopyTextureInfo, &dstTexelCopyTextureInfo, &extent); | |
| commands = encoder.Finish(); | |
| } | |
| auto recorder = Recorder::CreateAndStart(device); | |
| queue.Submit(1, &commands); | |
| auto replay = recorder->Replay(device); | |
| ExpectTextureEQ(replay, "dstTexture", {4}, myData); | |
| } | |
| // We make 3 textures. Put data in the first one. Copy to the 2nd one. | |
| // Copy tha the 3rd. Check the results. The reason for this test is that | |
| // the first texture is marked as initialized by WriteTexture. The 2nd is | |
| // not. So, if the texture is not marked as initialized by CopyT2T then | |
| // capture will fail as it will not copy the contents of the 2nd texture. | |
| TEST_P(CaptureAndReplayTests, CaptureCopyTextureToTextureFromCopyT2TTexture) { | |
| wgpu::Texture dataTexture = | |
| CreateTexture("dataTexture", {4}, wgpu::TextureFormat::R8Unorm, | |
| wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc); | |
| // Put data in data texture | |
| const uint8_t myData[] = {0x11, 0x22, 0x33, 0x44}; | |
| WriteFullTexture(dataTexture, wgpu::TextureFormat::R8Unorm, {4}, myData); | |
| wgpu::Texture srcTexture = | |
| CreateTexture("srcTexture", {4}, wgpu::TextureFormat::R8Unorm, | |
| wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc); | |
| // Copy the data texture ot the src texture | |
| { | |
| wgpu::CommandBuffer commands; | |
| { | |
| // Copy srcTexture to dstBuffer | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::TexelCopyTextureInfo srcTexelCopyTextureInfo = utils::CreateTexelCopyTextureInfo( | |
| dataTexture, 0, {0, 0, 0}, wgpu::TextureAspect::All); | |
| wgpu::TexelCopyTextureInfo dstTexelCopyTextureInfo = utils::CreateTexelCopyTextureInfo( | |
| srcTexture, 0, {0, 0, 0}, wgpu::TextureAspect::All); | |
| wgpu::Extent3D extent = {4, 1, 1}; | |
| encoder.CopyTextureToTexture(&srcTexelCopyTextureInfo, &dstTexelCopyTextureInfo, | |
| &extent); | |
| commands = encoder.Finish(); | |
| } | |
| queue.Submit(1, &commands); | |
| } | |
| wgpu::Texture dstTexture = | |
| CreateTexture("dstTexture", {4}, wgpu::TextureFormat::R8Unorm, | |
| wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc); | |
| wgpu::CommandBuffer commands; | |
| { | |
| // Copy srcTexture to dstBuffer | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::TexelCopyTextureInfo srcTexelCopyTextureInfo = | |
| utils::CreateTexelCopyTextureInfo(srcTexture, 0, {0, 0, 0}, wgpu::TextureAspect::All); | |
| wgpu::TexelCopyTextureInfo dstTexelCopyTextureInfo = | |
| utils::CreateTexelCopyTextureInfo(dstTexture, 0, {0, 0, 0}, wgpu::TextureAspect::All); | |
| wgpu::Extent3D extent = {4, 1, 1}; | |
| encoder.CopyTextureToTexture(&srcTexelCopyTextureInfo, &dstTexelCopyTextureInfo, &extent); | |
| commands = encoder.Finish(); | |
| } | |
| auto recorder = Recorder::CreateAndStart(device); | |
| queue.Submit(1, &commands); | |
| auto replay = recorder->Replay(device); | |
| ExpectTextureEQ(replay, "dstTexture", {4}, myData); | |
| } | |
| // Before capture, creates a texture and sets it in a compute pass as a storage texture. | |
| // Then, captures a copyT2T to a 2nd texture. Checks the 2nd texture has the correct data on replay. | |
| TEST_P(CaptureAndReplayTests, CaptureCopyTextureToTextureFromComputeTexture) { | |
| wgpu::Texture srcTexture = | |
| CreateTexture("srcTexture", {1}, wgpu::TextureFormat::RGBA8Uint, | |
| wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::StorageBinding); | |
| const char* shader = R"( | |
| @group(0) @binding(0) var tex: texture_storage_2d<rgba8uint, write>; | |
| @compute @workgroup_size(1) fn main() { | |
| textureStore(tex, vec2u(0), vec4<u32>(0x11, 0x22, 0x33, 0x44)); | |
| } | |
| )"; | |
| auto module = utils::CreateShaderModule(device, shader); | |
| wgpu::ComputePipelineDescriptor csDesc; | |
| csDesc.compute.module = module; | |
| wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc); | |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), | |
| { | |
| {0, srcTexture.CreateView()}, | |
| }); | |
| { | |
| wgpu::CommandBuffer commands; | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, bindGroup); | |
| pass.DispatchWorkgroups(1); | |
| pass.End(); | |
| commands = encoder.Finish(); | |
| } | |
| queue.Submit(1, &commands); | |
| } | |
| wgpu::Texture dstTexture = CreateTexture( | |
| "dstTexture", {1, 1, 1}, wgpu::TextureFormat::RGBA8Uint, wgpu::TextureUsage::CopyDst); | |
| // --- capture --- | |
| auto recorder = Recorder::CreateAndStart(device); | |
| { | |
| wgpu::CommandBuffer commands; | |
| { | |
| // Copy srcTexture to dstBuffer | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::TexelCopyTextureInfo srcTexelCopyTextureInfo = utils::CreateTexelCopyTextureInfo( | |
| srcTexture, 0, {0, 0, 0}, wgpu::TextureAspect::All); | |
| wgpu::TexelCopyTextureInfo dstTexelCopyTextureInfo = utils::CreateTexelCopyTextureInfo( | |
| dstTexture, 0, {0, 0, 0}, wgpu::TextureAspect::All); | |
| wgpu::Extent3D extent = {1, 1, 1}; | |
| encoder.CopyTextureToTexture(&srcTexelCopyTextureInfo, &dstTexelCopyTextureInfo, | |
| &extent); | |
| commands = encoder.Finish(); | |
| } | |
| queue.Submit(1, &commands); | |
| } | |
| // --- replay --- | |
| auto replay = recorder->Replay(device); | |
| utils::RGBA8 expected[] = {{0x11, 0x22, 0x33, 0x44}}; | |
| ExpectTextureEQ(replay, "dstTexture", {1}, expected); | |
| } | |
| // Before capture, creates a texture and sets in a render pass as a render attachment | |
| // Then, captures a copyT2T to a 2nd texture. Checks the 2nd texture has the correct data on replay. | |
| TEST_P(CaptureAndReplayTests, CaptureCopyTextureToTextureFromRenderTexture) { | |
| wgpu::Texture srcTexture = | |
| CreateTexture("srcTexture", {1}, wgpu::TextureFormat::RGBA8Uint, | |
| wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment); | |
| { | |
| wgpu::CommandBuffer commands; | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| utils::ComboRenderPassDescriptor passDescriptor({srcTexture.CreateView()}); | |
| passDescriptor.cColorAttachments[0].clearValue = {0x11, 0x22, 0x33, 0x44}; | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor); | |
| pass.End(); | |
| commands = encoder.Finish(); | |
| } | |
| queue.Submit(1, &commands); | |
| } | |
| wgpu::Texture dstTexture = CreateTexture( | |
| "dstTexture", {1, 1, 1}, wgpu::TextureFormat::RGBA8Uint, wgpu::TextureUsage::CopyDst); | |
| // --- capture --- | |
| auto recorder = Recorder::CreateAndStart(device); | |
| { | |
| wgpu::CommandBuffer commands; | |
| { | |
| // Copy srcTexture to dstBuffer | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::TexelCopyTextureInfo srcTexelCopyTextureInfo = utils::CreateTexelCopyTextureInfo( | |
| srcTexture, 0, {0, 0, 0}, wgpu::TextureAspect::All); | |
| wgpu::TexelCopyTextureInfo dstTexelCopyTextureInfo = utils::CreateTexelCopyTextureInfo( | |
| dstTexture, 0, {0, 0, 0}, wgpu::TextureAspect::All); | |
| wgpu::Extent3D extent = {1, 1, 1}; | |
| encoder.CopyTextureToTexture(&srcTexelCopyTextureInfo, &dstTexelCopyTextureInfo, | |
| &extent); | |
| commands = encoder.Finish(); | |
| } | |
| queue.Submit(1, &commands); | |
| } | |
| // --- replay --- | |
| auto replay = recorder->Replay(device); | |
| utils::RGBA8 expected[] = {{0x11, 0x22, 0x33, 0x44}}; | |
| ExpectTextureEQ(replay, "dstTexture", {1}, expected); | |
| } | |
| // Capture and replay the simplest compute shader. | |
| TEST_P(CaptureAndReplayTests, CaptureComputeShaderBasic) { | |
| const char* label = "MyBuffer"; | |
| wgpu::Buffer buffer = | |
| CreateBuffer(label, 4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst); | |
| const char* shader = R"( | |
| @group(0) @binding(0) var<storage, read_write> result : u32; | |
| @compute @workgroup_size(1) fn main() { | |
| result = 0x44332211; | |
| } | |
| )"; | |
| auto module = utils::CreateShaderModule(device, shader); | |
| wgpu::ComputePipelineDescriptor csDesc; | |
| csDesc.compute.module = module; | |
| wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc); | |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), | |
| { | |
| {0, buffer}, | |
| }); | |
| wgpu::CommandBuffer commands; | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, bindGroup); | |
| pass.DispatchWorkgroups(1); | |
| pass.End(); | |
| commands = encoder.Finish(); | |
| } | |
| // --- capture --- | |
| auto recorder = Recorder::CreateAndStart(device); | |
| queue.Submit(1, &commands); | |
| // --- replay --- | |
| auto replay = recorder->Replay(device); | |
| uint8_t expected[] = {0x11, 0x22, 0x33, 0x44}; | |
| ExpectBufferEQ(replay, label, expected); | |
| } | |
| // Capture and replay the simplest compute shader but set the bindGroup | |
| // before setting the pipeline. | |
| TEST_P(CaptureAndReplayTests, CaptureComputeShaderBasicSetBindGroupFirst) { | |
| const char* label = "MyBuffer"; | |
| wgpu::Buffer buffer = | |
| CreateBuffer(label, 4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst); | |
| const char* shader = R"( | |
| @group(0) @binding(0) var<storage, read_write> result : u32; | |
| @compute @workgroup_size(1) fn main() { | |
| result = 0x44332211; | |
| } | |
| )"; | |
| auto module = utils::CreateShaderModule(device, shader); | |
| wgpu::ComputePipelineDescriptor csDesc; | |
| csDesc.compute.module = module; | |
| wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc); | |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), | |
| { | |
| {0, buffer}, | |
| }); | |
| wgpu::CommandBuffer commands; | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetBindGroup(0, bindGroup); | |
| pass.SetPipeline(pipeline); | |
| pass.DispatchWorkgroups(1); | |
| pass.End(); | |
| commands = encoder.Finish(); | |
| } | |
| // --- capture --- | |
| auto recorder = Recorder::CreateAndStart(device); | |
| queue.Submit(1, &commands); | |
| // --- replay --- | |
| auto replay = recorder->Replay(device); | |
| uint8_t expected[] = {0x11, 0x22, 0x33, 0x44}; | |
| ExpectBufferEQ(replay, label, expected); | |
| } | |
| // Capture and replay 2 auto-layout compute pipelines with the same layout | |
| // This is to verify a bug fix. In Dawn, there is BindGroupLayout and | |
| // BindGroupLayoutInternal. In this test, given 2 pipelines with the same | |
| // layout, there will be 2 BindGroupLayout objects pointing to one | |
| // BindGroupLayoutInternal. That means that when serializing, one of them | |
| // will get the wrong Pipeline if the pipeline is incorrectly associated | |
| // with the one BindGroupLayoutInternal instead of each of the 2 pipelines | |
| // being separately associated with one of the 2 BindGroupLayout objects. | |
| // This is a regression test for crbug.com/455605671 | |
| TEST_P(CaptureAndReplayTests, CaptureTwoMatchingAutoLayoutComputePipelines) { | |
| wgpu::Buffer buffer1 = | |
| CreateBuffer("buffer1", 4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst); | |
| wgpu::Buffer buffer2 = | |
| CreateBuffer("buffer2", 4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst); | |
| const char* shader = R"( | |
| @group(0) @binding(0) var<storage, read_write> result : u32; | |
| @compute @workgroup_size(1) fn cs1() { | |
| result = 0x44332211; | |
| } | |
| @compute @workgroup_size(1) fn cs2() { | |
| result = 0x88776655; | |
| } | |
| )"; | |
| auto module = utils::CreateShaderModule(device, shader); | |
| wgpu::ComputePipelineDescriptor csDesc; | |
| csDesc.label = "pipeline1"; | |
| csDesc.compute.module = module; | |
| csDesc.compute.entryPoint = "cs1"; | |
| wgpu::ComputePipeline pipeline1 = device.CreateComputePipeline(&csDesc); | |
| csDesc.label = "pipeline2"; | |
| csDesc.compute.entryPoint = "cs2"; | |
| wgpu::ComputePipeline pipeline2 = device.CreateComputePipeline(&csDesc); | |
| wgpu::BindGroup bindGroup1 = utils::MakeBindGroup(device, pipeline1.GetBindGroupLayout(0), | |
| { | |
| {0, buffer1}, | |
| }); | |
| wgpu::BindGroup bindGroup2 = utils::MakeBindGroup(device, pipeline2.GetBindGroupLayout(0), | |
| { | |
| {0, buffer2}, | |
| }); | |
| wgpu::CommandBuffer commands; | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetPipeline(pipeline1); | |
| pass.SetBindGroup(0, bindGroup1); | |
| pass.DispatchWorkgroups(1); | |
| pass.SetPipeline(pipeline2); | |
| pass.SetBindGroup(0, bindGroup2); | |
| pass.DispatchWorkgroups(1); | |
| pass.End(); | |
| commands = encoder.Finish(); | |
| } | |
| // --- capture --- | |
| auto recorder = Recorder::CreateAndStart(device); | |
| queue.Submit(1, &commands); | |
| // --- replay --- | |
| auto replay = recorder->Replay(device); | |
| uint8_t expected1[] = {0x11, 0x22, 0x33, 0x44}; | |
| ExpectBufferEQ(replay, "buffer1", expected1); | |
| uint8_t expected2[] = {0x55, 0x66, 0x77, 0x88}; | |
| ExpectBufferEQ(replay, "buffer2", expected2); | |
| } | |
| // Capture and replay 2 bindGroups that use implicit bindGroupLayouts from | |
| // different pipelines but for 1, never set the pipeline nor dispatch. This effectively | |
| // makes it a no-op. The issue is, we can't easily serialize a bindGroup that uses an | |
| // implicit bindGroupLayout unless the pipeline that created that bindGroupLayout is | |
| // used in the command buffer. So, we just don't serialize those calls to setBindGroup | |
| // since they are effectively no-ops. This test checks things don't crash as if the | |
| // call was actually serialized it would reference a bindGroupLayout that does not | |
| // exist. | |
| TEST_P(CaptureAndReplayTests, CaptureTwoAutoLayoutComputePipelinesOneIsBoundButUnused) { | |
| wgpu::Buffer buffer = | |
| CreateBuffer("buffer", 4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst); | |
| const char* shader = R"( | |
| @group(0) @binding(0) var<storage, read_write> result : u32; | |
| @compute @workgroup_size(1) fn cs1() { | |
| result = 0x44332211; | |
| } | |
| @compute @workgroup_size(1) fn cs2() { | |
| result = 0x88776655; | |
| } | |
| )"; | |
| auto module = utils::CreateShaderModule(device, shader); | |
| wgpu::ComputePipelineDescriptor csDesc; | |
| csDesc.label = "pipeline1"; | |
| csDesc.compute.module = module; | |
| csDesc.compute.entryPoint = "cs1"; | |
| wgpu::ComputePipeline pipeline1 = device.CreateComputePipeline(&csDesc); | |
| csDesc.label = "pipeline2"; | |
| csDesc.compute.entryPoint = "cs2"; | |
| wgpu::ComputePipeline pipeline2 = device.CreateComputePipeline(&csDesc); | |
| wgpu::BindGroup bindGroup1 = utils::MakeBindGroup(device, pipeline1.GetBindGroupLayout(0), | |
| { | |
| {0, buffer}, | |
| }); | |
| wgpu::BindGroup bindGroup2 = utils::MakeBindGroup(device, pipeline2.GetBindGroupLayout(0), | |
| { | |
| {0, buffer}, | |
| }); | |
| wgpu::CommandBuffer commands; | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetBindGroup(0, bindGroup1); | |
| pass.SetPipeline(pipeline2); | |
| pass.SetBindGroup(0, bindGroup2); | |
| pass.DispatchWorkgroups(1); | |
| pass.End(); | |
| commands = encoder.Finish(); | |
| } | |
| // --- capture --- | |
| auto recorder = Recorder::CreateAndStart(device); | |
| queue.Submit(1, &commands); | |
| // --- replay --- | |
| auto replay = recorder->Replay(device); | |
| uint8_t expected[] = {0x55, 0x66, 0x77, 0x88}; | |
| ExpectBufferEQ(replay, "buffer", expected); | |
| } | |
| // Capture and replay the simplest render pass. | |
| // It just starts and ends a render pass and uses the clearValue to set | |
| // a texture. | |
| TEST_P(CaptureAndReplayTests, CaptureRenderPassBasic) { | |
| wgpu::Texture texture = CreateTexture("myTexture", {1}, wgpu::TextureFormat::RGBA8Uint, | |
| wgpu::TextureUsage::RenderAttachment); | |
| wgpu::CommandBuffer commands; | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| utils::ComboRenderPassDescriptor passDescriptor({texture.CreateView()}); | |
| passDescriptor.cColorAttachments[0].clearValue = {0x11, 0x22, 0x33, 0x44}; | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor); | |
| pass.End(); | |
| commands = encoder.Finish(); | |
| } | |
| // --- capture --- | |
| auto recorder = Recorder::CreateAndStart(device); | |
| queue.Submit(1, &commands); | |
| // --- replay --- | |
| auto replay = recorder->Replay(device); | |
| utils::RGBA8 expected[] = {{0x11, 0x22, 0x33, 0x44}}; | |
| ExpectTextureEQ(replay, "myTexture", {1}, expected); | |
| } | |
| // Capture and replay the a render pass where a texture is rendered into another. | |
| TEST_P(CaptureAndReplayTests, CaptureRenderPassBasicWithBindGroup) { | |
| wgpu::Texture srcTexture = | |
| CreateTexture("srcTexture", {1}, wgpu::TextureFormat::RGBA8Uint, | |
| wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopyDst); | |
| const uint8_t myData[] = {0x11, 0x22, 0x33, 0x44}; | |
| WriteFullTexture(srcTexture, wgpu::TextureFormat::RGBA8Uint, {1}, myData); | |
| wgpu::Texture dstTexture = CreateTexture("dstTexture", {1}, wgpu::TextureFormat::RGBA8Uint, | |
| wgpu::TextureUsage::RenderAttachment); | |
| const char* shader = R"( | |
| @group(0) @binding(0) var tex: texture_2d<u32>; | |
| @vertex fn vs() -> @builtin(position) vec4f { | |
| return vec4f(0.0, 0.0, 0.0, 1.0); | |
| } | |
| @fragment fn fs() -> @location(0) vec4u { | |
| return textureLoad(tex, vec2u(0), 0); | |
| } | |
| )"; | |
| auto module = utils::CreateShaderModule(device, shader); | |
| utils::ComboRenderPipelineDescriptor desc; | |
| desc.vertex.module = module; | |
| desc.cFragment.module = module; | |
| desc.cFragment.targetCount = 1; | |
| desc.cTargets[0].format = wgpu::TextureFormat::RGBA8Uint; | |
| desc.primitive.topology = wgpu::PrimitiveTopology::PointList; | |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&desc); | |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), | |
| { | |
| {0, srcTexture.CreateView()}, | |
| }); | |
| wgpu::CommandBuffer commands; | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| utils::ComboRenderPassDescriptor passDescriptor({dstTexture.CreateView()}); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor); | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, bindGroup); | |
| pass.Draw(1); | |
| pass.End(); | |
| commands = encoder.Finish(); | |
| } | |
| // --- capture --- | |
| auto recorder = Recorder::CreateAndStart(device); | |
| queue.Submit(1, &commands); | |
| // --- replay --- | |
| auto replay = recorder->Replay(device); | |
| utils::RGBA8 expected[] = {{0x11, 0x22, 0x33, 0x44}}; | |
| ExpectTextureEQ(replay, "dstTexture", {1}, expected); | |
| } | |
| TEST_P(CaptureAndReplayTests, CaptureRenderPassBasicWithAttributes) { | |
| const float myVertices[] = { | |
| -1, -1, 3, -1, -1, 3, | |
| }; | |
| wgpu::Buffer vertexBuffer = CreateBuffer( | |
| "vertexBuffer", sizeof(myVertices), wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Vertex); | |
| queue.WriteBuffer(vertexBuffer, 0, &myVertices, sizeof(myVertices)); | |
| wgpu::Texture dstTexture = CreateTexture("dstTexture", {1}, wgpu::TextureFormat::RGBA8Uint, | |
| wgpu::TextureUsage::RenderAttachment); | |
| const char* shader = R"( | |
| @vertex fn vs(@location(0) pos: vec4f) -> @builtin(position) vec4f { | |
| return pos; | |
| } | |
| @fragment fn fs() -> @location(0) vec4u { | |
| return vec4u(0x11, 0x22, 0x33, 0x44); | |
| } | |
| )"; | |
| auto module = utils::CreateShaderModule(device, shader); | |
| utils::ComboRenderPipelineDescriptor desc; | |
| desc.vertex.module = module; | |
| desc.cFragment.module = module; | |
| desc.cFragment.targetCount = 1; | |
| desc.cTargets[0].format = wgpu::TextureFormat::RGBA8Uint; | |
| desc.primitive.topology = wgpu::PrimitiveTopology::TriangleList; | |
| desc.cBuffers[0].arrayStride = 2 * sizeof(float); | |
| desc.cBuffers[0].attributeCount = 1; | |
| desc.cBuffers[0].attributes = &desc.cAttributes[0]; | |
| desc.cAttributes[0].shaderLocation = 0; | |
| desc.cAttributes[0].format = wgpu::VertexFormat::Float32x2; | |
| desc.vertex.bufferCount = 1; | |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&desc); | |
| wgpu::CommandBuffer commands; | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| utils::ComboRenderPassDescriptor passDescriptor({dstTexture.CreateView()}); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor); | |
| pass.SetPipeline(pipeline); | |
| pass.SetVertexBuffer(0, vertexBuffer); | |
| pass.Draw(3); | |
| pass.End(); | |
| commands = encoder.Finish(); | |
| } | |
| // --- capture --- | |
| auto recorder = Recorder::CreateAndStart(device); | |
| queue.Submit(1, &commands); | |
| // --- replay --- | |
| auto replay = recorder->Replay(device); | |
| utils::RGBA8 expected[] = {{0x11, 0x22, 0x33, 0x44}}; | |
| ExpectTextureEQ(replay, "dstTexture", {1}, expected); | |
| } | |
| // Capture and replay a compute shader with an explicit bindGroupLayout | |
| TEST_P(CaptureAndReplayTests, CaptureComputeShaderBasicExplicitBindGroup) { | |
| wgpu::BindGroupLayoutEntry entries[1]; | |
| entries[0].binding = 0; | |
| entries[0].visibility = wgpu::ShaderStage::Compute; | |
| entries[0].buffer.type = wgpu::BufferBindingType::Storage; | |
| wgpu::BindGroupLayoutDescriptor bglDesc; | |
| bglDesc.entryCount = 1; | |
| bglDesc.entries = entries; | |
| wgpu::BindGroupLayout layout = device.CreateBindGroupLayout(&bglDesc); | |
| wgpu::PipelineLayoutDescriptor plDesc; | |
| plDesc.bindGroupLayoutCount = 1; | |
| plDesc.bindGroupLayouts = &layout; | |
| wgpu::PipelineLayout pipelineLayout = device.CreatePipelineLayout(&plDesc); | |
| const char* label = "MyBuffer"; | |
| wgpu::Buffer buffer = | |
| CreateBuffer(label, 4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst); | |
| const char* shader = R"( | |
| @group(0) @binding(0) var<storage, read_write> result : u32; | |
| @compute @workgroup_size(1) fn main() { | |
| result = 0x44332211; | |
| } | |
| )"; | |
| auto module = utils::CreateShaderModule(device, shader); | |
| wgpu::ComputePipelineDescriptor csDesc; | |
| csDesc.layout = pipelineLayout; | |
| csDesc.compute.module = module; | |
| wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc); | |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), | |
| { | |
| {0, buffer}, | |
| }); | |
| wgpu::CommandBuffer commands; | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetBindGroup(0, bindGroup); | |
| pass.SetPipeline(pipeline); | |
| pass.DispatchWorkgroups(1); | |
| pass.End(); | |
| commands = encoder.Finish(); | |
| } | |
| // --- capture --- | |
| auto recorder = Recorder::CreateAndStart(device); | |
| queue.Submit(1, &commands); | |
| // --- replay --- | |
| auto replay = recorder->Replay(device); | |
| uint8_t expected[] = {0x11, 0x22, 0x33, 0x44}; | |
| ExpectBufferEQ(replay, label, expected); | |
| } | |
| // Capture and replay a pass that uses a storage texture | |
| TEST_P(CaptureAndReplayTests, CaptureStorageTextureUsageWithExplicitBindGroupLayout) { | |
| wgpu::BindGroupLayoutEntry entries[1]; | |
| entries[0].binding = 0; | |
| entries[0].visibility = wgpu::ShaderStage::Compute; | |
| entries[0].storageTexture.access = wgpu::StorageTextureAccess::WriteOnly; | |
| entries[0].storageTexture.format = wgpu::TextureFormat::RGBA8Uint; | |
| wgpu::BindGroupLayoutDescriptor bglDesc; | |
| bglDesc.entryCount = 1; | |
| bglDesc.entries = entries; | |
| wgpu::BindGroupLayout layout = device.CreateBindGroupLayout(&bglDesc); | |
| wgpu::PipelineLayoutDescriptor plDesc; | |
| plDesc.bindGroupLayoutCount = 1; | |
| plDesc.bindGroupLayouts = &layout; | |
| wgpu::PipelineLayout pipelineLayout = device.CreatePipelineLayout(&plDesc); | |
| wgpu::Texture texture = | |
| CreateTexture("myTexture", {1}, wgpu::TextureFormat::RGBA8Uint, | |
| wgpu::TextureUsage::StorageBinding | wgpu::TextureUsage::CopySrc); | |
| const char* shader = R"( | |
| @group(0) @binding(0) var tex: texture_storage_2d<rgba8uint, write>; | |
| @compute @workgroup_size(1) fn main() { | |
| textureStore(tex, vec2u(0), vec4u(0x11, 0x22, 0x33, 0x44)); | |
| } | |
| )"; | |
| auto module = utils::CreateShaderModule(device, shader); | |
| wgpu::ComputePipelineDescriptor csDesc; | |
| csDesc.layout = pipelineLayout; | |
| csDesc.compute.module = module; | |
| wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc); | |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), | |
| { | |
| {0, texture.CreateView()}, | |
| }); | |
| wgpu::CommandBuffer commands; | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetBindGroup(0, bindGroup); | |
| pass.SetPipeline(pipeline); | |
| pass.DispatchWorkgroups(1); | |
| pass.End(); | |
| commands = encoder.Finish(); | |
| } | |
| // --- capture --- | |
| auto recorder = Recorder::CreateAndStart(device); | |
| queue.Submit(1, &commands); | |
| // --- replay --- | |
| auto replay = recorder->Replay(device); | |
| utils::RGBA8 expected[] = {{0x11, 0x22, 0x33, 0x44}}; | |
| ExpectTextureEQ(replay, "myTexture", {1}, expected); | |
| } | |
| // Capture and replay a pass that uses a texture binding with explicit bind group layout. | |
| TEST_P(CaptureAndReplayTests, CaptureTextureUsageWithExplicitBindGroupLayout) { | |
| wgpu::BindGroupLayoutEntry entries[2]; | |
| entries[0].binding = 2; | |
| entries[0].visibility = wgpu::ShaderStage::Compute; | |
| entries[0].texture.sampleType = wgpu::TextureSampleType::Uint; | |
| entries[0].texture.viewDimension = wgpu::TextureViewDimension::e2D; | |
| entries[1].binding = 4; | |
| entries[1].visibility = wgpu::ShaderStage::Compute; | |
| entries[1].buffer.type = wgpu::BufferBindingType::Storage; | |
| wgpu::BindGroupLayoutDescriptor bglDesc; | |
| bglDesc.entryCount = 2; | |
| bglDesc.entries = entries; | |
| wgpu::BindGroupLayout layout = device.CreateBindGroupLayout(&bglDesc); | |
| wgpu::PipelineLayoutDescriptor plDesc; | |
| plDesc.bindGroupLayoutCount = 1; | |
| plDesc.bindGroupLayouts = &layout; | |
| wgpu::PipelineLayout pipelineLayout = device.CreatePipelineLayout(&plDesc); | |
| wgpu::Texture texture = | |
| CreateTexture("myTexture", {1}, wgpu::TextureFormat::RGBA8Uint, | |
| wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopyDst); | |
| const uint8_t myData[] = {0x11, 0x22, 0x33, 0x44}; | |
| WriteFullTexture(texture, wgpu::TextureFormat::RGBA8Uint, {1}, myData); | |
| wgpu::Buffer buffer = CreateBuffer( | |
| "myBuffer", 4, | |
| wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc); | |
| const char* shader = R"( | |
| @group(0) @binding(2) var tex: texture_2d<u32>; | |
| @group(0) @binding(4) var<storage, read_write> result: u32; | |
| @compute @workgroup_size(1) fn main() { | |
| let c = textureLoad(tex, vec2u(0), 0); | |
| result = pack4xU8Clamp(c); | |
| } | |
| )"; | |
| auto module = utils::CreateShaderModule(device, shader); | |
| wgpu::ComputePipelineDescriptor csDesc; | |
| csDesc.layout = pipelineLayout; | |
| csDesc.compute.module = module; | |
| wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc); | |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), | |
| { | |
| {2, texture.CreateView()}, | |
| {4, buffer}, | |
| }); | |
| wgpu::CommandBuffer commands; | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetBindGroup(0, bindGroup); | |
| pass.SetPipeline(pipeline); | |
| pass.DispatchWorkgroups(1); | |
| pass.End(); | |
| commands = encoder.Finish(); | |
| } | |
| // --- capture --- | |
| auto recorder = Recorder::CreateAndStart(device); | |
| queue.Submit(1, &commands); | |
| // --- replay --- | |
| auto replay = recorder->Replay(device); | |
| uint8_t expected[] = {0x11, 0x22, 0x33, 0x44}; | |
| ExpectBufferEQ(replay, "myBuffer", expected); | |
| } | |
| // Capture and replay a pass that uses a sampler binding with explicit bind group layout. | |
| TEST_P(CaptureAndReplayTests, CaptureSamplerUsageWithExplicitBindGroupLayout) { | |
| wgpu::BindGroupLayoutEntry entries[2]; | |
| entries[0].binding = 2; | |
| entries[0].visibility = wgpu::ShaderStage::Fragment; | |
| entries[0].texture.sampleType = wgpu::TextureSampleType::Float; | |
| entries[0].texture.viewDimension = wgpu::TextureViewDimension::e2D; | |
| entries[1].binding = 4; | |
| entries[1].visibility = wgpu::ShaderStage::Fragment; | |
| entries[1].sampler.type = wgpu::SamplerBindingType::Filtering; | |
| wgpu::BindGroupLayoutDescriptor bglDesc; | |
| bglDesc.entryCount = 2; | |
| bglDesc.entries = entries; | |
| wgpu::BindGroupLayout layout = device.CreateBindGroupLayout(&bglDesc); | |
| wgpu::PipelineLayoutDescriptor plDesc; | |
| plDesc.bindGroupLayoutCount = 1; | |
| plDesc.bindGroupLayouts = &layout; | |
| wgpu::PipelineLayout pipelineLayout = device.CreatePipelineLayout(&plDesc); | |
| wgpu::Texture srcTexture = | |
| CreateTexture("srcTexture", {1}, wgpu::TextureFormat::RGBA8Unorm, | |
| wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopyDst); | |
| const uint8_t myData[] = {0x11, 0x22, 0x33, 0x44}; | |
| WriteFullTexture(srcTexture, wgpu::TextureFormat::RGBA8Unorm, {1}, myData); | |
| wgpu::Sampler sampler = device.CreateSampler(); | |
| const char* shader = R"( | |
| @group(0) @binding(2) var tex: texture_2d<f32>; | |
| @group(0) @binding(4) var smp: sampler; | |
| @vertex fn vs() -> @builtin(position) vec4f { | |
| return vec4f(0, 0, 0, 1); | |
| } | |
| @fragment fn fs() -> @location(0) vec4f { | |
| return textureSample(tex, smp, vec2f(0)); | |
| } | |
| )"; | |
| auto module = utils::CreateShaderModule(device, shader); | |
| utils::ComboRenderPipelineDescriptor desc; | |
| desc.vertex.module = module; | |
| desc.cFragment.module = module; | |
| desc.cFragment.targetCount = 1; | |
| desc.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm; | |
| desc.primitive.topology = wgpu::PrimitiveTopology::PointList; | |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&desc); | |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), | |
| { | |
| {2, srcTexture.CreateView()}, | |
| {4, sampler}, | |
| }); | |
| wgpu::Texture dstTexture = | |
| CreateTexture("dstTexture", {1}, wgpu::TextureFormat::RGBA8Unorm, | |
| wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc); | |
| wgpu::CommandBuffer commands; | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| utils::ComboRenderPassDescriptor passDescriptor({dstTexture.CreateView()}); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor); | |
| pass.SetBindGroup(0, bindGroup); | |
| pass.SetPipeline(pipeline); | |
| pass.Draw(1); | |
| pass.End(); | |
| commands = encoder.Finish(); | |
| } | |
| // --- capture --- | |
| auto recorder = Recorder::CreateAndStart(device); | |
| queue.Submit(1, &commands); | |
| // --- replay --- | |
| auto replay = recorder->Replay(device); | |
| utils::RGBA8 expected[] = {{0x11, 0x22, 0x33, 0x44}}; | |
| ExpectTextureEQ(replay, "dstTexture", {1}, expected); | |
| } | |
| // Capture and replay a pass uses a depth attachment. | |
| // This was failing because the front end does not save the user's | |
| // stencilLoadOp and stencilStoreOp as the spec requires "undefined" | |
| TEST_P(CaptureAndReplayTests, CaptureDepthRenderPass) { | |
| wgpu::Texture texture = CreateTexture("texture", {1}, wgpu::TextureFormat::RGBA8Unorm, | |
| wgpu::TextureUsage::RenderAttachment); | |
| wgpu::Texture depthTexture = | |
| CreateTexture("depthTexture", {1}, wgpu::TextureFormat::Depth16Unorm, | |
| wgpu::TextureUsage::RenderAttachment); | |
| wgpu::CommandBuffer commands; | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| utils::ComboRenderPassDescriptor passDescriptor({texture.CreateView()}, | |
| depthTexture.CreateView()); | |
| passDescriptor.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Undefined; | |
| passDescriptor.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Undefined; | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor); | |
| pass.End(); | |
| commands = encoder.Finish(); | |
| } | |
| // --- capture --- | |
| auto recorder = Recorder::CreateAndStart(device); | |
| queue.Submit(1, &commands); | |
| // --- replay --- | |
| recorder->Replay(device); | |
| // We just expect no errors. | |
| } | |
| constexpr static uint64_t kSentinelValue = ~uint64_t(0u); | |
| class OcclusionExpectation : public detail::Expectation { | |
| public: | |
| enum class Result { Zero, NonZero }; | |
| ~OcclusionExpectation() override = default; | |
| explicit OcclusionExpectation(const std::vector<Result> expected) { mExpected = expected; } | |
| testing::AssertionResult Check(const void* data, size_t size) override { | |
| DAWN_ASSERT(size % sizeof(uint64_t) == 0); | |
| DAWN_ASSERT(size / sizeof(uint64_t) == mExpected.size()); | |
| const uint64_t* actual = static_cast<const uint64_t*>(data); | |
| for (size_t i = 0; i < size / sizeof(uint64_t); i++) { | |
| if (DAWN_UNSAFE_TODO(actual[i]) == kSentinelValue) { | |
| return testing::AssertionFailure() | |
| << "Data[" << i << "] was not written (it kept the sentinel value of " | |
| << kSentinelValue << ").\n"; | |
| } | |
| Result expected = mExpected[i]; | |
| if (expected == Result::Zero && DAWN_UNSAFE_TODO(actual[i]) != 0) { | |
| return testing::AssertionFailure() | |
| << "Expected data[" << i | |
| << "] to be zero, actual: " << DAWN_UNSAFE_TODO(actual[i]) << ".\n"; | |
| } | |
| if (expected == Result::NonZero && DAWN_UNSAFE_TODO(actual[i]) == 0) { | |
| return testing::AssertionFailure() | |
| << "Expected data[" << i << "] to be non-zero.\n"; | |
| } | |
| } | |
| return testing::AssertionSuccess(); | |
| } | |
| private: | |
| std::vector<Result> mExpected; | |
| }; | |
| // Capture and replay a pass that uses a QuerySet. | |
| // We use a point-list vertex shader that we can set the z value by passing a different vertex_index | |
| // via the firstVertex argument to draw. | |
| TEST_P(CaptureAndReplayTests, CaptureQuerySetBasic) { | |
| const char* shader = R"( | |
| @vertex fn vs(@builtin(vertex_index) vNdx: u32) -> @builtin(position) vec4f { | |
| return vec4f(0, 0, f32(vNdx) / 10.0, 1); | |
| } | |
| @fragment fn fs() -> @location(0) vec4f { | |
| return vec4f(0); | |
| } | |
| )"; | |
| auto module = utils::CreateShaderModule(device, shader); | |
| utils::ComboRenderPipelineDescriptor desc; | |
| desc.vertex.module = module; | |
| desc.cFragment.module = module; | |
| desc.cFragment.targetCount = 1; | |
| desc.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm; | |
| desc.primitive.topology = wgpu::PrimitiveTopology::PointList; | |
| wgpu::DepthStencilState* depthStencil = | |
| desc.EnableDepthStencil(wgpu::TextureFormat::Depth16Unorm); | |
| depthStencil->depthWriteEnabled = wgpu::OptionalBool::True; | |
| depthStencil->depthCompare = wgpu::CompareFunction::Less; | |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&desc); | |
| wgpu::Texture dstTexture = | |
| CreateTexture("dstTexture", {1}, wgpu::TextureFormat::RGBA8Unorm, | |
| wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc); | |
| wgpu::Texture depthTexture = | |
| CreateTexture("depthTexture", {1}, wgpu::TextureFormat::Depth16Unorm, | |
| wgpu::TextureUsage::RenderAttachment); | |
| constexpr uint32_t kNumQueries = 4; | |
| wgpu::QuerySetDescriptor qsDesc; | |
| qsDesc.label = "myQuerySet"; | |
| qsDesc.count = kNumQueries; | |
| qsDesc.type = wgpu::QueryType::Occlusion; | |
| wgpu::QuerySet querySet = device.CreateQuerySet(&qsDesc); | |
| wgpu::CommandBuffer commands; | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| utils::ComboRenderPassDescriptor passDescriptor({dstTexture.CreateView()}, | |
| depthTexture.CreateView()); | |
| passDescriptor.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Undefined; | |
| passDescriptor.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Undefined; | |
| passDescriptor.occlusionQuerySet = querySet; | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor); | |
| pass.SetPipeline(pipeline); | |
| uint32_t nextIndex = 0; | |
| auto DrawPixelAtDepthWithOcclusionTest = [&](uint32_t depth) { | |
| pass.BeginOcclusionQuery(nextIndex++); | |
| pass.Draw(1, 1, depth, 0); | |
| pass.EndOcclusionQuery(); | |
| }; | |
| DrawPixelAtDepthWithOcclusionTest(5); // draws at 0.5 (not occluded) | |
| DrawPixelAtDepthWithOcclusionTest(7); // draws at 0.7 (occluded) | |
| DrawPixelAtDepthWithOcclusionTest(2); // draws at 0.2 (not-occluded) | |
| DrawPixelAtDepthWithOcclusionTest(5); // draws at 0.5 (occluded) | |
| pass.End(); | |
| commands = encoder.Finish(); | |
| } | |
| // --- capture --- | |
| auto recorder = Recorder::CreateAndStart(device); | |
| queue.Submit(1, &commands); | |
| // --- replay --- | |
| auto replay = recorder->Replay(device); | |
| { | |
| auto qs = replay->GetObjectByLabel<wgpu::QuerySet>("myQuerySet"); | |
| ASSERT_TRUE(qs); | |
| uint64_t size = sizeof(uint64_t) * kNumQueries; | |
| auto resolveBuffer = | |
| CreateBuffer("", size, | |
| wgpu::BufferUsage::QueryResolve | wgpu::BufferUsage::CopySrc | | |
| wgpu::BufferUsage::CopyDst); | |
| std::vector<uint64_t> sentinels(kNumQueries, kSentinelValue); | |
| queue.WriteBuffer(resolveBuffer, 0, sentinels.data(), size); | |
| { | |
| wgpu::CommandBuffer resolveCommands; | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| encoder.ResolveQuerySet(qs, 0, kNumQueries, resolveBuffer, 0); | |
| resolveCommands = encoder.Finish(); | |
| queue.Submit(1, &resolveCommands); | |
| } | |
| EXPECT_BUFFER( | |
| resolveBuffer, 0, size, | |
| new OcclusionExpectation( | |
| {OcclusionExpectation::Result::NonZero, OcclusionExpectation::Result::Zero, | |
| OcclusionExpectation::Result::NonZero, OcclusionExpectation::Result::Zero})); | |
| } | |
| } | |
| // Capture and replay a render bundle. | |
| TEST_P(CaptureAndReplayTests, CaptureRenderBundleBasic) { | |
| const char* shader = R"( | |
| @vertex fn vs() -> @builtin(position) vec4f { | |
| return vec4f(0, 0, 0, 1); | |
| } | |
| @fragment fn fs() -> @location(0) vec4u { | |
| return vec4u(0x11, 0x22, 0x33, 0x44); | |
| } | |
| )"; | |
| auto module = utils::CreateShaderModule(device, shader); | |
| utils::ComboRenderPipelineDescriptor passDesc; | |
| passDesc.vertex.module = module; | |
| passDesc.cFragment.module = module; | |
| passDesc.cFragment.targetCount = 1; | |
| passDesc.cTargets[0].format = wgpu::TextureFormat::RGBA8Uint; | |
| passDesc.primitive.topology = wgpu::PrimitiveTopology::PointList; | |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&passDesc); | |
| utils::ComboRenderBundleEncoderDescriptor desc = {}; | |
| desc.colorFormatCount = 1; | |
| desc.cColorFormats[0] = wgpu::TextureFormat::RGBA8Uint; | |
| wgpu::RenderBundleEncoder renderBundleEncoder = device.CreateRenderBundleEncoder(&desc); | |
| renderBundleEncoder.SetPipeline(pipeline); | |
| renderBundleEncoder.Draw(1); | |
| wgpu::RenderBundle renderBundle = renderBundleEncoder.Finish(); | |
| wgpu::Texture dstTexture = | |
| CreateTexture("dstTexture", {1}, wgpu::TextureFormat::RGBA8Uint, | |
| wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc); | |
| wgpu::CommandBuffer commands; | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| utils::ComboRenderPassDescriptor passDescriptor({dstTexture.CreateView()}); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor); | |
| pass.ExecuteBundles(1, &renderBundle); | |
| pass.End(); | |
| commands = encoder.Finish(); | |
| } | |
| // --- capture --- | |
| auto recorder = Recorder::CreateAndStart(device); | |
| queue.Submit(1, &commands); | |
| // --- replay --- | |
| auto replay = recorder->Replay(device); | |
| utils::RGBA8 expected[] = {{0x11, 0x22, 0x33, 0x44}}; | |
| ExpectTextureEQ(replay, "dstTexture", {1}, expected); | |
| } | |
| // Test debug commands don't fail. | |
| TEST_P(CaptureAndReplayTests, PushPopInsertDebug) { | |
| utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, 4, 4); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| encoder.PushDebugGroup("Event Start"); | |
| encoder.InsertDebugMarker("Marker"); | |
| encoder.PopDebugGroup(); | |
| { | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo); | |
| pass.PushDebugGroup("Event Start"); | |
| pass.InsertDebugMarker("Marker"); | |
| pass.PopDebugGroup(); | |
| pass.End(); | |
| } | |
| { | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.PushDebugGroup("Event Start"); | |
| pass.InsertDebugMarker("Marker"); | |
| pass.PopDebugGroup(); | |
| pass.End(); | |
| } | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| // --- capture --- | |
| auto recorder = Recorder::CreateAndStart(device); | |
| queue.Submit(1, &commands); | |
| // --- replay --- | |
| recorder->Replay(device); | |
| // just expect no errors. | |
| } | |
| // Test capturing setting a label | |
| TEST_P(CaptureAndReplayTests, CaptureSetLabel) { | |
| wgpu::Buffer buffer = CreateBuffer("buf", 4, wgpu::BufferUsage::CopyDst); | |
| wgpu::Texture texture = | |
| CreateTexture("tex", {1}, wgpu::TextureFormat::RGBA8Uint, | |
| wgpu::TextureUsage::StorageBinding | wgpu::TextureUsage::CopySrc); | |
| wgpu::TextureView view = texture.CreateView(); | |
| wgpu::Sampler sampler = device.CreateSampler(); | |
| wgpu::QuerySetDescriptor qsDesc; | |
| qsDesc.count = 1; | |
| qsDesc.type = wgpu::QueryType::Occlusion; | |
| wgpu::QuerySet querySet = device.CreateQuerySet(&qsDesc); | |
| const char* shader = R"( | |
| @group(0) @binding(0) var tex: texture_storage_2d<rgba8uint, write>; | |
| @compute @workgroup_size(1) fn main() { | |
| textureStore(tex, vec2u(0), vec4<u32>(0)); | |
| } | |
| @vertex fn vs() -> @builtin(position) vec4f { | |
| return vec4f(0); | |
| } | |
| @fragment fn fs() -> @location(0) vec4u { | |
| return vec4u(0); | |
| } | |
| )"; | |
| auto module = utils::CreateShaderModule(device, shader); | |
| wgpu::BindGroupLayoutEntry entries[1]; | |
| entries[0].binding = 0; | |
| entries[0].visibility = wgpu::ShaderStage::Compute; | |
| entries[0].buffer.type = wgpu::BufferBindingType::Storage; | |
| wgpu::BindGroupLayoutDescriptor bglDesc; | |
| bglDesc.entryCount = 1; | |
| bglDesc.entries = entries; | |
| wgpu::BindGroupLayout layout = device.CreateBindGroupLayout(&bglDesc); | |
| wgpu::PipelineLayoutDescriptor plDesc; | |
| plDesc.bindGroupLayoutCount = 1; | |
| plDesc.bindGroupLayouts = &layout; | |
| wgpu::PipelineLayout pipelineLayout = device.CreatePipelineLayout(&plDesc); | |
| wgpu::ComputePipelineDescriptor csDesc; | |
| csDesc.compute.module = module; | |
| wgpu::ComputePipeline cPipeline = device.CreateComputePipeline(&csDesc); | |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, cPipeline.GetBindGroupLayout(0), | |
| { | |
| {0, view}, | |
| }); | |
| utils::ComboRenderPipelineDescriptor desc; | |
| desc.vertex.module = module; | |
| desc.cFragment.module = module; | |
| desc.cFragment.targetCount = 1; | |
| desc.cTargets[0].format = wgpu::TextureFormat::RGBA8Uint; | |
| desc.primitive.topology = wgpu::PrimitiveTopology::PointList; | |
| wgpu::RenderPipeline rPipeline = device.CreateRenderPipeline(&desc); | |
| utils::ComboRenderBundleEncoderDescriptor rbDesc = {}; | |
| rbDesc.colorFormatCount = 1; | |
| rbDesc.cColorFormats[0] = wgpu::TextureFormat::RGBA8Uint; | |
| wgpu::RenderBundleEncoder renderBundleEncoder = device.CreateRenderBundleEncoder(&rbDesc); | |
| wgpu::RenderBundle renderBundle = renderBundleEncoder.Finish(); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::CommandBuffer commandBuffer = encoder.Finish(); | |
| auto recorder = Recorder::CreateAndStart(device); | |
| // Note: the first time capture see's a resource here is | |
| // on SetLabel. The label is set before the resource is captured | |
| // so we won't see the original label. So, set them twice so we | |
| // can verify the label changed. | |
| bindGroup.SetLabel("bgA"); | |
| bindGroup.SetLabel("bgB"); | |
| buffer.SetLabel("bufA"); | |
| buffer.SetLabel("bufB"); | |
| commandBuffer.SetLabel("cbA"); | |
| commandBuffer.SetLabel("cbB"); | |
| cPipeline.SetLabel("cpA"); | |
| cPipeline.SetLabel("cpB"); | |
| device.SetLabel("devA"); | |
| device.SetLabel("devB"); | |
| layout.SetLabel("loA"); | |
| layout.SetLabel("loB"); | |
| pipelineLayout.SetLabel("plA"); | |
| pipelineLayout.SetLabel("plB"); | |
| querySet.SetLabel("qsA"); | |
| querySet.SetLabel("qsB"); | |
| rPipeline.SetLabel("rpA"); | |
| rPipeline.SetLabel("rpB"); | |
| renderBundle.SetLabel("rbA"); | |
| renderBundle.SetLabel("rbB"); | |
| sampler.SetLabel("smpA"); | |
| sampler.SetLabel("smpB"); | |
| module.SetLabel("modA"); | |
| module.SetLabel("modB"); | |
| texture.SetLabel("texA"); | |
| texture.SetLabel("texB"); | |
| view.SetLabel("viewA"); | |
| view.SetLabel("viewB"); | |
| auto replay = recorder->Replay(device); | |
| EXPECT_TRUE(replay->GetObjectByLabel<wgpu::BindGroup>("bgA") == nullptr); | |
| EXPECT_TRUE(replay->GetObjectByLabel<wgpu::BindGroup>("bgB") != nullptr); | |
| EXPECT_TRUE(replay->GetObjectByLabel<wgpu::Buffer>("buf") == nullptr); | |
| EXPECT_TRUE(replay->GetObjectByLabel<wgpu::Buffer>("bufA") == nullptr); | |
| EXPECT_TRUE(replay->GetObjectByLabel<wgpu::Buffer>("bufB") != nullptr); | |
| EXPECT_TRUE(replay->GetObjectByLabel<wgpu::CommandBuffer>("cbA") == nullptr); | |
| EXPECT_TRUE(replay->GetObjectByLabel<wgpu::CommandBuffer>("cbB") != nullptr); | |
| EXPECT_TRUE(replay->GetObjectByLabel<wgpu::ComputePipeline>("cpA") == nullptr); | |
| EXPECT_TRUE(replay->GetObjectByLabel<wgpu::ComputePipeline>("cpB") != nullptr); | |
| EXPECT_TRUE(replay->GetObjectByLabel<wgpu::Device>("devA") == nullptr); | |
| EXPECT_TRUE(replay->GetObjectByLabel<wgpu::Device>("devB") != nullptr); | |
| EXPECT_TRUE(replay->GetObjectByLabel<wgpu::BindGroupLayout>("loA") == nullptr); | |
| EXPECT_TRUE(replay->GetObjectByLabel<wgpu::BindGroupLayout>("loB") != nullptr); | |
| EXPECT_TRUE(replay->GetObjectByLabel<wgpu::PipelineLayout>("plA") == nullptr); | |
| EXPECT_TRUE(replay->GetObjectByLabel<wgpu::PipelineLayout>("plB") != nullptr); | |
| EXPECT_TRUE(replay->GetObjectByLabel<wgpu::RenderBundle>("rbA") == nullptr); | |
| EXPECT_TRUE(replay->GetObjectByLabel<wgpu::RenderBundle>("rbB") != nullptr); | |
| EXPECT_TRUE(replay->GetObjectByLabel<wgpu::RenderPipeline>("rpA") == nullptr); | |
| EXPECT_TRUE(replay->GetObjectByLabel<wgpu::RenderPipeline>("rpB") != nullptr); | |
| EXPECT_TRUE(replay->GetObjectByLabel<wgpu::QuerySet>("qsA") == nullptr); | |
| EXPECT_TRUE(replay->GetObjectByLabel<wgpu::QuerySet>("qsB") != nullptr); | |
| EXPECT_TRUE(replay->GetObjectByLabel<wgpu::Sampler>("smpA") == nullptr); | |
| EXPECT_TRUE(replay->GetObjectByLabel<wgpu::Sampler>("smpB") != nullptr); | |
| EXPECT_TRUE(replay->GetObjectByLabel<wgpu::ShaderModule>("modA") == nullptr); | |
| EXPECT_TRUE(replay->GetObjectByLabel<wgpu::ShaderModule>("modB") != nullptr); | |
| EXPECT_TRUE(replay->GetObjectByLabel<wgpu::Texture>("texA") == nullptr); | |
| EXPECT_TRUE(replay->GetObjectByLabel<wgpu::Texture>("texB") != nullptr); | |
| EXPECT_TRUE(replay->GetObjectByLabel<wgpu::TextureView>("viewA") == nullptr); | |
| EXPECT_TRUE(replay->GetObjectByLabel<wgpu::TextureView>("viewB") != nullptr); | |
| } | |
| // Capture SetBlendConstant. | |
| TEST_P(CaptureAndReplayTests, CaptureSetBlendConstant) { | |
| wgpu::Texture dstTexture = CreateTexture("dstTexture", {1}, wgpu::TextureFormat::RGBA8Unorm, | |
| wgpu::TextureUsage::RenderAttachment); | |
| const char* shader = R"( | |
| @vertex fn vs() -> @builtin(position) vec4f { | |
| return vec4f(0, 0, 0, 1); | |
| } | |
| @fragment fn fs() -> @location(0) vec4f { | |
| return vec4f(1); | |
| } | |
| )"; | |
| auto module = utils::CreateShaderModule(device, shader); | |
| utils::ComboRenderPipelineDescriptor desc; | |
| desc.vertex.module = module; | |
| desc.cFragment.module = module; | |
| desc.cFragment.targetCount = 1; | |
| desc.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm; | |
| desc.cTargets[0].blend = &desc.cBlends[0]; | |
| desc.cBlends[0].color.operation = wgpu::BlendOperation::Add; | |
| desc.cBlends[0].color.srcFactor = wgpu::BlendFactor::Constant; | |
| desc.cBlends[0].color.dstFactor = wgpu::BlendFactor::Zero; | |
| desc.cBlends[0].alpha.operation = wgpu::BlendOperation::Add; | |
| desc.cBlends[0].alpha.srcFactor = wgpu::BlendFactor::Constant; | |
| desc.cBlends[0].alpha.dstFactor = wgpu::BlendFactor::Zero; | |
| desc.primitive.topology = wgpu::PrimitiveTopology::PointList; | |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&desc); | |
| wgpu::CommandBuffer commands; | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| utils::ComboRenderPassDescriptor passDescriptor({dstTexture.CreateView()}); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor); | |
| pass.SetPipeline(pipeline); | |
| // the +0.1 is to try get the same result on all GPUs. | |
| wgpu::Color color = {11.1 / 255.0, 22.1 / 255.0, 33.1 / 255.0, 44.1 / 255.0}; | |
| pass.SetBlendConstant(&color); | |
| pass.Draw(1); | |
| pass.End(); | |
| commands = encoder.Finish(); | |
| } | |
| // --- capture --- | |
| auto recorder = Recorder::CreateAndStart(device); | |
| queue.Submit(1, &commands); | |
| // --- replay --- | |
| auto replay = recorder->Replay(device); | |
| utils::RGBA8 expected[] = {{11, 22, 33, 44}}; | |
| ExpectTextureEQ(replay, "dstTexture", {1}, expected); | |
| } | |
| // Capture DispatchIndirect. | |
| TEST_P(CaptureAndReplayTests, CaptureDispatchIndirect) { | |
| const char* shader = R"( | |
| @group(0) @binding(0) var<storage, read_write> result : u32; | |
| @compute @workgroup_size(1) fn main() { | |
| result = 0x44332211; | |
| } | |
| )"; | |
| auto module = utils::CreateShaderModule(device, shader); | |
| const uint32_t myData[] = {0x1, 0x1, 0x1}; | |
| wgpu::Buffer indirectBuffer = CreateBuffer( | |
| "indirect", sizeof(myData), wgpu::BufferUsage::Indirect | wgpu::BufferUsage::CopyDst); | |
| queue.WriteBuffer(indirectBuffer, 0, &myData, sizeof(myData)); | |
| wgpu::ComputePipelineDescriptor csDesc; | |
| csDesc.compute.module = module; | |
| wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc); | |
| const char* label = "MyBuffer"; | |
| wgpu::Buffer buffer = CreateBuffer(label, 4, wgpu::BufferUsage::Storage); | |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), | |
| { | |
| {0, buffer}, | |
| }); | |
| wgpu::CommandBuffer commands; | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetBindGroup(0, bindGroup); | |
| pass.SetPipeline(pipeline); | |
| pass.DispatchWorkgroupsIndirect(indirectBuffer, 0); | |
| pass.End(); | |
| commands = encoder.Finish(); | |
| } | |
| // --- capture --- | |
| auto recorder = Recorder::CreateAndStart(device); | |
| queue.Submit(1, &commands); | |
| // --- replay --- | |
| auto replay = recorder->Replay(device); | |
| uint8_t expected[] = {0x11, 0x22, 0x33, 0x44}; | |
| ExpectBufferEQ(replay, label, expected); | |
| } | |
| // Capture ClearBuffer. | |
| TEST_P(CaptureAndReplayTests, CaptureClearBuffer) { | |
| const uint32_t myData[] = {0x11111111, 0x22222222, 0x33333333}; | |
| wgpu::Buffer buffer = CreateBuffer("buf", sizeof(myData), | |
| wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst); | |
| queue.WriteBuffer(buffer, 0, &myData, sizeof(myData)); | |
| wgpu::CommandBuffer commands; | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| encoder.ClearBuffer(buffer, 4, 4); | |
| commands = encoder.Finish(); | |
| } | |
| // --- capture --- | |
| auto recorder = Recorder::CreateAndStart(device); | |
| queue.Submit(1, &commands); | |
| // --- replay --- | |
| auto replay = recorder->Replay(device); | |
| const uint8_t expected[] = {0x11, 0x11, 0x11, 0x11, 0, 0, 0, 0, 0x33, 0x33, 0x33, 0x33}; | |
| ExpectBufferEQ(replay, "buf", expected); | |
| } | |
| TEST_P(CaptureAndReplayTests, SetImmediateComputePass) { | |
| const char* shader = R"( | |
| var<immediate> value: u32; | |
| @group(0) @binding(0) var<storage, read_write> result : u32; | |
| @compute @workgroup_size(1) fn main() { | |
| result = value; | |
| } | |
| )"; | |
| auto module = utils::CreateShaderModule(device, shader); | |
| wgpu::BindGroupLayoutEntry entries[1]; | |
| entries[0].binding = 0; | |
| entries[0].visibility = wgpu::ShaderStage::Compute; | |
| entries[0].buffer.type = wgpu::BufferBindingType::Storage; | |
| wgpu::BindGroupLayoutDescriptor bglDesc; | |
| bglDesc.entryCount = 1; | |
| bglDesc.entries = entries; | |
| wgpu::BindGroupLayout layout = device.CreateBindGroupLayout(&bglDesc); | |
| wgpu::PipelineLayoutDescriptor plDesc; | |
| plDesc.label = "immediatePipelineLayout"; | |
| plDesc.bindGroupLayoutCount = 1; | |
| plDesc.bindGroupLayouts = &layout; | |
| plDesc.immediateSize = 4; | |
| wgpu::PipelineLayout pipelineLayout = device.CreatePipelineLayout(&plDesc); | |
| wgpu::ComputePipelineDescriptor csDesc; | |
| csDesc.label = "immediatePipeline"; | |
| csDesc.layout = pipelineLayout; | |
| csDesc.compute.module = module; | |
| wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc); | |
| const char* label = "MyBuffer"; | |
| wgpu::Buffer buffer = CreateBuffer(label, 4, wgpu::BufferUsage::Storage); | |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), | |
| { | |
| {0, buffer}, | |
| }); | |
| wgpu::CommandBuffer commands; | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetBindGroup(0, bindGroup); | |
| pass.SetPipeline(pipeline); | |
| const uint8_t data[] = {0x11, 0x22, 0x33, 0x44}; | |
| pass.SetImmediates(0, data, sizeof(data)); | |
| pass.DispatchWorkgroups(1); | |
| pass.End(); | |
| commands = encoder.Finish(); | |
| } | |
| // --- capture --- | |
| auto recorder = Recorder::CreateAndStart(device); | |
| queue.Submit(1, &commands); | |
| // --- replay --- | |
| auto replay = recorder->Replay(device); | |
| uint8_t expected[] = {0x11, 0x22, 0x33, 0x44}; | |
| ExpectBufferEQ(replay, label, expected); | |
| } | |
| TEST_P(CaptureAndReplayTests, SetImmediateRenderPass) { | |
| const char* shader = R"( | |
| var<immediate> value: u32; | |
| @vertex fn vs() -> @builtin(position) vec4f { | |
| return vec4f(0, 0, 0, 1); | |
| } | |
| @fragment fn fs() -> @location(0) vec4u { | |
| return vec4u(value); | |
| } | |
| )"; | |
| auto module = utils::CreateShaderModule(device, shader); | |
| wgpu::BindGroupLayoutEntry entries[1]; | |
| entries[0].binding = 0; | |
| entries[0].visibility = wgpu::ShaderStage::Compute; | |
| entries[0].buffer.type = wgpu::BufferBindingType::Storage; | |
| wgpu::BindGroupLayoutDescriptor bglDesc; | |
| bglDesc.entryCount = 1; | |
| bglDesc.entries = entries; | |
| wgpu::BindGroupLayout layout = device.CreateBindGroupLayout(&bglDesc); | |
| wgpu::PipelineLayoutDescriptor plDesc; | |
| plDesc.label = "immediatePipelineLayout"; | |
| plDesc.bindGroupLayoutCount = 0; | |
| plDesc.immediateSize = 4; | |
| wgpu::PipelineLayout pipelineLayout = device.CreatePipelineLayout(&plDesc); | |
| utils::ComboRenderPipelineDescriptor desc; | |
| desc.vertex.module = module; | |
| desc.cFragment.module = module; | |
| desc.cFragment.targetCount = 1; | |
| desc.cTargets[0].format = wgpu::TextureFormat::R32Uint; | |
| desc.primitive.topology = wgpu::PrimitiveTopology::PointList; | |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&desc); | |
| wgpu::Texture dstTexture = CreateTexture("dstTexture", {1}, wgpu::TextureFormat::R32Uint, | |
| wgpu::TextureUsage::RenderAttachment); | |
| wgpu::CommandBuffer commands; | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| utils::ComboRenderPassDescriptor passDescriptor({dstTexture.CreateView()}); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor); | |
| pass.SetPipeline(pipeline); | |
| const uint8_t data[] = {0x11, 0x22, 0x33, 0x44}; | |
| pass.SetImmediates(0, data, sizeof(data)); | |
| pass.Draw(1); | |
| pass.End(); | |
| commands = encoder.Finish(); | |
| } | |
| // --- capture --- | |
| auto recorder = Recorder::CreateAndStart(device); | |
| queue.Submit(1, &commands); | |
| // --- replay --- | |
| auto replay = recorder->Replay(device); | |
| const uint32_t expected[] = {0x44332211}; | |
| ExpectTextureEQ(replay, "dstTexture", {1}, expected); | |
| } | |
| // Make sure it does not capture the unmap of a buffer if that unmap is triggered by the buffer | |
| // destroy. | |
| TEST_P(CaptureAndReplayTests, MappedBufferDestroyed) { | |
| auto recorder = Recorder::CreateAndStart(device); | |
| { | |
| // Create a buffer mapped at creation and then release it. | |
| wgpu::Buffer buffer = CreateBuffer("MyBuffer", 4, wgpu::BufferUsage::CopyDst, true); | |
| } | |
| recorder->Replay(device); | |
| // just expect no errors | |
| } | |
| // Depth24Plus is not directly copyable as either src or dst so, we make a depth24plus. | |
| // put values in it via render pass. Then capture it in an empty render pass. | |
| // On replay we read the values via a compute shader. | |
| TEST_P(CaptureAndReplayTests, CaptureDepth24Plus) { | |
| // TODO(477645283): This fails only on WARP and after it fails, all following tests | |
| // fail to create a device. | |
| DAWN_SUPPRESS_TEST_IF(IsWARP()); | |
| constexpr uint32_t kNumLayers = 6; | |
| auto [cPipeline, commands] = [&]() { | |
| wgpu::Texture texture = CreateTexture( | |
| "myTexture", {1, 1, kNumLayers}, wgpu::TextureFormat::Depth24Plus, | |
| wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment); | |
| const char* shader = R"( | |
| struct VOut { | |
| @builtin(position) pos: vec4f, | |
| @location(0) @interpolate(flat, either) instance_index: u32, | |
| }; | |
| @vertex fn vs(@builtin(vertex_index) vNdx: u32, | |
| @builtin(instance_index) iNdx: u32) | |
| -> VOut { | |
| let pos = array( | |
| vec2f(-1, -1), | |
| vec2f(-1, 3), | |
| vec2f( 3, -1), | |
| ); | |
| return VOut(vec4f(pos[vNdx], 0, 1), iNdx); | |
| } | |
| @fragment fn fs(v: VOut) -> @builtin(frag_depth) f32 { | |
| return (f32(v.instance_index) + 0.5) / 6.0; | |
| } | |
| @group(0) @binding(0) var tex: texture_2d_array<f32>; | |
| @group(0) @binding(1) var<storage, read_write> result: array<f32>; | |
| @compute @workgroup_size(1) fn main(@builtin(global_invocation_id) gid: vec3u) { | |
| result[gid.x] = textureLoad(tex, vec2u(0), gid.x, 0).x; | |
| } | |
| )"; | |
| auto module = utils::CreateShaderModule(device, shader); | |
| // Put values in depth texture. This step is not captured. | |
| { | |
| utils::ComboRenderPipelineDescriptor desc; | |
| desc.vertex.module = module; | |
| desc.cFragment.module = module; | |
| desc.cFragment.targetCount = 0; | |
| desc.primitive.topology = wgpu::PrimitiveTopology::TriangleList; | |
| wgpu::DepthStencilState* depthStencil = | |
| desc.EnableDepthStencil(wgpu::TextureFormat::Depth24Plus); | |
| depthStencil->depthWriteEnabled = wgpu::OptionalBool::True; | |
| depthStencil->depthCompare = wgpu::CompareFunction::Always; | |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&desc); | |
| wgpu::CommandBuffer setupCommands; | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| for (uint32_t layer = 0; layer < kNumLayers; ++layer) { | |
| wgpu::TextureViewDescriptor viewDesc; | |
| viewDesc.baseArrayLayer = layer; | |
| viewDesc.arrayLayerCount = 1; | |
| utils::ComboRenderPassDescriptor passDescriptor({}, texture.CreateView(&viewDesc)); | |
| passDescriptor.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Load; | |
| passDescriptor.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Undefined; | |
| passDescriptor.cDepthStencilAttachmentInfo.stencilStoreOp = | |
| wgpu::StoreOp::Undefined; | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor); | |
| pass.SetPipeline(pipeline); | |
| pass.Draw(3, 1, 0, layer); | |
| pass.End(); | |
| } | |
| setupCommands = encoder.Finish(); | |
| queue.Submit(1, &setupCommands); | |
| } | |
| wgpu::ComputePipelineDescriptor csDesc; | |
| csDesc.compute.module = module; | |
| wgpu::ComputePipeline computePipeline = device.CreateComputePipeline(&csDesc); | |
| // Copy texture to temp buffer via compute shader during capture. | |
| // We don't care about the temp buffer. We just care that texture is | |
| // referenced so it will appear during replay. | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| { | |
| wgpu::Buffer tempBuffer = | |
| CreateBuffer("temp", sizeof(float) * kNumLayers, | |
| wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc); | |
| wgpu::BindGroup bindGroup = | |
| utils::MakeBindGroup(device, computePipeline.GetBindGroupLayout(0), | |
| { | |
| {0, texture.CreateView()}, | |
| {1, tempBuffer}, | |
| }); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetPipeline(computePipeline); | |
| pass.SetBindGroup(0, bindGroup); | |
| pass.DispatchWorkgroups(kNumLayers); | |
| pass.End(); | |
| } | |
| return std::make_pair(computePipeline, encoder.Finish()); | |
| }(); | |
| // --- capture --- | |
| auto recorder = Recorder::CreateAndStart(device); | |
| queue.Submit(1, &commands); | |
| // --- replay --- | |
| auto replay = recorder->Replay(device); | |
| // Read the replay version of myTexture in result via compute shader. | |
| wgpu::Buffer result = CreateBuffer("result", sizeof(float) * kNumLayers, | |
| wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc); | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::Texture texture = replay->GetObjectByLabel<wgpu::Texture>("myTexture"); | |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, cPipeline.GetBindGroupLayout(0), | |
| { | |
| {0, texture.CreateView()}, | |
| {1, result}, | |
| }); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetPipeline(cPipeline); | |
| pass.SetBindGroup(0, bindGroup); | |
| pass.DispatchWorkgroups(kNumLayers); | |
| pass.End(); | |
| wgpu::CommandBuffer replayCommands = encoder.Finish(); | |
| queue.Submit(1, &replayCommands); | |
| } | |
| std::array<float, kNumLayers> expected; | |
| for (uint32_t i = 0; i < kNumLayers; ++i) { | |
| DAWN_UNSAFE_TODO(expected[i]) = (i + 0.5f) / 6.f; | |
| } | |
| EXPECT_BUFFER_FLOAT_RANGE_TOLERANCE_EQ(expected.data(), result, 0, 6, 0.05); | |
| } | |
| // Test that you can not copy Depth24plus/Depth24PlusStencil8 depth aspect | |
| // The spec requires this to be validated out but the WGPU backend enables copying | |
| // on the inner device by the use_blit_for_depth24plus_texture_to_buffer_copy | |
| // toggle. Make sure that even when that toggle is enabled copying these formats | |
| // is validated out. | |
| TEST_P(CaptureAndReplayTests, Depth24PlusNotCopyable) { | |
| // No validation errors if validation is off. | |
| DAWN_TEST_UNSUPPORTED_IF(HasToggleEnabled("skip_validation")); | |
| wgpu::Buffer buffer = CreateBuffer("buf", 4, wgpu::BufferUsage::CopyDst); | |
| for (wgpu::TextureFormat format : | |
| {wgpu::TextureFormat::Depth24Plus, wgpu::TextureFormat::Depth24PlusStencil8}) { | |
| wgpu::Texture texture = CreateTexture("tex", {1}, format, wgpu::TextureUsage::CopySrc); | |
| wgpu::CommandBuffer commands; | |
| { | |
| // Copy srcTexture to dstBuffer | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::TexelCopyTextureInfo texelCopyTextureInfo = utils::CreateTexelCopyTextureInfo( | |
| texture, 0, {0, 0, 0}, wgpu::TextureAspect::DepthOnly); | |
| wgpu::TexelCopyBufferInfo texelCopyBufferInfo = | |
| utils::CreateTexelCopyBufferInfo(buffer, 0, 256, 1); | |
| wgpu::Extent3D extent = {1, 1, 1}; | |
| encoder.CopyTextureToBuffer(&texelCopyTextureInfo, &texelCopyBufferInfo, &extent); | |
| ASSERT_DEVICE_ERROR(encoder.Finish()); | |
| } | |
| } | |
| } | |
| // Test that capturing and replaying a BindGroup with an ExternalTexture works. | |
| TEST_P(CaptureAndReplayTests, BindGroupWithExternalTexture) { | |
| wgpu::TextureDescriptor textureDesc; | |
| textureDesc.size = {1, 1, 1}; | |
| textureDesc.format = wgpu::TextureFormat::RGBA8Unorm; | |
| textureDesc.usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment; | |
| wgpu::Texture texture = device.CreateTexture(&textureDesc); | |
| wgpu::ExternalTexture externalTexture = utils::MakePassthroughExternalTexture(device, texture); | |
| const char* shader = R"( | |
| @vertex fn vs() -> @builtin(position) vec4f { | |
| return vec4f(0, 0, 0, 1); | |
| } | |
| @group(0) @binding(0) var s : sampler; | |
| @group(0) @binding(1) var t : texture_external; | |
| @fragment fn main(@builtin(position) FragCoord : vec4f) | |
| -> @location(0) vec4f { | |
| return textureSampleBaseClampToEdge(t, s, FragCoord.xy / vec2f(4.0, 4.0)); | |
| } | |
| )"; | |
| auto module = utils::CreateShaderModule(device, shader); | |
| // Create an explicit bind group layout. | |
| auto bgl = utils::MakeBindGroupLayout( | |
| device, {{0, wgpu::ShaderStage::Fragment, wgpu::SamplerBindingType::Filtering}, | |
| {1, wgpu::ShaderStage::Fragment, &utils::kExternalTextureBindingLayout}}); | |
| utils::ComboRenderPipelineDescriptor descriptor; | |
| descriptor.layout = utils::MakeBasicPipelineLayout(device, &bgl); | |
| descriptor.vertex.module = module; | |
| descriptor.cFragment.module = module; | |
| descriptor.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm; | |
| auto pipeline = device.CreateRenderPipeline(&descriptor); | |
| auto sampler = device.CreateSampler(); | |
| auto bg = utils::MakeBindGroup(device, bgl, {{0, sampler}, {1, externalTexture}}); | |
| bg.SetLabel("MyBindGroup"); | |
| auto renderTexture = CreateTexture("renderTexture", {1}, wgpu::TextureFormat::RGBA8Unorm, | |
| wgpu::TextureUsage::RenderAttachment); | |
| utils::ComboRenderPassDescriptor renderPass({renderTexture.CreateView()}); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); | |
| { | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, bg); | |
| pass.Draw(3); | |
| pass.End(); | |
| } | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| // --- capture --- | |
| auto recorder = Recorder::CreateAndStart(device); | |
| queue.Submit(1, &commands); | |
| // --- replay --- | |
| auto replay = recorder->Replay(device); | |
| // Expect no errors. | |
| // Verify that the bind group exists in the replayed device. | |
| auto replayedBg = replay->GetObjectByLabel<wgpu::BindGroup>("MyBindGroup"); | |
| EXPECT_NE(replayedBg, nullptr); | |
| } | |
| DAWN_INSTANTIATE_TEST(CaptureAndReplayTests, WebGPUBackend()); | |
| class CaptureAndReplayDrawTests : public CaptureAndReplayTests { | |
| public: | |
| // Sets up point-list render pipeline to a 1x1 rgba8uint texture | |
| // and expects texture to be 'expected' | |
| template <typename Func, typename T> | |
| void TestDrawCommand(Func fn, const T& expected) { | |
| wgpu::Texture dstTexture = CreateTexture("dstTexture", {1}, wgpu::TextureFormat::RGBA8Uint, | |
| wgpu::TextureUsage::RenderAttachment); | |
| wgpu::Texture depthTexture = | |
| CreateTexture("depthTexture", {1}, wgpu::TextureFormat::Depth24PlusStencil8, | |
| wgpu::TextureUsage::RenderAttachment); | |
| const char* shader = R"( | |
| struct VOut { | |
| @builtin(position) pos: vec4f, | |
| @location(0) @interpolate(flat, either) params: vec4u, | |
| }; | |
| @vertex fn vs( | |
| @builtin(vertex_index) vNdx: u32, | |
| @builtin(instance_index) iNdx: u32) -> VOut | |
| { | |
| return VOut( | |
| vec4f(0, 0, 0, 1), | |
| vec4u(vNdx, iNdx, 0x33, 0x44)); | |
| } | |
| @fragment fn fs(v: VOut) -> @location(0) vec4u { | |
| return v.params; | |
| } | |
| )"; | |
| auto module = utils::CreateShaderModule(device, shader); | |
| utils::ComboRenderPipelineDescriptor desc; | |
| desc.vertex.module = module; | |
| desc.cFragment.module = module; | |
| desc.cFragment.targetCount = 1; | |
| desc.depthStencil = &desc.cDepthStencil; | |
| desc.cTargets[0].format = wgpu::TextureFormat::RGBA8Uint; | |
| desc.cDepthStencil.format = wgpu::TextureFormat::Depth24PlusStencil8; | |
| desc.cDepthStencil.depthCompare = wgpu::CompareFunction::Always; | |
| desc.cDepthStencil.stencilFront.compare = wgpu::CompareFunction::Equal; | |
| desc.primitive.topology = wgpu::PrimitiveTopology::PointList; | |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&desc); | |
| wgpu::CommandBuffer commands; | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| utils::ComboRenderPassDescriptor passDescriptor({dstTexture.CreateView()}, | |
| depthTexture.CreateView()); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor); | |
| pass.SetPipeline(pipeline); | |
| fn(pass); | |
| pass.End(); | |
| commands = encoder.Finish(); | |
| } | |
| // --- capture --- | |
| auto recorder = Recorder::CreateAndStart(device); | |
| queue.Submit(1, &commands); | |
| // --- replay --- | |
| auto replay = recorder->Replay(device); | |
| ExpectTextureEQ(replay, "dstTexture", {1}, expected); | |
| } | |
| }; | |
| // Capture DrawIndexed | |
| TEST_P(CaptureAndReplayDrawTests, CaptureDrawIndexed) { | |
| uint32_t indices[] = {0x10, 0x20, 0x30}; | |
| wgpu::Buffer indexBuffer = CreateBuffer("index", sizeof(indices), | |
| wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Index); | |
| queue.WriteBuffer(indexBuffer, 0, indices, sizeof(indices)); | |
| utils::RGBA8 expected[] = {{0x32, 0x3, 0x33, 0x44}}; | |
| TestDrawCommand( | |
| [&](wgpu::RenderPassEncoder pass) { | |
| pass.SetIndexBuffer(indexBuffer, wgpu::IndexFormat::Uint32); | |
| pass.DrawIndexed(1, // indexCount | |
| 1, // instanceCount | |
| 2, // firstIndex, | |
| 2, // baseVertex, | |
| 3); // firstInstance | |
| }, | |
| expected); | |
| } | |
| // Capture DrawIndirect | |
| TEST_P(CaptureAndReplayDrawTests, CaptureDrawIndirect) { | |
| uint32_t indirect[] = { | |
| 0x11, // vertexCount | |
| 0x22, // instanceCount | |
| 0, // firstVertex | |
| 0, // firstInstance (must be 0 without "indirect-first-instance") | |
| }; | |
| wgpu::Buffer indirectBuffer = CreateBuffer( | |
| "indirect", sizeof(indirect), wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Indirect); | |
| queue.WriteBuffer(indirectBuffer, 0, indirect, sizeof(indirect)); | |
| utils::RGBA8 expected[] = {{0x10, 0x21, 0x33, 0x44}}; | |
| TestDrawCommand([&](wgpu::RenderPassEncoder pass) { pass.DrawIndirect(indirectBuffer, 0); }, | |
| expected); | |
| } | |
| // Capture DrawIndexedIndirect | |
| TEST_P(CaptureAndReplayDrawTests, CaptureDrawIndexedIndirect) { | |
| uint32_t indices[] = {10, 20}; | |
| wgpu::Buffer indexBuffer = CreateBuffer("index", sizeof(indices), | |
| wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Index); | |
| uint32_t indirectIndexed[] = { | |
| 1, // indexCount | |
| 10, // instanceCount | |
| 1, // firstIndex | |
| 3, // baseVertex | |
| 0, // firstInstance (must be 0 without "indirect-first-instance") | |
| }; | |
| wgpu::Buffer indirectIndexedBuffer = | |
| CreateBuffer("indirectIndexed", sizeof(indirectIndexed), | |
| wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Indirect); | |
| queue.WriteBuffer(indirectIndexedBuffer, 0, indirectIndexed, sizeof(indirectIndexed)); | |
| utils::RGBA8 expected[] = {{0x3, 9, 0x33, 0x44}}; | |
| TestDrawCommand( | |
| [&](wgpu::RenderPassEncoder pass) { | |
| pass.SetIndexBuffer(indexBuffer, wgpu::IndexFormat::Uint32); | |
| pass.DrawIndexedIndirect(indirectIndexedBuffer, 0); | |
| }, | |
| expected); | |
| } | |
| // Capture SetViewport. Draws twice. The second draw should be ignored because | |
| // its out of the viewport. | |
| TEST_P(CaptureAndReplayDrawTests, CaptureSetViewport) { | |
| utils::RGBA8 expected[] = {{0x11, 0x22, 0x33, 0x44}}; | |
| TestDrawCommand( | |
| [&](wgpu::RenderPassEncoder pass) { | |
| pass.Draw(1, 1, 0x11, 0x22); | |
| pass.SetViewport(1, 1, 1, 1, 0, 1); | |
| pass.Draw(1, 1, 0x1, 0x2); | |
| }, | |
| expected); | |
| } | |
| // Capture SetScissorRect. Draws twice. The second draw should be ignored because | |
| // its out of the scissor rect. | |
| TEST_P(CaptureAndReplayDrawTests, CaptureSetScissorRect) { | |
| // TODO(464436694): Zero size scissor fails on Intel Mac for this case. | |
| DAWN_SUPPRESS_TEST_IF(IsWebGPUOn(wgpu::BackendType::Metal) && IsIntel()); | |
| utils::RGBA8 expected[] = {{0x11, 0x22, 0x33, 0x44}}; | |
| TestDrawCommand( | |
| [&](wgpu::RenderPassEncoder pass) { | |
| pass.Draw(1, 1, 0x11, 0x22); | |
| pass.SetScissorRect(0, 0, 0, 0); | |
| pass.Draw(1, 1, 0x1, 0x2); | |
| }, | |
| expected); | |
| } | |
| // Capture SetStencilReference. Draws twice. The second draw should be ignored because | |
| // it doesn't match the stencil reference. | |
| TEST_P(CaptureAndReplayDrawTests, CaptureSetStencilReference) { | |
| utils::RGBA8 expected[] = {{0x11, 0x22, 0x33, 0x44}}; | |
| TestDrawCommand( | |
| [&](wgpu::RenderPassEncoder pass) { | |
| pass.Draw(1, 1, 0x11, 0x22); | |
| pass.SetStencilReference(1); | |
| pass.Draw(1, 1, 0x1, 0x2); | |
| }, | |
| expected); | |
| } | |
| DAWN_INSTANTIATE_TEST(CaptureAndReplayDrawTests, WebGPUBackend()); | |
| class CaptureAndReplayTimestampTests : public CaptureAndReplayTests { | |
| protected: | |
| void SetUp() override { | |
| CaptureAndReplayTests::SetUp(); | |
| DAWN_TEST_UNSUPPORTED_IF( | |
| !SupportsFeatures({wgpu::FeatureName::ChromiumExperimentalTimestampQueryInsidePasses})); | |
| } | |
| std::vector<wgpu::FeatureName> GetRequiredFeatures() override { | |
| std::vector<wgpu::FeatureName> requiredFeatures = {}; | |
| if (SupportsFeatures({wgpu::FeatureName::ChromiumExperimentalTimestampQueryInsidePasses})) { | |
| requiredFeatures.push_back( | |
| wgpu::FeatureName::ChromiumExperimentalTimestampQueryInsidePasses); | |
| requiredFeatures.push_back(wgpu::FeatureName::TimestampQuery); | |
| } | |
| return requiredFeatures; | |
| } | |
| }; | |
| // Test WriteTimestamp in compute pass, render pass, and | |
| // command buffer. We don't expect any results. We only | |
| // expect it doesn't get any errors. | |
| TEST_P(CaptureAndReplayTimestampTests, WriteTimestamp) { | |
| wgpu::QuerySetDescriptor qsDesc; | |
| qsDesc.label = "myQuerySet"; | |
| qsDesc.count = 3; | |
| qsDesc.type = wgpu::QueryType::Timestamp; | |
| wgpu::QuerySet querySet = device.CreateQuerySet(&qsDesc); | |
| wgpu::CommandBuffer commands; | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| encoder.WriteTimestamp(querySet, 0); | |
| { | |
| utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, 1, 1); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo); | |
| pass.WriteTimestamp(querySet, 1); | |
| pass.End(); | |
| } | |
| { | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.WriteTimestamp(querySet, 2); | |
| pass.End(); | |
| } | |
| commands = encoder.Finish(); | |
| } | |
| // --- capture --- | |
| auto recorder = Recorder::CreateAndStart(device); | |
| queue.Submit(1, &commands); | |
| // --- replay --- | |
| recorder->Replay(device); | |
| // just expect no errors. | |
| } | |
| DAWN_INSTANTIATE_TEST(CaptureAndReplayTimestampTests, WebGPUBackend()); | |
| #ifdef DAWN_SUPPORTS_GLFW_FOR_WINDOWING | |
| struct GLFWindowDestroyer { | |
| void operator()(GLFWwindow* ptr) { glfwDestroyWindow(ptr); } | |
| }; | |
| class CaptureAndReplaySurfaceTests : public CaptureAndReplayTests { | |
| protected: | |
| void SetUp() override { | |
| CaptureAndReplayTests::SetUp(); | |
| // TODO(crbug.com/dawn/2531): Failing on newer Linux/Intel driver version. | |
| // However, IsIntel() and IsMesa() don't work with the null backend. | |
| DAWN_SUPPRESS_TEST_IF(IsLinux() && IsNull()); | |
| DAWN_SUPPRESS_TEST_IF(IsLinux() && IsVulkan() && IsIntel() && IsMesa("23.2")); | |
| // Causes flaky X11 resource exhaustion when run with SwiftShader. | |
| DAWN_SUPPRESS_TEST_IF(IsLinux() && IsVulkan() && IsSwiftshader()); | |
| DAWN_TEST_UNSUPPORTED_IF(!glfwInit()); | |
| glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); | |
| mWindow.reset(glfwCreateWindow(1, 1, "CaptureAndReplaySurfaceTests", nullptr, nullptr)); | |
| } | |
| void TearDown() override { | |
| CaptureAndReplayTests::TearDown(); | |
| mWindow.reset(); | |
| mReplayWindow.reset(); | |
| glfwTerminate(); | |
| } | |
| // Creates a window for a replay surface. DXGI only allows one flip-model swapchain per HWND at | |
| // a time, so the replay surface cannot reuse the same window as the capture surface while the | |
| // latter is still configured. The window is owned by the fixture so it outlives the surface. | |
| GLFWwindow* CreateReplayWindow() { | |
| glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); | |
| mReplayWindow.reset( | |
| glfwCreateWindow(1, 1, "CaptureAndReplaySurfaceTests replay", nullptr, nullptr)); | |
| return mReplayWindow.get(); | |
| } | |
| std::unique_ptr<GLFWwindow, GLFWindowDestroyer> mWindow; | |
| std::unique_ptr<GLFWwindow, GLFWindowDestroyer> mReplayWindow; | |
| }; | |
| TEST_P(CaptureAndReplaySurfaceTests, TestSurface) { | |
| DAWN_SUPPRESS_TEST_IF(IsWARP()); | |
| wgpu::Surface surface = wgpu::glfw::CreateSurfaceForWindow(instance, mWindow.get()); | |
| surface.SetLabel("mySurface"); | |
| wgpu::SurfaceConfiguration config = {}; | |
| config.device = device; | |
| config.format = wgpu::TextureFormat::BGRA8Unorm; | |
| config.usage = wgpu::TextureUsage::RenderAttachment; | |
| config.width = 1; | |
| config.height = 1; | |
| config.presentMode = wgpu::PresentMode::Fifo; | |
| // --- capture --- | |
| auto recorder = Recorder::CreateAndStart(device); | |
| surface.Configure(&config); | |
| wgpu::SurfaceTexture surfaceTexture; | |
| surface.GetCurrentTexture(&surfaceTexture); | |
| wgpu::Texture texture = surfaceTexture.texture; | |
| texture.SetLabel("backbuffer"); | |
| surface.Present(); | |
| // --- replay --- | |
| recorder->EndCapture(); | |
| // Check surface reflection info | |
| auto surfaceInfos = recorder->GetCapture()->GetSurfaceInfos(); | |
| EXPECT_EQ(surfaceInfos.size(), 1u); | |
| EXPECT_EQ(surfaceInfos[0].width, 1u); | |
| EXPECT_EQ(surfaceInfos[0].height, 1u); | |
| wgpu::Surface replaySurface = | |
| wgpu::glfw::CreateSurfaceForWindow(instance, CreateReplayWindow()); | |
| recorder->SetSurfaces({replaySurface}); | |
| auto replay = recorder->Replay(device); | |
| EXPECT_TRUE(replay->GetObjectByLabel<wgpu::Texture>("backbuffer") != nullptr); | |
| // Explicitly reset the recorder object to release all replayed resources (like Surface) | |
| // before the test ends and the device count check is performed. | |
| recorder.reset(); | |
| } | |
| TEST_P(CaptureAndReplaySurfaceTests, MultiFrame) { | |
| DAWN_SUPPRESS_TEST_IF(IsWARP()); | |
| wgpu::Surface surface = wgpu::glfw::CreateSurfaceForWindow(instance, mWindow.get()); | |
| surface.SetLabel("mySurface"); | |
| wgpu::SurfaceConfiguration config = {}; | |
| config.device = device; | |
| config.format = wgpu::TextureFormat::BGRA8Unorm; | |
| config.usage = wgpu::TextureUsage::RenderAttachment; | |
| config.width = 1; | |
| config.height = 1; | |
| config.presentMode = wgpu::PresentMode::Fifo; | |
| // --- capture --- | |
| auto recorder = Recorder::CreateAndStart(device); | |
| surface.Configure(&config); | |
| // Frame 1 | |
| { | |
| wgpu::SurfaceTexture surfaceTexture; | |
| surface.GetCurrentTexture(&surfaceTexture); | |
| surfaceTexture.texture.SetLabel("backbuffer1"); | |
| surface.Present(); | |
| } | |
| // Frame 2 | |
| { | |
| wgpu::SurfaceTexture surfaceTexture; | |
| surface.GetCurrentTexture(&surfaceTexture); | |
| surfaceTexture.texture.SetLabel("backbuffer2"); | |
| surface.Present(); | |
| } | |
| // --- replay --- | |
| recorder->EndCapture(); | |
| wgpu::Surface replaySurface = | |
| wgpu::glfw::CreateSurfaceForWindow(instance, CreateReplayWindow()); | |
| recorder->SetSurfaces({replaySurface}); | |
| auto replay = recorder->CreateReplay(device); | |
| // Replay Frame 1 | |
| EXPECT_EQ(replay->PlayFrame(), dawn::replay::ReplayStatus::Continuing); | |
| EXPECT_TRUE(replay->GetObjectByLabel<wgpu::Texture>("backbuffer1") != nullptr); | |
| EXPECT_TRUE(replay->GetObjectByLabel<wgpu::Texture>("backbuffer2") == nullptr); | |
| // Replay Frame 2 | |
| EXPECT_EQ(replay->PlayFrame(), dawn::replay::ReplayStatus::Continuing); | |
| EXPECT_TRUE(replay->GetObjectByLabel<wgpu::Texture>("backbuffer2") != nullptr); | |
| // No more frames | |
| EXPECT_EQ(replay->PlayFrame(), dawn::replay::ReplayStatus::Finished); | |
| // Explicitly reset the recorder object to release all replayed resources (like Surface) | |
| // before the test ends and the device count check is performed. | |
| recorder.reset(); | |
| } | |
| DAWN_INSTANTIATE_TEST(CaptureAndReplaySurfaceTests, WebGPUBackend()); | |
| #endif | |
| } // anonymous namespace | |
| } // namespace dawn |