| // 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::TextureFormat format = wgpu::TextureFormat::RGBA8Unorm) { | |
| wgpu::TextureDescriptor descriptor; | |
| descriptor.dimension = wgpu::TextureDimension::e2D; | |
| descriptor.size = {1, 1, 1}; | |
| descriptor.sampleCount = 1; | |
| descriptor.mipLevelCount = 1; | |
| descriptor.usage = usage; | |
| descriptor.format = format; | |
| 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<f32> { | |
| return vec4<f32>(); | |
| })"); | |
| 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<wgpu::BindGroupLayout> 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<f32> { | |
| return vec4<f32>(); | |
| })"); | |
| wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( | |
| struct RBuffer { | |
| value : f32; | |
| }; | |
| [[group(0), binding(0)]] var<storage, read> 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 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 that it is invalid to use the same texture as both readable and writable depth/stencil | |
| // attachment in the same render pass. But it is valid to use it as both readable and readonly | |
| // depth/stencil attachment in the same render pass. | |
| // Note that depth/stencil attachment is a special render attachment, it can be readonly. | |
| TEST_F(ResourceUsageTrackingTest, TextureWithSamplingAndDepthStencilAttachment) { | |
| // Create a texture | |
| wgpu::Texture texture = | |
| CreateTexture(wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment, | |
| wgpu::TextureFormat::Depth32Float); | |
| 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::Fragment, wgpu::TextureSampleType::Depth}}); | |
| wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, view}}); | |
| // Create a render pass to use the texture as a render target | |
| utils::ComboRenderPassDescriptor passDescriptor({}, view); | |
| passDescriptor.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Load; | |
| passDescriptor.cDepthStencilAttachmentInfo.depthStoreOp = wgpu::StoreOp::Store; | |
| // It is invalid to use the texture as both sampled and writeable depth/stencil attachment | |
| // in the same pass | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor); | |
| pass.SetBindGroup(0, bg); | |
| pass.EndPass(); | |
| ASSERT_DEVICE_ERROR(encoder.Finish()); | |
| } | |
| // It is valid to use the texture as both sampled and readonly depth/stencil attachment in | |
| // the same pass | |
| { | |
| passDescriptor.cDepthStencilAttachmentInfo.depthReadOnly = true; | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor); | |
| pass.SetBindGroup(0, bg); | |
| pass.EndPass(); | |
| 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::TextureBinding | | |
| wgpu::TextureUsage::StorageBinding); | |
| wgpu::TextureView view = texture.CreateView(); | |
| // Create bind groups to use the texture as sampled and writeonly bindings | |
| wgpu::BindGroupLayout readBGL = utils::MakeBindGroupLayout( | |
| device, {{0, wgpu::ShaderStage::Compute, wgpu::TextureSampleType::Float}}); | |
| 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 sampled 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::TextureBinding | | |
| wgpu::TextureUsage::StorageBinding); | |
| wgpu::TextureView view = texture.CreateView(); | |
| // Create bind groups to use the texture as sampled and writeonly bindings | |
| wgpu::BindGroupLayout writeBGL = utils::MakeBindGroupLayout( | |
| device, | |
| {{0, wgpu::ShaderStage::Compute, wgpu::StorageTextureAccess::WriteOnly, kFormat}}); | |
| wgpu::BindGroupLayout readBGL = utils::MakeBindGroupLayout( | |
| device, {{0, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float}}); | |
| wgpu::BindGroup writeBG = utils::MakeBindGroup(device, writeBGL, {{0, view}}); | |
| wgpu::BindGroup readBG = utils::MakeBindGroup(device, readBGL, {{0, view}}); | |
| // Use the texture as writeonly and sampled 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 sampled and writeonly storage bindings | |
| wgpu::BindGroupLayout readBGL = utils::MakeBindGroupLayout( | |
| device, {{0, wgpu::ShaderStage::Compute, wgpu::TextureSampleType::Float}}); | |
| 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 sampled and writeonly storage bindings | |
| wgpu::BindGroupLayout readBGL = utils::MakeBindGroupLayout( | |
| device, {{0, wgpu::ShaderStage::Compute, wgpu::TextureSampleType::Float}}); | |
| 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::TextureBinding | | |
| wgpu::TextureUsage::StorageBinding); | |
| wgpu::TextureView view0 = texture0.CreateView(); | |
| wgpu::Texture texture1 = CreateTexture(wgpu::TextureUsage::TextureBinding); | |
| wgpu::TextureView view1 = texture1.CreateView(); | |
| // Create the bind group to use the texture as sampled and writeonly bindings | |
| wgpu::BindGroupLayout writeBGL = utils::MakeBindGroupLayout( | |
| device, | |
| {{0, wgpu::ShaderStage::Compute, wgpu::StorageTextureAccess::WriteOnly, kFormat}}); | |
| wgpu::BindGroupLayout readBGL = utils::MakeBindGroupLayout( | |
| device, {{0, wgpu::ShaderStage::Compute, wgpu::TextureSampleType::Float}}); | |
| 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 sampled 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 sampled 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::TextureBinding | wgpu::TextureUsage::StorageBinding); | |
| wgpu::TextureView view = texture.CreateView(); | |
| // Test render pass for bind group. The conflict of sampled 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 = utils::MakeBindGroupLayout( | |
| device, | |
| {{0, wgpu::ShaderStage::Compute, wgpu::TextureSampleType::Float}, | |
| {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 sampled 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 = utils::MakeBindGroupLayout( | |
| device, | |
| {{0, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float}, | |
| {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::TextureBinding | 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 sampled storage in compute stage | |
| wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( | |
| device, {{0, wgpu::ShaderStage::Compute, wgpu::TextureSampleType::Float}}); | |
| 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 = utils::MakeBindGroupLayout( | |
| device, | |
| {{0, wgpu::ShaderStage::Fragment, 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}); | |
| // 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::TextureBinding | wgpu::TextureUsage::StorageBinding); | |
| wgpu::TextureView view = texture.CreateView(); | |
| // Create bind groups. | |
| wgpu::BindGroupLayout readBGL = utils::MakeBindGroupLayout( | |
| device, {{0, wgpu::ShaderStage::Fragment | wgpu::ShaderStage::Compute, | |
| wgpu::TextureSampleType::Float}}); | |
| 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 sampled storage texture | |
| wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"( | |
| [[stage(vertex)]] fn main() -> [[builtin(position)]] vec4<f32> { | |
| return vec4<f32>(); | |
| })"); | |
| wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( | |
| [[group(0), binding(0)]] var tex : texture_2d<f32>; | |
| [[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 |