| // Copyright 2020 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 <string> |
| #include <vector> |
| |
| #include "dawn/common/Assert.h" |
| #include "dawn/tests/unittests/validation/ValidationTest.h" |
| #include "dawn/utils/ComboRenderPipelineDescriptor.h" |
| #include "dawn/utils/TextureUtils.h" |
| #include "dawn/utils/WGPUHelpers.h" |
| |
| namespace dawn { |
| namespace { |
| |
| class StorageTextureValidationTests : public ValidationTest { |
| protected: |
| void SetUp() override { |
| ValidationTest::SetUp(); |
| |
| mDefaultVSModule = utils::CreateShaderModule(device, R"( |
| @vertex fn main() -> @builtin(position) vec4f { |
| return vec4f(0.0, 0.0, 0.0, 1.0); |
| })"); |
| mDefaultFSModule = utils::CreateShaderModule(device, R"( |
| @fragment fn main() -> @location(0) vec4f { |
| return vec4f(1.0, 0.0, 0.0, 1.0); |
| })"); |
| } |
| |
| static const char* GetFloatImageTypeDeclaration(wgpu::TextureViewDimension dimension) { |
| 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: |
| DAWN_UNREACHABLE(); |
| return ""; |
| } |
| } |
| |
| static std::string CreateComputeShaderWithStorageTexture( |
| wgpu::StorageTextureAccess storageTextureBindingType, |
| wgpu::TextureFormat textureFormat, |
| wgpu::TextureViewDimension textureViewDimension = wgpu::TextureViewDimension::e2D) { |
| return CreateShaderWithStorageTexture(storageTextureBindingType, textureFormat, |
| GetFloatImageTypeDeclaration(textureViewDimension)); |
| } |
| |
| static std::string CreateShaderWithStorageTexture( |
| wgpu::StorageTextureAccess storageTextureBindingType, |
| wgpu::TextureFormat textureFormat, |
| const char* imageTypeDeclaration = "texture_storage_2d", |
| wgpu::ShaderStage shaderStage = wgpu::ShaderStage::Compute) { |
| const char* access = ""; |
| switch (storageTextureBindingType) { |
| case wgpu::StorageTextureAccess::WriteOnly: |
| access = "write"; |
| break; |
| case wgpu::StorageTextureAccess::ReadOnly: |
| access = "read"; |
| break; |
| case wgpu::StorageTextureAccess::ReadWrite: |
| access = "read_write"; |
| break; |
| default: |
| DAWN_UNREACHABLE(); |
| } |
| const char* shaderStageDeclaration = ""; |
| switch (shaderStage) { |
| case wgpu::ShaderStage::Vertex: |
| shaderStageDeclaration = "@vertex"; |
| break; |
| case wgpu::ShaderStage::Fragment: |
| shaderStageDeclaration = "@fragment"; |
| break; |
| case wgpu::ShaderStage::Compute: |
| shaderStageDeclaration = "@compute @workgroup_size(1)"; |
| break; |
| default: |
| DAWN_UNREACHABLE(); |
| } |
| |
| std::ostringstream ostream; |
| ostream << "@group(0) @binding(0) var image0 : " << imageTypeDeclaration << "<" |
| << utils::GetWGSLImageFormatQualifier(textureFormat) << ", " << access << ">;\n" |
| << shaderStageDeclaration << " fn main()"; |
| if (shaderStage == wgpu::ShaderStage::Vertex) { |
| ostream << " -> @builtin(position) vec4f "; |
| } |
| |
| ostream << "{\n" |
| " _ = textureDimensions(image0);\n"; |
| if (shaderStage == wgpu::ShaderStage::Vertex) { |
| ostream << " return vec4f(0.0);"; |
| } |
| ostream << "}\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) { |
| constexpr uint32_t kWidth = 16u; |
| uint32_t height = dimension == wgpu::TextureDimension::e1D ? 1 : kWidth; |
| wgpu::TextureDescriptor descriptor; |
| descriptor.dimension = dimension; |
| descriptor.size = {kWidth, height, arrayLayerCount}; |
| descriptor.sampleCount = sampleCount; |
| descriptor.format = format; |
| descriptor.mipLevelCount = 1; |
| descriptor.usage = usage; |
| return device.CreateTexture(&descriptor); |
| } |
| |
| struct BindGroupLayoutTestSpec { |
| wgpu::ShaderStage stage = wgpu::ShaderStage::Compute; |
| wgpu::StorageTextureAccess type; |
| bool valid; |
| wgpu::TextureFormat storageTextureFormat = wgpu::TextureFormat::R32Uint; |
| }; |
| |
| void DoBindGroupLayoutTest(const std::vector<BindGroupLayoutTestSpec>& testSpecs) { |
| for (const auto& testSpec : testSpecs) { |
| wgpu::BindGroupLayoutEntry entry = utils::BindingLayoutEntryInitializationHelper( |
| 0, testSpec.stage, testSpec.type, testSpec.storageTextureFormat); |
| |
| wgpu::BindGroupLayoutDescriptor descriptor; |
| descriptor.entryCount = 1; |
| descriptor.entries = &entry; |
| |
| if (testSpec.valid) { |
| device.CreateBindGroupLayout(&descriptor); |
| } else { |
| ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&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. |
| { |
| ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, R"( |
| @group(0) @binding(0) var image0 : texture_storage_2d<rgba8unorm, write>; |
| @vertex |
| fn main(@builtin(vertex_index) vertex_index : u32) -> @builtin(position) vec4f { |
| textureStore(image0, vec2i(i32(vertex_index), 0), vec4f(1.0, 0.0, 0.0, 1.0)); |
| return vec4f(0.0); |
| })")); |
| } |
| |
| // 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>; |
| @fragment fn main(@builtin(position) position : vec4f) { |
| textureStore(image0, vec2i(position.xy), vec4f(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>; |
| |
| @compute @workgroup_size(1) |
| fn main(@builtin(local_invocation_id) LocalInvocationID : vec3u) { |
| textureStore(image0, vec2i(LocalInvocationID.xy), vec4f(0.0, 0.0, 0.0, 0.0)); |
| })"); |
| |
| wgpu::ComputePipelineDescriptor descriptor; |
| descriptor.layout = nullptr; |
| descriptor.compute.module = csModule; |
| |
| device.CreateComputePipeline(&descriptor); |
| } |
| } |
| |
| // Validate read-only, write-only and read-write storage textures are supported in shader modules, |
| // excepting that only read-only storage textures are supported in vertex shaders. |
| TEST_F(StorageTextureValidationTests, ReadWriteStorageTexture) { |
| constexpr std::array<wgpu::StorageTextureAccess, 3> kStorageTextureAccesses = { |
| {wgpu::StorageTextureAccess::ReadOnly, wgpu::StorageTextureAccess::WriteOnly, |
| wgpu::StorageTextureAccess::ReadWrite}}; |
| constexpr std::array<wgpu::ShaderStage, 3> kShaderStages = { |
| {wgpu::ShaderStage::Vertex, wgpu::ShaderStage::Fragment, wgpu::ShaderStage::Compute}}; |
| |
| for (wgpu::StorageTextureAccess access : kStorageTextureAccesses) { |
| for (wgpu::ShaderStage shaderStage : kShaderStages) { |
| if (shaderStage == wgpu::ShaderStage::Vertex && |
| access != wgpu::StorageTextureAccess::ReadOnly) { |
| continue; |
| } |
| std::string shader = CreateShaderWithStorageTexture( |
| access, wgpu::TextureFormat::R32Float, "texture_storage_2d", shaderStage); |
| utils::CreateShaderModule(device, shader.c_str()); |
| } |
| } |
| } |
| |
| // 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) { |
| // Only include valid WGSL texel format tokens. |
| constexpr std::array<wgpu::TextureFormat, 17> 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, |
| // Although BGRA8Unorm stoarge capability depends on Feature::BGRA8UnormStorage, |
| // It is always a valid storage format token in WGSL. |
| wgpu::TextureFormat::BGRA8Unorm}; |
| |
| for (wgpu::StorageTextureAccess storageTextureBindingType : kSupportedStorageTextureAccess) { |
| for (wgpu::TextureFormat format : kWGPUTextureFormatSupportedAsSPIRVImageFormats) { |
| std::string computeShader = |
| CreateComputeShaderWithStorageTexture(storageTextureBindingType, format); |
| utils::CreateShaderModule(device, computeShader.c_str()); |
| } |
| } |
| } |
| |
| class BGRA8UnormStorageTextureValidationTests |
| : public StorageTextureValidationTests, |
| // Bool param indicates whether requires the BGRA8UnormStorage feature or not. |
| public ::testing::WithParamInterface<bool> { |
| protected: |
| std::vector<wgpu::FeatureName> GetRequiredFeatures() override { |
| if (GetParam()) { |
| return {wgpu::FeatureName::BGRA8UnormStorage}; |
| } |
| return {}; |
| } |
| }; |
| |
| // Test that bgra8unorm storage texture at create bind group layout. |
| TEST_P(BGRA8UnormStorageTextureValidationTests, CreateBindGroupLayout) { |
| for (wgpu::StorageTextureAccess storageTextureBindingType : kSupportedStorageTextureAccess) { |
| wgpu::BindGroupLayoutEntry entry = utils::BindingLayoutEntryInitializationHelper( |
| 0, wgpu::ShaderStage::Compute, storageTextureBindingType, |
| wgpu::TextureFormat::BGRA8Unorm); |
| wgpu::BindGroupLayoutDescriptor descriptor; |
| descriptor.entryCount = 1; |
| descriptor.entries = &entry; |
| |
| if (GetParam()) { |
| device.CreateBindGroupLayout(&descriptor); |
| } else { |
| ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&descriptor)); |
| } |
| } |
| } |
| |
| // Test that bgra8unorm storage texture at create pipeline with implicit pipeline layout. |
| TEST_P(BGRA8UnormStorageTextureValidationTests, CreatePipelineWithImplicitLayout) { |
| for (wgpu::StorageTextureAccess storageTextureBindingType : kSupportedStorageTextureAccess) { |
| std::string computeShader = CreateComputeShaderWithStorageTexture( |
| storageTextureBindingType, wgpu::TextureFormat::BGRA8Unorm); |
| utils::CreateShaderModule(device, computeShader.c_str()); |
| |
| wgpu::ComputePipelineDescriptor descriptor; |
| descriptor.layout = nullptr; |
| descriptor.compute.module = utils::CreateShaderModule(device, computeShader.c_str()); |
| |
| if (GetParam()) { |
| wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&descriptor); |
| pipeline.GetBindGroupLayout(0); |
| } else { |
| ASSERT_DEVICE_ERROR(device.CreateComputePipeline(&descriptor)); |
| } |
| } |
| } |
| |
| // Test that bgra8unorm storage texture at create pipeline with explicit pipeline layout. |
| TEST_P(BGRA8UnormStorageTextureValidationTests, CreatePipelineWithExplicitLayout) { |
| // Skip if wgpu::FeatureName::BGRA8UnormStorage is not required as it will fail at create bind |
| // group layout. |
| DAWN_SKIP_TEST_IF(!GetParam()); |
| for (wgpu::StorageTextureAccess storageTextureBindingType : kSupportedStorageTextureAccess) { |
| wgpu::BindGroupLayout bindGroupLayout; |
| { |
| wgpu::BindGroupLayoutEntry entry = utils::BindingLayoutEntryInitializationHelper( |
| 0, wgpu::ShaderStage::Compute, storageTextureBindingType, |
| wgpu::TextureFormat::BGRA8Unorm); |
| wgpu::BindGroupLayoutDescriptor descriptor; |
| descriptor.entryCount = 1; |
| descriptor.entries = &entry; |
| bindGroupLayout = device.CreateBindGroupLayout(&descriptor); |
| } |
| |
| wgpu::PipelineLayout pipelineLayout; |
| { |
| wgpu::PipelineLayoutDescriptor descriptor; |
| descriptor.bindGroupLayoutCount = 1; |
| descriptor.bindGroupLayouts = &bindGroupLayout; |
| pipelineLayout = device.CreatePipelineLayout(&descriptor); |
| } |
| |
| std::string computeShader = CreateComputeShaderWithStorageTexture( |
| storageTextureBindingType, wgpu::TextureFormat::BGRA8Unorm); |
| wgpu::ComputePipelineDescriptor descriptor; |
| descriptor.layout = pipelineLayout; |
| descriptor.compute.module = utils::CreateShaderModule(device, computeShader.c_str()); |
| |
| wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&descriptor); |
| pipeline.GetBindGroupLayout(0); |
| } |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| , |
| BGRA8UnormStorageTextureValidationTests, |
| // Bool param indicates whether requires the BGRA8UnormStorage feature or not. |
| ::testing::ValuesIn({false, true})); |
| |
| // 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; |
| |
| 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, device, |
| UseCompatibilityMode())) { |
| utils::MakeBindGroupLayout(device, {bindGroupLayoutBinding}); |
| } else { |
| ASSERT_DEVICE_ERROR(utils::MakeBindGroupLayout(device, {bindGroupLayoutBinding})); |
| } |
| } |
| } |
| } |
| |
| class BGRA8UnormStorageBindGroupLayoutTest : public StorageTextureValidationTests { |
| protected: |
| std::vector<wgpu::FeatureName> GetRequiredFeatures() override { |
| return {wgpu::FeatureName::BGRA8UnormStorage}; |
| } |
| }; |
| |
| // Test that creating a bind group layout with BGRA8Unorm as storage texture format is valid when |
| // The optional feature bgra8unorm-storage is supported. |
| TEST_F(BGRA8UnormStorageBindGroupLayoutTest, BGRA8UnormAsStorage) { |
| wgpu::BindGroupLayoutEntry bindGroupLayoutBinding; |
| bindGroupLayoutBinding.binding = 0; |
| bindGroupLayoutBinding.visibility = wgpu::ShaderStage::Compute; |
| bindGroupLayoutBinding.storageTexture.format = wgpu::TextureFormat::BGRA8Unorm; |
| bindGroupLayoutBinding.storageTexture.viewDimension = wgpu::TextureViewDimension::e2D; |
| |
| for (wgpu::StorageTextureAccess bindingType : kSupportedStorageTextureAccess) { |
| bindGroupLayoutBinding.storageTexture.access = bindingType; |
| 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, device, |
| UseCompatibilityMode())) { |
| 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; |
| |
| // 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, device, UseCompatibilityMode())) { |
| 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; |
| |
| // 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, 5> kTextureUsages = { |
| wgpu::TextureUsage::CopySrc, wgpu::TextureUsage::CopyDst, |
| wgpu::TextureUsage::TextureBinding, wgpu::TextureUsage::StorageBinding, |
| wgpu::TextureUsage::RenderAttachment}; |
| |
| 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, device, |
| UseCompatibilityMode())) { |
| 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, device, |
| UseCompatibilityMode())) { |
| 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; |
| |
| constexpr std::array kSupportedDimensions = { |
| wgpu::TextureViewDimension::e1D, 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. |
| uint32_t depthOrArrayLayers = |
| dimensionOfTextureView == wgpu::TextureViewDimension::e1D ? 1u |
| : kDepthOrArrayLayers; |
| wgpu::Texture texture = |
| CreateTexture(wgpu::TextureUsage::StorageBinding, kStorageTextureFormat, 1, |
| depthOrArrayLayers, |
| 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 = CreateShaderWithStorageTexture( |
| bindingType, wgpu::TextureFormat::RGBA8Unorm, "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.End(); |
| 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.End(); |
| switch (storageTextureType) { |
| case wgpu::StorageTextureAccess::WriteOnly: |
| ASSERT_DEVICE_ERROR(encoder.Finish()); |
| break; |
| default: |
| DAWN_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.End(); |
| 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.End(); |
| encoder.Finish(); |
| } |
| } |
| |
| class ReadWriteStorageTextureValidationTests : public StorageTextureValidationTests {}; |
| |
| // Test that using read-only storage texture is valid for all shader stages in BindGroupLayout, |
| // while write-only and read-write storage textures are only valid for fragment and compute shader |
| // stages. |
| TEST_F(StorageTextureValidationTests, BindGroupLayoutWithStorageTextureBindingType) { |
| const std::vector<BindGroupLayoutTestSpec> kTestSpecs = { |
| {{wgpu::ShaderStage::Vertex, wgpu::StorageTextureAccess::WriteOnly, false}, |
| {wgpu::ShaderStage::Vertex, wgpu::StorageTextureAccess::ReadOnly, true}, |
| {wgpu::ShaderStage::Vertex, wgpu::StorageTextureAccess::ReadWrite, false}, |
| {wgpu::ShaderStage::Fragment, wgpu::StorageTextureAccess::WriteOnly, true}, |
| {wgpu::ShaderStage::Fragment, wgpu::StorageTextureAccess::ReadOnly, true}, |
| {wgpu::ShaderStage::Fragment, wgpu::StorageTextureAccess::ReadWrite, true}, |
| {wgpu::ShaderStage::Compute, wgpu::StorageTextureAccess::WriteOnly, true}, |
| {wgpu::ShaderStage::Compute, wgpu::StorageTextureAccess::ReadOnly, true}, |
| {wgpu::ShaderStage::Compute, wgpu::StorageTextureAccess::ReadWrite, true}}}; |
| DoBindGroupLayoutTest(kTestSpecs); |
| } |
| |
| // Test that using read-only storage texture in BindGroupLayout is valid with all formats that |
| // can be used as storage texture, while read-write storage texture access is only available on the |
| // formats that support read-write storage texture access. |
| TEST_F(ReadWriteStorageTextureValidationTests, ReadWriteStorageTextureFormatInBindGroupLayout) { |
| for (wgpu::TextureFormat format : utils::kAllTextureFormats) { |
| if (!utils::TextureFormatSupportsStorageTexture(format, device, UseCompatibilityMode())) { |
| continue; |
| } |
| |
| bool supportsReadWriteStorageTexture = |
| utils::TextureFormatSupportsReadWriteStorageTexture(format); |
| const std::vector<BindGroupLayoutTestSpec> kTestSpecs = { |
| {{wgpu::ShaderStage::Compute, wgpu::StorageTextureAccess::ReadOnly, true, format}, |
| {wgpu::ShaderStage::Compute, wgpu::StorageTextureAccess::ReadWrite, |
| supportsReadWriteStorageTexture, format}}}; |
| DoBindGroupLayoutTest(kTestSpecs); |
| } |
| } |
| |
| // Test that read-write storage texture access is only available on the storage texture formats that |
| // support read-write storage texture access in shader, while read-only storage texture access is |
| // available for all storage texture formats. |
| TEST_F(ReadWriteStorageTextureValidationTests, ReadWriteStorageTextureFormatInShader) { |
| constexpr std::array<wgpu::StorageTextureAccess, 2> kStorageTextureAccesses = { |
| {wgpu::StorageTextureAccess::ReadOnly, wgpu::StorageTextureAccess::ReadWrite}}; |
| for (wgpu::TextureFormat format : utils::kAllTextureFormats) { |
| if (!utils::TextureFormatSupportsStorageTexture(format, device, UseCompatibilityMode())) { |
| continue; |
| } |
| |
| for (wgpu::StorageTextureAccess access : kStorageTextureAccesses) { |
| std::string computeShader = CreateComputeShaderWithStorageTexture(access, format); |
| wgpu::ComputePipelineDescriptor computeDesc; |
| computeDesc.compute.module = utils::CreateShaderModule(device, computeShader.c_str()); |
| |
| switch (access) { |
| case wgpu::StorageTextureAccess::ReadOnly: |
| device.CreateComputePipeline(&computeDesc); |
| break; |
| case wgpu::StorageTextureAccess::ReadWrite: |
| if (utils::TextureFormatSupportsReadWriteStorageTexture(format)) { |
| device.CreateComputePipeline(&computeDesc); |
| } else { |
| ASSERT_DEVICE_ERROR(device.CreateComputePipeline(&computeDesc)); |
| } |
| break; |
| default: |
| DAWN_UNREACHABLE(); |
| } |
| } |
| } |
| } |
| |
| // Test that storage texture access in shader must be compatible with the one in pipeline layout |
| // when we create a pipeline with storage texture. Note that read-write storage texture access in |
| // pipeline layout is compatible with write-only storage texture access in shader. |
| TEST_F(ReadWriteStorageTextureValidationTests, StorageTextureAccessInPipeline) { |
| constexpr std::array<wgpu::StorageTextureAccess, 3> kStorageTextureAccesses = { |
| {wgpu::StorageTextureAccess::ReadOnly, wgpu::StorageTextureAccess::WriteOnly, |
| wgpu::StorageTextureAccess::ReadWrite}}; |
| constexpr wgpu::TextureFormat kFormat = wgpu::TextureFormat::R32Uint; |
| for (wgpu::StorageTextureAccess accessInShader : kStorageTextureAccesses) { |
| for (wgpu::StorageTextureAccess accessInBindGroupLayout : kStorageTextureAccesses) { |
| std::string computeShader = |
| CreateComputeShaderWithStorageTexture(accessInShader, kFormat); |
| wgpu::BindGroupLayout bindGroupLayout = utils::MakeBindGroupLayout( |
| device, {{0, wgpu::ShaderStage::Compute, accessInBindGroupLayout, kFormat, |
| wgpu::TextureViewDimension::e2D}}); |
| |
| wgpu::ComputePipelineDescriptor computePipelineDescriptor; |
| computePipelineDescriptor.compute.module = |
| utils::CreateShaderModule(device, computeShader.c_str()); |
| computePipelineDescriptor.layout = |
| utils::MakePipelineLayout(device, {{bindGroupLayout}}); |
| if (accessInShader == accessInBindGroupLayout || |
| (accessInShader == wgpu::StorageTextureAccess::WriteOnly && |
| accessInBindGroupLayout == wgpu::StorageTextureAccess::ReadWrite)) { |
| device.CreateComputePipeline(&computePipelineDescriptor); |
| } else { |
| ASSERT_DEVICE_ERROR(device.CreateComputePipeline(&computePipelineDescriptor)); |
| } |
| } |
| } |
| } |
| |
| class ReadWriteStorageTextureResourceUsageTrackingTests |
| : public ReadWriteStorageTextureValidationTests { |
| protected: |
| enum class BindingType : uint8_t { |
| ReadOnlyStorage = 0, |
| WriteOnlyStorage, |
| ReadWriteStorage, |
| TextureBinding, |
| BindingTypeCount, |
| }; |
| |
| void SetUp() override { |
| ReadWriteStorageTextureValidationTests::SetUp(); |
| |
| wgpu::Texture storageTexture = |
| CreateTexture(wgpu::TextureUsage::StorageBinding | wgpu::TextureUsage::TextureBinding | |
| wgpu::TextureUsage::RenderAttachment, |
| kFormat); |
| storageTextureView = storageTexture.CreateView(); |
| |
| bindGroupLayouts[static_cast<size_t>(BindingType::ReadOnlyStorage)] = |
| utils::MakeBindGroupLayout( |
| device, {{0, wgpu::ShaderStage::Fragment | wgpu::ShaderStage::Compute, |
| wgpu::StorageTextureAccess::ReadOnly, kFormat}}); |
| bindGroupLayouts[static_cast<size_t>(BindingType::WriteOnlyStorage)] = |
| utils::MakeBindGroupLayout( |
| device, {{0, wgpu::ShaderStage::Fragment | wgpu::ShaderStage::Compute, |
| wgpu::StorageTextureAccess::WriteOnly, kFormat}}); |
| bindGroupLayouts[static_cast<size_t>(BindingType::ReadWriteStorage)] = |
| utils::MakeBindGroupLayout( |
| device, {{0, wgpu::ShaderStage::Fragment | wgpu::ShaderStage::Compute, |
| wgpu::StorageTextureAccess::ReadWrite, kFormat}}); |
| bindGroupLayouts[static_cast<size_t>(BindingType::TextureBinding)] = |
| utils::MakeBindGroupLayout( |
| device, {{0, wgpu::ShaderStage::Fragment | wgpu::ShaderStage::Compute, |
| wgpu::TextureSampleType::Uint}}); |
| |
| for (size_t bindingType = 0; bindingType < bindGroups.size(); ++bindingType) { |
| bindGroups[bindingType] = utils::MakeBindGroup(device, bindGroupLayouts[bindingType], |
| {{0, storageTextureView}}); |
| } |
| } |
| |
| bool IsReadOnlyBindingType(BindingType bindingType) { |
| constexpr std::array<BindingType, 2> readonlyCompatibleUsageSet = { |
| BindingType::ReadOnlyStorage, BindingType::TextureBinding}; |
| return std::find(readonlyCompatibleUsageSet.begin(), readonlyCompatibleUsageSet.end(), |
| bindingType) != readonlyCompatibleUsageSet.end(); |
| } |
| |
| static const wgpu::TextureFormat kFormat = wgpu::TextureFormat::R32Uint; |
| |
| wgpu::TextureView storageTextureView; |
| std::array<wgpu::BindGroupLayout, static_cast<size_t>(BindingType::BindingTypeCount)> |
| bindGroupLayouts; |
| std::array<wgpu::BindGroup, static_cast<size_t>(BindingType::BindingTypeCount)> bindGroups; |
| }; |
| |
| // Test the usage scope rule and writable storage texture aliasing rule of read-only and read-write |
| // storage textures in a render pass. |
| TEST_F(ReadWriteStorageTextureResourceUsageTrackingTests, StorageTextureInRenderPass) { |
| wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"( |
| @vertex fn main() -> @builtin(position) vec4f { |
| return vec4f(); |
| })"); |
| |
| wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( |
| @fragment fn main() { |
| })"); |
| utils::ComboRenderPipelineDescriptor pipelineDescriptor; |
| pipelineDescriptor.vertex.module = vsModule; |
| pipelineDescriptor.cFragment.module = fsModule; |
| pipelineDescriptor.cTargets[0].writeMask = wgpu::ColorWriteMask::None; |
| |
| // Test usage scope rules in bind groups |
| { |
| PlaceholderRenderPass placeholderRenderPass(device); |
| |
| for (size_t bindingType1 = 0; |
| bindingType1 < static_cast<size_t>(BindingType::BindingTypeCount); ++bindingType1) { |
| for (size_t bindingType2 = 0; |
| bindingType2 < static_cast<size_t>(BindingType::BindingTypeCount); |
| ++bindingType2) { |
| pipelineDescriptor.layout = utils::MakePipelineLayout( |
| device, {{bindGroupLayouts[bindingType1], bindGroupLayouts[bindingType2]}}); |
| wgpu::RenderPipeline renderPipeline = |
| device.CreateRenderPipeline(&pipelineDescriptor); |
| |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::RenderPassEncoder renderPassEncoder = |
| encoder.BeginRenderPass(&placeholderRenderPass); |
| renderPassEncoder.SetBindGroup(0, bindGroups[bindingType1]); |
| renderPassEncoder.SetBindGroup(1, bindGroups[bindingType2]); |
| renderPassEncoder.SetPipeline(renderPipeline); |
| renderPassEncoder.Draw(1); |
| renderPassEncoder.End(); |
| |
| // It is valid if two usages are both read-only ones. |
| if (IsReadOnlyBindingType(static_cast<BindingType>(bindingType1)) && |
| IsReadOnlyBindingType(static_cast<BindingType>(bindingType2))) { |
| encoder.Finish(); |
| } else { |
| ASSERT_DEVICE_ERROR(encoder.Finish()); |
| } |
| } |
| } |
| } |
| |
| // Test usage scope rules on using the same texture as both read-only or read-write storage |
| // texture access and render attachment |
| { |
| utils::ComboRenderPassDescriptor renderPass({storageTextureView}); |
| std::array<size_t, 2> ReadWriteStorageTextureBindings = { |
| static_cast<size_t>(BindingType::ReadOnlyStorage), |
| static_cast<size_t>(BindingType::ReadWriteStorage)}; |
| for (size_t bindingType1 : ReadWriteStorageTextureBindings) { |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::RenderPassEncoder renderPassEncoder = encoder.BeginRenderPass(&renderPass); |
| renderPassEncoder.SetBindGroup(0, bindGroups[bindingType1]); |
| renderPassEncoder.End(); |
| ASSERT_DEVICE_ERROR(encoder.Finish()); |
| } |
| } |
| } |
| |
| // Test the usage scope rule and writable storage texture aliasing rule of read-only and read-write |
| // storage textures in a compute pass. |
| TEST_F(ReadWriteStorageTextureResourceUsageTrackingTests, StorageTextureInComputePass) { |
| wgpu::ShaderModule csModule = utils::CreateShaderModule(device, R"( |
| @compute @workgroup_size(1) fn main() { |
| })"); |
| |
| for (size_t bindingType1 = 0; bindingType1 < static_cast<size_t>(BindingType::BindingTypeCount); |
| ++bindingType1) { |
| for (size_t bindingType2 = 0; |
| bindingType2 < static_cast<size_t>(BindingType::BindingTypeCount); ++bindingType2) { |
| wgpu::ComputePipelineDescriptor pipelineDescriptor; |
| pipelineDescriptor.layout = utils::MakePipelineLayout( |
| device, {{bindGroupLayouts[bindingType1], bindGroupLayouts[bindingType2]}}); |
| pipelineDescriptor.compute.module = csModule; |
| wgpu::ComputePipeline computePipeline = |
| device.CreateComputePipeline(&pipelineDescriptor); |
| |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::ComputePassEncoder computePassEncoder = encoder.BeginComputePass(); |
| computePassEncoder.SetBindGroup(0, bindGroups[bindingType1]); |
| computePassEncoder.SetBindGroup(1, bindGroups[bindingType2]); |
| computePassEncoder.SetPipeline(computePipeline); |
| computePassEncoder.DispatchWorkgroups(1); |
| computePassEncoder.End(); |
| if ((IsReadOnlyBindingType(static_cast<BindingType>(bindingType1)) && |
| IsReadOnlyBindingType(static_cast<BindingType>(bindingType2)))) { |
| encoder.Finish(); |
| } else { |
| ASSERT_DEVICE_ERROR(encoder.Finish()); |
| } |
| } |
| } |
| } |
| |
| class R8UnormStorageValidationTests : public StorageTextureValidationTests { |
| std::vector<wgpu::FeatureName> GetRequiredFeatures() override { |
| return {wgpu::FeatureName::R8UnormStorage}; |
| } |
| }; |
| |
| // Check that it is allowed to create an R8Unorm texture with the storage usage. |
| TEST_F(R8UnormStorageValidationTests, TextureCreation) { |
| wgpu::TextureDescriptor desc; |
| desc.format = wgpu::TextureFormat::R8Unorm; |
| desc.usage = wgpu::TextureUsage::StorageBinding; |
| desc.size = {1, 1}; |
| device.CreateTexture(&desc); |
| } |
| |
| // Check that it is allowed to create a BGL with a read-only or write-only R8unorm storage texture |
| // entry. |
| TEST_F(R8UnormStorageValidationTests, BGLEntry) { |
| // Control case: read-only or write-only are allowed. |
| utils::MakeBindGroupLayout( |
| device, {{0, wgpu::ShaderStage::Fragment, wgpu::StorageTextureAccess::ReadOnly, |
| wgpu::TextureFormat::R8Unorm}}); |
| utils::MakeBindGroupLayout( |
| device, {{0, wgpu::ShaderStage::Fragment, wgpu::StorageTextureAccess::WriteOnly, |
| wgpu::TextureFormat::R8Unorm}}); |
| |
| // Error cases: read-write is disallowed. |
| ASSERT_DEVICE_ERROR(utils::MakeBindGroupLayout( |
| device, {{0, wgpu::ShaderStage::Fragment, wgpu::StorageTextureAccess::ReadWrite, |
| wgpu::TextureFormat::R8Unorm}})); |
| } |
| |
| // Check that using the `r8unorm` to create a WGSL compute shader is allowed. |
| TEST_F(R8UnormStorageValidationTests, ShaderModule) { |
| utils::CreateShaderModule(device, R"( |
| enable chromium_internal_graphite; |
| @group(0) @binding(0) var t : texture_storage_2d<r8unorm, write>; |
| )"); |
| } |
| |
| // Check that using an r8unorm storage texture read-only or write-only with implicit layout is |
| // valid. |
| TEST_F(R8UnormStorageValidationTests, End2endUsage) { |
| wgpu::ComputePipelineDescriptor cDesc; |
| cDesc.compute.module = utils::CreateShaderModule(device, R"( |
| enable chromium_internal_graphite; |
| @group(0) @binding(0) var input : texture_storage_2d<r8unorm, read>; |
| @group(0) @binding(1) var output : texture_storage_2d<r8unorm, write>; |
| |
| @workgroup_size(4, 4) @compute fn main(@builtin(local_invocation_id) id : vec3<u32>) { |
| textureStore(output, id.xy, 2 * textureLoad(input, id.xy)); |
| } |
| )"); |
| wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&cDesc); |
| |
| wgpu::TextureDescriptor tDesc; |
| tDesc.format = wgpu::TextureFormat::R8Unorm; |
| tDesc.usage = wgpu::TextureUsage::StorageBinding | wgpu::TextureUsage::CopySrc | |
| wgpu::TextureUsage::CopyDst; |
| tDesc.size = {4, 4}; |
| wgpu::Texture input = device.CreateTexture(&tDesc); |
| wgpu::Texture output = device.CreateTexture(&tDesc); |
| |
| wgpu::BindGroup bg = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), |
| { |
| {0, input.CreateView()}, |
| {1, output.CreateView()}, |
| }); |
| |
| 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(); |
| device.GetQueue().Submit(1, &commands); |
| } |
| |
| } // anonymous namespace |
| } // namespace dawn |