// 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 "src/dawn/tests/DawnTest.h"
#include "src/dawn/utils/ComboRenderPipelineDescriptor.h"
#include "src/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());

    // TODO(crbug.com/468047551): Fails on Win11/AMD RX 5500 XT.
    DAWN_SUPPRESS_TEST_IF(IsWindows11() && IsAMD() && 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());

    // TODO(crbug.com/468047551): Fails on Win11/AMD RX 5500 XT.
    DAWN_SUPPRESS_TEST_IF(IsWindows11() && IsAMD() && 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());

    // TODO(crbug.com/468047551): Fails on Win11/AMD RX 5500 XT.
    DAWN_SUPPRESS_TEST_IF(IsWindows11() && IsAMD() && 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
