| // Copyright 2020 The Dawn Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "tests/unittests/validation/ValidationTest.h" |
| #include "utils/ComboRenderPipelineDescriptor.h" |
| #include "utils/WGPUHelpers.h" |
| |
| class StorageTextureValidationTests : public ValidationTest { |
| protected: |
| wgpu::ShaderModule mDefaultVSModule = |
| utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"( |
| #version 450 |
| void main() { |
| gl_Position = vec4(0.f, 0.f, 0.f, 1.f); |
| })"); |
| wgpu::ShaderModule mDefaultFSModule = |
| utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"( |
| #version 450 |
| layout(location = 0) out vec4 fragColor; |
| void main() { |
| fragColor = vec4(1.f, 0.f, 0.f, 1.f); |
| })"); |
| }; |
| |
| // Validate read-only storage textures can be declared in vertex and fragment |
| // shaders, while writeonly storage textures can't. |
| TEST_F(StorageTextureValidationTests, RenderPipeline) { |
| // Readonly storage texture can be declared in a vertex shader. |
| { |
| wgpu::ShaderModule vsModule = |
| utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"( |
| #version 450 |
| layout(set = 0, binding = 0, rgba8) uniform readonly image2D image0; |
| void main() { |
| gl_Position = imageLoad(image0, ivec2(gl_VertexIndex, 0)); |
| })"); |
| |
| utils::ComboRenderPipelineDescriptor descriptor(device); |
| descriptor.layout = nullptr; |
| descriptor.vertexStage.module = vsModule; |
| descriptor.cFragmentStage.module = mDefaultFSModule; |
| device.CreateRenderPipeline(&descriptor); |
| } |
| |
| // Read-only storage textures can be declared in a fragment shader. |
| { |
| wgpu::ShaderModule fsModule = |
| utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"( |
| #version 450 |
| layout(set = 0, binding = 0, rgba8) uniform readonly image2D image0; |
| layout(location = 0) out vec4 fragColor; |
| void main() { |
| fragColor = imageLoad(image0, ivec2(gl_FragCoord.xy)); |
| })"); |
| |
| utils::ComboRenderPipelineDescriptor descriptor(device); |
| descriptor.layout = nullptr; |
| descriptor.vertexStage.module = mDefaultVSModule; |
| descriptor.cFragmentStage.module = fsModule; |
| device.CreateRenderPipeline(&descriptor); |
| } |
| |
| // Write-only storage textures cannot be declared in a vertex shader. |
| { |
| wgpu::ShaderModule vsModule = |
| utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"( |
| #version 450 |
| layout(set = 0, binding = 0, rgba8) uniform writeonly image2D image0; |
| void main() { |
| imageStore(image0, ivec2(gl_VertexIndex, 0), vec4(1.f, 0.f, 0.f, 1.f)); |
| })"); |
| |
| utils::ComboRenderPipelineDescriptor descriptor(device); |
| descriptor.layout = nullptr; |
| descriptor.vertexStage.module = vsModule; |
| descriptor.cFragmentStage.module = mDefaultFSModule; |
| ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor)); |
| } |
| |
| // Write-only storage textures cannot be declared in a fragment shader. |
| { |
| wgpu::ShaderModule fsModule = |
| utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"( |
| #version 450 |
| layout(set = 0, binding = 0, rgba8) uniform writeonly image2D image0; |
| void main() { |
| imageStore(image0, ivec2(gl_FragCoord.xy), vec4(1.f, 0.f, 0.f, 1.f)); |
| })"); |
| |
| utils::ComboRenderPipelineDescriptor descriptor(device); |
| descriptor.layout = nullptr; |
| descriptor.vertexStage.module = mDefaultVSModule; |
| descriptor.cFragmentStage.module = fsModule; |
| ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor)); |
| } |
| } |
| |
| // Validate both read-only and write-only storage textures can be declared in |
| // compute shaders. |
| TEST_F(StorageTextureValidationTests, ComputePipeline) { |
| // Read-only storage textures can be declared in a compute shader. |
| { |
| wgpu::ShaderModule csModule = |
| utils::CreateShaderModule(device, utils::SingleShaderStage::Compute, R"( |
| #version 450 |
| layout(set = 0, binding = 0, rgba8) uniform readonly image2D image0; |
| layout(std430, set = 0, binding = 0) buffer Buf { uint buf; }; |
| void main() { |
| vec4 pixel = imageLoad(image0, ivec2(gl_LocalInvocationID.xy)); |
| buf = uint(pixel.x); |
| })"); |
| |
| wgpu::ComputePipelineDescriptor descriptor; |
| descriptor.layout = nullptr; |
| descriptor.computeStage.module = csModule; |
| descriptor.computeStage.entryPoint = "main"; |
| |
| device.CreateComputePipeline(&descriptor); |
| } |
| |
| // Write-only storage textures can be declared in a compute shader. |
| { |
| wgpu::ShaderModule csModule = |
| utils::CreateShaderModule(device, utils::SingleShaderStage::Compute, R"( |
| #version 450 |
| layout(set = 0, binding = 0, rgba8) uniform writeonly image2D image0; |
| void main() { |
| imageStore(image0, ivec2(gl_LocalInvocationID.xy), vec4(0.f, 0.f, 0.f, 0.f)); |
| })"); |
| |
| wgpu::ComputePipelineDescriptor descriptor; |
| descriptor.layout = nullptr; |
| descriptor.computeStage.module = csModule; |
| descriptor.computeStage.entryPoint = "main"; |
| |
| device.CreateComputePipeline(&descriptor); |
| } |
| } |
| |
| // Validate read-write storage textures have not been supported yet. |
| TEST_F(StorageTextureValidationTests, ReadWriteStorageTexture) { |
| // Read-write storage textures cannot be declared in a vertex shader by default. |
| { |
| wgpu::ShaderModule vsModule = |
| utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"( |
| #version 450 |
| layout(set = 0, binding = 0, rgba8) uniform image2D image0; |
| void main() { |
| vec4 pixel = imageLoad(image0, ivec2(gl_VertexIndex, 0)); |
| imageStore(image0, ivec2(gl_VertexIndex, 0), pixel * 2); |
| })"); |
| |
| utils::ComboRenderPipelineDescriptor descriptor(device); |
| descriptor.layout = nullptr; |
| descriptor.vertexStage.module = vsModule; |
| descriptor.cFragmentStage.module = mDefaultFSModule; |
| ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor)); |
| } |
| |
| // Read-write storage textures cannot be declared in a fragment shader by default. |
| { |
| wgpu::ShaderModule fsModule = |
| utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"( |
| #version 450 |
| layout(set = 0, binding = 0, rgba8) uniform image2D image0; |
| void main() { |
| vec4 pixel = imageLoad(image0, ivec2(gl_FragCoord.xy)); |
| imageStore(image0, ivec2(gl_FragCoord.xy), pixel * 2); |
| })"); |
| |
| utils::ComboRenderPipelineDescriptor descriptor(device); |
| descriptor.layout = nullptr; |
| descriptor.vertexStage.module = mDefaultVSModule; |
| descriptor.cFragmentStage.module = fsModule; |
| ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor)); |
| } |
| |
| // Read-write storage textures cannot be declared in a compute shader by default. |
| { |
| wgpu::ShaderModule csModule = |
| utils::CreateShaderModule(device, utils::SingleShaderStage::Compute, R"( |
| #version 450 |
| layout(set = 0, binding = 0, rgba8) uniform image2D image0; |
| void main() { |
| vec4 pixel = imageLoad(image0, ivec2(gl_LocalInvocationID.xy)); |
| imageStore(image0, ivec2(gl_LocalInvocationID.xy), pixel * 2); |
| })"); |
| |
| wgpu::ComputePipelineDescriptor descriptor; |
| descriptor.layout = nullptr; |
| descriptor.computeStage.module = csModule; |
| descriptor.computeStage.entryPoint = "main"; |
| |
| ASSERT_DEVICE_ERROR(device.CreateComputePipeline(&descriptor)); |
| } |
| } |
| |
| // Test that using read-only storage texture and write-only storage texture in |
| // BindGroupLayout is valid, while using read-write storage texture is not allowed now. |
| TEST_F(StorageTextureValidationTests, BindGroupLayoutWithStorageTextureBindingType) { |
| struct TestSpec { |
| wgpu::ShaderStage stage; |
| wgpu::BindingType type; |
| bool valid; |
| }; |
| constexpr std::array<TestSpec, 9> kTestSpecs = { |
| {{wgpu::ShaderStage::Vertex, wgpu::BindingType::ReadonlyStorageTexture, true}, |
| {wgpu::ShaderStage::Vertex, wgpu::BindingType::WriteonlyStorageTexture, false}, |
| {wgpu::ShaderStage::Vertex, wgpu::BindingType::StorageTexture, false}, |
| {wgpu::ShaderStage::Fragment, wgpu::BindingType::ReadonlyStorageTexture, true}, |
| {wgpu::ShaderStage::Fragment, wgpu::BindingType::WriteonlyStorageTexture, false}, |
| {wgpu::ShaderStage::Fragment, wgpu::BindingType::StorageTexture, false}, |
| {wgpu::ShaderStage::Compute, wgpu::BindingType::ReadonlyStorageTexture, true}, |
| {wgpu::ShaderStage::Compute, wgpu::BindingType::WriteonlyStorageTexture, true}, |
| {wgpu::ShaderStage::Compute, wgpu::BindingType::StorageTexture, false}}}; |
| |
| for (const auto& testSpec : kTestSpecs) { |
| wgpu::BindGroupLayoutBinding binding = {0, testSpec.stage, testSpec.type}; |
| wgpu::BindGroupLayoutDescriptor descriptor; |
| descriptor.bindingCount = 1; |
| descriptor.bindings = &binding; |
| |
| if (testSpec.valid) { |
| device.CreateBindGroupLayout(&descriptor); |
| } else { |
| ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&descriptor)); |
| } |
| } |
| } |