| // Copyright 2021 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 <numeric> |
| #include <string> |
| #include <vector> |
| |
| #include "dawn/tests/DawnTest.h" |
| #include "dawn/utils/ComboRenderPipelineDescriptor.h" |
| #include "dawn/utils/WGPUHelpers.h" |
| |
| namespace dawn { |
| namespace { |
| |
| class ShaderTests : public DawnTest { |
| public: |
| wgpu::Buffer CreateBuffer(const std::vector<uint32_t>& data, |
| wgpu::BufferUsage usage = wgpu::BufferUsage::Storage | |
| wgpu::BufferUsage::CopySrc) { |
| uint64_t bufferSize = static_cast<uint64_t>(data.size() * sizeof(uint32_t)); |
| return utils::CreateBufferFromData(device, data.data(), bufferSize, usage); |
| } |
| |
| wgpu::Buffer CreateBuffer(const uint32_t count, |
| wgpu::BufferUsage usage = wgpu::BufferUsage::Storage | |
| wgpu::BufferUsage::CopySrc) { |
| return CreateBuffer(std::vector<uint32_t>(count, 0), usage); |
| } |
| |
| wgpu::ComputePipeline CreateComputePipeline( |
| const std::string& shader, |
| const char* entryPoint = nullptr, |
| const std::vector<wgpu::ConstantEntry>* constants = nullptr) { |
| wgpu::ComputePipelineDescriptor csDesc; |
| csDesc.compute.module = utils::CreateShaderModule(device, shader.c_str()); |
| csDesc.compute.entryPoint = entryPoint; |
| if (constants) { |
| csDesc.compute.constants = constants->data(); |
| csDesc.compute.constantCount = constants->size(); |
| } |
| return device.CreateComputePipeline(&csDesc); |
| } |
| }; |
| |
| // Test that log2 is being properly calculated, base on crbug.com/1046622 |
| TEST_P(ShaderTests, ComputeLog2) { |
| uint32_t const kSteps = 19; |
| std::vector<uint32_t> expected{0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 32}; |
| wgpu::Buffer buffer = CreateBuffer(kSteps); |
| |
| std::string shader = R"( |
| struct Buf { |
| data : array<u32, 19> |
| } |
| |
| @group(0) @binding(0) var<storage, read_write> buf : Buf; |
| |
| @compute @workgroup_size(1) fn main() { |
| let factor : f32 = 1.0001; |
| |
| buf.data[0] = u32(log2(1.0 * factor)); |
| buf.data[1] = u32(log2(2.0 * factor)); |
| buf.data[2] = u32(log2(3.0 * factor)); |
| buf.data[3] = u32(log2(4.0 * factor)); |
| buf.data[4] = u32(log2(7.0 * factor)); |
| buf.data[5] = u32(log2(8.0 * factor)); |
| buf.data[6] = u32(log2(15.0 * factor)); |
| buf.data[7] = u32(log2(16.0 * factor)); |
| buf.data[8] = u32(log2(31.0 * factor)); |
| buf.data[9] = u32(log2(32.0 * factor)); |
| buf.data[10] = u32(log2(63.0 * factor)); |
| buf.data[11] = u32(log2(64.0 * factor)); |
| buf.data[12] = u32(log2(127.0 * factor)); |
| buf.data[13] = u32(log2(128.0 * factor)); |
| buf.data[14] = u32(log2(255.0 * factor)); |
| buf.data[15] = u32(log2(256.0 * factor)); |
| buf.data[16] = u32(log2(511.0 * factor)); |
| buf.data[17] = u32(log2(512.0 * factor)); |
| buf.data[18] = u32(log2(4294967295.0 * factor)); |
| })"; |
| |
| wgpu::ComputePipeline pipeline = CreateComputePipeline(shader, "main"); |
| |
| wgpu::BindGroup bindGroup = |
| utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), {{0, buffer}}); |
| |
| wgpu::CommandBuffer commands; |
| { |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); |
| pass.SetPipeline(pipeline); |
| pass.SetBindGroup(0, bindGroup); |
| pass.DispatchWorkgroups(1); |
| pass.End(); |
| |
| commands = encoder.Finish(); |
| } |
| |
| queue.Submit(1, &commands); |
| |
| EXPECT_BUFFER_U32_RANGE_EQ(expected.data(), buffer, 0, kSteps); |
| } |
| |
| TEST_P(ShaderTests, BadWGSL) { |
| DAWN_TEST_UNSUPPORTED_IF(HasToggleEnabled("skip_validation")); |
| |
| std::string shader = R"( |
| I am an invalid shader and should never pass validation! |
| })"; |
| ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, shader.c_str())); |
| } |
| |
| // Tests that shaders using non-struct function parameters and return values for shader stage I/O |
| // can compile and link successfully. |
| TEST_P(ShaderTests, WGSLParamIO) { |
| std::string vertexShader = R"( |
| @vertex |
| fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4f { |
| var pos = array( |
| vec2f(-1.0, 1.0), |
| vec2f( 1.0, 1.0), |
| vec2f( 0.0, -1.0)); |
| return vec4f(pos[VertexIndex], 0.0, 1.0); |
| })"; |
| wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, vertexShader.c_str()); |
| |
| std::string fragmentShader = R"( |
| @fragment |
| fn main(@builtin(position) fragCoord : vec4f) -> @location(0) vec4f { |
| return vec4f(fragCoord.xy, 0.0, 1.0); |
| })"; |
| wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, fragmentShader.c_str()); |
| |
| utils::ComboRenderPipelineDescriptor rpDesc; |
| rpDesc.vertex.module = vsModule; |
| rpDesc.cFragment.module = fsModule; |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&rpDesc); |
| } |
| |
| // Tests that a vertex shader using struct function parameters and return values for shader stage |
| // I/O can compile and link successfully against a fragement shader using compatible non-struct I/O. |
| TEST_P(ShaderTests, WGSLMixedStructParamIO) { |
| std::string vertexShader = R"( |
| struct VertexIn { |
| @location(0) position : vec3f, |
| @location(1) color : vec4f, |
| } |
| |
| struct VertexOut { |
| @location(0) color : vec4f, |
| @builtin(position) position : vec4f, |
| } |
| |
| @vertex |
| fn main(input : VertexIn) -> VertexOut { |
| var output : VertexOut; |
| output.position = vec4f(input.position, 1.0); |
| output.color = input.color; |
| return output; |
| })"; |
| wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, vertexShader.c_str()); |
| |
| std::string fragmentShader = R"( |
| @fragment |
| fn main(@location(0) color : vec4f) -> @location(0) vec4f { |
| return color; |
| })"; |
| wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, fragmentShader.c_str()); |
| |
| utils::ComboRenderPipelineDescriptor rpDesc; |
| rpDesc.vertex.module = vsModule; |
| rpDesc.cFragment.module = fsModule; |
| rpDesc.vertex.bufferCount = 1; |
| rpDesc.cBuffers[0].attributeCount = 2; |
| rpDesc.cBuffers[0].arrayStride = 28; |
| rpDesc.cAttributes[0].shaderLocation = 0; |
| rpDesc.cAttributes[0].format = wgpu::VertexFormat::Float32x3; |
| rpDesc.cAttributes[1].shaderLocation = 1; |
| rpDesc.cAttributes[1].format = wgpu::VertexFormat::Float32x4; |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&rpDesc); |
| } |
| |
| // Tests that shaders using struct function parameters and return values for shader stage I/O |
| // can compile and link successfully. |
| TEST_P(ShaderTests, WGSLStructIO) { |
| std::string vertexShader = R"( |
| struct VertexIn { |
| @location(0) position : vec3f, |
| @location(1) color : vec4f, |
| } |
| |
| struct VertexOut { |
| @location(0) color : vec4f, |
| @builtin(position) position : vec4f, |
| } |
| |
| @vertex |
| fn main(input : VertexIn) -> VertexOut { |
| var output : VertexOut; |
| output.position = vec4f(input.position, 1.0); |
| output.color = input.color; |
| return output; |
| })"; |
| wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, vertexShader.c_str()); |
| |
| std::string fragmentShader = R"( |
| struct FragmentIn { |
| @location(0) color : vec4f, |
| @builtin(position) fragCoord : vec4f, |
| } |
| |
| @fragment |
| fn main(input : FragmentIn) -> @location(0) vec4f { |
| return input.color * input.fragCoord; |
| })"; |
| wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, fragmentShader.c_str()); |
| |
| utils::ComboRenderPipelineDescriptor rpDesc; |
| rpDesc.vertex.module = vsModule; |
| rpDesc.cFragment.module = fsModule; |
| rpDesc.vertex.bufferCount = 1; |
| rpDesc.cBuffers[0].attributeCount = 2; |
| rpDesc.cBuffers[0].arrayStride = 28; |
| rpDesc.cAttributes[0].shaderLocation = 0; |
| rpDesc.cAttributes[0].format = wgpu::VertexFormat::Float32x3; |
| rpDesc.cAttributes[1].shaderLocation = 1; |
| rpDesc.cAttributes[1].format = wgpu::VertexFormat::Float32x4; |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&rpDesc); |
| } |
| |
| // Tests that shaders I/O structs that us compatible locations but are not sorted by hand can link. |
| TEST_P(ShaderTests, WGSLUnsortedStructIO) { |
| std::string vertexShader = R"( |
| struct VertexIn { |
| @location(0) position : vec3f, |
| @location(1) color : vec4f, |
| } |
| |
| struct VertexOut { |
| @builtin(position) position : vec4f, |
| @location(0) color : vec4f, |
| } |
| |
| @vertex |
| fn main(input : VertexIn) -> VertexOut { |
| var output : VertexOut; |
| output.position = vec4f(input.position, 1.0); |
| output.color = input.color; |
| return output; |
| })"; |
| wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, vertexShader.c_str()); |
| |
| std::string fragmentShader = R"( |
| struct FragmentIn { |
| @location(0) color : vec4f, |
| @builtin(position) fragCoord : vec4f, |
| } |
| |
| @fragment |
| fn main(input : FragmentIn) -> @location(0) vec4f { |
| return input.color * input.fragCoord; |
| })"; |
| wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, fragmentShader.c_str()); |
| |
| utils::ComboRenderPipelineDescriptor rpDesc; |
| rpDesc.vertex.module = vsModule; |
| rpDesc.cFragment.module = fsModule; |
| rpDesc.vertex.bufferCount = 1; |
| rpDesc.cBuffers[0].attributeCount = 2; |
| rpDesc.cBuffers[0].arrayStride = 28; |
| rpDesc.cAttributes[0].shaderLocation = 0; |
| rpDesc.cAttributes[0].format = wgpu::VertexFormat::Float32x3; |
| rpDesc.cAttributes[1].shaderLocation = 1; |
| rpDesc.cAttributes[1].format = wgpu::VertexFormat::Float32x4; |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&rpDesc); |
| } |
| |
| // Tests that shaders I/O structs can be shared between vertex and fragment shaders. |
| TEST_P(ShaderTests, WGSLSharedStructIO) { |
| std::string shader = R"( |
| struct VertexIn { |
| @location(0) position : vec3f, |
| @location(1) color : vec4f, |
| } |
| |
| struct VertexOut { |
| @location(0) color : vec4f, |
| @builtin(position) position : vec4f, |
| } |
| |
| @vertex |
| fn vertexMain(input : VertexIn) -> VertexOut { |
| var output : VertexOut; |
| output.position = vec4f(input.position, 1.0); |
| output.color = input.color; |
| return output; |
| } |
| |
| @fragment |
| fn fragmentMain(input : VertexOut) -> @location(0) vec4f { |
| return input.color; |
| })"; |
| wgpu::ShaderModule shaderModule = utils::CreateShaderModule(device, shader.c_str()); |
| |
| utils::ComboRenderPipelineDescriptor rpDesc; |
| rpDesc.vertex.module = shaderModule; |
| rpDesc.cFragment.module = shaderModule; |
| rpDesc.vertex.bufferCount = 1; |
| rpDesc.cBuffers[0].attributeCount = 2; |
| rpDesc.cBuffers[0].arrayStride = 28; |
| rpDesc.cAttributes[0].shaderLocation = 0; |
| rpDesc.cAttributes[0].format = wgpu::VertexFormat::Float32x3; |
| rpDesc.cAttributes[1].shaderLocation = 1; |
| rpDesc.cAttributes[1].format = wgpu::VertexFormat::Float32x4; |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&rpDesc); |
| } |
| |
| // Tests that sparse input output locations should work properly. |
| // This test is not in dawn_unittests/RenderPipelineValidationTests because we want to test the |
| // compilation of the pipeline in D3D12 backend. |
| TEST_P(ShaderTests, WGSLInterstageVariablesSparse) { |
| std::string shader = R"( |
| struct ShaderIO { |
| @builtin(position) position : vec4f, |
| @location(1) attribute1 : vec4f, |
| @location(3) attribute3 : vec4f, |
| } |
| |
| @vertex |
| fn vertexMain() -> ShaderIO { |
| var output : ShaderIO; |
| output.position = vec4f(0.0, 0.0, 0.0, 1.0); |
| output.attribute1 = vec4f(0.0, 0.0, 0.0, 1.0); |
| output.attribute3 = vec4f(0.0, 0.0, 0.0, 1.0); |
| return output; |
| } |
| |
| @fragment |
| fn fragmentMain(input : ShaderIO) -> @location(0) vec4f { |
| return input.attribute1; |
| })"; |
| wgpu::ShaderModule shaderModule = utils::CreateShaderModule(device, shader.c_str()); |
| |
| utils::ComboRenderPipelineDescriptor rpDesc; |
| rpDesc.vertex.module = shaderModule; |
| rpDesc.cFragment.module = shaderModule; |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&rpDesc); |
| } |
| |
| // Tests that interstage built-in inputs and outputs usage mismatch don't mess up with input-output |
| // locations. |
| // This test is not in dawn_unittests/RenderPipelineValidationTests because we want to test the |
| // compilation of the pipeline in D3D12 backend. |
| TEST_P(ShaderTests, WGSLInterstageVariablesBuiltinsMismatched) { |
| std::string shader = R"( |
| struct VertexOut { |
| @builtin(position) position : vec4f, |
| @location(1) attribute1 : f32, |
| @location(3) attribute3 : vec4f, |
| } |
| |
| struct FragmentIn { |
| @location(3) attribute3 : vec4f, |
| @builtin(front_facing) front_facing : bool, |
| @location(1) attribute1 : f32, |
| @builtin(position) position : vec4f, |
| } |
| |
| @vertex |
| fn vertexMain() -> VertexOut { |
| var output : VertexOut; |
| output.position = vec4f(0.0, 0.0, 0.0, 1.0); |
| output.attribute1 = 1.0; |
| output.attribute3 = vec4f(0.0, 0.0, 0.0, 1.0); |
| return output; |
| } |
| |
| @fragment |
| fn fragmentMain(input : FragmentIn) -> @location(0) vec4f { |
| _ = input.front_facing; |
| _ = input.position.x; |
| return input.attribute3; |
| })"; |
| wgpu::ShaderModule shaderModule = utils::CreateShaderModule(device, shader.c_str()); |
| |
| utils::ComboRenderPipelineDescriptor rpDesc; |
| rpDesc.vertex.module = shaderModule; |
| rpDesc.cFragment.module = shaderModule; |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&rpDesc); |
| } |
| |
| // Tests that interstage inputs could be a prefix subset of the outputs. |
| // This test is not in dawn_unittests/RenderPipelineValidationTests because we want to test the |
| // compilation of the pipeline in D3D12 backend. |
| TEST_P(ShaderTests, WGSLInterstageVariablesPrefixSubset) { |
| std::string shader = R"( |
| struct VertexOut { |
| @builtin(position) position : vec4f, |
| @location(1) attribute1 : f32, |
| @location(3) attribute3 : vec4f, |
| } |
| |
| struct FragmentIn { |
| @location(1) attribute1 : f32, |
| @builtin(position) position : vec4f, |
| } |
| |
| @vertex |
| fn vertexMain() -> VertexOut { |
| var output : VertexOut; |
| output.position = vec4f(0.0, 0.0, 0.0, 1.0); |
| output.attribute1 = 1.0; |
| output.attribute3 = vec4f(0.0, 0.0, 0.0, 1.0); |
| return output; |
| } |
| |
| @fragment |
| fn fragmentMain(input : FragmentIn) -> @location(0) vec4f { |
| _ = input.position.x; |
| return vec4f(input.attribute1, 0.0, 0.0, 1.0); |
| })"; |
| wgpu::ShaderModule shaderModule = utils::CreateShaderModule(device, shader.c_str()); |
| |
| utils::ComboRenderPipelineDescriptor rpDesc; |
| rpDesc.vertex.module = shaderModule; |
| rpDesc.cFragment.module = shaderModule; |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&rpDesc); |
| } |
| |
| // Tests that interstage inputs could be a sparse non-prefix subset of the outputs. |
| // This test is not in dawn_unittests/RenderPipelineValidationTests because we want to test the |
| // compilation of the pipeline in D3D12 backend. |
| TEST_P(ShaderTests, WGSLInterstageVariablesSparseSubset) { |
| std::string shader = R"( |
| struct VertexOut { |
| @builtin(position) position : vec4f, |
| @location(1) attribute1 : f32, |
| @location(3) attribute3 : vec4f, |
| } |
| |
| struct FragmentIn { |
| @location(3) attribute3 : vec4f, |
| @builtin(position) position : vec4f, |
| } |
| |
| @vertex |
| fn vertexMain() -> VertexOut { |
| var output : VertexOut; |
| output.position = vec4f(0.0, 0.0, 0.0, 1.0); |
| output.attribute1 = 1.0; |
| output.attribute3 = vec4f(0.0, 0.0, 0.0, 1.0); |
| return output; |
| } |
| |
| @fragment |
| fn fragmentMain(input : FragmentIn) -> @location(0) vec4f { |
| _ = input.position.x; |
| return input.attribute3; |
| })"; |
| wgpu::ShaderModule shaderModule = utils::CreateShaderModule(device, shader.c_str()); |
| |
| utils::ComboRenderPipelineDescriptor rpDesc; |
| rpDesc.vertex.module = shaderModule; |
| rpDesc.cFragment.module = shaderModule; |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&rpDesc); |
| } |
| |
| // Tests that interstage inputs could be a sparse non-prefix subset of the outputs, and that |
| // fragment inputs are unused. This test is not in dawn_unittests/RenderPipelineValidationTests |
| // because we want to test the compilation of the pipeline in D3D12 backend. |
| TEST_P(ShaderTests, WGSLInterstageVariablesSparseSubsetUnused) { |
| std::string shader = R"( |
| struct VertexOut { |
| @builtin(position) position : vec4f, |
| @location(1) attribute1 : f32, |
| @location(3) attribute3 : vec4f, |
| } |
| |
| struct FragmentIn { |
| @location(3) attribute3 : vec4f, |
| @builtin(position) position : vec4f, |
| } |
| |
| @vertex |
| fn vertexMain() -> VertexOut { |
| var output : VertexOut; |
| output.position = vec4f(0.0, 0.0, 0.0, 1.0); |
| output.attribute1 = 1.0; |
| output.attribute3 = vec4f(0.0, 0.0, 0.0, 1.0); |
| return output; |
| } |
| |
| @fragment |
| fn fragmentMain(input : FragmentIn) -> @location(0) vec4f { |
| return vec4f(0.0, 0.0, 0.0, 1.0); |
| })"; |
| wgpu::ShaderModule shaderModule = utils::CreateShaderModule(device, shader.c_str()); |
| |
| utils::ComboRenderPipelineDescriptor rpDesc; |
| rpDesc.vertex.module = shaderModule; |
| rpDesc.cFragment.module = shaderModule; |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&rpDesc); |
| } |
| |
| // Tests that interstage inputs could be empty when outputs are not. |
| // This test is not in dawn_unittests/RenderPipelineValidationTests because we want to test the |
| // compilation of the pipeline in D3D12 backend. |
| TEST_P(ShaderTests, WGSLInterstageVariablesEmptySubset) { |
| std::string shader = R"( |
| struct VertexOut { |
| @builtin(position) position : vec4f, |
| @location(1) attribute1 : f32, |
| @location(3) attribute3 : vec4f, |
| } |
| |
| @vertex |
| fn vertexMain() -> VertexOut { |
| var output : VertexOut; |
| output.position = vec4f(0.0, 0.0, 0.0, 1.0); |
| output.attribute1 = 1.0; |
| output.attribute3 = vec4f(0.0, 0.0, 0.0, 1.0); |
| return output; |
| } |
| |
| @fragment |
| fn fragmentMain() -> @location(0) vec4f { |
| return vec4f(0.0, 0.0, 0.0, 1.0); |
| })"; |
| wgpu::ShaderModule shaderModule = utils::CreateShaderModule(device, shader.c_str()); |
| |
| utils::ComboRenderPipelineDescriptor rpDesc; |
| rpDesc.vertex.module = shaderModule; |
| rpDesc.cFragment.module = shaderModule; |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&rpDesc); |
| } |
| |
| // Regression test for crbug.com/dawn/1733. Even when user defined attribute input is empty, |
| // Builtin input for the next stage could still cause register mismatch issue on D3D12 HLSL |
| // compiler. So the TruncateInterstageVariables transform should still be run. This test is not in |
| // dawn_unittests/RenderPipelineValidationTests because we want to test the compilation of the |
| // pipeline in D3D12 backend. |
| TEST_P(ShaderTests, WGSLInterstageVariablesEmptyUserAttributeSubset) { |
| std::string shader = R"( |
| struct VertexOut { |
| @builtin(position) position : vec4f, |
| @location(1) attribute1 : f32, |
| @location(3) attribute3 : vec4f, |
| } |
| |
| @vertex |
| fn vertexMain() -> VertexOut { |
| var output : VertexOut; |
| output.position = vec4f(0.0, 0.0, 0.0, 1.0); |
| output.attribute1 = 1.0; |
| output.attribute3 = vec4f(0.0, 0.0, 0.0, 1.0); |
| return output; |
| } |
| |
| @fragment |
| fn fragmentMain(@builtin(position) position : vec4<f32>) -> @location(0) vec4f { |
| return vec4f(0.0, 0.0, 0.0, 1.0); |
| })"; |
| wgpu::ShaderModule shaderModule = utils::CreateShaderModule(device, shader.c_str()); |
| |
| utils::ComboRenderPipelineDescriptor rpDesc; |
| rpDesc.vertex.module = shaderModule; |
| rpDesc.cFragment.module = shaderModule; |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&rpDesc); |
| } |
| |
| // This is a regression test for an issue caused by the FirstIndexOffset transfrom being done before |
| // the BindingRemapper, causing an intermediate AST to be invalid (and fail the overall |
| // compilation). |
| TEST_P(ShaderTests, FirstIndexOffsetRegisterConflictInHLSLTransforms) { |
| const char* shader = R"( |
| // Dumped WGSL: |
| |
| struct Inputs { |
| @location(1) attrib1 : u32, |
| // The extra register added to handle base_vertex for vertex_index conflicts with [1] |
| @builtin(vertex_index) vertexIndex: u32, |
| } |
| |
| // [1] a binding point that conflicts with the regitster |
| struct S1 { data : array<vec4u, 20> } |
| @group(0) @binding(1) var<uniform> providedData1 : S1; |
| |
| @vertex fn vsMain(input : Inputs) -> @builtin(position) vec4f { |
| _ = providedData1.data[input.vertexIndex][0]; |
| return vec4f(); |
| } |
| |
| @fragment fn fsMain() -> @location(0) vec4f { |
| return vec4f(); |
| } |
| )"; |
| auto module = utils::CreateShaderModule(device, shader); |
| |
| utils::ComboRenderPipelineDescriptor rpDesc; |
| rpDesc.vertex.module = module; |
| rpDesc.cFragment.module = module; |
| rpDesc.vertex.bufferCount = 1; |
| rpDesc.cBuffers[0].attributeCount = 1; |
| rpDesc.cBuffers[0].arrayStride = 16; |
| rpDesc.cAttributes[0].shaderLocation = 1; |
| rpDesc.cAttributes[0].format = wgpu::VertexFormat::Uint8x2; |
| device.CreateRenderPipeline(&rpDesc); |
| } |
| |
| // Test that WGSL built-in variable @sample_index can be used in fragment shaders. |
| TEST_P(ShaderTests, SampleIndex) { |
| // TODO(crbug.com/dawn/673): Work around or enforce via validation that sample variables are not |
| // supported on some platforms. |
| DAWN_TEST_UNSUPPORTED_IF(HasToggleEnabled("disable_sample_variables")); |
| |
| // Compat mode does not support sample_index |
| DAWN_TEST_UNSUPPORTED_IF(IsCompatibilityMode()); |
| |
| wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"( |
| @vertex |
| fn main(@location(0) pos : vec4f) -> @builtin(position) vec4f { |
| return pos; |
| })"); |
| |
| wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( |
| @fragment fn main(@builtin(sample_index) sampleIndex : u32) |
| -> @location(0) vec4f { |
| return vec4f(f32(sampleIndex), 1.0, 0.0, 1.0); |
| })"); |
| |
| utils::ComboRenderPipelineDescriptor descriptor; |
| descriptor.vertex.module = vsModule; |
| descriptor.cFragment.module = fsModule; |
| descriptor.primitive.topology = wgpu::PrimitiveTopology::TriangleList; |
| descriptor.vertex.bufferCount = 1; |
| descriptor.cBuffers[0].arrayStride = 4 * sizeof(float); |
| descriptor.cBuffers[0].attributeCount = 1; |
| descriptor.cAttributes[0].format = wgpu::VertexFormat::Float32x4; |
| descriptor.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm; |
| |
| device.CreateRenderPipeline(&descriptor); |
| } |
| |
| // Test overridable constants without numeric identifiers |
| TEST_P(ShaderTests, OverridableConstants) { |
| uint32_t const kCount = 11; |
| std::vector<uint32_t> expected(kCount); |
| std::iota(expected.begin(), expected.end(), 0); |
| wgpu::Buffer buffer = CreateBuffer(kCount); |
| |
| std::string shader = R"( |
| override c0: bool; // type: bool |
| override c1: bool = false; // default override |
| override c2: f32; // type: float32 |
| override c3: f32 = 0.0; // default override |
| override c4: f32 = 4.0; // default |
| override c5: i32; // type: int32 |
| override c6: i32 = 0; // default override |
| override c7: i32 = 7; // default |
| override c8: u32; // type: uint32 |
| override c9: u32 = 0u; // default override |
| override c10: u32 = 10u; // default |
| |
| struct Buf { |
| data : array<u32, 11> |
| } |
| |
| @group(0) @binding(0) var<storage, read_write> buf : Buf; |
| |
| @compute @workgroup_size(1) fn main() { |
| buf.data[0] = u32(c0); |
| buf.data[1] = u32(c1); |
| buf.data[2] = u32(c2); |
| buf.data[3] = u32(c3); |
| buf.data[4] = u32(c4); |
| buf.data[5] = u32(c5); |
| buf.data[6] = u32(c6); |
| buf.data[7] = u32(c7); |
| buf.data[8] = u32(c8); |
| buf.data[9] = u32(c9); |
| buf.data[10] = u32(c10); |
| })"; |
| |
| std::vector<wgpu::ConstantEntry> constants; |
| constants.push_back({nullptr, "c0", 0}); |
| constants.push_back({nullptr, "c1", 1}); |
| constants.push_back({nullptr, "c2", 2}); |
| constants.push_back({nullptr, "c3", 3}); |
| // c4 is not assigned, testing default value |
| constants.push_back({nullptr, "c5", 5}); |
| constants.push_back({nullptr, "c6", 6}); |
| // c7 is not assigned, testing default value |
| constants.push_back({nullptr, "c8", 8}); |
| constants.push_back({nullptr, "c9", 9}); |
| // c10 is not assigned, testing default value |
| |
| wgpu::ComputePipeline pipeline = CreateComputePipeline(shader, "main", &constants); |
| |
| wgpu::BindGroup bindGroup = |
| utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), {{0, buffer}}); |
| |
| wgpu::CommandBuffer commands; |
| { |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); |
| pass.SetPipeline(pipeline); |
| pass.SetBindGroup(0, bindGroup); |
| pass.DispatchWorkgroups(1); |
| pass.End(); |
| |
| commands = encoder.Finish(); |
| } |
| |
| queue.Submit(1, &commands); |
| |
| EXPECT_BUFFER_U32_RANGE_EQ(expected.data(), buffer, 0, kCount); |
| } |
| |
| // Test one shader shared by two pipelines with different constants overridden |
| TEST_P(ShaderTests, OverridableConstantsSharedShader) { |
| std::vector<uint32_t> expected1{1}; |
| wgpu::Buffer buffer1 = CreateBuffer(expected1.size()); |
| std::vector<uint32_t> expected2{2}; |
| wgpu::Buffer buffer2 = CreateBuffer(expected2.size()); |
| |
| std::string shader = R"( |
| override a: u32; |
| |
| struct Buf { |
| data : array<u32, 1> |
| } |
| |
| @group(0) @binding(0) var<storage, read_write> buf : Buf; |
| |
| @compute @workgroup_size(1) fn main() { |
| buf.data[0] = a; |
| })"; |
| |
| std::vector<wgpu::ConstantEntry> constants1; |
| constants1.push_back({nullptr, "a", 1}); |
| std::vector<wgpu::ConstantEntry> constants2; |
| constants2.push_back({nullptr, "a", 2}); |
| |
| wgpu::ComputePipeline pipeline1 = CreateComputePipeline(shader, "main", &constants1); |
| wgpu::ComputePipeline pipeline2 = CreateComputePipeline(shader, "main", &constants2); |
| |
| wgpu::BindGroup bindGroup1 = |
| utils::MakeBindGroup(device, pipeline1.GetBindGroupLayout(0), {{0, buffer1}}); |
| wgpu::BindGroup bindGroup2 = |
| utils::MakeBindGroup(device, pipeline2.GetBindGroupLayout(0), {{0, buffer2}}); |
| |
| wgpu::CommandBuffer commands; |
| { |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); |
| pass.SetPipeline(pipeline1); |
| pass.SetBindGroup(0, bindGroup1); |
| pass.DispatchWorkgroups(1); |
| pass.SetPipeline(pipeline2); |
| pass.SetBindGroup(0, bindGroup2); |
| pass.DispatchWorkgroups(1); |
| pass.End(); |
| |
| commands = encoder.Finish(); |
| } |
| |
| queue.Submit(1, &commands); |
| |
| EXPECT_BUFFER_U32_RANGE_EQ(expected1.data(), buffer1, 0, expected1.size()); |
| EXPECT_BUFFER_U32_RANGE_EQ(expected2.data(), buffer2, 0, expected2.size()); |
| } |
| |
| // Test overridable constants work with workgroup size |
| TEST_P(ShaderTests, OverridableConstantsWorkgroupSize) { |
| std::string shader = R"( |
| override x: u32; |
| |
| struct Buf { |
| data : array<u32, 1> |
| } |
| |
| @group(0) @binding(0) var<storage, read_write> buf : Buf; |
| |
| @compute @workgroup_size(x) fn main( |
| @builtin(local_invocation_id) local_invocation_id : vec3u |
| ) { |
| if (local_invocation_id.x >= x - 1) { |
| buf.data[0] = local_invocation_id.x + 1; |
| } |
| })"; |
| |
| const uint32_t workgroup_size_x_1 = 16u; |
| const uint32_t workgroup_size_x_2 = 64u; |
| |
| std::vector<uint32_t> expected1{workgroup_size_x_1}; |
| wgpu::Buffer buffer1 = CreateBuffer(expected1.size()); |
| std::vector<uint32_t> expected2{workgroup_size_x_2}; |
| wgpu::Buffer buffer2 = CreateBuffer(expected2.size()); |
| |
| std::vector<wgpu::ConstantEntry> constants1; |
| constants1.push_back({nullptr, "x", static_cast<double>(workgroup_size_x_1)}); |
| std::vector<wgpu::ConstantEntry> constants2; |
| constants2.push_back({nullptr, "x", static_cast<double>(workgroup_size_x_2)}); |
| |
| wgpu::ComputePipeline pipeline1 = CreateComputePipeline(shader, "main", &constants1); |
| wgpu::ComputePipeline pipeline2 = CreateComputePipeline(shader, "main", &constants2); |
| |
| wgpu::BindGroup bindGroup1 = |
| utils::MakeBindGroup(device, pipeline1.GetBindGroupLayout(0), {{0, buffer1}}); |
| wgpu::BindGroup bindGroup2 = |
| utils::MakeBindGroup(device, pipeline2.GetBindGroupLayout(0), {{0, buffer2}}); |
| |
| wgpu::CommandBuffer commands; |
| { |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); |
| pass.SetPipeline(pipeline1); |
| pass.SetBindGroup(0, bindGroup1); |
| pass.DispatchWorkgroups(1); |
| pass.SetPipeline(pipeline2); |
| pass.SetBindGroup(0, bindGroup2); |
| pass.DispatchWorkgroups(1); |
| pass.End(); |
| |
| commands = encoder.Finish(); |
| } |
| |
| queue.Submit(1, &commands); |
| |
| EXPECT_BUFFER_U32_RANGE_EQ(expected1.data(), buffer1, 0, expected1.size()); |
| EXPECT_BUFFER_U32_RANGE_EQ(expected2.data(), buffer2, 0, expected2.size()); |
| } |
| |
| // Test overridable constants with numeric identifiers |
| TEST_P(ShaderTests, OverridableConstantsNumericIdentifiers) { |
| uint32_t const kCount = 4; |
| std::vector<uint32_t> expected{1u, 2u, 3u, 0u}; |
| wgpu::Buffer buffer = CreateBuffer(kCount); |
| |
| std::string shader = R"( |
| @id(1001) override c1: u32; // some big numeric id |
| @id(1) override c2: u32 = 0u; // id == 1 might collide with some generated constant id |
| @id(1003) override c3: u32 = 3u; // default |
| @id(1004) override c4: u32; // default unspecified |
| |
| struct Buf { |
| data : array<u32, 4> |
| } |
| |
| @group(0) @binding(0) var<storage, read_write> buf : Buf; |
| |
| @compute @workgroup_size(1) fn main() { |
| buf.data[0] = c1; |
| buf.data[1] = c2; |
| buf.data[2] = c3; |
| buf.data[3] = c4; |
| })"; |
| |
| std::vector<wgpu::ConstantEntry> constants; |
| constants.push_back({nullptr, "1001", 1}); |
| constants.push_back({nullptr, "1", 2}); |
| // c3 is not assigned, testing default value |
| constants.push_back({nullptr, "1004", 0}); |
| |
| wgpu::ComputePipeline pipeline = CreateComputePipeline(shader, "main", &constants); |
| |
| wgpu::BindGroup bindGroup = |
| utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), {{0, buffer}}); |
| |
| wgpu::CommandBuffer commands; |
| { |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); |
| pass.SetPipeline(pipeline); |
| pass.SetBindGroup(0, bindGroup); |
| pass.DispatchWorkgroups(1); |
| pass.End(); |
| |
| commands = encoder.Finish(); |
| } |
| |
| queue.Submit(1, &commands); |
| |
| EXPECT_BUFFER_U32_RANGE_EQ(expected.data(), buffer, 0, kCount); |
| } |
| |
| // Test overridable constants precision |
| // D3D12 HLSL shader uses defines so we want float number to have enough precision |
| TEST_P(ShaderTests, OverridableConstantsPrecision) { |
| uint32_t const kCount = 2; |
| float const kValue1 = 3.14159; |
| float const kValue2 = 3.141592653589793238; |
| std::vector<float> expected{kValue1, kValue2}; |
| wgpu::Buffer buffer = CreateBuffer(kCount); |
| |
| std::string shader = R"( |
| @id(1001) override c1: f32; |
| @id(1002) override c2: f32; |
| |
| struct Buf { |
| data : array<f32, 2> |
| } |
| |
| @group(0) @binding(0) var<storage, read_write> buf : Buf; |
| |
| @compute @workgroup_size(1) fn main() { |
| buf.data[0] = c1; |
| buf.data[1] = c2; |
| })"; |
| |
| std::vector<wgpu::ConstantEntry> constants; |
| constants.push_back({nullptr, "1001", kValue1}); |
| constants.push_back({nullptr, "1002", kValue2}); |
| wgpu::ComputePipeline pipeline = CreateComputePipeline(shader, "main", &constants); |
| |
| wgpu::BindGroup bindGroup = |
| utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), {{0, buffer}}); |
| |
| wgpu::CommandBuffer commands; |
| { |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); |
| pass.SetPipeline(pipeline); |
| pass.SetBindGroup(0, bindGroup); |
| pass.DispatchWorkgroups(1); |
| pass.End(); |
| |
| commands = encoder.Finish(); |
| } |
| |
| queue.Submit(1, &commands); |
| |
| EXPECT_BUFFER_FLOAT_RANGE_EQ(expected.data(), buffer, 0, kCount); |
| } |
| |
| // Test overridable constants for different entry points |
| TEST_P(ShaderTests, OverridableConstantsMultipleEntryPoints) { |
| uint32_t const kCount = 1; |
| std::vector<uint32_t> expected1{1u}; |
| std::vector<uint32_t> expected2{2u}; |
| std::vector<uint32_t> expected3{3u}; |
| |
| wgpu::Buffer buffer1 = CreateBuffer(kCount); |
| wgpu::Buffer buffer2 = CreateBuffer(kCount); |
| wgpu::Buffer buffer3 = CreateBuffer(kCount); |
| |
| std::string shader = R"( |
| @id(1001) override c1: u32; |
| @id(1002) override c2: u32; |
| @id(1003) override c3: u32; |
| |
| struct Buf { |
| data : array<u32, 1> |
| } |
| |
| @group(0) @binding(0) var<storage, read_write> buf : Buf; |
| |
| @compute @workgroup_size(1) fn main1() { |
| buf.data[0] = c1; |
| } |
| |
| @compute @workgroup_size(1) fn main2() { |
| buf.data[0] = c2; |
| } |
| |
| @compute @workgroup_size(c3) fn main3() { |
| buf.data[0] = 3u; |
| } |
| )"; |
| |
| std::vector<wgpu::ConstantEntry> constants1; |
| constants1.push_back({nullptr, "1001", 1}); |
| std::vector<wgpu::ConstantEntry> constants2; |
| constants2.push_back({nullptr, "1002", 2}); |
| std::vector<wgpu::ConstantEntry> constants3; |
| constants3.push_back({nullptr, "1003", 1}); |
| |
| wgpu::ShaderModule shaderModule = utils::CreateShaderModule(device, shader.c_str()); |
| |
| wgpu::ComputePipelineDescriptor csDesc1; |
| csDesc1.compute.module = shaderModule; |
| csDesc1.compute.entryPoint = "main1"; |
| csDesc1.compute.constants = constants1.data(); |
| csDesc1.compute.constantCount = constants1.size(); |
| wgpu::ComputePipeline pipeline1 = device.CreateComputePipeline(&csDesc1); |
| |
| wgpu::ComputePipelineDescriptor csDesc2; |
| csDesc2.compute.module = shaderModule; |
| csDesc2.compute.entryPoint = "main2"; |
| csDesc2.compute.constants = constants2.data(); |
| csDesc2.compute.constantCount = constants2.size(); |
| wgpu::ComputePipeline pipeline2 = device.CreateComputePipeline(&csDesc2); |
| |
| wgpu::ComputePipelineDescriptor csDesc3; |
| csDesc3.compute.module = shaderModule; |
| csDesc3.compute.entryPoint = "main3"; |
| csDesc3.compute.constants = constants3.data(); |
| csDesc3.compute.constantCount = constants3.size(); |
| wgpu::ComputePipeline pipeline3 = device.CreateComputePipeline(&csDesc3); |
| |
| wgpu::BindGroup bindGroup1 = |
| utils::MakeBindGroup(device, pipeline1.GetBindGroupLayout(0), {{0, buffer1}}); |
| wgpu::BindGroup bindGroup2 = |
| utils::MakeBindGroup(device, pipeline2.GetBindGroupLayout(0), {{0, buffer2}}); |
| wgpu::BindGroup bindGroup3 = |
| utils::MakeBindGroup(device, pipeline3.GetBindGroupLayout(0), {{0, buffer3}}); |
| |
| wgpu::CommandBuffer commands; |
| { |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); |
| pass.SetPipeline(pipeline1); |
| pass.SetBindGroup(0, bindGroup1); |
| pass.DispatchWorkgroups(1); |
| |
| pass.SetPipeline(pipeline2); |
| pass.SetBindGroup(0, bindGroup2); |
| pass.DispatchWorkgroups(1); |
| |
| pass.SetPipeline(pipeline3); |
| pass.SetBindGroup(0, bindGroup3); |
| pass.DispatchWorkgroups(1); |
| |
| pass.End(); |
| |
| commands = encoder.Finish(); |
| } |
| |
| queue.Submit(1, &commands); |
| |
| EXPECT_BUFFER_U32_RANGE_EQ(expected1.data(), buffer1, 0, kCount); |
| EXPECT_BUFFER_U32_RANGE_EQ(expected2.data(), buffer2, 0, kCount); |
| EXPECT_BUFFER_U32_RANGE_EQ(expected3.data(), buffer3, 0, kCount); |
| } |
| |
| // Test overridable constants with render pipeline |
| // Draw a triangle covering the render target, with vertex position and color values from |
| // overridable constants |
| TEST_P(ShaderTests, OverridableConstantsRenderPipeline) { |
| wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"( |
| @id(1111) override xright: f32; |
| @id(2222) override ytop: f32; |
| @vertex |
| fn main(@builtin(vertex_index) VertexIndex : u32) |
| -> @builtin(position) vec4f { |
| var pos = array( |
| vec2f(-1.0, ytop), |
| vec2f(-1.0, -ytop), |
| vec2f(xright, 0.0)); |
| |
| return vec4f(pos[VertexIndex], 0.0, 1.0); |
| })"); |
| |
| wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( |
| @id(1000) override intensity: f32 = 0.0; |
| @fragment fn main() |
| -> @location(0) vec4f { |
| return vec4f(intensity, intensity, intensity, 1.0); |
| })"); |
| |
| utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, 1, 1); |
| |
| utils::ComboRenderPipelineDescriptor descriptor; |
| descriptor.vertex.module = vsModule; |
| descriptor.cFragment.module = fsModule; |
| descriptor.primitive.topology = wgpu::PrimitiveTopology::TriangleList; |
| descriptor.cTargets[0].format = renderPass.colorFormat; |
| |
| std::vector<wgpu::ConstantEntry> vertexConstants; |
| vertexConstants.push_back({nullptr, "1111", 3.0}); // x right |
| vertexConstants.push_back({nullptr, "2222", 3.0}); // y top |
| descriptor.vertex.constants = vertexConstants.data(); |
| descriptor.vertex.constantCount = vertexConstants.size(); |
| std::vector<wgpu::ConstantEntry> fragmentConstants; |
| fragmentConstants.push_back({nullptr, "1000", 1.0}); // color intensity |
| descriptor.cFragment.constants = fragmentConstants.data(); |
| descriptor.cFragment.constantCount = fragmentConstants.size(); |
| |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor); |
| |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo); |
| pass.SetPipeline(pipeline); |
| pass.Draw(3); |
| pass.End(); |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8(255, 255, 255, 255), renderPass.color, 0, 0); |
| } |
| |
| // This is a regression test for crbug.com/dawn:1363 where the BindingRemapper transform was run |
| // before the SingleEntryPoint transform, causing one of the other entry points to have conflicting |
| // bindings. |
| TEST_P(ShaderTests, ConflictingBindingsDueToTransformOrder) { |
| wgpu::ShaderModule module = utils::CreateShaderModule(device, R"( |
| @group(0) @binding(0) var<uniform> b0 : u32; |
| @group(0) @binding(1) var<uniform> b1 : u32; |
| |
| @vertex fn vertex() -> @builtin(position) vec4f { |
| _ = b0; |
| return vec4f(0.0); |
| } |
| |
| @fragment fn fragment() -> @location(0) vec4f { |
| _ = b0; |
| _ = b1; |
| return vec4f(0.0); |
| } |
| )"); |
| |
| utils::ComboRenderPipelineDescriptor desc; |
| desc.vertex.module = module; |
| desc.cFragment.module = module; |
| |
| device.CreateRenderPipeline(&desc); |
| } |
| |
| // Check that chromium_disable_uniformity_analysis can be used. It is normally disallowed as unsafe |
| // but DawnTests allow all unsafe APIs by default. |
| TEST_P(ShaderTests, CheckUsageOf_chromium_disable_uniformity_analysis) { |
| wgpu::ShaderModule module = utils::CreateShaderModule(device, R"( |
| enable chromium_disable_uniformity_analysis; |
| |
| @compute @workgroup_size(8) fn uniformity_error( |
| @builtin(local_invocation_id) local_invocation_id : vec3u |
| ) { |
| if (local_invocation_id.x == 0u) { |
| workgroupBarrier(); |
| } |
| } |
| )"); |
| ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, R"( |
| @compute @workgroup_size(8) fn uniformity_error( |
| @builtin(local_invocation_id) local_invocation_id : vec3u |
| ) { |
| if (local_invocation_id.x == 0u) { |
| workgroupBarrier(); |
| } |
| } |
| )")); |
| } |
| |
| // Test that it is not possible to override the builtins in a way that breaks the robustness |
| // transform. |
| TEST_P(ShaderTests, ShaderOverridingRobustnessBuiltins) { |
| // Make the test compute pipeline. |
| wgpu::ComputePipelineDescriptor cDesc; |
| cDesc.compute.module = utils::CreateShaderModule(device, R"( |
| // A fake min() function that always returns 0. |
| fn min(a : u32, b : u32) -> u32 { |
| return 0; |
| } |
| |
| @group(0) @binding(0) var<storage, read_write> result : u32; |
| @compute @workgroup_size(1) fn little_bobby_tables() { |
| // Prevent the SingleEntryPoint transform from removing our min(). |
| let forceUseOfMin = min(0, 1); |
| |
| let values = array(1u, 2u); |
| let index = 1u; |
| // Robustness adds transforms values[index] into values[min(index, 1u)]. |
| // - If our min() is called, the this will be values[0] which is 1. |
| // - If the correct min() is called, the this will be values[1] which is 2. |
| result = values[index]; |
| } |
| )"); |
| wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&cDesc); |
| |
| // Test 4-byte buffer that will receive the result. |
| wgpu::BufferDescriptor bufDesc; |
| bufDesc.size = 4; |
| bufDesc.usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc; |
| wgpu::Buffer buf = device.CreateBuffer(&bufDesc); |
| |
| wgpu::BindGroup bg = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), {{0, buf}}); |
| |
| // Run the compute pipeline. |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); |
| pass.SetPipeline(pipeline); |
| pass.SetBindGroup(0, bg); |
| pass.DispatchWorkgroups(1); |
| pass.End(); |
| |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| // See the comment in the shader for why we expect a 2 here. |
| EXPECT_BUFFER_U32_EQ(2, buf, 0); |
| } |
| |
| // Test that when fragment input is a subset of the vertex output, the render pipeline should be |
| // valid. |
| TEST_P(ShaderTests, FragmentInputIsSubsetOfVertexOutput) { |
| wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"( |
| struct ShaderIO { |
| @location(1) var1: f32, |
| @location(3) @interpolate(flat) var3: u32, |
| @location(5) @interpolate(flat) var5: i32, |
| @location(7) var7: f32, |
| @location(9) @interpolate(flat) var9: u32, |
| @builtin(position) pos: vec4f, |
| } |
| |
| @vertex fn main(@builtin(vertex_index) VertexIndex : u32) |
| -> ShaderIO { |
| var pos = array( |
| vec2f(-1.0, 3.0), |
| vec2f(-1.0, -3.0), |
| vec2f(3.0, 0.0)); |
| |
| var shaderIO: ShaderIO; |
| shaderIO.var1 = 0.0; |
| shaderIO.var3 = 1u; |
| shaderIO.var5 = -9; |
| shaderIO.var7 = 1.0; |
| shaderIO.var9 = 0u; |
| shaderIO.pos = vec4f(pos[VertexIndex], 0.0, 1.0); |
| |
| return shaderIO; |
| })"); |
| |
| wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( |
| struct ShaderIO { |
| @location(3) @interpolate(flat) var3: u32, |
| @location(7) var7: f32, |
| } |
| |
| @fragment fn main(io: ShaderIO) |
| -> @location(0) vec4f { |
| return vec4f(f32(io.var3), io.var7, 1.0, 1.0); |
| })"); |
| |
| utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, 1, 1); |
| |
| utils::ComboRenderPipelineDescriptor descriptor; |
| descriptor.vertex.module = vsModule; |
| descriptor.cFragment.module = fsModule; |
| descriptor.primitive.topology = wgpu::PrimitiveTopology::TriangleList; |
| descriptor.cTargets[0].format = renderPass.colorFormat; |
| |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor); |
| |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo); |
| pass.SetPipeline(pipeline); |
| pass.Draw(3); |
| pass.End(); |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8(255, 255, 255, 255), renderPass.color, 0, 0); |
| } |
| |
| // Test that when fragment input is a subset of the vertex output and the order of them is |
| // different, the render pipeline should be valid. |
| TEST_P(ShaderTests, FragmentInputIsSubsetOfVertexOutputWithDifferentOrder) { |
| wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"( |
| struct ShaderIO { |
| @location(5) @align(16) var5: f32, |
| @location(1) var1: f32, |
| @location(2) var2: f32, |
| @location(3) @align(8) var3: f32, |
| @location(4) var4: vec4f, |
| @builtin(position) pos: vec4f, |
| } |
| |
| @vertex fn main(@builtin(vertex_index) VertexIndex : u32) |
| -> ShaderIO { |
| var pos = array( |
| vec2f(-1.0, 3.0), |
| vec2f(-1.0, -3.0), |
| vec2f(3.0, 0.0)); |
| |
| var shaderIO: ShaderIO; |
| shaderIO.var1 = 0.0; |
| shaderIO.var2 = 0.0; |
| shaderIO.var3 = 1.0; |
| shaderIO.var4 = vec4f(0.4, 0.4, 0.4, 0.4); |
| shaderIO.var5 = 1.0; |
| shaderIO.pos = vec4f(pos[VertexIndex], 0.0, 1.0); |
| |
| return shaderIO; |
| })"); |
| |
| wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( |
| struct ShaderIO { |
| @location(4) var4: vec4f, |
| @location(1) var1: f32, |
| @location(5) @align(16) var5: f32, |
| } |
| |
| @fragment fn main(io: ShaderIO) |
| -> @location(0) vec4f { |
| return vec4f(io.var1, io.var5, io.var4.x, 1.0); |
| })"); |
| |
| utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, 1, 1); |
| |
| utils::ComboRenderPipelineDescriptor descriptor; |
| descriptor.vertex.module = vsModule; |
| descriptor.cFragment.module = fsModule; |
| descriptor.primitive.topology = wgpu::PrimitiveTopology::TriangleList; |
| descriptor.cTargets[0].format = renderPass.colorFormat; |
| |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor); |
| |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo); |
| pass.SetPipeline(pipeline); |
| pass.Draw(3); |
| pass.End(); |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8(0, 255, 102, 255), renderPass.color, 0, 0); |
| } |
| |
| // Test that when fragment input is a subset of the vertex output and that when the builtin |
| // interstage variables may mess up with the order, the render pipeline should be valid. |
| TEST_P(ShaderTests, FragmentInputIsSubsetOfVertexOutputBuiltinOrder) { |
| wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"( |
| struct ShaderIO { |
| @location(1) var1: f32, |
| @builtin(position) pos: vec4f, |
| @location(8) var8: vec3f, |
| @location(7) var7: f32, |
| } |
| |
| @vertex fn main(@builtin(vertex_index) VertexIndex : u32) |
| -> ShaderIO { |
| var pos = array( |
| vec2f(-1.0, 3.0), |
| vec2f(-1.0, -3.0), |
| vec2f(3.0, 0.0)); |
| |
| var shaderIO: ShaderIO; |
| shaderIO.var1 = 0.0; |
| shaderIO.var7 = 1.0; |
| shaderIO.var8 = vec3f(1.0, 0.4, 0.0); |
| shaderIO.pos = vec4f(pos[VertexIndex], 0.0, 1.0); |
| |
| return shaderIO; |
| })"); |
| |
| wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( |
| struct ShaderIO { |
| @builtin(position) pos: vec4f, |
| @location(7) var7: f32, |
| } |
| |
| @fragment fn main(io: ShaderIO) |
| -> @location(0) vec4f { |
| return vec4f(0.0, io.var7, 0.4, 1.0); |
| })"); |
| |
| utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, 1, 1); |
| |
| utils::ComboRenderPipelineDescriptor descriptor; |
| descriptor.vertex.module = vsModule; |
| descriptor.cFragment.module = fsModule; |
| descriptor.primitive.topology = wgpu::PrimitiveTopology::TriangleList; |
| descriptor.cTargets[0].format = renderPass.colorFormat; |
| |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor); |
| |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo); |
| pass.SetPipeline(pipeline); |
| pass.Draw(3); |
| pass.End(); |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8(0, 255, 102, 255), renderPass.color, 0, 0); |
| } |
| |
| // Test that the derivative_uniformity diagnostic filter is handled correctly through the full |
| // shader compilation flow. |
| TEST_P(ShaderTests, DerivativeUniformityDiagnosticFilter) { |
| wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"( |
| struct VertexOut { |
| @builtin(position) pos : vec4f, |
| @location(0) value : f32, |
| } |
| |
| @vertex |
| fn main(@builtin(vertex_index) VertexIndex : u32) -> VertexOut { |
| const pos = array( |
| vec2( 1.0, -1.0), |
| vec2(-1.0, -1.0), |
| vec2( 0.0, 1.0), |
| ); |
| return VertexOut(vec4(pos[VertexIndex], 0.0, 1.0), 0.5); |
| })"); |
| |
| wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( |
| diagnostic(off, derivative_uniformity); |
| |
| @fragment |
| fn main(@location(0) value : f32) -> @location(0) vec4f { |
| if (value > 0) { |
| let intensity = 1.0 - dpdx(1.0); |
| return vec4(intensity, intensity, intensity, 1.0); |
| } |
| return vec4(1.0); |
| })"); |
| |
| utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, 1, 1); |
| |
| utils::ComboRenderPipelineDescriptor descriptor; |
| descriptor.vertex.module = vsModule; |
| descriptor.cFragment.module = fsModule; |
| descriptor.primitive.topology = wgpu::PrimitiveTopology::TriangleList; |
| descriptor.cTargets[0].format = renderPass.colorFormat; |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor); |
| |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo); |
| pass.SetPipeline(pipeline); |
| pass.Draw(3); |
| pass.End(); |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8(255, 255, 255, 255), renderPass.color, 0, 0); |
| } |
| |
| // Test that identifiers containing double underscores are renamed in the GLSL backend. |
| TEST_P(ShaderTests, DoubleUnderscore) { |
| wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"( |
| @vertex |
| fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4f { |
| const pos = array( |
| vec2( 1.0, -1.0), |
| vec2(-1.0, -1.0), |
| vec2( 0.0, 1.0), |
| ); |
| return vec4(pos[VertexIndex], 0.0, 1.0); |
| })"); |
| |
| wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( |
| diagnostic(off, derivative_uniformity); |
| |
| @fragment |
| fn main() -> @location(0) vec4f { |
| let re__sult = vec4f(1.0); |
| return re__sult; |
| })"); |
| |
| utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, 1, 1); |
| |
| utils::ComboRenderPipelineDescriptor descriptor; |
| descriptor.vertex.module = vsModule; |
| descriptor.cFragment.module = fsModule; |
| descriptor.primitive.topology = wgpu::PrimitiveTopology::TriangleList; |
| descriptor.cTargets[0].format = renderPass.colorFormat; |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor); |
| |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo); |
| pass.SetPipeline(pipeline); |
| pass.Draw(3); |
| pass.End(); |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8(255, 255, 255, 255), renderPass.color, 0, 0); |
| } |
| |
| // Test that matrices can be passed by value, which can cause issues on Qualcomm devices. |
| // See crbug.com/tint/2045#c5 |
| TEST_P(ShaderTests, PassMatrixByValue) { |
| std::vector<float> inputs{0.1, 0.2, 0.3, 0.0, 0.4, 0.5, 0.6, 0.0, 0.7, 0.8, 0.9, 0.0}; |
| std::vector<float> expected{1.1, 1.2, 1.3, 0.0, 1.4, 1.5, 1.6, 0.0, 1.7, 1.8, 1.9, 0.0}; |
| uint64_t bufferSize = static_cast<uint64_t>(inputs.size() * sizeof(float)); |
| wgpu::Buffer buffer = utils::CreateBufferFromData( |
| device, inputs.data(), bufferSize, wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc); |
| |
| std::string shader = R"( |
| @group(0) @binding(0) |
| var<storage, read_write> buffer : mat3x3f; |
| |
| fn foo(rhs : mat3x3f) { |
| buffer = buffer + rhs; |
| } |
| |
| @compute @workgroup_size(1) |
| fn main() { |
| let rhs = mat3x3f(1, 1, 1, 1, 1, 1, 1, 1, 1); |
| foo(rhs); |
| } |
| )"; |
| |
| wgpu::ComputePipeline pipeline = CreateComputePipeline(shader, "main"); |
| |
| wgpu::BindGroup bindGroup = |
| utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), {{0, buffer}}); |
| |
| wgpu::CommandBuffer commands; |
| { |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); |
| pass.SetPipeline(pipeline); |
| pass.SetBindGroup(0, bindGroup); |
| pass.DispatchWorkgroups(1); |
| pass.End(); |
| |
| commands = encoder.Finish(); |
| } |
| |
| queue.Submit(1, &commands); |
| |
| EXPECT_BUFFER_FLOAT_RANGE_EQ(expected.data(), buffer, 0, expected.size()); |
| } |
| |
| // Test that matrices in structs can be passed by value, which can cause issues on Qualcomm devices. |
| // See crbug.com/tint/2045#c5 |
| TEST_P(ShaderTests, PassMatrixInStructByValue) { |
| std::vector<float> inputs{0.1, 0.2, 0.3, 0.0, 0.4, 0.5, 0.6, 0.0, 0.7, 0.8, 0.9, 0.0}; |
| std::vector<float> expected{1.1, 1.2, 1.3, 0.0, 1.4, 1.5, 1.6, 0.0, 1.7, 1.8, 1.9, 0.0}; |
| uint64_t bufferSize = static_cast<uint64_t>(inputs.size() * sizeof(float)); |
| wgpu::Buffer buffer = utils::CreateBufferFromData( |
| device, inputs.data(), bufferSize, wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc); |
| |
| std::string shader = R"( |
| struct S { |
| m : mat3x3f, |
| } |
| |
| @group(0) @binding(0) |
| var<storage, read_write> buffer : S; |
| |
| fn foo(rhs : S) { |
| buffer = S(buffer.m + rhs.m); |
| } |
| |
| @compute @workgroup_size(1) |
| fn main() { |
| let rhs = S(mat3x3(1, 1, 1, 1, 1, 1, 1, 1, 1)); |
| foo(rhs); |
| } |
| )"; |
| |
| wgpu::ComputePipeline pipeline = CreateComputePipeline(shader, "main"); |
| |
| wgpu::BindGroup bindGroup = |
| utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), {{0, buffer}}); |
| |
| wgpu::CommandBuffer commands; |
| { |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); |
| pass.SetPipeline(pipeline); |
| pass.SetBindGroup(0, bindGroup); |
| pass.DispatchWorkgroups(1); |
| pass.End(); |
| |
| commands = encoder.Finish(); |
| } |
| |
| queue.Submit(1, &commands); |
| |
| EXPECT_BUFFER_FLOAT_RANGE_EQ(expected.data(), buffer, 0, expected.size()); |
| } |
| |
| // Test that robustness works correctly on uniform buffers that contain mat4x3 types, which can |
| // cause issues on Qualcomm devices. See crbug.com/tint/2074. |
| TEST_P(ShaderTests, Robustness_Uniform_Mat4x3) { |
| // Note: Using non-zero values would make the test more robust, but this involves small changes |
| // to the shader which stop the miscompile in the original bug from happening. |
| std::vector<float> inputs{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, |
| 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; |
| std::vector<uint32_t> constantData{0}; |
| std::vector<uint32_t> outputs{0xDEADBEEFu}; |
| uint64_t bufferSize = static_cast<uint64_t>(inputs.size() * sizeof(float)); |
| wgpu::Buffer buffer = |
| utils::CreateBufferFromData(device, inputs.data(), bufferSize, wgpu::BufferUsage::Uniform); |
| wgpu::Buffer constants = |
| utils::CreateBufferFromData(device, constantData.data(), 4, wgpu::BufferUsage::Uniform); |
| wgpu::Buffer output = utils::CreateBufferFromData( |
| device, outputs.data(), 4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc); |
| |
| // Note: This shader was lifted from WebGPU CTS, and triggers a miscompile for the second case. |
| // The miscompile disappears when too much of the unrelated code is deleted or changed, so the |
| // shader is left in it's original form. |
| std::string shader = R"( |
| struct Constants { |
| zero: u32 |
| }; |
| @group(0) @binding(2) var<uniform> constants: Constants; |
| |
| struct Result { |
| value: u32 |
| }; |
| @group(0) @binding(1) var<storage, read_write> result: Result; |
| |
| struct TestData { |
| data: mat4x3<f32>, |
| }; |
| @group(0) @binding(0) var<uniform> s: TestData; |
| |
| fn runTest() -> u32 { |
| { |
| let index = (0u); |
| if (any(s.data[index] != vec3<f32>())) { return 0x1001u; } |
| } |
| { |
| let index = (4u - 1u); |
| if (any(s.data[index] != vec3<f32>())) { return 0x1002u; } |
| } |
| { |
| let index = (4u); |
| if (any(s.data[index] != vec3<f32>())) { return 0x1003u; } |
| } |
| { |
| let index = (1000000u); |
| if (any(s.data[index] != vec3<f32>())) { return 0x1004u; } |
| } |
| { |
| let index = (4294967295u); |
| if (any(s.data[index] != vec3<f32>())) { return 0x1005u; } |
| } |
| { |
| let index = (2147483647u); |
| if (any(s.data[index] != vec3<f32>())) { return 0x1006u; } |
| } |
| { |
| let index = (0u) + 0u; |
| if (any(s.data[index] != vec3<f32>())) { return 0x1007u; } |
| } |
| { |
| let index = (4u - 1u) + 0u; |
| if (any(s.data[index] != vec3<f32>())) { return 0x1008u; } |
| } |
| { |
| let index = (4u) + 0u; |
| if (any(s.data[index] != vec3<f32>())) { return 0x1009u; } |
| } |
| { |
| let index = (1000000u) + 0u; |
| if (any(s.data[index] != vec3<f32>())) { return 0x100au; } |
| } |
| { |
| let index = (4294967295u) + 0u; |
| if (any(s.data[index] != vec3<f32>())) { return 0x100bu; } |
| } |
| { |
| let index = (2147483647u) + 0u; |
| if (any(s.data[index] != vec3<f32>())) { return 0x100cu; } |
| } |
| { |
| let index = (0u) + u32(constants.zero); |
| if (any(s.data[index] != vec3<f32>())) { return 0x100du; } |
| } |
| { |
| let index = (4u - 1u) + u32(constants.zero); |
| if (any(s.data[index] != vec3<f32>())) { return 0x100eu; } |
| } |
| { |
| let index = (4u) + u32(constants.zero); |
| if (any(s.data[index] != vec3<f32>())) { return 0x100fu; } |
| } |
| { |
| let index = (1000000u) + u32(constants.zero); |
| if (any(s.data[index] != vec3<f32>())) { return 0x1010u; } |
| } |
| { |
| let index = (4294967295u) + u32(constants.zero); |
| if (any(s.data[index] != vec3<f32>())) { return 0x1011u; } |
| } |
| { |
| let index = (2147483647u) + u32(constants.zero); |
| if (any(s.data[index] != vec3<f32>())) { return 0x1012u; } |
| } |
| { |
| let index = (0); |
| if (any(s.data[index] != vec3<f32>())) { return 0x1013u; } |
| } |
| { |
| let index = (4 - 1); |
| if (any(s.data[index] != vec3<f32>())) { return 0x1014u; } |
| } |
| { |
| let index = (-1); |
| if (any(s.data[index] != vec3<f32>())) { return 0x1015u; } |
| } |
| { |
| let index = (4); |
| if (any(s.data[index] != vec3<f32>())) { return 0x1016u; } |
| } |
| { |
| let index = (-1000000); |
| if (any(s.data[index] != vec3<f32>())) { return 0x1017u; } |
| } |
| { |
| let index = (1000000); |
| if (any(s.data[index] != vec3<f32>())) { return 0x1018u; } |
| } |
| { |
| let index = (-2147483648); |
| if (any(s.data[index] != vec3<f32>())) { return 0x1019u; } |
| } |
| { |
| let index = (2147483647); |
| if (any(s.data[index] != vec3<f32>())) { return 0x101au; } |
| } |
| { |
| let index = (0) + 0; |
| if (any(s.data[index] != vec3<f32>())) { return 0x101bu; } |
| } |
| { |
| let index = (4 - 1) + 0; |
| if (any(s.data[index] != vec3<f32>())) { return 0x101cu; } |
| } |
| { |
| let index = (-1) + 0; |
| if (any(s.data[index] != vec3<f32>())) { return 0x101du; } |
| } |
| { |
| let index = (4) + 0; |
| if (any(s.data[index] != vec3<f32>())) { return 0x101eu; } |
| } |
| { |
| let index = (-1000000) + 0; |
| if (any(s.data[index] != vec3<f32>())) { return 0x101fu; } |
| } |
| { |
| let index = (1000000) + 0; |
| if (any(s.data[index] != vec3<f32>())) { return 0x1020u; } |
| } |
| { |
| let index = (-2147483648) + 0; |
| if (any(s.data[index] != vec3<f32>())) { return 0x1021u; } |
| } |
| { |
| let index = (2147483647) + 0; |
| if (any(s.data[index] != vec3<f32>())) { return 0x1022u; } |
| } |
| { |
| let index = (0) + i32(constants.zero); |
| if (any(s.data[index] != vec3<f32>())) { return 0x1023u; } |
| } |
| { |
| let index = (4 - 1) + i32(constants.zero); |
| if (any(s.data[index] != vec3<f32>())) { return 0x1024u; } |
| } |
| { |
| let index = (-1) + i32(constants.zero); |
| if (any(s.data[index] != vec3<f32>())) { return 0x1025u; } |
| } |
| { |
| let index = (4) + i32(constants.zero); |
| if (any(s.data[index] != vec3<f32>())) { return 0x1026u; } |
| } |
| { |
| let index = (-1000000) + i32(constants.zero); |
| if (any(s.data[index] != vec3<f32>())) { return 0x1027u; } |
| } |
| { |
| let index = (1000000) + i32(constants.zero); |
| if (any(s.data[index] != vec3<f32>())) { return 0x1028u; } |
| } |
| { |
| let index = (-2147483648) + i32(constants.zero); |
| if (any(s.data[index] != vec3<f32>())) { return 0x1029u; } |
| } |
| { |
| let index = (2147483647) + i32(constants.zero); |
| if (any(s.data[index] != vec3<f32>())) { return 0x102au; } |
| } |
| return 0u; |
| } |
| |
| @compute @workgroup_size(1) |
| fn main() { |
| result.value = runTest(); |
| } |
| )"; |
| |
| wgpu::ComputePipeline pipeline = CreateComputePipeline(shader, "main"); |
| |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), |
| {{0, buffer}, {1, output}, {2, constants}}); |
| |
| wgpu::CommandBuffer commands; |
| { |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); |
| pass.SetPipeline(pipeline); |
| pass.SetBindGroup(0, bindGroup); |
| pass.DispatchWorkgroups(1); |
| pass.End(); |
| |
| commands = encoder.Finish(); |
| } |
| |
| queue.Submit(1, &commands); |
| |
| outputs[0] = 0u; |
| EXPECT_BUFFER_U32_RANGE_EQ(outputs.data(), output, 0, outputs.size()); |
| } |
| |
| // SSBOs declared with the same name in multiple shader stages must contain the same members in |
| // GLSL. If not renamed properly, names of binding at (2, 2) in the vertex stage and (0, 3) in the |
| // fragment stage can possibly collide. |
| TEST_P(ShaderTests, StorageAcrossStages) { |
| // TODO(crbug.com/dawn/2295): diagnose this failure on Pixel 6 OpenGLES |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsAndroid() && IsARM()); |
| |
| wgpu::ShaderModule module = utils::CreateShaderModule(device, R"( |
| @group(2) @binding(2) var<storage> u0_2: f32; |
| @group(1) @binding(1) var<storage> u0_1: f32; |
| @group(0) @binding(0) var<storage> u0_0: f32; |
| |
| @group(0) @binding(3) var<storage> u1_3: f32; |
| @group(2) @binding(2) var<storage> u1_2: f32; |
| @group(1) @binding(1) var<storage> u1_1: f32; |
| @group(0) @binding(0) var<storage> u1_0: f32; |
| |
| @vertex fn vertex() -> @builtin(position) vec4f { |
| _ = u0_0; |
| _ = u0_1; |
| _ = u0_2; |
| return vec4f(0); |
| } |
| |
| @fragment fn fragment() -> @location(0) vec4f { |
| _ = u1_0; |
| _ = u1_1; |
| _ = u1_2; |
| _ = u1_3; |
| return vec4f(0); |
| } |
| )"); |
| |
| utils::ComboRenderPipelineDescriptor desc; |
| desc.vertex.module = module; |
| desc.cFragment.module = module; |
| |
| device.CreateRenderPipeline(&desc); |
| } |
| |
| // SSBOs declared with the same name in multiple shader stages must contain the same members in |
| // GLSL. If not renamed properly, names of binding at (2, 2) in the vertex stage and (0, 3) in the |
| // fragment stage can possibly collide. |
| TEST_P(ShaderTests, StorageAcrossStagesStruct) { |
| // TODO(crbug.com/dawn/2295): diagnose this failure on Pixel 6 OpenGLES |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsAndroid() && IsARM()); |
| |
| wgpu::ShaderModule module = utils::CreateShaderModule(device, R"( |
| struct block { |
| inner: f32 |
| } |
| @group(2) @binding(2) var<storage> u0_2: block; |
| |
| @group(0) @binding(3) var<storage> u1_3: f32; |
| @group(2) @binding(2) var<storage> u1_2: f32; |
| |
| @vertex fn vertex() -> @builtin(position) vec4f { |
| _ = u0_2.inner; |
| return vec4f(0); |
| } |
| |
| @fragment fn fragment() -> @location(0) vec4f { |
| _ = u1_2; |
| _ = u1_3; |
| return vec4f(0); |
| } |
| )"); |
| |
| utils::ComboRenderPipelineDescriptor desc; |
| desc.vertex.module = module; |
| desc.cFragment.module = module; |
| |
| device.CreateRenderPipeline(&desc); |
| } |
| |
| // SSBOs declared with the same name in multiple shader stages must contain the same members in |
| // GLSL. If not renamed properly, names of binding at (2, 2) in the vertex stage and (0, 3) in the |
| // fragment stage can possibly collide. |
| TEST_P(ShaderTests, StorageAcrossStagesSeparateModules) { |
| // TODO(crbug.com/dawn/2295): diagnose this failure on Pixel 6 OpenGLES |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsAndroid() && IsARM()); |
| |
| wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"( |
| @group(2) @binding(2) var<storage> u0_2: f32; |
| @group(1) @binding(1) var<storage> u0_1: f32; |
| @group(0) @binding(0) var<storage> u0_0: f32; |
| |
| @vertex fn vertex() -> @builtin(position) vec4f { |
| _ = u0_0; |
| _ = u0_1; |
| _ = u0_2; |
| return vec4f(0); |
| } |
| )"); |
| wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( |
| @group(0) @binding(3) var<storage> u1_3: f32; |
| @group(2) @binding(2) var<storage> u1_2: f32; |
| @group(1) @binding(1) var<storage> u1_1: f32; |
| @group(0) @binding(0) var<storage> u1_0: f32; |
| |
| @fragment fn fragment() -> @location(0) vec4f { |
| _ = u1_0; |
| _ = u1_1; |
| _ = u1_2; |
| _ = u1_3; |
| return vec4f(0); |
| } |
| )"); |
| |
| utils::ComboRenderPipelineDescriptor desc; |
| desc.vertex.module = vsModule; |
| desc.cFragment.module = fsModule; |
| |
| device.CreateRenderPipeline(&desc); |
| } |
| |
| // Deliberately mismatch an SSBO block name at differrent stages. |
| TEST_P(ShaderTests, StorageAcrossStagesSeparateModuleMismatch) { |
| // TODO(crbug.com/dawn/2295): diagnose this failure on Pixel 6 OpenGLES |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsAndroid() && IsARM()); |
| |
| wgpu::ShaderModule module = utils::CreateShaderModule(device, R"( |
| @group(0) @binding(0) var<storage> tint_symbol_ubo_0: f32; |
| @group(0) @binding(1) var<storage> tint_symbol_ubo_1: u32; |
| |
| @vertex fn vertex() -> @builtin(position) vec4f { |
| _ = tint_symbol_ubo_0; |
| _ = tint_symbol_ubo_1; |
| return vec4f(tint_symbol_ubo_0) + vec4f(f32(tint_symbol_ubo_1)); |
| } |
| |
| @fragment fn fragment() -> @location(0) vec4f { |
| _ = tint_symbol_ubo_1; |
| return vec4f(f32(tint_symbol_ubo_1)); |
| } |
| )"); |
| |
| utils::ComboRenderPipelineDescriptor desc; |
| desc.vertex.module = module; |
| desc.cFragment.module = module; |
| |
| device.CreateRenderPipeline(&desc); |
| } |
| |
| // Having different block contents at the same binding point used in different stages is allowed. |
| TEST_P(ShaderTests, StorageAcrossStagesSameBindingPointCollide) { |
| // TODO(crbug.com/dawn/2295): diagnose this failure on Pixel 6 OpenGLES |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsAndroid() && IsARM()); |
| |
| wgpu::ShaderModule module = utils::CreateShaderModule(device, R"( |
| struct X { x : vec4f } |
| struct Y { y : vec4i } |
| |
| @group(0) @binding(0) var<storage> v : X; |
| @group(0) @binding(0) var<storage> f : Y; |
| |
| @vertex fn vertex() -> @builtin(position) vec4f { |
| _ = v; |
| return vec4f(); |
| } |
| |
| @fragment fn fragment() -> @location(0) vec4f { |
| _ = f; |
| return vec4f(); |
| } |
| )"); |
| |
| utils::ComboRenderPipelineDescriptor desc; |
| desc.vertex.module = module; |
| desc.cFragment.module = module; |
| |
| device.CreateRenderPipeline(&desc); |
| } |
| |
| // Having different block contents at the same binding point used in different stages is allowed, |
| // with or without struct wrapper. |
| TEST_P(ShaderTests, StorageAcrossStagesSameBindingPointCollideMixedStructDef) { |
| // TODO(crbug.com/dawn/2295): diagnose this failure on Pixel 6 OpenGLES |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsAndroid() && IsARM()); |
| |
| wgpu::ShaderModule module = utils::CreateShaderModule(device, R"( |
| struct X { x : vec4f } |
| |
| @group(0) @binding(0) var<storage> v : X; |
| @group(0) @binding(0) var<storage> f : vec3u; |
| |
| @vertex fn vertex() -> @builtin(position) vec4f { |
| _ = v; |
| return vec4f(); |
| } |
| |
| @fragment fn fragment() -> @location(0) vec4f { |
| _ = f; |
| return vec4f(); |
| } |
| )"); |
| |
| utils::ComboRenderPipelineDescriptor desc; |
| desc.vertex.module = module; |
| desc.cFragment.module = module; |
| |
| device.CreateRenderPipeline(&desc); |
| } |
| |
| // UBOs declared with the same name in multiple shader stages must contain the same members in GLSL. |
| // If not renamed properly, names of binding at (2, 2) in the vertex stage and (0, 3) in the |
| // fragment stage can possibly collide. |
| TEST_P(ShaderTests, UniformAcrossStages) { |
| wgpu::ShaderModule module = utils::CreateShaderModule(device, R"( |
| @group(2) @binding(2) var<uniform> u0_2: f32; |
| |
| @group(0) @binding(3) var<uniform> u1_3: f32; |
| @group(2) @binding(2) var<uniform> u1_2: f32; |
| |
| @vertex fn vertex() -> @builtin(position) vec4f { |
| _ = u0_2; |
| return vec4f(0); |
| } |
| |
| @fragment fn fragment() -> @location(0) vec4f { |
| _ = u1_2; |
| _ = u1_3; |
| return vec4f(0); |
| } |
| )"); |
| |
| utils::ComboRenderPipelineDescriptor desc; |
| desc.vertex.module = module; |
| desc.cFragment.module = module; |
| |
| device.CreateRenderPipeline(&desc); |
| } |
| |
| // UBOs declared with the same name in multiple shader stages must contain the same members in GLSL. |
| // If not renamed properly, names of binding at (2, 2) in the vertex stage and (0, 3) in the |
| // fragment stage can possibly collide. |
| TEST_P(ShaderTests, UniformAcrossStagesStruct) { |
| wgpu::ShaderModule module = utils::CreateShaderModule(device, R"( |
| struct block { |
| inner: f32 |
| } |
| @group(2) @binding(2) var<uniform> u0_2: block; |
| |
| @group(0) @binding(3) var<uniform> u1_3: f32; |
| @group(2) @binding(2) var<uniform> u1_2: f32; |
| |
| @vertex fn vertex() -> @builtin(position) vec4f { |
| _ = u0_2.inner; |
| return vec4f(0); |
| } |
| |
| @fragment fn fragment() -> @location(0) vec4f { |
| _ = u1_2; |
| _ = u1_3; |
| return vec4f(0); |
| } |
| )"); |
| |
| utils::ComboRenderPipelineDescriptor desc; |
| desc.vertex.module = module; |
| desc.cFragment.module = module; |
| |
| device.CreateRenderPipeline(&desc); |
| } |
| |
| // UBOs declared with the same name in multiple shader stages must contain the same members in GLSL. |
| // If not renamed properly, names of binding at (2, 2) in the vertex stage and (0, 3) in the |
| // fragment stage can possibly collide. |
| TEST_P(ShaderTests, UniformAcrossStagesSeparateModule) { |
| wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"( |
| @group(2) @binding(2) var<uniform> u0_2: f32; |
| |
| @vertex fn vertex() -> @builtin(position) vec4f { |
| _ = u0_2; |
| return vec4f(0); |
| } |
| )"); |
| wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( |
| @group(0) @binding(3) var<uniform> u1_3: f32; |
| @group(2) @binding(2) var<uniform> u1_2: f32; |
| |
| @fragment fn fragment() -> @location(0) vec4f { |
| _ = u1_2; |
| _ = u1_3; |
| return vec4f(0); |
| } |
| )"); |
| |
| utils::ComboRenderPipelineDescriptor desc; |
| desc.vertex.module = vsModule; |
| desc.cFragment.module = fsModule; |
| |
| device.CreateRenderPipeline(&desc); |
| } |
| |
| // Deliberately mismatch a UBO block name at differrent stages. |
| TEST_P(ShaderTests, UniformAcrossStagesSeparateModuleMismatch) { |
| wgpu::ShaderModule module = utils::CreateShaderModule(device, R"( |
| @group(0) @binding(0) var<uniform> tint_symbol_ubo_0: f32; |
| @group(0) @binding(1) var<uniform> tint_symbol_ubo_1: u32; |
| |
| @vertex fn vertex() -> @builtin(position) vec4f { |
| _ = tint_symbol_ubo_0; |
| _ = tint_symbol_ubo_1; |
| return vec4f(tint_symbol_ubo_0) + vec4f(f32(tint_symbol_ubo_1)); |
| } |
| |
| @fragment fn fragment() -> @location(0) vec4f { |
| _ = tint_symbol_ubo_1; |
| return vec4f(f32(tint_symbol_ubo_1)); |
| } |
| )"); |
| |
| utils::ComboRenderPipelineDescriptor desc; |
| desc.vertex.module = module; |
| desc.cFragment.module = module; |
| |
| device.CreateRenderPipeline(&desc); |
| } |
| |
| // Test that padding is correctly applied to a UBO used in both vert and |
| // frag stages. Insert an additional UBO in the frag stage before the reused UBO. |
| TEST_P(ShaderTests, UniformAcrossStagesSeparateModuleMismatchWithCustomSize) { |
| wgpu::ShaderModule module = utils::CreateShaderModule(device, R"( |
| struct A { |
| f : f32, |
| }; |
| struct B { |
| u : u32, |
| } |
| @group(0) @binding(0) var<uniform> tint_symbol_ubo_0: A; |
| @group(0) @binding(1) var<uniform> tint_symbol_ubo_1: B; |
| |
| @vertex fn vertex() -> @builtin(position) vec4f { |
| _ = tint_symbol_ubo_0; |
| _ = tint_symbol_ubo_1; |
| return vec4f(tint_symbol_ubo_0.f) + vec4f(f32(tint_symbol_ubo_1.u)); |
| } |
| |
| @fragment fn fragment() -> @location(0) vec4f { |
| _ = tint_symbol_ubo_1; |
| return vec4f(f32(tint_symbol_ubo_1.u)); |
| } |
| )"); |
| |
| utils::ComboRenderPipelineDescriptor desc; |
| desc.vertex.module = module; |
| desc.cFragment.module = module; |
| |
| device.CreateRenderPipeline(&desc); |
| } |
| |
| // Test that accessing instance_index in the vert shader and assigning to frag_depth in the frag |
| // shader works. |
| TEST_P(ShaderTests, FragDepthAndInstanceIndex) { |
| wgpu::ShaderModule module = utils::CreateShaderModule(device, R"( |
| @group(0) @binding(0) var<uniform> a : f32; |
| |
| @fragment fn fragment() -> @builtin(frag_depth) f32 { |
| return a; |
| } |
| |
| @vertex fn vertex(@builtin(instance_index) instance : u32) -> @builtin(position) vec4f { |
| return vec4f(f32(instance)); |
| } |
| )"); |
| |
| utils::ComboRenderPipelineDescriptor desc; |
| desc.vertex.module = module; |
| desc.cFragment.module = module; |
| desc.cFragment.targetCount = 0; |
| wgpu::DepthStencilState* dsState = desc.EnableDepthStencil(); |
| dsState->depthWriteEnabled = true; |
| dsState->depthCompare = wgpu::CompareFunction::Always; |
| |
| device.CreateRenderPipeline(&desc); |
| } |
| |
| // Having different block contents at the same binding point used in different stages is allowed. |
| TEST_P(ShaderTests, UniformAcrossStagesSameBindingPointCollide) { |
| wgpu::ShaderModule module = utils::CreateShaderModule(device, R"( |
| struct X { x : vec4f } |
| struct Y { y : vec4i } |
| |
| @group(0) @binding(0) var<uniform> v : X; |
| @group(0) @binding(0) var<uniform> f : Y; |
| |
| @vertex fn vertex() -> @builtin(position) vec4f { |
| _ = v; |
| return vec4f(); |
| } |
| |
| @fragment fn fragment() -> @location(0) vec4f { |
| _ = f; |
| return vec4f(); |
| } |
| )"); |
| |
| utils::ComboRenderPipelineDescriptor desc; |
| desc.vertex.module = module; |
| desc.cFragment.module = module; |
| |
| device.CreateRenderPipeline(&desc); |
| } |
| |
| // Having different block contents at the same binding point used in different stages is allowed, |
| // with or without struct wrapper. |
| TEST_P(ShaderTests, UniformAcrossStagesSameBindingPointCollideMixedStructDef) { |
| wgpu::ShaderModule module = utils::CreateShaderModule(device, R"( |
| struct X { x : vec4f } |
| |
| @group(0) @binding(0) var<uniform> v : X; |
| @group(0) @binding(0) var<uniform> f : vec3u; |
| |
| @vertex fn vertex() -> @builtin(position) vec4f { |
| _ = v; |
| return vec4f(); |
| } |
| |
| @fragment fn fragment() -> @location(0) vec4f { |
| _ = f; |
| return vec4f(); |
| } |
| )"); |
| |
| utils::ComboRenderPipelineDescriptor desc; |
| desc.vertex.module = module; |
| desc.cFragment.module = module; |
| |
| device.CreateRenderPipeline(&desc); |
| } |
| |
| // Test that the `w` component of fragment builtin position behaves correctly. |
| TEST_P(ShaderTests, FragmentPositionW) { |
| // TODO(crbug.com/dawn/2295): diagnose this failure on Pixel 4 OpenGLES |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsAndroid() && IsQualcomm()); |
| |
| wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"( |
| @vertex fn main(@builtin(vertex_index) vertex_index : u32) -> @builtin(position) vec4f { |
| let pos = array( |
| vec4f(-1.0, -1.0, 0.0, 2.0), |
| vec4f(-0.5, 0.0, 0.0, 2.0), |
| vec4f( 0.0, -1.0, 0.0, 2.0), |
| |
| vec4f( 0.0, -1.0, 0.0, 4.0), |
| vec4f( 0.5, 0.0, 0.0, 4.0), |
| vec4f( 1.0, -1.0, 0.0, 4.0), |
| |
| vec4f(-0.5, 0.0, 0.0, 8.0), |
| vec4f( 0.0, 1.0, 0.0, 8.0), |
| vec4f( 0.5, 0.0, 0.0, 8.0), |
| )[vertex_index]; |
| |
| return vec4(pos.xy * pos.w, 0.0, pos.w); |
| })"); |
| |
| wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( |
| @fragment fn main(@builtin(position) position : vec4f) -> @location(0) vec4f { |
| return vec4f(position.w, 0.0, 1.0, 1.0); |
| })"); |
| |
| utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, 64, 64); |
| |
| utils::ComboRenderPipelineDescriptor descriptor; |
| descriptor.vertex.module = vsModule; |
| descriptor.cFragment.module = fsModule; |
| descriptor.primitive.topology = wgpu::PrimitiveTopology::TriangleList; |
| descriptor.cTargets[0].format = renderPass.colorFormat; |
| |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor); |
| |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo); |
| pass.SetPipeline(pipeline); |
| pass.Draw(9); |
| pass.End(); |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| EXPECT_PIXEL_RGBA8_BETWEEN(utils::RGBA8(126, 0, 255, 255), utils::RGBA8(129, 0, 255, 255), |
| renderPass.color, 16, 48); |
| EXPECT_PIXEL_RGBA8_BETWEEN(utils::RGBA8(62, 0, 255, 255), utils::RGBA8(65, 0, 255, 255), |
| renderPass.color, 48, 48); |
| EXPECT_PIXEL_RGBA8_BETWEEN(utils::RGBA8(30, 0, 255, 255), utils::RGBA8(33, 0, 255, 255), |
| renderPass.color, 32, 16); |
| } |
| |
| // Regression test for crbug.com/dawn/2340. GLSL requires the main enty point to be named "main". |
| // We need to make sure when the entry point is "main" or other GLSL reserved keyword, |
| // the renaming is always properly handled for the GL backend no matter what |
| // "disable_symbol_renaming" is. |
| TEST_P(ShaderTests, EntryPointShaderKeywordsComputePipeline) { |
| { |
| // Entry point is "main". |
| std::string shader = R"( |
| @compute @workgroup_size(1) fn main() { |
| _ = 1; |
| })"; |
| |
| wgpu::ComputePipeline pipeline = CreateComputePipeline(shader, "main"); |
| } |
| { |
| // Entry point is a GLSL reserved keyword other than "main". |
| std::string shader = R"( |
| @compute @workgroup_size(1) fn mat2() { |
| _ = 1; |
| })"; |
| |
| wgpu::ComputePipeline pipeline = CreateComputePipeline(shader, "mat2"); |
| } |
| { |
| // Entry point is not a GLSL reserved keyword. |
| std::string shader = R"( |
| @compute @workgroup_size(1) fn foo1234() { |
| _ = 1; |
| })"; |
| |
| wgpu::ComputePipeline pipeline = CreateComputePipeline(shader, "foo1234"); |
| } |
| } |
| |
| // Regression test for crbug.com/dawn/2340. GLSL requires the main enty point to be named "main". |
| // We need to make sure when the entry point is "main" or other GLSL reserved keyword, |
| // the renaming is always properly handled for the GL backend no matter what |
| // "disable_symbol_renaming" is. |
| TEST_P(ShaderTests, EntryPointShaderKeywordsRenderPipeline) { |
| std::string shader = R"( |
| // Entry point is "main". |
| @vertex |
| fn main() -> @builtin(position) vec4f { |
| return vec4f(0.0, 0.0, 0.0, 1.0); |
| } |
| // Entry point is a GLSL reserved keyword other than "main". |
| @fragment |
| fn mat2() -> @location(0) vec4f { |
| return vec4f(0.0, 0.0, 0.0, 1.0); |
| } |
| // Entry point is not a GLSL reserved keyword. |
| @fragment |
| fn foo1234() -> @location(0) vec4f { |
| return vec4f(1.0, 1.0, 1.0, 1.0); |
| } |
| )"; |
| wgpu::ShaderModule shaderModule = utils::CreateShaderModule(device, shader.c_str()); |
| utils::ComboRenderPipelineDescriptor rpDesc; |
| rpDesc.vertex.module = shaderModule; |
| rpDesc.cFragment.module = shaderModule; |
| { |
| rpDesc.cFragment.entryPoint = "mat2"; |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&rpDesc); |
| } |
| { |
| rpDesc.cFragment.entryPoint = "foo1234"; |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&rpDesc); |
| } |
| } |
| |
| TEST_P(ShaderTests, PrivateVarInitWithStruct) { |
| wgpu::ComputePipeline pipeline = CreateComputePipeline(R"( |
| @binding(0) @group(0) var<storage, read_write> output : i32; |
| |
| struct S { |
| i : i32, |
| } |
| |
| var<private> P = S(42); |
| |
| @compute @workgroup_size(1) |
| fn main() { |
| output = P.i; |
| } |
| )"); |
| |
| wgpu::Buffer output = CreateBuffer(1); |
| |
| wgpu::BindGroup bindGroup = |
| utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), {{0, output}}); |
| |
| wgpu::CommandBuffer commands; |
| { |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); |
| pass.SetPipeline(pipeline); |
| pass.SetBindGroup(0, bindGroup); |
| pass.DispatchWorkgroups(1); |
| pass.End(); |
| |
| commands = encoder.Finish(); |
| } |
| |
| queue.Submit(1, &commands); |
| |
| EXPECT_BUFFER_U32_EQ(42, output, 0); |
| } |
| |
| TEST_P(ShaderTests, UnrestrictedPointerParameters) { |
| // TODO(crbug.com/dawn/2350): Investigate, fix. |
| DAWN_TEST_UNSUPPORTED_IF(IsD3D11()); |
| |
| wgpu::ComputePipeline pipeline = CreateComputePipeline(R"( |
| @binding(0) @group(0) var<uniform> input : array<vec4i, 4>; |
| @binding(1) @group(0) var<storage, read_write> output : array<vec4i, 4>; |
| |
| fn sum(f : ptr<function, i32>, |
| w : ptr<workgroup, atomic<i32>>, |
| p : ptr<private, i32>, |
| u : ptr<uniform, vec4i>) -> vec4i { |
| |
| return vec4(*f + atomicLoad(w) + *p) + *u; |
| } |
| |
| struct S { |
| i : i32, |
| } |
| |
| var<private> P0 = S(0); |
| var<private> P1 = S(10); |
| var<private> P2 = 20; |
| var<private> P3 = 30; |
| |
| struct T { |
| i : atomic<i32>, |
| } |
| |
| var<workgroup> W0 : T; |
| var<workgroup> W1 : atomic<i32>; |
| var<workgroup> W2 : T; |
| var<workgroup> W3 : atomic<i32>; |
| |
| @compute @workgroup_size(1) |
| fn main() { |
| atomicStore(&W0.i, 0); |
| atomicStore(&W1, 100); |
| atomicStore(&W2.i, 200); |
| atomicStore(&W3, 300); |
| |
| var F = array(0, 1000, 2000, 3000); |
| |
| output[0] = sum(&F[2], &W3, &P1.i, &input[0]); // vec4(2310) + vec4(1, 2, 3, 4) |
| output[1] = sum(&F[1], &W2.i, &P0.i, &input[1]); // vec4(1200) + vec4(4, 3, 2, 1) |
| output[2] = sum(&F[3], &W0.i, &P3, &input[2]); // vec4(3030) + vec4(2, 4, 1, 3) |
| output[3] = sum(&F[2], &W1, &P2, &input[3]); // vec4(2120) + vec4(4, 1, 2, 3) |
| } |
| )"); |
| |
| wgpu::Buffer input = CreateBuffer( |
| std::vector<uint32_t>{ |
| 1, 2, 3, 4, // [0] |
| 4, 3, 2, 1, // [1] |
| 2, 4, 1, 3, // [2] |
| 4, 1, 2, 3, // [3] |
| }, |
| wgpu::BufferUsage::Uniform); |
| |
| std::vector<uint32_t> expected{ |
| 2311, 2312, 2313, 2314, // [0] |
| 1204, 1203, 1202, 1201, // [1] |
| 3032, 3034, 3031, 3033, // [2] |
| 2124, 2121, 2122, 2123, // [3] |
| }; |
| |
| wgpu::Buffer output = CreateBuffer(expected.size()); |
| |
| wgpu::BindGroup bindGroup = |
| utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), {{0, input}, {1, output}}); |
| |
| wgpu::CommandBuffer commands; |
| { |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); |
| pass.SetPipeline(pipeline); |
| pass.SetBindGroup(0, bindGroup); |
| pass.DispatchWorkgroups(1); |
| pass.End(); |
| |
| commands = encoder.Finish(); |
| } |
| |
| queue.Submit(1, &commands); |
| |
| EXPECT_BUFFER_U32_RANGE_EQ(expected.data(), output, 0, expected.size()); |
| } |
| |
| DAWN_INSTANTIATE_TEST(ShaderTests, |
| D3D11Backend(), |
| D3D12Backend(), |
| D3D12Backend({"use_dxc"}), |
| MetalBackend(), |
| OpenGLBackend(), |
| OpenGLESBackend(), |
| OpenGLBackend({"disable_symbol_renaming"}), |
| OpenGLESBackend({"disable_symbol_renaming"}), |
| VulkanBackend()); |
| |
| } // anonymous namespace |
| } // namespace dawn |