| // Copyright 2019 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/DawnTest.h" |
| |
| #include "utils/WGPUHelpers.h" |
| |
| class ComputeStorageBufferBarrierTests : public DawnTest { |
| protected: |
| static constexpr uint32_t kNumValues = 100; |
| static constexpr uint32_t kIterations = 100; |
| }; |
| |
| // Test that multiple dispatches to increment values in a storage buffer are synchronized. |
| TEST_P(ComputeStorageBufferBarrierTests, AddIncrement) { |
| std::vector<uint32_t> data(kNumValues, 0); |
| std::vector<uint32_t> expected(kNumValues, 0x1234 * kIterations); |
| |
| uint64_t bufferSize = static_cast<uint64_t>(data.size() * sizeof(uint32_t)); |
| wgpu::Buffer buffer = utils::CreateBufferFromData( |
| device, data.data(), bufferSize, wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc); |
| |
| wgpu::ShaderModule module = utils::CreateShaderModule(device, R"( |
| struct Buf { |
| data : array<u32, 100>; |
| }; |
| |
| [[group(0), binding(0)]] var<storage, read_write> buf : Buf; |
| |
| [[stage(compute), workgroup_size(1)]] |
| fn main([[builtin(global_invocation_id)]] GlobalInvocationID : vec3<u32>) { |
| buf.data[GlobalInvocationID.x] = buf.data[GlobalInvocationID.x] + 0x1234u; |
| } |
| )"); |
| |
| wgpu::ComputePipelineDescriptor pipelineDesc = {}; |
| pipelineDesc.compute.module = module; |
| pipelineDesc.compute.entryPoint = "main"; |
| wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&pipelineDesc); |
| |
| wgpu::BindGroup bindGroup = |
| utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), {{0, buffer, 0, bufferSize}}); |
| |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); |
| pass.SetPipeline(pipeline); |
| pass.SetBindGroup(0, bindGroup); |
| for (uint32_t i = 0; i < kIterations; ++i) { |
| pass.Dispatch(kNumValues); |
| } |
| pass.EndPass(); |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| EXPECT_BUFFER_U32_RANGE_EQ(expected.data(), buffer, 0, kNumValues); |
| } |
| |
| // Test that multiple dispatches to increment values by ping-ponging between two storage buffers |
| // are synchronized. |
| TEST_P(ComputeStorageBufferBarrierTests, AddPingPong) { |
| std::vector<uint32_t> data(kNumValues, 0); |
| std::vector<uint32_t> expectedA(kNumValues, 0x1234 * kIterations); |
| std::vector<uint32_t> expectedB(kNumValues, 0x1234 * (kIterations - 1)); |
| |
| uint64_t bufferSize = static_cast<uint64_t>(data.size() * sizeof(uint32_t)); |
| |
| wgpu::Buffer bufferA = utils::CreateBufferFromData( |
| device, data.data(), bufferSize, wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc); |
| |
| wgpu::Buffer bufferB = utils::CreateBufferFromData( |
| device, data.data(), bufferSize, wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc); |
| |
| wgpu::ShaderModule module = utils::CreateShaderModule(device, R"( |
| struct Buf { |
| data : array<u32, 100>; |
| }; |
| |
| [[group(0), binding(0)]] var<storage, read_write> src : Buf; |
| [[group(0), binding(1)]] var<storage, read_write> dst : Buf; |
| |
| [[stage(compute), workgroup_size(1)]] |
| fn main([[builtin(global_invocation_id)]] GlobalInvocationID : vec3<u32>) { |
| dst.data[GlobalInvocationID.x] = src.data[GlobalInvocationID.x] + 0x1234u; |
| } |
| )"); |
| |
| wgpu::ComputePipelineDescriptor pipelineDesc = {}; |
| pipelineDesc.compute.module = module; |
| pipelineDesc.compute.entryPoint = "main"; |
| wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&pipelineDesc); |
| |
| wgpu::BindGroup bindGroupA = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), |
| { |
| {0, bufferA, 0, bufferSize}, |
| {1, bufferB, 0, bufferSize}, |
| }); |
| |
| wgpu::BindGroup bindGroupB = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), |
| { |
| {0, bufferB, 0, bufferSize}, |
| {1, bufferA, 0, bufferSize}, |
| }); |
| |
| wgpu::BindGroup bindGroups[2] = {bindGroupA, bindGroupB}; |
| |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); |
| pass.SetPipeline(pipeline); |
| |
| for (uint32_t i = 0; i < kIterations / 2; ++i) { |
| pass.SetBindGroup(0, bindGroups[0]); |
| pass.Dispatch(kNumValues); |
| pass.SetBindGroup(0, bindGroups[1]); |
| pass.Dispatch(kNumValues); |
| } |
| pass.EndPass(); |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| EXPECT_BUFFER_U32_RANGE_EQ(expectedA.data(), bufferA, 0, kNumValues); |
| EXPECT_BUFFER_U32_RANGE_EQ(expectedB.data(), bufferB, 0, kNumValues); |
| } |
| |
| // Test that multiple dispatches to increment values by ping-ponging between storage buffers and |
| // read-only storage buffers are synchronized in one compute pass. |
| TEST_P(ComputeStorageBufferBarrierTests, StorageAndReadonlyStoragePingPongInOnePass) { |
| std::vector<uint32_t> data(kNumValues, 0); |
| std::vector<uint32_t> expectedA(kNumValues, 0x1234 * kIterations); |
| std::vector<uint32_t> expectedB(kNumValues, 0x1234 * (kIterations - 1)); |
| |
| uint64_t bufferSize = static_cast<uint64_t>(data.size() * sizeof(uint32_t)); |
| |
| wgpu::Buffer bufferA = utils::CreateBufferFromData( |
| device, data.data(), bufferSize, wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc); |
| |
| wgpu::Buffer bufferB = utils::CreateBufferFromData( |
| device, data.data(), bufferSize, wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc); |
| |
| wgpu::ShaderModule module = utils::CreateShaderModule(device, R"( |
| struct Buf { |
| data : array<u32, 100>; |
| }; |
| |
| [[group(0), binding(0)]] var<storage, read> src : Buf; |
| [[group(0), binding(1)]] var<storage, read_write> dst : Buf; |
| |
| [[stage(compute), workgroup_size(1)]] |
| fn main([[builtin(global_invocation_id)]] GlobalInvocationID : vec3<u32>) { |
| dst.data[GlobalInvocationID.x] = src.data[GlobalInvocationID.x] + 0x1234u; |
| } |
| )"); |
| |
| wgpu::ComputePipelineDescriptor pipelineDesc = {}; |
| pipelineDesc.compute.module = module; |
| pipelineDesc.compute.entryPoint = "main"; |
| wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&pipelineDesc); |
| |
| wgpu::BindGroup bindGroupA = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), |
| { |
| {0, bufferA, 0, bufferSize}, |
| {1, bufferB, 0, bufferSize}, |
| }); |
| |
| wgpu::BindGroup bindGroupB = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), |
| { |
| {0, bufferB, 0, bufferSize}, |
| {1, bufferA, 0, bufferSize}, |
| }); |
| |
| wgpu::BindGroup bindGroups[2] = {bindGroupA, bindGroupB}; |
| |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); |
| pass.SetPipeline(pipeline); |
| |
| for (uint32_t i = 0; i < kIterations / 2; ++i) { |
| pass.SetBindGroup(0, bindGroups[0]); |
| pass.Dispatch(kNumValues); |
| pass.SetBindGroup(0, bindGroups[1]); |
| pass.Dispatch(kNumValues); |
| } |
| pass.EndPass(); |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| EXPECT_BUFFER_U32_RANGE_EQ(expectedA.data(), bufferA, 0, kNumValues); |
| EXPECT_BUFFER_U32_RANGE_EQ(expectedB.data(), bufferB, 0, kNumValues); |
| } |
| |
| // Test that Storage to Uniform buffer transitions work and synchronize correctly |
| // by ping-ponging between Storage/Uniform usage in sequential compute passes. |
| TEST_P(ComputeStorageBufferBarrierTests, UniformToStorageAddPingPong) { |
| std::vector<uint32_t> data(kNumValues, 0); |
| std::vector<uint32_t> expectedA(kNumValues, 0x1234 * kIterations); |
| std::vector<uint32_t> expectedB(kNumValues, 0x1234 * (kIterations - 1)); |
| |
| uint64_t bufferSize = static_cast<uint64_t>(data.size() * sizeof(uint32_t)); |
| |
| wgpu::Buffer bufferA = utils::CreateBufferFromData( |
| device, data.data(), bufferSize, |
| wgpu::BufferUsage::Storage | wgpu::BufferUsage::Uniform | wgpu::BufferUsage::CopySrc); |
| |
| wgpu::Buffer bufferB = utils::CreateBufferFromData( |
| device, data.data(), bufferSize, |
| wgpu::BufferUsage::Storage | wgpu::BufferUsage::Uniform | wgpu::BufferUsage::CopySrc); |
| |
| wgpu::ShaderModule module = utils::CreateShaderModule(device, R"( |
| struct Buf { |
| data : array<vec4<u32>, 25>; |
| }; |
| |
| [[group(0), binding(0)]] var<uniform> src : Buf; |
| [[group(0), binding(1)]] var<storage, read_write> dst : Buf; |
| |
| [[stage(compute), workgroup_size(1)]] |
| fn main([[builtin(global_invocation_id)]] GlobalInvocationID : vec3<u32>) { |
| dst.data[GlobalInvocationID.x] = src.data[GlobalInvocationID.x] + |
| vec4<u32>(0x1234u, 0x1234u, 0x1234u, 0x1234u); |
| } |
| )"); |
| |
| wgpu::ComputePipelineDescriptor pipelineDesc = {}; |
| pipelineDesc.compute.module = module; |
| pipelineDesc.compute.entryPoint = "main"; |
| wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&pipelineDesc); |
| |
| wgpu::BindGroup bindGroupA = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), |
| { |
| {0, bufferA, 0, bufferSize}, |
| {1, bufferB, 0, bufferSize}, |
| }); |
| |
| wgpu::BindGroup bindGroupB = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), |
| { |
| {0, bufferB, 0, bufferSize}, |
| {1, bufferA, 0, bufferSize}, |
| }); |
| |
| wgpu::BindGroup bindGroups[2] = {bindGroupA, bindGroupB}; |
| |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| |
| for (uint32_t i = 0, b = 0; i < kIterations; ++i, b = 1 - b) { |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); |
| pass.SetPipeline(pipeline); |
| pass.SetBindGroup(0, bindGroups[b]); |
| pass.Dispatch(kNumValues / 4); |
| pass.EndPass(); |
| } |
| |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| EXPECT_BUFFER_U32_RANGE_EQ(expectedA.data(), bufferA, 0, kNumValues); |
| EXPECT_BUFFER_U32_RANGE_EQ(expectedB.data(), bufferB, 0, kNumValues); |
| } |
| |
| // Test that Storage to Uniform buffer transitions work and synchronize correctly |
| // by ping-ponging between Storage/Uniform usage in one compute pass. |
| TEST_P(ComputeStorageBufferBarrierTests, UniformToStorageAddPingPongInOnePass) { |
| std::vector<uint32_t> data(kNumValues, 0); |
| std::vector<uint32_t> expectedA(kNumValues, 0x1234 * kIterations); |
| std::vector<uint32_t> expectedB(kNumValues, 0x1234 * (kIterations - 1)); |
| |
| uint64_t bufferSize = static_cast<uint64_t>(data.size() * sizeof(uint32_t)); |
| |
| wgpu::Buffer bufferA = utils::CreateBufferFromData( |
| device, data.data(), bufferSize, |
| wgpu::BufferUsage::Storage | wgpu::BufferUsage::Uniform | wgpu::BufferUsage::CopySrc); |
| |
| wgpu::Buffer bufferB = utils::CreateBufferFromData( |
| device, data.data(), bufferSize, |
| wgpu::BufferUsage::Storage | wgpu::BufferUsage::Uniform | wgpu::BufferUsage::CopySrc); |
| |
| wgpu::ShaderModule module = utils::CreateShaderModule(device, R"( |
| struct Buf { |
| data : array<vec4<u32>, 25>; |
| }; |
| |
| [[group(0), binding(0)]] var<uniform> src : Buf; |
| [[group(0), binding(1)]] var<storage, read_write> dst : Buf; |
| |
| [[stage(compute), workgroup_size(1)]] |
| fn main([[builtin(global_invocation_id)]] GlobalInvocationID : vec3<u32>) { |
| dst.data[GlobalInvocationID.x] = src.data[GlobalInvocationID.x] + |
| vec4<u32>(0x1234u, 0x1234u, 0x1234u, 0x1234u); |
| } |
| )"); |
| |
| wgpu::ComputePipelineDescriptor pipelineDesc = {}; |
| pipelineDesc.compute.module = module; |
| pipelineDesc.compute.entryPoint = "main"; |
| wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&pipelineDesc); |
| |
| wgpu::BindGroup bindGroupA = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), |
| { |
| {0, bufferA, 0, bufferSize}, |
| {1, bufferB, 0, bufferSize}, |
| }); |
| |
| wgpu::BindGroup bindGroupB = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), |
| { |
| {0, bufferB, 0, bufferSize}, |
| {1, bufferA, 0, bufferSize}, |
| }); |
| |
| wgpu::BindGroup bindGroups[2] = {bindGroupA, bindGroupB}; |
| |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); |
| for (uint32_t i = 0, b = 0; i < kIterations; ++i, b = 1 - b) { |
| pass.SetPipeline(pipeline); |
| pass.SetBindGroup(0, bindGroups[b]); |
| pass.Dispatch(kNumValues / 4); |
| } |
| pass.EndPass(); |
| |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| EXPECT_BUFFER_U32_RANGE_EQ(expectedA.data(), bufferA, 0, kNumValues); |
| EXPECT_BUFFER_U32_RANGE_EQ(expectedB.data(), bufferB, 0, kNumValues); |
| } |
| |
| // Test that barriers for dispatches correctly combine Indirect | Storage in backends with explicit |
| // barriers. Do this by: |
| // 1 - Initializing an indirect buffer with zeros. |
| // 2 - Write ones into it with a compute shader. |
| // 3 - Use the indirect buffer in a Dispatch while also reading its data. |
| TEST_P(ComputeStorageBufferBarrierTests, IndirectBufferCorrectBarrier) { |
| wgpu::ComputePipelineDescriptor step2PipelineDesc; |
| step2PipelineDesc.compute.entryPoint = "main"; |
| step2PipelineDesc.compute.module = utils::CreateShaderModule(device, R"( |
| struct Buf { |
| data : array<u32, 3>; |
| }; |
| [[group(0), binding(0)]] var<storage, read_write> buf : Buf; |
| |
| [[stage(compute), workgroup_size(1)]] fn main() { |
| buf.data = array<u32, 3>(1u, 1u, 1u); |
| } |
| )"); |
| wgpu::ComputePipeline step2Pipeline = device.CreateComputePipeline(&step2PipelineDesc); |
| |
| wgpu::ComputePipelineDescriptor step3PipelineDesc; |
| step3PipelineDesc.compute.entryPoint = "main"; |
| step3PipelineDesc.compute.module = utils::CreateShaderModule(device, R"( |
| struct Buf { |
| data : array<u32, 3>; |
| }; |
| [[group(0), binding(0)]] var<storage, read> buf : Buf; |
| |
| struct Result { |
| data : u32; |
| }; |
| [[group(0), binding(1)]] var<storage, read_write> result : Result; |
| |
| [[stage(compute), workgroup_size(1)]] fn main() { |
| result.data = 2u; |
| if (buf.data[0] == 1u && buf.data[1] == 1u && buf.data[2] == 1u) { |
| result.data = 1u; |
| } |
| } |
| )"); |
| wgpu::ComputePipeline step3Pipeline = device.CreateComputePipeline(&step3PipelineDesc); |
| |
| // 1 - Initializing an indirect buffer with zeros. |
| wgpu::Buffer buf = utils::CreateBufferFromData<uint32_t>( |
| device, wgpu::BufferUsage::Storage | wgpu::BufferUsage::Indirect, {0u, 0u, 0u}); |
| |
| // 2 - Write ones into it with a compute shader. |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); |
| |
| wgpu::BindGroup step2Group = |
| utils::MakeBindGroup(device, step2Pipeline.GetBindGroupLayout(0), {{0, buf}}); |
| |
| pass.SetPipeline(step2Pipeline); |
| pass.SetBindGroup(0, step2Group); |
| pass.Dispatch(1); |
| |
| // 3 - Use the indirect buffer in a Dispatch while also reading its data. |
| wgpu::Buffer resultBuffer = utils::CreateBufferFromData<uint32_t>( |
| device, wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc, {0u}); |
| wgpu::BindGroup step3Group = utils::MakeBindGroup(device, step3Pipeline.GetBindGroupLayout(0), |
| {{0, buf}, {1, resultBuffer}}); |
| |
| pass.SetPipeline(step3Pipeline); |
| pass.SetBindGroup(0, step3Group); |
| pass.DispatchIndirect(buf, 0); |
| |
| pass.EndPass(); |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| EXPECT_BUFFER_U32_EQ(1u, resultBuffer, 0); |
| } |
| |
| DAWN_INSTANTIATE_TEST(ComputeStorageBufferBarrierTests, |
| D3D12Backend(), |
| MetalBackend(), |
| OpenGLBackend(), |
| OpenGLESBackend(), |
| VulkanBackend()); |