| // 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 "common/Assert.h" | |
| #include "tests/unittests/validation/ValidationTest.h" | |
| #include "utils/ComboRenderPipelineDescriptor.h" | |
| #include "utils/TextureUtils.h" | |
| #include "utils/WGPUHelpers.h" | |
| class StorageTextureValidationTests : public ValidationTest { | |
| protected: | |
| void SetUp() override { | |
| ValidationTest::SetUp(); | |
| mDefaultVSModule = utils::CreateShaderModule(device, R"( | |
| [[stage(vertex)]] fn main() -> [[builtin(position)]] vec4<f32> { | |
| return vec4<f32>(0.0, 0.0, 0.0, 1.0); | |
| })"); | |
| mDefaultFSModule = utils::CreateShaderModule(device, R"( | |
| [[stage(fragment)]] fn main() -> [[location(0)]] vec4<f32> { | |
| return vec4<f32>(1.0, 0.0, 0.0, 1.0); | |
| })"); | |
| } | |
| static const char* GetFloatImageTypeDeclaration(wgpu::TextureViewDimension dimension) { | |
| // TODO(bclayton): Support and test texture_storage_1d_array | |
| switch (dimension) { | |
| case wgpu::TextureViewDimension::e1D: | |
| return "texture_storage_1d"; | |
| case wgpu::TextureViewDimension::e2D: | |
| return "texture_storage_2d"; | |
| case wgpu::TextureViewDimension::e2DArray: | |
| return "texture_storage_2d_array"; | |
| case wgpu::TextureViewDimension::e3D: | |
| return "texture_storage_3d"; | |
| case wgpu::TextureViewDimension::Cube: | |
| return "texture_storage_cube"; // Note: Doesn't exist in WGSL (yet) | |
| case wgpu::TextureViewDimension::CubeArray: | |
| return "texture_storage_cube_array"; // Note: Doesn't exist in WGSL (yet) | |
| case wgpu::TextureViewDimension::Undefined: | |
| default: | |
| UNREACHABLE(); | |
| return ""; | |
| } | |
| } | |
| static std::string CreateComputeShaderWithStorageTexture( | |
| wgpu::StorageTextureAccess storageTextureBindingType, | |
| wgpu::TextureFormat textureFormat, | |
| wgpu::TextureViewDimension textureViewDimension = wgpu::TextureViewDimension::e2D) { | |
| return CreateComputeShaderWithStorageTexture( | |
| storageTextureBindingType, utils::GetWGSLImageFormatQualifier(textureFormat), | |
| GetFloatImageTypeDeclaration(textureViewDimension)); | |
| } | |
| static std::string CreateComputeShaderWithStorageTexture( | |
| wgpu::StorageTextureAccess storageTextureBindingType, | |
| const char* imageFormatQualifier, | |
| const char* imageTypeDeclaration = "texture_storage_2d") { | |
| const char* access = ""; | |
| switch (storageTextureBindingType) { | |
| case wgpu::StorageTextureAccess::WriteOnly: | |
| access = "write"; | |
| break; | |
| default: | |
| UNREACHABLE(); | |
| break; | |
| } | |
| std::ostringstream ostream; | |
| ostream << "[[group(0), binding(0)]] var image0 : " << imageTypeDeclaration << "<" | |
| << imageFormatQualifier << ", " << access | |
| << ">;\n" | |
| "[[stage(compute), workgroup_size(1)]] fn main() {\n" | |
| " textureDimensions(image0);\n" | |
| "}\n"; | |
| return ostream.str(); | |
| } | |
| wgpu::Texture CreateTexture(wgpu::TextureUsage usage, | |
| wgpu::TextureFormat format, | |
| uint32_t sampleCount = 1, | |
| uint32_t arrayLayerCount = 1, | |
| wgpu::TextureDimension dimension = wgpu::TextureDimension::e2D) { | |
| wgpu::TextureDescriptor descriptor; | |
| descriptor.dimension = dimension; | |
| descriptor.size = {16, 16, arrayLayerCount}; | |
| descriptor.sampleCount = sampleCount; | |
| descriptor.format = format; | |
| descriptor.mipLevelCount = 1; | |
| descriptor.usage = usage; | |
| return device.CreateTexture(&descriptor); | |
| } | |
| wgpu::ShaderModule mDefaultVSModule; | |
| wgpu::ShaderModule mDefaultFSModule; | |
| const std::array<wgpu::StorageTextureAccess, 1> kSupportedStorageTextureAccess = { | |
| wgpu::StorageTextureAccess::WriteOnly}; | |
| }; | |
| // Validate read-only storage textures can be declared in vertex and fragment shaders, while | |
| // writeonly storage textures cannot be used in vertex shaders. | |
| TEST_F(StorageTextureValidationTests, RenderPipeline) { | |
| // Write-only storage textures cannot be declared in a vertex shader. | |
| { | |
| wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"( | |
| [[group(0), binding(0)]] var image0 : texture_storage_2d<rgba8unorm, write>; | |
| [[stage(vertex)]] | |
| fn main([[builtin(vertex_index)]] vertex_index : u32) -> [[builtin(position)]] vec4<f32> { | |
| textureStore(image0, vec2<i32>(i32(vertex_index), 0), vec4<f32>(1.0, 0.0, 0.0, 1.0)); | |
| return vec4<f32>(0.0); | |
| })"); | |
| utils::ComboRenderPipelineDescriptor descriptor; | |
| descriptor.layout = nullptr; | |
| descriptor.vertex.module = vsModule; | |
| descriptor.cFragment.module = mDefaultFSModule; | |
| ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor)); | |
| } | |
| // Write-only storage textures can be declared in a fragment shader. | |
| { | |
| wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( | |
| [[group(0), binding(0)]] var image0 : texture_storage_2d<rgba8unorm, write>; | |
| [[stage(fragment)]] fn main([[builtin(position)]] position : vec4<f32>) { | |
| textureStore(image0, vec2<i32>(position.xy), vec4<f32>(1.0, 0.0, 0.0, 1.0)); | |
| })"); | |
| utils::ComboRenderPipelineDescriptor descriptor; | |
| descriptor.layout = nullptr; | |
| descriptor.vertex.module = mDefaultVSModule; | |
| descriptor.cFragment.module = fsModule; | |
| descriptor.cTargets[0].writeMask = wgpu::ColorWriteMask::None; | |
| device.CreateRenderPipeline(&descriptor); | |
| } | |
| } | |
| // Validate both read-only and write-only storage textures can be declared in | |
| // compute shaders. | |
| TEST_F(StorageTextureValidationTests, ComputePipeline) { | |
| // Write-only storage textures can be declared in a compute shader. | |
| { | |
| wgpu::ShaderModule csModule = utils::CreateShaderModule(device, R"( | |
| [[group(0), binding(0)]] var image0 : texture_storage_2d<rgba8unorm, write>; | |
| [[stage(compute), workgroup_size(1)]] fn main([[builtin(local_invocation_id)]] LocalInvocationID : vec3<u32>) { | |
| textureStore(image0, vec2<i32>(LocalInvocationID.xy), vec4<f32>(0.0, 0.0, 0.0, 0.0)); | |
| })"); | |
| wgpu::ComputePipelineDescriptor descriptor; | |
| descriptor.layout = nullptr; | |
| descriptor.compute.module = csModule; | |
| descriptor.compute.entryPoint = "main"; | |
| device.CreateComputePipeline(&descriptor); | |
| } | |
| } | |
| // Validate read-write storage textures are not currently supported. | |
| TEST_F(StorageTextureValidationTests, ReadWriteStorageTexture) { | |
| // Read-write storage textures cannot be declared in a vertex shader by default. | |
| { | |
| ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, R"( | |
| [[group(0), binding(0)]] var image0 : texture_storage_2d<rgba8unorm, read_write>; | |
| [[stage(vertex)]] fn main() { | |
| textureDimensions(image0); | |
| })")); | |
| } | |
| // Read-write storage textures cannot be declared in a fragment shader by default. | |
| { | |
| ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, R"( | |
| [[group(0), binding(0)]] var image0 : texture_storage_2d<rgba8unorm, read_write>; | |
| [[stage(fragment)]] fn main() { | |
| textureDimensions(image0); | |
| })")); | |
| } | |
| // Read-write storage textures cannot be declared in a compute shader by default. | |
| { | |
| ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, R"( | |
| [[group(0), binding(0)]] var image0 : texture_storage_2d<rgba8unorm, read_write>; | |
| [[stage(compute), workgroup_size(1)]] fn main() { | |
| textureDimensions(image0); | |
| })")); | |
| } | |
| } | |
| // 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::StorageTextureAccess type; | |
| bool valid; | |
| }; | |
| constexpr std::array<TestSpec, 6> kTestSpecs = { | |
| {{wgpu::ShaderStage::Vertex, wgpu::StorageTextureAccess::WriteOnly, false}, | |
| {wgpu::ShaderStage::Fragment, wgpu::StorageTextureAccess::WriteOnly, true}, | |
| {wgpu::ShaderStage::Compute, wgpu::StorageTextureAccess::WriteOnly, true}}}; | |
| for (const auto& testSpec : kTestSpecs) { | |
| wgpu::BindGroupLayoutEntry entry = utils::BindingLayoutEntryInitializationHelper( | |
| 0, testSpec.stage, testSpec.type, wgpu::TextureFormat::R32Uint); | |
| wgpu::BindGroupLayoutDescriptor descriptor; | |
| descriptor.entryCount = 1; | |
| descriptor.entries = &entry; | |
| if (testSpec.valid) { | |
| device.CreateBindGroupLayout(&descriptor); | |
| } else { | |
| ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&descriptor)); | |
| } | |
| } | |
| } | |
| // Validate it is an error to declare a read-only or write-only storage texture in shaders with any | |
| // format that doesn't support TextureUsage::StorageBinding texture usages. | |
| TEST_F(StorageTextureValidationTests, StorageTextureFormatInShaders) { | |
| // Not include RGBA8UnormSrgb, BGRA8Unorm, BGRA8UnormSrgb because they are not related to any | |
| // SPIR-V Image Formats. | |
| constexpr std::array<wgpu::TextureFormat, 32> kWGPUTextureFormatSupportedAsSPIRVImageFormats = { | |
| wgpu::TextureFormat::R32Uint, wgpu::TextureFormat::R32Sint, | |
| wgpu::TextureFormat::R32Float, wgpu::TextureFormat::RGBA8Unorm, | |
| wgpu::TextureFormat::RGBA8Snorm, wgpu::TextureFormat::RGBA8Uint, | |
| wgpu::TextureFormat::RGBA8Sint, wgpu::TextureFormat::RG32Uint, | |
| wgpu::TextureFormat::RG32Sint, wgpu::TextureFormat::RG32Float, | |
| wgpu::TextureFormat::RGBA16Uint, wgpu::TextureFormat::RGBA16Sint, | |
| wgpu::TextureFormat::RGBA16Float, wgpu::TextureFormat::RGBA32Uint, | |
| wgpu::TextureFormat::RGBA32Sint, wgpu::TextureFormat::RGBA32Float, | |
| wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::R8Snorm, | |
| wgpu::TextureFormat::R8Uint, wgpu::TextureFormat::R8Sint, | |
| wgpu::TextureFormat::R16Uint, wgpu::TextureFormat::R16Sint, | |
| wgpu::TextureFormat::R16Float, wgpu::TextureFormat::RG8Unorm, | |
| wgpu::TextureFormat::RG8Snorm, wgpu::TextureFormat::RG8Uint, | |
| wgpu::TextureFormat::RG8Sint, wgpu::TextureFormat::RG16Uint, | |
| wgpu::TextureFormat::RG16Sint, wgpu::TextureFormat::RG16Float, | |
| wgpu::TextureFormat::RGB10A2Unorm, wgpu::TextureFormat::RG11B10Ufloat}; | |
| for (wgpu::StorageTextureAccess storageTextureBindingType : kSupportedStorageTextureAccess) { | |
| for (wgpu::TextureFormat format : kWGPUTextureFormatSupportedAsSPIRVImageFormats) { | |
| std::string computeShader = | |
| CreateComputeShaderWithStorageTexture(storageTextureBindingType, format); | |
| if (utils::TextureFormatSupportsStorageTexture(format)) { | |
| utils::CreateShaderModule(device, computeShader.c_str()); | |
| } else { | |
| ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, computeShader.c_str())); | |
| } | |
| } | |
| } | |
| } | |
| // Verify that declaring a storage texture format that is not supported in WebGPU causes validation | |
| // error. | |
| TEST_F(StorageTextureValidationTests, UnsupportedWGSLStorageTextureFormat) { | |
| constexpr std::array<wgpu::TextureFormat, 16> kUnsupportedTextureFormats = { | |
| wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::R8Snorm, | |
| wgpu::TextureFormat::R8Uint, wgpu::TextureFormat::R8Sint, | |
| wgpu::TextureFormat::R16Uint, wgpu::TextureFormat::R16Sint, | |
| wgpu::TextureFormat::R16Float, wgpu::TextureFormat::RG8Unorm, | |
| wgpu::TextureFormat::RG8Snorm, wgpu::TextureFormat::RG8Uint, | |
| wgpu::TextureFormat::RG8Sint, wgpu::TextureFormat::RG16Uint, | |
| wgpu::TextureFormat::RG16Sint, wgpu::TextureFormat::RG16Float, | |
| wgpu::TextureFormat::RGB10A2Unorm, wgpu::TextureFormat::RG11B10Ufloat, | |
| }; | |
| for (wgpu::StorageTextureAccess bindingType : kSupportedStorageTextureAccess) { | |
| for (wgpu::TextureFormat format : kUnsupportedTextureFormats) { | |
| std::string computeShader = CreateComputeShaderWithStorageTexture(bindingType, format); | |
| ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, computeShader.c_str())); | |
| } | |
| } | |
| } | |
| // Verify that declaring a storage texture dimension that isn't supported by | |
| // WebGPU causes a compile failure. WebGPU doesn't support using cube map | |
| // texture views and cube map array texture views as storage textures. | |
| TEST_F(StorageTextureValidationTests, UnsupportedTextureViewDimensionInShader) { | |
| constexpr std::array<wgpu::TextureViewDimension, 2> kUnsupportedTextureViewDimensions = { | |
| wgpu::TextureViewDimension::Cube, wgpu::TextureViewDimension::CubeArray}; | |
| constexpr wgpu::TextureFormat kFormat = wgpu::TextureFormat::R32Float; | |
| for (wgpu::StorageTextureAccess bindingType : kSupportedStorageTextureAccess) { | |
| for (wgpu::TextureViewDimension dimension : kUnsupportedTextureViewDimensions) { | |
| std::string computeShader = | |
| CreateComputeShaderWithStorageTexture(bindingType, kFormat, dimension); | |
| ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, computeShader.c_str())); | |
| } | |
| } | |
| } | |
| // Verify that declaring a texture view dimension that is not supported to be used as storage | |
| // textures in WebGPU in bind group layout causes validation error. WebGPU doesn't support using | |
| // cube map texture views and cube map array texture views as storage textures. | |
| TEST_F(StorageTextureValidationTests, UnsupportedTextureViewDimensionInBindGroupLayout) { | |
| constexpr std::array<wgpu::TextureViewDimension, 2> kUnsupportedTextureViewDimensions = { | |
| wgpu::TextureViewDimension::Cube, wgpu::TextureViewDimension::CubeArray}; | |
| constexpr wgpu::TextureFormat kFormat = wgpu::TextureFormat::R32Float; | |
| for (wgpu::StorageTextureAccess bindingType : kSupportedStorageTextureAccess) { | |
| for (wgpu::TextureViewDimension dimension : kUnsupportedTextureViewDimensions) { | |
| ASSERT_DEVICE_ERROR(utils::MakeBindGroupLayout( | |
| device, {{0, wgpu::ShaderStage::Compute, bindingType, kFormat, dimension}})); | |
| } | |
| } | |
| } | |
| // Verify when we create and use a bind group layout with storage textures in the creation of | |
| // render and compute pipeline, the binding type in the bind group layout must match the | |
| // declaration in the shader. | |
| TEST_F(StorageTextureValidationTests, BindGroupLayoutEntryTypeMatchesShaderDeclaration) { | |
| constexpr wgpu::TextureFormat kStorageTextureFormat = wgpu::TextureFormat::R32Float; | |
| std::initializer_list<utils::BindingLayoutEntryInitializationHelper> kSupportedBindingTypes = { | |
| {0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Uniform}, | |
| {0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage}, | |
| {0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::ReadOnlyStorage}, | |
| {0, wgpu::ShaderStage::Compute, wgpu::SamplerBindingType::Filtering}, | |
| {0, wgpu::ShaderStage::Compute, wgpu::TextureSampleType::Float}, | |
| {0, wgpu::ShaderStage::Compute, wgpu::StorageTextureAccess::WriteOnly, | |
| kStorageTextureFormat}}; | |
| for (wgpu::StorageTextureAccess bindingTypeInShader : kSupportedStorageTextureAccess) { | |
| // Create the compute shader with the given binding type. | |
| std::string computeShader = | |
| CreateComputeShaderWithStorageTexture(bindingTypeInShader, kStorageTextureFormat); | |
| wgpu::ShaderModule csModule = utils::CreateShaderModule(device, computeShader.c_str()); | |
| // Set common fields of compute pipeline descriptor. | |
| wgpu::ComputePipelineDescriptor defaultComputePipelineDescriptor; | |
| defaultComputePipelineDescriptor.compute.module = csModule; | |
| defaultComputePipelineDescriptor.compute.entryPoint = "main"; | |
| for (utils::BindingLayoutEntryInitializationHelper bindingLayoutEntry : | |
| kSupportedBindingTypes) { | |
| wgpu::ComputePipelineDescriptor computePipelineDescriptor = | |
| defaultComputePipelineDescriptor; | |
| // Create bind group layout with different binding types. | |
| wgpu::BindGroupLayout bindGroupLayout = | |
| utils::MakeBindGroupLayout(device, {bindingLayoutEntry}); | |
| computePipelineDescriptor.layout = | |
| utils::MakeBasicPipelineLayout(device, &bindGroupLayout); | |
| // The binding type in the bind group layout must the same as the related image object | |
| // declared in shader. | |
| if (bindingLayoutEntry.storageTexture.access == bindingTypeInShader) { | |
| device.CreateComputePipeline(&computePipelineDescriptor); | |
| } else { | |
| ASSERT_DEVICE_ERROR(device.CreateComputePipeline(&computePipelineDescriptor)); | |
| } | |
| } | |
| } | |
| } | |
| // Verify it is invalid not to set a valid texture format in a bind group layout when the binding | |
| // type is read-only or write-only storage texture. | |
| TEST_F(StorageTextureValidationTests, UndefinedStorageTextureFormatInBindGroupLayout) { | |
| wgpu::BindGroupLayoutEntry errorBindGroupLayoutEntry; | |
| errorBindGroupLayoutEntry.binding = 0; | |
| errorBindGroupLayoutEntry.visibility = wgpu::ShaderStage::Compute; | |
| errorBindGroupLayoutEntry.storageTexture.format = wgpu::TextureFormat::Undefined; | |
| for (wgpu::StorageTextureAccess bindingType : kSupportedStorageTextureAccess) { | |
| errorBindGroupLayoutEntry.storageTexture.access = bindingType; | |
| ASSERT_DEVICE_ERROR(utils::MakeBindGroupLayout(device, {errorBindGroupLayoutEntry})); | |
| } | |
| } | |
| // Verify it is invalid to create a bind group layout with storage textures and an unsupported | |
| // storage texture format. | |
| TEST_F(StorageTextureValidationTests, StorageTextureFormatInBindGroupLayout) { | |
| wgpu::BindGroupLayoutEntry defaultBindGroupLayoutEntry; | |
| defaultBindGroupLayoutEntry.binding = 0; | |
| defaultBindGroupLayoutEntry.visibility = wgpu::ShaderStage::Compute; | |
| for (wgpu::StorageTextureAccess bindingType : kSupportedStorageTextureAccess) { | |
| for (wgpu::TextureFormat textureFormat : utils::kAllTextureFormats) { | |
| wgpu::BindGroupLayoutEntry bindGroupLayoutBinding = defaultBindGroupLayoutEntry; | |
| bindGroupLayoutBinding.storageTexture.access = bindingType; | |
| bindGroupLayoutBinding.storageTexture.format = textureFormat; | |
| if (utils::TextureFormatSupportsStorageTexture(textureFormat)) { | |
| utils::MakeBindGroupLayout(device, {bindGroupLayoutBinding}); | |
| } else { | |
| ASSERT_DEVICE_ERROR(utils::MakeBindGroupLayout(device, {bindGroupLayoutBinding})); | |
| } | |
| } | |
| } | |
| } | |
| // Verify the storage texture format in the bind group layout must match the declaration in shader. | |
| TEST_F(StorageTextureValidationTests, BindGroupLayoutStorageTextureFormatMatchesShaderDeclaration) { | |
| for (wgpu::StorageTextureAccess bindingType : kSupportedStorageTextureAccess) { | |
| for (wgpu::TextureFormat storageTextureFormatInShader : utils::kAllTextureFormats) { | |
| if (!utils::TextureFormatSupportsStorageTexture(storageTextureFormatInShader)) { | |
| continue; | |
| } | |
| // Create the compute shader module with the given binding type and storage texture | |
| // format. | |
| std::string computeShader = | |
| CreateComputeShaderWithStorageTexture(bindingType, storageTextureFormatInShader); | |
| wgpu::ShaderModule csModule = utils::CreateShaderModule(device, computeShader.c_str()); | |
| // Set common fields of compute pipeline descriptor. | |
| wgpu::ComputePipelineDescriptor defaultComputePipelineDescriptor; | |
| defaultComputePipelineDescriptor.compute.module = csModule; | |
| defaultComputePipelineDescriptor.compute.entryPoint = "main"; | |
| // Set common fileds of bind group layout binding. | |
| utils::BindingLayoutEntryInitializationHelper defaultBindGroupLayoutEntry = { | |
| 0, wgpu::ShaderStage::Compute, bindingType, utils::kAllTextureFormats[0]}; | |
| for (wgpu::TextureFormat storageTextureFormatInBindGroupLayout : | |
| utils::kAllTextureFormats) { | |
| if (!utils::TextureFormatSupportsStorageTexture( | |
| storageTextureFormatInBindGroupLayout)) { | |
| continue; | |
| } | |
| // Create the bind group layout with the given storage texture format. | |
| wgpu::BindGroupLayoutEntry bindGroupLayoutBinding = defaultBindGroupLayoutEntry; | |
| bindGroupLayoutBinding.storageTexture.format = | |
| storageTextureFormatInBindGroupLayout; | |
| wgpu::BindGroupLayout bindGroupLayout = | |
| utils::MakeBindGroupLayout(device, {bindGroupLayoutBinding}); | |
| // Create the compute pipeline with the bind group layout. | |
| wgpu::ComputePipelineDescriptor computePipelineDescriptor = | |
| defaultComputePipelineDescriptor; | |
| computePipelineDescriptor.layout = | |
| utils::MakeBasicPipelineLayout(device, &bindGroupLayout); | |
| // The storage texture format in the bind group layout must be the same as the one | |
| // declared in the shader. | |
| if (storageTextureFormatInShader == storageTextureFormatInBindGroupLayout) { | |
| device.CreateComputePipeline(&computePipelineDescriptor); | |
| } else { | |
| ASSERT_DEVICE_ERROR(device.CreateComputePipeline(&computePipelineDescriptor)); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // Verify the dimension of the bind group layout with storage textures must match the one declared | |
| // in shader. | |
| TEST_F(StorageTextureValidationTests, BindGroupLayoutViewDimensionMatchesShaderDeclaration) { | |
| constexpr std::array<wgpu::TextureViewDimension, 4> kSupportedDimensions = { | |
| wgpu::TextureViewDimension::e1D, wgpu::TextureViewDimension::e2D, | |
| wgpu::TextureViewDimension::e2DArray, wgpu::TextureViewDimension::e3D}; | |
| constexpr wgpu::TextureFormat kStorageTextureFormat = wgpu::TextureFormat::R32Float; | |
| for (wgpu::StorageTextureAccess bindingType : kSupportedStorageTextureAccess) { | |
| for (wgpu::TextureViewDimension dimensionInShader : kSupportedDimensions) { | |
| // Create the compute shader with the given texture view dimension. | |
| std::string computeShader = CreateComputeShaderWithStorageTexture( | |
| bindingType, kStorageTextureFormat, dimensionInShader); | |
| wgpu::ShaderModule csModule = utils::CreateShaderModule(device, computeShader.c_str()); | |
| // Set common fields of compute pipeline descriptor. | |
| wgpu::ComputePipelineDescriptor defaultComputePipelineDescriptor; | |
| defaultComputePipelineDescriptor.compute.module = csModule; | |
| defaultComputePipelineDescriptor.compute.entryPoint = "main"; | |
| // Set common fields of bind group layout binding. | |
| utils::BindingLayoutEntryInitializationHelper defaultBindGroupLayoutEntry = { | |
| 0, wgpu::ShaderStage::Compute, bindingType, kStorageTextureFormat}; | |
| for (wgpu::TextureViewDimension dimensionInBindGroupLayout : kSupportedDimensions) { | |
| // Create the bind group layout with the given texture view dimension. | |
| wgpu::BindGroupLayoutEntry bindGroupLayoutBinding = defaultBindGroupLayoutEntry; | |
| bindGroupLayoutBinding.storageTexture.viewDimension = dimensionInBindGroupLayout; | |
| wgpu::BindGroupLayout bindGroupLayout = | |
| utils::MakeBindGroupLayout(device, {bindGroupLayoutBinding}); | |
| // Create the compute pipeline with the bind group layout. | |
| wgpu::ComputePipelineDescriptor computePipelineDescriptor = | |
| defaultComputePipelineDescriptor; | |
| computePipelineDescriptor.layout = | |
| utils::MakeBasicPipelineLayout(device, &bindGroupLayout); | |
| // The texture dimension in the bind group layout must be the same as the one | |
| // declared in the shader. | |
| if (dimensionInShader == dimensionInBindGroupLayout) { | |
| device.CreateComputePipeline(&computePipelineDescriptor); | |
| } else { | |
| ASSERT_DEVICE_ERROR(device.CreateComputePipeline(&computePipelineDescriptor)); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // Verify that only a texture view can be used as a read-only or write-only storage texture in a | |
| // bind group. | |
| TEST_F(StorageTextureValidationTests, StorageTextureBindingTypeInBindGroup) { | |
| constexpr wgpu::TextureFormat kStorageTextureFormat = wgpu::TextureFormat::R32Float; | |
| for (wgpu::StorageTextureAccess storageBindingType : kSupportedStorageTextureAccess) { | |
| // Create a bind group layout. | |
| wgpu::BindGroupLayoutEntry bindGroupLayoutBinding; | |
| bindGroupLayoutBinding.binding = 0; | |
| bindGroupLayoutBinding.visibility = wgpu::ShaderStage::Compute; | |
| bindGroupLayoutBinding.storageTexture.access = storageBindingType; | |
| bindGroupLayoutBinding.storageTexture.format = kStorageTextureFormat; | |
| wgpu::BindGroupLayout bindGroupLayout = | |
| utils::MakeBindGroupLayout(device, {bindGroupLayoutBinding}); | |
| // Buffers are not allowed to be used as storage textures in a bind group. | |
| { | |
| wgpu::BufferDescriptor descriptor; | |
| descriptor.size = 1024; | |
| descriptor.usage = wgpu::BufferUsage::Uniform; | |
| wgpu::Buffer buffer = device.CreateBuffer(&descriptor); | |
| ASSERT_DEVICE_ERROR(utils::MakeBindGroup(device, bindGroupLayout, {{0, buffer}})); | |
| } | |
| // Samplers are not allowed to be used as storage textures in a bind group. | |
| { | |
| wgpu::Sampler sampler = device.CreateSampler(); | |
| ASSERT_DEVICE_ERROR(utils::MakeBindGroup(device, bindGroupLayout, {{0, sampler}})); | |
| } | |
| // Texture views are allowed to be used as storage textures in a bind group. | |
| { | |
| wgpu::TextureView textureView = | |
| CreateTexture(wgpu::TextureUsage::StorageBinding, kStorageTextureFormat) | |
| .CreateView(); | |
| utils::MakeBindGroup(device, bindGroupLayout, {{0, textureView}}); | |
| } | |
| } | |
| } | |
| // Verify that a texture used as read-only or write-only storage texture in a bind group must be | |
| // created with the texture usage wgpu::TextureUsage::StorageBinding. | |
| TEST_F(StorageTextureValidationTests, StorageTextureUsageInBindGroup) { | |
| constexpr wgpu::TextureFormat kStorageTextureFormat = wgpu::TextureFormat::R32Float; | |
| constexpr std::array<wgpu::TextureUsage, 6> kTextureUsages = { | |
| wgpu::TextureUsage::CopySrc, wgpu::TextureUsage::CopyDst, | |
| wgpu::TextureUsage::TextureBinding, wgpu::TextureUsage::StorageBinding, | |
| wgpu::TextureUsage::RenderAttachment, wgpu::TextureUsage::Present}; | |
| for (wgpu::StorageTextureAccess storageBindingType : kSupportedStorageTextureAccess) { | |
| // Create a bind group layout. | |
| wgpu::BindGroupLayoutEntry bindGroupLayoutBinding; | |
| bindGroupLayoutBinding.binding = 0; | |
| bindGroupLayoutBinding.visibility = wgpu::ShaderStage::Compute; | |
| bindGroupLayoutBinding.storageTexture.access = storageBindingType; | |
| bindGroupLayoutBinding.storageTexture.format = wgpu::TextureFormat::R32Float; | |
| wgpu::BindGroupLayout bindGroupLayout = | |
| utils::MakeBindGroupLayout(device, {bindGroupLayoutBinding}); | |
| for (wgpu::TextureUsage usage : kTextureUsages) { | |
| // Create texture views with different texture usages | |
| wgpu::TextureView textureView = | |
| CreateTexture(usage, kStorageTextureFormat).CreateView(); | |
| // Verify that the texture used as storage texture must be created with the texture | |
| // usage wgpu::TextureUsage::StorageBinding. | |
| if (usage & wgpu::TextureUsage::StorageBinding) { | |
| utils::MakeBindGroup(device, bindGroupLayout, {{0, textureView}}); | |
| } else { | |
| ASSERT_DEVICE_ERROR( | |
| utils::MakeBindGroup(device, bindGroupLayout, {{0, textureView}})); | |
| } | |
| } | |
| } | |
| } | |
| // Verify that the format of a texture used as read-only or write-only storage texture in a bind | |
| // group must match the corresponding bind group binding. | |
| TEST_F(StorageTextureValidationTests, StorageTextureFormatInBindGroup) { | |
| for (wgpu::StorageTextureAccess storageBindingType : kSupportedStorageTextureAccess) { | |
| wgpu::BindGroupLayoutEntry defaultBindGroupLayoutEntry; | |
| defaultBindGroupLayoutEntry.binding = 0; | |
| defaultBindGroupLayoutEntry.visibility = wgpu::ShaderStage::Compute; | |
| defaultBindGroupLayoutEntry.storageTexture.access = storageBindingType; | |
| for (wgpu::TextureFormat formatInBindGroupLayout : utils::kAllTextureFormats) { | |
| if (!utils::TextureFormatSupportsStorageTexture(formatInBindGroupLayout)) { | |
| continue; | |
| } | |
| // Create a bind group layout with given storage texture format. | |
| wgpu::BindGroupLayoutEntry bindGroupLayoutBinding = defaultBindGroupLayoutEntry; | |
| bindGroupLayoutBinding.storageTexture.format = formatInBindGroupLayout; | |
| wgpu::BindGroupLayout bindGroupLayout = | |
| utils::MakeBindGroupLayout(device, {bindGroupLayoutBinding}); | |
| for (wgpu::TextureFormat textureViewFormat : utils::kAllTextureFormats) { | |
| if (!utils::TextureFormatSupportsStorageTexture(textureViewFormat)) { | |
| continue; | |
| } | |
| // Create texture views with different texture formats. | |
| wgpu::TextureView storageTextureView = | |
| CreateTexture(wgpu::TextureUsage::StorageBinding, textureViewFormat) | |
| .CreateView(); | |
| // Verify that the format of the texture view used as storage texture in a bind | |
| // group must match the storage texture format declaration in the bind group layout. | |
| if (textureViewFormat == formatInBindGroupLayout) { | |
| utils::MakeBindGroup(device, bindGroupLayout, {{0, storageTextureView}}); | |
| } else { | |
| ASSERT_DEVICE_ERROR( | |
| utils::MakeBindGroup(device, bindGroupLayout, {{0, storageTextureView}})); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // Verify that the dimension of a texture view used as read-only or write-only storage texture in a | |
| // bind group must match the corresponding bind group binding. | |
| TEST_F(StorageTextureValidationTests, StorageTextureViewDimensionInBindGroup) { | |
| constexpr wgpu::TextureFormat kStorageTextureFormat = wgpu::TextureFormat::R32Float; | |
| constexpr uint32_t kDepthOrArrayLayers = 6u; | |
| // TODO(crbug.com/dawn/814): test the use of 1D texture view dimensions when they are | |
| // supported in Dawn. | |
| constexpr std::array<wgpu::TextureViewDimension, 3> kSupportedDimensions = { | |
| wgpu::TextureViewDimension::e2D, wgpu::TextureViewDimension::e2DArray, | |
| wgpu::TextureViewDimension::e3D}; | |
| wgpu::TextureViewDescriptor kDefaultTextureViewDescriptor; | |
| kDefaultTextureViewDescriptor.format = kStorageTextureFormat; | |
| kDefaultTextureViewDescriptor.baseMipLevel = 0; | |
| kDefaultTextureViewDescriptor.mipLevelCount = 1; | |
| kDefaultTextureViewDescriptor.baseArrayLayer = 0; | |
| kDefaultTextureViewDescriptor.arrayLayerCount = 1u; | |
| for (wgpu::StorageTextureAccess storageBindingType : kSupportedStorageTextureAccess) { | |
| wgpu::BindGroupLayoutEntry defaultBindGroupLayoutEntry; | |
| defaultBindGroupLayoutEntry.binding = 0; | |
| defaultBindGroupLayoutEntry.visibility = wgpu::ShaderStage::Compute; | |
| defaultBindGroupLayoutEntry.storageTexture.access = storageBindingType; | |
| defaultBindGroupLayoutEntry.storageTexture.format = kStorageTextureFormat; | |
| for (wgpu::TextureViewDimension dimensionInBindGroupLayout : kSupportedDimensions) { | |
| // Create a bind group layout with given texture view dimension. | |
| wgpu::BindGroupLayoutEntry bindGroupLayoutBinding = defaultBindGroupLayoutEntry; | |
| bindGroupLayoutBinding.storageTexture.viewDimension = dimensionInBindGroupLayout; | |
| wgpu::BindGroupLayout bindGroupLayout = | |
| utils::MakeBindGroupLayout(device, {bindGroupLayoutBinding}); | |
| for (wgpu::TextureViewDimension dimensionOfTextureView : kSupportedDimensions) { | |
| // Create a texture view with given texture view dimension. | |
| wgpu::Texture texture = | |
| CreateTexture(wgpu::TextureUsage::StorageBinding, kStorageTextureFormat, 1, | |
| kDepthOrArrayLayers, | |
| utils::ViewDimensionToTextureDimension(dimensionOfTextureView)); | |
| wgpu::TextureViewDescriptor textureViewDescriptor = kDefaultTextureViewDescriptor; | |
| textureViewDescriptor.dimension = dimensionOfTextureView; | |
| wgpu::TextureView storageTextureView = texture.CreateView(&textureViewDescriptor); | |
| // Verify that the dimension of the texture view used as storage texture in a bind | |
| // group must match the texture view dimension declaration in the bind group layout. | |
| if (dimensionInBindGroupLayout == dimensionOfTextureView) { | |
| utils::MakeBindGroup(device, bindGroupLayout, {{0, storageTextureView}}); | |
| } else { | |
| ASSERT_DEVICE_ERROR( | |
| utils::MakeBindGroup(device, bindGroupLayout, {{0, storageTextureView}})); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // Verify multisampled storage textures cannot be supported now. | |
| TEST_F(StorageTextureValidationTests, MultisampledStorageTexture) { | |
| for (wgpu::StorageTextureAccess bindingType : kSupportedStorageTextureAccess) { | |
| std::string computeShader = | |
| CreateComputeShaderWithStorageTexture(bindingType, "", "image2DMS"); | |
| ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, computeShader.c_str())); | |
| } | |
| } | |
| // Verify it is valid to use a texture as either read-only storage texture or write-only storage | |
| // texture in a render pass. | |
| TEST_F(StorageTextureValidationTests, StorageTextureInRenderPass) { | |
| constexpr wgpu::TextureFormat kFormat = wgpu::TextureFormat::RGBA8Unorm; | |
| wgpu::Texture storageTexture = CreateTexture(wgpu::TextureUsage::StorageBinding, kFormat); | |
| wgpu::Texture outputAttachment = CreateTexture(wgpu::TextureUsage::RenderAttachment, kFormat); | |
| utils::ComboRenderPassDescriptor renderPassDescriptor({outputAttachment.CreateView()}); | |
| for (wgpu::StorageTextureAccess storageTextureType : kSupportedStorageTextureAccess) { | |
| // Create a bind group that contains a storage texture. | |
| wgpu::BindGroupLayout bindGroupLayout = utils::MakeBindGroupLayout( | |
| device, {{0, wgpu::ShaderStage::Fragment, storageTextureType, kFormat}}); | |
| wgpu::BindGroup bindGroupWithStorageTexture = | |
| utils::MakeBindGroup(device, bindGroupLayout, {{0, storageTexture.CreateView()}}); | |
| // It is valid to use a texture as read-only or write-only storage texture in the render | |
| // pass. | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder renderPassEncoder = encoder.BeginRenderPass(&renderPassDescriptor); | |
| renderPassEncoder.SetBindGroup(0, bindGroupWithStorageTexture); | |
| renderPassEncoder.EndPass(); | |
| encoder.Finish(); | |
| } | |
| } | |
| // Verify it is valid to use a a texture as both read-only storage texture and sampled texture in | |
| // one render pass, while it is invalid to use a texture as both write-only storage texture and | |
| // sampled texture in one render pass. | |
| TEST_F(StorageTextureValidationTests, StorageTextureAndSampledTextureInOneRenderPass) { | |
| constexpr wgpu::TextureFormat kFormat = wgpu::TextureFormat::RGBA8Unorm; | |
| wgpu::Texture storageTexture = CreateTexture( | |
| wgpu::TextureUsage::StorageBinding | wgpu::TextureUsage::TextureBinding, kFormat); | |
| wgpu::Texture outputAttachment = CreateTexture(wgpu::TextureUsage::RenderAttachment, kFormat); | |
| utils::ComboRenderPassDescriptor renderPassDescriptor({outputAttachment.CreateView()}); | |
| // Create a bind group that contains a storage texture and a sampled texture. | |
| for (wgpu::StorageTextureAccess storageTextureType : kSupportedStorageTextureAccess) { | |
| // Create a bind group that binds the same texture as both storage texture and sampled | |
| // texture. | |
| wgpu::BindGroupLayout bindGroupLayout = utils::MakeBindGroupLayout( | |
| device, {{0, wgpu::ShaderStage::Fragment, storageTextureType, kFormat}, | |
| {1, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float}}); | |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup( | |
| device, bindGroupLayout, | |
| {{0, storageTexture.CreateView()}, {1, storageTexture.CreateView()}}); | |
| // It is valid to use a a texture as both read-only storage texture and sampled texture in | |
| // one render pass, while it is invalid to use a texture as both write-only storage | |
| // texture an sampled texture in one render pass. | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder renderPassEncoder = encoder.BeginRenderPass(&renderPassDescriptor); | |
| renderPassEncoder.SetBindGroup(0, bindGroup); | |
| renderPassEncoder.EndPass(); | |
| switch (storageTextureType) { | |
| case wgpu::StorageTextureAccess::WriteOnly: | |
| ASSERT_DEVICE_ERROR(encoder.Finish()); | |
| break; | |
| default: | |
| UNREACHABLE(); | |
| break; | |
| } | |
| } | |
| } | |
| // Verify it is invalid to use a a texture as both storage texture (either read-only or write-only) | |
| // and render attachment in one render pass. | |
| TEST_F(StorageTextureValidationTests, StorageTextureAndRenderAttachmentInOneRenderPass) { | |
| constexpr wgpu::TextureFormat kFormat = wgpu::TextureFormat::RGBA8Unorm; | |
| wgpu::Texture storageTexture = CreateTexture( | |
| wgpu::TextureUsage::StorageBinding | wgpu::TextureUsage::RenderAttachment, kFormat); | |
| utils::ComboRenderPassDescriptor renderPassDescriptor({storageTexture.CreateView()}); | |
| for (wgpu::StorageTextureAccess storageTextureType : kSupportedStorageTextureAccess) { | |
| // Create a bind group that contains a storage texture. | |
| wgpu::BindGroupLayout bindGroupLayout = utils::MakeBindGroupLayout( | |
| device, {{0, wgpu::ShaderStage::Fragment, storageTextureType, kFormat}}); | |
| wgpu::BindGroup bindGroupWithStorageTexture = | |
| utils::MakeBindGroup(device, bindGroupLayout, {{0, storageTexture.CreateView()}}); | |
| // It is invalid to use a texture as both storage texture and render attachment in one | |
| // render pass. | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder renderPassEncoder = encoder.BeginRenderPass(&renderPassDescriptor); | |
| renderPassEncoder.SetBindGroup(0, bindGroupWithStorageTexture); | |
| renderPassEncoder.EndPass(); | |
| ASSERT_DEVICE_ERROR(encoder.Finish()); | |
| } | |
| } | |
| // Verify it is valid to use a texture as both storage texture (read-only or write-only) and | |
| // sampled texture in one compute pass. | |
| TEST_F(StorageTextureValidationTests, StorageTextureAndSampledTextureInOneComputePass) { | |
| constexpr wgpu::TextureFormat kFormat = wgpu::TextureFormat::RGBA8Unorm; | |
| wgpu::Texture storageTexture = CreateTexture( | |
| wgpu::TextureUsage::StorageBinding | wgpu::TextureUsage::TextureBinding, kFormat); | |
| for (wgpu::StorageTextureAccess storageTextureType : kSupportedStorageTextureAccess) { | |
| // Create a bind group that binds the same texture as both storage texture and sampled | |
| // texture. | |
| wgpu::BindGroupLayout bindGroupLayout = utils::MakeBindGroupLayout( | |
| device, {{0, wgpu::ShaderStage::Compute, storageTextureType, kFormat}, | |
| {1, wgpu::ShaderStage::Compute, wgpu::TextureSampleType::Float}}); | |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup( | |
| device, bindGroupLayout, | |
| {{0, storageTexture.CreateView()}, {1, storageTexture.CreateView()}}); | |
| // It is valid to use a a texture as both storage texture (read-only or write-only) and | |
| // sampled texture in one compute pass. | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder computePassEncoder = encoder.BeginComputePass(); | |
| computePassEncoder.SetBindGroup(0, bindGroup); | |
| computePassEncoder.EndPass(); | |
| encoder.Finish(); | |
| } | |
| } |