| // Copyright 2019 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 "dawn/tests/unittests/validation/ValidationTest.h" |
| |
| #include "dawn/native/BindGroupLayout.h" |
| #include "dawn/utils/ComboRenderPipelineDescriptor.h" |
| #include "dawn/utils/WGPUHelpers.h" |
| |
| namespace dawn { |
| namespace { |
| |
| using testing::Not; |
| |
| class GetBindGroupLayoutTests : public ValidationTest { |
| protected: |
| wgpu::RenderPipeline RenderPipelineFromFragmentShader(const char* shader) { |
| wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"( |
| @vertex fn main() -> @builtin(position) vec4f { |
| return vec4f(); |
| })"); |
| |
| wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, shader); |
| |
| utils::ComboRenderPipelineDescriptor descriptor; |
| descriptor.layout = nullptr; |
| descriptor.vertex.module = vsModule; |
| descriptor.cFragment.module = fsModule; |
| descriptor.cTargets[0].writeMask = wgpu::ColorWriteMask::None; |
| |
| return device.CreateRenderPipeline(&descriptor); |
| } |
| }; |
| |
| // Test that GetBindGroupLayout returns the same object for the same index |
| // and for matching layouts. |
| TEST_F(GetBindGroupLayoutTests, EquivalentBGLs) { |
| DAWN_SKIP_TEST_IF(UsesWire()); |
| |
| wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"( |
| struct S { |
| pos : vec4f |
| } |
| @group(0) @binding(0) var<uniform> uniform0 : S; |
| @group(1) @binding(0) var<uniform> uniform1 : S; |
| |
| @vertex fn main() -> @builtin(position) vec4f { |
| var pos : vec4f = uniform0.pos; |
| pos = uniform1.pos; |
| return vec4f(); |
| })"); |
| |
| wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( |
| struct S2 { |
| pos : vec4f |
| } |
| @group(2) @binding(0) var<uniform> uniform2 : S2; |
| |
| struct S3 { |
| pos : mat4x4<f32> |
| } |
| @group(3) @binding(0) var<storage, read_write> storage3 : S3; |
| |
| @fragment fn main() { |
| var pos_u : vec4f = uniform2.pos; |
| var pos_s : mat4x4<f32> = storage3.pos; |
| })"); |
| |
| utils::ComboRenderPipelineDescriptor descriptor; |
| descriptor.layout = nullptr; |
| descriptor.vertex.module = vsModule; |
| descriptor.cFragment.module = fsModule; |
| descriptor.cTargets[0].writeMask = wgpu::ColorWriteMask::None; |
| |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor); |
| |
| // A fully equivalent layout is returned for the same index. |
| EXPECT_THAT(pipeline.GetBindGroupLayout(0), BindGroupLayoutEq(pipeline.GetBindGroupLayout(0))); |
| |
| // Matching bind group layouts at different indices are fully equivalent. |
| EXPECT_THAT(pipeline.GetBindGroupLayout(0), BindGroupLayoutEq(pipeline.GetBindGroupLayout(1))); |
| |
| // BGLs with different bindings types are different. |
| EXPECT_THAT(pipeline.GetBindGroupLayout(2), |
| Not(BindGroupLayoutEq(pipeline.GetBindGroupLayout(3)))); |
| |
| // BGLs with different visibilities are different. |
| EXPECT_THAT(pipeline.GetBindGroupLayout(0), |
| Not(BindGroupLayoutEq(pipeline.GetBindGroupLayout(2)))); |
| } |
| |
| // Test that default BindGroupLayouts cannot be used in the creation of a new PipelineLayout |
| TEST_F(GetBindGroupLayoutTests, DefaultBindGroupLayoutPipelineCompatibility) { |
| wgpu::RenderPipeline pipeline = RenderPipelineFromFragmentShader(R"( |
| struct S { |
| pos : vec4f |
| } |
| @group(0) @binding(0) var<uniform> uniforms : S; |
| |
| @fragment fn main() { |
| var pos : vec4f = uniforms.pos; |
| })"); |
| |
| ASSERT_DEVICE_ERROR(utils::MakePipelineLayout(device, {pipeline.GetBindGroupLayout(0)})); |
| } |
| |
| // Test that getBindGroupLayout defaults are correct |
| // - shader stage visibility is the stage that adds the binding. |
| // - dynamic offsets is false |
| TEST_F(GetBindGroupLayoutTests, DefaultShaderStageAndDynamicOffsets) { |
| DAWN_SKIP_TEST_IF(UsesWire()); |
| |
| wgpu::RenderPipeline pipeline = RenderPipelineFromFragmentShader(R"( |
| struct S { |
| pos : vec4f |
| } |
| @group(0) @binding(0) var<uniform> uniforms : S; |
| |
| @fragment fn main() { |
| var pos : vec4f = uniforms.pos; |
| })"); |
| |
| wgpu::BindGroupLayoutEntry binding = {}; |
| binding.binding = 0; |
| binding.buffer.type = wgpu::BufferBindingType::Uniform; |
| binding.buffer.minBindingSize = 4 * sizeof(float); |
| |
| wgpu::BindGroupLayoutDescriptor desc = {}; |
| desc.entryCount = 1; |
| desc.entries = &binding; |
| |
| // Check that an otherwise compatible bind group layout is not fully equivalent to one created |
| // as part of a default pipeline layout, but cache equivalent. |
| { |
| binding.buffer.hasDynamicOffset = false; |
| binding.visibility = wgpu::ShaderStage::Fragment; |
| wgpu::BindGroupLayout bgl = device.CreateBindGroupLayout(&desc); |
| EXPECT_THAT(bgl, BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0))); |
| EXPECT_THAT(bgl, Not(BindGroupLayoutEq(pipeline.GetBindGroupLayout(0)))); |
| } |
| |
| // Check that any change in visibility doesn't match. |
| binding.visibility = wgpu::ShaderStage::Vertex; |
| EXPECT_THAT(device.CreateBindGroupLayout(&desc), |
| Not(BindGroupLayoutEq(pipeline.GetBindGroupLayout(0)))); |
| |
| binding.visibility = wgpu::ShaderStage::Compute; |
| EXPECT_THAT(device.CreateBindGroupLayout(&desc), |
| Not(BindGroupLayoutEq(pipeline.GetBindGroupLayout(0)))); |
| |
| // Check that any change in hasDynamicOffsets doesn't match. |
| binding.buffer.hasDynamicOffset = true; |
| binding.visibility = wgpu::ShaderStage::Fragment; |
| EXPECT_THAT(device.CreateBindGroupLayout(&desc), |
| Not(BindGroupLayoutEq(pipeline.GetBindGroupLayout(0)))); |
| } |
| |
| TEST_F(GetBindGroupLayoutTests, DefaultTextureSampleType) { |
| DAWN_SKIP_TEST_IF(UsesWire()); |
| |
| wgpu::BindGroupLayout filteringBGL = utils::MakeBindGroupLayout( |
| device, {{0, wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment, |
| wgpu::TextureSampleType::Float}, |
| {1, wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment, |
| wgpu::SamplerBindingType::Filtering}}); |
| |
| wgpu::BindGroupLayout nonFilteringBGL = utils::MakeBindGroupLayout( |
| device, {{0, wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment, |
| wgpu::TextureSampleType::UnfilterableFloat}, |
| {1, wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment, |
| wgpu::SamplerBindingType::Filtering}}); |
| |
| wgpu::ShaderModule emptyVertexModule = utils::CreateShaderModule(device, R"( |
| @group(0) @binding(0) var myTexture : texture_2d<f32>; |
| @group(0) @binding(1) var mySampler : sampler; |
| @vertex fn main() -> @builtin(position) vec4f { |
| _ = myTexture; |
| _ = mySampler; |
| return vec4f(); |
| })"); |
| |
| wgpu::ShaderModule textureLoadVertexModule = utils::CreateShaderModule(device, R"( |
| @group(0) @binding(0) var myTexture : texture_2d<f32>; |
| @group(0) @binding(1) var mySampler : sampler; |
| @vertex fn main() -> @builtin(position) vec4f { |
| _ = textureLoad(myTexture, vec2i(), 0); |
| _ = mySampler; |
| return vec4f(); |
| })"); |
| |
| wgpu::ShaderModule textureSampleVertexModule = utils::CreateShaderModule(device, R"( |
| @group(0) @binding(0) var myTexture : texture_2d<f32>; |
| @group(0) @binding(1) var mySampler : sampler; |
| @vertex fn main() -> @builtin(position) vec4f { |
| _ = textureSampleLevel(myTexture, mySampler, vec2f(), 0.0); |
| return vec4f(); |
| })"); |
| |
| wgpu::ShaderModule unusedTextureFragmentModule = utils::CreateShaderModule(device, R"( |
| @group(0) @binding(0) var myTexture : texture_2d<f32>; |
| @group(0) @binding(1) var mySampler : sampler; |
| @fragment fn main() { |
| _ = myTexture; |
| _ = mySampler; |
| })"); |
| |
| wgpu::ShaderModule textureLoadFragmentModule = utils::CreateShaderModule(device, R"( |
| @group(0) @binding(0) var myTexture : texture_2d<f32>; |
| @group(0) @binding(1) var mySampler : sampler; |
| @fragment fn main() { |
| _ = textureLoad(myTexture, vec2i(), 0); |
| _ = mySampler; |
| })"); |
| |
| wgpu::ShaderModule textureSampleFragmentModule = utils::CreateShaderModule(device, R"( |
| @group(0) @binding(0) var myTexture : texture_2d<f32>; |
| @group(0) @binding(1) var mySampler : sampler; |
| @fragment fn main() { |
| _ = textureSample(myTexture, mySampler, vec2f()); |
| })"); |
| |
| auto BGLFromModules = [this](wgpu::ShaderModule vertexModule, |
| wgpu::ShaderModule fragmentModule) { |
| utils::ComboRenderPipelineDescriptor descriptor; |
| descriptor.vertex.module = vertexModule; |
| descriptor.cFragment.module = fragmentModule; |
| descriptor.cTargets[0].writeMask = wgpu::ColorWriteMask::None; |
| return device.CreateRenderPipeline(&descriptor).GetBindGroupLayout(0); |
| }; |
| |
| // Textures not used default to non-filtering |
| { |
| wgpu::BindGroupLayout bgl = BGLFromModules(emptyVertexModule, unusedTextureFragmentModule); |
| EXPECT_THAT(bgl, BindGroupLayoutCacheEq(nonFilteringBGL)); |
| EXPECT_THAT(bgl, Not(BindGroupLayoutCacheEq(filteringBGL))); |
| } |
| |
| // Textures used with textureLoad default to non-filtering |
| { |
| wgpu::BindGroupLayout bgl = BGLFromModules(emptyVertexModule, textureLoadFragmentModule); |
| EXPECT_THAT(bgl, BindGroupLayoutCacheEq(nonFilteringBGL)); |
| EXPECT_THAT(bgl, Not(BindGroupLayoutCacheEq(filteringBGL))); |
| } |
| |
| // Textures used with textureLoad on both stages default to non-filtering |
| { |
| wgpu::BindGroupLayout bgl = |
| BGLFromModules(textureLoadVertexModule, textureLoadFragmentModule); |
| EXPECT_THAT(bgl, BindGroupLayoutCacheEq(nonFilteringBGL)); |
| EXPECT_THAT(bgl, Not(BindGroupLayoutCacheEq(filteringBGL))); |
| } |
| |
| // Textures used with textureSample default to filtering |
| { |
| wgpu::BindGroupLayout bgl = BGLFromModules(emptyVertexModule, textureSampleFragmentModule); |
| EXPECT_THAT(bgl, Not(BindGroupLayoutCacheEq(nonFilteringBGL))); |
| EXPECT_THAT(bgl, BindGroupLayoutCacheEq(filteringBGL)); |
| } |
| { |
| wgpu::BindGroupLayout bgl = |
| BGLFromModules(textureSampleVertexModule, unusedTextureFragmentModule); |
| EXPECT_THAT(bgl, Not(BindGroupLayoutCacheEq(nonFilteringBGL))); |
| EXPECT_THAT(bgl, BindGroupLayoutCacheEq(filteringBGL)); |
| } |
| |
| // Textures used with both textureLoad and textureSample default to filtering |
| { |
| wgpu::BindGroupLayout bgl = |
| BGLFromModules(textureLoadVertexModule, textureSampleFragmentModule); |
| EXPECT_THAT(bgl, Not(BindGroupLayoutCacheEq(nonFilteringBGL))); |
| EXPECT_THAT(bgl, BindGroupLayoutCacheEq(filteringBGL)); |
| } |
| { |
| wgpu::BindGroupLayout bgl = |
| BGLFromModules(textureSampleVertexModule, textureLoadFragmentModule); |
| EXPECT_THAT(bgl, Not(BindGroupLayoutCacheEq(nonFilteringBGL))); |
| EXPECT_THAT(bgl, BindGroupLayoutCacheEq(filteringBGL)); |
| } |
| } |
| |
| // Test GetBindGroupLayout works with a compute pipeline |
| TEST_F(GetBindGroupLayoutTests, ComputePipeline) { |
| DAWN_SKIP_TEST_IF(UsesWire()); |
| |
| wgpu::ShaderModule csModule = utils::CreateShaderModule(device, R"( |
| struct S { |
| pos : vec4f |
| } |
| @group(0) @binding(0) var<uniform> uniforms : S; |
| |
| @compute @workgroup_size(1) fn main() { |
| var pos : vec4f = uniforms.pos; |
| })"); |
| |
| wgpu::ComputePipelineDescriptor descriptor; |
| descriptor.layout = nullptr; |
| descriptor.compute.module = csModule; |
| descriptor.compute.entryPoint = "main"; |
| |
| wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&descriptor); |
| |
| wgpu::BindGroupLayoutEntry binding = {}; |
| binding.binding = 0; |
| binding.buffer.type = wgpu::BufferBindingType::Uniform; |
| binding.visibility = wgpu::ShaderStage::Compute; |
| binding.buffer.hasDynamicOffset = false; |
| binding.buffer.minBindingSize = 4 * sizeof(float); |
| |
| wgpu::BindGroupLayoutDescriptor desc = {}; |
| desc.entryCount = 1; |
| desc.entries = &binding; |
| |
| // The pipeline bind group should be cache equivalent, but not fully equivalent since it was |
| // default created by the pipeline. |
| wgpu::BindGroupLayout bgl = device.CreateBindGroupLayout(&desc); |
| EXPECT_THAT(bgl, Not(BindGroupLayoutEq(pipeline.GetBindGroupLayout(0)))); |
| EXPECT_THAT(bgl, BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0))); |
| } |
| |
| // Test that the binding type matches the shader. |
| TEST_F(GetBindGroupLayoutTests, BindingType) { |
| DAWN_SKIP_TEST_IF(UsesWire()); |
| |
| wgpu::BindGroupLayoutEntry binding = {}; |
| binding.binding = 0; |
| binding.buffer.hasDynamicOffset = false; |
| binding.buffer.minBindingSize = 4 * sizeof(float); |
| binding.visibility = wgpu::ShaderStage::Fragment; |
| |
| wgpu::BindGroupLayoutDescriptor desc = {}; |
| desc.entryCount = 1; |
| desc.entries = &binding; |
| |
| { |
| // Storage buffer binding is not supported in vertex shader. |
| binding.visibility = wgpu::ShaderStage::Fragment; |
| binding.buffer.type = wgpu::BufferBindingType::Storage; |
| wgpu::RenderPipeline pipeline = RenderPipelineFromFragmentShader(R"( |
| struct S { |
| pos : vec4f |
| } |
| @group(0) @binding(0) var<storage, read_write> ssbo : S; |
| |
| @fragment fn main() { |
| var pos : vec4f = ssbo.pos; |
| })"); |
| EXPECT_THAT(device.CreateBindGroupLayout(&desc), |
| BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0))); |
| } |
| { |
| binding.buffer.type = wgpu::BufferBindingType::Uniform; |
| wgpu::RenderPipeline pipeline = RenderPipelineFromFragmentShader(R"( |
| struct S { |
| pos : vec4f |
| } |
| @group(0) @binding(0) var<uniform> uniforms : S; |
| |
| @fragment fn main() { |
| var pos : vec4f = uniforms.pos; |
| })"); |
| EXPECT_THAT(device.CreateBindGroupLayout(&desc), |
| BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0))); |
| } |
| |
| { |
| binding.buffer.type = wgpu::BufferBindingType::ReadOnlyStorage; |
| wgpu::RenderPipeline pipeline = RenderPipelineFromFragmentShader(R"( |
| struct S { |
| pos : vec4f |
| } |
| @group(0) @binding(0) var<storage, read> ssbo : S; |
| |
| @fragment fn main() { |
| var pos : vec4f = ssbo.pos; |
| })"); |
| EXPECT_THAT(device.CreateBindGroupLayout(&desc), |
| BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0))); |
| } |
| |
| binding.buffer.type = wgpu::BufferBindingType::Undefined; |
| binding.buffer.minBindingSize = 0; |
| { |
| binding.texture.sampleType = wgpu::TextureSampleType::UnfilterableFloat; |
| wgpu::RenderPipeline pipeline = RenderPipelineFromFragmentShader(R"( |
| @group(0) @binding(0) var myTexture : texture_2d<f32>; |
| |
| @fragment fn main() { |
| _ = textureDimensions(myTexture); |
| })"); |
| EXPECT_THAT(device.CreateBindGroupLayout(&desc), |
| BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0))); |
| } |
| |
| { |
| binding.texture.multisampled = true; |
| wgpu::RenderPipeline pipeline = RenderPipelineFromFragmentShader(R"( |
| @group(0) @binding(0) var myTexture : texture_multisampled_2d<f32>; |
| |
| @fragment fn main() { |
| _ = textureDimensions(myTexture); |
| })"); |
| EXPECT_THAT(device.CreateBindGroupLayout(&desc), |
| BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0))); |
| } |
| |
| binding.texture.sampleType = wgpu::TextureSampleType::Undefined; |
| { |
| binding.sampler.type = wgpu::SamplerBindingType::Filtering; |
| wgpu::RenderPipeline pipeline = RenderPipelineFromFragmentShader(R"( |
| @group(0) @binding(0) var mySampler: sampler; |
| |
| @fragment fn main() { |
| _ = mySampler; |
| })"); |
| EXPECT_THAT(device.CreateBindGroupLayout(&desc), |
| BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0))); |
| } |
| } |
| |
| // Tests that the external texture binding type matches with a texture_external declared in the |
| // shader. |
| TEST_F(GetBindGroupLayoutTests, ExternalTextureBindingType) { |
| DAWN_SKIP_TEST_IF(UsesWire()); |
| |
| wgpu::BindGroupLayoutEntry binding = {}; |
| binding.binding = 0; |
| binding.visibility = wgpu::ShaderStage::Fragment; |
| |
| wgpu::BindGroupLayoutDescriptor desc = {}; |
| desc.entryCount = 1; |
| desc.entries = &binding; |
| |
| binding.nextInChain = &utils::kExternalTextureBindingLayout; |
| wgpu::RenderPipeline pipeline = RenderPipelineFromFragmentShader(R"( |
| @group(0) @binding(0) var myExternalTexture: texture_external; |
| |
| @fragment fn main() { |
| _ = myExternalTexture; |
| })"); |
| EXPECT_THAT(device.CreateBindGroupLayout(&desc), |
| BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0))); |
| } |
| |
| // Test that texture view dimension matches the shader. |
| TEST_F(GetBindGroupLayoutTests, TextureViewDimension) { |
| DAWN_SKIP_TEST_IF(UsesWire()); |
| |
| wgpu::BindGroupLayoutEntry binding = {}; |
| binding.binding = 0; |
| binding.visibility = wgpu::ShaderStage::Fragment; |
| binding.texture.sampleType = wgpu::TextureSampleType::UnfilterableFloat; |
| |
| wgpu::BindGroupLayoutDescriptor desc = {}; |
| desc.entryCount = 1; |
| desc.entries = &binding; |
| |
| { |
| binding.texture.viewDimension = wgpu::TextureViewDimension::e1D; |
| wgpu::RenderPipeline pipeline = RenderPipelineFromFragmentShader(R"( |
| @group(0) @binding(0) var myTexture : texture_1d<f32>; |
| |
| @fragment fn main() { |
| _ = textureDimensions(myTexture); |
| })"); |
| EXPECT_THAT(device.CreateBindGroupLayout(&desc), |
| BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0))); |
| } |
| |
| { |
| binding.texture.viewDimension = wgpu::TextureViewDimension::e2D; |
| wgpu::RenderPipeline pipeline = RenderPipelineFromFragmentShader(R"( |
| @group(0) @binding(0) var myTexture : texture_2d<f32>; |
| |
| @fragment fn main() { |
| _ = textureDimensions(myTexture); |
| })"); |
| EXPECT_THAT(device.CreateBindGroupLayout(&desc), |
| BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0))); |
| |
| // viewDimension defaults to 2D, so should be cached the same. |
| binding.texture.viewDimension = wgpu::TextureViewDimension::Undefined; |
| EXPECT_THAT(device.CreateBindGroupLayout(&desc), |
| BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0))); |
| } |
| |
| { |
| binding.texture.viewDimension = wgpu::TextureViewDimension::e2DArray; |
| wgpu::RenderPipeline pipeline = RenderPipelineFromFragmentShader(R"( |
| @group(0) @binding(0) var myTexture : texture_2d_array<f32>; |
| |
| @fragment fn main() { |
| _ = textureDimensions(myTexture); |
| })"); |
| EXPECT_THAT(device.CreateBindGroupLayout(&desc), |
| BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0))); |
| } |
| |
| { |
| binding.texture.viewDimension = wgpu::TextureViewDimension::e3D; |
| wgpu::RenderPipeline pipeline = RenderPipelineFromFragmentShader(R"( |
| @group(0) @binding(0) var myTexture : texture_3d<f32>; |
| |
| @fragment fn main() { |
| _ = textureDimensions(myTexture); |
| })"); |
| EXPECT_THAT(device.CreateBindGroupLayout(&desc), |
| BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0))); |
| } |
| |
| { |
| binding.texture.viewDimension = wgpu::TextureViewDimension::Cube; |
| wgpu::RenderPipeline pipeline = RenderPipelineFromFragmentShader(R"( |
| @group(0) @binding(0) var myTexture : texture_cube<f32>; |
| |
| @fragment fn main() { |
| _ = textureDimensions(myTexture); |
| })"); |
| EXPECT_THAT(device.CreateBindGroupLayout(&desc), |
| BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0))); |
| } |
| |
| { |
| binding.texture.viewDimension = wgpu::TextureViewDimension::CubeArray; |
| wgpu::RenderPipeline pipeline = RenderPipelineFromFragmentShader(R"( |
| @group(0) @binding(0) var myTexture : texture_cube_array<f32>; |
| |
| @fragment fn main() { |
| _ = textureDimensions(myTexture); |
| })"); |
| EXPECT_THAT(device.CreateBindGroupLayout(&desc), |
| BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0))); |
| } |
| } |
| |
| // Test that storageTexture view dimension matches the shader. |
| TEST_F(GetBindGroupLayoutTests, StorageTextureViewDimension) { |
| DAWN_SKIP_TEST_IF(UsesWire()); |
| |
| wgpu::BindGroupLayoutEntry binding = {}; |
| binding.binding = 0; |
| binding.visibility = wgpu::ShaderStage::Fragment; |
| binding.storageTexture.access = wgpu::StorageTextureAccess::WriteOnly; |
| binding.storageTexture.format = wgpu::TextureFormat::R32Float; |
| |
| wgpu::BindGroupLayoutDescriptor desc = {}; |
| desc.entryCount = 1; |
| desc.entries = &binding; |
| |
| { |
| binding.storageTexture.viewDimension = wgpu::TextureViewDimension::e1D; |
| wgpu::RenderPipeline pipeline = RenderPipelineFromFragmentShader(R"( |
| @group(0) @binding(0) var myTexture : texture_storage_1d<r32float, write>; |
| |
| @fragment fn main() { |
| _ = textureDimensions(myTexture); |
| })"); |
| EXPECT_THAT(device.CreateBindGroupLayout(&desc), |
| BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0))); |
| } |
| |
| { |
| binding.storageTexture.viewDimension = wgpu::TextureViewDimension::e2D; |
| wgpu::RenderPipeline pipeline = RenderPipelineFromFragmentShader(R"( |
| @group(0) @binding(0) var myTexture : texture_storage_2d<r32float, write>; |
| |
| @fragment fn main() { |
| _ = textureDimensions(myTexture); |
| })"); |
| EXPECT_THAT(device.CreateBindGroupLayout(&desc), |
| BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0))); |
| |
| // viewDimension defaults to 2D, so should be cached the same. |
| binding.storageTexture.viewDimension = wgpu::TextureViewDimension::Undefined; |
| EXPECT_THAT(device.CreateBindGroupLayout(&desc), |
| BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0))); |
| } |
| |
| { |
| binding.storageTexture.viewDimension = wgpu::TextureViewDimension::e2DArray; |
| wgpu::RenderPipeline pipeline = RenderPipelineFromFragmentShader(R"( |
| @group(0) @binding(0) var myTexture : texture_storage_2d_array<r32float, write>; |
| |
| @fragment fn main() { |
| _ = textureDimensions(myTexture); |
| })"); |
| EXPECT_THAT(device.CreateBindGroupLayout(&desc), |
| BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0))); |
| } |
| |
| { |
| binding.storageTexture.viewDimension = wgpu::TextureViewDimension::e3D; |
| wgpu::RenderPipeline pipeline = RenderPipelineFromFragmentShader(R"( |
| @group(0) @binding(0) var myTexture : texture_storage_3d<r32float, write>; |
| |
| @fragment fn main() { |
| _ = textureDimensions(myTexture); |
| })"); |
| EXPECT_THAT(device.CreateBindGroupLayout(&desc), |
| BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0))); |
| } |
| } |
| |
| // Test that texture component type matches the shader. |
| TEST_F(GetBindGroupLayoutTests, TextureComponentType) { |
| DAWN_SKIP_TEST_IF(UsesWire()); |
| |
| wgpu::BindGroupLayoutEntry binding = {}; |
| binding.binding = 0; |
| binding.visibility = wgpu::ShaderStage::Fragment; |
| |
| wgpu::BindGroupLayoutDescriptor desc = {}; |
| desc.entryCount = 1; |
| desc.entries = &binding; |
| |
| { |
| binding.texture.sampleType = wgpu::TextureSampleType::UnfilterableFloat; |
| wgpu::RenderPipeline pipeline = RenderPipelineFromFragmentShader(R"( |
| @group(0) @binding(0) var myTexture : texture_2d<f32>; |
| |
| @fragment fn main() { |
| _ = textureDimensions(myTexture); |
| })"); |
| EXPECT_THAT(device.CreateBindGroupLayout(&desc), |
| BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0))); |
| } |
| |
| { |
| binding.texture.sampleType = wgpu::TextureSampleType::Sint; |
| wgpu::RenderPipeline pipeline = RenderPipelineFromFragmentShader(R"( |
| @group(0) @binding(0) var myTexture : texture_2d<i32>; |
| |
| @fragment fn main() { |
| _ = textureDimensions(myTexture); |
| })"); |
| EXPECT_THAT(device.CreateBindGroupLayout(&desc), |
| BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0))); |
| } |
| |
| { |
| binding.texture.sampleType = wgpu::TextureSampleType::Uint; |
| wgpu::RenderPipeline pipeline = RenderPipelineFromFragmentShader(R"( |
| @group(0) @binding(0) var myTexture : texture_2d<u32>; |
| |
| @fragment fn main() { |
| _ = textureDimensions(myTexture); |
| })"); |
| EXPECT_THAT(device.CreateBindGroupLayout(&desc), |
| BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0))); |
| } |
| } |
| |
| // Test that binding= indices match. |
| TEST_F(GetBindGroupLayoutTests, BindingIndices) { |
| DAWN_SKIP_TEST_IF(UsesWire()); |
| |
| wgpu::BindGroupLayoutEntry binding = {}; |
| binding.visibility = wgpu::ShaderStage::Fragment; |
| binding.buffer.type = wgpu::BufferBindingType::Uniform; |
| binding.buffer.hasDynamicOffset = false; |
| binding.buffer.minBindingSize = 4 * sizeof(float); |
| |
| wgpu::BindGroupLayoutDescriptor desc = {}; |
| desc.entryCount = 1; |
| desc.entries = &binding; |
| |
| { |
| binding.binding = 0; |
| wgpu::RenderPipeline pipeline = RenderPipelineFromFragmentShader(R"( |
| struct S { |
| pos : vec4f |
| } |
| @group(0) @binding(0) var<uniform> uniforms : S; |
| |
| @fragment fn main() { |
| var pos : vec4f = uniforms.pos; |
| })"); |
| EXPECT_THAT(device.CreateBindGroupLayout(&desc), |
| BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0))); |
| } |
| |
| { |
| binding.binding = 1; |
| wgpu::RenderPipeline pipeline = RenderPipelineFromFragmentShader(R"( |
| struct S { |
| pos : vec4f |
| } |
| @group(0) @binding(1) var<uniform> uniforms : S; |
| |
| @fragment fn main() { |
| var pos : vec4f = uniforms.pos; |
| })"); |
| EXPECT_THAT(device.CreateBindGroupLayout(&desc), |
| BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0))); |
| } |
| |
| { |
| binding.binding = 2; |
| wgpu::RenderPipeline pipeline = RenderPipelineFromFragmentShader(R"( |
| struct S { |
| pos : vec4f |
| } |
| @group(0) @binding(1) var<uniform> uniforms : S; |
| |
| @fragment fn main() { |
| var pos : vec4f = uniforms.pos; |
| })"); |
| EXPECT_THAT(device.CreateBindGroupLayout(&desc), |
| Not(BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0)))); |
| } |
| } |
| |
| // Test it is valid to have duplicate bindings in the shaders. |
| TEST_F(GetBindGroupLayoutTests, DuplicateBinding) { |
| wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"( |
| struct S { |
| pos : vec4f |
| } |
| @group(0) @binding(0) var<uniform> uniform0 : S; |
| @group(1) @binding(0) var<uniform> uniform1 : S; |
| |
| @vertex fn main() -> @builtin(position) vec4f { |
| var pos : vec4f = uniform0.pos; |
| pos = uniform1.pos; |
| return vec4f(); |
| })"); |
| |
| wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( |
| struct S { |
| pos : vec4f |
| } |
| @group(1) @binding(0) var<uniform> uniforms : S; |
| |
| @fragment fn main() { |
| var pos : vec4f = uniforms.pos; |
| })"); |
| |
| utils::ComboRenderPipelineDescriptor descriptor; |
| descriptor.layout = nullptr; |
| descriptor.vertex.module = vsModule; |
| descriptor.cFragment.module = fsModule; |
| descriptor.cTargets[0].writeMask = wgpu::ColorWriteMask::None; |
| |
| device.CreateRenderPipeline(&descriptor); |
| } |
| |
| // Test that minBufferSize is set on the BGL and that the max of the min buffer sizes is used. |
| TEST_F(GetBindGroupLayoutTests, MinBufferSize) { |
| DAWN_SKIP_TEST_IF(UsesWire()); |
| |
| wgpu::ShaderModule vsModule4 = utils::CreateShaderModule(device, R"( |
| struct S { |
| pos : f32 |
| } |
| @group(0) @binding(0) var<uniform> uniforms : S; |
| |
| @vertex fn main() -> @builtin(position) vec4f { |
| var pos : f32 = uniforms.pos; |
| return vec4f(); |
| })"); |
| |
| wgpu::ShaderModule vsModule64 = utils::CreateShaderModule(device, R"( |
| struct S { |
| pos : mat4x4<f32> |
| } |
| @group(0) @binding(0) var<uniform> uniforms : S; |
| |
| @vertex fn main() -> @builtin(position) vec4f { |
| var pos : mat4x4<f32> = uniforms.pos; |
| return vec4f(); |
| })"); |
| |
| wgpu::ShaderModule fsModule4 = utils::CreateShaderModule(device, R"( |
| struct S { |
| pos : f32 |
| } |
| @group(0) @binding(0) var<uniform> uniforms : S; |
| |
| @fragment fn main() { |
| var pos : f32 = uniforms.pos; |
| })"); |
| |
| wgpu::ShaderModule fsModule64 = utils::CreateShaderModule(device, R"( |
| struct S { |
| pos : mat4x4<f32> |
| } |
| @group(0) @binding(0) var<uniform> uniforms : S; |
| |
| @fragment fn main() { |
| var pos : mat4x4<f32> = uniforms.pos; |
| })"); |
| |
| // Create BGLs with minBufferBindingSize 4 and 64. |
| wgpu::BindGroupLayoutEntry binding = {}; |
| binding.binding = 0; |
| binding.buffer.type = wgpu::BufferBindingType::Uniform; |
| binding.visibility = wgpu::ShaderStage::Fragment | wgpu::ShaderStage::Vertex; |
| |
| wgpu::BindGroupLayoutDescriptor desc = {}; |
| desc.entryCount = 1; |
| desc.entries = &binding; |
| |
| binding.buffer.minBindingSize = 4; |
| wgpu::BindGroupLayout bgl4 = device.CreateBindGroupLayout(&desc); |
| binding.buffer.minBindingSize = 64; |
| wgpu::BindGroupLayout bgl64 = device.CreateBindGroupLayout(&desc); |
| |
| utils::ComboRenderPipelineDescriptor descriptor; |
| descriptor.layout = nullptr; |
| descriptor.cTargets[0].writeMask = wgpu::ColorWriteMask::None; |
| |
| // Check with both stages using 4 bytes. |
| { |
| descriptor.vertex.module = vsModule4; |
| descriptor.cFragment.module = fsModule4; |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor); |
| EXPECT_THAT(pipeline.GetBindGroupLayout(0), BindGroupLayoutCacheEq(bgl4)); |
| } |
| |
| // Check that the max is taken between 4 and 64. |
| { |
| descriptor.vertex.module = vsModule64; |
| descriptor.cFragment.module = fsModule4; |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor); |
| EXPECT_THAT(pipeline.GetBindGroupLayout(0), BindGroupLayoutCacheEq(bgl64)); |
| } |
| |
| // Check that the order doesn't change that the max is taken. |
| { |
| descriptor.vertex.module = vsModule4; |
| descriptor.cFragment.module = fsModule64; |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor); |
| EXPECT_THAT(pipeline.GetBindGroupLayout(0), BindGroupLayoutCacheEq(bgl64)); |
| } |
| } |
| |
| // Test that the visibility is correctly aggregated if two stages have the exact same binding. |
| TEST_F(GetBindGroupLayoutTests, StageAggregation) { |
| DAWN_SKIP_TEST_IF(UsesWire()); |
| |
| wgpu::ShaderModule vsModuleNoSampler = utils::CreateShaderModule(device, R"( |
| @vertex fn main() -> @builtin(position) vec4f { |
| return vec4f(); |
| })"); |
| |
| wgpu::ShaderModule vsModuleSampler = utils::CreateShaderModule(device, R"( |
| @group(0) @binding(0) var mySampler: sampler; |
| @vertex fn main() -> @builtin(position) vec4f { |
| _ = mySampler; |
| return vec4f(); |
| })"); |
| |
| wgpu::ShaderModule fsModuleNoSampler = utils::CreateShaderModule(device, R"( |
| @fragment fn main() { |
| })"); |
| |
| wgpu::ShaderModule fsModuleSampler = utils::CreateShaderModule(device, R"( |
| @group(0) @binding(0) var mySampler: sampler; |
| @fragment fn main() { |
| _ = mySampler; |
| })"); |
| |
| // Create BGLs with minBufferBindingSize 4 and 64. |
| wgpu::BindGroupLayoutEntry binding = {}; |
| binding.binding = 0; |
| binding.sampler.type = wgpu::SamplerBindingType::Filtering; |
| |
| wgpu::BindGroupLayoutDescriptor desc = {}; |
| desc.entryCount = 1; |
| desc.entries = &binding; |
| |
| utils::ComboRenderPipelineDescriptor descriptor; |
| descriptor.layout = nullptr; |
| descriptor.cTargets[0].writeMask = wgpu::ColorWriteMask::None; |
| |
| // Check with only the vertex shader using the sampler |
| { |
| descriptor.vertex.module = vsModuleSampler; |
| descriptor.cFragment.module = fsModuleNoSampler; |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor); |
| |
| binding.visibility = wgpu::ShaderStage::Vertex; |
| EXPECT_THAT(device.CreateBindGroupLayout(&desc), |
| BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0))); |
| } |
| |
| // Check with only the fragment shader using the sampler |
| { |
| descriptor.vertex.module = vsModuleNoSampler; |
| descriptor.cFragment.module = fsModuleSampler; |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor); |
| |
| binding.visibility = wgpu::ShaderStage::Fragment; |
| EXPECT_THAT(device.CreateBindGroupLayout(&desc), |
| BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0))); |
| } |
| |
| // Check with both shaders using the sampler |
| { |
| descriptor.vertex.module = vsModuleSampler; |
| descriptor.cFragment.module = fsModuleSampler; |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor); |
| |
| binding.visibility = wgpu::ShaderStage::Fragment | wgpu::ShaderStage::Vertex; |
| EXPECT_THAT(device.CreateBindGroupLayout(&desc), |
| BindGroupLayoutCacheEq(pipeline.GetBindGroupLayout(0))); |
| } |
| } |
| |
| // Test it is invalid to have conflicting binding types in the shaders. |
| TEST_F(GetBindGroupLayoutTests, ConflictingBindingType) { |
| wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"( |
| struct S { |
| pos : vec4f |
| } |
| @group(0) @binding(0) var<uniform> ubo : S; |
| |
| @vertex fn main() -> @builtin(position) vec4f { |
| var pos : vec4f = ubo.pos; |
| return vec4f(); |
| })"); |
| |
| wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( |
| struct S { |
| pos : vec4f |
| } |
| @group(0) @binding(0) var<storage, read_write> ssbo : S; |
| |
| @fragment fn main() { |
| var pos : vec4f = ssbo.pos; |
| })"); |
| |
| utils::ComboRenderPipelineDescriptor descriptor; |
| descriptor.layout = nullptr; |
| descriptor.vertex.module = vsModule; |
| descriptor.cFragment.module = fsModule; |
| |
| ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor)); |
| } |
| |
| // Test it is invalid to have conflicting binding texture multisampling in the shaders. |
| TEST_F(GetBindGroupLayoutTests, ConflictingBindingTextureMultisampling) { |
| wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"( |
| @group(0) @binding(0) var myTexture : texture_2d<f32>; |
| |
| @vertex fn main() -> @builtin(position) vec4f { |
| _ = textureDimensions(myTexture); |
| return vec4f(); |
| })"); |
| |
| wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( |
| @group(0) @binding(0) var myTexture : texture_multisampled_2d<f32>; |
| |
| @fragment fn main() { |
| _ = textureDimensions(myTexture); |
| })"); |
| |
| utils::ComboRenderPipelineDescriptor descriptor; |
| descriptor.layout = nullptr; |
| descriptor.vertex.module = vsModule; |
| descriptor.cFragment.module = fsModule; |
| |
| ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor)); |
| } |
| |
| // Test it is invalid to have conflicting binding texture dimension in the shaders. |
| TEST_F(GetBindGroupLayoutTests, ConflictingBindingViewDimension) { |
| wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"( |
| @group(0) @binding(0) var myTexture : texture_2d<f32>; |
| |
| @vertex fn main() -> @builtin(position) vec4f { |
| _ = textureDimensions(myTexture); |
| return vec4f(); |
| })"); |
| |
| wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( |
| @group(0) @binding(0) var myTexture : texture_3d<f32>; |
| |
| @fragment fn main() { |
| _ = textureDimensions(myTexture); |
| })"); |
| |
| utils::ComboRenderPipelineDescriptor descriptor; |
| descriptor.layout = nullptr; |
| descriptor.vertex.module = vsModule; |
| descriptor.cFragment.module = fsModule; |
| |
| ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor)); |
| } |
| |
| // Test it is invalid to have conflicting binding texture component type in the shaders. |
| TEST_F(GetBindGroupLayoutTests, ConflictingBindingTextureComponentType) { |
| wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"( |
| @group(0) @binding(0) var myTexture : texture_2d<f32>; |
| |
| @vertex fn main() -> @builtin(position) vec4f { |
| _ = textureDimensions(myTexture); |
| return vec4f(); |
| })"); |
| |
| wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( |
| @group(0) @binding(0) var myTexture : texture_2d<i32>; |
| |
| @fragment fn main() { |
| _ = textureDimensions(myTexture); |
| })"); |
| |
| utils::ComboRenderPipelineDescriptor descriptor; |
| descriptor.layout = nullptr; |
| descriptor.vertex.module = vsModule; |
| descriptor.cFragment.module = fsModule; |
| |
| ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor)); |
| } |
| |
| // Test it is an error to query an out of range bind group layout. |
| TEST_F(GetBindGroupLayoutTests, OutOfRangeIndex) { |
| ASSERT_DEVICE_ERROR(RenderPipelineFromFragmentShader(R"( |
| @fragment fn main() { |
| })") |
| .GetBindGroupLayout(kMaxBindGroups)); |
| |
| ASSERT_DEVICE_ERROR(RenderPipelineFromFragmentShader(R"( |
| @fragment fn main() { |
| })") |
| .GetBindGroupLayout(kMaxBindGroups + 1)); |
| } |
| |
| // Test that unused indices return the empty bind group layout if before the last used index, an |
| // error otherwise. |
| TEST_F(GetBindGroupLayoutTests, UnusedIndex) { |
| DAWN_SKIP_TEST_IF(UsesWire()); |
| |
| wgpu::RenderPipeline pipeline = RenderPipelineFromFragmentShader(R"( |
| struct S { |
| pos : vec4f |
| } |
| @group(0) @binding(0) var<uniform> uniforms0 : S; |
| @group(2) @binding(0) var<uniform> uniforms2 : S; |
| |
| @fragment fn main() { |
| var pos : vec4f = uniforms0.pos; |
| pos = uniforms2.pos; |
| })"); |
| |
| wgpu::BindGroupLayoutDescriptor desc = {}; |
| desc.entryCount = 0; |
| desc.entries = nullptr; |
| |
| wgpu::BindGroupLayout emptyBindGroupLayout = device.CreateBindGroupLayout(&desc); |
| |
| EXPECT_THAT(pipeline.GetBindGroupLayout(0), |
| Not(BindGroupLayoutCacheEq(emptyBindGroupLayout))); // Used |
| EXPECT_THAT(pipeline.GetBindGroupLayout(1), |
| BindGroupLayoutCacheEq(emptyBindGroupLayout)); // Not used |
| EXPECT_THAT(pipeline.GetBindGroupLayout(2), |
| Not(BindGroupLayoutCacheEq(emptyBindGroupLayout))); // Used |
| ASSERT_DEVICE_ERROR(pipeline.GetBindGroupLayout(3)); // Past last defined BGL, error! |
| } |
| |
| // Test that after explicitly creating a pipeline with a pipeline layout, calling |
| // GetBindGroupLayout reflects the same bind group layouts. |
| TEST_F(GetBindGroupLayoutTests, Reflection) { |
| DAWN_SKIP_TEST_IF(UsesWire()); |
| |
| wgpu::BindGroupLayoutEntry binding = {}; |
| binding.binding = 0; |
| binding.buffer.type = wgpu::BufferBindingType::Uniform; |
| binding.visibility = wgpu::ShaderStage::Vertex; |
| |
| wgpu::BindGroupLayoutDescriptor bglDesc = {}; |
| bglDesc.entryCount = 1; |
| bglDesc.entries = &binding; |
| |
| wgpu::BindGroupLayout bindGroupLayout = device.CreateBindGroupLayout(&bglDesc); |
| |
| wgpu::PipelineLayoutDescriptor pipelineLayoutDesc = {}; |
| pipelineLayoutDesc.bindGroupLayoutCount = 1; |
| pipelineLayoutDesc.bindGroupLayouts = &bindGroupLayout; |
| |
| wgpu::PipelineLayout pipelineLayout = device.CreatePipelineLayout(&pipelineLayoutDesc); |
| |
| wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"( |
| struct S { |
| pos : vec4f |
| } |
| @group(0) @binding(0) var<uniform> uniforms : S; |
| |
| @vertex fn main() -> @builtin(position) vec4f { |
| var pos : vec4f = uniforms.pos; |
| return vec4f(); |
| })"); |
| |
| wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( |
| @fragment fn main() { |
| })"); |
| |
| utils::ComboRenderPipelineDescriptor pipelineDesc; |
| pipelineDesc.layout = pipelineLayout; |
| pipelineDesc.vertex.module = vsModule; |
| pipelineDesc.cFragment.module = fsModule; |
| pipelineDesc.cTargets[0].writeMask = wgpu::ColorWriteMask::None; |
| |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pipelineDesc); |
| |
| EXPECT_THAT(pipeline.GetBindGroupLayout(0), BindGroupLayoutEq(bindGroupLayout)); |
| } |
| |
| // Test that fragment output validation is for the correct entryPoint |
| TEST_F(GetBindGroupLayoutTests, FromCorrectEntryPoint) { |
| wgpu::ShaderModule module = utils::CreateShaderModule(device, R"( |
| struct Data { |
| data : f32 |
| } |
| @group(0) @binding(0) var<storage, read_write> data0 : Data; |
| @group(0) @binding(1) var<storage, read_write> data1 : Data; |
| |
| @compute @workgroup_size(1) fn compute0() { |
| data0.data = 0.0; |
| } |
| |
| @compute @workgroup_size(1) fn compute1() { |
| data1.data = 0.0; |
| } |
| )"); |
| |
| wgpu::ComputePipelineDescriptor pipelineDesc; |
| pipelineDesc.compute.module = module; |
| |
| // Get each entryPoint's BGL. |
| pipelineDesc.compute.entryPoint = "compute0"; |
| wgpu::ComputePipeline pipeline0 = device.CreateComputePipeline(&pipelineDesc); |
| wgpu::BindGroupLayout bgl0 = pipeline0.GetBindGroupLayout(0); |
| |
| pipelineDesc.compute.entryPoint = "compute1"; |
| wgpu::ComputePipeline pipeline1 = device.CreateComputePipeline(&pipelineDesc); |
| wgpu::BindGroupLayout bgl1 = pipeline1.GetBindGroupLayout(0); |
| |
| // Create the buffer used in the bindgroups. |
| wgpu::BufferDescriptor bufferDesc; |
| bufferDesc.size = 4; |
| bufferDesc.usage = wgpu::BufferUsage::Storage; |
| wgpu::Buffer buffer = device.CreateBuffer(&bufferDesc); |
| |
| // Success case, the BGL matches the descriptor for the bindgroup. |
| utils::MakeBindGroup(device, bgl0, {{0, buffer}}); |
| utils::MakeBindGroup(device, bgl1, {{1, buffer}}); |
| |
| // Error case, the BGL doesn't match the descriptor for the bindgroup. |
| ASSERT_DEVICE_ERROR(utils::MakeBindGroup(device, bgl0, {{1, buffer}})); |
| ASSERT_DEVICE_ERROR(utils::MakeBindGroup(device, bgl1, {{0, buffer}})); |
| } |
| |
| // Test that a pipeline full of explicitly empty BGLs correctly reflects them. |
| TEST_F(GetBindGroupLayoutTests, FullOfEmptyBGLs) { |
| DAWN_SKIP_TEST_IF(UsesWire()); |
| |
| wgpu::BindGroupLayout emptyBGL = utils::MakeBindGroupLayout(device, {}); |
| wgpu::PipelineLayout pl = |
| utils::MakePipelineLayout(device, {emptyBGL, emptyBGL, emptyBGL, emptyBGL}); |
| |
| wgpu::ComputePipelineDescriptor pipelineDesc; |
| pipelineDesc.layout = pl; |
| pipelineDesc.compute.entryPoint = "main"; |
| pipelineDesc.compute.module = utils::CreateShaderModule(device, R"( |
| @compute @workgroup_size(1) fn main() { |
| } |
| )"); |
| wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&pipelineDesc); |
| |
| EXPECT_THAT(pipeline.GetBindGroupLayout(0), BindGroupLayoutEq(emptyBGL)); |
| EXPECT_THAT(pipeline.GetBindGroupLayout(1), BindGroupLayoutEq(emptyBGL)); |
| EXPECT_THAT(pipeline.GetBindGroupLayout(2), BindGroupLayoutEq(emptyBGL)); |
| EXPECT_THAT(pipeline.GetBindGroupLayout(3), BindGroupLayoutEq(emptyBGL)); |
| } |
| |
| } // anonymous namespace |
| } // namespace dawn |