| // 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 <limits> | |
| #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 = 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, CanNotUseFragmentShaderWithSampleMask) { | |
| 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[] = { | |
| "linear", | |
| "perspective, sample", | |
| }; | |
| 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]; | |
| 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(); | |
| } | |
| // Test we get a validation error if we have 2 different views of a texture | |
| // in the same bind group. | |
| TEST_F(CompatValidationTest, 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_DEVICE_ERROR(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. | |
| TEST_F(CompatValidationTest, 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_DEVICE_ERROR(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_F(CompatValidationTest, 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_F(CompatValidationTest, 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_F(CompatValidationTest, 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. | |
| TEST_F(CompatValidationTest, 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_DEVICE_ERROR(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. | |
| TEST_F(CompatValidationTest, 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_DEVICE_ERROR(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_F(CompatValidationTest, | |
| 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_F(CompatValidationTest, 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_F(CompatValidationTest, 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. | |
| TEST_F(CompatValidationTest, 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_DEVICE_ERROR(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. | |
| TEST_F(CompatValidationTest, 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_DEVICE_ERROR(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_F(CompatValidationTest, | |
| 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_F(CompatValidationTest, | |
| 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")); | |
| } | |
| class CompatTextureViewDimensionValidationTests : public CompatValidationTest { | |
| 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. | |
| TEST_F(CompatTextureViewDimensionValidationTests, E1D) { | |
| for (auto viewDimension : kViewDimensions) { | |
| TestCreateTextureIsCompatibleWithViewDimension( | |
| 1, wgpu::TextureDimension::e1D, viewDimension, | |
| viewDimension == wgpu::TextureViewDimension::e1D); | |
| } | |
| } | |
| // Test creating 2d textures with each view dimension. | |
| TEST_F(CompatTextureViewDimensionValidationTests, E2D) { | |
| for (auto viewDimension : kViewDimensions) { | |
| TestCreateTextureIsCompatibleWithViewDimension( | |
| viewDimension == wgpu::TextureViewDimension::e2D ? 1 : 6, wgpu::TextureDimension::e2D, | |
| viewDimension, | |
| viewDimension != wgpu::TextureViewDimension::e1D && | |
| viewDimension != wgpu::TextureViewDimension::e3D); | |
| } | |
| } | |
| // Test creating 1d textures with each view dimension. | |
| TEST_F(CompatTextureViewDimensionValidationTests, E3D) { | |
| for (auto viewDimension : kViewDimensions) { | |
| TestCreateTextureIsCompatibleWithViewDimension( | |
| 1, wgpu::TextureDimension::e3D, viewDimension, | |
| viewDimension == wgpu::TextureViewDimension::e3D); | |
| } | |
| } | |
| // Test creating a 2d texture with a 2d view and depthOrArrayLayers > 1 fails | |
| TEST_F(CompatTextureViewDimensionValidationTests, E2DViewMoreThan1Layer) { | |
| TestCreateTextureLayersIsCompatibleWithViewDimension(2, wgpu::TextureDimension::e2D, | |
| wgpu::TextureViewDimension::e2D, false); | |
| } | |
| // Test creating a 2d texture with a cube view with depthOrArrayLayers != 6 fails | |
| TEST_F(CompatTextureViewDimensionValidationTests, CubeViewMoreWhereLayersIsNot6) { | |
| uint32_t layers[] = {1, 5, 6, 7, 12}; | |
| for (auto numLayers : layers) { | |
| TestCreateTextureLayersIsCompatibleWithViewDimension(numLayers, wgpu::TextureDimension::e2D, | |
| wgpu::TextureViewDimension::Cube, | |
| numLayers == 6); | |
| } | |
| } | |
| TEST_F(CompatTextureViewDimensionValidationTests, OneLayerIs2DView) { | |
| TestBindingTextureViewDimensions(1, wgpu::TextureViewDimension::Undefined, | |
| wgpu::TextureViewDimension::e2D, true); | |
| } | |
| // Test 2 layer texture gets a 2d-array viewDimension | |
| TEST_F(CompatTextureViewDimensionValidationTests, TwoLayersIs2DArrayView) { | |
| TestBindingTextureViewDimensions(2, wgpu::TextureViewDimension::Undefined, | |
| wgpu::TextureViewDimension::e2DArray, true); | |
| } | |
| // Test 6 layer texture gets a 2d-array viewDimension | |
| TEST_F(CompatTextureViewDimensionValidationTests, SixLayersIs2DArrayView) { | |
| TestBindingTextureViewDimensions(6, wgpu::TextureViewDimension::Undefined, | |
| wgpu::TextureViewDimension::e2DArray, true); | |
| } | |
| // Test 2d texture can not be viewed as 2D array | |
| TEST_F(CompatTextureViewDimensionValidationTests, TwoDTextureViewDimensionCanNotBeViewedAs2DArray) { | |
| TestBindingTextureViewDimensions(1, wgpu::TextureViewDimension::e2D, | |
| wgpu::TextureViewDimension::e2DArray, false); | |
| } | |
| // Test 2d-array texture can not be viewed as cube | |
| TEST_F(CompatTextureViewDimensionValidationTests, | |
| TwoDArrayTextureViewDimensionCanNotBeViewedAsCube) { | |
| TestBindingTextureViewDimensions(6, wgpu::TextureViewDimension::e2DArray, | |
| wgpu::TextureViewDimension::Cube, false); | |
| } | |
| // Test cube texture can not be viewed as 2d-array | |
| TEST_F(CompatTextureViewDimensionValidationTests, CubeTextureViewDimensionCanNotBeViewedAs2DArray) { | |
| TestBindingTextureViewDimensions(6, wgpu::TextureViewDimension::Cube, | |
| wgpu::TextureViewDimension::e2DArray, false); | |
| } | |
| // Test 2Darray != 2d | |
| // Test cube !== 2d | |
| // Test cube !== 2d-array | |
| class CompatCompressedCopyT2BAndCopyT2TValidationTests : public CompatValidationTest { | |
| protected: | |
| WGPUDevice CreateTestDevice(native::Adapter dawnAdapter, | |
| wgpu::DeviceDescriptor descriptor) override { | |
| std::vector<wgpu::FeatureName> requiredFeatures; | |
| for (TextureInfo textureInfo : textureInfos) { | |
| if (adapter.HasFeature(textureInfo.feature)) { | |
| requiredFeatures.push_back(textureInfo.feature); | |
| } | |
| } | |
| descriptor.requiredFeatures = requiredFeatures.data(); | |
| descriptor.requiredFeatureCount = requiredFeatures.size(); | |
| return dawnAdapter.CreateDevice(&descriptor); | |
| } | |
| 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); | |
| } | |
| } // anonymous namespace | |
| } // namespace dawn |