// Copyright 2020 The Dawn Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "tests/unittests/validation/ValidationTest.h" #include "utils/ComboRenderPipelineDescriptor.h" #include "utils/WGPUHelpers.h" namespace { class ResourceUsageTrackingTest : public ValidationTest { protected: wgpu::Buffer CreateBuffer(uint64_t size, wgpu::BufferUsage usage) { wgpu::BufferDescriptor descriptor; descriptor.size = size; descriptor.usage = usage; return device.CreateBuffer(&descriptor); } wgpu::Texture CreateTexture(wgpu::TextureUsage usage) { wgpu::TextureDescriptor descriptor; descriptor.dimension = wgpu::TextureDimension::e2D; descriptor.size = {1, 1, 1}; descriptor.sampleCount = 1; descriptor.mipLevelCount = 1; descriptor.usage = usage; descriptor.format = kFormat; return device.CreateTexture(&descriptor); } // Note that it is valid to bind any bind groups for indices that the pipeline doesn't use. // We create a no-op render or compute pipeline without any bindings, and set bind groups // in the caller, so it is always correct for binding validation between bind groups and // pipeline. But those bind groups in caller can be used for validation for other purposes. wgpu::RenderPipeline CreateNoOpRenderPipeline() { wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"( [[stage(vertex)]] fn main() -> [[builtin(position)]] vec4 { return vec4(); })"); wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( [[stage(fragment)]] fn main() { })"); utils::ComboRenderPipelineDescriptor pipelineDescriptor; pipelineDescriptor.vertex.module = vsModule; pipelineDescriptor.cFragment.module = fsModule; pipelineDescriptor.cTargets[0].writeMask = wgpu::ColorWriteMask::None; pipelineDescriptor.layout = utils::MakeBasicPipelineLayout(device, nullptr); return device.CreateRenderPipeline(&pipelineDescriptor); } wgpu::ComputePipeline CreateNoOpComputePipeline(std::vector bgls) { wgpu::ShaderModule csModule = utils::CreateShaderModule(device, R"( [[stage(compute), workgroup_size(1)]] fn main() { })"); wgpu::ComputePipelineDescriptor pipelineDescriptor; pipelineDescriptor.layout = utils::MakePipelineLayout(device, std::move(bgls)); pipelineDescriptor.compute.module = csModule; pipelineDescriptor.compute.entryPoint = "main"; return device.CreateComputePipeline(&pipelineDescriptor); } static constexpr wgpu::TextureFormat kFormat = wgpu::TextureFormat::RGBA8Unorm; }; // Test that using a single buffer in multiple read usages in the same pass is allowed. TEST_F(ResourceUsageTrackingTest, BufferWithMultipleReadUsage) { // Test render pass { // Create a buffer, and use the buffer as both vertex and index buffer. wgpu::Buffer buffer = CreateBuffer(4, wgpu::BufferUsage::Vertex | wgpu::BufferUsage::Index); wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); DummyRenderPass dummyRenderPass(device); wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); pass.SetIndexBuffer(buffer, wgpu::IndexFormat::Uint32); pass.SetVertexBuffer(0, buffer); pass.EndPass(); encoder.Finish(); } // Test compute pass { // Create buffer and bind group wgpu::Buffer buffer = CreateBuffer(4, wgpu::BufferUsage::Uniform | wgpu::BufferUsage::Storage); wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Uniform}, {1, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::ReadOnlyStorage}}); wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, buffer}, {1, buffer}}); // Use the buffer as both uniform and readonly storage buffer in compute pass. wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); pass.SetBindGroup(0, bg); pass.EndPass(); encoder.Finish(); } } // Test that it is invalid to use the same buffer as both readable and writable in the same // render pass. It is invalid in the same dispatch in compute pass. TEST_F(ResourceUsageTrackingTest, BufferWithReadAndWriteUsage) { // test render pass { // Create buffer and bind group wgpu::Buffer buffer = CreateBuffer(4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::Index); wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Storage}}); wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, buffer}}); // It is invalid to use the buffer as both index and storage in render pass wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); DummyRenderPass dummyRenderPass(device); wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); pass.SetIndexBuffer(buffer, wgpu::IndexFormat::Uint32); pass.SetBindGroup(0, bg); pass.EndPass(); ASSERT_DEVICE_ERROR(encoder.Finish()); } // test compute pass { // Create buffer and bind group wgpu::Buffer buffer = CreateBuffer(512, wgpu::BufferUsage::Storage); wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage}, {1, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::ReadOnlyStorage}}); wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, buffer, 0, 4}, {1, buffer, 256, 4}}); // Create a no-op compute pipeline wgpu::ComputePipeline cp = CreateNoOpComputePipeline({bgl}); // It is valid to use the buffer as both storage and readonly storage in a single // compute pass if dispatch command is not called. { wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); pass.SetBindGroup(0, bg); pass.EndPass(); encoder.Finish(); } // It is invalid to use the buffer as both storage and readonly storage in a single // dispatch. { wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); pass.SetPipeline(cp); pass.SetBindGroup(0, bg); pass.Dispatch(1); pass.EndPass(); ASSERT_DEVICE_ERROR(encoder.Finish()); } } } // Test the use of a buffer as a storage buffer multiple times in the same synchronization // scope. TEST_F(ResourceUsageTrackingTest, BufferUsedAsStorageMultipleTimes) { // Create buffer and bind group wgpu::Buffer buffer = CreateBuffer(512, wgpu::BufferUsage::Storage); wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Fragment | wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage}, {1, wgpu::ShaderStage::Fragment | wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage}}); wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, buffer, 0, 4}, {1, buffer, 256, 4}}); // test render pass { // It is valid to use multiple storage usages on the same buffer in render pass wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); DummyRenderPass dummyRenderPass(device); wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); pass.SetBindGroup(0, bg); pass.EndPass(); encoder.Finish(); } // test compute pass { // It is valid to use multiple storage usages on the same buffer in a dispatch wgpu::ComputePipeline cp = CreateNoOpComputePipeline({bgl}); wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); pass.SetPipeline(cp); pass.SetBindGroup(0, bg); pass.Dispatch(1); pass.EndPass(); encoder.Finish(); } } // Test that using the same buffer as both readable and writable in different passes is allowed TEST_F(ResourceUsageTrackingTest, BufferWithReadAndWriteUsageInDifferentPasses) { // Test render pass { // Create buffers that will be used as index and storage buffers wgpu::Buffer buffer0 = CreateBuffer(4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::Index); wgpu::Buffer buffer1 = CreateBuffer(4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::Index); // Create bind groups to use the buffer as storage wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Storage}}); wgpu::BindGroup bg0 = utils::MakeBindGroup(device, bgl, {{0, buffer0}}); wgpu::BindGroup bg1 = utils::MakeBindGroup(device, bgl, {{0, buffer1}}); // Use these two buffers as both index and storage in different render passes wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); DummyRenderPass dummyRenderPass(device); wgpu::RenderPassEncoder pass0 = encoder.BeginRenderPass(&dummyRenderPass); pass0.SetIndexBuffer(buffer0, wgpu::IndexFormat::Uint32); pass0.SetBindGroup(0, bg1); pass0.EndPass(); wgpu::RenderPassEncoder pass1 = encoder.BeginRenderPass(&dummyRenderPass); pass1.SetIndexBuffer(buffer1, wgpu::IndexFormat::Uint32); pass1.SetBindGroup(0, bg0); pass1.EndPass(); encoder.Finish(); } // Test compute pass { // Create buffer and bind groups that will be used as storage and uniform bindings wgpu::Buffer buffer = CreateBuffer(4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::Uniform); wgpu::BindGroupLayout bgl0 = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage}}); wgpu::BindGroupLayout bgl1 = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Uniform}}); wgpu::BindGroup bg0 = utils::MakeBindGroup(device, bgl0, {{0, buffer}}); wgpu::BindGroup bg1 = utils::MakeBindGroup(device, bgl1, {{0, buffer}}); // Use the buffer as both storage and uniform in different compute passes wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::ComputePassEncoder pass0 = encoder.BeginComputePass(); pass0.SetBindGroup(0, bg0); pass0.EndPass(); wgpu::ComputePassEncoder pass1 = encoder.BeginComputePass(); pass1.SetBindGroup(1, bg1); pass1.EndPass(); encoder.Finish(); } // Test render pass and compute pass mixed together with resource dependency. { // Create buffer and bind groups that will be used as storage and uniform bindings wgpu::Buffer buffer = CreateBuffer(4, wgpu::BufferUsage::Storage); wgpu::BindGroupLayout bgl0 = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage}}); wgpu::BindGroupLayout bgl1 = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::ReadOnlyStorage}}); wgpu::BindGroup bg0 = utils::MakeBindGroup(device, bgl0, {{0, buffer}}); wgpu::BindGroup bg1 = utils::MakeBindGroup(device, bgl1, {{0, buffer}}); // Use the buffer as storage and uniform in render pass and compute pass respectively wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::ComputePassEncoder pass0 = encoder.BeginComputePass(); pass0.SetBindGroup(0, bg0); pass0.EndPass(); DummyRenderPass dummyRenderPass(device); wgpu::RenderPassEncoder pass1 = encoder.BeginRenderPass(&dummyRenderPass); pass1.SetBindGroup(1, bg1); pass1.EndPass(); encoder.Finish(); } } // Test that it is invalid to use the same buffer as both readable and writable in different // draws in a single render pass. But it is valid in different dispatches in a single compute // pass. TEST_F(ResourceUsageTrackingTest, BufferWithReadAndWriteUsageInDifferentDrawsOrDispatches) { // Test render pass { // Create a buffer and a bind group wgpu::Buffer buffer = CreateBuffer(4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::Index); wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Storage}}); wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, buffer}}); // Create a no-op render pipeline. wgpu::RenderPipeline rp = CreateNoOpRenderPipeline(); // It is not allowed to use the same buffer as both readable and writable in different // draws within the same render pass. wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); DummyRenderPass dummyRenderPass(device); wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); pass.SetPipeline(rp); pass.SetIndexBuffer(buffer, wgpu::IndexFormat::Uint32); pass.Draw(3); pass.SetBindGroup(0, bg); pass.Draw(3); pass.EndPass(); ASSERT_DEVICE_ERROR(encoder.Finish()); } // test compute pass { // Create a buffer and bind groups wgpu::Buffer buffer = CreateBuffer(4, wgpu::BufferUsage::Storage); wgpu::BindGroupLayout bgl0 = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::ReadOnlyStorage}}); wgpu::BindGroupLayout bgl1 = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage}}); wgpu::BindGroup bg0 = utils::MakeBindGroup(device, bgl0, {{0, buffer}}); wgpu::BindGroup bg1 = utils::MakeBindGroup(device, bgl1, {{0, buffer}}); // Create a no-op compute pipeline. wgpu::ComputePipeline cp0 = CreateNoOpComputePipeline({bgl0}); wgpu::ComputePipeline cp1 = CreateNoOpComputePipeline({bgl1}); // It is valid to use the same buffer as both readable and writable in different // dispatches within the same compute pass. wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); pass.SetPipeline(cp0); pass.SetBindGroup(0, bg0); pass.Dispatch(1); pass.SetPipeline(cp1); pass.SetBindGroup(0, bg1); pass.Dispatch(1); pass.EndPass(); encoder.Finish(); } } // Test that it is invalid to use the same buffer as both readable and writable in a single // draw or dispatch. TEST_F(ResourceUsageTrackingTest, BufferWithReadAndWriteUsageInSingleDrawOrDispatch) { // Test render pass { // Create a buffer and a bind group wgpu::Buffer buffer = CreateBuffer(4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::Index); wgpu::BindGroupLayout writeBGL = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Storage}}); wgpu::BindGroup writeBG = utils::MakeBindGroup(device, writeBGL, {{0, buffer}}); // Create a no-op render pipeline. wgpu::RenderPipeline rp = CreateNoOpRenderPipeline(); // It is invalid to use the same buffer as both readable and writable usages in a single // draw wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); DummyRenderPass dummyRenderPass(device); wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); pass.SetPipeline(rp); pass.SetIndexBuffer(buffer, wgpu::IndexFormat::Uint32); pass.SetBindGroup(0, writeBG); pass.Draw(3); pass.EndPass(); ASSERT_DEVICE_ERROR(encoder.Finish()); } // test compute pass { // Create a buffer and bind groups wgpu::Buffer buffer = CreateBuffer(4, wgpu::BufferUsage::Storage); wgpu::BindGroupLayout readBGL = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::ReadOnlyStorage}}); wgpu::BindGroupLayout writeBGL = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage}}); wgpu::BindGroup readBG = utils::MakeBindGroup(device, readBGL, {{0, buffer}}); wgpu::BindGroup writeBG = utils::MakeBindGroup(device, writeBGL, {{0, buffer}}); // Create a no-op compute pipeline. wgpu::ComputePipeline cp = CreateNoOpComputePipeline({readBGL, writeBGL}); // It is invalid to use the same buffer as both readable and writable usages in a single // dispatch wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); pass.SetPipeline(cp); pass.SetBindGroup(0, readBG); pass.SetBindGroup(1, writeBG); pass.Dispatch(1); pass.EndPass(); ASSERT_DEVICE_ERROR(encoder.Finish()); } } // Test that using the same buffer as copy src/dst and writable/readable usage is allowed. TEST_F(ResourceUsageTrackingTest, BufferCopyAndBufferUsageInPass) { // Create buffers that will be used as both a copy src/dst buffer and a storage buffer wgpu::Buffer bufferSrc = CreateBuffer(4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc); wgpu::Buffer bufferDst = CreateBuffer(4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst); // Create the bind group to use the buffer as storage wgpu::BindGroupLayout bgl0 = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Storage}}); wgpu::BindGroup bg0 = utils::MakeBindGroup(device, bgl0, {{0, bufferSrc}}); wgpu::BindGroupLayout bgl1 = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::ReadOnlyStorage}}); wgpu::BindGroup bg1 = utils::MakeBindGroup(device, bgl1, {{0, bufferDst}}); // Use the buffer as both copy src and storage in render pass { wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); encoder.CopyBufferToBuffer(bufferSrc, 0, bufferDst, 0, 4); DummyRenderPass dummyRenderPass(device); wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); pass.SetBindGroup(0, bg0); pass.EndPass(); encoder.Finish(); } // Use the buffer as both copy dst and readonly storage in compute pass { wgpu::ComputePipeline cp = CreateNoOpComputePipeline({bgl1}); wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); encoder.CopyBufferToBuffer(bufferSrc, 0, bufferDst, 0, 4); wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); pass.SetBindGroup(0, bg1); pass.SetPipeline(cp); pass.Dispatch(1); pass.EndPass(); encoder.Finish(); } } // Test that all index buffers and vertex buffers take effect even though some buffers are // not used because they are overwritten by another consecutive call. TEST_F(ResourceUsageTrackingTest, BufferWithMultipleSetIndexOrVertexBuffer) { // Create buffers that will be used as both vertex and index buffer. wgpu::Buffer buffer0 = CreateBuffer( 4, wgpu::BufferUsage::Vertex | wgpu::BufferUsage::Index | wgpu::BufferUsage::Storage); wgpu::Buffer buffer1 = CreateBuffer(4, wgpu::BufferUsage::Vertex | wgpu::BufferUsage::Index); wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Storage}}); wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, buffer0}}); DummyRenderPass dummyRenderPass(device); // Set index buffer twice. The second one overwrites the first one. No buffer is used as // both read and write in the same pass. But the overwritten index buffer (buffer0) still // take effect during resource tracking. { wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); pass.SetIndexBuffer(buffer0, wgpu::IndexFormat::Uint32); pass.SetIndexBuffer(buffer1, wgpu::IndexFormat::Uint32); pass.SetBindGroup(0, bg); pass.EndPass(); ASSERT_DEVICE_ERROR(encoder.Finish()); } // Set index buffer twice. The second one overwrites the first one. buffer0 is used as both // read and write in the same pass { wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); pass.SetIndexBuffer(buffer1, wgpu::IndexFormat::Uint32); pass.SetIndexBuffer(buffer0, wgpu::IndexFormat::Uint32); pass.SetBindGroup(0, bg); pass.EndPass(); ASSERT_DEVICE_ERROR(encoder.Finish()); } // Set vertex buffer on the same index twice. The second one overwrites the first one. No // buffer is used as both read and write in the same pass. But the overwritten vertex buffer // (buffer0) still take effect during resource tracking. { wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); pass.SetVertexBuffer(0, buffer0); pass.SetVertexBuffer(0, buffer1); pass.SetBindGroup(0, bg); pass.EndPass(); ASSERT_DEVICE_ERROR(encoder.Finish()); } // Set vertex buffer on the same index twice. The second one overwrites the first one. // buffer0 is used as both read and write in the same pass { wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); pass.SetVertexBuffer(0, buffer1); pass.SetVertexBuffer(0, buffer0); pass.SetBindGroup(0, bg); pass.EndPass(); ASSERT_DEVICE_ERROR(encoder.Finish()); } } // Test that all consecutive SetBindGroup()s take effect even though some bind groups are not // used because they are overwritten by a consecutive call. TEST_F(ResourceUsageTrackingTest, BufferWithMultipleSetBindGroupsOnSameIndex) { // test render pass { // Create buffers that will be used as index and storage buffers wgpu::Buffer buffer0 = CreateBuffer(4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::Index); wgpu::Buffer buffer1 = CreateBuffer(4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::Index); // Create the bind group to use the buffer as storage wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Storage}}); wgpu::BindGroup bg0 = utils::MakeBindGroup(device, bgl, {{0, buffer0}}); wgpu::BindGroup bg1 = utils::MakeBindGroup(device, bgl, {{0, buffer1}}); DummyRenderPass dummyRenderPass(device); // Set bind group on the same index twice. The second one overwrites the first one. // No buffer is used as both read and write in the same pass. But the overwritten // bind group still take effect during resource tracking. { wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); pass.SetIndexBuffer(buffer0, wgpu::IndexFormat::Uint32); pass.SetBindGroup(0, bg0); pass.SetBindGroup(0, bg1); pass.EndPass(); ASSERT_DEVICE_ERROR(encoder.Finish()); } // Set bind group on the same index twice. The second one overwrites the first one. // buffer0 is used as both read and write in the same pass { wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); pass.SetIndexBuffer(buffer0, wgpu::IndexFormat::Uint32); pass.SetBindGroup(0, bg1); pass.SetBindGroup(0, bg0); pass.EndPass(); ASSERT_DEVICE_ERROR(encoder.Finish()); } } // test compute pass { // Create buffers that will be used as readonly and writable storage buffers wgpu::Buffer buffer0 = CreateBuffer(512, wgpu::BufferUsage::Storage); wgpu::Buffer buffer1 = CreateBuffer(4, wgpu::BufferUsage::Storage); // Create the bind group to use the buffer as storage wgpu::BindGroupLayout writeBGL = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage}}); wgpu::BindGroupLayout readBGL = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::ReadOnlyStorage}}); wgpu::BindGroup writeBG0 = utils::MakeBindGroup(device, writeBGL, {{0, buffer0, 0, 4}}); wgpu::BindGroup readBG0 = utils::MakeBindGroup(device, readBGL, {{0, buffer0, 256, 4}}); wgpu::BindGroup readBG1 = utils::MakeBindGroup(device, readBGL, {{0, buffer1, 0, 4}}); // Create a no-op compute pipeline. wgpu::ComputePipeline cp = CreateNoOpComputePipeline({writeBGL, readBGL}); // Set bind group against the same index twice. The second one overwrites the first one. // Then no buffer is used as both read and write in the same dispatch. But the // overwritten bind group still take effect. { wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); pass.SetBindGroup(0, writeBG0); pass.SetBindGroup(1, readBG0); pass.SetBindGroup(1, readBG1); pass.SetPipeline(cp); pass.Dispatch(1); pass.EndPass(); encoder.Finish(); } // Set bind group against the same index twice. The second one overwrites the first one. // Then buffer0 is used as both read and write in the same dispatch { wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); pass.SetBindGroup(0, writeBG0); pass.SetBindGroup(1, readBG1); pass.SetBindGroup(1, readBG0); pass.SetPipeline(cp); pass.Dispatch(1); pass.EndPass(); ASSERT_DEVICE_ERROR(encoder.Finish()); } } } // Test that it is invalid to have resource usage conflicts even when all bindings are not // visible to the programmable pass where it is used. TEST_F(ResourceUsageTrackingTest, BufferUsageConflictBetweenInvisibleStagesInBindGroup) { wgpu::Buffer buffer = CreateBuffer(4, wgpu::BufferUsage::Storage); // Test render pass for bind group. The conflict of readonly storage and storage usage // doesn't reside in render related stages at all { // Create a bind group whose bindings are not visible in render pass wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage}, {1, wgpu::ShaderStage::None, wgpu::BufferBindingType::ReadOnlyStorage}}); wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, buffer}, {1, buffer}}); // These two bindings are invisible in render pass. But we still track these bindings. wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); DummyRenderPass dummyRenderPass(device); wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); pass.SetBindGroup(0, bg); pass.EndPass(); ASSERT_DEVICE_ERROR(encoder.Finish()); } // Test compute pass for bind group. The conflict of readonly storage and storage usage // doesn't reside in compute related stage at all { // Create a bind group whose bindings are not visible in compute pass wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::ReadOnlyStorage}, {1, wgpu::ShaderStage::None, wgpu::BufferBindingType::Storage}}); wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, buffer}, {1, buffer}}); // Create a no-op compute pipeline. wgpu::ComputePipeline cp = CreateNoOpComputePipeline({bgl}); // These two bindings are invisible in the dispatch. But we still track these bindings. wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); pass.SetPipeline(cp); pass.SetBindGroup(0, bg); pass.Dispatch(1); pass.EndPass(); ASSERT_DEVICE_ERROR(encoder.Finish()); } } // Test that it is invalid to have resource usage conflicts even when one of the bindings is not // visible to the programmable pass where it is used. TEST_F(ResourceUsageTrackingTest, BufferUsageConflictWithInvisibleStageInBindGroup) { // Test render pass for bind group and index buffer. The conflict of storage and index // buffer usage resides between fragment stage and compute stage. But the compute stage // binding is not visible in render pass. { wgpu::Buffer buffer = CreateBuffer(4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::Index); wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage}}); wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, buffer}}); // Buffer usage in compute stage in bind group conflicts with index buffer. And binding // for compute stage is not visible in render pass. But we still track this binding. wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); DummyRenderPass dummyRenderPass(device); wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); pass.SetIndexBuffer(buffer, wgpu::IndexFormat::Uint32); pass.SetBindGroup(0, bg); pass.EndPass(); ASSERT_DEVICE_ERROR(encoder.Finish()); } // Test compute pass for bind group. The conflict of readonly storage and storage buffer // usage resides between compute stage and fragment stage. But the fragment stage binding is // not visible in the dispatch. { wgpu::Buffer buffer = CreateBuffer(4, wgpu::BufferUsage::Storage); wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::ReadOnlyStorage}, {1, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage}}); wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, buffer}, {1, buffer}}); // Create a no-op compute pipeline. wgpu::ComputePipeline cp = CreateNoOpComputePipeline({bgl}); // Buffer usage in compute stage conflicts with buffer usage in fragment stage. And // binding for fragment stage is not visible in the dispatch. But we still track this // invisible binding. wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); pass.SetPipeline(cp); pass.SetBindGroup(0, bg); pass.Dispatch(1); pass.EndPass(); ASSERT_DEVICE_ERROR(encoder.Finish()); } } // Test that it is invalid to have resource usage conflicts even when one of the bindings is not // used in the pipeline. TEST_F(ResourceUsageTrackingTest, BufferUsageConflictWithUnusedPipelineBindings) { wgpu::Buffer buffer = CreateBuffer(4, wgpu::BufferUsage::Storage); // Test render pass for bind groups with unused bindings. The conflict of readonly storage // and storage usages resides in different bind groups, although some bindings may not be // used because its bind group layout is not designated in pipeline layout. { // Create bind groups. The bindings are visible for render pass. wgpu::BindGroupLayout bgl0 = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::ReadOnlyStorage}}); wgpu::BindGroupLayout bgl1 = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Storage}}); wgpu::BindGroup bg0 = utils::MakeBindGroup(device, bgl0, {{0, buffer}}); wgpu::BindGroup bg1 = utils::MakeBindGroup(device, bgl1, {{0, buffer}}); // Create a passthrough render pipeline with a readonly buffer wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"( [[stage(vertex)]] fn main() -> [[builtin(position)]] vec4 { return vec4(); })"); wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( [[block]] struct RBuffer { value : f32; }; [[group(0), binding(0)]] var rBuffer : RBuffer; [[stage(fragment)]] fn main() { })"); utils::ComboRenderPipelineDescriptor pipelineDescriptor; pipelineDescriptor.vertex.module = vsModule; pipelineDescriptor.cFragment.module = fsModule; pipelineDescriptor.cTargets[0].writeMask = wgpu::ColorWriteMask::None; pipelineDescriptor.layout = utils::MakeBasicPipelineLayout(device, &bgl0); wgpu::RenderPipeline rp = device.CreateRenderPipeline(&pipelineDescriptor); // Resource in bg1 conflicts with resources used in bg0. However, bindings in bg1 is // not used in pipeline. But we still track this binding. wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); DummyRenderPass dummyRenderPass(device); wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); pass.SetBindGroup(0, bg0); pass.SetBindGroup(1, bg1); pass.SetPipeline(rp); pass.Draw(3); pass.EndPass(); ASSERT_DEVICE_ERROR(encoder.Finish()); } // Test that an unused bind group is not used to detect conflicts between bindings in // compute passes. { // Create bind groups. The bindings are visible for compute pass. wgpu::BindGroupLayout bgl0 = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::ReadOnlyStorage}}); wgpu::BindGroupLayout bgl1 = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage}}); wgpu::BindGroup bg0 = utils::MakeBindGroup(device, bgl0, {{0, buffer}}); wgpu::BindGroup bg1 = utils::MakeBindGroup(device, bgl1, {{0, buffer}}); // Create a compute pipeline with only one of the two BGLs. wgpu::ComputePipeline cp = CreateNoOpComputePipeline({bgl0}); // Resource in bg1 conflicts with resources used in bg0. However, the binding in bg1 is // not used in pipeline so no error is produced in the dispatch. wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); pass.SetBindGroup(0, bg0); pass.SetBindGroup(1, bg1); pass.SetPipeline(cp); pass.Dispatch(1); pass.EndPass(); encoder.Finish(); } } // Test that using a single texture in multiple read usages in the same pass is allowed. TEST_F(ResourceUsageTrackingTest, TextureWithMultipleReadUsages) { // Create a texture that will be used as both sampled and readonly storage texture wgpu::Texture texture = CreateTexture(wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::StorageBinding); wgpu::TextureView view = texture.CreateView(); // Create a bind group to use the texture as sampled and readonly storage bindings wgpu::BindGroupLayout bgl; // TODO(crbug.com/dawn/1025): Remove once ReadOnly storage texture deprecation period is // passed. EXPECT_DEPRECATION_WARNING( bgl = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Fragment | wgpu::ShaderStage::Compute, wgpu::TextureSampleType::Float}, {1, wgpu::ShaderStage::Fragment | wgpu::ShaderStage::Compute, wgpu::StorageTextureAccess::ReadOnly, kFormat}})); wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, view}, {1, view}}); // Test render pass { // Use the texture as both sampled and readonly storage in the same render pass wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); DummyRenderPass dummyRenderPass(device); wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); pass.SetBindGroup(0, bg); pass.EndPass(); encoder.Finish(); } // Test compute pass { // Use the texture as both sampled and readonly storage in the same compute pass wgpu::ComputePipeline cp = CreateNoOpComputePipeline({bgl}); wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); pass.SetBindGroup(0, bg); pass.SetPipeline(cp); pass.Dispatch(1); pass.EndPass(); encoder.Finish(); } } // Test that it is invalid to use the same texture as both readable and writable in the same // render pass. It is invalid in the same dispatch in compute pass. TEST_F(ResourceUsageTrackingTest, TextureWithReadAndWriteUsage) { // Test render pass { // Create a texture wgpu::Texture texture = CreateTexture(wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment); wgpu::TextureView view = texture.CreateView(); // Create a bind group to use the texture as sampled binding wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Vertex, wgpu::TextureSampleType::Float}}); wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, view}}); // Create a render pass to use the texture as a render target utils::ComboRenderPassDescriptor renderPass({view}); // It is invalid to use the texture as both sampled and render target in the same pass wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); pass.SetBindGroup(0, bg); pass.EndPass(); ASSERT_DEVICE_ERROR(encoder.Finish()); } // Test compute pass { // Create a texture wgpu::Texture texture = CreateTexture(wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::StorageBinding); wgpu::TextureView view = texture.CreateView(); // Create a bind group to use the texture as sampled and writeonly bindings wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Compute, wgpu::TextureSampleType::Float}, {1, wgpu::ShaderStage::Compute, wgpu::StorageTextureAccess::WriteOnly, kFormat}}); wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, view}, {1, view}}); // Create a no-op compute pipeline wgpu::ComputePipeline cp = CreateNoOpComputePipeline({bgl}); // It is valid to use the texture as both sampled and writeonly storage in a single // compute pass if dispatch command is not called. { wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); pass.SetBindGroup(0, bg); pass.EndPass(); encoder.Finish(); } // It is invalid to use the texture as both sampled and writeonly storage in a single // dispatch { wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); pass.SetPipeline(cp); pass.SetBindGroup(0, bg); pass.Dispatch(1); pass.EndPass(); ASSERT_DEVICE_ERROR(encoder.Finish()); } } } // Test using multiple writable usages on the same texture in a single pass/dispatch TEST_F(ResourceUsageTrackingTest, TextureWithMultipleWriteUsage) { // Test render pass { // Create a texture wgpu::Texture texture = CreateTexture(wgpu::TextureUsage::StorageBinding | wgpu::TextureUsage::RenderAttachment); wgpu::TextureView view = texture.CreateView(); // Create a bind group to use the texture as writeonly storage binding wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Fragment, wgpu::StorageTextureAccess::WriteOnly, kFormat}}); wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, view}}); // It is invalid to use the texture as both writeonly storage and render target in // the same pass { utils::ComboRenderPassDescriptor renderPass({view}); wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); pass.SetBindGroup(0, bg); pass.EndPass(); ASSERT_DEVICE_ERROR(encoder.Finish()); } // It is valid to use multiple writeonly storage usages on the same texture in render // pass { wgpu::BindGroup bg1 = utils::MakeBindGroup(device, bgl, {{0, view}}); wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); DummyRenderPass dummyRenderPass(device); wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); pass.SetBindGroup(0, bg); pass.SetBindGroup(1, bg1); pass.EndPass(); encoder.Finish(); } } // Test compute pass { // Create a texture wgpu::Texture texture = CreateTexture(wgpu::TextureUsage::StorageBinding); wgpu::TextureView view = texture.CreateView(); // Create a bind group to use the texture as sampled and writeonly bindings wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Compute, wgpu::StorageTextureAccess::WriteOnly, kFormat}, {1, wgpu::ShaderStage::Compute, wgpu::StorageTextureAccess::WriteOnly, kFormat}}); wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, view}, {1, view}}); // Create a no-op compute pipeline wgpu::ComputePipeline cp = CreateNoOpComputePipeline({bgl}); // It is valid to use the texture as multiple writeonly storage usages in a single // dispatch wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); pass.SetPipeline(cp); pass.SetBindGroup(0, bg); pass.Dispatch(1); pass.EndPass(); encoder.Finish(); } } // Test that a single subresource of a texture cannot be used as a render attachment more than // once in the same pass. TEST_F(ResourceUsageTrackingTest, TextureWithMultipleRenderAttachmentUsage) { // Create a texture with two array layers wgpu::TextureDescriptor descriptor; descriptor.dimension = wgpu::TextureDimension::e2D; descriptor.size = {1, 1, 2}; descriptor.usage = wgpu::TextureUsage::RenderAttachment; descriptor.format = kFormat; wgpu::Texture texture = device.CreateTexture(&descriptor); wgpu::TextureViewDescriptor viewDesc = {}; viewDesc.arrayLayerCount = 1; wgpu::TextureView viewLayer0 = texture.CreateView(&viewDesc); viewDesc.baseArrayLayer = 1; wgpu::TextureView viewLayer1 = texture.CreateView(&viewDesc); // Control: It is valid to use layer0 as a render target for one attachment, and // layer1 as the second attachment in the same pass { utils::ComboRenderPassDescriptor renderPass({viewLayer0, viewLayer1}); wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); pass.EndPass(); encoder.Finish(); } // Control: It is valid to use layer0 as a render target in separate passes. { utils::ComboRenderPassDescriptor renderPass({viewLayer0}); wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::RenderPassEncoder pass0 = encoder.BeginRenderPass(&renderPass); pass0.EndPass(); wgpu::RenderPassEncoder pass1 = encoder.BeginRenderPass(&renderPass); pass1.EndPass(); encoder.Finish(); } // It is invalid to use layer0 as a render target for both attachments in the same pass { utils::ComboRenderPassDescriptor renderPass({viewLayer0, viewLayer0}); wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); pass.EndPass(); ASSERT_DEVICE_ERROR(encoder.Finish()); } // It is invalid to use layer1 as a render target for both attachments in the same pass { utils::ComboRenderPassDescriptor renderPass({viewLayer1, viewLayer1}); wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); pass.EndPass(); ASSERT_DEVICE_ERROR(encoder.Finish()); } } // Test that using the same texture as both readable and writable in different passes is // allowed TEST_F(ResourceUsageTrackingTest, TextureWithReadAndWriteUsageInDifferentPasses) { // Test render pass { // Create textures that will be used as both a sampled texture and a render target wgpu::Texture t0 = CreateTexture(wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment); wgpu::TextureView v0 = t0.CreateView(); wgpu::Texture t1 = CreateTexture(wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment); wgpu::TextureView v1 = t1.CreateView(); // Create bind groups to use the texture as sampled wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Vertex, wgpu::TextureSampleType::Float}}); wgpu::BindGroup bg0 = utils::MakeBindGroup(device, bgl, {{0, v0}}); wgpu::BindGroup bg1 = utils::MakeBindGroup(device, bgl, {{0, v1}}); // Create render passes that will use the textures as render attachments utils::ComboRenderPassDescriptor renderPass0({v1}); utils::ComboRenderPassDescriptor renderPass1({v0}); // Use the textures as both sampled and render attachments in different passes wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::RenderPassEncoder pass0 = encoder.BeginRenderPass(&renderPass0); pass0.SetBindGroup(0, bg0); pass0.EndPass(); wgpu::RenderPassEncoder pass1 = encoder.BeginRenderPass(&renderPass1); pass1.SetBindGroup(0, bg1); pass1.EndPass(); encoder.Finish(); } // Test compute pass { // Create a texture that will be used storage texture wgpu::Texture texture = CreateTexture(wgpu::TextureUsage::StorageBinding); wgpu::TextureView view = texture.CreateView(); // Create bind groups to use the texture as readonly and writeonly bindings wgpu::BindGroupLayout readBGL; // TODO(crbug.com/dawn/1025): Remove once ReadOnly storage texture deprecation period is // passed. EXPECT_DEPRECATION_WARNING( readBGL = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Compute, wgpu::StorageTextureAccess::ReadOnly, kFormat}})); wgpu::BindGroupLayout writeBGL = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Compute, wgpu::StorageTextureAccess::WriteOnly, kFormat}}); wgpu::BindGroup readBG = utils::MakeBindGroup(device, readBGL, {{0, view}}); wgpu::BindGroup writeBG = utils::MakeBindGroup(device, writeBGL, {{0, view}}); // Use the textures as both readonly and writeonly storages in different passes wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::ComputePassEncoder pass0 = encoder.BeginComputePass(); pass0.SetBindGroup(0, readBG); pass0.EndPass(); wgpu::ComputePassEncoder pass1 = encoder.BeginComputePass(); pass1.SetBindGroup(0, writeBG); pass1.EndPass(); encoder.Finish(); } // Test compute pass and render pass mixed together with resource dependency { // Create a texture that will be used a storage texture wgpu::Texture texture = CreateTexture(wgpu::TextureUsage::StorageBinding); wgpu::TextureView view = texture.CreateView(); // Create bind groups to use the texture as readonly and writeonly bindings wgpu::BindGroupLayout writeBGL = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Compute, wgpu::StorageTextureAccess::WriteOnly, kFormat}}); wgpu::BindGroupLayout readBGL; // TODO(crbug.com/dawn/1025): Remove once ReadOnly storage texture deprecation period is // passed. EXPECT_DEPRECATION_WARNING( readBGL = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Fragment, wgpu::StorageTextureAccess::ReadOnly, kFormat}})); wgpu::BindGroup writeBG = utils::MakeBindGroup(device, writeBGL, {{0, view}}); wgpu::BindGroup readBG = utils::MakeBindGroup(device, readBGL, {{0, view}}); // Use the texture as writeonly and readonly storage in compute pass and render // pass respectively wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::ComputePassEncoder pass0 = encoder.BeginComputePass(); pass0.SetBindGroup(0, writeBG); pass0.EndPass(); DummyRenderPass dummyRenderPass(device); wgpu::RenderPassEncoder pass1 = encoder.BeginRenderPass(&dummyRenderPass); pass1.SetBindGroup(0, readBG); pass1.EndPass(); encoder.Finish(); } } // Test that it is invalid to use the same texture as both readable and writable in different // draws in a single render pass. But it is valid in different dispatches in a single compute // pass. TEST_F(ResourceUsageTrackingTest, TextureWithReadAndWriteUsageOnDifferentDrawsOrDispatches) { // Create a texture that will be used both as a sampled texture and a storage texture wgpu::Texture texture = CreateTexture(wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::StorageBinding); wgpu::TextureView view = texture.CreateView(); // Test render pass { // Create bind groups to use the texture as sampled and writeonly storage bindings wgpu::BindGroupLayout sampledBGL = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float}}); wgpu::BindGroupLayout writeBGL = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Fragment, wgpu::StorageTextureAccess::WriteOnly, kFormat}}); wgpu::BindGroup sampledBG = utils::MakeBindGroup(device, sampledBGL, {{0, view}}); wgpu::BindGroup writeBG = utils::MakeBindGroup(device, writeBGL, {{0, view}}); // Create a no-op render pipeline. wgpu::RenderPipeline rp = CreateNoOpRenderPipeline(); // It is not allowed to use the same texture as both readable and writable in different // draws within the same render pass. wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); DummyRenderPass dummyRenderPass(device); wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); pass.SetPipeline(rp); pass.SetBindGroup(0, sampledBG); pass.Draw(3); pass.SetBindGroup(0, writeBG); pass.Draw(3); pass.EndPass(); ASSERT_DEVICE_ERROR(encoder.Finish()); } // Test compute pass { // Create bind groups to use the texture as readonly and writeonly storage bindings wgpu::BindGroupLayout readBGL; // TODO(crbug.com/dawn/1025): Remove once ReadOnly storage texture deprecation period is // passed. EXPECT_DEPRECATION_WARNING( readBGL = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Compute, wgpu::StorageTextureAccess::ReadOnly, kFormat}})); wgpu::BindGroupLayout writeBGL = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Compute, wgpu::StorageTextureAccess::WriteOnly, kFormat}}); wgpu::BindGroup readBG = utils::MakeBindGroup(device, readBGL, {{0, view}}); wgpu::BindGroup writeBG = utils::MakeBindGroup(device, writeBGL, {{0, view}}); // Create a no-op compute pipeline. wgpu::ComputePipeline readCp = CreateNoOpComputePipeline({readBGL}); wgpu::ComputePipeline writeCp = CreateNoOpComputePipeline({writeBGL}); // It is valid to use the same texture as both readable and writable in different // dispatches within the same compute pass. wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); pass.SetPipeline(readCp); pass.SetBindGroup(0, readBG); pass.Dispatch(1); pass.SetPipeline(writeCp); pass.SetBindGroup(0, writeBG); pass.Dispatch(1); pass.EndPass(); encoder.Finish(); } } // Test that it is invalid to use the same texture as both readable and writable in a single // draw or dispatch. TEST_F(ResourceUsageTrackingTest, TextureWithReadAndWriteUsageInSingleDrawOrDispatch) { // Create a texture that will be used both as a sampled texture and a storage texture wgpu::Texture texture = CreateTexture(wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::StorageBinding); wgpu::TextureView view = texture.CreateView(); // Test render pass { // Create the bind group to use the texture as sampled and writeonly storage bindings wgpu::BindGroupLayout sampledBGL = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float}}); wgpu::BindGroupLayout writeBGL = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Fragment, wgpu::StorageTextureAccess::WriteOnly, kFormat}}); wgpu::BindGroup sampledBG = utils::MakeBindGroup(device, sampledBGL, {{0, view}}); wgpu::BindGroup writeBG = utils::MakeBindGroup(device, writeBGL, {{0, view}}); // Create a no-op render pipeline. wgpu::RenderPipeline rp = CreateNoOpRenderPipeline(); // It is invalid to use the same texture as both readable and writable usages in a // single draw wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); DummyRenderPass dummyRenderPass(device); wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); pass.SetPipeline(rp); pass.SetBindGroup(0, sampledBG); pass.SetBindGroup(1, writeBG); pass.Draw(3); pass.EndPass(); ASSERT_DEVICE_ERROR(encoder.Finish()); } // Test compute pass { // Create the bind group to use the texture as readonly and writeonly storage bindings wgpu::BindGroupLayout readBGL; // TODO(crbug.com/dawn/1025): Remove once ReadOnly storage texture deprecation period is // passed. EXPECT_DEPRECATION_WARNING( readBGL = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Compute, wgpu::StorageTextureAccess::ReadOnly, kFormat}})); wgpu::BindGroupLayout writeBGL = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Compute, wgpu::StorageTextureAccess::WriteOnly, kFormat}}); wgpu::BindGroup readBG = utils::MakeBindGroup(device, readBGL, {{0, view}}); wgpu::BindGroup writeBG = utils::MakeBindGroup(device, writeBGL, {{0, view}}); // Create a no-op compute pipeline. wgpu::ComputePipeline cp = CreateNoOpComputePipeline({readBGL, writeBGL}); // It is invalid to use the same texture as both readable and writable usages in a // single dispatch wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); pass.SetPipeline(cp); pass.SetBindGroup(0, readBG); pass.SetBindGroup(1, writeBG); pass.Dispatch(1); pass.EndPass(); ASSERT_DEVICE_ERROR(encoder.Finish()); } } // Test that using a single texture as copy src/dst and writable/readable usage in pass is // allowed. TEST_F(ResourceUsageTrackingTest, TextureCopyAndTextureUsageInPass) { // Create textures that will be used as both a sampled texture and a render target wgpu::Texture texture0 = CreateTexture(wgpu::TextureUsage::CopySrc); wgpu::Texture texture1 = CreateTexture(wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment); wgpu::TextureView view0 = texture0.CreateView(); wgpu::TextureView view1 = texture1.CreateView(); wgpu::ImageCopyTexture srcView = utils::CreateImageCopyTexture(texture0, 0, {0, 0, 0}); wgpu::ImageCopyTexture dstView = utils::CreateImageCopyTexture(texture1, 0, {0, 0, 0}); wgpu::Extent3D copySize = {1, 1, 1}; // Use the texture as both copy dst and render attachment in render pass { wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); encoder.CopyTextureToTexture(&srcView, &dstView, ©Size); utils::ComboRenderPassDescriptor renderPass({view1}); wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); pass.EndPass(); encoder.Finish(); } // Use the texture as both copy dst and readable usage in compute pass { // Create the bind group to use the texture as sampled wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Compute, wgpu::TextureSampleType::Float}}); wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, view1}}); wgpu::ComputePipeline cp = CreateNoOpComputePipeline({bgl}); wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); encoder.CopyTextureToTexture(&srcView, &dstView, ©Size); wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); pass.SetBindGroup(0, bg); pass.SetPipeline(cp); pass.Dispatch(1); pass.EndPass(); encoder.Finish(); } } // Test that all consecutive SetBindGroup()s take effect even though some bind groups are not // used because they are overwritten by a consecutive call. TEST_F(ResourceUsageTrackingTest, TextureWithMultipleSetBindGroupsOnSameIndex) { // Test render pass { // Create textures that will be used as both a sampled texture and a render target wgpu::Texture texture0 = CreateTexture(wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment); wgpu::TextureView view0 = texture0.CreateView(); wgpu::Texture texture1 = CreateTexture(wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment); wgpu::TextureView view1 = texture1.CreateView(); // Create the bind group to use the texture as sampled wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Vertex, wgpu::TextureSampleType::Float}}); wgpu::BindGroup bg0 = utils::MakeBindGroup(device, bgl, {{0, view0}}); wgpu::BindGroup bg1 = utils::MakeBindGroup(device, bgl, {{0, view1}}); // Create the render pass that will use the texture as an render attachment utils::ComboRenderPassDescriptor renderPass({view0}); // Set bind group on the same index twice. The second one overwrites the first one. // No texture is used as both sampled and render attachment in the same pass. But the // overwritten texture still take effect during resource tracking. { wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); pass.SetBindGroup(0, bg0); pass.SetBindGroup(0, bg1); pass.EndPass(); ASSERT_DEVICE_ERROR(encoder.Finish()); } // Set bind group on the same index twice. The second one overwrites the first one. // texture0 is used as both sampled and render attachment in the same pass { wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); pass.SetBindGroup(0, bg1); pass.SetBindGroup(0, bg0); pass.EndPass(); ASSERT_DEVICE_ERROR(encoder.Finish()); } } // Test compute pass { // Create a texture that will be used both as storage texture wgpu::Texture texture0 = CreateTexture(wgpu::TextureUsage::StorageBinding); wgpu::TextureView view0 = texture0.CreateView(); wgpu::Texture texture1 = CreateTexture(wgpu::TextureUsage::StorageBinding); wgpu::TextureView view1 = texture1.CreateView(); // Create the bind group to use the texture as readonly and writeonly bindings wgpu::BindGroupLayout writeBGL = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Compute, wgpu::StorageTextureAccess::WriteOnly, kFormat}}); wgpu::BindGroupLayout readBGL; // TODO(crbug.com/dawn/1025): Remove once ReadOnly storage texture deprecation period is // passed. EXPECT_DEPRECATION_WARNING( readBGL = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Compute, wgpu::StorageTextureAccess::ReadOnly, kFormat}})); wgpu::BindGroup writeBG0 = utils::MakeBindGroup(device, writeBGL, {{0, view0}}); wgpu::BindGroup readBG0 = utils::MakeBindGroup(device, readBGL, {{0, view0}}); wgpu::BindGroup readBG1 = utils::MakeBindGroup(device, readBGL, {{0, view1}}); // Create a no-op compute pipeline. wgpu::ComputePipeline cp = CreateNoOpComputePipeline({writeBGL, readBGL}); // Set bind group on the same index twice. The second one overwrites the first one. // No texture is used as both readonly and writeonly storage in the same dispatch so // there are no errors. { wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); pass.SetBindGroup(0, writeBG0); pass.SetBindGroup(1, readBG0); pass.SetBindGroup(1, readBG1); pass.SetPipeline(cp); pass.Dispatch(1); pass.EndPass(); encoder.Finish(); } // Set bind group on the same index twice. The second one overwrites the first one. // texture0 is used as both writeonly and readonly storage in the same dispatch, which // is an error. { wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); pass.SetBindGroup(0, writeBG0); pass.SetBindGroup(1, readBG1); pass.SetBindGroup(1, readBG0); pass.SetPipeline(cp); pass.Dispatch(1); pass.EndPass(); ASSERT_DEVICE_ERROR(encoder.Finish()); } } } // Test that it is invalid to have resource usage conflicts even when all bindings are not // visible to the programmable pass where it is used. TEST_F(ResourceUsageTrackingTest, TextureUsageConflictBetweenInvisibleStagesInBindGroup) { // Create texture and texture view wgpu::Texture texture = CreateTexture(wgpu::TextureUsage::StorageBinding); wgpu::TextureView view = texture.CreateView(); // Test render pass for bind group. The conflict of readonly storage and writeonly storage // usage doesn't reside in render related stages at all { // Create a bind group whose bindings are not visible in render pass wgpu::BindGroupLayout bgl; // TODO(crbug.com/dawn/1025): Remove once ReadOnly storage texture deprecation period is // passed. EXPECT_DEPRECATION_WARNING( bgl = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Compute, wgpu::StorageTextureAccess::ReadOnly, kFormat}, {1, wgpu::ShaderStage::None, wgpu::StorageTextureAccess::WriteOnly, kFormat}})); wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, view}, {1, view}}); // These two bindings are invisible in render pass. But we still track these bindings. wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); DummyRenderPass dummyRenderPass(device); wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); pass.SetBindGroup(0, bg); pass.EndPass(); ASSERT_DEVICE_ERROR(encoder.Finish()); } // Test compute pass for bind group. The conflict of readonly storage and writeonly storage // usage doesn't reside in compute related stage at all { // Create a bind group whose bindings are not visible in compute pass wgpu::BindGroupLayout bgl; // TODO(crbug.com/dawn/1025): Remove once ReadOnly storage texture deprecation period is // passed. EXPECT_DEPRECATION_WARNING( bgl = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Fragment, wgpu::StorageTextureAccess::ReadOnly, kFormat}, {1, wgpu::ShaderStage::None, wgpu::StorageTextureAccess::WriteOnly, kFormat}})); wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, view}, {1, view}}); // Create a no-op compute pipeline. wgpu::ComputePipeline cp = CreateNoOpComputePipeline({bgl}); // These two bindings are invisible in compute pass. But we still track these bindings. wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); pass.SetPipeline(cp); pass.SetBindGroup(0, bg); pass.Dispatch(1); pass.EndPass(); ASSERT_DEVICE_ERROR(encoder.Finish()); } } // Test that it is invalid to have resource usage conflicts even when one of the bindings is not // visible to the programmable pass where it is used. TEST_F(ResourceUsageTrackingTest, TextureUsageConflictWithInvisibleStageInBindGroup) { // Create texture and texture view wgpu::Texture texture = CreateTexture(wgpu::TextureUsage::StorageBinding | wgpu::TextureUsage::RenderAttachment); wgpu::TextureView view = texture.CreateView(); // Test render pass { // Create the render pass that will use the texture as an render attachment utils::ComboRenderPassDescriptor renderPass({view}); // Create a bind group which use the texture as readonly storage in compute stage wgpu::BindGroupLayout bgl; // TODO(crbug.com/dawn/1025): Remove once ReadOnly storage texture deprecation period is // passed. EXPECT_DEPRECATION_WARNING( bgl = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Compute, wgpu::StorageTextureAccess::ReadOnly, kFormat}})); wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, view}}); // Texture usage in compute stage in bind group conflicts with render target. And // binding for compute stage is not visible in render pass. But we still track this // binding. wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); pass.SetBindGroup(0, bg); pass.EndPass(); ASSERT_DEVICE_ERROR(encoder.Finish()); } // Test compute pass { // Create a bind group which contains both fragment and compute stages wgpu::BindGroupLayout bgl; // TODO(crbug.com/dawn/1025): Remove once ReadOnly storage texture deprecation period is // passed. EXPECT_DEPRECATION_WARNING( bgl = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Fragment, wgpu::StorageTextureAccess::ReadOnly, kFormat}, {1, wgpu::ShaderStage::Compute, wgpu::StorageTextureAccess::WriteOnly, kFormat}})); wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, view}, {1, view}}); // Create a no-op compute pipeline. wgpu::ComputePipeline cp = CreateNoOpComputePipeline({bgl}); // Texture usage in compute stage conflicts with texture usage in fragment stage. And // binding for fragment stage is not visible in compute pass. But we still track this // invisible binding. wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); pass.SetPipeline(cp); pass.SetBindGroup(0, bg); pass.Dispatch(1); pass.EndPass(); ASSERT_DEVICE_ERROR(encoder.Finish()); } } // Test that it is invalid to have resource usage conflicts even when one of the bindings is not // used in the pipeline. TEST_F(ResourceUsageTrackingTest, TextureUsageConflictWithUnusedPipelineBindings) { // Create texture and texture view wgpu::Texture texture = CreateTexture(wgpu::TextureUsage::StorageBinding); wgpu::TextureView view = texture.CreateView(); // Create bind groups. wgpu::BindGroupLayout readBGL; // TODO(crbug.com/dawn/1025): Remove once ReadOnly storage texture deprecation period is // passed. EXPECT_DEPRECATION_WARNING( readBGL = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Fragment | wgpu::ShaderStage::Compute, wgpu::StorageTextureAccess::ReadOnly, kFormat}})); wgpu::BindGroupLayout writeBGL = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Fragment | wgpu::ShaderStage::Compute, wgpu::StorageTextureAccess::WriteOnly, kFormat}}); wgpu::BindGroup readBG = utils::MakeBindGroup(device, readBGL, {{0, view}}); wgpu::BindGroup writeBG = utils::MakeBindGroup(device, writeBGL, {{0, view}}); // Test render pass { // Create a passthrough render pipeline with a readonly storage texture wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"( [[stage(vertex)]] fn main() -> [[builtin(position)]] vec4 { return vec4(); })"); wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( [[group(0), binding(0)]] var tex : texture_storage_2d; [[stage(fragment)]] fn main() { })"); utils::ComboRenderPipelineDescriptor pipelineDescriptor; pipelineDescriptor.vertex.module = vsModule; pipelineDescriptor.cFragment.module = fsModule; pipelineDescriptor.cTargets[0].writeMask = wgpu::ColorWriteMask::None; pipelineDescriptor.layout = utils::MakeBasicPipelineLayout(device, &readBGL); wgpu::RenderPipeline rp = device.CreateRenderPipeline(&pipelineDescriptor); // Texture binding in readBG conflicts with texture binding in writeBG. The binding // in writeBG is not used in pipeline. But we still track this binding. wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); DummyRenderPass dummyRenderPass(device); wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); pass.SetBindGroup(0, readBG); pass.SetBindGroup(1, writeBG); pass.SetPipeline(rp); pass.Draw(3); pass.EndPass(); ASSERT_DEVICE_ERROR(encoder.Finish()); } // Test compute pass { wgpu::ComputePipeline cp = CreateNoOpComputePipeline({readBGL}); // Texture binding in readBG conflicts with texture binding in writeBG. The binding // in writeBG is not used in pipeline's layout so it isn't an error. wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); pass.SetBindGroup(0, readBG); pass.SetBindGroup(1, writeBG); pass.SetPipeline(cp); pass.Dispatch(1); pass.EndPass(); encoder.Finish(); } } // Test that using an indirect buffer is disallowed with a writable usage (like storage) but // allowed with a readable usage (like readonly storage). TEST_F(ResourceUsageTrackingTest, IndirectBufferWithReadOrWriteStorage) { wgpu::Buffer buffer = CreateBuffer(20, wgpu::BufferUsage::Indirect | wgpu::BufferUsage::Storage); wgpu::BindGroupLayout readBGL = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::ReadOnlyStorage}}); wgpu::BindGroupLayout writeBGL = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage}}); wgpu::BindGroup readBG = utils::MakeBindGroup(device, readBGL, {{0, buffer}}); wgpu::BindGroup writeBG = utils::MakeBindGroup(device, writeBGL, {{0, buffer}}); // Test pipelines wgpu::RenderPipeline rp = CreateNoOpRenderPipeline(); wgpu::ComputePipeline readCp = CreateNoOpComputePipeline({readBGL}); wgpu::ComputePipeline writeCp = CreateNoOpComputePipeline({writeBGL}); // Test that indirect + readonly is allowed in the same render pass. { wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); DummyRenderPass dummyRenderPass(device); wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); pass.SetPipeline(rp); pass.SetBindGroup(0, readBG); pass.DrawIndirect(buffer, 0); pass.EndPass(); encoder.Finish(); } // Test that indirect + writable is disallowed in the same render pass. { wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); DummyRenderPass dummyRenderPass(device); wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); pass.SetPipeline(rp); pass.SetBindGroup(0, writeBG); pass.DrawIndirect(buffer, 0); pass.EndPass(); ASSERT_DEVICE_ERROR(encoder.Finish()); } // Test that indirect + readonly is allowed in the same dispatch { wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); pass.SetPipeline(readCp); pass.SetBindGroup(0, readBG); pass.DispatchIndirect(buffer, 0); pass.EndPass(); encoder.Finish(); } // Test that indirect + writable is disallowed in the same dispatch { wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); pass.SetPipeline(writeCp); pass.SetBindGroup(0, writeBG); pass.DispatchIndirect(buffer, 0); pass.EndPass(); ASSERT_DEVICE_ERROR(encoder.Finish()); } } // TODO (yunchao.he@intel.com): // // * Add tests for multiple encoders upon the same resource simultaneously. This situation fits // some cases like VR, multi-threading, etc. // // * Add tests for bundle } // anonymous namespace