| // Copyright 2023 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 <string> |
| #include <vector> |
| |
| #include "dawn/common/Assert.h" |
| #include "dawn/common/Constants.h" |
| #include "dawn/common/Numeric.h" |
| #include "dawn/tests/unittests/validation/ValidationTest.h" |
| #include "dawn/utils/ComboRenderPipelineDescriptor.h" |
| #include "dawn/utils/WGPUHelpers.h" |
| |
| namespace dawn { |
| namespace { |
| |
| // Helper for describing bindings throughout the tests |
| struct BindingDescriptor { |
| utils::BindingInitializationHelper binding; |
| wgpu::BufferBindingType type = wgpu::BufferBindingType::Storage; |
| |
| bool hasDynamicOffset = false; |
| uint32_t dynamicOffset = 0; |
| |
| wgpu::ShaderStage visibility = wgpu::ShaderStage::Compute | wgpu::ShaderStage::Fragment; |
| }; |
| |
| using BindingDescriptorGroups = std::vector<std::vector<BindingDescriptor>>; |
| struct TestSet { |
| bool valid; |
| BindingDescriptorGroups bindingEntries; |
| }; |
| |
| // Creates a bind group with given bindings for shader text |
| std::string GenerateBindingString(const BindingDescriptorGroups& bindingsGroups) { |
| std::ostringstream ostream; |
| size_t index = 0; |
| size_t groupIndex = 0; |
| for (const auto& bindings : bindingsGroups) { |
| for (const BindingDescriptor& b : bindings) { |
| ostream << "struct S" << index << " { " |
| << "buffer : array<f32>" |
| << "}\n"; |
| ostream << "@group(" << groupIndex << ") @binding(" << b.binding.binding << ") "; |
| switch (b.type) { |
| case wgpu::BufferBindingType::Uniform: |
| ostream << "var<uniform> b" << index << " : S" << index << ";\n"; |
| break; |
| case wgpu::BufferBindingType::Storage: |
| ostream << "var<storage, read_write> b" << index << " : S" << index << ";\n"; |
| break; |
| case wgpu::BufferBindingType::ReadOnlyStorage: |
| ostream << "var<storage, read> b" << index << " : S" << index << ";\n"; |
| break; |
| default: |
| DAWN_UNREACHABLE(); |
| } |
| index++; |
| } |
| groupIndex++; |
| } |
| return ostream.str(); |
| } |
| |
| std::string GenerateReferenceString(const BindingDescriptorGroups& bindingsGroups, |
| wgpu::ShaderStage stage) { |
| std::ostringstream ostream; |
| size_t index = 0; |
| for (const auto& bindings : bindingsGroups) { |
| for (const BindingDescriptor& b : bindings) { |
| if (b.visibility & stage) { |
| ostream << "_ = b" << index << "." |
| << "buffer[0]" |
| << ";\n"; |
| } |
| index++; |
| } |
| } |
| return ostream.str(); |
| } |
| |
| // Creates a compute shader with given bindings |
| std::string CreateComputeShaderWithBindings(const BindingDescriptorGroups& bindingsGroups) { |
| return GenerateBindingString(bindingsGroups) + "@compute @workgroup_size(1,1,1) fn main() {\n" + |
| GenerateReferenceString(bindingsGroups, wgpu::ShaderStage::Compute) + "}"; |
| } |
| |
| // Creates a vertex shader with given bindings |
| std::string CreateVertexShaderWithBindings(const BindingDescriptorGroups& bindingsGroups) { |
| return GenerateBindingString(bindingsGroups) + |
| "@vertex fn main() -> @builtin(position) vec4<f32> {\n" + |
| GenerateReferenceString(bindingsGroups, wgpu::ShaderStage::Vertex) + |
| "\n return vec4<f32>(); " + "}"; |
| } |
| |
| // Creates a fragment shader with given bindings |
| std::string CreateFragmentShaderWithBindings(const BindingDescriptorGroups& bindingsGroups) { |
| return GenerateBindingString(bindingsGroups) + "@fragment fn main() {\n" + |
| GenerateReferenceString(bindingsGroups, wgpu::ShaderStage::Fragment) + "}"; |
| } |
| |
| class WritableBufferBindingAliasingValidationTests : public ValidationTest { |
| public: |
| wgpu::Buffer CreateBuffer(uint64_t bufferSize, wgpu::BufferUsage usage) { |
| wgpu::BufferDescriptor bufferDescriptor; |
| bufferDescriptor.size = bufferSize; |
| bufferDescriptor.usage = usage; |
| |
| return device.CreateBuffer(&bufferDescriptor); |
| } |
| |
| // Creates compute pipeline given a layout and shader |
| wgpu::ComputePipeline CreateComputePipeline(const std::vector<wgpu::BindGroupLayout>& layouts, |
| const std::string& shader) { |
| wgpu::ShaderModule csModule = utils::CreateShaderModule(device, shader.c_str()); |
| |
| wgpu::ComputePipelineDescriptor csDesc; |
| wgpu::PipelineLayoutDescriptor descriptor; |
| descriptor.bindGroupLayoutCount = layouts.size(); |
| descriptor.bindGroupLayouts = layouts.data(); |
| csDesc.layout = device.CreatePipelineLayout(&descriptor); |
| csDesc.compute.module = csModule; |
| csDesc.compute.entryPoint = "main"; |
| |
| return device.CreateComputePipeline(&csDesc); |
| } |
| |
| // Creates render pipeline given layouts and shaders |
| wgpu::RenderPipeline CreateRenderPipeline(const std::vector<wgpu::BindGroupLayout>& layouts, |
| const std::string& vertexShader, |
| const std::string& fragShader) { |
| wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, vertexShader.c_str()); |
| |
| wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, fragShader.c_str()); |
| |
| utils::ComboRenderPipelineDescriptor pipelineDescriptor; |
| pipelineDescriptor.vertex.module = vsModule; |
| pipelineDescriptor.cFragment.module = fsModule; |
| pipelineDescriptor.cTargets[0].writeMask = wgpu::ColorWriteMask::None; |
| pipelineDescriptor.layout = nullptr; |
| if (!layouts.empty()) { |
| wgpu::PipelineLayoutDescriptor descriptor; |
| descriptor.bindGroupLayoutCount = layouts.size(); |
| descriptor.bindGroupLayouts = layouts.data(); |
| pipelineDescriptor.layout = device.CreatePipelineLayout(&descriptor); |
| } |
| |
| return device.CreateRenderPipeline(&pipelineDescriptor); |
| } |
| |
| // Creates bind group layout with given minimum sizes for each binding |
| wgpu::BindGroupLayout CreateBindGroupLayout(const std::vector<BindingDescriptor>& bindings) { |
| std::vector<wgpu::BindGroupLayoutEntry> entries; |
| |
| for (size_t i = 0; i < bindings.size(); ++i) { |
| const BindingDescriptor& b = bindings[i]; |
| wgpu::BindGroupLayoutEntry e = {}; |
| e.binding = b.binding.binding; |
| e.visibility = b.visibility; |
| e.buffer.type = b.type; |
| e.buffer.minBindingSize = 0; |
| e.buffer.hasDynamicOffset = b.hasDynamicOffset; |
| |
| entries.push_back(e); |
| } |
| |
| wgpu::BindGroupLayoutDescriptor descriptor; |
| descriptor.entryCount = entries.size(); |
| descriptor.entries = entries.data(); |
| return device.CreateBindGroupLayout(&descriptor); |
| } |
| |
| std::vector<wgpu::BindGroup> CreateBindGroups(const std::vector<wgpu::BindGroupLayout>& layouts, |
| const BindingDescriptorGroups& bindingsGroups) { |
| std::vector<wgpu::BindGroup> bindGroups; |
| |
| DAWN_ASSERT(layouts.size() == bindingsGroups.size()); |
| for (size_t groupIdx = 0; groupIdx < layouts.size(); groupIdx++) { |
| const auto& bindings = bindingsGroups[groupIdx]; |
| |
| std::vector<wgpu::BindGroupEntry> entries; |
| entries.reserve(bindings.size()); |
| for (const auto& binding : bindings) { |
| entries.push_back(binding.binding.GetAsBinding()); |
| } |
| |
| wgpu::BindGroupDescriptor descriptor; |
| descriptor.layout = layouts[groupIdx]; |
| descriptor.entryCount = checked_cast<uint32_t>(entries.size()); |
| descriptor.entries = entries.data(); |
| |
| bindGroups.push_back(device.CreateBindGroup(&descriptor)); |
| } |
| |
| return bindGroups; |
| } |
| |
| // Runs a single dispatch with given pipeline and bind group |
| void TestDispatch(const wgpu::ComputePipeline& computePipeline, |
| const std::vector<wgpu::BindGroup>& bindGroups, |
| const TestSet& test) { |
| wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder(); |
| wgpu::ComputePassEncoder computePassEncoder = commandEncoder.BeginComputePass(); |
| computePassEncoder.SetPipeline(computePipeline); |
| |
| DAWN_ASSERT(bindGroups.size() == test.bindingEntries.size()); |
| DAWN_ASSERT(bindGroups.size() > 0); |
| for (size_t i = 0; i < bindGroups.size(); ++i) { |
| // Assuming that in our test we |
| // (1) only have buffer bindings and |
| // (2) only have buffer bindings with same hasDynamicOffset across one bindGroup, |
| // the dynamic buffer binding is always compact. |
| if (test.bindingEntries[i][0].hasDynamicOffset) { |
| // build the dynamicOffset vector |
| const auto& b = test.bindingEntries[i]; |
| std::vector<uint32_t> dynamicOffsets(b.size()); |
| for (size_t j = 0; j < b.size(); ++j) { |
| dynamicOffsets[j] = b[j].dynamicOffset; |
| } |
| |
| computePassEncoder.SetBindGroup(i, bindGroups[i], dynamicOffsets.size(), |
| dynamicOffsets.data()); |
| } else { |
| computePassEncoder.SetBindGroup(i, bindGroups[i]); |
| } |
| } |
| |
| computePassEncoder.DispatchWorkgroups(1); |
| computePassEncoder.End(); |
| if (!test.valid) { |
| ASSERT_DEVICE_ERROR(commandEncoder.Finish()); |
| } else { |
| commandEncoder.Finish(); |
| } |
| } |
| |
| // Runs a single draw with given pipeline and bind group |
| void TestDraw(const wgpu::RenderPipeline& renderPipeline, |
| const std::vector<wgpu::BindGroup>& bindGroups, |
| const TestSet& test) { |
| PlaceholderRenderPass renderPass(device); |
| |
| wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder(); |
| wgpu::RenderPassEncoder renderPassEncoder = commandEncoder.BeginRenderPass(&renderPass); |
| renderPassEncoder.SetPipeline(renderPipeline); |
| |
| DAWN_ASSERT(bindGroups.size() == test.bindingEntries.size()); |
| DAWN_ASSERT(bindGroups.size() > 0); |
| for (size_t i = 0; i < bindGroups.size(); ++i) { |
| // Assuming that in our test we |
| // (1) only have buffer bindings and |
| // (2) only have buffer bindings with same hasDynamicOffset across one bindGroup, |
| // the dynamic buffer binding is always compact. |
| if (test.bindingEntries[i][0].hasDynamicOffset) { |
| const auto& b = test.bindingEntries[i]; |
| std::vector<uint32_t> dynamicOffsets(b.size()); |
| for (size_t j = 0; j < b.size(); ++j) { |
| dynamicOffsets[j] = b[j].dynamicOffset; |
| } |
| |
| renderPassEncoder.SetBindGroup(i, bindGroups[i], dynamicOffsets.size(), |
| dynamicOffsets.data()); |
| } else { |
| renderPassEncoder.SetBindGroup(i, bindGroups[i]); |
| } |
| } |
| |
| renderPassEncoder.Draw(3); |
| renderPassEncoder.End(); |
| if (!test.valid) { |
| ASSERT_DEVICE_ERROR(commandEncoder.Finish()); |
| } else { |
| commandEncoder.Finish(); |
| } |
| } |
| |
| void TestBindings(const wgpu::ComputePipeline& computePipeline, |
| const wgpu::RenderPipeline& renderPipeline, |
| const std::vector<wgpu::BindGroupLayout>& layouts, |
| const TestSet& test) { |
| std::vector<wgpu::BindGroup> bindGroups = CreateBindGroups(layouts, test.bindingEntries); |
| |
| TestDispatch(computePipeline, bindGroups, test); |
| TestDraw(renderPipeline, bindGroups, test); |
| } |
| }; |
| |
| // Test various combinations of buffer ranges, buffer usages, bind groups, etc. validating aliasing |
| TEST_F(WritableBufferBindingAliasingValidationTests, BasicTest) { |
| wgpu::Buffer bufferStorage = |
| CreateBuffer(1024, wgpu::BufferUsage::Uniform | wgpu::BufferUsage::Storage); |
| wgpu::Buffer bufferStorage2 = |
| CreateBuffer(1024, wgpu::BufferUsage::Uniform | wgpu::BufferUsage::Storage); |
| wgpu::Buffer bufferNoStorage = CreateBuffer(1024, wgpu::BufferUsage::Uniform); |
| |
| std::vector<TestSet> testSet = { |
| // same buffer, ranges don't overlap |
| {true, |
| {{ |
| {{0, bufferStorage, 256, 16}, wgpu::BufferBindingType::Storage}, |
| {{1, bufferStorage, 0, 8}, wgpu::BufferBindingType::Storage}, |
| }}}, |
| // same buffer, ranges overlap, in same bind group, max0 >= min1 |
| {false, |
| {{ |
| {{0, bufferStorage, 256, 16}, wgpu::BufferBindingType::Storage}, |
| {{1, bufferStorage, 0, 264}, wgpu::BufferBindingType::Storage}, |
| }}}, |
| // same buffer, ranges overlap, in same bind group, max1 >= min0 |
| {false, |
| {{ |
| {{0, bufferStorage, 0, 264}, wgpu::BufferBindingType::Storage}, |
| {{1, bufferStorage, 256, 16}, wgpu::BufferBindingType::Storage}, |
| }}}, |
| // same buffer, ranges don't overlap, in different bind group |
| {true, |
| {{ |
| {{0, bufferStorage, 256, 16}, wgpu::BufferBindingType::Storage}, |
| }, |
| { |
| {{0, bufferStorage, 0, 8}, wgpu::BufferBindingType::Storage}, |
| }}}, |
| // same buffer, ranges overlap, in different bind group |
| {false, |
| {{ |
| {{0, bufferStorage, 0, 16}, wgpu::BufferBindingType::Storage}, |
| }, |
| { |
| {{0, bufferStorage, 0, 8}, wgpu::BufferBindingType::Storage}, |
| }}}, |
| // same buffer, ranges overlap, but with read-only storage buffer type |
| {true, |
| {{ |
| {{0, bufferStorage, 0, 16}, wgpu::BufferBindingType::ReadOnlyStorage}, |
| {{1, bufferStorage, 0, 8}, wgpu::BufferBindingType::ReadOnlyStorage}, |
| }}}, |
| // different buffer, ranges overlap, valid |
| {true, |
| {{ |
| {{0, bufferStorage, 0, 16}, wgpu::BufferBindingType::Storage}, |
| {{1, bufferStorage2, 0, 8}, wgpu::BufferBindingType::Storage}, |
| }}}, |
| // same buffer, ranges don't overlap, but dynamic offset creates overlap. |
| {false, |
| {{ |
| {{0, bufferStorage, 256, 16}, wgpu::BufferBindingType::Storage, true, 0}, |
| {{1, bufferStorage, 0, 8}, wgpu::BufferBindingType::Storage, true, 256}, |
| }}}, |
| // same buffer, ranges don't overlap, but one binding has dynamic offset and creates |
| // overlap. |
| {false, |
| {{ |
| {{0, bufferStorage, 256, 16}, wgpu::BufferBindingType::Storage}, |
| }, |
| { |
| {{0, bufferStorage, 0, 8}, wgpu::BufferBindingType::Storage, true, 256}, |
| }}}, |
| }; |
| |
| for (const auto& test : testSet) { |
| std::vector<wgpu::BindGroupLayout> layouts; |
| for (const std::vector<BindingDescriptor>& bindings : test.bindingEntries) { |
| layouts.push_back(CreateBindGroupLayout(bindings)); |
| } |
| |
| std::string computeShader = CreateComputeShaderWithBindings(test.bindingEntries); |
| wgpu::ComputePipeline computePipeline = CreateComputePipeline(layouts, computeShader); |
| |
| std::string vertexShader = CreateVertexShaderWithBindings(test.bindingEntries); |
| std::string fragmentShader = CreateFragmentShaderWithBindings(test.bindingEntries); |
| wgpu::RenderPipeline renderPipeline = |
| CreateRenderPipeline(layouts, vertexShader, fragmentShader); |
| |
| TestBindings(computePipeline, renderPipeline, layouts, test); |
| } |
| } |
| |
| // Test if validate bind group lazy aspect flag is set and checked properly |
| TEST_F(WritableBufferBindingAliasingValidationTests, SetBindGroupLazyAspect) { |
| wgpu::Buffer bufferStorage = |
| CreateBuffer(1024, wgpu::BufferUsage::Uniform | wgpu::BufferUsage::Storage); |
| |
| // no overlap, create valid bindGroups |
| std::vector<BindingDescriptor> bindingDescriptor0 = { |
| {{0, bufferStorage, 256, 16}, wgpu::BufferBindingType::Storage}, |
| {{1, bufferStorage, 0, 8}, wgpu::BufferBindingType::Storage}, |
| }; |
| // overlap, create invalid bindGroups |
| std::vector<BindingDescriptor> bindingDescriptor1 = { |
| {{0, bufferStorage, 0, 16}, wgpu::BufferBindingType::Storage}, |
| {{1, bufferStorage, 0, 8}, wgpu::BufferBindingType::Storage}, |
| }; |
| |
| // bindingDescriptor0 and 1 share the same bind group layout, shader and pipeline |
| wgpu::BindGroupLayout layout = CreateBindGroupLayout(bindingDescriptor0); |
| |
| std::string computeShader = CreateComputeShaderWithBindings({bindingDescriptor0}); |
| wgpu::ComputePipeline computePipeline = CreateComputePipeline({layout}, computeShader); |
| std::string vertexShader = CreateVertexShaderWithBindings({bindingDescriptor0}); |
| std::string fragmentShader = CreateFragmentShaderWithBindings({bindingDescriptor0}); |
| wgpu::RenderPipeline renderPipeline = |
| CreateRenderPipeline({layout}, vertexShader, fragmentShader); |
| |
| std::vector<wgpu::BindGroup> bindGroups = |
| CreateBindGroups({layout, layout}, {bindingDescriptor0, bindingDescriptor1}); |
| |
| // Test compute pass dispatch |
| |
| // bindGroups[0] is valid |
| { |
| wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder(); |
| wgpu::ComputePassEncoder computePassEncoder = commandEncoder.BeginComputePass(); |
| computePassEncoder.SetPipeline(computePipeline); |
| |
| computePassEncoder.SetBindGroup(0, bindGroups[0]); |
| computePassEncoder.DispatchWorkgroups(1); |
| |
| computePassEncoder.End(); |
| commandEncoder.Finish(); |
| } |
| |
| // bindGroups[1] is invalid |
| { |
| wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder(); |
| wgpu::ComputePassEncoder computePassEncoder = commandEncoder.BeginComputePass(); |
| computePassEncoder.SetPipeline(computePipeline); |
| |
| computePassEncoder.SetBindGroup(0, bindGroups[1]); |
| computePassEncoder.DispatchWorkgroups(1); |
| |
| computePassEncoder.End(); |
| ASSERT_DEVICE_ERROR(commandEncoder.Finish()); |
| } |
| |
| // setting bindGroups[1] first and then resetting to bindGroups[0] is valid |
| { |
| wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder(); |
| wgpu::ComputePassEncoder computePassEncoder = commandEncoder.BeginComputePass(); |
| computePassEncoder.SetPipeline(computePipeline); |
| |
| computePassEncoder.SetBindGroup(0, bindGroups[1]); |
| computePassEncoder.SetBindGroup(0, bindGroups[0]); |
| computePassEncoder.DispatchWorkgroups(1); |
| |
| computePassEncoder.End(); |
| commandEncoder.Finish(); |
| } |
| |
| // Test render pass draw |
| |
| PlaceholderRenderPass renderPass(device); |
| |
| // bindGroups[0] is valid |
| { |
| wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder(); |
| wgpu::RenderPassEncoder renderPassEncoder = commandEncoder.BeginRenderPass(&renderPass); |
| renderPassEncoder.SetPipeline(renderPipeline); |
| |
| renderPassEncoder.SetBindGroup(0, bindGroups[0]); |
| renderPassEncoder.Draw(3); |
| |
| renderPassEncoder.End(); |
| commandEncoder.Finish(); |
| } |
| |
| // bindGroups[1] is invalid |
| { |
| wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder(); |
| wgpu::RenderPassEncoder renderPassEncoder = commandEncoder.BeginRenderPass(&renderPass); |
| renderPassEncoder.SetPipeline(renderPipeline); |
| |
| renderPassEncoder.SetBindGroup(0, bindGroups[1]); |
| renderPassEncoder.Draw(3); |
| |
| renderPassEncoder.End(); |
| ASSERT_DEVICE_ERROR(commandEncoder.Finish()); |
| } |
| |
| // setting bindGroups[1] first and then resetting to bindGroups[0] is valid |
| { |
| wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder(); |
| wgpu::RenderPassEncoder renderPassEncoder = commandEncoder.BeginRenderPass(&renderPass); |
| renderPassEncoder.SetPipeline(renderPipeline); |
| |
| renderPassEncoder.SetBindGroup(0, bindGroups[1]); |
| renderPassEncoder.SetBindGroup(0, bindGroups[0]); |
| renderPassEncoder.Draw(3); |
| |
| renderPassEncoder.End(); |
| commandEncoder.Finish(); |
| } |
| } |
| |
| // Test if validate bind group lazy aspect flag is set and checked properly for bind group layout |
| // with dynamic offset |
| TEST_F(WritableBufferBindingAliasingValidationTests, SetBindGroupLazyAspectDynamicOffset) { |
| wgpu::Buffer bufferStorage = |
| CreateBuffer(1024, wgpu::BufferUsage::Uniform | wgpu::BufferUsage::Storage); |
| |
| // no overlap, but has dynamic offset |
| std::vector<BindingDescriptor> bindingDescriptor = { |
| {{0, bufferStorage, 256, 16}, wgpu::BufferBindingType::Storage, true}, |
| {{1, bufferStorage, 0, 8}, wgpu::BufferBindingType::Storage, true}, |
| }; |
| |
| wgpu::BindGroupLayout layout = CreateBindGroupLayout(bindingDescriptor); |
| |
| std::string computeShader = CreateComputeShaderWithBindings({bindingDescriptor}); |
| wgpu::ComputePipeline computePipeline = CreateComputePipeline({layout}, computeShader); |
| std::string vertexShader = CreateVertexShaderWithBindings({bindingDescriptor}); |
| std::string fragmentShader = CreateFragmentShaderWithBindings({bindingDescriptor}); |
| wgpu::RenderPipeline renderPipeline = |
| CreateRenderPipeline({layout}, vertexShader, fragmentShader); |
| |
| std::vector<wgpu::BindGroup> bindGroups = CreateBindGroups({layout}, {bindingDescriptor}); |
| |
| // Test compute pass dispatch |
| |
| // bindGroups[0] is valid |
| { |
| wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder(); |
| wgpu::ComputePassEncoder computePassEncoder = commandEncoder.BeginComputePass(); |
| computePassEncoder.SetPipeline(computePipeline); |
| |
| std::vector<uint32_t> dynamicOffsets = {0, 0}; |
| |
| computePassEncoder.SetBindGroup(0, bindGroups[0], dynamicOffsets.size(), |
| dynamicOffsets.data()); |
| computePassEncoder.DispatchWorkgroups(1); |
| |
| computePassEncoder.End(); |
| commandEncoder.Finish(); |
| } |
| |
| // bindGroups[0] is invalid with given dynamic offsets |
| { |
| wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder(); |
| wgpu::ComputePassEncoder computePassEncoder = commandEncoder.BeginComputePass(); |
| computePassEncoder.SetPipeline(computePipeline); |
| |
| std::vector<uint32_t> dynamicOffsets = {0, 256}; |
| |
| computePassEncoder.SetBindGroup(0, bindGroups[0], dynamicOffsets.size(), |
| dynamicOffsets.data()); |
| computePassEncoder.DispatchWorkgroups(1); |
| |
| computePassEncoder.End(); |
| ASSERT_DEVICE_ERROR(commandEncoder.Finish()); |
| } |
| |
| // setting invalid dynamic offsets first and then resetting to valid dynamic offsets should be |
| // valid |
| { |
| wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder(); |
| wgpu::ComputePassEncoder computePassEncoder = commandEncoder.BeginComputePass(); |
| computePassEncoder.SetPipeline(computePipeline); |
| |
| std::vector<uint32_t> dynamicOffsetsValid = {0, 0}; |
| std::vector<uint32_t> dynamicOffsetsInvalid = {0, 256}; |
| |
| computePassEncoder.SetBindGroup(0, bindGroups[0], dynamicOffsetsInvalid.size(), |
| dynamicOffsetsInvalid.data()); |
| computePassEncoder.SetBindGroup(0, bindGroups[0], dynamicOffsetsValid.size(), |
| dynamicOffsetsValid.data()); |
| computePassEncoder.DispatchWorkgroups(1); |
| |
| computePassEncoder.End(); |
| commandEncoder.Finish(); |
| } |
| |
| // Test render pass draw |
| |
| PlaceholderRenderPass renderPass(device); |
| |
| // bindGroups[0] is valid |
| { |
| wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder(); |
| wgpu::RenderPassEncoder renderPassEncoder = commandEncoder.BeginRenderPass(&renderPass); |
| renderPassEncoder.SetPipeline(renderPipeline); |
| |
| std::vector<uint32_t> dynamicOffsets = {0, 0}; |
| |
| renderPassEncoder.SetBindGroup(0, bindGroups[0], dynamicOffsets.size(), |
| dynamicOffsets.data()); |
| renderPassEncoder.Draw(3); |
| |
| renderPassEncoder.End(); |
| commandEncoder.Finish(); |
| } |
| |
| // bindGroups[0] is invalid with given dynamic offsets |
| { |
| wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder(); |
| wgpu::RenderPassEncoder renderPassEncoder = commandEncoder.BeginRenderPass(&renderPass); |
| renderPassEncoder.SetPipeline(renderPipeline); |
| |
| std::vector<uint32_t> dynamicOffsets = {0, 256}; |
| |
| renderPassEncoder.SetBindGroup(0, bindGroups[0], dynamicOffsets.size(), |
| dynamicOffsets.data()); |
| renderPassEncoder.Draw(3); |
| |
| renderPassEncoder.End(); |
| ASSERT_DEVICE_ERROR(commandEncoder.Finish()); |
| } |
| |
| // setting invalid dynamic offsets first and then resetting to valid dynamic offsets should be |
| // valid |
| { |
| wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder(); |
| wgpu::RenderPassEncoder renderPassEncoder = commandEncoder.BeginRenderPass(&renderPass); |
| renderPassEncoder.SetPipeline(renderPipeline); |
| |
| std::vector<uint32_t> dynamicOffsetsValid = {0, 0}; |
| std::vector<uint32_t> dynamicOffsetsInvalid = {0, 256}; |
| |
| renderPassEncoder.SetBindGroup(0, bindGroups[0], dynamicOffsetsInvalid.size(), |
| dynamicOffsetsInvalid.data()); |
| renderPassEncoder.SetBindGroup(0, bindGroups[0], dynamicOffsetsValid.size(), |
| dynamicOffsetsValid.data()); |
| renderPassEncoder.Draw(3); |
| |
| renderPassEncoder.End(); |
| commandEncoder.Finish(); |
| } |
| } |
| |
| } // anonymous namespace |
| } // namespace dawn |