| // Copyright 2025 The Dawn & Tint Authors | |
| // | |
| // Redistribution and use in source and binary forms, with or without | |
| // modification, are permitted provided that the following conditions are met: | |
| // | |
| // 1. Redistributions of source code must retain the above copyright notice, this | |
| // list of conditions and the following disclaimer. | |
| // | |
| // 2. Redistributions in binary form must reproduce the above copyright notice, | |
| // this list of conditions and the following disclaimer in the documentation | |
| // and/or other materials provided with the distribution. | |
| // | |
| // 3. Neither the name of the copyright holder nor the names of its | |
| // contributors may be used to endorse or promote products derived from | |
| // this software without specific prior written permission. | |
| // | |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
| // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
| // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
| // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| #include <utility> | |
| #include <vector> | |
| #include "src/dawn/tests/unittests/validation/ValidationTest.h" | |
| #include "src/dawn/utils/ComboRenderBundleEncoderDescriptor.h" | |
| #include "src/dawn/utils/ComboRenderPipelineDescriptor.h" | |
| #include "src/dawn/utils/ScopedIgnoreValidationErrors.h" | |
| #include "src/dawn/utils/WGPUHelpers.h" | |
| namespace dawn { | |
| namespace { | |
| class ResourceTableValidationTest : public ValidationTest { | |
| protected: | |
| std::vector<wgpu::FeatureName> GetRequiredFeatures() override { | |
| return {wgpu::FeatureName::ChromiumExperimentalSamplingResourceTable}; | |
| } | |
| wgpu::ResourceTable MakeResourceTable(uint32_t size) { | |
| wgpu::ResourceTableDescriptor desc; | |
| desc.size = size; | |
| return device.CreateResourceTable(&desc); | |
| } | |
| wgpu::ResourceTable MakeErrorResourceTable(uint32_t size = 1) { | |
| wgpu::RenderPassMaxDrawCount maxDraw; | |
| maxDraw.maxDrawCount = 1000; | |
| wgpu::ResourceTableDescriptor desc{ | |
| .nextInChain = &maxDraw, | |
| .size = size, | |
| }; | |
| wgpu::ResourceTable table; | |
| ASSERT_DEVICE_ERROR(table = device.CreateResourceTable(&desc)); | |
| return table; | |
| } | |
| wgpu::ComputePipeline MakeComputePipeline(bool defaulted, bool usesResourceTable) { | |
| auto shaderModuleUsesTable = utils::CreateShaderModule(device, R"( | |
| enable chromium_experimental_resource_table; | |
| @compute @workgroup_size(1) fn main() { | |
| _ = hasResource<texture_2d<f32>>(0); | |
| } | |
| )"); | |
| auto shaderModuleNoTable = utils::CreateShaderModule(device, R"( | |
| @compute @workgroup_size(1) fn main() {} | |
| )"); | |
| wgpu::ComputePipelineDescriptor csDesc; | |
| csDesc.compute.module = usesResourceTable ? shaderModuleUsesTable : shaderModuleNoTable; | |
| if (defaulted) { | |
| csDesc.layout = nullptr; | |
| return device.CreateComputePipeline(&csDesc); | |
| } | |
| wgpu::PipelineLayoutResourceTable plResourceTable; | |
| plResourceTable.usesResourceTable = true; | |
| wgpu::PipelineLayoutDescriptor pipelineLayoutDescriptor; | |
| pipelineLayoutDescriptor.bindGroupLayoutCount = 0; | |
| if (usesResourceTable) { | |
| pipelineLayoutDescriptor.nextInChain = &plResourceTable; | |
| } | |
| csDesc.layout = device.CreatePipelineLayout(&pipelineLayoutDescriptor); | |
| return device.CreateComputePipeline(&csDesc); | |
| } | |
| wgpu::RenderPipeline MakeRenderPipeline(bool defaulted, bool usesResourceTable) { | |
| auto shaderModuleUsesTable = utils::CreateShaderModule(device, R"( | |
| enable chromium_experimental_resource_table; | |
| @vertex fn vs() -> @builtin(position) vec4f { | |
| return vec4f(); | |
| } | |
| @fragment fn fs() -> @location(0) vec4f { | |
| _ = hasResource<texture_2d<f32>>(0); | |
| return vec4f(); | |
| } | |
| )"); | |
| auto shaderModuleNoTable = utils::CreateShaderModule(device, R"( | |
| @vertex fn vs() -> @builtin(position) vec4f { | |
| return vec4f(); | |
| } | |
| @fragment fn fs() -> @location(0) vec4f { | |
| return vec4f(); | |
| } | |
| )"); | |
| utils::ComboRenderPipelineDescriptor pDesc; | |
| pDesc.vertex.module = usesResourceTable ? shaderModuleUsesTable : shaderModuleNoTable; | |
| pDesc.cFragment.module = usesResourceTable ? shaderModuleUsesTable : shaderModuleNoTable; | |
| if (defaulted) { | |
| pDesc.layout = nullptr; | |
| return device.CreateRenderPipeline(&pDesc); | |
| } | |
| wgpu::PipelineLayoutResourceTable plResourceTable; | |
| plResourceTable.usesResourceTable = true; | |
| wgpu::PipelineLayoutDescriptor pipelineLayoutDescriptor; | |
| pipelineLayoutDescriptor.bindGroupLayoutCount = 0; | |
| if (usesResourceTable) { | |
| pipelineLayoutDescriptor.nextInChain = &plResourceTable; | |
| } | |
| pDesc.layout = device.CreatePipelineLayout(&pipelineLayoutDescriptor); | |
| return device.CreateRenderPipeline(&pDesc); | |
| } | |
| enum class Mutator : uint8_t { | |
| Update, | |
| InsertBinding, | |
| }; | |
| void TestMutator(Mutator mutator, const wgpu::BindingResource* resource, bool success) { | |
| wgpu::ResourceTable table = MakeResourceTable(1); | |
| switch (mutator) { | |
| case Mutator::Update: { | |
| wgpu::Status status; | |
| if (success) { | |
| status = table.Update(0, resource); | |
| } else { | |
| ASSERT_DEVICE_ERROR(status = table.Update(0, resource)); | |
| } | |
| EXPECT_EQ(status, wgpu::Status::Success); | |
| break; | |
| } | |
| case Mutator::InsertBinding: { | |
| uint32_t slot = wgpu::kInvalidBinding; | |
| if (success) { | |
| slot = table.InsertBinding(resource); | |
| } else { | |
| ASSERT_DEVICE_ERROR(slot = table.InsertBinding(resource)); | |
| } | |
| EXPECT_EQ(slot, 0u); | |
| break; | |
| } | |
| } | |
| } | |
| // Helper to make sure that the resource table is marked as used. Even if internally Dawn | |
| // doesn't track this, it makes tests more clearly correct. | |
| void UseResourceTableInSubmit(wgpu::ResourceTable table) { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetResourceTable(table); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| device.GetQueue().Submit(1, &commands); | |
| } | |
| }; | |
| class ResourceTableValidationTestDisabled : public ValidationTest { | |
| protected: | |
| std::vector<wgpu::FeatureName> GetRequiredFeatures() override { return {}; } | |
| }; | |
| // Test that validates that the feature must be enabled | |
| TEST_F(ResourceTableValidationTestDisabled, FeatureNotEnabled) { | |
| wgpu::ResourceTableDescriptor descriptor; | |
| descriptor.size = 1u; | |
| ASSERT_DEVICE_ERROR(device.CreateResourceTable(&descriptor)); | |
| } | |
| // Test that setting invalid size returns nullptr | |
| TEST_F(ResourceTableValidationTest, InvalidSize) { | |
| wgpu::ResourceTableDescriptor descriptor; | |
| // Size 0 is valid | |
| descriptor.size = 0u; | |
| device.CreateResourceTable(&descriptor); | |
| // Size of 1 is valid | |
| descriptor.size = 1u; | |
| device.CreateResourceTable(&descriptor); | |
| // Size of kMaxResourceTableSize is valid | |
| descriptor.size = kMaxResourceTableSize; | |
| device.CreateResourceTable(&descriptor); | |
| // Size > limits is invalid | |
| descriptor.size = kMaxResourceTableSize + 1u; | |
| wgpu::ResourceTable table = device.CreateResourceTable(&descriptor); | |
| ASSERT_EQ(table.Get(), nullptr); | |
| } | |
| // Test that setting nextInChain to anything is an error | |
| TEST_F(ResourceTableValidationTest, NextInChain) { | |
| // Control case, nextInChain = nullptr is valid. | |
| { | |
| wgpu::ResourceTableDescriptor descriptor{ | |
| .nextInChain = nullptr, | |
| .size = 3, | |
| }; | |
| device.CreateResourceTable(&descriptor); | |
| } | |
| // Control case, nextInChain = non null is invalid. | |
| { | |
| wgpu::RenderPassMaxDrawCount maxDraw; | |
| maxDraw.maxDrawCount = 1000; | |
| wgpu::ResourceTableDescriptor descriptor{ | |
| .nextInChain = &maxDraw, | |
| .size = 3, | |
| }; | |
| ASSERT_DEVICE_ERROR(device.CreateResourceTable(&descriptor)); | |
| } | |
| } | |
| // Test the Destroy call on a ResourceTable | |
| TEST_F(ResourceTableValidationTest, Destroy) { | |
| wgpu::ResourceTableDescriptor descriptor; | |
| descriptor.size = 1u; | |
| wgpu::ResourceTable resourceTable = device.CreateResourceTable(&descriptor); | |
| // Calling destroy is valid | |
| resourceTable.Destroy(); | |
| // Calling it multiple times is valid | |
| resourceTable.Destroy(); | |
| } | |
| // Control case where enabling use of a resource table with the feature enabled is valid. | |
| TEST_F(ResourceTableValidationTest, PipelineLayoutCreation_SuccessWithFeatureEnabled) { | |
| wgpu::PipelineLayoutDescriptor pipelineLayoutDescriptor; | |
| pipelineLayoutDescriptor.bindGroupLayoutCount = 0; | |
| wgpu::PipelineLayoutResourceTable resourceTable; | |
| resourceTable.usesResourceTable = true; | |
| pipelineLayoutDescriptor.nextInChain = &resourceTable; | |
| device.CreatePipelineLayout(&pipelineLayoutDescriptor); | |
| } | |
| // Error case where enabling use of a resource table with the feature disabled is an error. | |
| TEST_F(ResourceTableValidationTestDisabled, PipelineLayoutCreation_FailureWithFeatureDisabled) { | |
| wgpu::PipelineLayoutDescriptor pipelineLayoutDescriptor; | |
| pipelineLayoutDescriptor.bindGroupLayoutCount = 0; | |
| wgpu::PipelineLayoutResourceTable resourceTable; | |
| pipelineLayoutDescriptor.nextInChain = &resourceTable; | |
| // Failure case | |
| resourceTable.usesResourceTable = true; | |
| ASSERT_DEVICE_ERROR(device.CreatePipelineLayout(&pipelineLayoutDescriptor)); | |
| // Success case | |
| resourceTable.usesResourceTable = false; | |
| device.CreatePipelineLayout(&pipelineLayoutDescriptor); | |
| } | |
| // Error case where compiling a shader using the resource table with the extension disabled is an | |
| // error. | |
| TEST_F(ResourceTableValidationTestDisabled, WGSLEnableNotAllowed) { | |
| ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, R"( | |
| enable chromium_experimental_resource_table; | |
| @compute @workgroup_size(1) fn main() { | |
| _ = hasResource<texture_2d<f32>>(0); | |
| } | |
| )")); | |
| } | |
| // Test that a shader using a resource table requires a layout with one. | |
| TEST_F(ResourceTableValidationTest, PipelineCreation_ShaderRequiresLayoutWithResourceTable) { | |
| wgpu::ComputePipelineDescriptor csDesc; | |
| csDesc.compute.module = utils::CreateShaderModule(device, R"( | |
| enable chromium_experimental_resource_table; | |
| @compute @workgroup_size(1) fn main() { | |
| _ = hasResource<texture_2d<f32>>(0); | |
| } | |
| )"); | |
| wgpu::PipelineLayoutDescriptor pipelineLayoutDescriptor; | |
| pipelineLayoutDescriptor.bindGroupLayoutCount = 0; | |
| wgpu::PipelineLayoutResourceTable resourceTable; | |
| pipelineLayoutDescriptor.nextInChain = &resourceTable; | |
| // Success case, the layout uses a resource table | |
| resourceTable.usesResourceTable = true; | |
| csDesc.layout = device.CreatePipelineLayout(&pipelineLayoutDescriptor); | |
| device.CreateComputePipeline(&csDesc); | |
| // Failure case, the layout does not use a resource table | |
| resourceTable.usesResourceTable = false; | |
| csDesc.layout = device.CreatePipelineLayout(&pipelineLayoutDescriptor); | |
| ASSERT_DEVICE_ERROR(device.CreateComputePipeline(&csDesc)); | |
| } | |
| // Test that it is valid to have a layout specifying a resource table with a shader that | |
| // doesn't have one. | |
| TEST_F(ResourceTableValidationTest, PipelineCreation_ShaderNoResourceTableWithLayoutThatHasOne) { | |
| wgpu::ComputePipelineDescriptor csDesc; | |
| csDesc.compute.module = utils::CreateShaderModule(device, R"( | |
| @compute @workgroup_size(1) fn main() { | |
| } | |
| )"); | |
| wgpu::PipelineLayoutDescriptor pipelineLayoutDescriptor; | |
| pipelineLayoutDescriptor.bindGroupLayoutCount = 0; | |
| wgpu::PipelineLayoutResourceTable resourceTable; | |
| pipelineLayoutDescriptor.nextInChain = &resourceTable; | |
| resourceTable.usesResourceTable = true; | |
| csDesc.layout = device.CreatePipelineLayout(&pipelineLayoutDescriptor); | |
| device.CreateComputePipeline(&csDesc); | |
| } | |
| // Test that an defaulted pipeline layout with a shader that uses a resource table has a | |
| // PipelineLayoutResourceTable with usesResourceTable == true. | |
| TEST_F(ResourceTableValidationTest, PipelineCreation_DefaultedLayoutWithResourceTable) { | |
| wgpu::ComputePipelineDescriptor csDesc; | |
| csDesc.compute.module = utils::CreateShaderModule(device, R"( | |
| enable chromium_experimental_resource_table; | |
| @compute @workgroup_size(1) fn main() { | |
| _ = hasResource<texture_2d<f32>>(0); | |
| } | |
| )"); | |
| csDesc.layout = nullptr; // Auto | |
| device.CreateComputePipeline(&csDesc); | |
| } | |
| // Test that an defaulted pipeline layout with a multi-stage shader where only one stage uses a | |
| // resource table has a PipelineLayoutResourceTable with usesResourceTable == true. | |
| TEST_F(ResourceTableValidationTest, PipelineCreation_OneShaderDefaultedLayoutWithResourceTable) { | |
| wgpu::ComputePipelineDescriptor csDesc; | |
| csDesc.compute.module = utils::CreateShaderModule(device, R"( | |
| enable chromium_experimental_resource_table; | |
| @vertex fn vs() -> @builtin(position) vec4f { | |
| return vec4f(0, 0, 0.5, 0.5); | |
| } | |
| @compute @workgroup_size(1) fn compute_main() { | |
| _ = hasResource<texture_2d<f32>>(0); | |
| } | |
| @fragment fn fs() -> @location(0) vec4f { | |
| return vec4f(1.0, 0.0, 0.0, 1.0); | |
| } | |
| )"); | |
| csDesc.layout = nullptr; // Auto | |
| device.CreateComputePipeline(&csDesc); | |
| } | |
| // Test that a resource table uses up a BindGroupLayout slot | |
| TEST_F(ResourceTableValidationTest, PipelineLayoutCreation_ResourceTableUsesBindGroupLayoutSlot) { | |
| // Control case: max bgls, no resource table | |
| { | |
| std::vector bgLayout(kMaxBindGroups, utils::MakeBindGroupLayout(device, {})); | |
| wgpu::PipelineLayoutDescriptor pipelineLayoutDescriptor; | |
| pipelineLayoutDescriptor.bindGroupLayoutCount = bgLayout.size(); | |
| pipelineLayoutDescriptor.bindGroupLayouts = bgLayout.data(); | |
| device.CreatePipelineLayout(&pipelineLayoutDescriptor); | |
| } | |
| // Failure case: not enough room for bgls and a resource table | |
| { | |
| std::vector bgLayout(kMaxBindGroups, utils::MakeBindGroupLayout(device, {})); | |
| wgpu::PipelineLayoutDescriptor pipelineLayoutDescriptor; | |
| pipelineLayoutDescriptor.bindGroupLayoutCount = bgLayout.size(); | |
| pipelineLayoutDescriptor.bindGroupLayouts = bgLayout.data(); | |
| wgpu::PipelineLayoutResourceTable resourceTable; | |
| resourceTable.usesResourceTable = true; | |
| pipelineLayoutDescriptor.nextInChain = &resourceTable; | |
| ASSERT_DEVICE_ERROR(device.CreatePipelineLayout(&pipelineLayoutDescriptor)); | |
| } | |
| // Success case: enough room for bgls and a resource table | |
| { | |
| std::vector bgLayout(kMaxBindGroups - 1, utils::MakeBindGroupLayout(device, {})); | |
| wgpu::PipelineLayoutDescriptor pipelineLayoutDescriptor; | |
| pipelineLayoutDescriptor.bindGroupLayoutCount = bgLayout.size(); | |
| pipelineLayoutDescriptor.bindGroupLayouts = bgLayout.data(); | |
| wgpu::PipelineLayoutResourceTable resourceTable; | |
| resourceTable.usesResourceTable = true; | |
| pipelineLayoutDescriptor.nextInChain = &resourceTable; | |
| device.CreatePipelineLayout(&pipelineLayoutDescriptor); | |
| } | |
| } | |
| // Test that a resource table uses up a storage buffer binding | |
| TEST_F(ResourceTableValidationTest, PipelineLayoutCreation_ResourceTableUsesOneStorageBuffer) { | |
| const uint32_t maxStorageBuffers = deviceLimits.maxStorageBuffersPerShaderStage; | |
| std::vector<wgpu::BindGroupLayoutEntry> storageBufferEntries(maxStorageBuffers); | |
| for (size_t i = 0; i < storageBufferEntries.size(); i++) { | |
| storageBufferEntries[i].buffer.type = wgpu::BufferBindingType::ReadOnlyStorage; | |
| storageBufferEntries[i].visibility = | |
| wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment | wgpu::ShaderStage::Compute; | |
| storageBufferEntries[i].binding = i; | |
| } | |
| // Success case: exactly maxStorageBuffers are used (1 for the resource table, max - 1 for BGL | |
| // entries). | |
| { | |
| wgpu::BindGroupLayoutDescriptor bglDesc = { | |
| .entryCount = maxStorageBuffers - 1, | |
| .entries = storageBufferEntries.data(), | |
| }; | |
| wgpu::BindGroupLayout bgl = device.CreateBindGroupLayout(&bglDesc); | |
| wgpu::PipelineLayoutResourceTable resourceTable; | |
| resourceTable.usesResourceTable = true; | |
| wgpu::PipelineLayoutDescriptor plDesc = { | |
| .nextInChain = &resourceTable, | |
| .bindGroupLayoutCount = 1, | |
| .bindGroupLayouts = &bgl, | |
| }; | |
| device.CreatePipelineLayout(&plDesc); | |
| } | |
| // Error case: the resource table additional storage buffer make the layout go over the limit. | |
| { | |
| wgpu::BindGroupLayoutDescriptor bglDesc = { | |
| .entryCount = maxStorageBuffers, | |
| .entries = storageBufferEntries.data(), | |
| }; | |
| wgpu::BindGroupLayout bgl = device.CreateBindGroupLayout(&bglDesc); | |
| wgpu::PipelineLayoutResourceTable resourceTable; | |
| resourceTable.usesResourceTable = true; | |
| wgpu::PipelineLayoutDescriptor plDesc = { | |
| .nextInChain = &resourceTable, | |
| .bindGroupLayoutCount = 1, | |
| .bindGroupLayouts = &bgl, | |
| }; | |
| ASSERT_DEVICE_ERROR(device.CreatePipelineLayout(&plDesc)); | |
| } | |
| } | |
| // Test that an defaulted pipeline layout with a resource table uses up a BindGroupLayout slot | |
| TEST_F(ResourceTableValidationTest, | |
| PipelineCreation_DefaultedLayoutWithResourceTableUsesBindGroupLayoutSlot) { | |
| wgpu::ComputePipelineDescriptor csDesc; | |
| csDesc.layout = nullptr; // Auto | |
| // Control case: max bgls, no resource table | |
| { | |
| csDesc.compute.module = utils::CreateShaderModule(device, R"( | |
| enable chromium_experimental_resource_table; | |
| @group(0) @binding(0) var<uniform> a : u32; | |
| @group(1) @binding(0) var<uniform> b : u32; | |
| @group(2) @binding(0) var<uniform> c : u32; | |
| @group(3) @binding(0) var<uniform> d : u32; | |
| @compute @workgroup_size(1) fn main() { | |
| // _ = hasResource<texture_2d<f32>>(0); | |
| _ = a; | |
| _ = b; | |
| _ = c; | |
| _ = d; | |
| } | |
| )"); | |
| device.CreateComputePipeline(&csDesc); | |
| } | |
| // Failure case: not enough room for bgls and a resource table | |
| { | |
| csDesc.compute.module = utils::CreateShaderModule(device, R"( | |
| enable chromium_experimental_resource_table; | |
| @group(0) @binding(0) var<uniform> a : u32; | |
| @group(1) @binding(0) var<uniform> b : u32; | |
| @group(2) @binding(0) var<uniform> c : u32; | |
| @group(3) @binding(0) var<uniform> d : u32; | |
| @compute @workgroup_size(1) fn main() { | |
| _ = hasResource<texture_2d<f32>>(0); | |
| _ = a; | |
| _ = b; | |
| _ = c; | |
| _ = d; | |
| } | |
| )"); | |
| ASSERT_DEVICE_ERROR(device.CreateComputePipeline(&csDesc)); | |
| } | |
| // Success case: enough room for bgls and a resource table | |
| { | |
| csDesc.compute.module = utils::CreateShaderModule(device, R"( | |
| enable chromium_experimental_resource_table; | |
| @group(0) @binding(0) var<uniform> a : u32; | |
| @group(1) @binding(0) var<uniform> b : u32; | |
| @group(2) @binding(0) var<uniform> c : u32; | |
| @compute @workgroup_size(1) fn main() { | |
| _ = hasResource<texture_2d<f32>>(0); | |
| _ = a; | |
| _ = b; | |
| _ = c; | |
| } | |
| )"); | |
| device.CreateComputePipeline(&csDesc); | |
| } | |
| } | |
| // Test that GetBindGroupLayout is valid for one less BGL if resource tables are used. | |
| TEST_F(ResourceTableValidationTest, GetBindGroupLayoutValidForOneLessIndex) { | |
| // Default behavior case: GetBGL is valid until kMaxBindGroups - 1 when no resource table is | |
| // used. | |
| { | |
| wgpu::ComputePipelineDescriptor csDesc; | |
| csDesc.layout = nullptr; | |
| csDesc.compute.module = utils::CreateShaderModule(device, R"( | |
| enable chromium_experimental_resource_table; | |
| @compute @workgroup_size(1) fn main() { | |
| } | |
| )"); | |
| wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc); | |
| pipeline.GetBindGroupLayout(kMaxBindGroups - 1); | |
| ASSERT_DEVICE_ERROR(pipeline.GetBindGroupLayout(kMaxBindGroups)); | |
| } | |
| // Resource table case: GetBGL is valid until kMaxBindGroups - 2. | |
| { | |
| wgpu::ComputePipelineDescriptor csDesc; | |
| csDesc.layout = nullptr; | |
| csDesc.compute.module = utils::CreateShaderModule(device, R"( | |
| enable chromium_experimental_resource_table; | |
| @compute @workgroup_size(1) fn main() { | |
| _ = hasResource<texture_2d<f32>>(0); | |
| } | |
| )"); | |
| wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc); | |
| pipeline.GetBindGroupLayout(kMaxBindGroups - 2); | |
| ASSERT_DEVICE_ERROR(pipeline.GetBindGroupLayout(kMaxBindGroups - 1)); | |
| } | |
| } | |
| // Tests calling ComputePassEncoder::SetResourceTable | |
| TEST_F(ResourceTableValidationTest, ComputePassEncoder_SetResourceTable) { | |
| // Failure case: invalid encoder state | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.End(); | |
| pass.SetResourceTable(nullptr); | |
| ASSERT_DEVICE_ERROR(encoder.Finish()); | |
| } | |
| // Failure case: invalid resource table | |
| { | |
| ASSERT_DEVICE_ERROR(wgpu::ResourceTable resourceTable = MakeErrorResourceTable()); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetResourceTable(resourceTable); | |
| pass.End(); | |
| ASSERT_DEVICE_ERROR(encoder.Finish()); | |
| } | |
| // Success case: valid resource table | |
| { | |
| wgpu::ResourceTable resourceTable = MakeResourceTable(1); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetResourceTable(resourceTable); | |
| pass.End(); | |
| encoder.Finish(); | |
| } | |
| // Success case: null resource table | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetResourceTable(nullptr); | |
| pass.End(); | |
| encoder.Finish(); | |
| } | |
| } | |
| // Tests calling RenderPassEncoder::SetResourceTable | |
| TEST_F(ResourceTableValidationTest, RenderPassEncoder_SetResourceTable) { | |
| auto rp = utils::CreateBasicRenderPass(device, 1, 1, wgpu::TextureFormat::RGBA8Unorm); | |
| // Failure case: invalid encoder state | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| pass.End(); | |
| pass.SetResourceTable(nullptr); | |
| ASSERT_DEVICE_ERROR(encoder.Finish()); | |
| } | |
| // Failure case: invalid resource table | |
| { | |
| ASSERT_DEVICE_ERROR(wgpu::ResourceTable resourceTable = MakeErrorResourceTable()); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| pass.SetResourceTable(resourceTable); | |
| pass.End(); | |
| ASSERT_DEVICE_ERROR(encoder.Finish()); | |
| } | |
| // Success case: valid resource table | |
| { | |
| wgpu::ResourceTable resourceTable = MakeResourceTable(1); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| pass.SetResourceTable(resourceTable); | |
| pass.End(); | |
| encoder.Finish(); | |
| } | |
| // Success case: null resource table | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| pass.SetResourceTable(nullptr); | |
| pass.End(); | |
| encoder.Finish(); | |
| } | |
| } | |
| // Tests calling RenderBundleEncoder::SetResourceTable | |
| TEST_F(ResourceTableValidationTest, RenderBundleEncoder_SetResourceTable) { | |
| auto rp = utils::CreateBasicRenderPass(device, 1, 1, wgpu::TextureFormat::RGBA8Unorm); | |
| utils::ComboRenderBundleEncoderDescriptor desc = {}; | |
| desc.colorFormatCount = 1; | |
| desc.cColorFormats[0] = rp.colorFormat; | |
| // Failure case: invalid encoder state | |
| { | |
| wgpu::RenderBundleEncoder rbe = device.CreateRenderBundleEncoder(&desc); | |
| wgpu::RenderBundle renderBundle = rbe.Finish(); | |
| ASSERT_DEVICE_ERROR(rbe.SetResourceTable(nullptr)); | |
| } | |
| // Failure case: invalid resource table | |
| { | |
| ASSERT_DEVICE_ERROR(wgpu::ResourceTable resourceTable = MakeErrorResourceTable()); | |
| wgpu::RenderBundleEncoder rbe = device.CreateRenderBundleEncoder(&desc); | |
| rbe.SetResourceTable(resourceTable); | |
| ASSERT_DEVICE_ERROR(wgpu::RenderBundle renderBundle = rbe.Finish()); | |
| } | |
| // Success case: valid resource table | |
| { | |
| wgpu::ResourceTable resourceTable = MakeResourceTable(1); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| wgpu::RenderBundleEncoder rbe = device.CreateRenderBundleEncoder(&desc); | |
| rbe.SetResourceTable(resourceTable); | |
| wgpu::RenderBundle renderBundle = rbe.Finish(); | |
| pass.ExecuteBundles(1, &renderBundle); | |
| pass.End(); | |
| encoder.Finish(); | |
| } | |
| // Success case: null resource table | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| wgpu::RenderBundleEncoder rbe = device.CreateRenderBundleEncoder(&desc); | |
| rbe.SetResourceTable(nullptr); | |
| wgpu::RenderBundle renderBundle = rbe.Finish(); | |
| pass.ExecuteBundles(1, &renderBundle); | |
| pass.End(); | |
| encoder.Finish(); | |
| } | |
| } | |
| // Tests calling ComputePassEncoder::SetResourceTable when the feature is disabled | |
| TEST_F(ResourceTableValidationTestDisabled, ComputePassEncoder_SetResourceTable) { | |
| // Failure case: feature is disabled | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetResourceTable(nullptr); | |
| pass.End(); | |
| ASSERT_DEVICE_ERROR(encoder.Finish()); | |
| } | |
| // Tests calling RenderPassEncoder::SetResourceTable when the feature is disabled | |
| TEST_F(ResourceTableValidationTestDisabled, RenderPassEncoder_SetResourceTable) { | |
| // Failure case: feature is disabled | |
| auto rp = utils::CreateBasicRenderPass(device, 1, 1, wgpu::TextureFormat::RGBA8Unorm); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| pass.SetResourceTable(nullptr); | |
| pass.End(); | |
| ASSERT_DEVICE_ERROR(encoder.Finish()); | |
| } | |
| // Tests calling RenderBundleEncoder::SetResourceTable when the feature is disabled | |
| TEST_F(ResourceTableValidationTestDisabled, RenderBundleEncoder_SetResourceTable) { | |
| // Failure case: feature is disabled | |
| utils::ComboRenderBundleEncoderDescriptor desc = {}; | |
| desc.colorFormatCount = 1; | |
| desc.cColorFormats[0] = wgpu::TextureFormat::RGBA8Unorm; | |
| wgpu::RenderBundleEncoder rbe = device.CreateRenderBundleEncoder(&desc); | |
| rbe.SetResourceTable(nullptr); | |
| ASSERT_DEVICE_ERROR(rbe.Finish()); | |
| } | |
| // Tests that resource tables set on a compute pass can be used in submit | |
| TEST_F(ResourceTableValidationTest, ComputePassEncoder_CanUseInSubmit) { | |
| wgpu::ResourceTable resourceTable = MakeResourceTable(1); | |
| wgpu::ResourceTable resourceTable2 = MakeResourceTable(1); | |
| wgpu::ResourceTable resourceTable3 = MakeResourceTable(1); | |
| // Success case: resource table can be used in submit | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetResourceTable(resourceTable); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| device.GetQueue().Submit(1, &commands); | |
| } | |
| // Success case: resource table can be used in multiple passes for submit | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetResourceTable(resourceTable); | |
| pass.End(); | |
| wgpu::ComputePassEncoder pass2 = encoder.BeginComputePass(); | |
| pass2.SetResourceTable(resourceTable); | |
| pass2.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| device.GetQueue().Submit(1, &commands); | |
| } | |
| // Failure case: resource table has been destroyed | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetResourceTable(resourceTable); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| resourceTable.Destroy(); // Destroy it | |
| ASSERT_DEVICE_ERROR(device.GetQueue().Submit(1, &commands)); | |
| } | |
| // Failure case: one of multiple resource tables has been destroyed | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetResourceTable(resourceTable); | |
| pass.SetResourceTable(resourceTable2); | |
| pass.SetResourceTable(resourceTable3); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| resourceTable2.Destroy(); // Destroy one | |
| ASSERT_DEVICE_ERROR(device.GetQueue().Submit(1, &commands)); | |
| } | |
| // Failure case: one of multiple resource tables in another pass has been destroyed | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetResourceTable(resourceTable); | |
| pass.SetResourceTable(resourceTable2); | |
| pass.End(); | |
| wgpu::ComputePassEncoder pass2 = encoder.BeginComputePass(); | |
| pass2.SetResourceTable(resourceTable); | |
| pass2.SetResourceTable(resourceTable3); | |
| pass2.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| resourceTable3.Destroy(); // Destroy one | |
| ASSERT_DEVICE_ERROR(device.GetQueue().Submit(1, &commands)); | |
| } | |
| // Failure case: resource table must still be valid if set, then nullptr is set | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetResourceTable(resourceTable); | |
| pass.SetResourceTable(nullptr); // Clear it | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| resourceTable.Destroy(); // Destroy it | |
| ASSERT_DEVICE_ERROR(device.GetQueue().Submit(1, &commands)); | |
| } | |
| } | |
| // Tests that resource tables set on a render pass can be used in submit | |
| TEST_F(ResourceTableValidationTest, RenderPassEncoder_CanUseInSubmit) { | |
| wgpu::ResourceTable resourceTable = MakeResourceTable(1); | |
| wgpu::ResourceTable resourceTable2 = MakeResourceTable(1); | |
| wgpu::ResourceTable resourceTable3 = MakeResourceTable(1); | |
| auto rp = utils::CreateBasicRenderPass(device, 1, 1, wgpu::TextureFormat::RGBA8Unorm); | |
| // Success case: resource table can be used in submit | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| pass.SetResourceTable(resourceTable); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| device.GetQueue().Submit(1, &commands); | |
| } | |
| // Success case: resource table can be used in multiple passes for submit | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| pass.SetResourceTable(resourceTable); | |
| pass.End(); | |
| wgpu::RenderPassEncoder pass2 = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| pass2.SetResourceTable(resourceTable); | |
| pass2.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| device.GetQueue().Submit(1, &commands); | |
| } | |
| // Failure case: resource table has been destroyed | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| pass.SetResourceTable(resourceTable); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| resourceTable.Destroy(); // Destroy it | |
| ASSERT_DEVICE_ERROR(device.GetQueue().Submit(1, &commands)); | |
| } | |
| // Failure case: one of multiple resource tables has been destroyed | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| pass.SetResourceTable(resourceTable); | |
| pass.SetResourceTable(resourceTable2); | |
| pass.SetResourceTable(resourceTable3); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| resourceTable2.Destroy(); // Destroy one | |
| ASSERT_DEVICE_ERROR(device.GetQueue().Submit(1, &commands)); | |
| } | |
| // Failure case: one of multiple resource tables in another pass has been destroyed | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| pass.SetResourceTable(resourceTable); | |
| pass.SetResourceTable(resourceTable2); | |
| pass.End(); | |
| wgpu::RenderPassEncoder pass2 = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| pass2.SetResourceTable(resourceTable); | |
| pass2.SetResourceTable(resourceTable3); | |
| pass2.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| resourceTable3.Destroy(); // Destroy one | |
| ASSERT_DEVICE_ERROR(device.GetQueue().Submit(1, &commands)); | |
| } | |
| // Failure case: resource table must still be valid if set, then nullptr is set | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| pass.SetResourceTable(resourceTable); | |
| pass.SetResourceTable(nullptr); // Clear it | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| resourceTable.Destroy(); // Destroy it | |
| ASSERT_DEVICE_ERROR(device.GetQueue().Submit(1, &commands)); | |
| } | |
| } | |
| // Tests that resource tables set on a render bundle can be used in submit | |
| TEST_F(ResourceTableValidationTest, RenderBundleEncoder_CanUseInSubmit) { | |
| wgpu::ResourceTable resourceTable = MakeResourceTable(1); | |
| wgpu::ResourceTable resourceTable2 = MakeResourceTable(1); | |
| wgpu::ResourceTable resourceTable3 = MakeResourceTable(1); | |
| auto rp = utils::CreateBasicRenderPass(device, 1, 1, wgpu::TextureFormat::RGBA8Unorm); | |
| utils::ComboRenderBundleEncoderDescriptor desc = {}; | |
| desc.colorFormatCount = 1; | |
| desc.cColorFormats[0] = rp.colorFormat; | |
| // Success case: resource table can be used in submit | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| wgpu::RenderBundleEncoder rbe = device.CreateRenderBundleEncoder(&desc); | |
| rbe.SetResourceTable(resourceTable); | |
| wgpu::RenderBundle renderBundle = rbe.Finish(); | |
| pass.ExecuteBundles(1, &renderBundle); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| device.GetQueue().Submit(1, &commands); | |
| } | |
| // Success case: resource table can be used in multiple passes for submit | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| wgpu::RenderBundleEncoder rbe = device.CreateRenderBundleEncoder(&desc); | |
| rbe.SetResourceTable(resourceTable); | |
| wgpu::RenderBundle renderBundle = rbe.Finish(); | |
| pass.ExecuteBundles(1, &renderBundle); | |
| pass.End(); | |
| wgpu::RenderPassEncoder pass2 = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| wgpu::RenderBundleEncoder rbe2 = device.CreateRenderBundleEncoder(&desc); | |
| rbe2.SetResourceTable(resourceTable); | |
| wgpu::RenderBundle renderBundle2 = rbe2.Finish(); | |
| pass2.ExecuteBundles(1, &renderBundle2); | |
| pass2.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| device.GetQueue().Submit(1, &commands); | |
| } | |
| // Success case: resource table can be used in multiple rbes in one pass for submit | |
| { | |
| std::vector<wgpu::RenderBundle> renderBundles; | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| wgpu::RenderBundleEncoder rbe = device.CreateRenderBundleEncoder(&desc); | |
| rbe.SetResourceTable(resourceTable); | |
| renderBundles.push_back(rbe.Finish()); | |
| wgpu::RenderBundleEncoder rbe2 = device.CreateRenderBundleEncoder(&desc); | |
| rbe2.SetResourceTable(resourceTable); | |
| renderBundles.push_back(rbe2.Finish()); | |
| pass.ExecuteBundles(renderBundles.size(), renderBundles.data()); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| device.GetQueue().Submit(1, &commands); | |
| } | |
| // Failure case: resource table has been destroyed | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| wgpu::RenderBundleEncoder rbe = device.CreateRenderBundleEncoder(&desc); | |
| rbe.SetResourceTable(resourceTable); | |
| wgpu::RenderBundle renderBundle = rbe.Finish(); | |
| pass.ExecuteBundles(1, &renderBundle); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| resourceTable.Destroy(); // Destroy it | |
| ASSERT_DEVICE_ERROR(device.GetQueue().Submit(1, &commands)); | |
| } | |
| // Failure case: one of multiple resource tables has been destroyed | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| wgpu::RenderBundleEncoder rbe = device.CreateRenderBundleEncoder(&desc); | |
| rbe.SetResourceTable(resourceTable); | |
| rbe.SetResourceTable(resourceTable2); | |
| rbe.SetResourceTable(resourceTable3); | |
| wgpu::RenderBundle renderBundle = rbe.Finish(); | |
| pass.ExecuteBundles(1, &renderBundle); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| resourceTable2.Destroy(); // Destroy one | |
| ASSERT_DEVICE_ERROR(device.GetQueue().Submit(1, &commands)); | |
| } | |
| // Failure case: one of multiple resource tables in another pass has been destroyed | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| wgpu::RenderBundleEncoder rbe = device.CreateRenderBundleEncoder(&desc); | |
| rbe.SetResourceTable(resourceTable); | |
| rbe.SetResourceTable(resourceTable2); | |
| wgpu::RenderBundle renderBundle = rbe.Finish(); | |
| pass.ExecuteBundles(1, &renderBundle); | |
| pass.End(); | |
| wgpu::RenderPassEncoder pass2 = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| wgpu::RenderBundleEncoder rbe2 = device.CreateRenderBundleEncoder(&desc); | |
| rbe2.SetResourceTable(resourceTable); | |
| rbe2.SetResourceTable(resourceTable3); | |
| wgpu::RenderBundle renderBundle2 = rbe2.Finish(); | |
| pass2.ExecuteBundles(1, &renderBundle2); | |
| pass2.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| resourceTable3.Destroy(); // Destroy one | |
| ASSERT_DEVICE_ERROR(device.GetQueue().Submit(1, &commands)); | |
| } | |
| // Failure case: resource table must still be valid if set, then nullptr is set | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| wgpu::RenderBundleEncoder rbe = device.CreateRenderBundleEncoder(&desc); | |
| rbe.SetResourceTable(resourceTable); | |
| rbe.SetResourceTable(nullptr); // Clear it | |
| wgpu::RenderBundle renderBundle = rbe.Finish(); | |
| pass.ExecuteBundles(1, &renderBundle); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| resourceTable.Destroy(); // Destroy it | |
| ASSERT_DEVICE_ERROR(device.GetQueue().Submit(1, &commands)); | |
| } | |
| } | |
| // Tests that the resource table can be used in dispatch | |
| TEST_F(ResourceTableValidationTest, Submit_DispatchRequiresResourceTable) { | |
| for (bool defaulted : {true, false}) { | |
| wgpu::ComputePipeline pipelineUsesTable = MakeComputePipeline(defaulted, true); | |
| wgpu::ComputePipeline pipelineNoTable = MakeComputePipeline(defaulted, false); | |
| wgpu::ResourceTable resourceTable = MakeResourceTable(1); | |
| wgpu::ResourceTable resourceTable2 = MakeResourceTable(1); | |
| // Success case: `usesResourceTable` is enabled, and one has been set on the pass | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetResourceTable(resourceTable); | |
| pass.SetPipeline(pipelineUsesTable); | |
| pass.DispatchWorkgroups(1); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| device.GetQueue().Submit(1, &commands); | |
| } | |
| // Failure case: `usesResourceTable` is enabled, but none has been set on the pass | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetPipeline(pipelineUsesTable); | |
| pass.DispatchWorkgroups(1); | |
| pass.End(); | |
| ASSERT_DEVICE_ERROR(wgpu::CommandBuffer commands = encoder.Finish()); | |
| } | |
| // Failure case: `usesResourceTable` is enabled, one then nullptr set on the pass | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetResourceTable(resourceTable); // Set a valid one | |
| pass.SetResourceTable(nullptr); // Then clear it | |
| pass.SetPipeline(pipelineUsesTable); | |
| pass.DispatchWorkgroups(1); | |
| pass.End(); | |
| ASSERT_DEVICE_ERROR(wgpu::CommandBuffer commands = encoder.Finish()); | |
| } | |
| // Success case: `usesResourceTable` is enabled, one then nullptr then another set on the | |
| // pass | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetResourceTable(resourceTable); // Set a valid one | |
| pass.SetResourceTable(nullptr); // Then clear it | |
| pass.SetResourceTable(resourceTable2); // Then set another valid one | |
| pass.SetPipeline(pipelineUsesTable); | |
| pass.DispatchWorkgroups(1); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| device.GetQueue().Submit(1, &commands); | |
| } | |
| // Success case: `usesResourceTable` enabled only on pass 2 with table set | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetPipeline(pipelineNoTable); | |
| pass.DispatchWorkgroups(1); | |
| pass.End(); | |
| wgpu::ComputePassEncoder pass2 = encoder.BeginComputePass(); | |
| pass2.SetResourceTable(resourceTable); | |
| pass2.SetPipeline(pipelineUsesTable); | |
| pass2.DispatchWorkgroups(1); | |
| pass2.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| } | |
| // Failure case: `usesResourceTable` enabled only on pass 2 but no table set | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetPipeline(pipelineNoTable); | |
| pass.DispatchWorkgroups(1); | |
| pass.End(); | |
| wgpu::ComputePassEncoder pass2 = encoder.BeginComputePass(); | |
| pass2.SetPipeline(pipelineUsesTable); | |
| pass2.DispatchWorkgroups(1); | |
| pass2.End(); | |
| ASSERT_DEVICE_ERROR(wgpu::CommandBuffer commands = encoder.Finish()); | |
| } | |
| } | |
| } | |
| // Tests that the resource table can be used in draw | |
| TEST_F(ResourceTableValidationTest, Submit_DrawRequiresResourceTable) { | |
| for (bool defaulted : {true, false}) { | |
| wgpu::RenderPipeline pipelineUsesTable = MakeRenderPipeline(defaulted, true); | |
| wgpu::RenderPipeline pipelineNoTable = MakeRenderPipeline(defaulted, false); | |
| wgpu::ResourceTable resourceTable = MakeResourceTable(1); | |
| wgpu::ResourceTable resourceTable2 = MakeResourceTable(1); | |
| auto rp = utils::CreateBasicRenderPass(device, 1, 1, wgpu::TextureFormat::RGBA8Unorm); | |
| // Success case: `usesResourceTable` is enabled, and one has been set on the pass | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| pass.SetResourceTable(resourceTable); | |
| pass.SetPipeline(pipelineUsesTable); | |
| pass.Draw(1); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| device.GetQueue().Submit(1, &commands); | |
| } | |
| // Failure case: `usesResourceTable` is enabled, but none has been set on the pass | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| pass.SetPipeline(pipelineUsesTable); | |
| pass.Draw(1); | |
| pass.End(); | |
| ASSERT_DEVICE_ERROR(wgpu::CommandBuffer commands = encoder.Finish()); | |
| } | |
| // Failure case: `usesResourceTable` is enabled, one then nullptr set on the pass | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| pass.SetResourceTable(resourceTable); // Set a valid one | |
| pass.SetResourceTable(nullptr); // Then clear it | |
| pass.SetPipeline(pipelineUsesTable); | |
| pass.Draw(1); | |
| pass.End(); | |
| ASSERT_DEVICE_ERROR(wgpu::CommandBuffer commands = encoder.Finish()); | |
| } | |
| // Success case: `usesResourceTable` is enabled, one then nullptr then another set on the | |
| // pass | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| pass.SetResourceTable(resourceTable); | |
| pass.SetResourceTable(nullptr); // Then clear it | |
| pass.SetResourceTable(resourceTable2); // Then set another valid one | |
| pass.SetPipeline(pipelineUsesTable); | |
| pass.Draw(1); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| device.GetQueue().Submit(1, &commands); | |
| } | |
| // Success case: single pass toggles between pipelines that do not use and use a table | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| pass.SetPipeline(pipelineNoTable); | |
| pass.Draw(1); | |
| pass.SetPipeline(pipelineUsesTable); | |
| pass.SetResourceTable(resourceTable); | |
| pass.Draw(1); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| device.GetQueue().Submit(1, &commands); | |
| } | |
| // Failure case: single pass toggles between pipelines that do not use and use a table, but | |
| // does not set a table when required | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| pass.SetPipeline(pipelineNoTable); | |
| pass.Draw(1); | |
| pass.SetPipeline(pipelineUsesTable); | |
| pass.Draw(1); | |
| pass.End(); | |
| ASSERT_DEVICE_ERROR(wgpu::CommandBuffer commands = encoder.Finish()); | |
| } | |
| // Success case: `usesResourceTable` enabled only on pass 2 with table set | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| pass.SetPipeline(pipelineNoTable); | |
| pass.Draw(1); | |
| pass.End(); | |
| wgpu::RenderPassEncoder pass2 = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| pass2.SetResourceTable(resourceTable); | |
| pass2.SetPipeline(pipelineUsesTable); | |
| pass2.Draw(1); | |
| pass2.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| device.GetQueue().Submit(1, &commands); | |
| } | |
| // Failure case: `usesResourceTable` enabled only on pass 2 but no table set | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| pass.SetPipeline(pipelineNoTable); | |
| pass.Draw(1); | |
| pass.End(); | |
| wgpu::RenderPassEncoder pass2 = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| pass2.SetPipeline(pipelineUsesTable); | |
| pass2.Draw(1); | |
| pass2.End(); | |
| ASSERT_DEVICE_ERROR(wgpu::CommandBuffer commands = encoder.Finish()); | |
| } | |
| } | |
| } | |
| // Tests that the resource table in RenderBundle can be used in draw | |
| TEST_F(ResourceTableValidationTest, Submit_RenderBundleDrawRequiresResourceTable) { | |
| for (bool defaulted : {true, false}) { | |
| wgpu::RenderPipeline pipelineUsesTable = MakeRenderPipeline(defaulted, true); | |
| wgpu::RenderPipeline pipelineNoTable = MakeRenderPipeline(defaulted, false); | |
| wgpu::ResourceTable resourceTable = MakeResourceTable(1); | |
| wgpu::ResourceTable resourceTable2 = MakeResourceTable(1); | |
| auto rp = utils::CreateBasicRenderPass(device, 1, 1, wgpu::TextureFormat::RGBA8Unorm); | |
| utils::ComboRenderBundleEncoderDescriptor desc = {}; | |
| desc.colorFormatCount = 1; | |
| desc.cColorFormats[0] = rp.colorFormat; | |
| // Success case: `usesResourceTable` is enabled, and one has been set on the encoder | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| wgpu::RenderBundleEncoder rbe = device.CreateRenderBundleEncoder(&desc); | |
| rbe.SetResourceTable(resourceTable); | |
| rbe.SetPipeline(pipelineUsesTable); | |
| rbe.Draw(1); | |
| wgpu::RenderBundle renderBundle = rbe.Finish(); | |
| pass.ExecuteBundles(1, &renderBundle); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| device.GetQueue().Submit(1, &commands); | |
| } | |
| // Failure case: `usesResourceTable` is enabled, but none has been set on the encoder | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| wgpu::RenderBundleEncoder rbe = device.CreateRenderBundleEncoder(&desc); | |
| rbe.SetPipeline(pipelineUsesTable); | |
| rbe.Draw(1); | |
| ASSERT_DEVICE_ERROR(wgpu::RenderBundle renderBundle = rbe.Finish()); | |
| } | |
| // Failure case: `usesResourceTable` is enabled, one then nullptr set on the encoder | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| wgpu::RenderBundleEncoder rbe = device.CreateRenderBundleEncoder(&desc); | |
| rbe.SetResourceTable(resourceTable); // Set a valid one | |
| rbe.SetResourceTable(nullptr); // Then clear it | |
| rbe.SetPipeline(pipelineUsesTable); | |
| rbe.Draw(1); | |
| ASSERT_DEVICE_ERROR(wgpu::RenderBundle renderBundle = rbe.Finish()); | |
| } | |
| // Success case: `usesResourceTable` is enabled, one then nullptr then another set on the | |
| // encoder | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| wgpu::RenderBundleEncoder rbe = device.CreateRenderBundleEncoder(&desc); | |
| rbe.SetResourceTable(resourceTable); // Set a valid one | |
| rbe.SetResourceTable(nullptr); // Then clear it | |
| rbe.SetResourceTable(resourceTable2); // Then set another valid one | |
| rbe.SetPipeline(pipelineUsesTable); | |
| rbe.Draw(1); | |
| wgpu::RenderBundle renderBundle = rbe.Finish(); | |
| pass.ExecuteBundles(1, &renderBundle); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| device.GetQueue().Submit(1, &commands); | |
| } | |
| // Success case: single rbe toggles between pipelines that do not use and use a table | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| wgpu::RenderBundleEncoder rbe = device.CreateRenderBundleEncoder(&desc); | |
| rbe.SetPipeline(pipelineUsesTable); | |
| rbe.SetResourceTable(resourceTable); | |
| rbe.Draw(1); | |
| rbe.SetPipeline(pipelineNoTable); | |
| rbe.Draw(1); | |
| wgpu::RenderBundle renderBundle = rbe.Finish(); | |
| pass.ExecuteBundles(1, &renderBundle); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| device.GetQueue().Submit(1, &commands); | |
| } | |
| // Failure case: single rbe toggles between pipelines that do not use and use a table, but | |
| // does not set a table when required | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| wgpu::RenderBundleEncoder rbe = device.CreateRenderBundleEncoder(&desc); | |
| rbe.SetPipeline(pipelineUsesTable); | |
| rbe.Draw(1); | |
| rbe.SetPipeline(pipelineNoTable); | |
| rbe.Draw(1); | |
| ASSERT_DEVICE_ERROR(wgpu::RenderBundle renderBundle = rbe.Finish()); | |
| } | |
| // Success case: mix pass and rbe | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| pass.SetPipeline(pipelineUsesTable); | |
| pass.SetResourceTable(resourceTable); | |
| pass.SetPipeline(pipelineNoTable); | |
| pass.Draw(1); | |
| wgpu::RenderBundleEncoder rbe = device.CreateRenderBundleEncoder(&desc); | |
| rbe.SetPipeline(pipelineUsesTable); | |
| rbe.SetResourceTable(resourceTable); | |
| rbe.Draw(1); | |
| rbe.SetPipeline(pipelineNoTable); | |
| rbe.Draw(1); | |
| wgpu::RenderBundle renderBundle = rbe.Finish(); | |
| pass.ExecuteBundles(1, &renderBundle); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| device.GetQueue().Submit(1, &commands); | |
| } | |
| } | |
| } | |
| // Test that render bundles do not persist resource tables onto a render pass | |
| TEST_F(ResourceTableValidationTest, RenderBundleDoesNotPersistResourceTable) { | |
| auto rp = utils::CreateBasicRenderPass(device, 1, 1, wgpu::TextureFormat::RGBA8Unorm); | |
| utils::ComboRenderBundleEncoderDescriptor desc = {}; | |
| desc.colorFormatCount = 1; | |
| desc.cColorFormats[0] = rp.colorFormat; | |
| // Success case: pipeline uses table, one is set on the bundle, and one on the pass | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| // Create render bundle with a resource table | |
| wgpu::RenderBundleEncoder rbe = device.CreateRenderBundleEncoder(&desc); | |
| wgpu::ResourceTable resourceTable = MakeResourceTable(1); | |
| rbe.SetResourceTable(resourceTable); | |
| wgpu::RenderBundle renderBundle = rbe.Finish(); | |
| auto pipelineUsesTable = MakeRenderPipeline(true, true); | |
| pass.ExecuteBundles(1, &renderBundle); | |
| pass.SetPipeline(pipelineUsesTable); | |
| pass.SetResourceTable(resourceTable); // Set table on the pass | |
| pass.Draw(1); | |
| pass.End(); | |
| encoder.Finish(); | |
| } | |
| // Failure case: pipeline uses table, one is set on the bundle, but not on the pass | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| // Create render bundle with a resource table | |
| wgpu::RenderBundleEncoder rbe = device.CreateRenderBundleEncoder(&desc); | |
| wgpu::ResourceTable resourceTable = MakeResourceTable(1); | |
| rbe.SetResourceTable(resourceTable); | |
| wgpu::RenderBundle renderBundle = rbe.Finish(); | |
| auto pipelineUsesTable = MakeRenderPipeline(true, true); | |
| pass.ExecuteBundles(1, &renderBundle); | |
| pass.SetPipeline(pipelineUsesTable); | |
| pass.Draw(1); | |
| pass.End(); | |
| ASSERT_DEVICE_ERROR(encoder.Finish()); | |
| } | |
| } | |
| // Test that render bundles do not inherit resource tables from a render pass | |
| TEST_F(ResourceTableValidationTest, RenderBundleDoesNotInheritResourceTable) { | |
| auto rp = utils::CreateBasicRenderPass(device, 1, 1, wgpu::TextureFormat::RGBA8Unorm); | |
| utils::ComboRenderBundleEncoderDescriptor desc = {}; | |
| desc.colorFormatCount = 1; | |
| desc.cColorFormats[0] = rp.colorFormat; | |
| // Success case: pipeline uses table, one is set on the bundle, and one on the pass | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| wgpu::ResourceTable resourceTable = MakeResourceTable(1); | |
| // Create render bundle with a resource table | |
| wgpu::RenderBundleEncoder rbe = device.CreateRenderBundleEncoder(&desc); | |
| rbe.SetResourceTable(resourceTable); // Set table on the bundle | |
| wgpu::RenderBundle renderBundle = rbe.Finish(); | |
| auto pipelineUsesTable = MakeRenderPipeline(true, true); | |
| pass.ExecuteBundles(1, &renderBundle); | |
| pass.SetPipeline(pipelineUsesTable); | |
| pass.SetResourceTable(resourceTable); // Set table on the pass | |
| pass.Draw(1); | |
| pass.End(); | |
| encoder.Finish(); | |
| } | |
| // Failure case: pipeline uses table, one is set on the pass, but not on the bundle | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| wgpu::ResourceTable resourceTable = MakeResourceTable(1); | |
| // Create render bundle without a resource table | |
| wgpu::RenderBundleEncoder rbe = device.CreateRenderBundleEncoder(&desc); | |
| wgpu::RenderBundle renderBundle = rbe.Finish(); | |
| auto pipelineUsesTable = MakeRenderPipeline(true, true); | |
| pass.SetPipeline(pipelineUsesTable); | |
| pass.SetResourceTable(resourceTable); // Set table on the pass | |
| pass.ExecuteBundles(1, &renderBundle); | |
| pass.Draw(1); | |
| pass.End(); | |
| ASSERT_DEVICE_ERROR(encoder.Finish()); | |
| } | |
| } | |
| // Checks that only texture views and samplers are allowed as resources in mutators for | |
| // SamplingResourceTable. | |
| TEST_F(ResourceTableValidationTest, MutatorBindingKindValidation) { | |
| // Create the texture to put in the table. | |
| wgpu::TextureDescriptor tDesc = { | |
| .usage = wgpu::TextureUsage::TextureBinding, | |
| .size = {1, 1}, | |
| .format = wgpu::TextureFormat::RGBA8Unorm, | |
| }; | |
| wgpu::Texture texture = device.CreateTexture(&tDesc); | |
| // Create the buffer to put in the table. | |
| wgpu::BufferDescriptor bDesc{ | |
| .usage = wgpu::BufferUsage::Storage, | |
| .size = 4, | |
| }; | |
| wgpu::Buffer buffer = device.CreateBuffer(&bDesc); | |
| // Create the sampler to put in the table. | |
| wgpu::Sampler sampler = device.CreateSampler(); | |
| for (auto mutator : {Mutator::Update, Mutator::InsertBinding}) { | |
| // Success case: a single texture is valid. | |
| { | |
| wgpu::BindingResource resource = {.textureView = texture.CreateView()}; | |
| TestMutator(mutator, &resource, true); | |
| } | |
| // Success case: a single sampler is valid | |
| { | |
| wgpu::BindingResource resource = {.sampler = sampler}; | |
| TestMutator(mutator, &resource, true); | |
| } | |
| // Error case: a buffer is an error. | |
| { | |
| wgpu::BindingResource resource = {.buffer = buffer}; | |
| TestMutator(mutator, &resource, false); | |
| } | |
| // Error case: both a sampler and a texture at the same time is an error. | |
| { | |
| wgpu::BindingResource resource = {.sampler = sampler, | |
| .textureView = texture.CreateView()}; | |
| TestMutator(mutator, &resource, false); | |
| } | |
| } | |
| } | |
| // Tests that resources added/inserted on a table must be valid objects | |
| TEST_F(ResourceTableValidationTest, MutatorResourceMustBeValid) { | |
| // Create the texture to put in the table. | |
| wgpu::TextureDescriptor tDesc = { | |
| .usage = wgpu::TextureUsage::TextureBinding, | |
| .size = {1, 1}, | |
| .format = wgpu::TextureFormat::RGBA8Unorm, | |
| }; | |
| wgpu::Texture textureGood = device.CreateTexture(&tDesc); | |
| tDesc.size = {0, 0}; | |
| ASSERT_DEVICE_ERROR(wgpu::Texture textureBad = device.CreateTexture(&tDesc)); | |
| // Create the sampler to put in the table. | |
| wgpu::SamplerDescriptor sDesc = {}; | |
| wgpu::Sampler samplerGood = device.CreateSampler(&sDesc); | |
| sDesc.lodMinClamp = -1; | |
| ASSERT_DEVICE_ERROR(wgpu::Sampler samplerBad = device.CreateSampler(&sDesc)); | |
| for (auto mutator : {Mutator::Update, Mutator::InsertBinding}) { | |
| // Success case: valid texture | |
| { | |
| wgpu::BindingResource resource = {.textureView = textureGood.CreateView()}; | |
| TestMutator(mutator, &resource, true); | |
| } | |
| // Error case: invalid texture | |
| { | |
| ASSERT_DEVICE_ERROR( | |
| wgpu::BindingResource resource = {.textureView = textureBad.CreateView()}); | |
| TestMutator(mutator, &resource, false); | |
| } | |
| // Success case: valid sampler | |
| { | |
| wgpu::BindingResource resource = {.sampler = samplerGood}; | |
| TestMutator(mutator, &resource, true); | |
| } | |
| // Error case: invalid sampler | |
| { | |
| wgpu::BindingResource resource = {.sampler = samplerBad}; | |
| TestMutator(mutator, &resource, false); | |
| } | |
| } | |
| } | |
| // Tests that adding and removing different types of resources works | |
| TEST_F(ResourceTableValidationTest, MutatorsMultipleResources) { | |
| // Create the texture to put in the table. | |
| wgpu::TextureDescriptor tDesc{ | |
| .usage = wgpu::TextureUsage::TextureBinding, | |
| .size = {1, 1}, | |
| .format = wgpu::TextureFormat::RGBA8Unorm, | |
| }; | |
| wgpu::BindingResource textureResource = {.textureView = | |
| device.CreateTexture(&tDesc).CreateView()}; | |
| wgpu::BindingResource samplerResource = {.sampler = device.CreateSampler()}; | |
| auto table = MakeResourceTable(42); | |
| EXPECT_EQ(wgpu::Status::Success, table.Update(0, &textureResource)); | |
| EXPECT_EQ(wgpu::Status::Success, table.Update(1, &samplerResource)); | |
| EXPECT_EQ(2u, table.InsertBinding(&textureResource)); | |
| EXPECT_EQ(3u, table.InsertBinding(&samplerResource)); | |
| EXPECT_EQ(wgpu::Status::Success, table.RemoveBinding(0)); | |
| EXPECT_EQ(wgpu::Status::Success, table.RemoveBinding(1)); | |
| EXPECT_EQ(wgpu::Status::Success, table.RemoveBinding(2)); | |
| EXPECT_EQ(wgpu::Status::Success, table.RemoveBinding(3)); | |
| } | |
| // Check that the view must have only the TextureBinding usage for SamplingResourceTable. | |
| // TODO(https://issues.chromium.org/473444515): Support storage textures in FullResourceTable | |
| // TODO(https://issues.chromium.org/382544164): Support texel buffers in FullResourceTable | |
| TEST_F(ResourceTableValidationTest, MutatorTextureViewMustBeOnlyTextureBinding) { | |
| // Create the texture to put in the table. | |
| wgpu::TextureDescriptor tDesc{ | |
| .usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding | | |
| wgpu::TextureUsage::StorageBinding, | |
| .size = {1, 1}, | |
| .format = wgpu::TextureFormat::R32Uint, | |
| }; | |
| wgpu::Texture tex = device.CreateTexture(&tDesc); | |
| for (auto mutator : {Mutator::Update, Mutator::InsertBinding}) { | |
| // Control case: limiting the usage to TextureBinding is valid. | |
| { | |
| wgpu::TextureViewDescriptor vDesc{ | |
| .usage = wgpu::TextureUsage::TextureBinding, | |
| }; | |
| wgpu::BindingResource resource = {.textureView = tex.CreateView(&vDesc)}; | |
| TestMutator(mutator, &resource, true); | |
| } | |
| // Error case: having unrelated usages in the view is not allowed. RenderAttachment case. | |
| { | |
| wgpu::TextureViewDescriptor vDesc{ | |
| .usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment, | |
| }; | |
| wgpu::BindingResource resource = {.textureView = tex.CreateView(&vDesc)}; | |
| TestMutator(mutator, &resource, false); | |
| } | |
| // Error case: having unrelated usages in the view is not allowed. StorageBinding case. | |
| { | |
| wgpu::TextureViewDescriptor vDesc{ | |
| .usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::StorageBinding, | |
| }; | |
| wgpu::BindingResource resource = {.textureView = tex.CreateView(&vDesc)}; | |
| TestMutator(mutator, &resource, false); | |
| } | |
| // Error case: the defaulted texture usages don't contain TextureBinding. | |
| { | |
| wgpu::TextureDescriptor tDesc2 = tDesc; | |
| tDesc2.usage = wgpu::TextureUsage::CopyDst; | |
| wgpu::BindingResource resource = {.textureView = | |
| device.CreateTexture(&tDesc2).CreateView()}; | |
| TestMutator(mutator, &resource, false); | |
| } | |
| } | |
| } | |
| // Check that the texture view must have a single aspect for mutators. | |
| TEST_F(ResourceTableValidationTest, MutatorTextureViewMustBeSingleAspect) { | |
| // Create the texture to put in the table. | |
| wgpu::TextureDescriptor tDesc{ | |
| .usage = wgpu::TextureUsage::TextureBinding, | |
| .size = {1, 1}, | |
| .format = wgpu::TextureFormat::Depth24PlusStencil8, | |
| }; | |
| wgpu::Texture tex = device.CreateTexture(&tDesc); | |
| for (auto mutator : {Mutator::Update, Mutator::InsertBinding}) { | |
| // Success case, only the depth aspect is selected. | |
| { | |
| wgpu::TextureViewDescriptor vDesc{ | |
| .aspect = wgpu::TextureAspect::DepthOnly, | |
| }; | |
| wgpu::BindingResource resource = {.textureView = tex.CreateView(&vDesc)}; | |
| TestMutator(mutator, &resource, true); | |
| } | |
| // Success case, only the stencil aspect is selected. | |
| { | |
| wgpu::TextureViewDescriptor vDesc{ | |
| .aspect = wgpu::TextureAspect::StencilOnly, | |
| }; | |
| wgpu::BindingResource resource = {.textureView = tex.CreateView(&vDesc)}; | |
| TestMutator(mutator, &resource, true); | |
| } | |
| // Error case: both aspects are selected. | |
| { | |
| wgpu::BindingResource resource = {.textureView = tex.CreateView()}; | |
| TestMutator(mutator, &resource, false); | |
| } | |
| } | |
| } | |
| class ResourceTableStaticSamplerValidationTest : public ResourceTableValidationTest { | |
| protected: | |
| std::vector<wgpu::FeatureName> GetRequiredFeatures() override { | |
| return { | |
| wgpu::FeatureName::ChromiumExperimentalSamplingResourceTable, | |
| wgpu::FeatureName::StaticSamplers, | |
| wgpu::FeatureName::YCbCrVulkanSamplers, | |
| }; | |
| } | |
| }; | |
| // Checks that YCbCr samplers cannot be added to a resource table | |
| TEST_F(ResourceTableStaticSamplerValidationTest, MutatorSamplerMustNotBeYCbCr) { | |
| wgpu::SamplerDescriptor samplerDesc = {}; | |
| wgpu::Sampler samplerDefault = device.CreateSampler(&samplerDesc); | |
| wgpu::YCbCrVkDescriptor yCbCrDesc = {}; | |
| yCbCrDesc.externalFormat = 1; | |
| samplerDesc.nextInChain = &yCbCrDesc; | |
| wgpu::Sampler samplerYCbCr = device.CreateSampler(&samplerDesc); | |
| for (auto mutator : {Mutator::Update, Mutator::InsertBinding}) { | |
| // Success case: default sampler | |
| { | |
| wgpu::BindingResource resource = {.sampler = samplerDefault}; | |
| TestMutator(mutator, &resource, true); | |
| } | |
| // Error case: YCbCr sampler | |
| { | |
| wgpu::BindingResource resource = {.sampler = samplerYCbCr}; | |
| TestMutator(mutator, &resource, false); | |
| } | |
| } | |
| } | |
| // Checks that YCbCr texture views cannot be added to a resource table | |
| TEST_F(ResourceTableStaticSamplerValidationTest, MutatorViewMustNotBeYCbCr) { | |
| wgpu::TextureDescriptor tDesc{ | |
| .usage = wgpu::TextureUsage::TextureBinding, | |
| .size = {1, 1}, | |
| .format = wgpu::TextureFormat::RGBA8Unorm, | |
| }; | |
| wgpu::Texture textureDefault = device.CreateTexture(&tDesc); | |
| tDesc.format = wgpu::TextureFormat::OpaqueYCbCrAndroid; | |
| wgpu::Texture textureYCbCr = device.CreateTexture(&tDesc); | |
| wgpu::YCbCrVkDescriptor yCbCrDesc = {}; | |
| yCbCrDesc.externalFormat = 1; | |
| wgpu::TextureViewDescriptor vDesc{ | |
| .nextInChain = &yCbCrDesc, | |
| .format = wgpu::TextureFormat::OpaqueYCbCrAndroid, | |
| .arrayLayerCount = 1, | |
| .usage = wgpu::TextureUsage::TextureBinding, | |
| }; | |
| for (auto mutator : {Mutator::Update, Mutator::InsertBinding}) { | |
| // Success case: default texture view | |
| { | |
| wgpu::BindingResource resource = {.textureView = textureDefault.CreateView()}; | |
| TestMutator(mutator, &resource, true); | |
| } | |
| // Error case: YCbCr texture view | |
| { | |
| wgpu::BindingResource resource = {.textureView = textureYCbCr.CreateView(&vDesc)}; | |
| // TestMutator(mutator, &resource, false); | |
| } | |
| } | |
| } | |
| // Test that it is not allowed to call Update, RemoveBinding or InsertBinding after the table is | |
| // destroyed. | |
| TEST_F(ResourceTableValidationTest, MutatorsAfterDestroy) { | |
| // Create the texture to put in the table. | |
| wgpu::TextureDescriptor tDesc{ | |
| .usage = wgpu::TextureUsage::TextureBinding, | |
| .size = {1, 1}, | |
| .format = wgpu::TextureFormat::RGBA8Unorm, | |
| }; | |
| wgpu::BindingResource resource = {.textureView = device.CreateTexture(&tDesc).CreateView()}; | |
| // This is "content timeline" validation so it works the same on error tables and valid tables, | |
| // and we ignore device-timeline validation errors, they are not what we are testing here. | |
| for (auto table : {MakeResourceTable(7), MakeErrorResourceTable(7)}) { | |
| utils::ScopedIgnoreValidationErrors ignoreErrors(device); | |
| // Add a few bindings just to test RemoveBinding | |
| EXPECT_EQ(wgpu::Status::Success, table.Update(0, &resource)); | |
| EXPECT_EQ(wgpu::Status::Success, table.Update(1, &resource)); | |
| // Success cases, calling mutators before destroying is valid. | |
| EXPECT_EQ(wgpu::Status::Success, table.Update(2, &resource)); | |
| EXPECT_NE(wgpu::kInvalidBinding, table.InsertBinding(&resource)); | |
| EXPECT_EQ(wgpu::Status::Success, table.RemoveBinding(0)); | |
| // Error case, after destruction all mutators return errors. | |
| table.Destroy(); | |
| EXPECT_EQ(wgpu::Status::Error, table.Update(6, &resource)); | |
| EXPECT_EQ(wgpu::kInvalidBinding, table.InsertBinding(&resource)); | |
| EXPECT_EQ(wgpu::Status::Error, table.RemoveBinding(1)); | |
| } | |
| } | |
| // Test that it is not allowed to call Update, RemoveBinding with slots past the end. | |
| TEST_F(ResourceTableValidationTest, MutatorsAfterTableEnd) { | |
| // Create the texture to put in the table. | |
| wgpu::TextureDescriptor tDesc{ | |
| .usage = wgpu::TextureUsage::TextureBinding, | |
| .size = {1, 1}, | |
| .format = wgpu::TextureFormat::RGBA8Unorm, | |
| }; | |
| wgpu::BindingResource resource = {.textureView = device.CreateTexture(&tDesc).CreateView()}; | |
| // This is "content timeline" validation so it works the same on error tables and valid tables, | |
| // and we ignore device-timeline validation errors, they are not what we are testing here. | |
| for (auto table : {MakeResourceTable(42), MakeErrorResourceTable(42)}) { | |
| utils::ScopedIgnoreValidationErrors ignoreErrors(device); | |
| // Success cases, calling mutators with slots in bounds. | |
| EXPECT_EQ(wgpu::Status::Success, table.Update(0, &resource)); | |
| EXPECT_EQ(wgpu::Status::Success, table.Update(41, &resource)); | |
| EXPECT_EQ(wgpu::Status::Success, table.RemoveBinding(0)); | |
| EXPECT_EQ(wgpu::Status::Success, table.RemoveBinding(41)); | |
| // Error case, calling mutators with out of bounds slots. | |
| EXPECT_EQ(wgpu::Status::Error, table.Update(42, &resource)); | |
| EXPECT_EQ(wgpu::Status::Error, table.RemoveBinding(42)); | |
| } | |
| } | |
| // Test that Update/RemoveBinding return success but generates a validation error when used on an | |
| // invalid table. | |
| TEST_F(ResourceTableValidationTest, MutatorsOnInvalidTable) { | |
| // Create the texture to put in the table. | |
| wgpu::TextureDescriptor tDesc{ | |
| .usage = wgpu::TextureUsage::TextureBinding, | |
| .size = {1, 1}, | |
| .format = wgpu::TextureFormat::RGBA8Unorm, | |
| }; | |
| wgpu::BindingResource resource = {.textureView = device.CreateTexture(&tDesc).CreateView()}; | |
| // Test on a valid table. | |
| { | |
| wgpu::ResourceTable table = MakeResourceTable(3); | |
| EXPECT_EQ(wgpu::Status::Success, table.Update(0, &resource)); | |
| EXPECT_EQ(wgpu::Status::Success, table.RemoveBinding(0)); | |
| EXPECT_NE(wgpu::kInvalidBinding, table.InsertBinding(&resource)); | |
| } | |
| // Test on an invalid table. | |
| { | |
| wgpu::ResourceTable table = MakeErrorResourceTable(3); | |
| ASSERT_DEVICE_ERROR(EXPECT_EQ(wgpu::Status::Success, table.Update(0, &resource))); | |
| ASSERT_DEVICE_ERROR(EXPECT_EQ(wgpu::Status::Success, table.RemoveBinding(0))); | |
| ASSERT_DEVICE_ERROR(EXPECT_NE(wgpu::kInvalidBinding, table.InsertBinding(&resource))); | |
| } | |
| } | |
| // Test that Update() can be called on a table slot if it has never been used before. | |
| TEST_F(ResourceTableValidationTest, UpdateBindingWhenNeverUsed) { | |
| wgpu::TextureDescriptor tDesc{ | |
| .usage = wgpu::TextureUsage::TextureBinding, | |
| .size = {1, 1}, | |
| .format = wgpu::TextureFormat::RGBA8Unorm, | |
| }; | |
| wgpu::BindingResource resource = {.textureView = device.CreateTexture(&tDesc).CreateView()}; | |
| // This is "content timeline" validation so it works the same on error tables and valid tables, | |
| // and we ignore device-timeline validation errors, they are not what we are testing here. | |
| for (auto table : {MakeResourceTable(3), MakeErrorResourceTable(3)}) { | |
| utils::ScopedIgnoreValidationErrors ignoreErrors(device); | |
| // Updating slot 0 when it has never been used is valid, but a second time is an error. | |
| EXPECT_EQ(wgpu::Status::Success, table.Update(0, &resource)); | |
| EXPECT_EQ(wgpu::Status::Error, table.Update(0, &resource)); | |
| // Even after using the table, a previously unused entry is valid to update. | |
| UseResourceTableInSubmit(table); | |
| EXPECT_EQ(wgpu::Status::Success, table.Update(1, &resource)); | |
| } | |
| } | |
| // Test that Remove() can be called on a table slot even when it was never used. | |
| TEST_F(ResourceTableValidationTest, RemoveBindingWhenNeverUsed) { | |
| // This is "content timeline" validation so it works the same on error tables and valid tables, | |
| // and we ignore device-timeline validation errors, they are not what we are testing here. | |
| for (auto table : {MakeResourceTable(3), MakeErrorResourceTable(3)}) { | |
| utils::ScopedIgnoreValidationErrors ignoreErrors(device); | |
| EXPECT_EQ(wgpu::Status::Success, table.RemoveBinding(0)); | |
| } | |
| } | |
| // Check that a table slot can be updated only after all commands submitted prior to RemoveBinding | |
| // are completed. | |
| TEST_F(ResourceTableValidationTest, UpdateAfterRemoveRequiresGPUIsFinished) { | |
| wgpu::TextureDescriptor tDesc{ | |
| .usage = wgpu::TextureUsage::TextureBinding, | |
| .size = {1, 1}, | |
| .format = wgpu::TextureFormat::RGBA8Unorm, | |
| }; | |
| wgpu::BindingResource resource = {.textureView = device.CreateTexture(&tDesc).CreateView()}; | |
| // This is "content timeline" validation so it works the same on error tables and valid tables, | |
| // and we ignore device-timeline validation errors, they are not what we are testing here. | |
| for (auto table : {MakeResourceTable(1), MakeErrorResourceTable(1)}) { | |
| utils::ScopedIgnoreValidationErrors ignoreErrors(device); | |
| // Removing while the table is still potentially in used by the GPU is an error. But | |
| // immediately after we know that the GPU is finished, it is valid. | |
| EXPECT_EQ(wgpu::Status::Success, table.Update(0, &resource)); | |
| bool updateValid = false; | |
| UseResourceTableInSubmit(table); | |
| device.GetQueue().OnSubmittedWorkDone( | |
| wgpu::CallbackMode::AllowSpontaneous, | |
| [&](wgpu::QueueWorkDoneStatus, wgpu::StringView) { updateValid = true; }); | |
| EXPECT_EQ(wgpu::Status::Success, table.RemoveBinding(0)); | |
| // The null backend happens to call OnSubmittedWorkDone immediately because commands take 0 | |
| // time. This test is duplicated in the end2end tests where OnSubmittedWorkDone won't fire | |
| // immediately. | |
| if (updateValid) { | |
| EXPECT_EQ(wgpu::Status::Success, table.Update(0, &resource)); | |
| updateValid = false; | |
| } else { | |
| EXPECT_EQ(wgpu::Status::Error, table.Update(0, &resource)); | |
| } | |
| WaitForAllOperations(); | |
| if (updateValid) { | |
| EXPECT_EQ(wgpu::Status::Success, table.Update(0, &resource)); | |
| } else { | |
| EXPECT_EQ(wgpu::Status::Error, table.Update(0, &resource)); | |
| } | |
| } | |
| } | |
| // Check that trying to insert bindings fail when no more are available. | |
| TEST_F(ResourceTableValidationTest, InsertBindingFailWhenNoMoreSpace) { | |
| wgpu::TextureDescriptor tDesc{ | |
| .usage = wgpu::TextureUsage::TextureBinding, | |
| .size = {1, 1}, | |
| .format = wgpu::TextureFormat::RGBA8Unorm, | |
| }; | |
| wgpu::BindingResource resource = {.textureView = device.CreateTexture(&tDesc).CreateView()}; | |
| // This is "content timeline" validation so it works the same on error tables and valid tables, | |
| // and we ignore device-timeline validation errors, they are not what we are testing here. | |
| for (auto table : {MakeResourceTable(3), MakeErrorResourceTable(3)}) { | |
| utils::ScopedIgnoreValidationErrors ignoreErrors(device); | |
| // There is space for only three resources. | |
| EXPECT_EQ(0u, table.InsertBinding(&resource)); | |
| EXPECT_EQ(1u, table.InsertBinding(&resource)); | |
| EXPECT_EQ(2u, table.InsertBinding(&resource)); | |
| EXPECT_EQ(wgpu::kInvalidBinding, table.InsertBinding(&resource)); | |
| // Remove one binding (and wait for it to be recycled), it will be available for | |
| // InsertBinding after which a new InsertBinding will still run out of space. | |
| EXPECT_EQ(wgpu::Status::Success, table.RemoveBinding(1)); | |
| UseResourceTableInSubmit(table); | |
| WaitForAllOperations(); | |
| EXPECT_EQ(1u, table.InsertBinding(&resource)); | |
| EXPECT_EQ(wgpu::kInvalidBinding, table.InsertBinding(&resource)); | |
| } | |
| } | |
| // Check that bindings that are inserted are unavailable for Update() until RemoveBinding. | |
| TEST_F(ResourceTableValidationTest, InsertBindingPreventsUpdate) { | |
| wgpu::TextureDescriptor tDesc{ | |
| .usage = wgpu::TextureUsage::TextureBinding, | |
| .size = {1, 1}, | |
| .format = wgpu::TextureFormat::RGBA8Unorm, | |
| }; | |
| wgpu::BindingResource resource = {.textureView = device.CreateTexture(&tDesc).CreateView()}; | |
| // This is "content timeline" validation so it works the same on error tables and valid tables, | |
| // and we ignore device-timeline validation errors, they are not what we are testing here. | |
| for (auto table : {MakeResourceTable(1), MakeErrorResourceTable(1)}) { | |
| utils::ScopedIgnoreValidationErrors ignoreErrors(device); | |
| EXPECT_EQ(0u, table.InsertBinding(&resource)); | |
| EXPECT_EQ(wgpu::Status::Error, table.Update(0, &resource)); | |
| // Remove one binding (and wait for it to be recycled), it will be available for Update. | |
| EXPECT_EQ(wgpu::Status::Success, table.RemoveBinding(0)); | |
| UseResourceTableInSubmit(table); | |
| WaitForAllOperations(); | |
| EXPECT_EQ(wgpu::Status::Success, table.Update(0, &resource)); | |
| } | |
| } | |
| // Check that InsertBinding skips over used slots. | |
| TEST_F(ResourceTableValidationTest, InsertBindingSkipsOverUsedSlots) { | |
| wgpu::TextureDescriptor tDesc{ | |
| .usage = wgpu::TextureUsage::TextureBinding, | |
| .size = {1, 1}, | |
| .format = wgpu::TextureFormat::RGBA8Unorm, | |
| }; | |
| wgpu::BindingResource resource = {.textureView = device.CreateTexture(&tDesc).CreateView()}; | |
| // This is "content timeline" validation so it works the same on error tables and valid tables, | |
| // and we ignore device-timeline validation errors, they are not what we are testing here. | |
| for (auto table : {MakeResourceTable(5), MakeErrorResourceTable(5)}) { | |
| utils::ScopedIgnoreValidationErrors ignoreErrors(device); | |
| EXPECT_EQ(wgpu::Status::Success, table.Update(1, &resource)); | |
| EXPECT_EQ(wgpu::Status::Success, table.Update(3, &resource)); | |
| // InsertBinding skips over entries used by Update() | |
| EXPECT_EQ(0u, table.InsertBinding(&resource)); | |
| EXPECT_EQ(2u, table.InsertBinding(&resource)); | |
| EXPECT_EQ(4u, table.InsertBinding(&resource)); | |
| // Remove bindings in inverse order. | |
| for (uint32_t i : {4, 3, 2}) { | |
| EXPECT_EQ(wgpu::Status::Success, table.RemoveBinding(i)); | |
| } | |
| UseResourceTableInSubmit(table); | |
| WaitForAllOperations(); | |
| // InsertBinding should still return the min available slot. | |
| EXPECT_EQ(2u, table.InsertBinding(&resource)); | |
| EXPECT_EQ(3u, table.InsertBinding(&resource)); | |
| EXPECT_EQ(4u, table.InsertBinding(&resource)); | |
| } | |
| } | |
| // Test the value returned by GetSize right after creating the table. | |
| TEST_F(ResourceTableValidationTest, GetSizeAfterCreation) { | |
| // Valid resource tables of varying size. | |
| { | |
| EXPECT_EQ(0u, MakeResourceTable(0).GetSize()); | |
| EXPECT_EQ(42u, MakeResourceTable(42).GetSize()); | |
| EXPECT_EQ(kMaxResourceTableSize, MakeResourceTable(kMaxResourceTableSize).GetSize()); | |
| } | |
| // Invalid resource tables of varying size under the limit. | |
| { | |
| EXPECT_EQ(0u, MakeErrorResourceTable(0).GetSize()); | |
| EXPECT_EQ(42u, MakeErrorResourceTable(42).GetSize()); | |
| EXPECT_EQ(kMaxResourceTableSize, MakeErrorResourceTable(kMaxResourceTableSize).GetSize()); | |
| } | |
| } | |
| // Test the value returned by GetSize after calling Destroy() should return the same value. | |
| TEST_F(ResourceTableValidationTest, GetSizeAfterDestroy) { | |
| // Valid resource table. | |
| { | |
| wgpu::ResourceTable table = MakeResourceTable(42); | |
| EXPECT_EQ(42u, table.GetSize()); | |
| table.Destroy(); | |
| EXPECT_EQ(42u, table.GetSize()); | |
| } | |
| // Invalid resource table. | |
| { | |
| wgpu::ResourceTable table = MakeResourceTable(42); | |
| EXPECT_EQ(42u, table.GetSize()); | |
| table.Destroy(); | |
| EXPECT_EQ(42u, table.GetSize()); | |
| } | |
| } | |
| } // namespace | |
| } // namespace dawn |