From 421684f9438d2c847b1c6571055074272d15c089 Mon Sep 17 00:00:00 2001 From: Jiawei Shao Date: Wed, 11 Mar 2020 01:28:48 +0000 Subject: [PATCH] Support Storage Textures as Valid Binding Types This patch adds the basic validation of read-only storage texture, write-only storage texture and read-write storage texture as new binding types with no bind group layout provided in the creation of pipeline state objects. - Read-only storage textures can be used in vertex, fragment and compute shaders. - Write-only storage textures can only be used in compute shaders due to the limitation on Metal. - Read-write storage textures are not allowed now and they are reserved to be supported as an extension in the future. BUG=dawn:267 TEST=dawn_unittests Change-Id: Iffc432f29a855b85d59451cb3c50269e03b84627 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/16661 Commit-Queue: Jiawei Shao Reviewed-by: Corentin Wallez --- BUILD.gn | 2 + dawn.json | 4 +- src/dawn_native/BindGroup.cpp | 6 + .../BindGroupAndStorageBarrierTracker.h | 5 +- src/dawn_native/BindGroupLayout.cpp | 41 +++- src/dawn_native/CMakeLists.txt | 1 + src/dawn_native/PipelineLayout.cpp | 36 ++- src/dawn_native/ProgrammablePassEncoder.cpp | 2 + src/dawn_native/ShaderModule.cpp | 12 + src/dawn_native/d3d12/BindGroupD3D12.cpp | 6 +- .../d3d12/BindGroupLayoutD3D12.cpp | 6 + src/dawn_native/d3d12/CommandBufferD3D12.cpp | 4 + src/dawn_native/d3d12/PipelineLayoutD3D12.cpp | 2 + src/dawn_native/metal/CommandBufferMTL.mm | 2 + src/dawn_native/metal/PipelineLayoutMTL.mm | 2 + src/dawn_native/opengl/CommandBufferGL.cpp | 2 + src/dawn_native/opengl/PipelineGL.cpp | 2 + src/dawn_native/opengl/PipelineLayoutGL.cpp | 2 + src/dawn_native/vulkan/CommandBufferVk.cpp | 2 + .../StorageTextureValidationTests.cpp | 207 ++++++++++++++++++ 20 files changed, 326 insertions(+), 20 deletions(-) create mode 100644 src/tests/unittests/validation/StorageTextureValidationTests.cpp diff --git a/BUILD.gn b/BUILD.gn index f09523e7ae..6a4f381956 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -171,6 +171,7 @@ source_set("libdawn_native_sources") { "src/dawn_native/BackendConnection.h", "src/dawn_native/BindGroup.cpp", "src/dawn_native/BindGroup.h", + "src/dawn_native/BindGroupAndStorageBarrierTracker.h", "src/dawn_native/BindGroupLayout.cpp", "src/dawn_native/BindGroupLayout.h", "src/dawn_native/BindGroupTracker.h", @@ -863,6 +864,7 @@ test("dawn_unittests") { "src/tests/unittests/validation/RenderPipelineValidationTests.cpp", "src/tests/unittests/validation/SamplerValidationTests.cpp", "src/tests/unittests/validation/ShaderModuleValidationTests.cpp", + "src/tests/unittests/validation/StorageTextureValidationTests.cpp", "src/tests/unittests/validation/TextureValidationTests.cpp", "src/tests/unittests/validation/TextureViewValidationTests.cpp", "src/tests/unittests/validation/ToggleValidationTests.cpp", diff --git a/dawn.json b/dawn.json index 434ad14d8b..bec9c9ea0a 100644 --- a/dawn.json +++ b/dawn.json @@ -114,7 +114,9 @@ {"value": 2, "name": "readonly storage buffer"}, {"value": 3, "name": "sampler"}, {"value": 4, "name": "sampled texture"}, - {"value": 5, "name": "storage texture"} + {"value": 5, "name": "storage texture"}, + {"value": 6, "name": "readonly storage texture"}, + {"value": 7, "name": "writeonly storage texture"} ] }, "blend descriptor": { diff --git a/src/dawn_native/BindGroup.cpp b/src/dawn_native/BindGroup.cpp index 9e85be14da..fe8166f56f 100644 --- a/src/dawn_native/BindGroup.cpp +++ b/src/dawn_native/BindGroup.cpp @@ -159,6 +159,12 @@ namespace dawn_native { case wgpu::BindingType::Sampler: DAWN_TRY(ValidateSamplerBinding(device, binding)); break; + // TODO(jiawei.shao@intel.com): support creating bind group with read-only and + // write-only storage textures. + case wgpu::BindingType::ReadonlyStorageTexture: + case wgpu::BindingType::WriteonlyStorageTexture: + return DAWN_VALIDATION_ERROR( + "Readonly and writeonly storage textures are not supported."); case wgpu::BindingType::StorageTexture: UNREACHABLE(); break; diff --git a/src/dawn_native/BindGroupAndStorageBarrierTracker.h b/src/dawn_native/BindGroupAndStorageBarrierTracker.h index 0c016e1be7..06fc268a98 100644 --- a/src/dawn_native/BindGroupAndStorageBarrierTracker.h +++ b/src/dawn_native/BindGroupAndStorageBarrierTracker.h @@ -15,9 +15,8 @@ #ifndef DAWNNATIVE_BINDGROUPANDSTORAGEBARRIERTRACKER_H_ #define DAWNNATIVE_BINDGROUPANDSTORAGEBARRIERTRACKER_H_ -#include "dawn_native/BindGroupTracker.h" - #include "dawn_native/BindGroup.h" +#include "dawn_native/BindGroupTracker.h" namespace dawn_native { @@ -62,6 +61,8 @@ namespace dawn_native { break; case wgpu::BindingType::StorageTexture: + case wgpu::BindingType::ReadonlyStorageTexture: + case wgpu::BindingType::WriteonlyStorageTexture: // Not implemented. default: diff --git a/src/dawn_native/BindGroupLayout.cpp b/src/dawn_native/BindGroupLayout.cpp index d16330aeab..c583137734 100644 --- a/src/dawn_native/BindGroupLayout.cpp +++ b/src/dawn_native/BindGroupLayout.cpp @@ -14,22 +14,45 @@ #include "dawn_native/BindGroupLayout.h" +#include + #include "common/BitSetIterator.h" #include "common/HashUtils.h" #include "dawn_native/Device.h" #include "dawn_native/ValidationUtils_autogen.h" -#include - namespace dawn_native { MaybeError ValidateBindingTypeWithShaderStageVisibility( wgpu::BindingType bindingType, wgpu::ShaderStage shaderStageVisibility) { - if (bindingType == wgpu::BindingType::StorageBuffer && - (shaderStageVisibility & wgpu::ShaderStage::Vertex) != 0) { - return DAWN_VALIDATION_ERROR( - "storage buffer binding is not supported in vertex shader"); + // TODO(jiawei.shao@intel.com): support read-write storage textures. + switch (bindingType) { + case wgpu::BindingType::StorageBuffer: { + if ((shaderStageVisibility & wgpu::ShaderStage::Vertex) != 0) { + return DAWN_VALIDATION_ERROR( + "storage buffer binding is not supported in vertex shader"); + } + } break; + + case wgpu::BindingType::WriteonlyStorageTexture: { + if ((shaderStageVisibility & + (wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment)) != 0) { + return DAWN_VALIDATION_ERROR( + "write-only storage texture binding is only supported in compute shader"); + } + } break; + + case wgpu::BindingType::StorageTexture: { + return DAWN_VALIDATION_ERROR("Read-write storage texture binding is not supported"); + } break; + + case wgpu::BindingType::UniformBuffer: + case wgpu::BindingType::ReadonlyStorageBuffer: + case wgpu::BindingType::Sampler: + case wgpu::BindingType::SampledTexture: + case wgpu::BindingType::ReadonlyStorageTexture: + break; } return {}; @@ -78,6 +101,8 @@ namespace dawn_native { break; case wgpu::BindingType::SampledTexture: case wgpu::BindingType::Sampler: + case wgpu::BindingType::ReadonlyStorageTexture: + case wgpu::BindingType::WriteonlyStorageTexture: if (binding.hasDynamicOffset) { return DAWN_VALIDATION_ERROR("Samplers and textures cannot be dynamic"); } @@ -105,7 +130,7 @@ namespace dawn_native { } return {}; - } + } // namespace dawn_native namespace { size_t HashBindingInfo(const BindGroupLayoutBase::LayoutBindingInfo& info) { @@ -171,6 +196,8 @@ namespace dawn_native { case wgpu::BindingType::SampledTexture: case wgpu::BindingType::Sampler: case wgpu::BindingType::StorageTexture: + case wgpu::BindingType::ReadonlyStorageTexture: + case wgpu::BindingType::WriteonlyStorageTexture: UNREACHABLE(); break; } diff --git a/src/dawn_native/CMakeLists.txt b/src/dawn_native/CMakeLists.txt index e90f790614..b3e4a8969a 100644 --- a/src/dawn_native/CMakeLists.txt +++ b/src/dawn_native/CMakeLists.txt @@ -31,6 +31,7 @@ target_sources(dawn_native PRIVATE "BackendConnection.h" "BindGroup.cpp" "BindGroup.h" + "BindGroupAndStorageBarrierTracker.h" "BindGroupLayout.cpp" "BindGroupLayout.h" "BindGroupTracker.h" diff --git a/src/dawn_native/PipelineLayout.cpp b/src/dawn_native/PipelineLayout.cpp index 5cdf781a35..6f22685871 100644 --- a/src/dawn_native/PipelineLayout.cpp +++ b/src/dawn_native/PipelineLayout.cpp @@ -33,6 +33,31 @@ namespace dawn_native { lhs.textureComponentType == rhs.textureComponentType; } + wgpu::ShaderStage GetShaderStageVisibilityWithBindingType(wgpu::BindingType bindingType) { + // TODO(jiawei.shao@intel.com): support read-only and read-write storage textures. + switch (bindingType) { + case wgpu::BindingType::StorageBuffer: + return wgpu::ShaderStage::Fragment | wgpu::ShaderStage::Compute; + + case wgpu::BindingType::WriteonlyStorageTexture: + return wgpu::ShaderStage::Compute; + + case wgpu::BindingType::StorageTexture: + UNREACHABLE(); + return wgpu::ShaderStage::None; + + case wgpu::BindingType::UniformBuffer: + case wgpu::BindingType::ReadonlyStorageBuffer: + case wgpu::BindingType::Sampler: + case wgpu::BindingType::SampledTexture: + case wgpu::BindingType::ReadonlyStorageTexture: + return wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment | + wgpu::ShaderStage::Compute; + } + + return {}; + } + } // anonymous namespace MaybeError ValidatePipelineLayoutDescriptor(DeviceBase* device, @@ -135,14 +160,9 @@ namespace dawn_native { DAWN_TRY(ValidateBindingTypeWithShaderStageVisibility( bindingInfo.type, StageBit(module->GetExecutionModel()))); - if (bindingInfo.type == wgpu::BindingType::StorageBuffer) { - bindingSlot.visibility = - wgpu::ShaderStage::Fragment | wgpu::ShaderStage::Compute; - } else { - bindingSlot.visibility = wgpu::ShaderStage::Vertex | - wgpu::ShaderStage::Fragment | - wgpu::ShaderStage::Compute; - } + bindingSlot.visibility = + GetShaderStageVisibilityWithBindingType(bindingInfo.type); + bindingSlot.type = bindingInfo.type; bindingSlot.hasDynamicOffset = false; bindingSlot.multisampled = bindingInfo.multisampled; diff --git a/src/dawn_native/ProgrammablePassEncoder.cpp b/src/dawn_native/ProgrammablePassEncoder.cpp index bedf0c4417..a68c58fb72 100644 --- a/src/dawn_native/ProgrammablePassEncoder.cpp +++ b/src/dawn_native/ProgrammablePassEncoder.cpp @@ -59,6 +59,8 @@ namespace dawn_native { break; case wgpu::BindingType::StorageTexture: + case wgpu::BindingType::ReadonlyStorageTexture: + case wgpu::BindingType::WriteonlyStorageTexture: UNREACHABLE(); break; } diff --git a/src/dawn_native/ShaderModule.cpp b/src/dawn_native/ShaderModule.cpp index eca1733c47..a6a12c56ee 100644 --- a/src/dawn_native/ShaderModule.cpp +++ b/src/dawn_native/ShaderModule.cpp @@ -406,6 +406,16 @@ namespace dawn_native { info->type = wgpu::BindingType::StorageBuffer; } } break; + case wgpu::BindingType::StorageTexture: { + spirv_cross::Bitset flags = compiler.get_decoration_bitset(resource.id); + if (flags.get(spv::DecorationNonReadable)) { + info->type = wgpu::BindingType::WriteonlyStorageTexture; + } else if (flags.get(spv::DecorationNonWritable)) { + info->type = wgpu::BindingType::ReadonlyStorageTexture; + } else { + info->type = wgpu::BindingType::StorageTexture; + } + } break; default: info->type = bindingType; } @@ -421,6 +431,8 @@ namespace dawn_native { wgpu::BindingType::Sampler)); DAWN_TRY(ExtractResourcesBinding(resources.storage_buffers, compiler, wgpu::BindingType::StorageBuffer)); + DAWN_TRY(ExtractResourcesBinding(resources.storage_images, compiler, + wgpu::BindingType::StorageTexture)); // Extract the vertex attributes if (mExecutionModel == SingleShaderStage::Vertex) { diff --git a/src/dawn_native/d3d12/BindGroupD3D12.cpp b/src/dawn_native/d3d12/BindGroupD3D12.cpp index c8138b2004..0f9ea86d26 100644 --- a/src/dawn_native/d3d12/BindGroupD3D12.cpp +++ b/src/dawn_native/d3d12/BindGroupD3D12.cpp @@ -13,15 +13,15 @@ // limitations under the License. #include "dawn_native/d3d12/BindGroupD3D12.h" + #include "common/BitSetIterator.h" #include "dawn_native/d3d12/BindGroupLayoutD3D12.h" #include "dawn_native/d3d12/BufferD3D12.h" +#include "dawn_native/d3d12/DeviceD3D12.h" #include "dawn_native/d3d12/SamplerD3D12.h" #include "dawn_native/d3d12/ShaderVisibleDescriptorAllocatorD3D12.h" #include "dawn_native/d3d12/TextureD3D12.h" -#include "dawn_native/d3d12/DeviceD3D12.h" - namespace dawn_native { namespace d3d12 { BindGroup::BindGroup(Device* device, const BindGroupDescriptor* descriptor) @@ -161,6 +161,8 @@ namespace dawn_native { namespace d3d12 { } break; case wgpu::BindingType::StorageTexture: + case wgpu::BindingType::ReadonlyStorageTexture: + case wgpu::BindingType::WriteonlyStorageTexture: UNREACHABLE(); break; diff --git a/src/dawn_native/d3d12/BindGroupLayoutD3D12.cpp b/src/dawn_native/d3d12/BindGroupLayoutD3D12.cpp index cce919634e..80e9d8c430 100644 --- a/src/dawn_native/d3d12/BindGroupLayoutD3D12.cpp +++ b/src/dawn_native/d3d12/BindGroupLayoutD3D12.cpp @@ -47,6 +47,8 @@ namespace dawn_native { namespace d3d12 { break; case wgpu::BindingType::StorageTexture: + case wgpu::BindingType::ReadonlyStorageTexture: + case wgpu::BindingType::WriteonlyStorageTexture: UNREACHABLE(); break; } @@ -107,6 +109,8 @@ namespace dawn_native { namespace d3d12 { case wgpu::BindingType::SampledTexture: case wgpu::BindingType::Sampler: case wgpu::BindingType::StorageTexture: + case wgpu::BindingType::ReadonlyStorageTexture: + case wgpu::BindingType::WriteonlyStorageTexture: UNREACHABLE(); break; } @@ -129,6 +133,8 @@ namespace dawn_native { namespace d3d12 { break; case wgpu::BindingType::StorageTexture: + case wgpu::BindingType::ReadonlyStorageTexture: + case wgpu::BindingType::WriteonlyStorageTexture: UNREACHABLE(); break; diff --git a/src/dawn_native/d3d12/CommandBufferD3D12.cpp b/src/dawn_native/d3d12/CommandBufferD3D12.cpp index c7c42fea90..7f7bc6ab5b 100644 --- a/src/dawn_native/d3d12/CommandBufferD3D12.cpp +++ b/src/dawn_native/d3d12/CommandBufferD3D12.cpp @@ -136,6 +136,8 @@ namespace dawn_native { namespace d3d12 { break; case wgpu::BindingType::StorageTexture: + case wgpu::BindingType::ReadonlyStorageTexture: + case wgpu::BindingType::WriteonlyStorageTexture: // Not implemented. case wgpu::BindingType::UniformBuffer: @@ -224,6 +226,8 @@ namespace dawn_native { namespace d3d12 { case wgpu::BindingType::SampledTexture: case wgpu::BindingType::Sampler: case wgpu::BindingType::StorageTexture: + case wgpu::BindingType::ReadonlyStorageTexture: + case wgpu::BindingType::WriteonlyStorageTexture: UNREACHABLE(); break; } diff --git a/src/dawn_native/d3d12/PipelineLayoutD3D12.cpp b/src/dawn_native/d3d12/PipelineLayoutD3D12.cpp index 8d3dd175eb..7afadc780b 100644 --- a/src/dawn_native/d3d12/PipelineLayoutD3D12.cpp +++ b/src/dawn_native/d3d12/PipelineLayoutD3D12.cpp @@ -51,6 +51,8 @@ namespace dawn_native { namespace d3d12 { case wgpu::BindingType::SampledTexture: case wgpu::BindingType::Sampler: case wgpu::BindingType::StorageTexture: + case wgpu::BindingType::ReadonlyStorageTexture: + case wgpu::BindingType::WriteonlyStorageTexture: UNREACHABLE(); } } diff --git a/src/dawn_native/metal/CommandBufferMTL.mm b/src/dawn_native/metal/CommandBufferMTL.mm index 628f77b856..7958217c98 100644 --- a/src/dawn_native/metal/CommandBufferMTL.mm +++ b/src/dawn_native/metal/CommandBufferMTL.mm @@ -599,6 +599,8 @@ namespace dawn_native { namespace metal { } break; case wgpu::BindingType::StorageTexture: + case wgpu::BindingType::ReadonlyStorageTexture: + case wgpu::BindingType::WriteonlyStorageTexture: UNREACHABLE(); break; } diff --git a/src/dawn_native/metal/PipelineLayoutMTL.mm b/src/dawn_native/metal/PipelineLayoutMTL.mm index 1d3e22088d..3a092380fa 100644 --- a/src/dawn_native/metal/PipelineLayoutMTL.mm +++ b/src/dawn_native/metal/PipelineLayoutMTL.mm @@ -54,6 +54,8 @@ namespace dawn_native { namespace metal { textureIndex++; break; case wgpu::BindingType::StorageTexture: + case wgpu::BindingType::ReadonlyStorageTexture: + case wgpu::BindingType::WriteonlyStorageTexture: UNREACHABLE(); break; } diff --git a/src/dawn_native/opengl/CommandBufferGL.cpp b/src/dawn_native/opengl/CommandBufferGL.cpp index 2194016fb2..25d3c81265 100644 --- a/src/dawn_native/opengl/CommandBufferGL.cpp +++ b/src/dawn_native/opengl/CommandBufferGL.cpp @@ -305,6 +305,8 @@ namespace dawn_native { namespace opengl { } break; case wgpu::BindingType::StorageTexture: + case wgpu::BindingType::ReadonlyStorageTexture: + case wgpu::BindingType::WriteonlyStorageTexture: UNREACHABLE(); break; diff --git a/src/dawn_native/opengl/PipelineGL.cpp b/src/dawn_native/opengl/PipelineGL.cpp index e2808758fd..71e2b83fb9 100644 --- a/src/dawn_native/opengl/PipelineGL.cpp +++ b/src/dawn_native/opengl/PipelineGL.cpp @@ -140,6 +140,8 @@ namespace dawn_native { namespace opengl { break; case wgpu::BindingType::StorageTexture: + case wgpu::BindingType::ReadonlyStorageTexture: + case wgpu::BindingType::WriteonlyStorageTexture: UNREACHABLE(); break; diff --git a/src/dawn_native/opengl/PipelineLayoutGL.cpp b/src/dawn_native/opengl/PipelineLayoutGL.cpp index 530e7d0f83..18fd4f2fe1 100644 --- a/src/dawn_native/opengl/PipelineLayoutGL.cpp +++ b/src/dawn_native/opengl/PipelineLayoutGL.cpp @@ -56,6 +56,8 @@ namespace dawn_native { namespace opengl { break; case wgpu::BindingType::StorageTexture: + case wgpu::BindingType::ReadonlyStorageTexture: + case wgpu::BindingType::WriteonlyStorageTexture: UNREACHABLE(); break; diff --git a/src/dawn_native/vulkan/CommandBufferVk.cpp b/src/dawn_native/vulkan/CommandBufferVk.cpp index 7e128066c7..ab70419cc9 100644 --- a/src/dawn_native/vulkan/CommandBufferVk.cpp +++ b/src/dawn_native/vulkan/CommandBufferVk.cpp @@ -149,6 +149,8 @@ namespace dawn_native { namespace vulkan { break; case wgpu::BindingType::StorageTexture: + case wgpu::BindingType::ReadonlyStorageTexture: + case wgpu::BindingType::WriteonlyStorageTexture: // Not implemented. case wgpu::BindingType::UniformBuffer: diff --git a/src/tests/unittests/validation/StorageTextureValidationTests.cpp b/src/tests/unittests/validation/StorageTextureValidationTests.cpp new file mode 100644 index 0000000000..56cf999925 --- /dev/null +++ b/src/tests/unittests/validation/StorageTextureValidationTests.cpp @@ -0,0 +1,207 @@ +// 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)); + } +}