| // Copyright 2025 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 <cmath> |
| #include <string> |
| #include <vector> |
| |
| #include "dawn/tests/DawnTest.h" |
| #include "dawn/utils/ComboRenderPipelineDescriptor.h" |
| #include "dawn/utils/WGPUHelpers.h" |
| |
| namespace dawn { |
| namespace { |
| |
| class TextureFormatsTier1Test : public DawnTest { |
| protected: |
| std::vector<wgpu::FeatureName> GetRequiredFeatures() override { |
| std::vector<wgpu::FeatureName> requiredFeatures = {}; |
| if (SupportsFeatures({wgpu::FeatureName::TextureFormatsTier1})) { |
| requiredFeatures.push_back(wgpu::FeatureName::TextureFormatsTier1); |
| } |
| return requiredFeatures; |
| } |
| |
| const char* GetFullScreenQuadVS() { |
| return R"( |
| @vertex |
| fn vs_main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4<f32> { |
| var positions = array<vec2<f32>, 3>( |
| vec2<f32>(-1.0, -1.0), |
| vec2<f32>( 3.0, -1.0), |
| vec2<f32>( -1.0, 3.0)); |
| return vec4<f32>(positions[VertexIndex], 0.0, 1.0); |
| } |
| )"; |
| } |
| |
| std::string GenerateFragmentShader(const std::vector<float>& srcColorFloats) { |
| std::ostringstream fsCodeStream; |
| fsCodeStream << R"( |
| @fragment |
| fn fs_main() -> @location(0) vec4<f32> { |
| return vec4<f32>()"; |
| |
| for (size_t i = 0; i < 4; ++i) { |
| if (i < srcColorFloats.size()) { |
| fsCodeStream << srcColorFloats[i]; |
| } else { |
| fsCodeStream << (i == 3 ? "1.0" : "0.0"); |
| } |
| if (i < 3) { |
| fsCodeStream << ", "; |
| } |
| } |
| |
| fsCodeStream << ");\n" |
| << "}\n"; |
| |
| return fsCodeStream.str(); |
| } |
| |
| // Function to run a basic render pass drawing a full-screen quad. |
| // Returns the finished command buffer for submission. |
| wgpu::CommandBuffer RunDrawPass(wgpu::TextureView renderTargetView, |
| wgpu::TextureFormat renderTargetFormat, |
| const std::vector<float>& drawColorFloats, |
| wgpu::MultisampleState multisampleState = {}) { |
| std::string fsCode = GenerateFragmentShader(drawColorFloats); |
| wgpu::ShaderModule shaderModule = |
| utils::CreateShaderModule(device, (GetFullScreenQuadVS() + fsCode).c_str()); |
| |
| utils::ComboRenderPipelineDescriptor pipelineDesc; |
| pipelineDesc.vertex.module = shaderModule; |
| pipelineDesc.cFragment.module = shaderModule; |
| pipelineDesc.primitive.topology = wgpu::PrimitiveTopology::TriangleList; |
| pipelineDesc.cFragment.targetCount = 1; |
| pipelineDesc.cTargets[0].format = renderTargetFormat; |
| pipelineDesc.multisample = multisampleState; // Apply provided multisample state |
| pipelineDesc.cTargets[0].writeMask = wgpu::ColorWriteMask::All; |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pipelineDesc); |
| |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| utils::ComboRenderPassDescriptor renderPass; |
| renderPass.cColorAttachments[0].view = renderTargetView; |
| renderPass.cColorAttachments[0].loadOp = wgpu::LoadOp::Clear; |
| renderPass.cColorAttachments[0].storeOp = wgpu::StoreOp::Store; |
| renderPass.cColorAttachments[0].clearValue = {0.0f, 0.0f, 0.0f, 0.0f}; |
| renderPass.colorAttachmentCount = 1; |
| |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); |
| pass.SetPipeline(pipeline); |
| pass.Draw(3); |
| pass.End(); |
| return encoder.Finish(); |
| } |
| |
| // Function to run a render pass with blending. |
| wgpu::CommandBuffer RunBlendPass(wgpu::TextureView renderTargetView, |
| wgpu::TextureFormat renderTargetFormat, |
| const std::vector<float>& srcColorFloats, |
| const std::vector<float>& clearColorFloats) { |
| std::string fsCode = GenerateFragmentShader(srcColorFloats); |
| wgpu::ShaderModule shaderModule = |
| utils::CreateShaderModule(device, (GetFullScreenQuadVS() + fsCode).c_str()); |
| |
| utils::ComboRenderPipelineDescriptor pipelineDesc; |
| pipelineDesc.vertex.module = shaderModule; |
| pipelineDesc.cFragment.module = shaderModule; |
| pipelineDesc.primitive.topology = wgpu::PrimitiveTopology::TriangleList; |
| pipelineDesc.cFragment.targetCount = 1; |
| pipelineDesc.cTargets[0].format = renderTargetFormat; |
| pipelineDesc.cTargets[0].blend = &pipelineDesc.cBlends[0]; |
| |
| // Standard alpha blending: SrcAlpha * Src + (1 - SrcAlpha) * Dst |
| pipelineDesc.cBlends[0].color.srcFactor = wgpu::BlendFactor::SrcAlpha; |
| pipelineDesc.cBlends[0].color.dstFactor = wgpu::BlendFactor::OneMinusSrcAlpha; |
| pipelineDesc.cBlends[0].color.operation = wgpu::BlendOperation::Add; |
| pipelineDesc.cBlends[0].alpha.srcFactor = wgpu::BlendFactor::One; |
| pipelineDesc.cBlends[0].alpha.dstFactor = wgpu::BlendFactor::Zero; |
| pipelineDesc.cBlends[0].alpha.operation = wgpu::BlendOperation::Add; |
| pipelineDesc.cTargets[0].writeMask = wgpu::ColorWriteMask::All; |
| |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pipelineDesc); |
| |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| utils::ComboRenderPassDescriptor renderPass; |
| renderPass.cColorAttachments[0].view = renderTargetView; |
| renderPass.cColorAttachments[0].loadOp = wgpu::LoadOp::Clear; |
| renderPass.cColorAttachments[0].storeOp = wgpu::StoreOp::Store; |
| renderPass.cColorAttachments[0].clearValue = {clearColorFloats[0], clearColorFloats[1], |
| clearColorFloats[2], clearColorFloats[3]}; |
| renderPass.colorAttachmentCount = 1; |
| |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); |
| pass.SetPipeline(pipeline); |
| pass.Draw(3); |
| pass.End(); |
| return encoder.Finish(); |
| } |
| |
| // Helper function for calculating expectations and checking pixels. |
| void CalculateAndCheckPixels(wgpu::Texture texture, |
| wgpu::TextureFormat format, |
| const std::vector<float>& originData) { |
| uint32_t componentCount = utils::GetTextureComponentCount(format); |
| |
| // Define vectors for expected bounds for each possible format type. |
| std::vector<int8_t> expectedLowerBoundSnorm8; |
| std::vector<int8_t> expectedUpperBoundSnorm8; |
| std::vector<int16_t> expectedLowerBoundSnorm16; |
| std::vector<int16_t> expectedUpperBoundSnorm16; |
| std::vector<uint16_t> expectedLowerBoundUnorm16; |
| std::vector<uint16_t> expectedUpperBoundUnorm16; |
| |
| for (uint32_t i = 0; i < componentCount; ++i) { |
| float floatComponent = originData[i]; |
| // FLOAT -> SNORM/UNORM is permitted tolerance of 0.6f ULP. See |
| // https://microsoft.github.io/DirectX-Specs/d3d/archive/D3D11_3_FunctionalSpec.htm#3.2.3.4%20FLOAT%20-%3E%20SNORM |
| float tolerance = 0.6f; |
| |
| switch (format) { |
| case wgpu::TextureFormat::R8Snorm: |
| case wgpu::TextureFormat::RG8Snorm: |
| case wgpu::TextureFormat::RGBA8Snorm: { |
| float scaledComponent = floatComponent * 127.0f; |
| int8_t lowerExpectation = |
| utils::ConvertFloatToSnorm8(scaledComponent - tolerance); |
| int8_t upperExpectation = |
| utils::ConvertFloatToSnorm8(scaledComponent + tolerance); |
| expectedLowerBoundSnorm8.push_back(lowerExpectation); |
| expectedUpperBoundSnorm8.push_back(upperExpectation); |
| break; |
| } |
| case wgpu::TextureFormat::R16Snorm: |
| case wgpu::TextureFormat::RG16Snorm: |
| case wgpu::TextureFormat::RGBA16Snorm: { |
| float scaledComponent = floatComponent * 32767.0f; |
| int16_t lowerExpectation = |
| utils::ConvertFloatToSnorm16(scaledComponent - tolerance); |
| int16_t upperExpectation = |
| utils::ConvertFloatToSnorm16(scaledComponent + tolerance); |
| expectedLowerBoundSnorm16.push_back(lowerExpectation); |
| expectedUpperBoundSnorm16.push_back(upperExpectation); |
| break; |
| } |
| case wgpu::TextureFormat::R16Unorm: |
| case wgpu::TextureFormat::RG16Unorm: |
| case wgpu::TextureFormat::RGBA16Unorm: { |
| float clampedComponent = floatComponent * 65535.0f; |
| uint16_t lowerExpectation = |
| utils::ConvertFloatToUnorm16(clampedComponent - tolerance); |
| uint16_t upperExpectation = |
| utils::ConvertFloatToUnorm16(clampedComponent + tolerance); |
| expectedLowerBoundUnorm16.push_back(lowerExpectation); |
| expectedUpperBoundUnorm16.push_back(upperExpectation); |
| break; |
| } |
| default: |
| DAWN_UNREACHABLE(); |
| } |
| } |
| |
| // Perform the texture pixel validation based on the determined format type. |
| // It asserts that the pixel at {0,0} with size {1,1} falls within the calculated bounds. |
| switch (format) { |
| case wgpu::TextureFormat::R8Snorm: |
| case wgpu::TextureFormat::RG8Snorm: |
| case wgpu::TextureFormat::RGBA8Snorm: |
| EXPECT_TEXTURE_NORM_BETWEEN(expectedLowerBoundSnorm8, expectedUpperBoundSnorm8, |
| texture, {0, 0}, {1, 1}, format); |
| break; |
| case wgpu::TextureFormat::R16Snorm: |
| case wgpu::TextureFormat::RG16Snorm: |
| case wgpu::TextureFormat::RGBA16Snorm: |
| EXPECT_TEXTURE_NORM_BETWEEN(expectedLowerBoundSnorm16, expectedUpperBoundSnorm16, |
| texture, {0, 0}, {1, 1}, format); |
| break; |
| case wgpu::TextureFormat::R16Unorm: |
| case wgpu::TextureFormat::RG16Unorm: |
| case wgpu::TextureFormat::RGBA16Unorm: |
| EXPECT_TEXTURE_NORM_BETWEEN(expectedLowerBoundUnorm16, expectedUpperBoundUnorm16, |
| texture, {0, 0}, {1, 1}, format); |
| break; |
| default: |
| DAWN_UNREACHABLE(); |
| } |
| } |
| }; |
| |
| class RenderAttachmentFormatsTest : public TextureFormatsTier1Test { |
| protected: |
| void RunRenderTest(wgpu::TextureFormat format, const std::vector<float>& originData) { |
| DAWN_TEST_UNSUPPORTED_IF(!device.HasFeature(wgpu::FeatureName::TextureFormatsTier1)); |
| |
| wgpu::Extent3D textureSize = {16, 16, 1}; |
| wgpu::TextureDescriptor textureDesc; |
| textureDesc.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc; |
| textureDesc.dimension = wgpu::TextureDimension::e2D; |
| textureDesc.size = textureSize; |
| textureDesc.format = format; |
| |
| wgpu::Texture texture = device.CreateTexture(&textureDesc); |
| |
| wgpu::CommandBuffer drawCommands = RunDrawPass(texture.CreateView(), format, originData); |
| queue.Submit(1, &drawCommands); |
| CalculateAndCheckPixels(texture, format, originData); |
| } |
| }; |
| |
| // Test that r8snorm format is valid as renderable texture if |
| // 'texture-formats-tier1' is enabled. |
| TEST_P(RenderAttachmentFormatsTest, R8SnormRenderAttachment) { |
| std::vector<float> originData = {-0.5f}; // R |
| RunRenderTest(wgpu::TextureFormat::R8Snorm, originData); |
| } |
| |
| // Test that rg8snorm format is valid as renderable texture if |
| // 'texture-formats-tier1' is enabled. |
| TEST_P(RenderAttachmentFormatsTest, RG8SnormRenderAttachment) { |
| std::vector<float> originData = {-0.5f, 0.25f}; // RG |
| RunRenderTest(wgpu::TextureFormat::RG8Snorm, originData); |
| } |
| |
| // Test that rgba8snorm format is valid as renderable texture if |
| // 'texture-formats-tier1' is enabled. |
| TEST_P(RenderAttachmentFormatsTest, RGBA8SnormRenderAttachment) { |
| std::vector<float> originData = {-0.5f, 0.25f, -1.0f, 1.0f}; // RGBA |
| RunRenderTest(wgpu::TextureFormat::RGBA8Snorm, originData); |
| } |
| |
| // Test that r16unorm format is valid as renderable texture if |
| // 'texture-formats-tier1' is enabled. |
| TEST_P(RenderAttachmentFormatsTest, R16UnormRenderAttachment) { |
| std::vector<float> originData = {0.375f}; |
| RunRenderTest(wgpu::TextureFormat::R16Unorm, originData); |
| } |
| |
| // Test that r16snorm format is valid as renderable texture if |
| // 'texture-formats-tier1' is enabled. |
| TEST_P(RenderAttachmentFormatsTest, R16SnormRenderAttachment) { |
| std::vector<float> originData = {-0.375f}; |
| RunRenderTest(wgpu::TextureFormat::R16Snorm, originData); |
| } |
| |
| // Test that rg16unorm format is valid as renderable texture if |
| // 'texture-formats-tier1' is enabled. |
| TEST_P(RenderAttachmentFormatsTest, RG16UnormRenderAttachment) { |
| std::vector<float> originData = {0.375f, 0.75f}; |
| RunRenderTest(wgpu::TextureFormat::RG16Unorm, originData); |
| } |
| |
| // Test that rg16snorm format is valid as renderable texture if |
| // 'texture-formats-tier1' is enabled. |
| TEST_P(RenderAttachmentFormatsTest, RG16SnormRenderAttachment) { |
| std::vector<float> originData = {-0.375f, 0.625f}; |
| RunRenderTest(wgpu::TextureFormat::RG16Snorm, originData); |
| } |
| |
| // Test that rgba16unorm format is valid as renderable texture if |
| // 'texture-formats-tier1' is enabled. |
| TEST_P(RenderAttachmentFormatsTest, RGBA16UnormRenderAttachment) { |
| std::vector<float> originData = {0.375f, 0.75f, 0.125f, 0.875f}; |
| RunRenderTest(wgpu::TextureFormat::RGBA16Unorm, originData); |
| } |
| |
| // Test that rgba16snorm format is valid as renderable texture if |
| // 'texture-formats-tier1' is enabled. |
| TEST_P(RenderAttachmentFormatsTest, RGBA16SnormRenderAttachment) { |
| std::vector<float> originData = {-0.375f, 0.625f, -0.875f, 0.5f}; |
| RunRenderTest(wgpu::TextureFormat::RGBA16Snorm, originData); |
| } |
| |
| DAWN_INSTANTIATE_TEST(RenderAttachmentFormatsTest, |
| D3D11Backend(), |
| D3D12Backend(), |
| MetalBackend(), |
| OpenGLBackend(), |
| VulkanBackend(), |
| WebGPUBackend()); |
| |
| class BlendableFormatsTest : public TextureFormatsTier1Test { |
| protected: |
| void RunBlendTest(wgpu::TextureFormat format, |
| const std::vector<float>& srcColorFloats, |
| const std::vector<float>& clearColorFloats) { |
| DAWN_TEST_UNSUPPORTED_IF(!device.HasFeature(wgpu::FeatureName::TextureFormatsTier1)); |
| |
| wgpu::Extent3D textureSize = {16, 16, 1}; |
| wgpu::TextureDescriptor textureDesc; |
| textureDesc.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc; |
| textureDesc.dimension = wgpu::TextureDimension::e2D; |
| textureDesc.size = textureSize; |
| textureDesc.format = format; |
| wgpu::Texture renderTarget = device.CreateTexture(&textureDesc); |
| wgpu::TextureView renderTargetView = renderTarget.CreateView(); |
| |
| wgpu::CommandBuffer blendCommands = |
| RunBlendPass(renderTargetView, format, srcColorFloats, clearColorFloats); |
| queue.Submit(1, &blendCommands); |
| |
| std::vector<float> expectedBlendedFloats(4); |
| float srcR = (srcColorFloats.size() > 0) ? srcColorFloats[0] : 0.0f; |
| float srcG = (srcColorFloats.size() > 1) ? srcColorFloats[1] : 0.0f; |
| float srcB = (srcColorFloats.size() > 2) ? srcColorFloats[2] : 0.0f; |
| float srcA = (srcColorFloats.size() > 3) ? srcColorFloats[3] : 1.0f; |
| |
| float dstR = clearColorFloats[0]; |
| float dstG = clearColorFloats[1]; |
| float dstB = clearColorFloats[2]; |
| |
| expectedBlendedFloats[0] = srcR * srcA + dstR * (1.0f - srcA); |
| expectedBlendedFloats[1] = srcG * srcA + dstG * (1.0f - srcA); |
| expectedBlendedFloats[2] = srcB * srcA + dstB * (1.0f - srcA); |
| expectedBlendedFloats[3] = srcA; |
| CalculateAndCheckPixels(renderTarget, format, expectedBlendedFloats); |
| } |
| }; |
| |
| // Test that r8snorm format is blendable when 'texture-formats-tier1' is enabled. |
| TEST_P(BlendableFormatsTest, R8SnormBlendable) { |
| std::vector<float> srcColor = {1.0f}; |
| std::vector<float> clearColor = {0.0f, 0.0f, 1.0f, 1.0f}; |
| RunBlendTest(wgpu::TextureFormat::R8Snorm, srcColor, clearColor); |
| } |
| |
| // Test that rg8snorm format is blendable when 'texture-formats-tier1' is enabled. |
| TEST_P(BlendableFormatsTest, RG8SnormBlendable) { |
| std::vector<float> srcColor = {1.0f, -0.5f}; |
| std::vector<float> clearColor = {0.0f, 0.0f, 1.0f, 1.0f}; |
| RunBlendTest(wgpu::TextureFormat::RG8Snorm, srcColor, clearColor); |
| } |
| |
| // Test that rgba8snorm format is blendable when 'texture-formats-tier1' is enabled. |
| TEST_P(BlendableFormatsTest, RGBA8SnormBlendable) { |
| std::vector<float> srcColor = {1.0f, -0.5f, 0.0f, 0.5f}; |
| std::vector<float> clearColor = {0.0f, 0.0f, 1.0f, 1.0f}; |
| RunBlendTest(wgpu::TextureFormat::RGBA8Snorm, srcColor, clearColor); |
| } |
| |
| // Test that r16unorm format is blendable when 'texture-formats-tier1' is enabled. |
| TEST_P(BlendableFormatsTest, R16UnormBlendable) { |
| std::vector<float> srcColor = {1.0f}; |
| std::vector<float> clearColor = {0.0f, 0.0f, 1.0f, 1.0f}; |
| RunBlendTest(wgpu::TextureFormat::R16Unorm, srcColor, clearColor); |
| } |
| |
| // Test that r16snorm format is blendable when 'texture-formats-tier1' is enabled. |
| TEST_P(BlendableFormatsTest, R16SnormBlendable) { |
| std::vector<float> srcColor = {1.0f}; |
| std::vector<float> clearColor = {0.0f, 0.0f, 1.0f, 1.0f}; |
| RunBlendTest(wgpu::TextureFormat::R16Snorm, srcColor, clearColor); |
| } |
| |
| // Test that rg16unorm format is blendable when 'texture-formats-tier1' is enabled. |
| TEST_P(BlendableFormatsTest, RG16UnormBlendable) { |
| std::vector<float> srcColor = {1.0f, 0.0f}; |
| std::vector<float> clearColor = {0.0f, 0.0f, 1.0f, 1.0f}; |
| RunBlendTest(wgpu::TextureFormat::RG16Unorm, srcColor, clearColor); |
| } |
| |
| // Test that rg16snorm format is blendable when 'texture-formats-tier1' is enabled. |
| TEST_P(BlendableFormatsTest, RG16SnormBlendable) { |
| std::vector<float> srcColor = {1.0f, -0.5f}; |
| std::vector<float> clearColor = {0.0f, 0.0f, 1.0f, 1.0f}; |
| RunBlendTest(wgpu::TextureFormat::RG16Snorm, srcColor, clearColor); |
| } |
| |
| // Test that rgba16unorm format is blendable when 'texture-formats-tier1' is enabled. |
| TEST_P(BlendableFormatsTest, RGBA16UnormBlendable) { |
| std::vector<float> srcColor = {1.0f, 0.0f, 0.0f, 0.5f}; |
| std::vector<float> clearColor = {0.0f, 0.0f, 1.0f, 1.0f}; |
| RunBlendTest(wgpu::TextureFormat::RGBA16Unorm, srcColor, clearColor); |
| } |
| |
| // Test that rgba16snorm format is blendable when 'texture-formats-tier1' is enabled. |
| TEST_P(BlendableFormatsTest, RGBA16SnormBlendable) { |
| std::vector<float> srcColor = {1.0f, -0.5f, 0.0f, 0.5f}; |
| std::vector<float> clearColor = {0.0f, 0.0f, 1.0f, 1.0f}; |
| RunBlendTest(wgpu::TextureFormat::RGBA16Snorm, srcColor, clearColor); |
| } |
| |
| DAWN_INSTANTIATE_TEST(BlendableFormatsTest, |
| D3D11Backend(), |
| D3D12Backend(), |
| MetalBackend(), |
| OpenGLBackend(), |
| VulkanBackend(), |
| WebGPUBackend()); |
| |
| class MultisampleResolveFormatsTest : public TextureFormatsTier1Test { |
| protected: |
| static constexpr uint32_t kSize = 16; |
| static constexpr uint32_t kMultisampleCount = 4; |
| static constexpr uint32_t kSingleSampleCount = 1; |
| |
| void RunMultisampleResolveFormatsTest(wgpu::TextureFormat format, |
| const std::vector<float>& expectedDrawColorFloats) { |
| DAWN_TEST_UNSUPPORTED_IF(!device.HasFeature(wgpu::FeatureName::TextureFormatsTier1)); |
| |
| std::string fsCode = GenerateFragmentShader(expectedDrawColorFloats); |
| wgpu::ShaderModule shaderModule = |
| utils::CreateShaderModule(device, (GetFullScreenQuadVS() + fsCode).c_str()); |
| utils::ComboRenderPipelineDescriptor pipelineDesc; |
| pipelineDesc.vertex.module = shaderModule; |
| pipelineDesc.cFragment.module = shaderModule; |
| pipelineDesc.primitive.topology = wgpu::PrimitiveTopology::TriangleList; |
| pipelineDesc.cFragment.targetCount = 1; |
| pipelineDesc.cTargets[0].format = format; |
| pipelineDesc.multisample.count = kMultisampleCount; |
| pipelineDesc.cTargets[0].writeMask = wgpu::ColorWriteMask::All; |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pipelineDesc); |
| |
| wgpu::TextureDescriptor multisampleColorDesc; |
| multisampleColorDesc.usage = wgpu::TextureUsage::RenderAttachment; |
| multisampleColorDesc.dimension = wgpu::TextureDimension::e2D; |
| multisampleColorDesc.size = {kSize, kSize, 1}; |
| multisampleColorDesc.format = format; |
| multisampleColorDesc.sampleCount = kMultisampleCount; |
| |
| wgpu::Texture multisampleColorTexture = device.CreateTexture(&multisampleColorDesc); |
| wgpu::TextureView multisampleColorView = multisampleColorTexture.CreateView(); |
| |
| wgpu::TextureDescriptor resolveTargetDesc; |
| resolveTargetDesc.usage = |
| wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc; |
| resolveTargetDesc.dimension = wgpu::TextureDimension::e2D; |
| resolveTargetDesc.size = {kSize, kSize, 1}; |
| resolveTargetDesc.format = format; |
| resolveTargetDesc.sampleCount = kSingleSampleCount; |
| |
| wgpu::Texture resolveTargetTexture = device.CreateTexture(&resolveTargetDesc); |
| wgpu::TextureView resolveTargetView = resolveTargetTexture.CreateView(); |
| |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| utils::ComboRenderPassDescriptor renderPass; |
| renderPass.cColorAttachments[0].view = multisampleColorView; |
| renderPass.cColorAttachments[0].resolveTarget = resolveTargetView; |
| renderPass.cColorAttachments[0].loadOp = wgpu::LoadOp::Clear; |
| renderPass.cColorAttachments[0].storeOp = wgpu::StoreOp::Store; |
| renderPass.cColorAttachments[0].clearValue = {0.0f, 0.0f, 0.0f, 0.0f}; |
| renderPass.colorAttachmentCount = 1; |
| |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); |
| |
| pass.SetPipeline(pipeline); |
| pass.Draw(3); |
| pass.End(); |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| CalculateAndCheckPixels(resolveTargetTexture, format, expectedDrawColorFloats); |
| } |
| }; |
| |
| // Test that r8snorm format has multisample and resolve capability |
| // if 'texture-formats-tier1' is enabled. |
| TEST_P(MultisampleResolveFormatsTest, R8SnormMultisampleResolve) { |
| // TODO(crbug.com/468047551): Fails on Win11/NVIDIA GTX 1660. |
| DAWN_SUPPRESS_TEST_IF(IsWindows11() && IsNvidia() && IsD3D12() && IsBackendValidationEnabled()); |
| |
| std::vector<float> expectedDrawColor = {1.0f}; |
| RunMultisampleResolveFormatsTest(wgpu::TextureFormat::R8Snorm, expectedDrawColor); |
| } |
| |
| // Test that rg8snorm format has multisample and resolve capability |
| // if 'texture-formats-tier1' is enabled. |
| TEST_P(MultisampleResolveFormatsTest, RG8SnormMultisampleResolve) { |
| // TODO(crbug.com/468047551): Fails on Win11/NVIDIA GTX 1660. |
| DAWN_SUPPRESS_TEST_IF(IsWindows11() && IsNvidia() && IsD3D12() && IsBackendValidationEnabled()); |
| |
| std::vector<float> expectedDrawColor = {1.0f, -0.5f}; |
| RunMultisampleResolveFormatsTest(wgpu::TextureFormat::RG8Snorm, expectedDrawColor); |
| } |
| |
| // Test that rgba8snorm format has multisample and resolve capability |
| // if 'texture-formats-tier1' is enabled. |
| TEST_P(MultisampleResolveFormatsTest, RGBA8SnormMultisampleResolve) { |
| // TODO(crbug.com/468047551): Fails on Win11/NVIDIA GTX 1660. |
| DAWN_SUPPRESS_TEST_IF(IsWindows11() && IsNvidia() && IsD3D12() && IsBackendValidationEnabled()); |
| |
| std::vector<float> expectedDrawColor = {1.0f, -0.5f, -1.0f, 0.5f}; |
| RunMultisampleResolveFormatsTest(wgpu::TextureFormat::RGBA8Snorm, expectedDrawColor); |
| } |
| |
| DAWN_INSTANTIATE_TEST(MultisampleResolveFormatsTest, |
| D3D11Backend(), |
| D3D12Backend(), |
| MetalBackend(), |
| OpenGLBackend(), |
| VulkanBackend(), |
| WebGPUBackend()); |
| |
| class MultisampleFormatsTest : public TextureFormatsTier1Test { |
| protected: |
| static constexpr uint32_t kSize = 16; |
| static constexpr uint32_t kMultisampleCount = 4; |
| |
| void SetUp() override { |
| TextureFormatsTier1Test::SetUp(); |
| DAWN_SUPPRESS_TEST_IF(!IsCompatibilityMode()); |
| |
| // TODO(crbug.com/473899151): [Capture] multisampled. |
| DAWN_SUPPRESS_TEST_IF(IsCaptureReplayCheckingEnabled()); |
| } |
| |
| // Fragment shader for the second pass: reading multisample texture and writing verification |
| // color. This shader outputs red if all samples match the expected color, else green. |
| std::string GetMultisampleValidationFS(wgpu::TextureFormat format) { |
| std::ostringstream fsCodeStream; |
| std::string componentType = "f32"; |
| std::string textureType = "texture_multisampled_2d<f32>"; |
| |
| fsCodeStream << R"( |
| @group(0) @binding(0) var myMultisampleTexture: )" |
| << textureType << R"(; |
| @group(0) @binding(1) var<uniform> expectedColorUniform: vec4<f32>; |
| |
| @fragment |
| fn fs_main(@builtin(position) fragCoord: vec4<f32>) -> @location(0) vec4<f32> { |
| let texelCoord = vec2<i32>(fragCoord.xy); |
| let numSamples = textureNumSamples(myMultisampleTexture); |
| |
| var allSamplesMatch = true; |
| // Epsilon for floating point comparison of normalized values |
| // A small epsilon like 1/65535 or 1/32767 is appropriate for 16-bit formats. |
| // We will use a slightly larger one to account for potential precision differences. |
| let epsilon = 0.005; |
| |
| for (var i: u32 = 0; i < numSamples; i = i + 1) { |
| let sampleColor = textureLoad(myMultisampleTexture, texelCoord, i); |
| if (abs(sampleColor.r - expectedColorUniform.r) > epsilon || |
| abs(sampleColor.g - expectedColorUniform.g) > epsilon || |
| abs(sampleColor.b - expectedColorUniform.b) > epsilon || |
| abs(sampleColor.a - expectedColorUniform.a) > epsilon) { |
| allSamplesMatch = false; |
| break; |
| } |
| } |
| |
| if (allSamplesMatch) { |
| return vec4<f32>(1.0, 0.0, 0.0, 1.0); // Output Red if all samples match |
| } else { |
| return vec4<f32>(0.0, 1.0, 0.0, 1.0); // Output Green if mismatch |
| } |
| } |
| )"; |
| return fsCodeStream.str(); |
| } |
| |
| // This function tests multisampled texture functionality in WebGPU. It renders |
| // a color to a multisampled texture, then samples it and validates the color |
| // to ensure correct multisampling and format handling. |
| void RunMultisampleFormatsTest(wgpu::TextureFormat format, |
| std::vector<float> expectedDrawColorFloats) { |
| DAWN_TEST_UNSUPPORTED_IF(!device.HasFeature(wgpu::FeatureName::TextureFormatsTier1)); |
| |
| // Create a multisampled texture that will be drawn to. |
| wgpu::TextureDescriptor multisampleColorDesc; |
| multisampleColorDesc.usage = |
| wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding; |
| multisampleColorDesc.dimension = wgpu::TextureDimension::e2D; |
| multisampleColorDesc.size = {kSize, kSize, 1}; |
| multisampleColorDesc.format = format; |
| multisampleColorDesc.sampleCount = kMultisampleCount; |
| |
| wgpu::Texture multisampleColorTexture = device.CreateTexture(&multisampleColorDesc); |
| wgpu::TextureView multisampleColorView = multisampleColorTexture.CreateView({}); |
| |
| // This pass draws a full-screen quad with the expected color to the multisampled texture. |
| wgpu::CommandBuffer drawCommands = |
| RunDrawPass(multisampleColorView, format, expectedDrawColorFloats, |
| {/* nextInChain */ nullptr, kMultisampleCount}); |
| queue.Submit(1, &drawCommands); |
| |
| // Create a single-sampled texture to render the validation result to. |
| // This will be checked by EXPECT_PIXEL_RGBA8_EQ. |
| wgpu::TextureDescriptor validationTextureDesc; |
| validationTextureDesc.usage = |
| wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc; |
| validationTextureDesc.dimension = wgpu::TextureDimension::e2D; |
| validationTextureDesc.size = {kSize, kSize, 1}; |
| validationTextureDesc.format = wgpu::TextureFormat::RGBA8Unorm; |
| validationTextureDesc.sampleCount = 1; |
| |
| wgpu::Texture validationTexture = device.CreateTexture(&validationTextureDesc); |
| wgpu::TextureView validationTextureView = validationTexture.CreateView({}); |
| |
| std::string validationFsCode = GetMultisampleValidationFS(format); |
| wgpu::ShaderModule validationShaderModule = |
| utils::CreateShaderModule(device, (GetFullScreenQuadVS() + validationFsCode).c_str()); |
| |
| // Pad the expectedDrawColorFloats to ensure it's vec4 for the uniform buffer. |
| while (expectedDrawColorFloats.size() < 3) { |
| expectedDrawColorFloats.push_back(0.0f); |
| } |
| if (expectedDrawColorFloats.size() < 4) { |
| expectedDrawColorFloats.push_back(1.0f); |
| } |
| |
| // Create uniform buffer to pass expected color to the validation shader. |
| wgpu::BufferDescriptor uniformBufferDesc; |
| uniformBufferDesc.size = 16; |
| uniformBufferDesc.usage = wgpu::BufferUsage::Uniform | wgpu::BufferUsage::CopyDst; |
| wgpu::Buffer expectedColorBuffer = device.CreateBuffer(&uniformBufferDesc); |
| queue.WriteBuffer(expectedColorBuffer, 0, expectedDrawColorFloats.data(), 16); |
| |
| wgpu::BindGroupLayout validationBindGroupLayout = utils::MakeBindGroupLayout( |
| device, {{0, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::UnfilterableFloat, |
| wgpu::TextureViewDimension::e2D, true}, |
| {1, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Uniform}}); |
| |
| wgpu::BindGroup validationBindGroup = |
| utils::MakeBindGroup(device, validationBindGroupLayout, |
| {{0, multisampleColorView}, {1, expectedColorBuffer}}); |
| |
| // Pipeline for the second pass, which samples the multisampled texture. |
| utils::ComboRenderPipelineDescriptor validationPipelineDesc; |
| validationPipelineDesc.vertex.module = validationShaderModule; |
| validationPipelineDesc.cFragment.module = validationShaderModule; |
| validationPipelineDesc.primitive.topology = wgpu::PrimitiveTopology::TriangleList; |
| validationPipelineDesc.cFragment.targetCount = 1; |
| validationPipelineDesc.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm; |
| validationPipelineDesc.multisample.count = 1; |
| validationPipelineDesc.layout = |
| utils::MakePipelineLayout(device, {validationBindGroupLayout}); |
| validationPipelineDesc.cTargets[0].writeMask = wgpu::ColorWriteMask::All; |
| |
| wgpu::RenderPipeline validationPipeline = |
| device.CreateRenderPipeline(&validationPipelineDesc); |
| |
| // This pass draws to the validationTexture, sampling the content from |
| // multisampleColorTexture. |
| wgpu::CommandEncoder encoder2 = device.CreateCommandEncoder(); |
| utils::ComboRenderPassDescriptor renderPass2; |
| renderPass2.cColorAttachments[0].view = validationTextureView; |
| renderPass2.cColorAttachments[0].loadOp = wgpu::LoadOp::Clear; |
| renderPass2.cColorAttachments[0].storeOp = wgpu::StoreOp::Store; |
| renderPass2.cColorAttachments[0].clearValue = {0.0f, 0.0f, 0.0f, 0.0f}; |
| renderPass2.colorAttachmentCount = 1; |
| |
| wgpu::RenderPassEncoder pass2 = encoder2.BeginRenderPass(&renderPass2); |
| pass2.SetPipeline(validationPipeline); |
| pass2.SetBindGroup(0, validationBindGroup); |
| pass2.Draw(3); |
| pass2.End(); |
| |
| wgpu::CommandBuffer commands2 = encoder2.Finish(); |
| queue.Submit(1, &commands2); |
| |
| // We expect red, confirming the multisampled texture was correctly drawn to and sampled. |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kRed, validationTexture, 0, 0); |
| } |
| }; |
| |
| // Test that r16unorm format has multisample capability if 'texture-formats-tier1' is enabled. |
| TEST_P(MultisampleFormatsTest, R16UnormMultisample) { |
| RunMultisampleFormatsTest(wgpu::TextureFormat::R16Unorm, {0.5f}); |
| } |
| |
| // Test that r16snorm format has multisample capability if 'texture-formats-tier1' is enabled. |
| TEST_P(MultisampleFormatsTest, R16SnormMultisample) { |
| RunMultisampleFormatsTest(wgpu::TextureFormat::R16Snorm, {0.25f}); |
| } |
| |
| // Test that rg16unorm format has multisample capability if 'texture-formats-tier1' is enabled. |
| TEST_P(MultisampleFormatsTest, RG16UnormMultisample) { |
| RunMultisampleFormatsTest(wgpu::TextureFormat::RG16Unorm, {0.25f, 0.75f}); |
| } |
| |
| // Test that rg16snorm format has multisample capability if 'texture-formats-tier1' is enabled. |
| TEST_P(MultisampleFormatsTest, RG16SnormMultisample) { |
| RunMultisampleFormatsTest(wgpu::TextureFormat::RG16Snorm, {-0.5f, 0.5f}); |
| } |
| |
| // Test that rgba16unorm format has multisample capability if 'texture-formats-tier1' is enabled. |
| TEST_P(MultisampleFormatsTest, RGBA16UnormMultisample) { |
| RunMultisampleFormatsTest(wgpu::TextureFormat::RGBA16Unorm, {0.1f, 0.4f, 0.7f, 1.0f}); |
| } |
| |
| // Test that rgba16snorm format has multisample capability if 'texture-formats-tier1' is enabled. |
| TEST_P(MultisampleFormatsTest, RGBA16SnormMultisample) { |
| RunMultisampleFormatsTest(wgpu::TextureFormat::RGBA16Snorm, {-0.8f, -0.2f, 0.3f, 0.9f}); |
| } |
| |
| DAWN_INSTANTIATE_TEST(MultisampleFormatsTest, |
| D3D11Backend(), |
| D3D12Backend(), |
| MetalBackend(), |
| OpenGLBackend(), |
| VulkanBackend(), |
| WebGPUBackend()); |
| |
| } // anonymous namespace |
| } // namespace dawn |