| // 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 <vector> |
| |
| #include "dawn/tests/DawnTest.h" |
| #include "dawn/utils/ComboRenderPipelineDescriptor.h" |
| #include "dawn/utils/WGPUHelpers.h" |
| |
| namespace dawn { |
| namespace { |
| |
| class SizedBindingArrayTests : public DawnTest { |
| public: |
| void SetUp() override { |
| DawnTest::SetUp(); |
| |
| // TODO(https://issues.chromium.org/411573959) Fails using WARP but not on real hardware. |
| // WARP 10.0.19031.4355 samples the wrong textures when indexing while WARP 1.0.12.0 fails |
| // pipeline creation. |
| DAWN_SUPPRESS_TEST_IF(IsD3D12() && IsWARP()); |
| } |
| |
| // A 1x1 texture with a single value to check the correct binding is used. |
| wgpu::Texture MakeTestR8Texture(uint8_t value) { |
| wgpu::TextureDescriptor desc; |
| desc.size = {1, 1}; |
| desc.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::TextureBinding; |
| desc.format = wgpu::TextureFormat::R8Unorm; |
| wgpu::Texture texture = device.CreateTexture(&desc); |
| |
| wgpu::TexelCopyTextureInfo srcInfo = utils::CreateTexelCopyTextureInfo(texture); |
| wgpu::TexelCopyBufferLayout dstInfo = {}; |
| wgpu::Extent3D copySize = {1, 1, 1}; |
| queue.WriteTexture(&srcInfo, &value, 1, &dstInfo, ©Size); |
| |
| return texture; |
| } |
| |
| // Test textures that can be used that samplers correctly clamp or wrap. |
| wgpu::Texture MakeTest2x1R8Texture(uint8_t left, uint8_t right) { |
| wgpu::TextureDescriptor desc; |
| desc.size = {2, 1}; |
| desc.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::TextureBinding; |
| desc.format = wgpu::TextureFormat::R8Unorm; |
| wgpu::Texture texture = device.CreateTexture(&desc); |
| |
| wgpu::TexelCopyTextureInfo srcInfo = utils::CreateTexelCopyTextureInfo(texture); |
| wgpu::TexelCopyBufferLayout dstInfo = {}; |
| wgpu::Extent3D copySize = {2, 1, 1}; |
| std::array<uint8_t, 2> data = {left, right}; |
| queue.WriteTexture(&srcInfo, &data, sizeof(data), &dstInfo, ©Size); |
| |
| return texture; |
| } |
| }; |
| |
| // Test accessing a binding_array with constant values. |
| TEST_P(SizedBindingArrayTests, IndexingWithConstants) { |
| // Make the test pipeline |
| wgpu::ShaderModule module = utils::CreateShaderModule(device, R"( |
| @vertex fn vs() -> @builtin(position) vec4f { |
| return vec4f(0, 0, 0.5, 0.5); |
| } |
| |
| @group(0) @binding(0) var ts : binding_array<texture_2d<f32>, 4>; |
| @fragment fn fs() -> @location(0) vec4f { |
| let r = textureLoad(ts[0], vec2(0, 0), 0)[0]; |
| let g = textureLoad(ts[1], vec2(0, 0), 0)[0]; |
| let b = textureLoad(ts[2], vec2(0, 0), 0)[0]; |
| let a = textureLoad(ts[3], vec2(0, 0), 0)[0]; |
| return vec4(r, g, b, a); |
| } |
| )"); |
| |
| utils::ComboRenderPipelineDescriptor pDesc; |
| pDesc.vertex.module = module; |
| pDesc.cFragment.module = module; |
| pDesc.cFragment.targetCount = 1; |
| pDesc.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm; |
| pDesc.primitive.topology = wgpu::PrimitiveTopology::PointList; |
| wgpu::RenderPipeline testPipeline = device.CreateRenderPipeline(&pDesc); |
| |
| // Create a bind group with textures of decreasing values |
| wgpu::BindGroup arrayGroup = utils::MakeBindGroup(device, testPipeline.GetBindGroupLayout(0), |
| { |
| {3, MakeTestR8Texture(0).CreateView()}, |
| {2, MakeTestR8Texture(1).CreateView()}, |
| {1, MakeTestR8Texture(2).CreateView()}, |
| {0, MakeTestR8Texture(3).CreateView()}, |
| }); |
| |
| // Run the test |
| auto rp = utils::CreateBasicRenderPass(device, 1, 1); |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); |
| |
| pass.SetPipeline(testPipeline); |
| pass.SetBindGroup(0, arrayGroup); |
| pass.Draw(1); |
| pass.End(); |
| |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8(3, 2, 1, 0), rp.color, 0, 0); |
| } |
| |
| // Test accessing a binding_array with dynamically uniform values. |
| TEST_P(SizedBindingArrayTests, IndexingWithDynamicallyUniformValues) { |
| // Compat should disallow dynamically uniform indexing of binding_array of textures. |
| DAWN_TEST_UNSUPPORTED_IF(IsCompatibilityMode()); |
| |
| // TODO(https://issues.chromium.org/425328998): D3D11 exposes core when FXC cannot support |
| // core's uniform indexing of binding_array<texture*>. We should make D3D11 expose only compat |
| // by default. |
| DAWN_SUPPRESS_TEST_IF(IsD3D11()); |
| |
| // Make the test pipeline |
| wgpu::ShaderModule module = utils::CreateShaderModule(device, R"( |
| @vertex fn vs() -> @builtin(position) vec4f { |
| return vec4f(0, 0, 0.5, 0.5); |
| } |
| |
| @group(0) @binding(0) var<storage> indices : array<u32, 4>; |
| @group(0) @binding(1) var ts : binding_array<texture_2d<f32>, 4>; |
| @fragment fn fs() -> @location(0) vec4f { |
| let r = textureLoad(ts[indices[0]], vec2(0, 0), 0)[0]; |
| let g = textureLoad(ts[indices[1]], vec2(0, 0), 0)[0]; |
| let b = textureLoad(ts[indices[2]], vec2(0, 0), 0)[0]; |
| let a = textureLoad(ts[indices[3]], vec2(0, 0), 0)[0]; |
| return vec4(r, g, b, a); |
| } |
| )"); |
| |
| utils::ComboRenderPipelineDescriptor pDesc; |
| pDesc.vertex.module = module; |
| pDesc.cFragment.module = module; |
| pDesc.cFragment.targetCount = 1; |
| pDesc.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm; |
| pDesc.primitive.topology = wgpu::PrimitiveTopology::PointList; |
| wgpu::RenderPipeline testPipeline = device.CreateRenderPipeline(&pDesc); |
| |
| // Create a bind group with the indirection |
| wgpu::Buffer indices = |
| utils::CreateBufferFromData<uint32_t>(device, wgpu::BufferUsage::Storage, {2, 3, 1, 0}); |
| |
| wgpu::BindGroup arrayGroup = utils::MakeBindGroup(device, testPipeline.GetBindGroupLayout(0), |
| { |
| {0, indices}, |
| {1, MakeTestR8Texture(0).CreateView()}, |
| {2, MakeTestR8Texture(1).CreateView()}, |
| {3, MakeTestR8Texture(2).CreateView()}, |
| {4, MakeTestR8Texture(3).CreateView()}, |
| }); |
| |
| // Run the test |
| auto rp = utils::CreateBasicRenderPass(device, 1, 1); |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); |
| |
| pass.SetPipeline(testPipeline); |
| pass.SetBindGroup(0, arrayGroup); |
| pass.Draw(1); |
| pass.End(); |
| |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8(2, 3, 1, 0), rp.color, 0, 0); |
| } |
| |
| // Test accessing a binding_array surrounded with other bindings in the layout. |
| TEST_P(SizedBindingArrayTests, ArrayEntryOversizedAndSurrounded) { |
| // Create the BGL with an array larger than the declaration in the shader such the trailing |
| // binding will be unused. |
| wgpu::BindGroupLayout bgl; |
| { |
| std::array<wgpu::BindGroupLayoutEntry, 3> entries; |
| |
| entries[0].binding = 0; |
| entries[0].visibility = wgpu::ShaderStage::Fragment; |
| entries[0].texture.sampleType = wgpu::TextureSampleType::Float; |
| |
| entries[1].binding = 1; |
| entries[1].visibility = wgpu::ShaderStage::Fragment; |
| entries[1].bindingArraySize = 3; |
| entries[1].texture.sampleType = wgpu::TextureSampleType::Float; |
| |
| entries[2].binding = 4; |
| entries[2].visibility = wgpu::ShaderStage::Fragment; |
| entries[2].texture.sampleType = wgpu::TextureSampleType::Float; |
| |
| wgpu::BindGroupLayoutDescriptor bglDesc; |
| bglDesc.entryCount = entries.size(); |
| bglDesc.entries = entries.data(); |
| bgl = device.CreateBindGroupLayout(&bglDesc); |
| } |
| |
| // Make the test pipeline |
| wgpu::ShaderModule module = utils::CreateShaderModule(device, R"( |
| @vertex fn vs() -> @builtin(position) vec4f { |
| return vec4f(0, 0, 0.5, 0.5); |
| } |
| |
| @group(0) @binding(0) var t0 : texture_2d<f32>; |
| // Note that the layout will contain one extra entry using @binding(3) |
| @group(0) @binding(1) var ts : binding_array<texture_2d<f32>, 2>; |
| @group(0) @binding(4) var t3 : texture_2d<f32>; |
| @fragment fn fs() -> @location(0) vec4f { |
| let r = textureLoad(t0, vec2(0, 0), 0)[0]; |
| let g = textureLoad(ts[0], vec2(0, 0), 0)[0]; |
| let b = textureLoad(ts[1], vec2(0, 0), 0)[0]; |
| let a = textureLoad(t3, vec2(0, 0), 0)[0]; |
| return vec4(r, g, b, a); |
| } |
| )"); |
| |
| utils::ComboRenderPipelineDescriptor pDesc; |
| pDesc.layout = utils::MakePipelineLayout(device, {bgl}); |
| pDesc.vertex.module = module; |
| pDesc.cFragment.module = module; |
| pDesc.cFragment.targetCount = 1; |
| pDesc.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm; |
| pDesc.primitive.topology = wgpu::PrimitiveTopology::PointList; |
| wgpu::RenderPipeline testPipeline = device.CreateRenderPipeline(&pDesc); |
| |
| // Create a bind group with the explicit layout and the extra array element |
| wgpu::BindGroup arrayGroup = utils::MakeBindGroup(device, bgl, |
| { |
| {0, MakeTestR8Texture(0).CreateView()}, |
| {1, MakeTestR8Texture(1).CreateView()}, |
| {2, MakeTestR8Texture(2).CreateView()}, |
| {3, MakeTestR8Texture(42).CreateView()}, |
| {4, MakeTestR8Texture(3).CreateView()}, |
| }); |
| |
| // Run the test |
| auto rp = utils::CreateBasicRenderPass(device, 1, 1); |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); |
| |
| pass.SetPipeline(testPipeline); |
| pass.SetBindGroup(0, arrayGroup); |
| pass.Draw(1); |
| pass.End(); |
| |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8(0, 1, 2, 3), rp.color, 0, 0); |
| } |
| |
| // Test that an arrayed BGLEntry of size 2 is compatible with a non-arrayed binding. |
| TEST_P(SizedBindingArrayTests, NonArrayedBindingCompatibleWithArrayedBGLEntry) { |
| // Create the BGL with an oversized array |
| wgpu::BindGroupLayout bgl; |
| { |
| wgpu::BindGroupLayoutEntry entry; |
| |
| entry.binding = 0; |
| entry.visibility = wgpu::ShaderStage::Fragment; |
| entry.bindingArraySize = 2; |
| entry.texture.sampleType = wgpu::TextureSampleType::Float; |
| |
| wgpu::BindGroupLayoutDescriptor bglDesc; |
| bglDesc.entryCount = 1; |
| bglDesc.entries = &entry; |
| bgl = device.CreateBindGroupLayout(&bglDesc); |
| } |
| |
| // Make the test pipeline |
| wgpu::ShaderModule module = utils::CreateShaderModule(device, R"( |
| @vertex fn vs() -> @builtin(position) vec4f { |
| return vec4f(0, 0, 0.5, 0.5); |
| } |
| |
| @group(0) @binding(0) var t : texture_2d<f32>; |
| @fragment fn fs() -> @location(0) vec4f { |
| return vec4f(textureLoad(t, vec2(0, 0), 0).r, 0, 0, 0); |
| } |
| )"); |
| |
| utils::ComboRenderPipelineDescriptor pDesc; |
| pDesc.layout = utils::MakePipelineLayout(device, {bgl}); |
| pDesc.vertex.module = module; |
| pDesc.cFragment.module = module; |
| pDesc.cFragment.targetCount = 1; |
| pDesc.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm; |
| pDesc.primitive.topology = wgpu::PrimitiveTopology::PointList; |
| wgpu::RenderPipeline testPipeline = device.CreateRenderPipeline(&pDesc); |
| |
| // Run the test |
| wgpu::BindGroup arrayGroup = utils::MakeBindGroup(device, bgl, |
| { |
| {0, MakeTestR8Texture(33).CreateView()}, |
| {1, MakeTestR8Texture(34).CreateView()}, |
| }); |
| |
| auto rp = utils::CreateBasicRenderPass(device, 1, 1); |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); |
| |
| pass.SetPipeline(testPipeline); |
| pass.SetBindGroup(0, arrayGroup); |
| pass.Draw(1); |
| pass.End(); |
| |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8(33, 0, 0, 0), rp.color, 0, 0); |
| } |
| |
| // Test that binding_array<T, 1> is compatible with a non-arrayed layout. |
| TEST_P(SizedBindingArrayTests, BindingArraySize1CompatibleWithNonArrayedBGL) { |
| // Make the test pipeline |
| wgpu::ShaderModule module = utils::CreateShaderModule(device, R"( |
| @vertex fn vs() -> @builtin(position) vec4f { |
| return vec4f(0, 0, 0.5, 0.5); |
| } |
| |
| @group(0) @binding(0) var ts : binding_array<texture_2d<f32>, 1>; |
| @fragment fn fs() -> @location(0) vec4f { |
| return vec4f(textureLoad(ts[0], vec2(0, 0), 0).r, 0, 0, 0); |
| } |
| )"); |
| |
| wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( |
| device, {{0, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float}}); |
| |
| utils::ComboRenderPipelineDescriptor pDesc; |
| pDesc.layout = utils::MakePipelineLayout(device, {bgl}); |
| pDesc.vertex.module = module; |
| pDesc.cFragment.module = module; |
| pDesc.cFragment.targetCount = 1; |
| pDesc.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm; |
| pDesc.primitive.topology = wgpu::PrimitiveTopology::PointList; |
| wgpu::RenderPipeline testPipeline = device.CreateRenderPipeline(&pDesc); |
| |
| // Run the test |
| wgpu::BindGroup arrayGroup = utils::MakeBindGroup(device, bgl, |
| { |
| {0, MakeTestR8Texture(42).CreateView()}, |
| }); |
| |
| auto rp = utils::CreateBasicRenderPass(device, 1, 1); |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); |
| |
| pass.SetPipeline(testPipeline); |
| pass.SetBindGroup(0, arrayGroup); |
| pass.Draw(1); |
| pass.End(); |
| |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8(42, 0, 0, 0), rp.color, 0, 0); |
| } |
| |
| // Test passing sampled textures of binding_array as function arguments. |
| TEST_P(SizedBindingArrayTests, BindingArraySampledTextureAsFunctionArgument) { |
| // TODO(https://issues.chromium.org/411573957) OgenGL requires that indices in arrays of sampler |
| // be constants but even after the DirectVariableAccess transform, the index is a function |
| // parameter (that's constant at the callsite) and not a constant. |
| DAWN_SUPPRESS_TEST_IF(IsOpenGL() || IsOpenGLES()); |
| |
| // Make the test pipeline |
| wgpu::ShaderModule module = utils::CreateShaderModule(device, R"( |
| @vertex fn vs() -> @builtin(position) vec4f { |
| return vec4f(0, 0, 0.5, 0.5); |
| } |
| |
| fn load(t : texture_2d<f32>) -> f32 { |
| return textureLoad(t, vec2(0, 0), 0)[0]; |
| } |
| |
| @group(0) @binding(0) var ts : binding_array<texture_2d<f32>, 4>; |
| @fragment fn fs() -> @location(0) vec4f { |
| let r = load(ts[0]); |
| let g = load(ts[1]); |
| let b = load(ts[2]); |
| let a = load(ts[3]); |
| return vec4(r, g, b, a); |
| } |
| )"); |
| |
| utils::ComboRenderPipelineDescriptor pDesc; |
| pDesc.vertex.module = module; |
| pDesc.cFragment.module = module; |
| pDesc.cFragment.targetCount = 1; |
| pDesc.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm; |
| pDesc.primitive.topology = wgpu::PrimitiveTopology::PointList; |
| wgpu::RenderPipeline testPipeline = device.CreateRenderPipeline(&pDesc); |
| |
| // Create a bind group with textures of decreasing values |
| wgpu::BindGroup arrayGroup = utils::MakeBindGroup(device, testPipeline.GetBindGroupLayout(0), |
| { |
| {3, MakeTestR8Texture(0).CreateView()}, |
| {2, MakeTestR8Texture(1).CreateView()}, |
| {1, MakeTestR8Texture(2).CreateView()}, |
| {0, MakeTestR8Texture(3).CreateView()}, |
| }); |
| |
| // Run the test |
| auto rp = utils::CreateBasicRenderPass(device, 1, 1); |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); |
| |
| pass.SetPipeline(testPipeline); |
| pass.SetBindGroup(0, arrayGroup); |
| pass.Draw(1); |
| pass.End(); |
| |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8(3, 2, 1, 0), rp.color, 0, 0); |
| } |
| |
| // Test accessing a binding_array of sampled textures passed as function argument. |
| TEST_P(SizedBindingArrayTests, BindingArrayOfSampledTexturesPassedAsArgument) { |
| // Crashes on the Intel Windows Vulkan shader compiler. |
| DAWN_SUPPRESS_TEST_IF(IsVulkan() && IsWindows() && IsIntel()); |
| |
| // Make the test pipeline |
| wgpu::ShaderModule module = utils::CreateShaderModule(device, R"( |
| @vertex fn vs() -> @builtin(position) vec4f { |
| return vec4f(0, 0, 0.5, 0.5); |
| } |
| |
| @group(0) @binding(0) var ts : binding_array<texture_2d<f32>, 4>; |
| fn f(textures : binding_array<texture_2d<f32>, 4>) -> vec4f { |
| let r = textureLoad(textures[0], vec2(0, 0), 0)[0]; |
| let g = textureLoad(textures[1], vec2(0, 0), 0)[0]; |
| let b = textureLoad(textures[2], vec2(0, 0), 0)[0]; |
| let a = textureLoad(textures[3], vec2(0, 0), 0)[0]; |
| return vec4(r, g, b, a); |
| } |
| |
| @fragment fn fs() -> @location(0) vec4f { |
| return f(ts); |
| } |
| )"); |
| |
| utils::ComboRenderPipelineDescriptor pDesc; |
| pDesc.vertex.module = module; |
| pDesc.cFragment.module = module; |
| pDesc.cFragment.targetCount = 1; |
| pDesc.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm; |
| pDesc.primitive.topology = wgpu::PrimitiveTopology::PointList; |
| wgpu::RenderPipeline testPipeline = device.CreateRenderPipeline(&pDesc); |
| |
| // Create a bind group with textures of decreasing values |
| wgpu::BindGroup arrayGroup = utils::MakeBindGroup(device, testPipeline.GetBindGroupLayout(0), |
| { |
| {3, MakeTestR8Texture(0).CreateView()}, |
| {2, MakeTestR8Texture(1).CreateView()}, |
| {1, MakeTestR8Texture(2).CreateView()}, |
| {0, MakeTestR8Texture(3).CreateView()}, |
| }); |
| |
| // Run the test |
| auto rp = utils::CreateBasicRenderPass(device, 1, 1); |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); |
| |
| pass.SetPipeline(testPipeline); |
| pass.SetBindGroup(0, arrayGroup); |
| pass.Draw(1); |
| pass.End(); |
| |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8(3, 2, 1, 0), rp.color, 0, 0); |
| } |
| |
| // Test that multiple texture and sampler combinations are handled correctly (in particular on GL). |
| TEST_P(SizedBindingArrayTests, TextureAndSamplerCombination) { |
| // Make the test pipeline |
| wgpu::ShaderModule module = utils::CreateShaderModule(device, R"( |
| @vertex fn vs() -> @builtin(position) vec4f { |
| return vec4f(0, 0, 0.5, 0.5); |
| } |
| |
| @group(0) @binding(0) var ts : binding_array<texture_2d<f32>, 2>; |
| @group(0) @binding(2) var samplerClamping : sampler; |
| @group(0) @binding(3) var samplerWrapping : sampler; |
| @fragment fn fs() -> @location(0) vec4f { |
| let r = textureSample(ts[0], samplerWrapping, vec2(1.25, 0.5))[0]; |
| let g = textureSample(ts[0], samplerClamping, vec2(1.25, 0.5))[0]; |
| let b = textureSample(ts[1], samplerWrapping, vec2(1.25, 0.5))[0]; |
| let a = textureSample(ts[1], samplerClamping, vec2(1.25, 0.5))[0]; |
| return vec4(r, g, b, a); |
| } |
| )"); |
| |
| utils::ComboRenderPipelineDescriptor pDesc; |
| pDesc.vertex.module = module; |
| pDesc.cFragment.module = module; |
| pDesc.cFragment.targetCount = 1; |
| pDesc.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm; |
| pDesc.primitive.topology = wgpu::PrimitiveTopology::PointList; |
| wgpu::RenderPipeline testPipeline = device.CreateRenderPipeline(&pDesc); |
| |
| // Create a bind group with textures of decreasing values |
| wgpu::SamplerDescriptor sDesc = {}; |
| sDesc.addressModeU = wgpu::AddressMode::ClampToEdge; |
| wgpu::Sampler samplerClamping = device.CreateSampler(&sDesc); |
| sDesc.addressModeU = wgpu::AddressMode::Repeat; |
| wgpu::Sampler samplerWrapping = device.CreateSampler(&sDesc); |
| |
| wgpu::BindGroup arrayGroup = |
| utils::MakeBindGroup(device, testPipeline.GetBindGroupLayout(0), |
| { |
| {0, MakeTest2x1R8Texture(3, 2).CreateView()}, |
| {1, MakeTest2x1R8Texture(1, 0).CreateView()}, |
| {2, samplerClamping}, |
| {3, samplerWrapping}, |
| }); |
| |
| // Run the test |
| auto rp = utils::CreateBasicRenderPass(device, 1, 1); |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); |
| |
| pass.SetPipeline(testPipeline); |
| pass.SetBindGroup(0, arrayGroup); |
| pass.Draw(1); |
| pass.End(); |
| |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8(3, 2, 1, 0), rp.color, 0, 0); |
| } |
| |
| // Test that calling textureNumLevels on an element of a binding_array returns the correct value, |
| // this is targeted for the GL backend that has emulation of that builtin with a UBO. |
| TEST_P(SizedBindingArrayTests, TextureNumLevels) { |
| // Make the test pipeline |
| wgpu::ShaderModule module = utils::CreateShaderModule(device, R"( |
| @vertex fn vs() -> @builtin(position) vec4f { |
| return vec4f(0, 0, 0.5, 0.5); |
| } |
| |
| @group(0) @binding(0) var ts : binding_array<texture_2d<f32>, 3>; |
| @fragment fn fs() -> @location(0) vec4f { |
| let r = f32(textureNumLevels(ts[0])) / 255.0; |
| let g = f32(textureNumLevels(ts[1])) / 255.0; |
| let b = f32(textureNumLevels(ts[2])) / 255.0; |
| let a : f32 = 0.0; |
| return vec4(r, g, b, a); |
| } |
| )"); |
| |
| utils::ComboRenderPipelineDescriptor pDesc; |
| pDesc.vertex.module = module; |
| pDesc.cFragment.module = module; |
| pDesc.cFragment.targetCount = 1; |
| pDesc.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm; |
| pDesc.primitive.topology = wgpu::PrimitiveTopology::PointList; |
| wgpu::RenderPipeline testPipeline = device.CreateRenderPipeline(&pDesc); |
| |
| // Create a bind group with textures views of decreasing numlevels. |
| wgpu::TextureDescriptor tDesc; |
| tDesc.size = {4, 4}; |
| tDesc.format = wgpu::TextureFormat::RGBA8Unorm; |
| tDesc.usage = wgpu::TextureUsage::TextureBinding; |
| tDesc.mipLevelCount = 3; |
| wgpu::TextureView view3 = device.CreateTexture(&tDesc).CreateView(); |
| tDesc.mipLevelCount = 2; |
| wgpu::TextureView view2 = device.CreateTexture(&tDesc).CreateView(); |
| tDesc.mipLevelCount = 1; |
| wgpu::TextureView view1 = device.CreateTexture(&tDesc).CreateView(); |
| |
| wgpu::BindGroup arrayGroup = utils::MakeBindGroup(device, testPipeline.GetBindGroupLayout(0), |
| { |
| {0, view3}, |
| {1, view2}, |
| {2, view1}, |
| }); |
| |
| // Run the test |
| auto rp = utils::CreateBasicRenderPass(device, 1, 1); |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); |
| |
| pass.SetPipeline(testPipeline); |
| pass.SetBindGroup(0, arrayGroup); |
| pass.Draw(1); |
| pass.End(); |
| |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8(3, 2, 1, 0), rp.color, 0, 0); |
| } |
| |
| DAWN_INSTANTIATE_TEST(SizedBindingArrayTests, |
| D3D11Backend(), |
| D3D12Backend(), |
| MetalBackend(), |
| OpenGLBackend(), |
| OpenGLESBackend(), |
| VulkanBackend()); |
| |
| class DynamicBindingArrayTests : public DawnTest { |
| public: |
| void SetUp() override { |
| DawnTest::SetUp(); |
| DAWN_TEST_UNSUPPORTED_IF( |
| !SupportsFeatures({wgpu::FeatureName::ChromiumExperimentalBindless})); |
| } |
| |
| std::vector<wgpu::FeatureName> GetRequiredFeatures() override { |
| if (SupportsFeatures({wgpu::FeatureName::ChromiumExperimentalBindless})) { |
| return {wgpu::FeatureName::ChromiumExperimentalBindless}; |
| } |
| return {}; |
| } |
| |
| // Helper similar to utils::MakeBindGroupLayout but that adds a dynamic array. |
| wgpu::BindGroupLayout MakeBindGroupLayout( |
| wgpu::DynamicBindingKind kind, |
| uint32_t dynamicArrayStart = 0, |
| std::initializer_list<utils::BindingLayoutEntryInitializationHelper> entriesInitializer = |
| {}) { |
| std::vector<wgpu::BindGroupLayoutEntry> entries; |
| for (const utils::BindingLayoutEntryInitializationHelper& entry : entriesInitializer) { |
| entries.push_back(entry); |
| } |
| |
| wgpu::BindGroupLayoutDynamicBindingArray dynamic; |
| dynamic.dynamicArray.kind = kind; |
| dynamic.dynamicArray.start = dynamicArrayStart; |
| |
| wgpu::BindGroupLayoutDescriptor descriptor; |
| descriptor.nextInChain = &dynamic; |
| descriptor.entryCount = entries.size(); |
| descriptor.entries = entries.data(); |
| return device.CreateBindGroupLayout(&descriptor); |
| } |
| |
| // Helper similar to utils::MakeBindGroup but that adds a dynamic array. |
| wgpu::BindGroup MakeBindGroup( |
| const wgpu::BindGroupLayout& layout, |
| uint32_t dynamicArraySize, |
| std::initializer_list<utils::BindingInitializationHelper> entriesInitializer) { |
| std::vector<wgpu::BindGroupEntry> entries; |
| for (const utils::BindingInitializationHelper& helper : entriesInitializer) { |
| entries.push_back(helper.GetAsBinding()); |
| } |
| |
| wgpu::BindGroupDynamicBindingArray dynamic; |
| dynamic.dynamicArraySize = dynamicArraySize; |
| |
| wgpu::BindGroupDescriptor descriptor; |
| descriptor.nextInChain = &dynamic; |
| descriptor.layout = layout; |
| descriptor.entryCount = entries.size(); |
| descriptor.entries = entries.data(); |
| |
| return device.CreateBindGroup(&descriptor); |
| } |
| }; |
| |
| // Tests that creating the bind group that's only a dynamic array doesn't crash in backends. |
| TEST_P(DynamicBindingArrayTests, BindGroupOnlyDynamicArray) { |
| wgpu::BindGroupLayout bgl = MakeBindGroupLayout(wgpu::DynamicBindingKind::SampledTexture); |
| |
| wgpu::TextureDescriptor tDesc; |
| tDesc.format = wgpu::TextureFormat::R32Float; |
| tDesc.size = {1, 1}; |
| tDesc.usage = wgpu::TextureUsage::TextureBinding; |
| wgpu::Texture tex = device.CreateTexture(&tDesc); |
| |
| // Make a dense dynamic array of size 1. |
| MakeBindGroup(bgl, 1, {{0, tex.CreateView()}}); |
| |
| // Make a dense dynamic array of size 3. |
| MakeBindGroup(bgl, 3, |
| { |
| {0, tex.CreateView()}, |
| {1, tex.CreateView()}, |
| {2, tex.CreateView()}, |
| }); |
| |
| // Make a sparse dynamic array. |
| MakeBindGroup(bgl, 3, {{1, tex.CreateView()}}); |
| |
| // Make an empty dynamic array. |
| MakeBindGroup(bgl, 3, {}); |
| } |
| |
| // Tests that creating the bind group that has static bindings and a dynamic array doesn't crash in |
| // backends. |
| TEST_P(DynamicBindingArrayTests, BindGroupDynamicArrayWithStaticBindings) { |
| wgpu::BindGroupLayout bgl = MakeBindGroupLayout( |
| wgpu::DynamicBindingKind::SampledTexture, 4, |
| { |
| // Buffer sampler storage texture |
| {0, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Uniform}, |
| {1, wgpu::ShaderStage::Fragment, wgpu::SamplerBindingType::Filtering}, |
| {2, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float}, |
| }); |
| |
| wgpu::TextureDescriptor tDesc; |
| tDesc.format = wgpu::TextureFormat::R16Float; |
| tDesc.size = {1, 1}; |
| tDesc.usage = wgpu::TextureUsage::TextureBinding; |
| wgpu::Texture tex = device.CreateTexture(&tDesc); |
| |
| wgpu::Sampler sampler = device.CreateSampler(); |
| |
| wgpu::BufferDescriptor bDesc; |
| bDesc.size = 4; |
| bDesc.usage = wgpu::BufferUsage::Uniform; |
| wgpu::Buffer buffer = device.CreateBuffer(&bDesc); |
| |
| // Make a dense dynamic array of size 1. |
| MakeBindGroup(bgl, 1, |
| {{0, buffer}, {1, sampler}, {2, tex.CreateView()}, {4, tex.CreateView()}}); |
| |
| // Make a dense dynamic array of size 3. |
| MakeBindGroup(bgl, 3, |
| { |
| {0, buffer}, |
| {1, sampler}, |
| {2, tex.CreateView()}, |
| {4, tex.CreateView()}, |
| {5, tex.CreateView()}, |
| {6, tex.CreateView()}, |
| }); |
| |
| // Make a sparse dynamic array. |
| MakeBindGroup(bgl, 3, |
| {{0, buffer}, {1, sampler}, {2, tex.CreateView()}, {5, tex.CreateView()}}); |
| |
| // Make an empty dynamic array. |
| MakeBindGroup(bgl, 3, {{0, buffer}, {1, sampler}, {2, tex.CreateView()}}); |
| } |
| |
| // Test that creating bind groups of different sizes doesn't end up reuse incorrectly sized |
| // allocations. |
| TEST_P(DynamicBindingArrayTests, RecyclingDoesntReuseTooSmallAllocation) { |
| wgpu::BindGroupLayout bgl = MakeBindGroupLayout(wgpu::DynamicBindingKind::SampledTexture); |
| |
| for (uint32_t i = 0; i < 10; i++) { |
| MakeBindGroup(bgl, i, {}); |
| |
| // Wait to ensure some deallocation happens and has a chance to cause incorrect recycling. |
| WaitForAllOperations(); |
| } |
| } |
| |
| DAWN_INSTANTIATE_TEST(DynamicBindingArrayTests, D3D12Backend(), MetalBackend(), VulkanBackend()); |
| |
| } // anonymous namespace |
| } // namespace dawn |