// Copyright 2021 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 <optional>
#include <vector>

#include "src/dawn/tests/DawnTest.h"
#include "src/dawn/utils/ComboRenderPipelineDescriptor.h"
#include "src/dawn/utils/TextureUtils.h"
#include "src/dawn/utils/WGPUHelpers.h"

namespace dawn {
namespace {

constexpr static uint32_t kSize = 4;

using TextureFormat = wgpu::TextureFormat;
DAWN_TEST_PARAM_STRUCT(ReadOnlyDepthStencilAttachmentTestsParams, TextureFormat);

class ReadOnlyDepthStencilAttachmentTests
    : public DawnTestWithParams<ReadOnlyDepthStencilAttachmentTestsParams> {
  protected:
    void SetUp() override {
        DawnTestWithParams<ReadOnlyDepthStencilAttachmentTestsParams>::SetUp();
        DAWN_TEST_UNSUPPORTED_IF(!mIsFormatSupported);
    }

    std::vector<wgpu::FeatureName> GetRequiredFeatures() override {
        switch (GetParam().mTextureFormat) {
            case wgpu::TextureFormat::Depth32FloatStencil8:
                if (SupportsFeatures({wgpu::FeatureName::Depth32FloatStencil8})) {
                    mIsFormatSupported = true;
                    return {wgpu::FeatureName::Depth32FloatStencil8};
                }

                return {};
            default:
                mIsFormatSupported = true;
                return {};
        }
    }

    struct TestSpec {
        wgpu::TextureAspect readonlyAspects;
        std::optional<wgpu::TextureAspect> sampledAspect = std::nullopt;

        wgpu::CompareFunction depthCompare = wgpu::CompareFunction::Always;
        wgpu::CompareFunction stencilCompare = wgpu::CompareFunction::Always;
        wgpu::OptionalBool depthWriteEnabled = wgpu::OptionalBool::False;
        bool stencilWriteEnabled = false;

        float depthClearValue = 0.0;
        uint32_t stencilClearValue = 0;
        uint32_t stencilRef = 0;

        wgpu::Texture depthStencilTexture;
    };

    wgpu::RenderPipeline CreateRenderPipeline(wgpu::TextureFormat format,
                                              const TestSpec& spec) const {
        utils::ComboRenderPipelineDescriptor pipelineDescriptor;

        // Draw a rectangle via two triangles. The depth value of the top of the rectangle is 0.4.
        // The depth value of the bottom is 0.0. The depth value gradually change from 0.4 to 0.0
        // from the top to the bottom. The top part will compare with the depth values and fail to
        // pass the depth test. The bottom part will compare with the depth values in depth buffer
        // and pass the depth test, and sample from the depth buffer in fragment shader in the same
        // pipeline.
        pipelineDescriptor.vertex.module = utils::CreateShaderModule(device, R"(
            @vertex
            fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4f {
                var pos = array(
                    vec3f(-1.0,  1.0, 0.4),
                    vec3f(-1.0, -1.0, 0.0),
                    vec3f( 1.0,  1.0, 0.4),
                    vec3f( 1.0,  1.0, 0.4),
                    vec3f(-1.0, -1.0, 0.0),
                    vec3f( 1.0, -1.0, 0.0));
                return vec4f(pos[VertexIndex], 1.0);
            })");

        wgpu::BindGroupLayout bgl;
        if (!spec.sampledAspect.has_value()) {
            // Draw a solid blue into color buffer if not sample from depth/stencil attachment.
            pipelineDescriptor.cFragment.module = utils::CreateShaderModule(device, R"(
                @fragment fn main() -> @location(0) vec4f {
                    return vec4f(0.0, 0.0, 1.0, 0.0);
                })");
            bgl = utils::MakeBindGroupLayout(device, {});
        } else if (spec.sampledAspect == wgpu::TextureAspect::DepthOnly) {
            // Sample from depth attachment and draw that sampled texel into color buffer.
            if (IsCompatibilityMode()) {
                // Can not use texture_depth_xx with non-comparison sampler in compat mode.
                pipelineDescriptor.cFragment.module = utils::CreateShaderModule(device, R"(
                        @group(0) @binding(0) var samp : sampler;
                        @group(0) @binding(1) var tex : texture_2d<f32>;

                        @fragment
                        fn main(@builtin(position) FragCoord : vec4f) -> @location(0) vec4f {
                            return vec4f(textureSample(tex, samp, FragCoord.xy).r, 0.0, 0.0, 0.0);
                        })");
                bgl = utils::MakeBindGroupLayout(
                    device,
                    {
                        {0, wgpu::ShaderStage::Fragment, wgpu::SamplerBindingType::NonFiltering},
                        {1, wgpu::ShaderStage::Fragment,
                         wgpu::TextureSampleType::UnfilterableFloat},
                    });
            } else {
                pipelineDescriptor.cFragment.module = utils::CreateShaderModule(device, R"(
                        @group(0) @binding(0) var samp : sampler;
                        @group(0) @binding(1) var tex : texture_depth_2d;

                        @fragment
                        fn main(@builtin(position) FragCoord : vec4f) -> @location(0) vec4f {
                            return vec4f(textureSample(tex, samp, FragCoord.xy), 0.0, 0.0, 0.0);
                        })");
                bgl = utils::MakeBindGroupLayout(
                    device,
                    {
                        {0, wgpu::ShaderStage::Fragment, wgpu::SamplerBindingType::NonFiltering},
                        {1, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Depth},
                    });
            }
        } else {
            DAWN_ASSERT(spec.sampledAspect == wgpu::TextureAspect::StencilOnly);
            // Sample from stencil attachment and draw that sampled texel into color buffer.
            pipelineDescriptor.cFragment.module = utils::CreateShaderModule(device, R"(
                    @group(0) @binding(0) var samp : sampler;
                    @group(0) @binding(1) var tex : texture_2d<u32>;

                    @fragment
                    fn main(@builtin(position) FragCoord : vec4f) -> @location(0) vec4f {
                        _ = samp;
                        var texel = textureLoad(tex, vec2i(FragCoord.xy), 0);
                        return vec4f(f32(texel[0]) / 255.0, 0.0, 0.0, 0.0);
                    })");
            bgl = utils::MakeBindGroupLayout(
                device,
                {
                    {0, wgpu::ShaderStage::Fragment, wgpu::SamplerBindingType::NonFiltering},
                    {1, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Uint},
                });
        }

        wgpu::DepthStencilState* depthStencil = pipelineDescriptor.EnableDepthStencil(format);
        depthStencil->depthCompare = spec.depthCompare;
        depthStencil->depthWriteEnabled = spec.depthWriteEnabled;
        depthStencil->stencilFront.compare = spec.stencilCompare;
        if (spec.stencilWriteEnabled) {
            depthStencil->stencilFront.passOp = wgpu::StencilOperation::Replace;
        }

        wgpu::PipelineLayoutDescriptor plDescriptor;
        plDescriptor.bindGroupLayoutCount = 1;
        plDescriptor.bindGroupLayouts = &bgl;
        pipelineDescriptor.layout = device.CreatePipelineLayout(&plDescriptor);

        return device.CreateRenderPipeline(&pipelineDescriptor);
    }

    struct RenderResult {
        wgpu::Texture color;
        wgpu::Texture depthStencil;
    };
    RenderResult DoRender(const TestSpec& spec) const {
        wgpu::TextureFormat testFormat = GetParam().mTextureFormat;

        // Create or reuse the test textures.
        wgpu::Texture depthStencilTexture = spec.depthStencilTexture;
        if (!depthStencilTexture) {
            wgpu::TextureDescriptor dsTextureDesc = {};
            dsTextureDesc.size = {kSize, kSize, 1};
            dsTextureDesc.format = testFormat;
            dsTextureDesc.usage =
                wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding;
            depthStencilTexture = device.CreateTexture(&dsTextureDesc);
        }

        wgpu::TextureDescriptor colorTextureDesc = {};
        colorTextureDesc.size = {kSize, kSize, 1};
        colorTextureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
        colorTextureDesc.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc;
        wgpu::Texture colorTexture = device.CreateTexture(&colorTextureDesc);

        wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();

        // Do a first render pass that writes the initial values to the aspects that will be
        // readonly.
        if (!spec.depthStencilTexture) {
            utils::ComboRenderPassDescriptor passDescriptorInit({},
                                                                depthStencilTexture.CreateView());
            passDescriptorInit.UnsetDepthStencilLoadStoreOpsForFormat(testFormat);
            passDescriptorInit.cDepthStencilAttachmentInfo.depthClearValue = spec.depthClearValue;
            passDescriptorInit.cDepthStencilAttachmentInfo.stencilClearValue =
                spec.stencilClearValue;

            wgpu::RenderPassEncoder passInit = commandEncoder.BeginRenderPass(&passDescriptorInit);
            passInit.End();
        }

        // Do the render pass with the readonly attachment, that will potentially use the pipeline
        // to read and/or write attachments.
        utils::ComboRenderPassDescriptor passDescriptor({colorTexture.CreateView()},
                                                        depthStencilTexture.CreateView());
        passDescriptor.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Load;
        passDescriptor.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Load;
        passDescriptor.UnsetDepthStencilLoadStoreOpsForFormat(testFormat);

        if (spec.readonlyAspects != wgpu::TextureAspect::StencilOnly) {
            passDescriptor.cDepthStencilAttachmentInfo.depthReadOnly = true;
            passDescriptor.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Undefined;
            passDescriptor.cDepthStencilAttachmentInfo.depthStoreOp = wgpu::StoreOp::Undefined;
        }
        if (spec.readonlyAspects != wgpu::TextureAspect::DepthOnly) {
            passDescriptor.cDepthStencilAttachmentInfo.stencilReadOnly = true;
            passDescriptor.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Undefined;
            passDescriptor.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Undefined;
        }

        // Create a render pass with readonly depth/stencil attachment. The attachment has already
        // been initialized. The pipeline in this render pass will sample from the attachment. TODO
        // The pipeline will read from the attachment to do depth/stencil test too.
        wgpu::RenderPassEncoder pass = commandEncoder.BeginRenderPass(&passDescriptor);
        wgpu::RenderPipeline pipeline = CreateRenderPipeline(testFormat, spec);
        pass.SetPipeline(pipeline);
        pass.SetStencilReference(spec.stencilRef);

        // Bind the bindgroup is needed.
        if (spec.sampledAspect.has_value()) {
            wgpu::TextureViewDescriptor viewDesc = {};
            viewDesc.aspect = spec.sampledAspect.value();
            wgpu::TextureView view = depthStencilTexture.CreateView(&viewDesc);

            wgpu::BindGroup bindGroup = utils::MakeBindGroup(
                device, pipeline.GetBindGroupLayout(0), {{0, device.CreateSampler()}, {1, view}});
            pass.SetBindGroup(0, bindGroup);
        } else {
            wgpu::BindGroup bindGroup =
                utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), {});
            pass.SetBindGroup(0, bindGroup);
        }

        pass.Draw(6);
        pass.End();

        wgpu::CommandBuffer commands = commandEncoder.Finish();
        queue.Submit(1, &commands);

        return {colorTexture, depthStencilTexture};
    }

    void CheckFullColor(wgpu::Texture color, utils::RGBA8 fullColor) {
        std::vector<utils::RGBA8> expected(kSize * kSize, fullColor);
        EXPECT_TEXTURE_EQ(expected.data(), color, {0, 0}, {kSize, kSize});
    }

    void CheckTopBottomColor(wgpu::Texture color, utils::RGBA8 topColor, utils::RGBA8 bottomColor) {
        std::vector<utils::RGBA8> expectedTop(kSize * kSize / 2, topColor);
        EXPECT_TEXTURE_EQ(expectedTop.data(), color, {0, 0}, {kSize, kSize / 2});
        std::vector<utils::RGBA8> expectedBottom(kSize * kSize / 2, bottomColor);
        EXPECT_TEXTURE_EQ(expectedBottom.data(), color, {0, kSize / 2}, {kSize, kSize / 2});
    }

  private:
    bool mIsFormatSupported = false;
};

class ReadOnlyDepthAttachmentTests : public ReadOnlyDepthStencilAttachmentTests {};

TEST_P(ReadOnlyDepthAttachmentTests, SampleFromAttachment) {
    TestSpec spec;
    spec.readonlyAspects = wgpu::TextureAspect::DepthOnly;
    spec.sampledAspect = wgpu::TextureAspect::DepthOnly;
    spec.depthCompare = wgpu::CompareFunction::LessEqual;
    spec.depthClearValue = 0.2;
    auto render = DoRender(spec);

    // The top part is not rendered by the pipeline. Its color is the default clear color for
    // color attachment.
    // The bottom part is rendered, whose red channel is sampled from depth attachment, which
    // is initialized into 0.2.
    CheckTopBottomColor(render.color, {0, 0, 0, 0}, {static_cast<uint8_t>(0.2 * 255), 0, 0, 0});
}

TEST_P(ReadOnlyDepthAttachmentTests, NotSampleFromAttachment) {
    TestSpec spec;
    spec.readonlyAspects = wgpu::TextureAspect::DepthOnly;
    spec.depthCompare = wgpu::CompareFunction::LessEqual;
    spec.depthClearValue = 0.2;
    auto render = DoRender(spec);

    // The top part is not rendered by the pipeline. Its color is the default clear color for
    // color attachment.
    // The bottom part is rendered. Its color is set to blue.
    CheckTopBottomColor(render.color, {0, 0, 0, 0}, {0, 0, 255, 0});
}

// Regression test for crbug.com/dawn/1512 where having aspectReadOnly for an unused aspect of a
// depth-stencil texture would cause the attachment to be considered read-only, causing layout
// mismatch issues.
TEST_P(ReadOnlyDepthAttachmentTests, UnusedAspectWithReadOnly) {
    wgpu::TextureFormat format = GetParam().mTextureFormat;
    wgpu::TextureDescriptor tDesc;
    tDesc.size = {1, 1};
    tDesc.format = format;
    tDesc.usage = wgpu::TextureUsage::RenderAttachment;
    wgpu::Texture depthStencilTexture = device.CreateTexture(&tDesc);

    utils::ComboRenderPassDescriptor passDescriptor({}, depthStencilTexture.CreateView());
    if (utils::IsStencilOnlyFormat(format)) {
        passDescriptor.cDepthStencilAttachmentInfo.depthReadOnly = true;
        passDescriptor.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Undefined;
        passDescriptor.cDepthStencilAttachmentInfo.depthStoreOp = wgpu::StoreOp::Undefined;
    } else {
        passDescriptor.cDepthStencilAttachmentInfo.depthReadOnly = false;
    }
    if (utils::IsDepthOnlyFormat(format)) {
        passDescriptor.cDepthStencilAttachmentInfo.stencilReadOnly = true;
        passDescriptor.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Undefined;
        passDescriptor.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Undefined;
    } else {
        passDescriptor.cDepthStencilAttachmentInfo.stencilReadOnly = false;
    }

    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
    wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor);
    pass.End();
    wgpu::CommandBuffer commands = encoder.Finish();

    queue.Submit(1, &commands);
}

class ReadOnlyStencilAttachmentTests : public ReadOnlyDepthStencilAttachmentTests {};

TEST_P(ReadOnlyStencilAttachmentTests, SampleFromAttachment) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    // stencilRefValue < stencilValue (stencilInitValue), so stencil test passes. The pipeline
    // samples from stencil buffer and writes into color buffer.
    {
        TestSpec spec;
        spec.readonlyAspects = wgpu::TextureAspect::StencilOnly;
        spec.sampledAspect = wgpu::TextureAspect::StencilOnly;
        spec.stencilCompare = wgpu::CompareFunction::LessEqual;
        spec.stencilClearValue = 3;
        spec.stencilRef = 2;
        auto render = DoRender(spec);
        CheckFullColor(render.color, {3, 0, 0, 0});
    }

    // stencilRefValue > stencilValue (stencilInitValue), so stencil test fails. The pipeline
    // doesn't change color buffer. Sampled data from stencil buffer is discarded.
    {
        TestSpec spec;
        spec.readonlyAspects = wgpu::TextureAspect::StencilOnly;
        spec.sampledAspect = wgpu::TextureAspect::StencilOnly;
        spec.stencilCompare = wgpu::CompareFunction::LessEqual;
        spec.stencilClearValue = 1;
        spec.stencilRef = 2;
        auto render = DoRender(spec);
        CheckFullColor(render.color, {0, 0, 0, 0});
    }
}

TEST_P(ReadOnlyStencilAttachmentTests, NotSampleFromAttachment) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    // stencilRefValue < stencilValue (stencilInitValue), so stencil test passes. The pipeline
    // draw solid blue into color buffer.
    {
        TestSpec spec;
        spec.readonlyAspects = wgpu::TextureAspect::StencilOnly;
        spec.stencilCompare = wgpu::CompareFunction::LessEqual;
        spec.stencilClearValue = 3;
        spec.stencilRef = 2;
        auto render = DoRender(spec);
        CheckFullColor(render.color, {0, 0, 255, 0});
    }

    // stencilRefValue > stencilValue (stencilInitValue), so stencil test fails. The pipeline
    // doesn't change color buffer. drawing data is discarded.
    {
        TestSpec spec;
        spec.readonlyAspects = wgpu::TextureAspect::StencilOnly;
        spec.stencilCompare = wgpu::CompareFunction::LessEqual;
        spec.stencilClearValue = 1;
        spec.stencilRef = 2;
        auto render = DoRender(spec);
        CheckFullColor(render.color, {0, 0, 0, 0});
    }
}

class ReadOnlyDepthAndStencilAttachmentTests : public ReadOnlyDepthStencilAttachmentTests {
  protected:
    void SetUp() override {
        ReadOnlyDepthStencilAttachmentTests::SetUp();

        // TODO(crbug.com/473870505): [Capture] support depth/stencil and multi-planar textures.
        DAWN_SUPPRESS_TEST_IF(IsCaptureReplayCheckingEnabled());
    }
};

// Test that using stencilReadOnly while modifying the depth aspect works.
TEST_P(ReadOnlyDepthAndStencilAttachmentTests, ModifyDepthSampleStencil) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    // Stencil test is always true but the depth test passes only for the
    TestSpec spec1;
    spec1.readonlyAspects = wgpu::TextureAspect::StencilOnly;
    spec1.sampledAspect = wgpu::TextureAspect::StencilOnly;
    spec1.stencilClearValue = 42;
    spec1.depthClearValue = 0.2;
    spec1.depthCompare = wgpu::CompareFunction::LessEqual;
    spec1.depthWriteEnabled = wgpu::OptionalBool::True;
    auto render1 = DoRender(spec1);

    // Stencil was read successfully, but only in the bottom part.
    CheckTopBottomColor(render1.color, {0, 0, 0, 0}, {42, 0, 0, 0});

    // Check that the depth was written by setting depthCompare equal, and rendering a solid
    // blue color. The depth was only written on the bottom half due to the depth test in the
    // first render.
    TestSpec spec2;
    spec2.readonlyAspects = wgpu::TextureAspect::StencilOnly;
    spec2.depthStencilTexture = render1.depthStencil;
    spec2.depthCompare = wgpu::CompareFunction::Equal;
    auto render2 = DoRender(spec2);
    CheckTopBottomColor(render2.color, {0, 0, 0, 0}, {0, 0, 255, 0});
}

// Test that using depthReadOnly while modifying the stencil aspect works.
TEST_P(ReadOnlyDepthAndStencilAttachmentTests, SampleDepthModifyStencil) {
    // Depth/stencil tests are true, the depth is correctly sampled from the depthClearValue.
    // The stencil is written to the value of the stencil ref.
    TestSpec spec1;
    spec1.readonlyAspects = wgpu::TextureAspect::DepthOnly;
    spec1.sampledAspect = wgpu::TextureAspect::DepthOnly;
    spec1.depthClearValue = 0.2;
    spec1.stencilWriteEnabled = true;
    spec1.stencilRef = 42;
    spec1.stencilClearValue = 0;
    auto render1 = DoRender(spec1);
    CheckFullColor(render1.color, {static_cast<uint8_t>(0.2 * 255), 0, 0, 0});

    // The stencil test checks that the stencil ref matches what's in the stencil buffer
    // so that we know it was correctly written. The whole quad should be drawn.
    TestSpec spec2;
    spec2.readonlyAspects = wgpu::TextureAspect::DepthOnly;
    spec2.depthStencilTexture = render1.depthStencil;
    spec2.stencilCompare = wgpu::CompareFunction::Equal;
    spec2.stencilRef = 42;
    auto render2 = DoRender(spec2);
    CheckFullColor(render2.color, {0, 0, 255, 0});
}

// Test sampling depth with both the depth and stencil readonly.
TEST_P(ReadOnlyDepthAndStencilAttachmentTests, BothReadOnlySampleDepth) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    // Sample the depth while using both depth an stencil testing.

    // First render: depth test passes only for the bottom half, stencil passes.
    TestSpec spec;
    spec.readonlyAspects = wgpu::TextureAspect::All;
    spec.sampledAspect = wgpu::TextureAspect::DepthOnly;
    spec.depthCompare = wgpu::CompareFunction::LessEqual;
    spec.depthClearValue = 0.2;
    spec.stencilRef = 42;
    spec.stencilClearValue = 43;
    spec.stencilCompare = wgpu::CompareFunction::LessEqual;
    auto render1 = DoRender(spec);
    CheckTopBottomColor(render1.color, {0, 0, 0, 0}, {static_cast<uint8_t>(0.2 * 255), 0, 0, 0});

    // Second render: stencil test fails.
    spec.stencilClearValue = 41;
    auto render2 = DoRender(spec);
    CheckFullColor(render2.color, {0, 0, 0, 0});
}

// Test sampling stencil with both the depth and stencil readonly.
TEST_P(ReadOnlyDepthAndStencilAttachmentTests, BothReadOnlySampleStencil) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    // Sample the stencil while using both depth an stencil testing.

    // First render: depth test passes only for the bottom half, stencil passes.
    TestSpec spec;
    spec.readonlyAspects = wgpu::TextureAspect::All;
    spec.sampledAspect = wgpu::TextureAspect::StencilOnly;
    spec.depthCompare = wgpu::CompareFunction::LessEqual;
    spec.depthClearValue = 0.2;
    spec.stencilRef = 42;
    spec.stencilClearValue = 43;
    spec.stencilCompare = wgpu::CompareFunction::LessEqual;
    auto render1 = DoRender(spec);
    CheckTopBottomColor(render1.color, {0, 0, 0, 0}, {43, 0, 0, 0});

    // Second render: stencil test fails.
    spec.stencilClearValue = 41;
    auto render2 = DoRender(spec);
    CheckFullColor(render2.color, {0, 0, 0, 0});
}

DAWN_INSTANTIATE_TEST_P(
    ReadOnlyDepthAttachmentTests,
    {D3D11Backend(), D3D12Backend(), D3D12Backend({}, {"use_d3d12_render_pass"}), MetalBackend(),
     OpenGLBackend(), OpenGLESBackend(), VulkanBackend({"vulkan_use_dynamic_rendering"}, {}),
     VulkanBackend({"vulkan_use_create_render_pass_2"}, {"vulkan_use_dynamic_rendering"}),
     VulkanBackend({}, {"vulkan_use_create_render_pass_2", "vulkan_use_dynamic_rendering"}),
     WebGPUBackend()},
    std::vector<wgpu::TextureFormat>(utils::kDepthFormats.begin(), utils::kDepthFormats.end()));
DAWN_INSTANTIATE_TEST_P(
    ReadOnlyStencilAttachmentTests,
    {D3D11Backend(), D3D12Backend(), D3D12Backend({}, {"use_d3d12_render_pass"}), MetalBackend(),
     OpenGLBackend(), OpenGLESBackend(), VulkanBackend({"vulkan_use_dynamic_rendering"}, {}),
     VulkanBackend({"vulkan_use_create_render_pass_2"}, {"vulkan_use_dynamic_rendering"}),
     VulkanBackend({}, {"vulkan_use_create_render_pass_2", "vulkan_use_dynamic_rendering"}),
     WebGPUBackend()},
    std::vector<wgpu::TextureFormat>(utils::kStencilFormats.begin(), utils::kStencilFormats.end()));
DAWN_INSTANTIATE_TEST_P(
    ReadOnlyDepthAndStencilAttachmentTests,
    {D3D11Backend(), D3D12Backend(), D3D12Backend({}, {"use_d3d12_render_pass"}), MetalBackend(),
     OpenGLBackend(), OpenGLESBackend(), VulkanBackend({"vulkan_use_dynamic_rendering"}, {}),
     VulkanBackend({"vulkan_use_create_render_pass_2"}, {"vulkan_use_dynamic_rendering"}),
     VulkanBackend({}, {"vulkan_use_create_render_pass_2", "vulkan_use_dynamic_rendering"}),
     WebGPUBackend()},
    std::vector<wgpu::TextureFormat>(utils::kDepthAndStencilFormats.begin(),
                                     utils::kDepthAndStencilFormats.end()));

}  // anonymous namespace
}  // namespace dawn
