| // Copyright 2023 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 <algorithm> | |
| #include <limits> | |
| #include <sstream> | |
| #include <string> | |
| #include <vector> | |
| #include "absl/strings/str_format.h" | |
| #include "absl/strings/str_join.h" | |
| #include "dawn/tests/unittests/validation/ValidationTest.h" | |
| #include "dawn/utils/ComboRenderPipelineDescriptor.h" | |
| #include "dawn/utils/WGPUHelpers.h" | |
| namespace dawn { | |
| namespace { | |
| class CompatValidationTest : public ValidationTest { | |
| protected: | |
| bool UseCompatibilityMode() const override { return true; } | |
| }; | |
| TEST_F(CompatValidationTest, CanNotCreateCubeArrayTextureView) { | |
| wgpu::TextureDescriptor descriptor; | |
| descriptor.size = {1, 1, 6}; | |
| descriptor.dimension = wgpu::TextureDimension::e2D; | |
| descriptor.format = wgpu::TextureFormat::RGBA8Unorm; | |
| descriptor.usage = wgpu::TextureUsage::TextureBinding; | |
| wgpu::Texture cubeTexture = device.CreateTexture(&descriptor); | |
| { | |
| wgpu::TextureViewDescriptor cubeViewDescriptor; | |
| cubeViewDescriptor.dimension = wgpu::TextureViewDimension::Cube; | |
| cubeViewDescriptor.format = wgpu::TextureFormat::RGBA8Unorm; | |
| cubeTexture.CreateView(&cubeViewDescriptor); | |
| } | |
| { | |
| wgpu::TextureViewDescriptor cubeArrayViewDescriptor; | |
| cubeArrayViewDescriptor.dimension = wgpu::TextureViewDimension::CubeArray; | |
| cubeArrayViewDescriptor.format = wgpu::TextureFormat::RGBA8Unorm; | |
| ASSERT_DEVICE_ERROR(cubeTexture.CreateView(&cubeArrayViewDescriptor)); | |
| } | |
| cubeTexture.Destroy(); | |
| } | |
| TEST_F(CompatValidationTest, CanNotSpecifyAlternateCompatibleViewFormatRGBA8Unorm) { | |
| constexpr wgpu::TextureFormat viewFormat = wgpu::TextureFormat::RGBA8UnormSrgb; | |
| wgpu::TextureDescriptor descriptor; | |
| descriptor.size = {1, 1, 1}; | |
| descriptor.dimension = wgpu::TextureDimension::e2D; | |
| descriptor.format = wgpu::TextureFormat::RGBA8Unorm; | |
| descriptor.usage = wgpu::TextureUsage::TextureBinding; | |
| descriptor.viewFormatCount = 1; | |
| descriptor.viewFormats = &viewFormat; | |
| wgpu::Texture texture; | |
| ASSERT_DEVICE_ERROR(texture = device.CreateTexture(&descriptor), | |
| testing::HasSubstr("must match format")); | |
| texture.Destroy(); | |
| } | |
| TEST_F(CompatValidationTest, CanNotSpecifyAlternateCompatibleViewFormatRGBA8UnormSrgb) { | |
| constexpr wgpu::TextureFormat viewFormat = wgpu::TextureFormat::RGBA8Unorm; | |
| wgpu::TextureDescriptor descriptor; | |
| descriptor.size = {1, 1, 1}; | |
| descriptor.dimension = wgpu::TextureDimension::e2D; | |
| descriptor.format = wgpu::TextureFormat::RGBA8UnormSrgb; | |
| descriptor.usage = wgpu::TextureUsage::TextureBinding; | |
| descriptor.viewFormatCount = 1; | |
| descriptor.viewFormats = &viewFormat; | |
| wgpu::Texture texture; | |
| ASSERT_DEVICE_ERROR(texture = device.CreateTexture(&descriptor), | |
| testing::HasSubstr("must match format")); | |
| texture.Destroy(); | |
| } | |
| TEST_F(CompatValidationTest, CanNotCreatePipelineWithDifferentPerTargetBlendStateOrWriteMask) { | |
| wgpu::ShaderModule module = utils::CreateShaderModule(device, R"( | |
| @vertex fn vs() -> @builtin(position) vec4f { | |
| return vec4f(0); | |
| } | |
| struct FragmentOut { | |
| @location(0) fragColor0 : vec4f, | |
| @location(1) fragColor1 : vec4f, | |
| @location(2) fragColor2 : vec4f, | |
| } | |
| @fragment fn fs() -> FragmentOut { | |
| var output : FragmentOut; | |
| output.fragColor0 = vec4f(0); | |
| output.fragColor1 = vec4f(0); | |
| output.fragColor2 = vec4f(0); | |
| return output; | |
| } | |
| )"); | |
| utils::ComboRenderPipelineDescriptor testDescriptor; | |
| testDescriptor.layout = {}; | |
| testDescriptor.vertex.module = module; | |
| testDescriptor.cFragment.module = module; | |
| testDescriptor.cFragment.targetCount = 3; | |
| testDescriptor.cTargets[1].format = wgpu::TextureFormat::Undefined; | |
| for (int i = 0; i < 10; ++i) { | |
| wgpu::BlendState blend0; | |
| wgpu::BlendState blend2; | |
| // Blend state intentionally omitted for target 1 | |
| testDescriptor.cTargets[0].blend = &blend0; | |
| testDescriptor.cTargets[2].blend = &blend2; | |
| bool expectError = true; | |
| switch (i) { | |
| case 0: // default | |
| expectError = false; | |
| break; | |
| case 1: // no blend | |
| testDescriptor.cTargets[0].blend = nullptr; | |
| break; | |
| case 2: // no blend second target | |
| testDescriptor.cTargets[2].blend = nullptr; | |
| break; | |
| case 3: // color.operation | |
| blend2.color.operation = wgpu::BlendOperation::Subtract; | |
| break; | |
| case 4: // color.srcFactor | |
| blend2.color.srcFactor = wgpu::BlendFactor::SrcAlpha; | |
| break; | |
| case 5: // color.dstFactor | |
| blend2.color.dstFactor = wgpu::BlendFactor::DstAlpha; | |
| break; | |
| case 6: // alpha.operation | |
| blend2.alpha.operation = wgpu::BlendOperation::Subtract; | |
| break; | |
| case 7: // alpha.srcFactor | |
| blend2.alpha.srcFactor = wgpu::BlendFactor::SrcAlpha; | |
| break; | |
| case 8: // alpha.dstFactor | |
| blend2.alpha.dstFactor = wgpu::BlendFactor::DstAlpha; | |
| break; | |
| case 9: // writeMask | |
| testDescriptor.cTargets[2].writeMask = wgpu::ColorWriteMask::Green; | |
| break; | |
| default: | |
| DAWN_UNREACHABLE(); | |
| } | |
| if (expectError) { | |
| ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&testDescriptor)); | |
| } else { | |
| device.CreateRenderPipeline(&testDescriptor); | |
| } | |
| } | |
| } | |
| TEST_F(CompatValidationTest, CanNotCreatePipelineWithNonZeroDepthBiasClamp) { | |
| wgpu::ShaderModule module = utils::CreateShaderModule(device, R"( | |
| @vertex fn vs() -> @builtin(position) vec4f { | |
| return vec4f(0); | |
| } | |
| @fragment fn fs() -> @location(0) vec4f { | |
| return vec4f(0); | |
| } | |
| )"); | |
| utils::ComboRenderPipelineDescriptor testDescriptor; | |
| testDescriptor.layout = {}; | |
| testDescriptor.vertex.module = module; | |
| testDescriptor.cFragment.module = module; | |
| testDescriptor.cFragment.targetCount = 1; | |
| testDescriptor.cTargets[1].format = wgpu::TextureFormat::RGBA8Unorm; | |
| wgpu::DepthStencilState* depthStencil = | |
| testDescriptor.EnableDepthStencil(wgpu::TextureFormat::Depth24Plus); | |
| depthStencil->depthWriteEnabled = wgpu::OptionalBool::True; | |
| depthStencil->depthBias = 0; | |
| depthStencil->depthBiasSlopeScale = 0; | |
| depthStencil->depthBiasClamp = 0; | |
| device.CreateRenderPipeline(&testDescriptor); | |
| depthStencil->depthBiasClamp = 1; | |
| ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&testDescriptor)); | |
| } | |
| TEST_F(CompatValidationTest, CanNotCreatePipelineWithTextureLoadOfDepthTexture) { | |
| wgpu::ShaderModule module = utils::CreateShaderModule(device, R"( | |
| @group(0) @binding(0) var<storage, read_write> dstBuf : array<vec4f>; | |
| @group(0) @binding(1) var tex1 : texture_2d<f32>; | |
| @group(0) @binding(2) var tex2 : texture_depth_2d; | |
| @group(0) @binding(3) var tex3 : texture_depth_2d_array; | |
| @group(0) @binding(4) var tex4 : texture_depth_multisampled_2d; | |
| @compute @workgroup_size(1) fn main1() { | |
| dstBuf[0] = textureLoad(tex1, vec2(0), 0); | |
| } | |
| @compute @workgroup_size(1) fn main2() { | |
| dstBuf[0] = vec4f(textureLoad(tex2, vec2(0), 0)); | |
| } | |
| @compute @workgroup_size(1) fn main3() { | |
| dstBuf[0] = vec4f(textureLoad(tex3, vec2(0), 0, 0)); | |
| } | |
| @compute @workgroup_size(1) fn main4() { | |
| dstBuf[4] = vec4f(textureLoad(tex4, vec2(0), 0)); | |
| } | |
| )"); | |
| const char* entryPoints[] = {"main1", "main2", "main3", "main4"}; | |
| for (auto entryPoint : entryPoints) { | |
| wgpu::ComputePipelineDescriptor pDesc; | |
| pDesc.compute.module = module; | |
| pDesc.compute.entryPoint = entryPoint; | |
| if (entryPoint == entryPoints[0]) { | |
| device.CreateComputePipeline(&pDesc); | |
| } else { | |
| ASSERT_DEVICE_ERROR( | |
| device.CreateComputePipeline(&pDesc), | |
| testing::HasSubstr( | |
| "textureLoad can not be used with depth textures in compatibility mode")); | |
| } | |
| } | |
| } | |
| TEST_F(CompatValidationTest, CanNotCreatePipelineWithDepthTextureUsedWithNonComparisonSampler) { | |
| wgpu::ShaderModule module = utils::CreateShaderModule(device, R"( | |
| @group(1) @binding(0) var s: sampler; | |
| @group(1) @binding(1) var sc: sampler_comparison; | |
| @group(0) @binding(0) var tex2d : texture_depth_2d; | |
| @group(0) @binding(1) var tex2dArray: texture_depth_2d_array; | |
| @group(0) @binding(2) var texCube : texture_depth_cube; | |
| @group(0) @binding(3) var texCubeArray : texture_depth_cube_array; | |
| @vertex fn vs() -> @builtin(position) vec4f { | |
| return vec4f(0); | |
| } | |
| // valid | |
| @fragment fn main0() -> @location(0) vec4f { | |
| return textureGatherCompare(tex2d, sc, vec2(0), 0) + | |
| textureGatherCompare(tex2dArray, sc, vec2(0), 0, 0) + | |
| textureGatherCompare(texCube, sc, vec3(0), 0) + | |
| textureGatherCompare(texCubeArray, sc, vec3(0), 0, 0) + | |
| vec4f(textureSampleCompare(tex2d, sc, vec2(0), 0)) + | |
| vec4f(textureSampleCompare(tex2dArray, sc, vec2(0), 0, 0)) + | |
| vec4f(textureSampleCompare(texCube, sc, vec3(0), 0)) + | |
| vec4f(textureSampleCompare(texCubeArray, sc, vec3(0), 0, 0)) + | |
| vec4f(textureSampleCompareLevel(tex2d, sc, vec2(0), 0)) + | |
| vec4f(textureSampleCompareLevel(tex2dArray, sc, vec2(0), 0, 0)) + | |
| vec4f(textureSampleCompareLevel(texCube, sc, vec3(0), 0)) + | |
| vec4f(textureSampleCompareLevel(texCubeArray, sc, vec3(0), 0, 0)) ; | |
| } | |
| @fragment fn main1() -> @location(0) vec4f { | |
| return textureGather(tex2d, s, vec2(0)); | |
| } | |
| @fragment fn main2() -> @location(0) vec4f { | |
| return textureGather(tex2dArray, s, vec2(0), 0); | |
| } | |
| @fragment fn main3() -> @location(0) vec4f { | |
| return textureGather(texCube, s, vec3(0)); | |
| } | |
| @fragment fn main4() -> @location(0) vec4f { | |
| return textureGather(texCubeArray, s, vec3(0), 0); | |
| } | |
| @fragment fn main5() -> @location(0) vec4f { | |
| return vec4f(textureSample(tex2d, s, vec2(0))); | |
| } | |
| @fragment fn main6() -> @location(0) vec4f { | |
| return vec4f(textureSample(tex2dArray, s, vec2(0), 0)); | |
| } | |
| @fragment fn main7() -> @location(0) vec4f { | |
| return vec4f(textureSample(texCube, s, vec3(0))); | |
| } | |
| @fragment fn main8() -> @location(0) vec4f { | |
| return vec4f(textureSample(texCubeArray, s, vec3(0), 0)); | |
| } | |
| @fragment fn main9() -> @location(0) vec4f { | |
| return vec4f(textureSampleLevel(tex2d, s, vec2(0), 0)); | |
| } | |
| @fragment fn main10() -> @location(0) vec4f { | |
| return vec4f(textureSampleLevel(tex2dArray, s, vec2(0), 0, 0)); | |
| } | |
| @fragment fn main11() -> @location(0) vec4f { | |
| return vec4f(textureSampleLevel(texCube, s, vec3(0), 0)); | |
| } | |
| @fragment fn main12() -> @location(0) vec4f { | |
| return vec4f(textureSampleLevel(texCubeArray, s, vec3(0), 0, 0)); | |
| } | |
| )"); | |
| const char* entryPoints[] = {"main0", "main1", "main2", "main3", "main4", "main5", "main6", | |
| "main7", "main8", "main9", "main10", "main11", "main12"}; | |
| for (auto entryPoint : entryPoints) { | |
| utils::ComboRenderPipelineDescriptor pDesc; | |
| pDesc.vertex.module = module; | |
| pDesc.cFragment.module = module; | |
| pDesc.cFragment.entryPoint = entryPoint; | |
| pDesc.cFragment.targetCount = 1; | |
| pDesc.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm; | |
| if (entryPoint == entryPoints[0]) { | |
| device.CreateRenderPipeline(&pDesc); | |
| } else { | |
| ASSERT_DEVICE_ERROR( | |
| device.CreateRenderPipeline(&pDesc), | |
| testing::HasSubstr("texture_depth_xx can not be used with non-comparison samplers " | |
| "in compatibility mode")); | |
| } | |
| } | |
| } | |
| TEST_F(CompatValidationTest, CanNotUseTooManyTextureSamplerCombos) { | |
| wgpu::SupportedLimits limits; | |
| device.GetLimits(&limits); | |
| uint32_t maxCombos = std::min(limits.limits.maxSampledTexturesPerShaderStage, | |
| limits.limits.maxSamplersPerShaderStage); | |
| struct Test { | |
| bool expectSuccess; | |
| uint32_t numCombos; | |
| uint32_t numNonSamplerUsages; | |
| uint32_t numExternalTextures; | |
| bool useSameExternalTexture; | |
| wgpu::ShaderStage stages; | |
| }; | |
| // clang-format off | |
| Test comboTests[] = { | |
| // num use | |
| // non num same | |
| // sampler external external | |
| // pass numCombos uses textures tex stage | |
| {true , maxCombos , 0 , 0 , false, wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment}, | |
| {true , 1 , maxCombos, 0 , false, wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment}, | |
| {false, 2 , maxCombos, 0 , false, wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment}, | |
| {true , maxCombos - 4, 0 , 1 , false, wgpu::ShaderStage::Vertex}, | |
| {false, maxCombos - 3, 0 , 1 , false, wgpu::ShaderStage::Vertex}, | |
| {true , maxCombos - 8, 0 , 2 , false, wgpu::ShaderStage::Vertex}, | |
| {false, maxCombos - 7, 0 , 2 , false, wgpu::ShaderStage::Vertex}, | |
| {true , maxCombos - 7, 0 , 2 , true, wgpu::ShaderStage::Vertex}, | |
| {false, maxCombos - 6, 0 , 2 , true, wgpu::ShaderStage::Vertex}, | |
| {true , maxCombos - 4, 0 , 1 , false, wgpu::ShaderStage::Fragment}, | |
| {false, maxCombos - 3, 0 , 1 , false, wgpu::ShaderStage::Fragment}, | |
| {true , maxCombos - 8, 0 , 2 , false, wgpu::ShaderStage::Fragment}, | |
| {false, maxCombos - 7, 0 , 2 , false, wgpu::ShaderStage::Fragment}, | |
| {true , maxCombos - 7, 0 , 2 , true, wgpu::ShaderStage::Fragment}, | |
| {false, maxCombos - 6, 0 , 2 , true, wgpu::ShaderStage::Fragment}, | |
| {false, maxCombos + 1, 0 , 0 , false, wgpu::ShaderStage::Vertex}, | |
| {false, maxCombos + 1, 0 , 0 , false, wgpu::ShaderStage::Fragment}, | |
| }; | |
| // clang-format on | |
| for (const auto& test : comboTests) { | |
| uint32_t maxTexturesPerShaderStage = | |
| limits.limits.maxSampledTexturesPerShaderStage - (test.numExternalTextures * 3); | |
| auto numCombos = test.numCombos; | |
| std::vector<std::string> textureDeclarations[2]; | |
| std::vector<std::string> samplerDeclarations[2]; | |
| std::vector<std::string> usages[2]; | |
| for (uint32_t stage = 0; stage < 2; ++stage) { | |
| uint32_t count = 0; | |
| for (uint32_t t = 0; count < numCombos && t < maxTexturesPerShaderStage; ++t) { | |
| textureDeclarations[stage].push_back( | |
| absl::StrFormat("@group(%u) @binding(%u) var t%u_%u: texture_2d<f32>;", | |
| stage * 2, t, stage, t)); | |
| for (uint32_t s = 0; | |
| count < numCombos && t < limits.limits.maxSamplersPerShaderStage; ++s) { | |
| if (t == 0) { | |
| samplerDeclarations[stage].push_back( | |
| absl::StrFormat("@group(%u) @binding(%u) var s%u_%u: sampler;", | |
| (stage * 2) + 1, s, stage, s)); | |
| } | |
| usages[stage].push_back( | |
| absl::StrFormat("c += textureSampleLevel(t%u_%u, s%u_%u, vec2f(0), 0);", | |
| stage, t, stage, s)); | |
| ++count; | |
| } | |
| } | |
| for (uint32_t t = 0; t < test.numNonSamplerUsages; ++t) { | |
| if (t >= textureDeclarations[stage].size()) { | |
| textureDeclarations[stage].push_back( | |
| absl::StrFormat("@group(%u) @binding(%u) var t%u_%u: texture_2d<f32>;", | |
| stage * 2, t, stage, t)); | |
| } | |
| usages[stage].push_back( | |
| absl::StrFormat("c += textureLoad(t%u_%u, vec2u(0), 0);", stage, t)); | |
| } | |
| for (uint32_t t = 0; t < test.numExternalTextures; ++t) { | |
| if (t == 0 || !test.useSameExternalTexture) { | |
| auto et = textureDeclarations[stage].size() + t; | |
| textureDeclarations[stage].push_back( | |
| absl::StrFormat("@group(%u) @binding(%u) var e%u_%u: texture_external;", | |
| stage * 2, et, stage, t)); | |
| } | |
| usages[stage].push_back( | |
| absl::StrFormat("c += textureSampleBaseClampToEdge(e%u_%u, s%u_%u, vec2f(0));", | |
| stage, test.useSameExternalTexture ? 0 : t, stage, | |
| test.useSameExternalTexture ? t : 0)); | |
| } | |
| } | |
| auto wgsl = | |
| absl::StrFormat(R"( | |
| %s | |
| %s | |
| %s | |
| %s | |
| fn usage0() -> vec4f { | |
| var c: vec4f; | |
| %s | |
| return c; | |
| } | |
| fn usage1() -> vec4f { | |
| var c: vec4f; | |
| %s | |
| return c; | |
| } | |
| @vertex fn vs() -> @builtin(position) vec4f { | |
| _ = %s; | |
| return vec4f(0); | |
| } | |
| @group(2) @binding(0) var tt: texture_2d<f32>; | |
| @fragment fn fs() -> @location(0) vec4f { | |
| return %s; | |
| } | |
| )", | |
| absl::StrJoin(textureDeclarations[0], "\n"), | |
| absl::StrJoin(samplerDeclarations[0], "\n"), | |
| absl::StrJoin(textureDeclarations[1], "\n"), | |
| absl::StrJoin(samplerDeclarations[1], "\n"), | |
| absl::StrJoin(usages[0], "\n "), absl::StrJoin(usages[1], "\n "), | |
| test.stages & wgpu::ShaderStage::Vertex ? "usage0()" : "vec4f(0)", | |
| test.stages & wgpu::ShaderStage::Fragment ? "usage1()" : "vec4f(0)"); | |
| wgpu::ShaderModule module = utils::CreateShaderModule(device, wgsl.c_str()); | |
| utils::ComboRenderPipelineDescriptor descriptor; | |
| descriptor.vertex.module = module; | |
| descriptor.cFragment.module = module; | |
| descriptor.cFragment.targetCount = 1; | |
| descriptor.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm; | |
| if (test.expectSuccess) { | |
| device.CreateRenderPipeline(&descriptor); | |
| } else { | |
| ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor), | |
| testing::HasSubstr("compat")); | |
| } | |
| } | |
| } | |
| TEST_F(CompatValidationTest, CanNotUseSampleMask) { | |
| wgpu::ShaderModule moduleSampleMaskOutput = utils::CreateShaderModule(device, R"( | |
| @vertex fn vs() -> @builtin(position) vec4f { | |
| return vec4f(1); | |
| } | |
| struct Output { | |
| @builtin(sample_mask) mask_out: u32, | |
| @location(0) color : vec4f, | |
| } | |
| @fragment fn fsWithoutSampleMaskUsage() -> @location(0) vec4f { | |
| return vec4f(1.0, 1.0, 1.0, 1.0); | |
| } | |
| @fragment fn fsWithSampleMaskUsage() -> Output { | |
| var o: Output; | |
| // We need to make sure this sample_mask isn't optimized out even its value equals "no op". | |
| o.mask_out = 0xFFFFFFFFu; | |
| o.color = vec4f(1.0, 1.0, 1.0, 1.0); | |
| return o; | |
| } | |
| )"); | |
| // Check we can use a fragment shader that doesn't use sample_mask from | |
| // the same module as one that does. | |
| { | |
| utils::ComboRenderPipelineDescriptor descriptor; | |
| descriptor.vertex.module = moduleSampleMaskOutput; | |
| descriptor.cFragment.module = moduleSampleMaskOutput; | |
| descriptor.cFragment.entryPoint = "fsWithoutSampleMaskUsage"; | |
| descriptor.multisample.count = 4; | |
| descriptor.multisample.alphaToCoverageEnabled = false; | |
| device.CreateRenderPipeline(&descriptor); | |
| } | |
| // Check we can not use a fragment shader that uses sample_mask. | |
| { | |
| utils::ComboRenderPipelineDescriptor descriptor; | |
| descriptor.vertex.module = moduleSampleMaskOutput; | |
| descriptor.cFragment.module = moduleSampleMaskOutput; | |
| descriptor.cFragment.entryPoint = "fsWithSampleMaskUsage"; | |
| descriptor.multisample.count = 4; | |
| descriptor.multisample.alphaToCoverageEnabled = false; | |
| ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor), | |
| testing::HasSubstr("sample_mask")); | |
| } | |
| } | |
| TEST_F(CompatValidationTest, CanNotUseFragmentShaderWithSampleIndex) { | |
| wgpu::ShaderModule moduleSampleMaskOutput = utils::CreateShaderModule(device, R"( | |
| @vertex fn vs() -> @builtin(position) vec4f { | |
| return vec4f(1); | |
| } | |
| struct Output { | |
| @location(0) color : vec4f, | |
| } | |
| @fragment fn fsWithoutSampleIndexUsage() -> @location(0) vec4f { | |
| return vec4f(1.0, 1.0, 1.0, 1.0); | |
| } | |
| @fragment fn fsWithSampleIndexUsage(@builtin(sample_index) sNdx: u32) -> Output { | |
| var o: Output; | |
| _ = sNdx; | |
| o.color = vec4f(1.0, 1.0, 1.0, 1.0); | |
| return o; | |
| } | |
| )"); | |
| // Check we can use a fragment shader that doesn't use sample_index from | |
| // the same module as one that does. | |
| { | |
| utils::ComboRenderPipelineDescriptor descriptor; | |
| descriptor.vertex.module = moduleSampleMaskOutput; | |
| descriptor.vertex.entryPoint = "vs"; | |
| descriptor.cFragment.module = moduleSampleMaskOutput; | |
| descriptor.cFragment.entryPoint = "fsWithoutSampleIndexUsage"; | |
| descriptor.multisample.count = 4; | |
| descriptor.multisample.alphaToCoverageEnabled = false; | |
| device.CreateRenderPipeline(&descriptor); | |
| } | |
| // Check we can not use a fragment shader that uses sample_index. | |
| { | |
| utils::ComboRenderPipelineDescriptor descriptor; | |
| descriptor.vertex.module = moduleSampleMaskOutput; | |
| descriptor.vertex.entryPoint = "vs"; | |
| descriptor.cFragment.module = moduleSampleMaskOutput; | |
| descriptor.cFragment.entryPoint = "fsWithSampleIndexUsage"; | |
| descriptor.multisample.count = 4; | |
| descriptor.multisample.alphaToCoverageEnabled = false; | |
| ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor), | |
| testing::HasSubstr("sample_index")); | |
| } | |
| } | |
| TEST_F(CompatValidationTest, CanNotUseShaderWithUnsupportedInterpolateTypeOrSampling) { | |
| static const char* interpolateParams[] = { | |
| "perspective", // should pass | |
| "linear", "perspective, sample", "flat", "flat, first", | |
| }; | |
| for (auto interpolateParam : interpolateParams) { | |
| auto wgsl = absl::StrFormat(R"( | |
| struct Vertex { | |
| @builtin(position) pos: vec4f, | |
| @location(0) @interpolate(%s) color : vec4f, | |
| }; | |
| @vertex fn vs() -> Vertex { | |
| var v: Vertex; | |
| v.pos = vec4f(1); | |
| v.color = vec4f(1); | |
| return v; | |
| } | |
| @fragment fn fsWithoutBadInterpolationUsage() -> @location(0) vec4f { | |
| return vec4f(1); | |
| } | |
| @fragment fn fsWithBadInterpolationUsage1(v: Vertex) -> @location(0) vec4f { | |
| return vec4f(1); | |
| } | |
| @fragment fn fsWithBadInterpolationUsage2(v: Vertex) -> @location(0) vec4f { | |
| return v.pos; | |
| } | |
| @fragment fn fsWithBadInterpolationUsage3(v: Vertex) -> @location(0) vec4f { | |
| return v.color; | |
| } | |
| )", | |
| interpolateParam); | |
| wgpu::ShaderModule moduleInterpolationLinear = | |
| utils::CreateShaderModule(device, wgsl.c_str()); | |
| static const char* entryPoints[] = { | |
| "fsWithoutBadInterpolationUsage", | |
| "fsWithBadInterpolationUsage1", | |
| "fsWithBadInterpolationUsage2", | |
| "fsWithBadInterpolationUsage3", | |
| }; | |
| for (auto entryPoint : entryPoints) { | |
| utils::ComboRenderPipelineDescriptor descriptor; | |
| descriptor.vertex.module = moduleInterpolationLinear; | |
| descriptor.cFragment.module = moduleInterpolationLinear; | |
| descriptor.cFragment.entryPoint = entryPoint; | |
| bool shouldSucceed = | |
| entryPoint == entryPoints[0] || interpolateParam == interpolateParams[0]; | |
| if (shouldSucceed) { | |
| device.CreateRenderPipeline(&descriptor); | |
| } else { | |
| ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor), | |
| testing::HasSubstr("in compatibility mode")); | |
| } | |
| } | |
| } | |
| } | |
| TEST_F(CompatValidationTest, CanNotCreateRGxxxStorageTexture) { | |
| const wgpu::TextureFormat formats[] = { | |
| wgpu::TextureFormat::RGBA8Unorm, // pass check | |
| wgpu::TextureFormat::RG32Sint, | |
| wgpu::TextureFormat::RG32Uint, | |
| wgpu::TextureFormat::RG32Float, | |
| }; | |
| for (auto format : formats) { | |
| wgpu::TextureDescriptor descriptor; | |
| descriptor.size = {1, 1, 1}; | |
| descriptor.dimension = wgpu::TextureDimension::e2D; | |
| descriptor.format = format; | |
| descriptor.usage = wgpu::TextureUsage::StorageBinding; | |
| wgpu::Texture texture; | |
| if (format == wgpu::TextureFormat::RGBA8Unorm) { | |
| texture = device.CreateTexture(&descriptor); | |
| } else { | |
| ASSERT_DEVICE_ERROR(texture = device.CreateTexture(&descriptor)); | |
| } | |
| texture.Destroy(); | |
| } | |
| } | |
| constexpr const char* kRenderTwoTexturesOneBindgroupWGSL = R"( | |
| @vertex | |
| fn vs(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4f { | |
| var pos = array( | |
| vec4f(-1, 3, 0, 1), | |
| vec4f( 3, -1, 0, 1), | |
| vec4f(-1, -1, 0, 1)); | |
| return pos[VertexIndex]; | |
| } | |
| @group(0) @binding(0) var tex0 : texture_2d<f32>; | |
| @group(0) @binding(1) var tex1 : texture_2d<f32>; | |
| @fragment | |
| fn fs(@builtin(position) pos: vec4f) -> @location(0) vec4f { | |
| _ = tex0; | |
| _ = tex1; | |
| return vec4f(0); | |
| } | |
| )"; | |
| constexpr const char* kRenderTwoTexturesTwoBindgroupsWGSL = R"( | |
| @vertex | |
| fn vs(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4f { | |
| var pos = array( | |
| vec4f(-1, 3, 0, 1), | |
| vec4f( 3, -1, 0, 1), | |
| vec4f(-1, -1, 0, 1)); | |
| return pos[VertexIndex]; | |
| } | |
| @group(0) @binding(0) var tex0 : texture_2d<f32>; | |
| @group(1) @binding(0) var tex1 : texture_2d<f32>; | |
| @fragment | |
| fn fs(@builtin(position) pos: vec4f) -> @location(0) vec4f { | |
| _ = tex0; | |
| _ = tex1; | |
| return vec4f(0); | |
| } | |
| )"; | |
| void TestMultipleTextureViewValidationInRenderPass( | |
| wgpu::Device device, | |
| const char* wgsl, | |
| std::function<void(wgpu::Device device, | |
| wgpu::Texture texture, | |
| wgpu::RenderPipeline pipeline, | |
| std::function<void(wgpu::RenderPassEncoder pass)> drawFn)> fn) { | |
| wgpu::TextureDescriptor descriptor; | |
| descriptor.size = {2, 1, 1}; | |
| descriptor.mipLevelCount = 2; | |
| descriptor.dimension = wgpu::TextureDimension::e2D; | |
| descriptor.format = wgpu::TextureFormat::RGBA8Unorm; | |
| descriptor.usage = wgpu::TextureUsage::TextureBinding; | |
| wgpu::Texture texture = device.CreateTexture(&descriptor); | |
| constexpr uint32_t indices[] = {0, 1, 2}; | |
| wgpu::Buffer indexBuffer = | |
| utils::CreateBufferFromData(device, indices, sizeof indices, wgpu::BufferUsage::Index); | |
| // Create a pipeline that will sample from 2 2D textures and output to an attachment. | |
| wgpu::ShaderModule module = utils::CreateShaderModule(device, wgsl); | |
| utils::ComboRenderPipelineDescriptor pDesc; | |
| pDesc.vertex.module = module; | |
| pDesc.cFragment.module = module; | |
| pDesc.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm; | |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pDesc); | |
| fn(device, texture, pipeline, [](wgpu::RenderPassEncoder pass) { pass.Draw(3); }); | |
| fn(device, texture, pipeline, [indexBuffer](wgpu::RenderPassEncoder pass) { | |
| pass.SetIndexBuffer(indexBuffer, wgpu::IndexFormat::Uint32); | |
| pass.DrawIndexed(3); | |
| }); | |
| indexBuffer.Destroy(); | |
| texture.Destroy(); | |
| } | |
| enum FlexibleTextureViewsFeature { | |
| Enabled, | |
| Disabled, | |
| }; | |
| class CompatTextureViewValidationTests | |
| : public CompatValidationTest, | |
| public ::testing::WithParamInterface<FlexibleTextureViewsFeature> { | |
| public: | |
| static std::string PrintToStringParamName( | |
| const testing::TestParamInfo<FlexibleTextureViewsFeature>& info) { | |
| std::ostringstream ss; | |
| ss << "WithFlexibleTextureViews"; | |
| if (info.param == FlexibleTextureViewsFeature::Enabled) { | |
| ss << "Enabled"; | |
| } else { | |
| ss << "Disabled"; | |
| } | |
| return ss.str(); | |
| } | |
| protected: | |
| std::vector<wgpu::FeatureName> GetRequiredFeatures() override { | |
| if (HasFlexibleTextureViews()) { | |
| return {wgpu::FeatureName::FlexibleTextureViews}; | |
| } | |
| return {}; | |
| } | |
| bool HasFlexibleTextureViews() const { | |
| return GetParam() == FlexibleTextureViewsFeature::Enabled; | |
| } | |
| }; | |
| #define ASSERT_TEXTURE_VIEW_ERROR_IF_NO_FLEXIBLE_FEATURE(statement, matcher) \ | |
| do { \ | |
| if (HasFlexibleTextureViews()) { \ | |
| statement; \ | |
| } else { \ | |
| ASSERT_DEVICE_ERROR(statement, matcher); \ | |
| } \ | |
| } while (0) | |
| // Test we get a validation error if we have 2 different views of a texture | |
| // in the same bind group. Unless FlexibleTextureViews is enabled. | |
| TEST_P(CompatTextureViewValidationTests, CanNotDrawDifferentMipsSameTextureSameBindGroup) { | |
| TestMultipleTextureViewValidationInRenderPass( | |
| device, kRenderTwoTexturesOneBindgroupWGSL, | |
| [this](wgpu::Device device, wgpu::Texture texture, wgpu::RenderPipeline pipeline, | |
| std::function<void(wgpu::RenderPassEncoder pass)> drawFn) { | |
| wgpu::TextureViewDescriptor mip0ViewDesc; | |
| mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D; | |
| mip0ViewDesc.baseMipLevel = 0; | |
| mip0ViewDesc.mipLevelCount = 1; | |
| wgpu::TextureViewDescriptor mip1ViewDesc; | |
| mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D; | |
| mip1ViewDesc.baseMipLevel = 1; | |
| mip1ViewDesc.mipLevelCount = 1; | |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup( | |
| device, pipeline.GetBindGroupLayout(0), | |
| {{0, texture.CreateView(&mip0ViewDesc)}, {1, texture.CreateView(&mip1ViewDesc)}}); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| utils::BasicRenderPass rp = utils::CreateBasicRenderPass(device, 4, 1); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, bindGroup); | |
| drawFn(pass); | |
| pass.End(); | |
| ASSERT_TEXTURE_VIEW_ERROR_IF_NO_FLEXIBLE_FEATURE(encoder.Finish(), | |
| testing::HasSubstr("different views")); | |
| }); | |
| } | |
| // Test we get a validation error if we have 2 different views of a texture spanning | |
| // different bind groups. Unless FlexibleTextureViews is enabled. | |
| TEST_P(CompatTextureViewValidationTests, CanNotDrawDifferentMipsSameTextureDifferentBindGroups) { | |
| TestMultipleTextureViewValidationInRenderPass( | |
| device, kRenderTwoTexturesTwoBindgroupsWGSL, | |
| [this](wgpu::Device device, wgpu::Texture texture, wgpu::RenderPipeline pipeline, | |
| std::function<void(wgpu::RenderPassEncoder pass)> drawFn) { | |
| wgpu::TextureViewDescriptor mip0ViewDesc; | |
| mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D; | |
| mip0ViewDesc.baseMipLevel = 0; | |
| mip0ViewDesc.mipLevelCount = 1; | |
| wgpu::TextureViewDescriptor mip1ViewDesc; | |
| mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D; | |
| mip1ViewDesc.baseMipLevel = 1; | |
| mip1ViewDesc.mipLevelCount = 1; | |
| wgpu::BindGroup bindGroup0 = utils::MakeBindGroup( | |
| device, pipeline.GetBindGroupLayout(0), {{0, texture.CreateView(&mip0ViewDesc)}}); | |
| wgpu::BindGroup bindGroup1 = utils::MakeBindGroup( | |
| device, pipeline.GetBindGroupLayout(1), {{0, texture.CreateView(&mip1ViewDesc)}}); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| utils::BasicRenderPass rp = utils::CreateBasicRenderPass(device, 4, 1); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, bindGroup0); | |
| pass.SetBindGroup(1, bindGroup1); | |
| drawFn(pass); | |
| pass.End(); | |
| ASSERT_TEXTURE_VIEW_ERROR_IF_NO_FLEXIBLE_FEATURE(encoder.Finish(), | |
| testing::HasSubstr("different views")); | |
| }); | |
| } | |
| // Test that it's possible to set a bindgroup that uses a texture with multiple views | |
| // which would be an error if you issued a draw command but, you then fix the issue by replacing | |
| // the bindgroup with one that does not have multiple views. We're trying to test | |
| // that the implementation does the validation at draw command time and not before. | |
| TEST_P(CompatTextureViewValidationTests, | |
| CanBindDifferentMipsSameTextureSameBindGroupAndFixWithoutError) { | |
| TestMultipleTextureViewValidationInRenderPass( | |
| device, kRenderTwoTexturesOneBindgroupWGSL, | |
| [](wgpu::Device device, wgpu::Texture texture, wgpu::RenderPipeline pipeline, | |
| std::function<void(wgpu::RenderPassEncoder pass)> drawFn) { | |
| wgpu::TextureViewDescriptor mip0ViewDesc; | |
| mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D; | |
| mip0ViewDesc.baseMipLevel = 0; | |
| mip0ViewDesc.mipLevelCount = 1; | |
| wgpu::TextureViewDescriptor mip1ViewDesc; | |
| mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D; | |
| mip1ViewDesc.baseMipLevel = 1; | |
| mip1ViewDesc.mipLevelCount = 1; | |
| // Bindgroup with different views of same texture | |
| wgpu::BindGroup badBindGroup = utils::MakeBindGroup( | |
| device, pipeline.GetBindGroupLayout(0), | |
| {{0, texture.CreateView(&mip0ViewDesc)}, {1, texture.CreateView(&mip1ViewDesc)}}); | |
| // Bindgroup with same views of texture | |
| wgpu::BindGroup goodBindGroup = utils::MakeBindGroup( | |
| device, pipeline.GetBindGroupLayout(0), | |
| {{0, texture.CreateView(&mip0ViewDesc)}, {1, texture.CreateView(&mip0ViewDesc)}}); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| utils::BasicRenderPass rp = utils::CreateBasicRenderPass(device, 4, 1); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, badBindGroup); | |
| pass.SetBindGroup(0, goodBindGroup); | |
| drawFn(pass); | |
| pass.End(); | |
| // No Error is expected | |
| encoder.Finish(); | |
| }); | |
| } | |
| // Test that having 2 texture views that have the same settings, in 2 different | |
| // bindgroups, does not generate a validation error. | |
| TEST_P(CompatTextureViewValidationTests, CanBindSameViewIn2BindGroups) { | |
| TestMultipleTextureViewValidationInRenderPass( | |
| device, kRenderTwoTexturesTwoBindgroupsWGSL, | |
| [](wgpu::Device device, wgpu::Texture texture, wgpu::RenderPipeline pipeline, | |
| std::function<void(wgpu::RenderPassEncoder pass)> drawFn) { | |
| wgpu::TextureViewDescriptor mip0ViewDesc; | |
| mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D; | |
| wgpu::TextureViewDescriptor mip1ViewDesc; | |
| mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D; | |
| wgpu::BindGroup bindGroup0 = utils::MakeBindGroup( | |
| device, pipeline.GetBindGroupLayout(0), {{0, texture.CreateView(&mip0ViewDesc)}}); | |
| // Bindgroup with same view of texture as bindGroup0 | |
| wgpu::BindGroup bindGroup1 = utils::MakeBindGroup( | |
| device, pipeline.GetBindGroupLayout(1), {{0, texture.CreateView(&mip1ViewDesc)}}); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| utils::BasicRenderPass rp = utils::CreateBasicRenderPass(device, 4, 1); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, bindGroup0); | |
| pass.SetBindGroup(1, bindGroup1); | |
| drawFn(pass); | |
| pass.End(); | |
| // No Error is expected | |
| encoder.Finish(); | |
| }); | |
| } | |
| // Test that no validation error happens if we have multiple views of a texture | |
| // but don't draw. | |
| TEST_P(CompatTextureViewValidationTests, NoErrorIfMultipleDifferentViewsOfTextureAreNotUsed) { | |
| TestMultipleTextureViewValidationInRenderPass( | |
| device, kRenderTwoTexturesTwoBindgroupsWGSL, | |
| [](wgpu::Device device, wgpu::Texture texture, wgpu::RenderPipeline pipeline, | |
| std::function<void(wgpu::RenderPassEncoder pass)> drawFn) { | |
| wgpu::TextureViewDescriptor mip0ViewDesc; | |
| mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D; | |
| wgpu::TextureViewDescriptor mip1ViewDesc; | |
| mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D; | |
| mip1ViewDesc.baseMipLevel = 1; | |
| mip1ViewDesc.mipLevelCount = 1; | |
| // Bindgroup with different views of same texture | |
| wgpu::BindGroup bindGroup0 = utils::MakeBindGroup( | |
| device, pipeline.GetBindGroupLayout(0), {{0, texture.CreateView(&mip0ViewDesc)}}); | |
| // Bindgroup with same views of texture | |
| wgpu::BindGroup bindGroup1 = utils::MakeBindGroup( | |
| device, pipeline.GetBindGroupLayout(1), {{0, texture.CreateView(&mip1ViewDesc)}}); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| utils::BasicRenderPass rp = utils::CreateBasicRenderPass(device, 4, 1); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, bindGroup0); | |
| pass.SetBindGroup(1, bindGroup1); | |
| pass.End(); | |
| // No Error is expected because draw was never called | |
| encoder.Finish(); | |
| }); | |
| } | |
| constexpr const char* kComputeTwoTexturesOneBindgroupWGSL = R"( | |
| @group(0) @binding(0) var tex0 : texture_2d<f32>; | |
| @group(0) @binding(1) var tex1 : texture_2d<f32>; | |
| @compute @workgroup_size(1) | |
| fn cs() { | |
| _ = tex0; | |
| _ = tex1; | |
| } | |
| )"; | |
| constexpr const char* kComputeTwoTexturesTwoBindgroupsWGSL = R"( | |
| @group(0) @binding(0) var tex0 : texture_2d<f32>; | |
| @group(1) @binding(0) var tex1 : texture_2d<f32>; | |
| @compute @workgroup_size(1) | |
| fn cs() { | |
| _ = tex0; | |
| _ = tex1; | |
| } | |
| )"; | |
| void TestMultipleTextureViewValidationInComputePass( | |
| wgpu::Device device, | |
| const char* wgsl, | |
| wgpu::TextureUsage textureUsage, | |
| std::function<void(wgpu::Device device, | |
| wgpu::Texture texture, | |
| wgpu::ComputePipeline pipeline, | |
| std::function<void(wgpu::ComputePassEncoder pass)> dispatchFn)> fn) { | |
| wgpu::TextureDescriptor descriptor; | |
| descriptor.size = {2, 1, 1}; | |
| descriptor.mipLevelCount = 2; | |
| descriptor.dimension = wgpu::TextureDimension::e2D; | |
| descriptor.format = wgpu::TextureFormat::RGBA8Unorm; | |
| descriptor.usage = textureUsage; | |
| wgpu::Texture texture = device.CreateTexture(&descriptor); | |
| constexpr float indirectData[] = {1, 1, 1}; | |
| wgpu::Buffer indirectBuffer = utils::CreateBufferFromData( | |
| device, indirectData, sizeof indirectData, wgpu::BufferUsage::Indirect); | |
| // Create a pipeline that will sample from 2 2D textures and output to an attachment. | |
| wgpu::ShaderModule module = utils::CreateShaderModule(device, wgsl); | |
| wgpu::ComputePipelineDescriptor pDesc; | |
| pDesc.compute.module = module; | |
| wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&pDesc); | |
| fn(device, texture, pipeline, | |
| [](wgpu::ComputePassEncoder pass) { pass.DispatchWorkgroups(1); }); | |
| fn(device, texture, pipeline, [indirectBuffer](wgpu::ComputePassEncoder pass) { | |
| pass.DispatchWorkgroupsIndirect(indirectBuffer, 0); | |
| }); | |
| indirectBuffer.Destroy(); | |
| texture.Destroy(); | |
| } | |
| // Test we get a validation error if we have 2 different views of a texture | |
| // in the same bind group. Unless FlexibleTextureViews is enabled. | |
| TEST_P(CompatTextureViewValidationTests, CanNotComputeWithDifferentMipsSameTextureSameBindGroup) { | |
| TestMultipleTextureViewValidationInComputePass( | |
| device, kComputeTwoTexturesOneBindgroupWGSL, wgpu::TextureUsage::TextureBinding, | |
| [this](wgpu::Device device, wgpu::Texture texture, wgpu::ComputePipeline pipeline, | |
| std::function<void(wgpu::ComputePassEncoder pass)> dispatchFn) { | |
| wgpu::TextureViewDescriptor mip0ViewDesc; | |
| mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D; | |
| mip0ViewDesc.baseMipLevel = 0; | |
| mip0ViewDesc.mipLevelCount = 1; | |
| wgpu::TextureViewDescriptor mip1ViewDesc; | |
| mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D; | |
| mip1ViewDesc.baseMipLevel = 1; | |
| mip1ViewDesc.mipLevelCount = 1; | |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup( | |
| device, pipeline.GetBindGroupLayout(0), | |
| {{0, texture.CreateView(&mip0ViewDesc)}, {1, texture.CreateView(&mip1ViewDesc)}}); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass({}); | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, bindGroup); | |
| dispatchFn(pass); | |
| pass.End(); | |
| ASSERT_TEXTURE_VIEW_ERROR_IF_NO_FLEXIBLE_FEATURE(encoder.Finish(), | |
| testing::HasSubstr("different views")); | |
| }); | |
| } | |
| // Test we get a validation error if we have 2 different views of a texture spanning | |
| // different bind groups. Unless FlexibleTextureViews is enabled. | |
| TEST_P(CompatTextureViewValidationTests, | |
| CanNotComputeWithDifferentMipsSameTextureDifferentBindGroups) { | |
| TestMultipleTextureViewValidationInComputePass( | |
| device, kComputeTwoTexturesTwoBindgroupsWGSL, wgpu::TextureUsage::TextureBinding, | |
| [this](wgpu::Device device, wgpu::Texture texture, wgpu::ComputePipeline pipeline, | |
| std::function<void(wgpu::ComputePassEncoder pass)> dispatchFn) { | |
| wgpu::TextureViewDescriptor mip0ViewDesc; | |
| mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D; | |
| mip0ViewDesc.baseMipLevel = 0; | |
| mip0ViewDesc.mipLevelCount = 1; | |
| wgpu::TextureViewDescriptor mip1ViewDesc; | |
| mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D; | |
| mip1ViewDesc.baseMipLevel = 1; | |
| mip1ViewDesc.mipLevelCount = 1; | |
| wgpu::BindGroup bindGroup0 = utils::MakeBindGroup( | |
| device, pipeline.GetBindGroupLayout(0), {{0, texture.CreateView(&mip0ViewDesc)}}); | |
| wgpu::BindGroup bindGroup1 = utils::MakeBindGroup( | |
| device, pipeline.GetBindGroupLayout(1), {{0, texture.CreateView(&mip1ViewDesc)}}); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass({}); | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, bindGroup0); | |
| pass.SetBindGroup(1, bindGroup1); | |
| dispatchFn(pass); | |
| pass.End(); | |
| ASSERT_TEXTURE_VIEW_ERROR_IF_NO_FLEXIBLE_FEATURE(encoder.Finish(), | |
| testing::HasSubstr("different views")); | |
| }); | |
| } | |
| // Test that it's possible to set a bindgroup that uses a texture with multiple views | |
| // which would be an error if you issued a draw command but, you then fix the issue by replacing | |
| // the bindgroup with one that does not have multiple views. We're trying to test | |
| // that the implementation does the validation at draw command time and not before. | |
| TEST_P(CompatTextureViewValidationTests, | |
| CanBindDifferentMipsSameTextureSameBindGroupAndFixWithoutErrorInComputePass) { | |
| TestMultipleTextureViewValidationInComputePass( | |
| device, kComputeTwoTexturesOneBindgroupWGSL, wgpu::TextureUsage::TextureBinding, | |
| [](wgpu::Device device, wgpu::Texture texture, wgpu::ComputePipeline pipeline, | |
| std::function<void(wgpu::ComputePassEncoder pass)> dispatchFn) { | |
| wgpu::TextureViewDescriptor mip0ViewDesc; | |
| mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D; | |
| mip0ViewDesc.baseMipLevel = 0; | |
| mip0ViewDesc.mipLevelCount = 1; | |
| wgpu::TextureViewDescriptor mip1ViewDesc; | |
| mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D; | |
| mip1ViewDesc.baseMipLevel = 1; | |
| mip1ViewDesc.mipLevelCount = 1; | |
| // Bindgroup with different views of same texture | |
| wgpu::BindGroup badBindGroup = utils::MakeBindGroup( | |
| device, pipeline.GetBindGroupLayout(0), | |
| {{0, texture.CreateView(&mip0ViewDesc)}, {1, texture.CreateView(&mip1ViewDesc)}}); | |
| // Bindgroup with same views of texture | |
| wgpu::BindGroup goodBindGroup = utils::MakeBindGroup( | |
| device, pipeline.GetBindGroupLayout(0), | |
| {{0, texture.CreateView(&mip0ViewDesc)}, {1, texture.CreateView(&mip0ViewDesc)}}); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass({}); | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, badBindGroup); | |
| pass.SetBindGroup(0, goodBindGroup); | |
| dispatchFn(pass); | |
| pass.End(); | |
| // No Error is expected | |
| encoder.Finish(); | |
| }); | |
| } | |
| // Test that having 2 texture views that have the same settings, in 2 different | |
| // bindgroups, does not generate a validation error. | |
| TEST_P(CompatTextureViewValidationTests, CanBindSameViewIn2BindGroupsInComputePass) { | |
| TestMultipleTextureViewValidationInComputePass( | |
| device, kComputeTwoTexturesTwoBindgroupsWGSL, wgpu::TextureUsage::TextureBinding, | |
| [](wgpu::Device device, wgpu::Texture texture, wgpu::ComputePipeline pipeline, | |
| std::function<void(wgpu::ComputePassEncoder pass)> dispatchFn) { | |
| wgpu::TextureViewDescriptor mip0ViewDesc; | |
| mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D; | |
| wgpu::TextureViewDescriptor mip1ViewDesc; | |
| mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D; | |
| wgpu::BindGroup bindGroup0 = utils::MakeBindGroup( | |
| device, pipeline.GetBindGroupLayout(0), {{0, texture.CreateView(&mip0ViewDesc)}}); | |
| // Bindgroup with same view of texture as bindGroup0 | |
| wgpu::BindGroup bindGroup1 = utils::MakeBindGroup( | |
| device, pipeline.GetBindGroupLayout(1), {{0, texture.CreateView(&mip1ViewDesc)}}); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass({}); | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, bindGroup0); | |
| pass.SetBindGroup(1, bindGroup1); | |
| dispatchFn(pass); | |
| pass.End(); | |
| // No Error is expected | |
| encoder.Finish(); | |
| }); | |
| } | |
| // Test that no validation error happens if we have multiple views of a texture | |
| // but don't draw. | |
| TEST_P(CompatTextureViewValidationTests, | |
| NoErrorIfMultipleDifferentViewsOfTextureAreNotUsedInComputePass) { | |
| TestMultipleTextureViewValidationInComputePass( | |
| device, kComputeTwoTexturesTwoBindgroupsWGSL, wgpu::TextureUsage::TextureBinding, | |
| [](wgpu::Device device, wgpu::Texture texture, wgpu::ComputePipeline pipeline, | |
| std::function<void(wgpu::ComputePassEncoder pass)> dispatchFn) { | |
| wgpu::TextureViewDescriptor mip0ViewDesc; | |
| mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D; | |
| wgpu::TextureViewDescriptor mip1ViewDesc; | |
| mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D; | |
| mip1ViewDesc.baseMipLevel = 1; | |
| mip1ViewDesc.mipLevelCount = 1; | |
| // Bindgroup with different views of same texture | |
| wgpu::BindGroup bindGroup0 = utils::MakeBindGroup( | |
| device, pipeline.GetBindGroupLayout(0), {{0, texture.CreateView(&mip0ViewDesc)}}); | |
| // Bindgroup with same views of texture | |
| wgpu::BindGroup bindGroup1 = utils::MakeBindGroup( | |
| device, pipeline.GetBindGroupLayout(1), {{0, texture.CreateView(&mip1ViewDesc)}}); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass({}); | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, bindGroup0); | |
| pass.SetBindGroup(1, bindGroup1); | |
| pass.End(); | |
| // No Error is expected because draw was never called | |
| encoder.Finish(); | |
| }); | |
| } | |
| constexpr const char* kComputeTwoStorageTexturesOneBindgroupWGSL = R"( | |
| @group(0) @binding(0) var tex0 : texture_storage_2d<rgba8unorm, write>; | |
| @group(0) @binding(1) var tex1 : texture_storage_2d<rgba8unorm, write>; | |
| @compute @workgroup_size(1) | |
| fn cs() { | |
| _ = tex0; | |
| _ = tex1; | |
| } | |
| )"; | |
| constexpr const char* kComputeTwoStorageTexturesTwoBindgroupsWGSL = R"( | |
| @group(0) @binding(0) var tex0 : texture_storage_2d<rgba8unorm, write>; | |
| @group(1) @binding(0) var tex1 : texture_storage_2d<rgba8unorm, write>; | |
| @compute @workgroup_size(1) | |
| fn cs() { | |
| _ = tex0; | |
| _ = tex1; | |
| } | |
| )"; | |
| // Test we get a validation error if we have 2 different views of a storage texture | |
| // in the same bind group. Unless FlexibleTextureViews is enabled. | |
| TEST_P(CompatTextureViewValidationTests, | |
| CanNotComputeWithDifferentMipsSameStorageTextureSameBindGroup) { | |
| TestMultipleTextureViewValidationInComputePass( | |
| device, kComputeTwoStorageTexturesOneBindgroupWGSL, wgpu::TextureUsage::StorageBinding, | |
| [this](wgpu::Device device, wgpu::Texture texture, wgpu::ComputePipeline pipeline, | |
| std::function<void(wgpu::ComputePassEncoder pass)> dispatchFn) { | |
| wgpu::TextureViewDescriptor mip0ViewDesc; | |
| mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D; | |
| mip0ViewDesc.baseMipLevel = 0; | |
| mip0ViewDesc.mipLevelCount = 1; | |
| wgpu::TextureViewDescriptor mip1ViewDesc; | |
| mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D; | |
| mip1ViewDesc.baseMipLevel = 1; | |
| mip1ViewDesc.mipLevelCount = 1; | |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup( | |
| device, pipeline.GetBindGroupLayout(0), | |
| {{0, texture.CreateView(&mip0ViewDesc)}, {1, texture.CreateView(&mip1ViewDesc)}}); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass({}); | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, bindGroup); | |
| dispatchFn(pass); | |
| pass.End(); | |
| ASSERT_TEXTURE_VIEW_ERROR_IF_NO_FLEXIBLE_FEATURE(encoder.Finish(), | |
| testing::HasSubstr("different views")); | |
| }); | |
| } | |
| // Test we get a validation error if we have 2 different views of a texture spanning | |
| // different bind groups. Unless FlexibleTextureViews is enabled. | |
| TEST_P(CompatTextureViewValidationTests, | |
| CanNotComputeWithDifferentMipsSameStorageTextureDifferentBindGroups) { | |
| TestMultipleTextureViewValidationInComputePass( | |
| device, kComputeTwoStorageTexturesTwoBindgroupsWGSL, wgpu::TextureUsage::StorageBinding, | |
| [this](wgpu::Device device, wgpu::Texture texture, wgpu::ComputePipeline pipeline, | |
| std::function<void(wgpu::ComputePassEncoder pass)> dispatchFn) { | |
| wgpu::TextureViewDescriptor mip0ViewDesc; | |
| mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D; | |
| mip0ViewDesc.baseMipLevel = 0; | |
| mip0ViewDesc.mipLevelCount = 1; | |
| wgpu::TextureViewDescriptor mip1ViewDesc; | |
| mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D; | |
| mip1ViewDesc.baseMipLevel = 1; | |
| mip1ViewDesc.mipLevelCount = 1; | |
| wgpu::BindGroup bindGroup0 = utils::MakeBindGroup( | |
| device, pipeline.GetBindGroupLayout(0), {{0, texture.CreateView(&mip0ViewDesc)}}); | |
| wgpu::BindGroup bindGroup1 = utils::MakeBindGroup( | |
| device, pipeline.GetBindGroupLayout(1), {{0, texture.CreateView(&mip1ViewDesc)}}); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass({}); | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, bindGroup0); | |
| pass.SetBindGroup(1, bindGroup1); | |
| dispatchFn(pass); | |
| pass.End(); | |
| ASSERT_TEXTURE_VIEW_ERROR_IF_NO_FLEXIBLE_FEATURE(encoder.Finish(), | |
| testing::HasSubstr("different views")); | |
| }); | |
| } | |
| // Test that it's possible to set a bindgroup that uses a texture with multiple views | |
| // which would be an error if you issued a draw command but, you then fix the issue by replacing | |
| // the bindgroup with one that does not have multiple views. We're trying to test | |
| // that the implementation does the validation at draw command time and not before. | |
| TEST_P(CompatTextureViewValidationTests, | |
| CanBindDifferentMipsSameStorageTextureSameBindGroupAndFixWithoutErrorInComputePass) { | |
| TestMultipleTextureViewValidationInComputePass( | |
| device, kComputeTwoStorageTexturesOneBindgroupWGSL, wgpu::TextureUsage::StorageBinding, | |
| [](wgpu::Device device, wgpu::Texture texture, wgpu::ComputePipeline pipeline, | |
| std::function<void(wgpu::ComputePassEncoder pass)> dispatchFn) { | |
| wgpu::TextureViewDescriptor mip0ViewDesc; | |
| mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D; | |
| mip0ViewDesc.baseMipLevel = 0; | |
| mip0ViewDesc.mipLevelCount = 1; | |
| wgpu::TextureViewDescriptor mip1ViewDesc; | |
| mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D; | |
| mip1ViewDesc.baseMipLevel = 1; | |
| mip1ViewDesc.mipLevelCount = 1; | |
| // Bindgroup with different views of same texture | |
| wgpu::BindGroup badBindGroup = utils::MakeBindGroup( | |
| device, pipeline.GetBindGroupLayout(0), | |
| {{0, texture.CreateView(&mip0ViewDesc)}, {1, texture.CreateView(&mip1ViewDesc)}}); | |
| wgpu::TextureDescriptor descriptor; | |
| descriptor.size = {2, 1, 1}; | |
| descriptor.mipLevelCount = 2; | |
| descriptor.dimension = wgpu::TextureDimension::e2D; | |
| descriptor.format = wgpu::TextureFormat::RGBA8Unorm; | |
| descriptor.usage = wgpu::TextureUsage::StorageBinding; | |
| wgpu::Texture texture2 = device.CreateTexture(&descriptor); | |
| // Bindgroup with same views of texture | |
| wgpu::BindGroup goodBindGroup = utils::MakeBindGroup( | |
| device, pipeline.GetBindGroupLayout(0), | |
| {{0, texture.CreateView(&mip0ViewDesc)}, {1, texture2.CreateView(&mip0ViewDesc)}}); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass({}); | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, badBindGroup); | |
| pass.SetBindGroup(0, goodBindGroup); | |
| dispatchFn(pass); | |
| pass.End(); | |
| // No Error is expected | |
| encoder.Finish(); | |
| }); | |
| } | |
| // Test that no validation error happens if we have multiple views of a texture | |
| // but don't draw. | |
| TEST_P(CompatTextureViewValidationTests, | |
| NoErrorIfMultipleDifferentViewsOfStorageTextureAreNotUsedInComputePass) { | |
| TestMultipleTextureViewValidationInComputePass( | |
| device, kComputeTwoStorageTexturesTwoBindgroupsWGSL, wgpu::TextureUsage::StorageBinding, | |
| [](wgpu::Device device, wgpu::Texture texture, wgpu::ComputePipeline pipeline, | |
| std::function<void(wgpu::ComputePassEncoder pass)> dispatchFn) { | |
| wgpu::TextureViewDescriptor mip0ViewDesc; | |
| mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D; | |
| mip0ViewDesc.mipLevelCount = 1; | |
| wgpu::TextureViewDescriptor mip1ViewDesc; | |
| mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D; | |
| mip1ViewDesc.baseMipLevel = 1; | |
| mip1ViewDesc.mipLevelCount = 1; | |
| // Bindgroup with different views of same texture | |
| wgpu::BindGroup bindGroup0 = utils::MakeBindGroup( | |
| device, pipeline.GetBindGroupLayout(0), {{0, texture.CreateView(&mip0ViewDesc)}}); | |
| // Bindgroup with same views of texture | |
| wgpu::BindGroup bindGroup1 = utils::MakeBindGroup( | |
| device, pipeline.GetBindGroupLayout(1), {{0, texture.CreateView(&mip1ViewDesc)}}); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass({}); | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, bindGroup0); | |
| pass.SetBindGroup(1, bindGroup1); | |
| pass.End(); | |
| // No Error is expected because draw was never called | |
| encoder.Finish(); | |
| }); | |
| } | |
| TEST_F(CompatValidationTest, CanNotCreateBGRA8UnormSRGBTexture) { | |
| wgpu::TextureDescriptor descriptor; | |
| descriptor.size = {1, 1, 1}; | |
| descriptor.dimension = wgpu::TextureDimension::e2D; | |
| descriptor.format = wgpu::TextureFormat::BGRA8UnormSrgb; | |
| descriptor.usage = wgpu::TextureUsage::TextureBinding; | |
| ASSERT_DEVICE_ERROR(device.CreateTexture(&descriptor), | |
| testing::HasSubstr("not supported in compatibility mode")); | |
| } | |
| TEST_F(CompatValidationTest, CanNotCreateBGRA8UnormTextureWithBGRA8UnormSrgbView) { | |
| constexpr wgpu::TextureFormat viewFormat = wgpu::TextureFormat::BGRA8UnormSrgb; | |
| wgpu::TextureDescriptor descriptor; | |
| descriptor.size = {1, 1, 1}; | |
| descriptor.dimension = wgpu::TextureDimension::e2D; | |
| descriptor.format = wgpu::TextureFormat::BGRA8Unorm; | |
| descriptor.usage = wgpu::TextureUsage::TextureBinding; | |
| descriptor.viewFormatCount = 1; | |
| descriptor.viewFormats = &viewFormat; | |
| ASSERT_DEVICE_ERROR(device.CreateTexture(&descriptor), | |
| testing::HasSubstr("not supported in compatibility mode")); | |
| } | |
| TEST_F(CompatValidationTest, CanNotCopyMultisampleTextureToTexture) { | |
| wgpu::TextureDescriptor srcDescriptor; | |
| srcDescriptor.size = {4, 4, 1}; | |
| srcDescriptor.dimension = wgpu::TextureDimension::e2D; | |
| srcDescriptor.format = wgpu::TextureFormat::RGBA8Unorm; | |
| srcDescriptor.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment; | |
| srcDescriptor.sampleCount = 4; | |
| wgpu::Texture srcTexture = device.CreateTexture(&srcDescriptor); | |
| wgpu::TextureDescriptor dstDescriptor; | |
| dstDescriptor.size = {4, 4, 1}; | |
| dstDescriptor.dimension = wgpu::TextureDimension::e2D; | |
| dstDescriptor.format = wgpu::TextureFormat::RGBA8Unorm; | |
| dstDescriptor.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment; | |
| dstDescriptor.sampleCount = 4; | |
| wgpu::Texture dstTexture = device.CreateTexture(&dstDescriptor); | |
| wgpu::ImageCopyTexture source = utils::CreateImageCopyTexture(srcTexture); | |
| wgpu::ImageCopyTexture destination = utils::CreateImageCopyTexture(dstTexture); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| encoder.CopyTextureToTexture(&source, &destination, &srcDescriptor.size); | |
| ASSERT_DEVICE_ERROR(encoder.Finish(), | |
| testing::HasSubstr("cannot be copied in compatibility mode")); | |
| } | |
| // Regression test for crbug.com/339704108 | |
| // Error texture should not resolve mCompatibilityTextureBindingViewDimension, | |
| // as dimension could be in bad form. | |
| TEST_P(CompatTextureViewValidationTests, | |
| DoNotResolveDefaultTextureBindingViewDimensionOnErrorTexture) { | |
| // Set incompatible texture format and view format. | |
| // This validation happens before texture dimension validation and binding view dimension | |
| // resolving and shall return an error texture. | |
| constexpr wgpu::TextureFormat format = wgpu::TextureFormat::BGRA8Unorm; | |
| constexpr wgpu::TextureFormat viewFormat = wgpu::TextureFormat::RGBA8UnormSrgb; | |
| wgpu::TextureDescriptor descriptor; | |
| descriptor.size = {1, 1, 1}; | |
| descriptor.dimension = wgpu::TextureDimension::Undefined; | |
| descriptor.format = format; | |
| descriptor.usage = wgpu::TextureUsage::TextureBinding; | |
| descriptor.viewFormatCount = 1; | |
| descriptor.viewFormats = &viewFormat; | |
| ASSERT_DEVICE_ERROR(device.CreateTexture(&descriptor)); | |
| } | |
| // Regression test for crbug.com/341167195 | |
| // Resolved default compatibility textureBindingViewDimension should be validated as it may come | |
| // from the TextureBindingViewDimensionDescriptor | |
| TEST_P(CompatTextureViewValidationTests, InvalidTextureBindingViewDimensionDescriptorDescriptor) { | |
| wgpu::TextureDescriptor descriptor; | |
| descriptor.size = {1, 1, 1}; | |
| descriptor.dimension = wgpu::TextureDimension::Undefined; | |
| descriptor.format = wgpu::TextureFormat::RGBA8Unorm; | |
| descriptor.usage = wgpu::TextureUsage::TextureBinding; | |
| wgpu::TextureBindingViewDimensionDescriptor textureBindingViewDimensionDesc; | |
| descriptor.nextInChain = &textureBindingViewDimensionDesc; | |
| // Forcefully set an invalid view dimension. | |
| textureBindingViewDimensionDesc.textureBindingViewDimension = | |
| static_cast<wgpu::TextureViewDimension>(99); | |
| ASSERT_TEXTURE_VIEW_ERROR_IF_NO_FLEXIBLE_FEATURE(device.CreateTexture(&descriptor), testing::_); | |
| } | |
| class CompatTextureViewDimensionValidationTests : public CompatTextureViewValidationTests { | |
| protected: | |
| void TestBindingTextureViewDimensions( | |
| const uint32_t depth, | |
| const wgpu::TextureViewDimension textureBindingViewDimension, | |
| const wgpu::TextureViewDimension viewDimension, | |
| bool success) { | |
| wgpu::BindGroupLayout layout = utils::MakeBindGroupLayout( | |
| device, {{0, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float, | |
| viewDimension == wgpu::TextureViewDimension::Undefined | |
| ? wgpu::TextureViewDimension::e2D | |
| : viewDimension}}); | |
| wgpu::Texture texture = CreateTextureWithViewDimension(depth, wgpu::TextureDimension::e2D, | |
| textureBindingViewDimension); | |
| wgpu::TextureViewDescriptor viewDesc = {}; | |
| viewDesc.dimension = viewDimension; | |
| if (success) { | |
| utils::MakeBindGroup(device, layout, {{0, texture.CreateView(&viewDesc)}}); | |
| } else { | |
| ASSERT_DEVICE_ERROR( | |
| utils::MakeBindGroup(device, layout, {{0, texture.CreateView(&viewDesc)}}), | |
| testing::HasSubstr("must match textureBindingViewDimension")); | |
| } | |
| texture.Destroy(); | |
| } | |
| void TestCreateTextureWithViewDimensionImpl( | |
| const uint32_t depth, | |
| const wgpu::TextureDimension dimension, | |
| const wgpu::TextureViewDimension textureBindingViewDimension, | |
| bool success, | |
| const char* expectedSubstr) { | |
| if (success) { | |
| CreateTextureWithViewDimension(depth, dimension, textureBindingViewDimension); | |
| } else { | |
| ASSERT_DEVICE_ERROR( | |
| CreateTextureWithViewDimension(depth, dimension, textureBindingViewDimension); | |
| testing::HasSubstr(expectedSubstr)); | |
| } | |
| } | |
| void TestCreateTextureIsCompatibleWithViewDimension( | |
| const uint32_t depth, | |
| const wgpu::TextureDimension dimension, | |
| const wgpu::TextureViewDimension textureBindingViewDimension, | |
| bool success) { | |
| TestCreateTextureWithViewDimensionImpl(depth, dimension, textureBindingViewDimension, | |
| success, "is not compatible with the dimension"); | |
| } | |
| void TestCreateTextureLayersIsCompatibleWithViewDimension( | |
| const uint32_t depth, | |
| const wgpu::TextureDimension dimension, | |
| const wgpu::TextureViewDimension textureBindingViewDimension, | |
| bool success) { | |
| TestCreateTextureWithViewDimensionImpl(depth, dimension, textureBindingViewDimension, | |
| success, | |
| "is only compatible with depthOrArrayLayers =="); | |
| } | |
| wgpu::Texture CreateTextureWithViewDimension( | |
| const uint32_t depth, | |
| const wgpu::TextureDimension dimension, | |
| const wgpu::TextureViewDimension textureBindingViewDimension) { | |
| constexpr wgpu::TextureFormat viewFormat = wgpu::TextureFormat::RGBA8Unorm; | |
| wgpu::TextureDescriptor textureDesc; | |
| textureDesc.size = {1, 1, depth}; | |
| textureDesc.dimension = dimension; | |
| textureDesc.format = wgpu::TextureFormat::RGBA8Unorm; | |
| textureDesc.usage = wgpu::TextureUsage::TextureBinding; | |
| textureDesc.viewFormatCount = 1; | |
| textureDesc.viewFormats = &viewFormat; | |
| wgpu::TextureBindingViewDimensionDescriptor textureBindingViewDimensionDesc; | |
| if (textureBindingViewDimension != wgpu::TextureViewDimension::Undefined) { | |
| textureDesc.nextInChain = &textureBindingViewDimensionDesc; | |
| textureBindingViewDimensionDesc.textureBindingViewDimension = | |
| textureBindingViewDimension; | |
| } | |
| return device.CreateTexture(&textureDesc); | |
| } | |
| }; | |
| // Note: CubeArray is not included because CubeArray is not allowed | |
| // in compatibility mode. | |
| const wgpu::TextureViewDimension kViewDimensions[] = { | |
| wgpu::TextureViewDimension::e1D, wgpu::TextureViewDimension::e2D, | |
| wgpu::TextureViewDimension::e3D, wgpu::TextureViewDimension::e2DArray, | |
| wgpu::TextureViewDimension::Cube, | |
| }; | |
| // Test creating 1d textures with each view dimension. Unless FlexibleTextureViews is enabled. | |
| TEST_P(CompatTextureViewDimensionValidationTests, E1D) { | |
| for (auto viewDimension : kViewDimensions) { | |
| TestCreateTextureIsCompatibleWithViewDimension( | |
| 1, wgpu::TextureDimension::e1D, viewDimension, | |
| HasFlexibleTextureViews() || viewDimension == wgpu::TextureViewDimension::e1D); | |
| } | |
| } | |
| // Test creating 2d textures with each view dimension. Unless FlexibleTextureViews is enabled. | |
| TEST_P(CompatTextureViewDimensionValidationTests, E2D) { | |
| for (auto viewDimension : kViewDimensions) { | |
| TestCreateTextureIsCompatibleWithViewDimension( | |
| viewDimension == wgpu::TextureViewDimension::e2D ? 1 : 6, wgpu::TextureDimension::e2D, | |
| viewDimension, | |
| HasFlexibleTextureViews() || (viewDimension != wgpu::TextureViewDimension::e1D && | |
| viewDimension != wgpu::TextureViewDimension::e3D)); | |
| } | |
| } | |
| // Test creating 1d textures with each view dimension. Unless FlexibleTextureViews is enabled. | |
| TEST_P(CompatTextureViewDimensionValidationTests, E3D) { | |
| for (auto viewDimension : kViewDimensions) { | |
| TestCreateTextureIsCompatibleWithViewDimension( | |
| 1, wgpu::TextureDimension::e3D, viewDimension, | |
| HasFlexibleTextureViews() || viewDimension == wgpu::TextureViewDimension::e3D); | |
| } | |
| } | |
| // Test creating a 2d texture with a 2d view and depthOrArrayLayers > 1 fails. Unless | |
| // FlexibleTextureViews is enabled. | |
| TEST_P(CompatTextureViewDimensionValidationTests, E2DViewMoreThan1Layer) { | |
| TestCreateTextureLayersIsCompatibleWithViewDimension( | |
| 2, wgpu::TextureDimension::e2D, wgpu::TextureViewDimension::e2D, HasFlexibleTextureViews()); | |
| } | |
| // Test creating a 2d texture with a cube view with depthOrArrayLayers != 6 fails. Unless | |
| // FlexibleTextureViews is enabled. | |
| TEST_P(CompatTextureViewDimensionValidationTests, CubeViewMoreWhereLayersIsNot6) { | |
| uint32_t layers[] = {1, 5, 6, 7, 12}; | |
| for (auto numLayers : layers) { | |
| TestCreateTextureLayersIsCompatibleWithViewDimension( | |
| numLayers, wgpu::TextureDimension::e2D, wgpu::TextureViewDimension::Cube, | |
| HasFlexibleTextureViews() || numLayers == 6); | |
| } | |
| } | |
| TEST_P(CompatTextureViewDimensionValidationTests, OneLayerIs2DView) { | |
| TestBindingTextureViewDimensions(1, wgpu::TextureViewDimension::Undefined, | |
| wgpu::TextureViewDimension::e2D, true); | |
| } | |
| // Test 2 layer texture gets a 2d-array viewDimension | |
| TEST_P(CompatTextureViewDimensionValidationTests, TwoLayersIs2DArrayView) { | |
| TestBindingTextureViewDimensions(2, wgpu::TextureViewDimension::Undefined, | |
| wgpu::TextureViewDimension::e2DArray, true); | |
| } | |
| // Test 6 layer texture gets a 2d-array viewDimension | |
| TEST_P(CompatTextureViewDimensionValidationTests, SixLayersIs2DArrayView) { | |
| TestBindingTextureViewDimensions(6, wgpu::TextureViewDimension::Undefined, | |
| wgpu::TextureViewDimension::e2DArray, true); | |
| } | |
| // Test 2d texture can not be viewed as 2D array. Unless FlexibleTextureViews is enabled. | |
| TEST_P(CompatTextureViewDimensionValidationTests, TwoDTextureViewDimensionCanNotBeViewedAs2DArray) { | |
| TestBindingTextureViewDimensions(1, wgpu::TextureViewDimension::e2D, | |
| wgpu::TextureViewDimension::e2DArray, | |
| HasFlexibleTextureViews()); | |
| } | |
| // Test 2d-array texture can not be viewed as cube. Unless FlexibleTextureViews is enabled. | |
| TEST_P(CompatTextureViewDimensionValidationTests, | |
| TwoDArrayTextureViewDimensionCanNotBeViewedAsCube) { | |
| TestBindingTextureViewDimensions(6, wgpu::TextureViewDimension::e2DArray, | |
| wgpu::TextureViewDimension::Cube, HasFlexibleTextureViews()); | |
| } | |
| // Test cube texture can not be viewed as 2d-array. Unless FlexibleTextureViews is enabled. | |
| TEST_P(CompatTextureViewDimensionValidationTests, CubeTextureViewDimensionCanNotBeViewedAs2DArray) { | |
| TestBindingTextureViewDimensions(6, wgpu::TextureViewDimension::Cube, | |
| wgpu::TextureViewDimension::e2DArray, | |
| HasFlexibleTextureViews()); | |
| } | |
| // Test 2Darray != 2d | |
| // Test cube !== 2d | |
| // Test cube !== 2d-array | |
| class CompatCompressedCopyT2BAndCopyT2TValidationTests : public CompatValidationTest { | |
| protected: | |
| std::vector<wgpu::FeatureName> GetRequiredFeatures() override { | |
| std::vector<wgpu::FeatureName> requiredFeatures; | |
| for (TextureInfo textureInfo : textureInfos) { | |
| if (adapter.HasFeature(textureInfo.feature)) { | |
| requiredFeatures.push_back(textureInfo.feature); | |
| } | |
| } | |
| return requiredFeatures; | |
| } | |
| struct TextureInfo { | |
| wgpu::FeatureName feature; | |
| wgpu::TextureFormat format; | |
| }; | |
| static constexpr TextureInfo textureInfos[] = { | |
| { | |
| wgpu::FeatureName::TextureCompressionBC, | |
| wgpu::TextureFormat::BC2RGBAUnorm, | |
| }, | |
| { | |
| wgpu::FeatureName::TextureCompressionETC2, | |
| wgpu::TextureFormat::ETC2RGB8Unorm, | |
| }, | |
| { | |
| wgpu::FeatureName::TextureCompressionASTC, | |
| wgpu::TextureFormat::ASTC4x4Unorm, | |
| }, | |
| }; | |
| }; | |
| TEST_F(CompatCompressedCopyT2BAndCopyT2TValidationTests, CanNotCopyCompressedTextureToBuffer) { | |
| for (TextureInfo textureInfo : textureInfos) { | |
| if (!device.HasFeature(textureInfo.feature)) { | |
| continue; | |
| } | |
| wgpu::TextureDescriptor descriptor; | |
| descriptor.size = {4, 4, 1}; | |
| descriptor.dimension = wgpu::TextureDimension::e2D; | |
| descriptor.format = textureInfo.format; | |
| descriptor.usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopySrc; | |
| wgpu::Texture texture = device.CreateTexture(&descriptor); | |
| wgpu::BufferDescriptor bufferDescriptor; | |
| bufferDescriptor.size = 256 * 4; | |
| bufferDescriptor.usage = wgpu::BufferUsage::CopyDst; | |
| wgpu::Buffer buffer = device.CreateBuffer(&bufferDescriptor); | |
| wgpu::ImageCopyTexture source = utils::CreateImageCopyTexture(texture); | |
| wgpu::ImageCopyBuffer destination = utils::CreateImageCopyBuffer(buffer, 0, 256, 4); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| encoder.CopyTextureToBuffer(&source, &destination, &descriptor.size); | |
| ASSERT_DEVICE_ERROR(encoder.Finish(), testing::HasSubstr("cannot be used")); | |
| } | |
| } | |
| TEST_F(CompatCompressedCopyT2BAndCopyT2TValidationTests, CanNotCopyCompressedTextureToTexture) { | |
| for (TextureInfo textureInfo : textureInfos) { | |
| if (!device.HasFeature(textureInfo.feature)) { | |
| continue; | |
| } | |
| wgpu::TextureDescriptor descriptor; | |
| descriptor.size = {4, 4, 1}; | |
| descriptor.dimension = wgpu::TextureDimension::e2D; | |
| descriptor.format = textureInfo.format; | |
| descriptor.usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopySrc; | |
| wgpu::Texture srcTexture = device.CreateTexture(&descriptor); | |
| descriptor.usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopyDst; | |
| wgpu::Texture dstTexture = device.CreateTexture(&descriptor); | |
| wgpu::ImageCopyTexture source = utils::CreateImageCopyTexture(srcTexture); | |
| wgpu::ImageCopyTexture destination = utils::CreateImageCopyTexture(dstTexture); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| encoder.CopyTextureToTexture(&source, &destination, &descriptor.size); | |
| ASSERT_DEVICE_ERROR(encoder.Finish(), testing::HasSubstr("cannot be used")); | |
| } | |
| } | |
| class CompatMaxVertexAttributesTest : public CompatValidationTest { | |
| protected: | |
| void TestMaxVertexAttributes(bool usesVertexIndex, bool usesInstanceIndex) { | |
| wgpu::SupportedLimits limits; | |
| device.GetLimits(&limits); | |
| uint32_t maxAttributes = limits.limits.maxVertexAttributes; | |
| uint32_t numAttributesUsedByBuiltins = | |
| (usesVertexIndex ? 1 : 0) + (usesInstanceIndex ? 1 : 0); | |
| TestAttributes(maxAttributes - numAttributesUsedByBuiltins, usesVertexIndex, | |
| usesInstanceIndex, true); | |
| if (usesVertexIndex || usesInstanceIndex) { | |
| TestAttributes(maxAttributes - numAttributesUsedByBuiltins + 1, usesVertexIndex, | |
| usesInstanceIndex, false); | |
| } | |
| } | |
| void TestAttributes(uint32_t numAttributes, | |
| bool usesVertexIndex, | |
| bool usesInstanceIndex, | |
| bool expectSuccess) { | |
| std::vector<std::string> inputs; | |
| std::vector<std::string> outputs; | |
| utils::ComboRenderPipelineDescriptor descriptor; | |
| descriptor.layout = {}; | |
| descriptor.vertex.bufferCount = 1; | |
| descriptor.cBuffers[0].arrayStride = 16; | |
| descriptor.cBuffers[0].attributeCount = numAttributes; | |
| for (uint32_t i = 0; i < numAttributes; ++i) { | |
| inputs.push_back(absl::StrFormat("@location(%u) v%u: vec4f", i, i)); | |
| outputs.push_back(absl::StrFormat("v%u", i)); | |
| descriptor.cAttributes[i].format = wgpu::VertexFormat::Float32x4; | |
| descriptor.cAttributes[i].shaderLocation = i; | |
| } | |
| if (usesVertexIndex) { | |
| inputs.push_back("@builtin(vertex_index) vNdx: u32"); | |
| outputs.push_back("vec4f(f32(vNdx))"); | |
| } | |
| if (usesInstanceIndex) { | |
| inputs.push_back("@builtin(instance_index) iNdx: u32"); | |
| outputs.push_back("vec4f(f32(iNdx))"); | |
| } | |
| auto wgsl = absl::StrFormat(R"( | |
| @fragment fn fs() -> @location(0) vec4f { | |
| return vec4f(1); | |
| } | |
| @vertex fn vs(%s) -> @builtin(position) vec4f { | |
| return %s; | |
| } | |
| )", | |
| absl::StrJoin(inputs, ", "), absl::StrJoin(outputs, " + ")); | |
| wgpu::ShaderModule module = utils::CreateShaderModule(device, wgsl.c_str()); | |
| descriptor.vertex.module = module; | |
| descriptor.cFragment.module = module; | |
| if (expectSuccess) { | |
| device.CreateRenderPipeline(&descriptor); | |
| } else { | |
| ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor), | |
| testing::HasSubstr("compat")); | |
| } | |
| } | |
| }; | |
| TEST_F(CompatMaxVertexAttributesTest, CanUseMaxVertexAttributes) { | |
| TestMaxVertexAttributes(false, false); | |
| } | |
| TEST_F(CompatMaxVertexAttributesTest, VertexIndexTakesAnAttribute) { | |
| TestMaxVertexAttributes(true, false); | |
| } | |
| TEST_F(CompatMaxVertexAttributesTest, InstanceIndexTakesAnAttribute) { | |
| TestMaxVertexAttributes(false, true); | |
| } | |
| TEST_F(CompatMaxVertexAttributesTest, VertexAndInstanceIndexEachTakeAnAttribute) { | |
| TestMaxVertexAttributes(true, true); | |
| } | |
| INSTANTIATE_TEST_SUITE_P(, | |
| CompatTextureViewValidationTests, | |
| ::testing::Values(FlexibleTextureViewsFeature::Disabled, | |
| FlexibleTextureViewsFeature::Enabled), | |
| CompatTextureViewValidationTests::PrintToStringParamName); | |
| INSTANTIATE_TEST_SUITE_P(, | |
| CompatTextureViewDimensionValidationTests, | |
| ::testing::Values(FlexibleTextureViewsFeature::Disabled, | |
| FlexibleTextureViewsFeature::Enabled), | |
| CompatTextureViewValidationTests::PrintToStringParamName); | |
| } // anonymous namespace | |
| } // namespace dawn |