// Copyright 2023 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
//    list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
//    contributors may be used to endorse or promote products derived from
//    this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include <algorithm>
#include <limits>
#include <sstream>
#include <string>
#include <vector>

#include "absl/strings/str_format.h"
#include "absl/strings/str_join.h"
#include "src/dawn/tests/unittests/validation/ValidationTest.h"
#include "src/dawn/utils/ComboRenderPipelineDescriptor.h"
#include "src/dawn/utils/WGPUHelpers.h"
#include "src/utils/compiler.h"

namespace dawn {
namespace {

class CompatValidationTest : public ValidationTest {
  protected:
    bool UseCompatibilityMode() const override { return true; }
};

TEST_F(CompatValidationTest, CanNotCreateCubeArrayTextureView) {
    wgpu::TextureDescriptor descriptor;
    descriptor.size = {1, 1, 6};
    descriptor.dimension = wgpu::TextureDimension::e2D;
    descriptor.format = wgpu::TextureFormat::RGBA8Unorm;
    descriptor.usage = wgpu::TextureUsage::TextureBinding;
    wgpu::Texture cubeTexture = device.CreateTexture(&descriptor);

    {
        wgpu::TextureViewDescriptor cubeViewDescriptor;
        cubeViewDescriptor.dimension = wgpu::TextureViewDimension::Cube;
        cubeViewDescriptor.format = wgpu::TextureFormat::RGBA8Unorm;

        cubeTexture.CreateView(&cubeViewDescriptor);
    }

    {
        wgpu::TextureViewDescriptor cubeArrayViewDescriptor;
        cubeArrayViewDescriptor.dimension = wgpu::TextureViewDimension::CubeArray;
        cubeArrayViewDescriptor.format = wgpu::TextureFormat::RGBA8Unorm;

        ASSERT_DEVICE_ERROR(cubeTexture.CreateView(&cubeArrayViewDescriptor));
    }

    cubeTexture.Destroy();
}

TEST_F(CompatValidationTest, CanNotCreateCubeArrayTexture) {
    wgpu::TextureDescriptor descriptor;
    descriptor.size = {1, 1, 6};
    descriptor.dimension = wgpu::TextureDimension::e2D;
    descriptor.format = wgpu::TextureFormat::RGBA8Unorm;
    descriptor.usage = wgpu::TextureUsage::TextureBinding;

    wgpu::TextureBindingViewDimension textureBindingViewDimensionDesc;
    textureBindingViewDimensionDesc.textureBindingViewDimension =
        wgpu::TextureViewDimension::CubeArray;
    descriptor.nextInChain = &textureBindingViewDimensionDesc;

    ASSERT_DEVICE_ERROR(device.CreateTexture(&descriptor));
}

TEST_F(CompatValidationTest, CanNotSpecifyAlternateCompatibleViewFormatRGBA8Unorm) {
    constexpr wgpu::TextureFormat viewFormat = wgpu::TextureFormat::RGBA8UnormSrgb;

    wgpu::TextureDescriptor descriptor;
    descriptor.size = {1, 1, 1};
    descriptor.dimension = wgpu::TextureDimension::e2D;
    descriptor.format = wgpu::TextureFormat::RGBA8Unorm;
    descriptor.usage = wgpu::TextureUsage::TextureBinding;
    descriptor.viewFormatCount = 1;
    descriptor.viewFormats = &viewFormat;
    wgpu::Texture texture;
    ASSERT_DEVICE_ERROR(texture = device.CreateTexture(&descriptor),
                        testing::HasSubstr("must match format"));
    texture.Destroy();
}

TEST_F(CompatValidationTest, CanNotSpecifyAlternateCompatibleViewFormatRGBA8UnormSrgb) {
    constexpr wgpu::TextureFormat viewFormat = wgpu::TextureFormat::RGBA8Unorm;

    wgpu::TextureDescriptor descriptor;
    descriptor.size = {1, 1, 1};
    descriptor.dimension = wgpu::TextureDimension::e2D;
    descriptor.format = wgpu::TextureFormat::RGBA8UnormSrgb;
    descriptor.usage = wgpu::TextureUsage::TextureBinding;
    descriptor.viewFormatCount = 1;
    descriptor.viewFormats = &viewFormat;
    wgpu::Texture texture;
    ASSERT_DEVICE_ERROR(texture = device.CreateTexture(&descriptor),
                        testing::HasSubstr("must match format"));
    texture.Destroy();
}

TEST_F(CompatValidationTest, CanNotCreatePipelineWithDifferentPerTargetBlendStateOrWriteMask) {
    wgpu::ShaderModule module = utils::CreateShaderModule(device, R"(
        @vertex fn vs() -> @builtin(position) vec4f {
            return vec4f(0);
        }

        struct FragmentOut {
            @location(0) fragColor0 : vec4f,
            @location(1) fragColor1 : vec4f,
            @location(2) fragColor2 : vec4f,
        }

        @fragment fn fs() -> FragmentOut {
            var output : FragmentOut;
            output.fragColor0 = vec4f(0);
            output.fragColor1 = vec4f(0);
            output.fragColor2 = vec4f(0);
            return output;
        }
    )");

    utils::ComboRenderPipelineDescriptor testDescriptor;
    testDescriptor.layout = {};
    testDescriptor.vertex.module = module;
    testDescriptor.cFragment.module = module;
    testDescriptor.cFragment.targetCount = 3;
    testDescriptor.cTargets[1].format = wgpu::TextureFormat::Undefined;

    for (int i = 0; i < 10; ++i) {
        wgpu::BlendState blend0;
        wgpu::BlendState blend2;

        // Blend state intentionally omitted for target 1
        testDescriptor.cTargets[0].blend = &blend0;
        testDescriptor.cTargets[2].blend = &blend2;

        bool expectError = true;
        switch (i) {
            case 0:  // default
                expectError = false;
                break;
            case 1:  // no blend
                testDescriptor.cTargets[0].blend = nullptr;
                break;
            case 2:  // no blend second target
                testDescriptor.cTargets[2].blend = nullptr;
                break;
            case 3:  // color.operation
                blend2.color.operation = wgpu::BlendOperation::Subtract;
                break;
            case 4:  // color.srcFactor
                blend2.color.srcFactor = wgpu::BlendFactor::SrcAlpha;
                break;
            case 5:  // color.dstFactor
                blend2.color.dstFactor = wgpu::BlendFactor::DstAlpha;
                break;
            case 6:  // alpha.operation
                blend2.alpha.operation = wgpu::BlendOperation::Subtract;
                break;
            case 7:  // alpha.srcFactor
                blend2.alpha.srcFactor = wgpu::BlendFactor::SrcAlpha;
                break;
            case 8:  // alpha.dstFactor
                blend2.alpha.dstFactor = wgpu::BlendFactor::DstAlpha;
                break;
            case 9:  // writeMask
                testDescriptor.cTargets[2].writeMask = wgpu::ColorWriteMask::Green;
                break;
            default:
                DAWN_UNREACHABLE();
        }

        if (expectError) {
            ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&testDescriptor));
        } else {
            device.CreateRenderPipeline(&testDescriptor);
        }
    }
}

TEST_F(CompatValidationTest, CanNotCreatePipelineWithNonZeroDepthBiasClamp) {
    wgpu::ShaderModule module = utils::CreateShaderModule(device, R"(
        @vertex fn vs() -> @builtin(position) vec4f {
            return vec4f(0);
        }

        @fragment fn fs() -> @location(0) vec4f {
            return vec4f(0);
        }
    )");

    utils::ComboRenderPipelineDescriptor testDescriptor;
    testDescriptor.layout = {};
    testDescriptor.vertex.module = module;
    testDescriptor.cFragment.module = module;
    testDescriptor.cFragment.targetCount = 1;
    testDescriptor.cTargets[1].format = wgpu::TextureFormat::RGBA8Unorm;

    wgpu::DepthStencilState* depthStencil =
        testDescriptor.EnableDepthStencil(wgpu::TextureFormat::Depth24Plus);
    depthStencil->depthWriteEnabled = wgpu::OptionalBool::True;
    depthStencil->depthBias = 0;
    depthStencil->depthBiasSlopeScale = 0;

    depthStencil->depthBiasClamp = 0;
    device.CreateRenderPipeline(&testDescriptor);

    depthStencil->depthBiasClamp = 1;
    ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&testDescriptor));
}

TEST_F(CompatValidationTest, CanNotCreatePipelineWithFineDerivatives) {
    std::string builtins[] = {"dpdx", "dpdxFine", "dpdyFine", "fwidthFine"};
    for (auto builtin : builtins) {
        auto wgsl = absl::StrFormat(
            R"(
                struct VOut {
                  @builtin(position) pos: vec4f,
                  @location(0) i: vec4f,
                };

                @vertex fn vs() -> VOut {
                    return VOut(vec4f(0), vec4f(0));
                }

                @fragment fn fs(v: VOut) -> @location(0) vec4f {
                    _ = %s(v.i);
                    return vec4f(0);
                }
            )",
            builtin);

        wgpu::ShaderModule module = utils::CreateShaderModule(device, wgsl);

        utils::ComboRenderPipelineDescriptor testDescriptor;
        testDescriptor.layout = {};
        testDescriptor.vertex.module = module;
        testDescriptor.cFragment.module = module;
        testDescriptor.cFragment.targetCount = 1;
        testDescriptor.cTargets[1].format = wgpu::TextureFormat::RGBA8Unorm;

        if (builtin == "dpdx") {
            device.CreateRenderPipeline(&testDescriptor);
        } else {
            ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&testDescriptor));
        }
    }
}

TEST_F(CompatValidationTest, CanNotCreatePipelineWithTextureLoadOfDepthTexture) {
    wgpu::ShaderModule module = utils::CreateShaderModule(device, R"(
        @group(0) @binding(0) var<storage, read_write> dstBuf : array<vec4f>;
        @group(0) @binding(1) var tex1 : texture_2d<f32>;
        @group(0) @binding(2) var tex2 : texture_depth_2d;
        @group(0) @binding(3) var tex3 : texture_depth_2d_array;
        @group(0) @binding(4) var tex4 : texture_depth_multisampled_2d;

        @compute @workgroup_size(1) fn main1() {
            dstBuf[0] = textureLoad(tex1, vec2(0), 0);
        }

        @compute @workgroup_size(1) fn main2() {
            dstBuf[0] = vec4f(textureLoad(tex2, vec2(0), 0));
        }

        @compute @workgroup_size(1) fn main3() {
            dstBuf[0] = vec4f(textureLoad(tex3, vec2(0), 0, 0));
        }

        @compute @workgroup_size(1) fn main4() {
            dstBuf[4] = vec4f(textureLoad(tex4, vec2(0), 0));
        }
    )");

    const char* entryPoints[] = {"main1", "main2", "main3", "main4"};
    for (auto entryPoint : entryPoints) {
        wgpu::ComputePipelineDescriptor pDesc;
        pDesc.compute.module = module;
        pDesc.compute.entryPoint = entryPoint;
        if (entryPoint == entryPoints[0]) {
            device.CreateComputePipeline(&pDesc);
        } else {
            ASSERT_DEVICE_ERROR(
                device.CreateComputePipeline(&pDesc),
                testing::HasSubstr(
                    "textureLoad can not be used with depth textures in compatibility mode"));
        }
    }
}

// Test that large group ids don't crash
TEST_F(CompatValidationTest, LargeGroupIds) {
    wgpu::ShaderModule module = utils::CreateShaderModule(device, R"(
@group(0) @binding(0) var smp: sampler;
@group(2345) @binding(0) var tex1: texture_2d<f32>;
@group(6789) @binding(0) var tex2: texture_2d<f32>;

@vertex fn vs() -> @builtin(position) vec4f {
    let c = textureLoad(tex1, vec2u(0), 0) + textureLoad(tex2, vec2u(0), 0);
    return c;
}

@fragment fn fs() -> @location(0) vec4f {
    let c = textureSample(tex1, smp, vec2f(0)) + textureSample(tex2, smp, vec2f(0));
    return c;
}

@compute @workgroup_size(1) fn main() {
    let c = textureSampleLevel(tex1, smp, vec2f(0), 0) + textureSampleLevel(tex2, smp, vec2f(0), 0);
})");

    {
        wgpu::ComputePipelineDescriptor pDesc;
        pDesc.compute.module = module;
        ASSERT_DEVICE_ERROR(device.CreateComputePipeline(&pDesc),
                            testing::HasSubstr("exceeds maxBindGroups"));
    }
    {
        utils::ComboRenderPipelineDescriptor pDesc;
        pDesc.vertex.module = module;
        pDesc.cFragment.module = module;
        pDesc.cFragment.targetCount = 1;
        pDesc.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm;

        ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&pDesc),
                            testing::HasSubstr("exceeds maxBindGroups"));
    }
}

TEST_F(CompatValidationTest, CanNotCreatePipelineWithDepthTextureUsedWithNonComparisonSampler) {
    wgpu::ShaderModule module = utils::CreateShaderModule(device, R"(
        @group(1) @binding(0) var s: sampler;
        @group(1) @binding(1) var sc: sampler_comparison;
        @group(0) @binding(0) var tex2d : texture_depth_2d;
        @group(0) @binding(1) var tex2dArray: texture_depth_2d_array;
        @group(0) @binding(2) var texCube : texture_depth_cube;
        @group(0) @binding(3) var texCubeArray : texture_depth_cube_array;

        @vertex fn vs() -> @builtin(position) vec4f {
            return vec4f(0);
        }

        // valid
        @fragment fn main0() -> @location(0) vec4f {
            return textureGatherCompare(tex2d, sc, vec2(0), 0) +
                   textureGatherCompare(tex2dArray, sc, vec2(0), 0, 0) +
                   textureGatherCompare(texCube, sc, vec3(0), 0) +
                   textureGatherCompare(texCubeArray, sc, vec3(0), 0, 0) +
                   vec4f(textureSampleCompare(tex2d, sc, vec2(0), 0)) +
                   vec4f(textureSampleCompare(tex2dArray, sc, vec2(0), 0, 0)) +
                   vec4f(textureSampleCompare(texCube, sc, vec3(0), 0)) +
                   vec4f(textureSampleCompare(texCubeArray, sc, vec3(0), 0, 0)) +
                   vec4f(textureSampleCompareLevel(tex2d, sc, vec2(0), 0)) +
                   vec4f(textureSampleCompareLevel(tex2dArray, sc, vec2(0), 0, 0)) +
                   vec4f(textureSampleCompareLevel(texCube, sc, vec3(0), 0)) +
                   vec4f(textureSampleCompareLevel(texCubeArray, sc, vec3(0), 0, 0)) ;
        }

        @fragment fn main1() -> @location(0) vec4f {
            return textureGather(tex2d, s, vec2(0));
        }

        @fragment fn main2() -> @location(0) vec4f {
            return textureGather(tex2dArray, s, vec2(0), 0);
        }

        @fragment fn main3() -> @location(0) vec4f {
            return textureGather(texCube, s, vec3(0));
        }

        @fragment fn main4() -> @location(0) vec4f {
            return textureGather(texCubeArray, s, vec3(0), 0);
        }

        @fragment fn main5() -> @location(0) vec4f {
            return vec4f(textureSample(tex2d, s, vec2(0)));
        }

        @fragment fn main6() -> @location(0) vec4f {
            return vec4f(textureSample(tex2dArray, s, vec2(0), 0));
        }

        @fragment fn main7() -> @location(0) vec4f {
            return vec4f(textureSample(texCube, s, vec3(0)));
        }

        @fragment fn main8() -> @location(0) vec4f {
            return vec4f(textureSample(texCubeArray, s, vec3(0), 0));
        }

        @fragment fn main9() -> @location(0) vec4f {
            return vec4f(textureSampleLevel(tex2d, s, vec2(0), 0));
        }

        @fragment fn main10() -> @location(0) vec4f {
            return vec4f(textureSampleLevel(tex2dArray, s, vec2(0), 0, 0));
        }

        @fragment fn main11() -> @location(0) vec4f {
            return vec4f(textureSampleLevel(texCube, s, vec3(0), 0));
        }

        @fragment fn main12() -> @location(0) vec4f {
            return vec4f(textureSampleLevel(texCubeArray, s, vec3(0), 0, 0));
        }
    )");

    const char* entryPoints[] = {"main0", "main1", "main2", "main3",  "main4",  "main5", "main6",
                                 "main7", "main8", "main9", "main10", "main11", "main12"};
    for (auto entryPoint : entryPoints) {
        utils::ComboRenderPipelineDescriptor pDesc;
        pDesc.vertex.module = module;
        pDesc.cFragment.module = module;
        pDesc.cFragment.entryPoint = entryPoint;
        pDesc.cFragment.targetCount = 1;
        pDesc.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm;
        if (entryPoint == entryPoints[0]) {
            device.CreateRenderPipeline(&pDesc);
        } else {
            ASSERT_DEVICE_ERROR(
                device.CreateRenderPipeline(&pDesc),
                testing::HasSubstr("texture_depth_xx can not be used with non-comparison samplers "
                                   "in compatibility mode"));
        }
    }
}

TEST_F(CompatValidationTest, CanNotUseTooManyTextureSamplerCombos) {
    wgpu::Limits limits;
    device.GetLimits(&limits);
    uint32_t maxCombos =
        std::min(limits.maxSampledTexturesPerShaderStage, limits.maxSamplersPerShaderStage);

    struct Test {
        bool expectSuccess;
        uint32_t numCombos;
        uint32_t numNonSamplerUsages;
        uint32_t numExternalTextures;
        bool useSameExternalTexture;
        wgpu::ShaderStage stages;
    };
    // clang-format off
    Test comboTests[] = {
        //                     num                 use
        //                     non        num      same
        //                     sampler    external external
        // pass numCombos      uses       textures tex        stage
        {true , maxCombos    , 0        , 0      , false, wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment},
        {true , 1            , maxCombos, 0      , false, wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment},
        {false, 2            , maxCombos, 0      , false, wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment},
        {true , maxCombos - 4, 0        , 1      , false, wgpu::ShaderStage::Vertex},
        {false, maxCombos - 3, 0        , 1      , false, wgpu::ShaderStage::Vertex},
        {true , maxCombos - 8, 0        , 2      , false, wgpu::ShaderStage::Vertex},
        {false, maxCombos - 7, 0        , 2      , false, wgpu::ShaderStage::Vertex},
        {true , maxCombos - 7, 0        , 2      , true,  wgpu::ShaderStage::Vertex},
        {false, maxCombos - 6, 0        , 2      , true,  wgpu::ShaderStage::Vertex},
        {true , maxCombos - 4, 0        , 1      , false, wgpu::ShaderStage::Fragment},
        {false, maxCombos - 3, 0        , 1      , false, wgpu::ShaderStage::Fragment},
        {true , maxCombos - 8, 0        , 2      , false, wgpu::ShaderStage::Fragment},
        {false, maxCombos - 7, 0        , 2      , false, wgpu::ShaderStage::Fragment},
        {true , maxCombos - 7, 0        , 2      , true,  wgpu::ShaderStage::Fragment},
        {false, maxCombos - 6, 0        , 2      , true,  wgpu::ShaderStage::Fragment},
        {false, maxCombos + 1, 0        , 0      , false, wgpu::ShaderStage::Vertex},
        {false, maxCombos + 1, 0        , 0      , false, wgpu::ShaderStage::Fragment},
    };
    // clang-format on
    for (const auto& test : comboTests) {
        uint32_t maxTexturesPerShaderStage =
            limits.maxSampledTexturesPerShaderStage - (test.numExternalTextures * 3);
        auto numCombos = test.numCombos;
        std::vector<std::string> textureDeclarations[2];
        std::vector<std::string> samplerDeclarations[2];
        std::vector<std::string> usages[2];
        for (uint32_t stage = 0; stage < 2; ++stage) {
            uint32_t count = 0;
            for (uint32_t t = 0; count < numCombos && t < maxTexturesPerShaderStage; ++t) {
                DAWN_UNSAFE_TODO(textureDeclarations[stage])
                    .push_back(
                        absl::StrFormat("@group(%u) @binding(%u) var t%u_%u: texture_2d<f32>;",
                                        stage * 2, t, stage, t));
                for (uint32_t s = 0; count < numCombos && t < limits.maxSamplersPerShaderStage;
                     ++s) {
                    if (t == 0) {
                        DAWN_UNSAFE_TODO(samplerDeclarations[stage])
                            .push_back(
                                absl::StrFormat("@group(%u) @binding(%u) var s%u_%u: sampler;",
                                                (stage * 2) + 1, s, stage, s));
                    }
                    DAWN_UNSAFE_TODO(usages[stage])
                        .push_back(
                            absl::StrFormat("c += textureSampleLevel(t%u_%u, s%u_%u, vec2f(0), 0);",
                                            stage, t, stage, s));
                    ++count;
                }
            }

            for (uint32_t t = 0; t < test.numNonSamplerUsages; ++t) {
                if (t >= DAWN_UNSAFE_TODO(textureDeclarations[stage]).size()) {
                    DAWN_UNSAFE_TODO(textureDeclarations[stage])
                        .push_back(
                            absl::StrFormat("@group(%u) @binding(%u) var t%u_%u: texture_2d<f32>;",
                                            stage * 2, t, stage, t));
                }
                DAWN_UNSAFE_TODO(usages[stage])
                    .push_back(absl::StrFormat("c += textureLoad(t%u_%u, vec2u(0), 0);", stage, t));
            }

            for (uint32_t t = 0; t < test.numExternalTextures; ++t) {
                if (t == 0 || !test.useSameExternalTexture) {
                    auto et = DAWN_UNSAFE_TODO(textureDeclarations[stage]).size() + t;
                    DAWN_UNSAFE_TODO(textureDeclarations[stage])
                        .push_back(
                            absl::StrFormat("@group(%u) @binding(%u) var e%u_%u: texture_external;",
                                            stage * 2, et, stage, t));
                }
                DAWN_UNSAFE_TODO(usages[stage])
                    .push_back(absl::StrFormat(
                        "c += textureSampleBaseClampToEdge(e%u_%u, s%u_%u, vec2f(0));", stage,
                        test.useSameExternalTexture ? 0 : t, stage,
                        test.useSameExternalTexture ? t : 0));
            }
        }

        auto wgsl =
            absl::StrFormat(R"(
%s
%s

%s
%s

fn usage0() -> vec4f {
  var c: vec4f;
  %s
  return c;
}

fn usage1() -> vec4f {
  var c: vec4f;
  %s
  return c;
}

@vertex fn vs() -> @builtin(position) vec4f {
  _ = %s;
  return vec4f(0);
}

@group(2) @binding(0) var tt: texture_2d<f32>;

@fragment fn fs() -> @location(0) vec4f {
  return %s;
}
        )",
                            absl::StrJoin(textureDeclarations[0], "\n"),
                            absl::StrJoin(samplerDeclarations[0], "\n"),
                            absl::StrJoin(textureDeclarations[1], "\n"),
                            absl::StrJoin(samplerDeclarations[1], "\n"),
                            absl::StrJoin(usages[0], "\n  "), absl::StrJoin(usages[1], "\n  "),
                            test.stages & wgpu::ShaderStage::Vertex ? "usage0()" : "vec4f(0)",
                            test.stages & wgpu::ShaderStage::Fragment ? "usage1()" : "vec4f(0)");

        wgpu::ShaderModule module = utils::CreateShaderModule(device, wgsl.c_str());
        utils::ComboRenderPipelineDescriptor descriptor;
        descriptor.vertex.module = module;
        descriptor.cFragment.module = module;
        descriptor.cFragment.targetCount = 1;
        descriptor.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm;

        if (test.expectSuccess) {
            device.CreateRenderPipeline(&descriptor);
        } else {
            ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor),
                                testing::HasSubstr("compat"));
        }
    }
}

TEST_F(CompatValidationTest, CanNotUseSampleMask) {
    wgpu::ShaderModule moduleSampleMaskOutput = utils::CreateShaderModule(device, R"(
        @vertex fn vs() -> @builtin(position) vec4f {
            return vec4f(1);
        }
        struct Output {
            @builtin(sample_mask) mask_out: u32,
            @location(0) color : vec4f,
        }
        @fragment fn fsWithoutSampleMaskUsage() -> @location(0) vec4f {
            return vec4f(1.0, 1.0, 1.0, 1.0);
        }
        @fragment fn fsWithSampleMaskUsage() -> Output {
            var o: Output;
            // We need to make sure this sample_mask isn't optimized out even its value equals "no op".
            o.mask_out = 0xFFFFFFFFu;
            o.color = vec4f(1.0, 1.0, 1.0, 1.0);
            return o;
        }
    )");

    // Check we can use a fragment shader that doesn't use sample_mask from
    // the same module as one that does.
    {
        utils::ComboRenderPipelineDescriptor descriptor;
        descriptor.vertex.module = moduleSampleMaskOutput;
        descriptor.cFragment.module = moduleSampleMaskOutput;
        descriptor.cFragment.entryPoint = "fsWithoutSampleMaskUsage";
        descriptor.multisample.count = 4;
        descriptor.multisample.alphaToCoverageEnabled = false;

        device.CreateRenderPipeline(&descriptor);
    }

    // Check we can not use a fragment shader that uses sample_mask.
    {
        utils::ComboRenderPipelineDescriptor descriptor;
        descriptor.vertex.module = moduleSampleMaskOutput;
        descriptor.cFragment.module = moduleSampleMaskOutput;
        descriptor.cFragment.entryPoint = "fsWithSampleMaskUsage";
        descriptor.multisample.count = 4;
        descriptor.multisample.alphaToCoverageEnabled = false;

        ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor),
                            testing::HasSubstr("sample_mask"));
    }
}

TEST_F(CompatValidationTest, CanNotUseFragmentShaderWithSampleIndex) {
    wgpu::ShaderModule moduleSampleMaskOutput = utils::CreateShaderModule(device, R"(
        @vertex fn vs() -> @builtin(position) vec4f {
            return vec4f(1);
        }
        struct Output {
            @location(0) color : vec4f,
        }
        @fragment fn fsWithoutSampleIndexUsage() -> @location(0) vec4f {
            return vec4f(1.0, 1.0, 1.0, 1.0);
        }
        @fragment fn fsWithSampleIndexUsage(@builtin(sample_index) sNdx: u32) -> Output {
            var o: Output;
            _ = sNdx;
            o.color = vec4f(1.0, 1.0, 1.0, 1.0);
            return o;
        }
    )");

    // Check we can use a fragment shader that doesn't use sample_index from
    // the same module as one that does.
    {
        utils::ComboRenderPipelineDescriptor descriptor;
        descriptor.vertex.module = moduleSampleMaskOutput;
        descriptor.vertex.entryPoint = "vs";
        descriptor.cFragment.module = moduleSampleMaskOutput;
        descriptor.cFragment.entryPoint = "fsWithoutSampleIndexUsage";
        descriptor.multisample.count = 4;
        descriptor.multisample.alphaToCoverageEnabled = false;

        device.CreateRenderPipeline(&descriptor);
    }

    // Check we can not use a fragment shader that uses sample_index.
    {
        utils::ComboRenderPipelineDescriptor descriptor;
        descriptor.vertex.module = moduleSampleMaskOutput;
        descriptor.vertex.entryPoint = "vs";
        descriptor.cFragment.module = moduleSampleMaskOutput;
        descriptor.cFragment.entryPoint = "fsWithSampleIndexUsage";
        descriptor.multisample.count = 4;
        descriptor.multisample.alphaToCoverageEnabled = false;

        ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor),
                            testing::HasSubstr("sample_index"));
    }
}

TEST_F(CompatValidationTest, CanNotUseShaderWithUnsupportedInterpolateTypeOrSampling) {
    static const char* interpolateParams[] = {
        "perspective",  // should pass
        "linear",      "perspective, sample", "flat", "flat, first",
    };
    for (auto interpolateParam : interpolateParams) {
        auto wgsl = absl::StrFormat(R"(
            struct Vertex {
                @builtin(position) pos: vec4f,
                @location(0) @interpolate(%s) color : vec4f,
            };
            @vertex fn vs() -> Vertex {
                var v: Vertex;
                v.pos = vec4f(1);
                v.color = vec4f(1);
                return v;
            }
            @fragment fn fsWithoutBadInterpolationUsage() -> @location(0) vec4f {
                return vec4f(1);
            }
            @fragment fn fsWithBadInterpolationUsage1(v: Vertex) -> @location(0) vec4f {
                return vec4f(1);
            }
            @fragment fn fsWithBadInterpolationUsage2(v: Vertex) -> @location(0) vec4f {
                return v.pos;
            }
            @fragment fn fsWithBadInterpolationUsage3(v: Vertex) -> @location(0) vec4f {
                return v.color;
            }
        )",
                                    interpolateParam);
        wgpu::ShaderModule moduleInterpolationLinear =
            utils::CreateShaderModule(device, wgsl.c_str());

        static const char* entryPoints[] = {
            "fsWithoutBadInterpolationUsage",
            "fsWithBadInterpolationUsage1",
            "fsWithBadInterpolationUsage2",
            "fsWithBadInterpolationUsage3",
        };
        for (auto entryPoint : entryPoints) {
            utils::ComboRenderPipelineDescriptor descriptor;
            descriptor.vertex.module = moduleInterpolationLinear;
            descriptor.cFragment.module = moduleInterpolationLinear;
            descriptor.cFragment.entryPoint = entryPoint;

            bool shouldSucceed =
                entryPoint == entryPoints[0] || interpolateParam == interpolateParams[0];

            if (shouldSucceed) {
                device.CreateRenderPipeline(&descriptor);
            } else {
                ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor),
                                    testing::HasSubstr("in compatibility mode"));
            }
        }
    }
}

TEST_F(CompatValidationTest, CanNotCreateRGxxxStorageTexture) {
    const wgpu::TextureFormat formats[] = {
        wgpu::TextureFormat::RGBA8Unorm,  // pass check
        wgpu::TextureFormat::RG32Sint,
        wgpu::TextureFormat::RG32Uint,
        wgpu::TextureFormat::RG32Float,
    };
    for (auto format : formats) {
        wgpu::TextureDescriptor descriptor;
        descriptor.size = {1, 1, 1};
        descriptor.dimension = wgpu::TextureDimension::e2D;
        descriptor.format = format;
        descriptor.usage = wgpu::TextureUsage::StorageBinding;
        wgpu::Texture texture;

        if (format == wgpu::TextureFormat::RGBA8Unorm) {
            texture = device.CreateTexture(&descriptor);
        } else {
            ASSERT_DEVICE_ERROR(texture = device.CreateTexture(&descriptor));
        }
        texture.Destroy();
    }
}

TEST_F(CompatValidationTest, CanNotUseStorageBufferInVertexStageWithDefaultLimit0) {
    const wgpu::ShaderStage stages[] = {
        wgpu::ShaderStage::Compute,
        wgpu::ShaderStage::Fragment,
        wgpu::ShaderStage::Vertex,
    };
    const wgpu::BufferBindingType buffer_types[] = {
        wgpu::BufferBindingType::Storage,
        wgpu::BufferBindingType::ReadOnlyStorage,
    };
    for (auto stage : stages) {
        for (auto buffer_type : buffer_types) {
            if (stage == wgpu::ShaderStage::Vertex &&
                buffer_type == wgpu::BufferBindingType::Storage) {
                continue;
            }
            wgpu::BindGroupLayoutEntry entries[1];
            entries[0].binding = 0;
            entries[0].visibility = stage;
            entries[0].buffer.type = buffer_type;

            wgpu::BindGroupLayoutDescriptor descriptor;
            descriptor.entryCount = 1;
            descriptor.entries = entries;

            if (stage != wgpu::ShaderStage::Vertex) {
                wgpu::BindGroupLayout layout = device.CreateBindGroupLayout(&descriptor);
            } else {
                ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&descriptor),
                                    testing::HasSubstr("maxStorageBuffers"));
            }
        }
    }
}

TEST_F(CompatValidationTest, CanNotUseStorageTexturesInVertexStageWithDefaultLimit0) {
    const wgpu::ShaderStage stages[] = {
        wgpu::ShaderStage::Compute,
        wgpu::ShaderStage::Fragment,
        wgpu::ShaderStage::Vertex,
    };
    const wgpu::StorageTextureAccess access_types[] = {
        wgpu::StorageTextureAccess::ReadOnly,
        wgpu::StorageTextureAccess::ReadWrite,
        wgpu::StorageTextureAccess::WriteOnly,
    };
    for (auto stage : stages) {
        for (auto access : access_types) {
            if (stage == wgpu::ShaderStage::Vertex &&
                (access == wgpu::StorageTextureAccess::ReadWrite ||
                 access == wgpu::StorageTextureAccess::WriteOnly)) {
                continue;
            }
            wgpu::BindGroupLayoutEntry entries[1];
            entries[0].binding = 0;
            entries[0].visibility = stage;
            entries[0].storageTexture.format = wgpu::TextureFormat::R32Float;
            entries[0].storageTexture.access = access;

            wgpu::BindGroupLayoutDescriptor descriptor;
            descriptor.entryCount = 1;
            descriptor.entries = entries;

            if (stage != wgpu::ShaderStage::Vertex) {
                wgpu::BindGroupLayout layout = device.CreateBindGroupLayout(&descriptor);
            } else {
                ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&descriptor),
                                    testing::HasSubstr("maxStorageTextures"));
            }
        }
    }
}

constexpr const char* kRenderTwoTexturesOneBindgroupWGSL = R"(
    @vertex
    fn vs(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4f {
        var pos = array(
            vec4f(-1,  3, 0, 1),
            vec4f( 3, -1, 0, 1),
            vec4f(-1, -1, 0, 1));
        return pos[VertexIndex];
    }

    @group(0) @binding(0) var tex0 : texture_2d<f32>;
    @group(0) @binding(1) var tex1 : texture_2d<f32>;

    @fragment
    fn fs(@builtin(position) pos: vec4f) -> @location(0) vec4f {
        _ = tex0;
        _ = tex1;
        return vec4f(0);
    }
)";

constexpr const char* kSampleDepthSampleStencilOneBindgroupWGSL = R"(
    @vertex
    fn vs(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4f {
        var pos = array(
            vec4f(-1,  3, 0, 1),
            vec4f( 3, -1, 0, 1),
            vec4f(-1, -1, 0, 1));
        return pos[VertexIndex];
    }

    @group(0) @binding(0) var depth : texture_2d<f32>;
    @group(0) @binding(1) var stencil : texture_2d<u32>;

    @fragment
    fn fs(@builtin(position) pos: vec4f) -> @location(0) vec4f {
        _ = depth;
        _ = stencil;
        return vec4f(0);
    }
)";

constexpr const char* kRenderTwoTexturesTwoBindgroupsWGSL = R"(
    @vertex
    fn vs(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4f {
        var pos = array(
            vec4f(-1,  3, 0, 1),
            vec4f( 3, -1, 0, 1),
            vec4f(-1, -1, 0, 1));
        return pos[VertexIndex];
    }

    @group(0) @binding(0) var tex0 : texture_2d<f32>;
    @group(1) @binding(0) var tex1 : texture_2d<f32>;

    @fragment
    fn fs(@builtin(position) pos: vec4f) -> @location(0) vec4f {
        _ = tex0;
        _ = tex1;
        return vec4f(0);
    }
)";

void TestMultipleTextureViewValidationInRenderPass(
    wgpu::Device device,
    wgpu::TextureFormat format,
    const char* wgsl,
    std::function<void(wgpu::Device device,
                       wgpu::Texture texture,
                       wgpu::RenderPipeline pipeline,
                       std::function<void(wgpu::RenderPassEncoder pass)> drawFn)> fn) {
    wgpu::TextureDescriptor descriptor;
    descriptor.size = {2, 1, 1};
    descriptor.mipLevelCount = 2;
    descriptor.dimension = wgpu::TextureDimension::e2D;
    descriptor.format = format;
    descriptor.usage = wgpu::TextureUsage::TextureBinding;

    wgpu::Texture texture = device.CreateTexture(&descriptor);

    constexpr uint32_t indices[] = {0, 1, 2};
    wgpu::Buffer indexBuffer =
        utils::CreateBufferFromData(device, indices, sizeof indices, wgpu::BufferUsage::Index);

    // Create a pipeline that will sample from 2 2D textures and output to an attachment.
    wgpu::ShaderModule module = utils::CreateShaderModule(device, wgsl);

    utils::ComboRenderPipelineDescriptor pDesc;
    pDesc.vertex.module = module;
    pDesc.cFragment.module = module;
    pDesc.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm;
    wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pDesc);

    fn(device, texture, pipeline, [](wgpu::RenderPassEncoder pass) { pass.Draw(3); });

    fn(device, texture, pipeline, [indexBuffer](wgpu::RenderPassEncoder pass) {
        pass.SetIndexBuffer(indexBuffer, wgpu::IndexFormat::Uint32);
        pass.DrawIndexed(3);
    });

    indexBuffer.Destroy();
    texture.Destroy();
}

enum FlexibleTextureViewsFeature {
    Enabled,
    Disabled,
};

class CompatTextureViewValidationTests
    : public CompatValidationTest,
      public ::testing::WithParamInterface<FlexibleTextureViewsFeature> {
  public:
    static std::string PrintToStringParamName(
        const testing::TestParamInfo<FlexibleTextureViewsFeature>& info) {
        std::ostringstream ss;
        ss << "WithFlexibleTextureViews";
        if (info.param == FlexibleTextureViewsFeature::Enabled) {
            ss << "Enabled";
        } else {
            ss << "Disabled";
        }

        return ss.str();
    }

  protected:
    std::vector<wgpu::FeatureName> GetRequiredFeatures() override {
        if (HasFlexibleTextureViews()) {
            return {wgpu::FeatureName::FlexibleTextureViews};
        }

        return {};
    }

    bool HasFlexibleTextureViews() const {
        return GetParam() == FlexibleTextureViewsFeature::Enabled;
    }
};

#define ASSERT_TEXTURE_VIEW_ERROR_IF_NO_FLEXIBLE_FEATURE(statement, matcher) \
    do {                                                                     \
        if (HasFlexibleTextureViews()) {                                     \
            statement;                                                       \
        } else {                                                             \
            ASSERT_DEVICE_ERROR(statement, matcher);                         \
        }                                                                    \
    } while (0)

// Test we get a validation error if we have 2 different views of a texture
// in the same bind group. Unless FlexibleTextureViews is enabled.
TEST_P(CompatTextureViewValidationTests, CanNotDrawDifferentMipsSameTextureSameBindGroup) {
    TestMultipleTextureViewValidationInRenderPass(
        device, wgpu::TextureFormat::RGBA8Unorm, kRenderTwoTexturesOneBindgroupWGSL,
        [this](wgpu::Device device, wgpu::Texture texture, wgpu::RenderPipeline pipeline,
               std::function<void(wgpu::RenderPassEncoder pass)> drawFn) {
            wgpu::TextureViewDescriptor mip0ViewDesc;
            mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
            mip0ViewDesc.baseMipLevel = 0;
            mip0ViewDesc.mipLevelCount = 1;

            wgpu::TextureViewDescriptor mip1ViewDesc;
            mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
            mip1ViewDesc.baseMipLevel = 1;
            mip1ViewDesc.mipLevelCount = 1;

            wgpu::BindGroup bindGroup = utils::MakeBindGroup(
                device, pipeline.GetBindGroupLayout(0),
                {{0, texture.CreateView(&mip0ViewDesc)}, {1, texture.CreateView(&mip1ViewDesc)}});

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

            utils::BasicRenderPass rp = utils::CreateBasicRenderPass(device, 4, 1);
            wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo);
            pass.SetPipeline(pipeline);
            pass.SetBindGroup(0, bindGroup);
            drawFn(pass);
            pass.End();

            ASSERT_TEXTURE_VIEW_ERROR_IF_NO_FLEXIBLE_FEATURE(encoder.Finish(),
                                                             testing::HasSubstr("different views"));
        });
}

// Test we get a validation error if we have 2 different views of a texture spanning
// different bind groups. Unless FlexibleTextureViews is enabled.
TEST_P(CompatTextureViewValidationTests, CanNotDrawDifferentMipsSameTextureDifferentBindGroups) {
    TestMultipleTextureViewValidationInRenderPass(
        device, wgpu::TextureFormat::RGBA8Unorm, kRenderTwoTexturesTwoBindgroupsWGSL,
        [this](wgpu::Device device, wgpu::Texture texture, wgpu::RenderPipeline pipeline,
               std::function<void(wgpu::RenderPassEncoder pass)> drawFn) {
            wgpu::TextureViewDescriptor mip0ViewDesc;
            mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
            mip0ViewDesc.baseMipLevel = 0;
            mip0ViewDesc.mipLevelCount = 1;

            wgpu::TextureViewDescriptor mip1ViewDesc;
            mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
            mip1ViewDesc.baseMipLevel = 1;
            mip1ViewDesc.mipLevelCount = 1;

            wgpu::BindGroup bindGroup0 = utils::MakeBindGroup(
                device, pipeline.GetBindGroupLayout(0), {{0, texture.CreateView(&mip0ViewDesc)}});

            wgpu::BindGroup bindGroup1 = utils::MakeBindGroup(
                device, pipeline.GetBindGroupLayout(1), {{0, texture.CreateView(&mip1ViewDesc)}});

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

            utils::BasicRenderPass rp = utils::CreateBasicRenderPass(device, 4, 1);
            wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo);
            pass.SetPipeline(pipeline);
            pass.SetBindGroup(0, bindGroup0);
            pass.SetBindGroup(1, bindGroup1);
            drawFn(pass);
            pass.End();

            ASSERT_TEXTURE_VIEW_ERROR_IF_NO_FLEXIBLE_FEATURE(encoder.Finish(),
                                                             testing::HasSubstr("different views"));
        });
}

// Test that it's possible to set a bindgroup that uses a texture with multiple views
// which would be an error if you issued a draw command but, you then fix the issue by replacing
// the bindgroup with one that does not have multiple views. We're trying to test
// that the implementation does the validation at draw command time and not before.
TEST_P(CompatTextureViewValidationTests,
       CanBindDifferentMipsSameTextureSameBindGroupAndFixWithoutError) {
    TestMultipleTextureViewValidationInRenderPass(
        device, wgpu::TextureFormat::RGBA8Unorm, kRenderTwoTexturesOneBindgroupWGSL,
        [](wgpu::Device device, wgpu::Texture texture, wgpu::RenderPipeline pipeline,
           std::function<void(wgpu::RenderPassEncoder pass)> drawFn) {
            wgpu::TextureViewDescriptor mip0ViewDesc;
            mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
            mip0ViewDesc.baseMipLevel = 0;
            mip0ViewDesc.mipLevelCount = 1;

            wgpu::TextureViewDescriptor mip1ViewDesc;
            mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
            mip1ViewDesc.baseMipLevel = 1;
            mip1ViewDesc.mipLevelCount = 1;

            // Bindgroup with different views of same texture
            wgpu::BindGroup badBindGroup = utils::MakeBindGroup(
                device, pipeline.GetBindGroupLayout(0),
                {{0, texture.CreateView(&mip0ViewDesc)}, {1, texture.CreateView(&mip1ViewDesc)}});

            // Bindgroup with same views of texture
            wgpu::BindGroup goodBindGroup = utils::MakeBindGroup(
                device, pipeline.GetBindGroupLayout(0),
                {{0, texture.CreateView(&mip0ViewDesc)}, {1, texture.CreateView(&mip0ViewDesc)}});

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

            utils::BasicRenderPass rp = utils::CreateBasicRenderPass(device, 4, 1);
            wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo);
            pass.SetPipeline(pipeline);
            pass.SetBindGroup(0, badBindGroup);
            pass.SetBindGroup(0, goodBindGroup);
            drawFn(pass);
            pass.End();

            // No Error is expected
            encoder.Finish();
        });
}

// Test that having 2 texture views that have the same settings, in 2 different
// bindgroups, does not generate a validation error.
TEST_P(CompatTextureViewValidationTests, CanBindSameViewIn2BindGroups) {
    TestMultipleTextureViewValidationInRenderPass(
        device, wgpu::TextureFormat::RGBA8Unorm, kRenderTwoTexturesTwoBindgroupsWGSL,
        [](wgpu::Device device, wgpu::Texture texture, wgpu::RenderPipeline pipeline,
           std::function<void(wgpu::RenderPassEncoder pass)> drawFn) {
            wgpu::TextureViewDescriptor mip0ViewDesc;
            mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D;

            wgpu::TextureViewDescriptor mip1ViewDesc;
            mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D;

            wgpu::BindGroup bindGroup0 = utils::MakeBindGroup(
                device, pipeline.GetBindGroupLayout(0), {{0, texture.CreateView(&mip0ViewDesc)}});

            // Bindgroup with same view of texture as bindGroup0
            wgpu::BindGroup bindGroup1 = utils::MakeBindGroup(
                device, pipeline.GetBindGroupLayout(1), {{0, texture.CreateView(&mip1ViewDesc)}});

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

            utils::BasicRenderPass rp = utils::CreateBasicRenderPass(device, 4, 1);
            wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo);
            pass.SetPipeline(pipeline);
            pass.SetBindGroup(0, bindGroup0);
            pass.SetBindGroup(1, bindGroup1);
            drawFn(pass);
            pass.End();

            // No Error is expected
            encoder.Finish();
        });
}

// Test that no validation error happens if we have multiple views of a texture
// but don't draw.
TEST_P(CompatTextureViewValidationTests, NoErrorIfMultipleDifferentViewsOfTextureAreNotUsed) {
    TestMultipleTextureViewValidationInRenderPass(
        device, wgpu::TextureFormat::RGBA8Unorm, kRenderTwoTexturesTwoBindgroupsWGSL,
        [](wgpu::Device device, wgpu::Texture texture, wgpu::RenderPipeline pipeline,
           std::function<void(wgpu::RenderPassEncoder pass)> drawFn) {
            wgpu::TextureViewDescriptor mip0ViewDesc;
            mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D;

            wgpu::TextureViewDescriptor mip1ViewDesc;
            mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
            mip1ViewDesc.baseMipLevel = 1;
            mip1ViewDesc.mipLevelCount = 1;

            // Bindgroup with different views of same texture
            wgpu::BindGroup bindGroup0 = utils::MakeBindGroup(
                device, pipeline.GetBindGroupLayout(0), {{0, texture.CreateView(&mip0ViewDesc)}});

            // Bindgroup with same views of texture
            wgpu::BindGroup bindGroup1 = utils::MakeBindGroup(
                device, pipeline.GetBindGroupLayout(1), {{0, texture.CreateView(&mip1ViewDesc)}});

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

            utils::BasicRenderPass rp = utils::CreateBasicRenderPass(device, 4, 1);
            wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo);
            pass.SetPipeline(pipeline);
            pass.SetBindGroup(0, bindGroup0);
            pass.SetBindGroup(1, bindGroup1);
            pass.End();

            // No Error is expected because draw was never called
            encoder.Finish();
        });
}

constexpr const char* kComputeTwoTexturesOneBindgroupWGSL = R"(
    @group(0) @binding(0) var tex0 : texture_2d<f32>;
    @group(0) @binding(1) var tex1 : texture_2d<f32>;

    @compute @workgroup_size(1)
    fn cs() {
        _ = tex0;
        _ = tex1;
    }
)";

constexpr const char* kComputeTwoTexturesTwoBindgroupsWGSL = R"(
    @group(0) @binding(0) var tex0 : texture_2d<f32>;
    @group(1) @binding(0) var tex1 : texture_2d<f32>;

    @compute @workgroup_size(1)
    fn cs() {
        _ = tex0;
        _ = tex1;
    }
)";

void TestMultipleTextureViewValidationInComputePass(
    wgpu::Device device,
    const char* wgsl,
    wgpu::TextureUsage textureUsage,
    std::function<void(wgpu::Device device,
                       wgpu::Texture texture,
                       wgpu::ComputePipeline pipeline,
                       std::function<void(wgpu::ComputePassEncoder pass)> dispatchFn)> fn) {
    wgpu::TextureDescriptor descriptor;
    descriptor.size = {2, 1, 1};
    descriptor.mipLevelCount = 2;
    descriptor.dimension = wgpu::TextureDimension::e2D;
    descriptor.format = wgpu::TextureFormat::RGBA8Unorm;
    descriptor.usage = textureUsage;
    wgpu::Texture texture = device.CreateTexture(&descriptor);

    constexpr float indirectData[] = {1, 1, 1};
    wgpu::Buffer indirectBuffer = utils::CreateBufferFromData(
        device, indirectData, sizeof indirectData, wgpu::BufferUsage::Indirect);

    // Create a pipeline that will sample from 2 2D textures and output to an attachment.
    wgpu::ShaderModule module = utils::CreateShaderModule(device, wgsl);

    wgpu::ComputePipelineDescriptor pDesc;
    pDesc.compute.module = module;
    wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&pDesc);

    fn(device, texture, pipeline,
       [](wgpu::ComputePassEncoder pass) { pass.DispatchWorkgroups(1); });

    fn(device, texture, pipeline, [indirectBuffer](wgpu::ComputePassEncoder pass) {
        pass.DispatchWorkgroupsIndirect(indirectBuffer, 0);
    });

    indirectBuffer.Destroy();
    texture.Destroy();
}

// Test we get a validation error if we have 2 different views of a texture
// in the same bind group. Unless FlexibleTextureViews is enabled.
TEST_P(CompatTextureViewValidationTests, CanNotComputeWithDifferentMipsSameTextureSameBindGroup) {
    TestMultipleTextureViewValidationInComputePass(
        device, kComputeTwoTexturesOneBindgroupWGSL, wgpu::TextureUsage::TextureBinding,
        [this](wgpu::Device device, wgpu::Texture texture, wgpu::ComputePipeline pipeline,
               std::function<void(wgpu::ComputePassEncoder pass)> dispatchFn) {
            wgpu::TextureViewDescriptor mip0ViewDesc;
            mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
            mip0ViewDesc.baseMipLevel = 0;
            mip0ViewDesc.mipLevelCount = 1;

            wgpu::TextureViewDescriptor mip1ViewDesc;
            mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
            mip1ViewDesc.baseMipLevel = 1;
            mip1ViewDesc.mipLevelCount = 1;

            wgpu::BindGroup bindGroup = utils::MakeBindGroup(
                device, pipeline.GetBindGroupLayout(0),
                {{0, texture.CreateView(&mip0ViewDesc)}, {1, texture.CreateView(&mip1ViewDesc)}});

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

            wgpu::ComputePassEncoder pass = encoder.BeginComputePass({});
            pass.SetPipeline(pipeline);
            pass.SetBindGroup(0, bindGroup);
            dispatchFn(pass);
            pass.End();

            ASSERT_TEXTURE_VIEW_ERROR_IF_NO_FLEXIBLE_FEATURE(encoder.Finish(),
                                                             testing::HasSubstr("different views"));
        });
}

// Test we get a validation error if we have 2 different views of a texture spanning
// different bind groups. Unless FlexibleTextureViews is enabled.
TEST_P(CompatTextureViewValidationTests,
       CanNotComputeWithDifferentMipsSameTextureDifferentBindGroups) {
    TestMultipleTextureViewValidationInComputePass(
        device, kComputeTwoTexturesTwoBindgroupsWGSL, wgpu::TextureUsage::TextureBinding,
        [this](wgpu::Device device, wgpu::Texture texture, wgpu::ComputePipeline pipeline,
               std::function<void(wgpu::ComputePassEncoder pass)> dispatchFn) {
            wgpu::TextureViewDescriptor mip0ViewDesc;
            mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
            mip0ViewDesc.baseMipLevel = 0;
            mip0ViewDesc.mipLevelCount = 1;

            wgpu::TextureViewDescriptor mip1ViewDesc;
            mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
            mip1ViewDesc.baseMipLevel = 1;
            mip1ViewDesc.mipLevelCount = 1;

            wgpu::BindGroup bindGroup0 = utils::MakeBindGroup(
                device, pipeline.GetBindGroupLayout(0), {{0, texture.CreateView(&mip0ViewDesc)}});

            wgpu::BindGroup bindGroup1 = utils::MakeBindGroup(
                device, pipeline.GetBindGroupLayout(1), {{0, texture.CreateView(&mip1ViewDesc)}});

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

            wgpu::ComputePassEncoder pass = encoder.BeginComputePass({});
            pass.SetPipeline(pipeline);
            pass.SetBindGroup(0, bindGroup0);
            pass.SetBindGroup(1, bindGroup1);
            dispatchFn(pass);
            pass.End();

            ASSERT_TEXTURE_VIEW_ERROR_IF_NO_FLEXIBLE_FEATURE(encoder.Finish(),
                                                             testing::HasSubstr("different views"));
        });
}

// Test that it's possible to set a bindgroup that uses a texture with multiple views
// which would be an error if you issued a draw command but, you then fix the issue by replacing
// the bindgroup with one that does not have multiple views. We're trying to test
// that the implementation does the validation at draw command time and not before.
TEST_P(CompatTextureViewValidationTests,
       CanBindDifferentMipsSameTextureSameBindGroupAndFixWithoutErrorInComputePass) {
    TestMultipleTextureViewValidationInComputePass(
        device, kComputeTwoTexturesOneBindgroupWGSL, wgpu::TextureUsage::TextureBinding,
        [](wgpu::Device device, wgpu::Texture texture, wgpu::ComputePipeline pipeline,
           std::function<void(wgpu::ComputePassEncoder pass)> dispatchFn) {
            wgpu::TextureViewDescriptor mip0ViewDesc;
            mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
            mip0ViewDesc.baseMipLevel = 0;
            mip0ViewDesc.mipLevelCount = 1;

            wgpu::TextureViewDescriptor mip1ViewDesc;
            mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
            mip1ViewDesc.baseMipLevel = 1;
            mip1ViewDesc.mipLevelCount = 1;

            // Bindgroup with different views of same texture
            wgpu::BindGroup badBindGroup = utils::MakeBindGroup(
                device, pipeline.GetBindGroupLayout(0),
                {{0, texture.CreateView(&mip0ViewDesc)}, {1, texture.CreateView(&mip1ViewDesc)}});

            // Bindgroup with same views of texture
            wgpu::BindGroup goodBindGroup = utils::MakeBindGroup(
                device, pipeline.GetBindGroupLayout(0),
                {{0, texture.CreateView(&mip0ViewDesc)}, {1, texture.CreateView(&mip0ViewDesc)}});

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

            wgpu::ComputePassEncoder pass = encoder.BeginComputePass({});
            pass.SetPipeline(pipeline);
            pass.SetBindGroup(0, badBindGroup);
            pass.SetBindGroup(0, goodBindGroup);
            dispatchFn(pass);
            pass.End();

            // No Error is expected
            encoder.Finish();
        });
}

// Test that having 2 texture views that have the same settings, in 2 different
// bindgroups, does not generate a validation error.
TEST_P(CompatTextureViewValidationTests, CanBindSameViewIn2BindGroupsInComputePass) {
    TestMultipleTextureViewValidationInComputePass(
        device, kComputeTwoTexturesTwoBindgroupsWGSL, wgpu::TextureUsage::TextureBinding,
        [](wgpu::Device device, wgpu::Texture texture, wgpu::ComputePipeline pipeline,
           std::function<void(wgpu::ComputePassEncoder pass)> dispatchFn) {
            wgpu::TextureViewDescriptor mip0ViewDesc;
            mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D;

            wgpu::TextureViewDescriptor mip1ViewDesc;
            mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D;

            wgpu::BindGroup bindGroup0 = utils::MakeBindGroup(
                device, pipeline.GetBindGroupLayout(0), {{0, texture.CreateView(&mip0ViewDesc)}});

            // Bindgroup with same view of texture as bindGroup0
            wgpu::BindGroup bindGroup1 = utils::MakeBindGroup(
                device, pipeline.GetBindGroupLayout(1), {{0, texture.CreateView(&mip1ViewDesc)}});

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

            wgpu::ComputePassEncoder pass = encoder.BeginComputePass({});
            pass.SetPipeline(pipeline);
            pass.SetBindGroup(0, bindGroup0);
            pass.SetBindGroup(1, bindGroup1);
            dispatchFn(pass);
            pass.End();

            // No Error is expected
            encoder.Finish();
        });
}

// Test that no validation error happens if we have multiple views of a texture
// but don't draw.
TEST_P(CompatTextureViewValidationTests,
       NoErrorIfMultipleDifferentViewsOfTextureAreNotUsedInComputePass) {
    TestMultipleTextureViewValidationInComputePass(
        device, kComputeTwoTexturesTwoBindgroupsWGSL, wgpu::TextureUsage::TextureBinding,
        [](wgpu::Device device, wgpu::Texture texture, wgpu::ComputePipeline pipeline,
           std::function<void(wgpu::ComputePassEncoder pass)> dispatchFn) {
            wgpu::TextureViewDescriptor mip0ViewDesc;
            mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D;

            wgpu::TextureViewDescriptor mip1ViewDesc;
            mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
            mip1ViewDesc.baseMipLevel = 1;
            mip1ViewDesc.mipLevelCount = 1;

            // Bindgroup with different views of same texture
            wgpu::BindGroup bindGroup0 = utils::MakeBindGroup(
                device, pipeline.GetBindGroupLayout(0), {{0, texture.CreateView(&mip0ViewDesc)}});

            // Bindgroup with same views of texture
            wgpu::BindGroup bindGroup1 = utils::MakeBindGroup(
                device, pipeline.GetBindGroupLayout(1), {{0, texture.CreateView(&mip1ViewDesc)}});

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

            wgpu::ComputePassEncoder pass = encoder.BeginComputePass({});
            pass.SetPipeline(pipeline);
            pass.SetBindGroup(0, bindGroup0);
            pass.SetBindGroup(1, bindGroup1);
            pass.End();

            // No Error is expected because draw was never called
            encoder.Finish();
        });
}

constexpr const char* kComputeTwoStorageTexturesOneBindgroupWGSL = R"(
    @group(0) @binding(0) var tex0 : texture_storage_2d<rgba8unorm, write>;
    @group(0) @binding(1) var tex1 : texture_storage_2d<rgba8unorm, write>;

    @compute @workgroup_size(1)
    fn cs() {
        _ = tex0;
        _ = tex1;
    }
)";

constexpr const char* kComputeTwoStorageTexturesTwoBindgroupsWGSL = R"(
    @group(0) @binding(0) var tex0 : texture_storage_2d<rgba8unorm, write>;
    @group(1) @binding(0) var tex1 : texture_storage_2d<rgba8unorm, write>;

    @compute @workgroup_size(1)
    fn cs() {
        _ = tex0;
        _ = tex1;
    }
)";

// Test we get a validation error if we have 2 different views of a storage texture
// in the same bind group. Unless FlexibleTextureViews is enabled.
TEST_P(CompatTextureViewValidationTests,
       CanNotComputeWithDifferentMipsSameStorageTextureSameBindGroup) {
    TestMultipleTextureViewValidationInComputePass(
        device, kComputeTwoStorageTexturesOneBindgroupWGSL, wgpu::TextureUsage::StorageBinding,
        [this](wgpu::Device device, wgpu::Texture texture, wgpu::ComputePipeline pipeline,
               std::function<void(wgpu::ComputePassEncoder pass)> dispatchFn) {
            wgpu::TextureViewDescriptor mip0ViewDesc;
            mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
            mip0ViewDesc.baseMipLevel = 0;
            mip0ViewDesc.mipLevelCount = 1;

            wgpu::TextureViewDescriptor mip1ViewDesc;
            mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
            mip1ViewDesc.baseMipLevel = 1;
            mip1ViewDesc.mipLevelCount = 1;

            wgpu::BindGroup bindGroup = utils::MakeBindGroup(
                device, pipeline.GetBindGroupLayout(0),
                {{0, texture.CreateView(&mip0ViewDesc)}, {1, texture.CreateView(&mip1ViewDesc)}});

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

            wgpu::ComputePassEncoder pass = encoder.BeginComputePass({});
            pass.SetPipeline(pipeline);
            pass.SetBindGroup(0, bindGroup);
            dispatchFn(pass);
            pass.End();

            ASSERT_TEXTURE_VIEW_ERROR_IF_NO_FLEXIBLE_FEATURE(encoder.Finish(),
                                                             testing::HasSubstr("different views"));
        });
}

// Test we get a validation error if we have 2 different views of a texture spanning
// different bind groups. Unless FlexibleTextureViews is enabled.
TEST_P(CompatTextureViewValidationTests,
       CanNotComputeWithDifferentMipsSameStorageTextureDifferentBindGroups) {
    TestMultipleTextureViewValidationInComputePass(
        device, kComputeTwoStorageTexturesTwoBindgroupsWGSL, wgpu::TextureUsage::StorageBinding,
        [this](wgpu::Device device, wgpu::Texture texture, wgpu::ComputePipeline pipeline,
               std::function<void(wgpu::ComputePassEncoder pass)> dispatchFn) {
            wgpu::TextureViewDescriptor mip0ViewDesc;
            mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
            mip0ViewDesc.baseMipLevel = 0;
            mip0ViewDesc.mipLevelCount = 1;

            wgpu::TextureViewDescriptor mip1ViewDesc;
            mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
            mip1ViewDesc.baseMipLevel = 1;
            mip1ViewDesc.mipLevelCount = 1;

            wgpu::BindGroup bindGroup0 = utils::MakeBindGroup(
                device, pipeline.GetBindGroupLayout(0), {{0, texture.CreateView(&mip0ViewDesc)}});

            wgpu::BindGroup bindGroup1 = utils::MakeBindGroup(
                device, pipeline.GetBindGroupLayout(1), {{0, texture.CreateView(&mip1ViewDesc)}});

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

            wgpu::ComputePassEncoder pass = encoder.BeginComputePass({});
            pass.SetPipeline(pipeline);
            pass.SetBindGroup(0, bindGroup0);
            pass.SetBindGroup(1, bindGroup1);
            dispatchFn(pass);
            pass.End();

            ASSERT_TEXTURE_VIEW_ERROR_IF_NO_FLEXIBLE_FEATURE(encoder.Finish(),
                                                             testing::HasSubstr("different views"));
        });
}

// Test that it's possible to set a bindgroup that uses a texture with multiple views
// which would be an error if you issued a draw command but, you then fix the issue by replacing
// the bindgroup with one that does not have multiple views. We're trying to test
// that the implementation does the validation at draw command time and not before.
TEST_P(CompatTextureViewValidationTests,
       CanBindDifferentMipsSameStorageTextureSameBindGroupAndFixWithoutErrorInComputePass) {
    TestMultipleTextureViewValidationInComputePass(
        device, kComputeTwoStorageTexturesOneBindgroupWGSL, wgpu::TextureUsage::StorageBinding,
        [](wgpu::Device device, wgpu::Texture texture, wgpu::ComputePipeline pipeline,
           std::function<void(wgpu::ComputePassEncoder pass)> dispatchFn) {
            wgpu::TextureViewDescriptor mip0ViewDesc;
            mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
            mip0ViewDesc.baseMipLevel = 0;
            mip0ViewDesc.mipLevelCount = 1;

            wgpu::TextureViewDescriptor mip1ViewDesc;
            mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
            mip1ViewDesc.baseMipLevel = 1;
            mip1ViewDesc.mipLevelCount = 1;

            // Bindgroup with different views of same texture
            wgpu::BindGroup badBindGroup = utils::MakeBindGroup(
                device, pipeline.GetBindGroupLayout(0),
                {{0, texture.CreateView(&mip0ViewDesc)}, {1, texture.CreateView(&mip1ViewDesc)}});

            wgpu::TextureDescriptor descriptor;
            descriptor.size = {2, 1, 1};
            descriptor.mipLevelCount = 2;
            descriptor.dimension = wgpu::TextureDimension::e2D;
            descriptor.format = wgpu::TextureFormat::RGBA8Unorm;
            descriptor.usage = wgpu::TextureUsage::StorageBinding;
            wgpu::Texture texture2 = device.CreateTexture(&descriptor);

            // Bindgroup with same views of texture
            wgpu::BindGroup goodBindGroup = utils::MakeBindGroup(
                device, pipeline.GetBindGroupLayout(0),
                {{0, texture.CreateView(&mip0ViewDesc)}, {1, texture2.CreateView(&mip0ViewDesc)}});

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

            wgpu::ComputePassEncoder pass = encoder.BeginComputePass({});
            pass.SetPipeline(pipeline);
            pass.SetBindGroup(0, badBindGroup);
            pass.SetBindGroup(0, goodBindGroup);
            dispatchFn(pass);
            pass.End();

            // No Error is expected
            encoder.Finish();
        });
}

// Test that no validation error happens if we have multiple views of a texture
// but don't draw.
TEST_P(CompatTextureViewValidationTests,
       NoErrorIfMultipleDifferentViewsOfStorageTextureAreNotUsedInComputePass) {
    TestMultipleTextureViewValidationInComputePass(
        device, kComputeTwoStorageTexturesTwoBindgroupsWGSL, wgpu::TextureUsage::StorageBinding,
        [](wgpu::Device device, wgpu::Texture texture, wgpu::ComputePipeline pipeline,
           std::function<void(wgpu::ComputePassEncoder pass)> dispatchFn) {
            wgpu::TextureViewDescriptor mip0ViewDesc;
            mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
            mip0ViewDesc.mipLevelCount = 1;

            wgpu::TextureViewDescriptor mip1ViewDesc;
            mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
            mip1ViewDesc.baseMipLevel = 1;
            mip1ViewDesc.mipLevelCount = 1;

            // Bindgroup with different views of same texture
            wgpu::BindGroup bindGroup0 = utils::MakeBindGroup(
                device, pipeline.GetBindGroupLayout(0), {{0, texture.CreateView(&mip0ViewDesc)}});

            // Bindgroup with same views of texture
            wgpu::BindGroup bindGroup1 = utils::MakeBindGroup(
                device, pipeline.GetBindGroupLayout(1), {{0, texture.CreateView(&mip1ViewDesc)}});

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

            wgpu::ComputePassEncoder pass = encoder.BeginComputePass({});
            pass.SetPipeline(pipeline);
            pass.SetBindGroup(0, bindGroup0);
            pass.SetBindGroup(1, bindGroup1);
            pass.End();

            // No Error is expected because draw was never called
            encoder.Finish();
        });
}

TEST_F(CompatValidationTest, CanNotCreateBGRA8UnormSRGBTexture) {
    wgpu::TextureDescriptor descriptor;
    descriptor.size = {1, 1, 1};
    descriptor.dimension = wgpu::TextureDimension::e2D;
    descriptor.format = wgpu::TextureFormat::BGRA8UnormSrgb;
    descriptor.usage = wgpu::TextureUsage::TextureBinding;

    ASSERT_DEVICE_ERROR(device.CreateTexture(&descriptor),
                        testing::HasSubstr("not supported in compatibility mode"));
}

TEST_F(CompatValidationTest, CanNotCreateBGRA8UnormTextureWithBGRA8UnormSrgbView) {
    constexpr wgpu::TextureFormat viewFormat = wgpu::TextureFormat::BGRA8UnormSrgb;

    wgpu::TextureDescriptor descriptor;
    descriptor.size = {1, 1, 1};
    descriptor.dimension = wgpu::TextureDimension::e2D;
    descriptor.format = wgpu::TextureFormat::BGRA8Unorm;
    descriptor.usage = wgpu::TextureUsage::TextureBinding;
    descriptor.viewFormatCount = 1;
    descriptor.viewFormats = &viewFormat;

    ASSERT_DEVICE_ERROR(device.CreateTexture(&descriptor),
                        testing::HasSubstr("not supported in compatibility mode"));
}

TEST_F(CompatValidationTest, CanNotCopyMultisampleTextureToTexture) {
    wgpu::TextureDescriptor srcDescriptor;
    srcDescriptor.size = {4, 4, 1};
    srcDescriptor.dimension = wgpu::TextureDimension::e2D;
    srcDescriptor.format = wgpu::TextureFormat::RGBA8Unorm;
    srcDescriptor.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment;
    srcDescriptor.sampleCount = 4;
    wgpu::Texture srcTexture = device.CreateTexture(&srcDescriptor);

    wgpu::TextureDescriptor dstDescriptor;
    dstDescriptor.size = {4, 4, 1};
    dstDescriptor.dimension = wgpu::TextureDimension::e2D;
    dstDescriptor.format = wgpu::TextureFormat::RGBA8Unorm;
    dstDescriptor.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment;
    dstDescriptor.sampleCount = 4;
    wgpu::Texture dstTexture = device.CreateTexture(&dstDescriptor);

    wgpu::TexelCopyTextureInfo source = utils::CreateTexelCopyTextureInfo(srcTexture);
    wgpu::TexelCopyTextureInfo destination = utils::CreateTexelCopyTextureInfo(dstTexture);

    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
    encoder.CopyTextureToTexture(&source, &destination, &srcDescriptor.size);
    ASSERT_DEVICE_ERROR(encoder.Finish(),
                        testing::HasSubstr("cannot be copied in compatibility mode"));
}

// Regression test for crbug.com/339704108
// Error texture should not resolve mCompatibilityTextureBindingViewDimension,
// as dimension could be in bad form.
TEST_P(CompatTextureViewValidationTests,
       DoNotResolveDefaultTextureBindingViewDimensionOnErrorTexture) {
    // Set incompatible texture format and view format.
    // This validation happens before texture dimension validation and binding view dimension
    // resolving and shall return an error texture.
    constexpr wgpu::TextureFormat format = wgpu::TextureFormat::BGRA8Unorm;
    constexpr wgpu::TextureFormat viewFormat = wgpu::TextureFormat::RGBA8UnormSrgb;

    wgpu::TextureDescriptor descriptor;
    descriptor.size = {1, 1, 1};
    descriptor.dimension = wgpu::TextureDimension::Undefined;
    descriptor.format = format;
    descriptor.usage = wgpu::TextureUsage::TextureBinding;
    descriptor.viewFormatCount = 1;
    descriptor.viewFormats = &viewFormat;

    ASSERT_DEVICE_ERROR(device.CreateTexture(&descriptor));
}

// Regression test for crbug.com/341167195
// Resolved default compatibility textureBindingViewDimension should be validated as it may come
// from the TextureBindingViewDimension
TEST_P(CompatTextureViewValidationTests, InvalidTextureBindingViewDimensionDescriptorDescriptor) {
    wgpu::TextureDescriptor descriptor;
    descriptor.size = {1, 1, 1};
    descriptor.dimension = wgpu::TextureDimension::Undefined;
    descriptor.format = wgpu::TextureFormat::RGBA8Unorm;
    descriptor.usage = wgpu::TextureUsage::TextureBinding;

    wgpu::TextureBindingViewDimension textureBindingViewDimensionDesc;
    descriptor.nextInChain = &textureBindingViewDimensionDesc;
    // Forcefully set an invalid view dimension.
    textureBindingViewDimensionDesc.textureBindingViewDimension =
        static_cast<wgpu::TextureViewDimension>(99);

    ASSERT_TEXTURE_VIEW_ERROR_IF_NO_FLEXIBLE_FEATURE(device.CreateTexture(&descriptor), testing::_);
}

class CompatTextureViewDimensionValidationTests : public CompatTextureViewValidationTests {
  protected:
    void TestBindingTextureViewDimensions(
        const uint32_t depth,
        const wgpu::TextureViewDimension textureBindingViewDimension,
        const wgpu::TextureViewDimension viewDimension,
        bool success) {
        wgpu::BindGroupLayout layout = utils::MakeBindGroupLayout(
            device, {{0, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float,
                      viewDimension == wgpu::TextureViewDimension::Undefined
                          ? wgpu::TextureViewDimension::e2D
                          : viewDimension}});

        wgpu::Texture texture = CreateTextureWithViewDimension(depth, wgpu::TextureDimension::e2D,
                                                               textureBindingViewDimension);

        wgpu::TextureViewDescriptor viewDesc = {};
        viewDesc.dimension = viewDimension;

        if (success) {
            utils::MakeBindGroup(device, layout, {{0, texture.CreateView(&viewDesc)}});
        } else {
            ASSERT_DEVICE_ERROR(
                utils::MakeBindGroup(device, layout, {{0, texture.CreateView(&viewDesc)}}),
                testing::HasSubstr("must match textureBindingViewDimension"));
        }

        texture.Destroy();
    }

    void TestCreateTextureWithViewDimensionImpl(
        const uint32_t depth,
        const wgpu::TextureDimension dimension,
        const wgpu::TextureViewDimension textureBindingViewDimension,
        bool success,
        const char* expectedSubstr) {
        if (success) {
            CreateTextureWithViewDimension(depth, dimension, textureBindingViewDimension);
        } else {
            ASSERT_DEVICE_ERROR(
                CreateTextureWithViewDimension(depth, dimension, textureBindingViewDimension),
                testing::HasSubstr(expectedSubstr));
        }
    }

    void TestCreateTextureIsCompatibleWithViewDimension(
        const uint32_t depth,
        const wgpu::TextureDimension dimension,
        const wgpu::TextureViewDimension textureBindingViewDimension,
        bool success) {
        TestCreateTextureWithViewDimensionImpl(depth, dimension, textureBindingViewDimension,
                                               success, "is not compatible with the dimension");
    }

    void TestCreateTextureLayersIsCompatibleWithViewDimension(
        const uint32_t depth,
        const wgpu::TextureDimension dimension,
        const wgpu::TextureViewDimension textureBindingViewDimension,
        bool success) {
        TestCreateTextureWithViewDimensionImpl(depth, dimension, textureBindingViewDimension,
                                               success,
                                               "is only compatible with depthOrArrayLayers equals");
    }

    wgpu::Texture CreateTextureWithViewDimension(
        const uint32_t depth,
        const wgpu::TextureDimension dimension,
        const wgpu::TextureViewDimension textureBindingViewDimension) {
        constexpr wgpu::TextureFormat viewFormat = wgpu::TextureFormat::RGBA8Unorm;

        wgpu::TextureDescriptor textureDesc;
        textureDesc.size = {1, 1, depth};
        textureDesc.dimension = dimension;
        textureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
        textureDesc.usage = wgpu::TextureUsage::TextureBinding;
        textureDesc.viewFormatCount = 1;
        textureDesc.viewFormats = &viewFormat;

        wgpu::TextureBindingViewDimension textureBindingViewDimensionDesc;

        if (textureBindingViewDimension != wgpu::TextureViewDimension::Undefined) {
            textureDesc.nextInChain = &textureBindingViewDimensionDesc;
            textureBindingViewDimensionDesc.textureBindingViewDimension =
                textureBindingViewDimension;
        }

        return device.CreateTexture(&textureDesc);
    }
};

// Note: CubeArray is not included because CubeArray is not allowed
// in compatibility mode.
const wgpu::TextureViewDimension kViewDimensions[] = {
    wgpu::TextureViewDimension::e1D,  wgpu::TextureViewDimension::e2D,
    wgpu::TextureViewDimension::e3D,  wgpu::TextureViewDimension::e2DArray,
    wgpu::TextureViewDimension::Cube,
};

// Test creating 1d textures with each view dimension. Unless FlexibleTextureViews is enabled.
TEST_P(CompatTextureViewDimensionValidationTests, E1D) {
    for (auto viewDimension : kViewDimensions) {
        TestCreateTextureIsCompatibleWithViewDimension(
            1, wgpu::TextureDimension::e1D, viewDimension,
            HasFlexibleTextureViews() || viewDimension == wgpu::TextureViewDimension::e1D);
    }
}

// Test creating 2d textures with each view dimension. Unless FlexibleTextureViews is enabled.
TEST_P(CompatTextureViewDimensionValidationTests, E2D) {
    for (auto viewDimension : kViewDimensions) {
        TestCreateTextureIsCompatibleWithViewDimension(
            viewDimension == wgpu::TextureViewDimension::e2D ? 1 : 6, wgpu::TextureDimension::e2D,
            viewDimension,
            HasFlexibleTextureViews() || (viewDimension != wgpu::TextureViewDimension::e1D &&
                                          viewDimension != wgpu::TextureViewDimension::e3D));
    }
}

// Test creating 1d textures with each view dimension. Unless FlexibleTextureViews is enabled.
TEST_P(CompatTextureViewDimensionValidationTests, E3D) {
    for (auto viewDimension : kViewDimensions) {
        TestCreateTextureIsCompatibleWithViewDimension(
            1, wgpu::TextureDimension::e3D, viewDimension,
            HasFlexibleTextureViews() || viewDimension == wgpu::TextureViewDimension::e3D);
    }
}

// Test creating a 2d texture with a 2d view and depthOrArrayLayers > 1 fails. Unless
// FlexibleTextureViews is enabled.
TEST_P(CompatTextureViewDimensionValidationTests, E2DViewMoreThan1Layer) {
    TestCreateTextureLayersIsCompatibleWithViewDimension(
        2, wgpu::TextureDimension::e2D, wgpu::TextureViewDimension::e2D, HasFlexibleTextureViews());
}

// Test creating a 2d texture with a cube view with depthOrArrayLayers != 6 fails. Unless
// FlexibleTextureViews is enabled.
TEST_P(CompatTextureViewDimensionValidationTests, CubeViewMoreWhereLayersIsNot6) {
    uint32_t layers[] = {1, 5, 6, 7, 12};
    for (auto numLayers : layers) {
        TestCreateTextureLayersIsCompatibleWithViewDimension(
            numLayers, wgpu::TextureDimension::e2D, wgpu::TextureViewDimension::Cube,
            HasFlexibleTextureViews() || numLayers == 6);
    }
}

TEST_P(CompatTextureViewDimensionValidationTests, OneLayerIs2DView) {
    TestBindingTextureViewDimensions(1, wgpu::TextureViewDimension::Undefined,
                                     wgpu::TextureViewDimension::e2D, true);
}

// Test 2 layer texture gets a 2d-array viewDimension
TEST_P(CompatTextureViewDimensionValidationTests, TwoLayersIs2DArrayView) {
    TestBindingTextureViewDimensions(2, wgpu::TextureViewDimension::Undefined,
                                     wgpu::TextureViewDimension::e2DArray, true);
}

// Test 6 layer texture gets a 2d-array viewDimension
TEST_P(CompatTextureViewDimensionValidationTests, SixLayersIs2DArrayView) {
    TestBindingTextureViewDimensions(6, wgpu::TextureViewDimension::Undefined,
                                     wgpu::TextureViewDimension::e2DArray, true);
}

// Test 2d texture can not be viewed as 2D array. Unless FlexibleTextureViews is enabled.
TEST_P(CompatTextureViewDimensionValidationTests, TwoDTextureViewDimensionCanNotBeViewedAs2DArray) {
    TestBindingTextureViewDimensions(1, wgpu::TextureViewDimension::e2D,
                                     wgpu::TextureViewDimension::e2DArray,
                                     HasFlexibleTextureViews());
}

// Test 2d-array texture can not be viewed as cube. Unless FlexibleTextureViews is enabled.
TEST_P(CompatTextureViewDimensionValidationTests,
       TwoDArrayTextureViewDimensionCanNotBeViewedAsCube) {
    TestBindingTextureViewDimensions(6, wgpu::TextureViewDimension::e2DArray,
                                     wgpu::TextureViewDimension::Cube, HasFlexibleTextureViews());
}

// Test cube texture can not be viewed as 2d-array. Unless FlexibleTextureViews is enabled.
TEST_P(CompatTextureViewDimensionValidationTests, CubeTextureViewDimensionCanNotBeViewedAs2DArray) {
    TestBindingTextureViewDimensions(6, wgpu::TextureViewDimension::Cube,
                                     wgpu::TextureViewDimension::e2DArray,
                                     HasFlexibleTextureViews());
}

TEST_P(CompatTextureViewValidationTests, CanNotDrawDifferentAspectSameTextureSameBindGroup) {
    TestMultipleTextureViewValidationInRenderPass(
        device, wgpu::TextureFormat::Depth24PlusStencil8, kSampleDepthSampleStencilOneBindgroupWGSL,
        [this](wgpu::Device device, wgpu::Texture texture, wgpu::RenderPipeline pipeline,
               std::function<void(wgpu::RenderPassEncoder pass)> drawFn) {
            wgpu::TextureViewDescriptor viewDesc1;
            viewDesc1.dimension = wgpu::TextureViewDimension::e2D;
            viewDesc1.aspect = wgpu::TextureAspect::DepthOnly;

            wgpu::TextureViewDescriptor viewDesc2;
            viewDesc2.dimension = wgpu::TextureViewDimension::e2D;
            viewDesc2.aspect = wgpu::TextureAspect::StencilOnly;

            wgpu::BindGroup bindGroup = utils::MakeBindGroup(
                device, pipeline.GetBindGroupLayout(0),
                {{0, texture.CreateView(&viewDesc1)}, {1, texture.CreateView(&viewDesc2)}});

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

            utils::BasicRenderPass rp = utils::CreateBasicRenderPass(device, 4, 1);
            wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo);
            pass.SetPipeline(pipeline);
            pass.SetBindGroup(0, bindGroup);
            drawFn(pass);
            pass.End();

            ASSERT_TEXTURE_VIEW_ERROR_IF_NO_FLEXIBLE_FEATURE(encoder.Finish(),
                                                             testing::HasSubstr("different views"));
        });
}

// Test 2Darray != 2d
// Test cube !== 2d
// Test cube !== 2d-array

class CompatCompressedCopyT2BAndCopyT2TValidationTests : public CompatValidationTest {
  protected:
    std::vector<wgpu::FeatureName> GetRequiredFeatures() override {
        std::vector<wgpu::FeatureName> requiredFeatures;
        for (TextureInfo textureInfo : textureInfos) {
            if (adapter.HasFeature(textureInfo.feature)) {
                requiredFeatures.push_back(textureInfo.feature);
            }
        }
        return requiredFeatures;
    }

    struct TextureInfo {
        wgpu::FeatureName feature;
        wgpu::TextureFormat format;
    };
    static constexpr TextureInfo textureInfos[] = {
        {
            wgpu::FeatureName::TextureCompressionBC,
            wgpu::TextureFormat::BC2RGBAUnorm,
        },
        {
            wgpu::FeatureName::TextureCompressionETC2,
            wgpu::TextureFormat::ETC2RGB8Unorm,
        },
        {
            wgpu::FeatureName::TextureCompressionASTC,
            wgpu::TextureFormat::ASTC4x4Unorm,
        },
    };
};

TEST_F(CompatCompressedCopyT2BAndCopyT2TValidationTests, CanNotCopyCompressedTextureToBuffer) {
    for (TextureInfo textureInfo : textureInfos) {
        if (!device.HasFeature(textureInfo.feature)) {
            continue;
        }

        wgpu::TextureDescriptor descriptor;
        descriptor.size = {4, 4, 1};
        descriptor.dimension = wgpu::TextureDimension::e2D;
        descriptor.format = textureInfo.format;
        descriptor.usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopySrc;
        wgpu::Texture texture = device.CreateTexture(&descriptor);

        wgpu::BufferDescriptor bufferDescriptor;
        bufferDescriptor.size = 256 * 4;
        bufferDescriptor.usage = wgpu::BufferUsage::CopyDst;
        wgpu::Buffer buffer = device.CreateBuffer(&bufferDescriptor);

        wgpu::TexelCopyTextureInfo source = utils::CreateTexelCopyTextureInfo(texture);
        wgpu::TexelCopyBufferInfo destination = utils::CreateTexelCopyBufferInfo(buffer, 0, 256, 4);

        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
        encoder.CopyTextureToBuffer(&source, &destination, &descriptor.size);
        ASSERT_DEVICE_ERROR(encoder.Finish(), testing::HasSubstr("cannot be used"));
    }
}

TEST_F(CompatCompressedCopyT2BAndCopyT2TValidationTests, CanNotCopyCompressedTextureToTexture) {
    for (TextureInfo textureInfo : textureInfos) {
        if (!device.HasFeature(textureInfo.feature)) {
            continue;
        }

        wgpu::TextureDescriptor descriptor;
        descriptor.size = {4, 4, 1};
        descriptor.dimension = wgpu::TextureDimension::e2D;
        descriptor.format = textureInfo.format;
        descriptor.usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopySrc;
        wgpu::Texture srcTexture = device.CreateTexture(&descriptor);

        descriptor.usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopyDst;
        wgpu::Texture dstTexture = device.CreateTexture(&descriptor);

        wgpu::TexelCopyTextureInfo source = utils::CreateTexelCopyTextureInfo(srcTexture);
        wgpu::TexelCopyTextureInfo destination = utils::CreateTexelCopyTextureInfo(dstTexture);

        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
        encoder.CopyTextureToTexture(&source, &destination, &descriptor.size);
        ASSERT_DEVICE_ERROR(encoder.Finish(), testing::HasSubstr("cannot be used"));
    }
}

class CompatMaxVertexAttributesTest : public CompatValidationTest {
  protected:
    void TestMaxVertexAttributes(bool usesVertexIndex, bool usesInstanceIndex) {
        wgpu::Limits limits;
        device.GetLimits(&limits);

        uint32_t maxAttributes = limits.maxVertexAttributes;
        uint32_t numAttributesUsedByBuiltins =
            (usesVertexIndex ? 1 : 0) + (usesInstanceIndex ? 1 : 0);

        TestAttributes(maxAttributes - numAttributesUsedByBuiltins, usesVertexIndex,
                       usesInstanceIndex, true);
        if (usesVertexIndex || usesInstanceIndex) {
            TestAttributes(maxAttributes - numAttributesUsedByBuiltins + 1, usesVertexIndex,
                           usesInstanceIndex, false);
        }
    }

    void TestAttributes(uint32_t numAttributes,
                        bool usesVertexIndex,
                        bool usesInstanceIndex,
                        bool expectSuccess) {
        std::vector<std::string> inputs;
        std::vector<std::string> outputs;

        utils::ComboRenderPipelineDescriptor descriptor;
        descriptor.layout = {};
        descriptor.vertex.bufferCount = 1;
        descriptor.cBuffers[0].arrayStride = 16;
        descriptor.cBuffers[0].attributeCount = numAttributes;

        for (uint32_t i = 0; i < numAttributes; ++i) {
            inputs.push_back(absl::StrFormat("@location(%u) v%u: vec4f", i, i));
            outputs.push_back(absl::StrFormat("v%u", i));
            descriptor.cAttributes[i].format = wgpu::VertexFormat::Float32x4;
            descriptor.cAttributes[i].shaderLocation = i;
        }

        if (usesVertexIndex) {
            inputs.push_back("@builtin(vertex_index) vNdx: u32");
            outputs.push_back("vec4f(f32(vNdx))");
        }

        if (usesInstanceIndex) {
            inputs.push_back("@builtin(instance_index) iNdx: u32");
            outputs.push_back("vec4f(f32(iNdx))");
        }

        auto wgsl = absl::StrFormat(R"(
            @fragment fn fs() -> @location(0) vec4f {
                return vec4f(1);
            }
            @vertex fn vs(%s) -> @builtin(position) vec4f {
                return %s;
            }
            )",
                                    absl::StrJoin(inputs, ", "), absl::StrJoin(outputs, " + "));

        wgpu::ShaderModule module = utils::CreateShaderModule(device, wgsl.c_str());
        descriptor.vertex.module = module;
        descriptor.cFragment.module = module;

        if (expectSuccess) {
            device.CreateRenderPipeline(&descriptor);
        } else {
            ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor),
                                testing::HasSubstr("compat"));
        }
    }
};

TEST_F(CompatMaxVertexAttributesTest, CanUseMaxVertexAttributes) {
    TestMaxVertexAttributes(false, false);
}

TEST_F(CompatMaxVertexAttributesTest, VertexIndexTakesAnAttribute) {
    TestMaxVertexAttributes(true, false);
}

TEST_F(CompatMaxVertexAttributesTest, InstanceIndexTakesAnAttribute) {
    TestMaxVertexAttributes(false, true);
}

TEST_F(CompatMaxVertexAttributesTest, VertexAndInstanceIndexEachTakeAnAttribute) {
    TestMaxVertexAttributes(true, true);
}

enum class SwizzleChannel {
    Red,
    Green,
    Blue,
    Alpha,
};

class CompatTextureViewSwizzleValidationTests : public CompatTextureViewValidationTests {
  protected:
    std::vector<wgpu::FeatureName> GetRequiredFeatures() override {
        std::vector<wgpu::FeatureName> requiredFeatures =
            CompatTextureViewValidationTests::GetRequiredFeatures();
        requiredFeatures.push_back(wgpu::FeatureName::TextureComponentSwizzle);
        return requiredFeatures;
    }

    void SetSwizzleChannel(wgpu::TextureComponentSwizzle& swizzle,
                           SwizzleChannel channel,
                           wgpu::ComponentSwizzle value) {
        switch (channel) {
            case SwizzleChannel::Red:
                swizzle.r = value;
                break;
            case SwizzleChannel::Green:
                swizzle.g = value;
                break;
            case SwizzleChannel::Blue:
                swizzle.b = value;
                break;
            case SwizzleChannel::Alpha:
                swizzle.a = value;
                break;
            default:
                DAWN_UNREACHABLE();
        }
    }
};

// Test we get a validation error if we have 2 different swizzles of a texture
// in the same bind group. Unless FlexibleTextureViews is enabled.
TEST_P(CompatTextureViewSwizzleValidationTests,
       CanNotDrawDifferentSwizzleSameTextureSameBindGroup) {
    for (auto swizzleChannel : {SwizzleChannel::Red, SwizzleChannel::Green, SwizzleChannel::Blue,
                                SwizzleChannel::Alpha}) {
        TestMultipleTextureViewValidationInRenderPass(
            device, wgpu::TextureFormat::RGBA8Unorm, kRenderTwoTexturesOneBindgroupWGSL,
            [this, &swizzleChannel](wgpu::Device device, wgpu::Texture texture,
                                    wgpu::RenderPipeline pipeline,
                                    std::function<void(wgpu::RenderPassEncoder pass)> drawFn) {
                wgpu::TextureViewDescriptor viewDesc1;
                wgpu::TextureComponentSwizzleDescriptor swizzleDesc1 = {};
                SetSwizzleChannel(swizzleDesc1.swizzle, swizzleChannel,
                                  wgpu::ComponentSwizzle::Zero);
                viewDesc1.nextInChain = &swizzleDesc1;

                wgpu::TextureViewDescriptor viewDesc2;
                wgpu::TextureComponentSwizzleDescriptor swizzleDesc2 = {};
                SetSwizzleChannel(swizzleDesc2.swizzle, swizzleChannel,
                                  wgpu::ComponentSwizzle::One);
                viewDesc2.nextInChain = &swizzleDesc2;

                wgpu::BindGroup bindGroup = utils::MakeBindGroup(
                    device, pipeline.GetBindGroupLayout(0),
                    {{0, texture.CreateView(&viewDesc1)}, {1, texture.CreateView(&viewDesc2)}});

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

                utils::BasicRenderPass rp = utils::CreateBasicRenderPass(device, 4, 1);
                wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo);
                pass.SetPipeline(pipeline);
                pass.SetBindGroup(0, bindGroup);
                drawFn(pass);
                pass.End();

                ASSERT_TEXTURE_VIEW_ERROR_IF_NO_FLEXIBLE_FEATURE(
                    encoder.Finish(), testing::HasSubstr("different views"));
            });
    }
}

INSTANTIATE_TEST_SUITE_P(,
                         CompatTextureViewValidationTests,
                         ::testing::Values(FlexibleTextureViewsFeature::Disabled,
                                           FlexibleTextureViewsFeature::Enabled),
                         CompatTextureViewValidationTests::PrintToStringParamName);

INSTANTIATE_TEST_SUITE_P(,
                         CompatTextureViewDimensionValidationTests,
                         ::testing::Values(FlexibleTextureViewsFeature::Disabled,
                                           FlexibleTextureViewsFeature::Enabled),
                         CompatTextureViewValidationTests::PrintToStringParamName);

INSTANTIATE_TEST_SUITE_P(,
                         CompatTextureViewSwizzleValidationTests,
                         ::testing::Values(FlexibleTextureViewsFeature::Disabled,
                                           FlexibleTextureViewsFeature::Enabled),
                         CompatTextureViewValidationTests::PrintToStringParamName);

class CompatLayoutLimitsTests : public CompatValidationTest {
  protected:
    void GetRequiredLimits(const dawn::utils::ComboLimits& supported,
                           dawn::utils::ComboLimits& required) override {
        required.maxStorageBuffersInFragmentStage = supported.maxStorageBuffersInFragmentStage / 2;
        required.maxStorageBuffersInVertexStage = supported.maxStorageBuffersInVertexStage / 2;
        required.maxStorageTexturesInFragmentStage =
            supported.maxStorageTexturesInFragmentStage / 2;
        required.maxStorageTexturesInVertexStage = supported.maxStorageTexturesInVertexStage / 2;
        required.maxStorageBuffersPerShaderStage = supported.maxStorageBuffersPerShaderStage;
        required.maxStorageTexturesPerShaderStage = supported.maxStorageTexturesPerShaderStage;
    }

    void DoBindGroupLayoutTest(uint32_t limitInStage,
                               uint32_t limitPerStage,
                               const wgpu::BindGroupLayoutEntry& entry,
                               const char* expectedErrorSubstring) {
        EXPECT_TRUE(limitInStage > 0);
        EXPECT_TRUE(limitInStage < limitPerStage);

        std::vector<wgpu::BindGroupLayoutEntry> entries(limitInStage + 1);
        for (size_t i = 0; i < entries.size(); ++i) {
            entries[i] = entry;
            entries[i].binding = i;
        }

        wgpu::BindGroupLayoutDescriptor descriptor;
        descriptor.entryCount = entries.size();
        descriptor.entries = entries.data();
        ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&descriptor),
                            testing::HasSubstr(expectedErrorSubstring));
    }

    void DoPipelineLayoutTest(uint32_t limitInStage,
                              uint32_t limitPerStage,
                              const wgpu::BindGroupLayoutEntry& entry,
                              const char* expectedErrorSubstring) {
        EXPECT_TRUE(limitInStage > 0);
        EXPECT_TRUE(limitInStage < limitPerStage);

        wgpu::BindGroupLayout bgls[2];

        std::vector<wgpu::BindGroupLayoutEntry> entries(limitInStage);
        for (size_t i = 0; i < entries.size(); ++i) {
            entries[i] = entry;
            entries[i].binding = i;
        }

        wgpu::BindGroupLayoutDescriptor descriptor;
        descriptor.entryCount = entries.size();
        descriptor.entries = entries.data();
        bgls[0] = device.CreateBindGroupLayout(&descriptor);

        descriptor.entryCount = 1;
        descriptor.entries = entries.data();
        bgls[1] = device.CreateBindGroupLayout(&descriptor);

        wgpu::PipelineLayoutDescriptor pipelineLayoutDescriptor = {};
        pipelineLayoutDescriptor.bindGroupLayoutCount = 2;
        pipelineLayoutDescriptor.bindGroupLayouts = bgls;

        ASSERT_DEVICE_ERROR(device.CreatePipelineLayout(&pipelineLayoutDescriptor),
                            testing::HasSubstr(expectedErrorSubstring));
    }
};

// Test that in compat we get an error if we use more than maxStorageBuffersInFragmentStage
// when it's lower than maxStorageBuffersPerShaderStage in createBindGroupLayout
TEST_F(CompatLayoutLimitsTests, CanNotPassLimitOfStorageBuffersInFragmentStageBindGroupLayout) {
    const auto& limits = GetSupportedLimits();
    wgpu::BindGroupLayoutEntry entry;
    entry.visibility = wgpu::ShaderStage::Fragment;
    entry.buffer.type = wgpu::BufferBindingType::ReadOnlyStorage;
    DoBindGroupLayoutTest(limits.maxStorageBuffersInFragmentStage,
                          limits.maxStorageBuffersPerShaderStage, entry,
                          "maxStorageBuffersInFragmentStage");
}

// Test that in compat we get an error if we use more than maxStorageBuffersInVertexStage
// when it's lower than maxStorageBuffersPerShaderStage in createBindGroupLayout
TEST_F(CompatLayoutLimitsTests, CanNotPassLimitOfStorageBuffersInVertexStageBindGroupLayout) {
    const auto& limits = GetSupportedLimits();
    wgpu::BindGroupLayoutEntry entry;
    entry.visibility = wgpu::ShaderStage::Vertex;
    entry.buffer.type = wgpu::BufferBindingType::ReadOnlyStorage;
    DoBindGroupLayoutTest(limits.maxStorageBuffersInVertexStage,
                          limits.maxStorageBuffersPerShaderStage, entry,
                          "maxStorageBuffersInVertexStage");
}

// Test that in compat we get an error if we use more than maxStorageTexturesInVertexStage
// when it's lower than maxStorageTexturesPerShaderStage in createBindGroupLayout
TEST_F(CompatLayoutLimitsTests, CanNotPassLimitOfStorageTexturesInVertexStageBindGroupLayout) {
    const auto& limits = GetSupportedLimits();
    wgpu::BindGroupLayoutEntry entry;
    entry.visibility = wgpu::ShaderStage::Vertex;
    entry.storageTexture.format = wgpu::TextureFormat::R32Float;
    entry.storageTexture.access = wgpu::StorageTextureAccess::ReadOnly;
    DoBindGroupLayoutTest(limits.maxStorageTexturesInVertexStage,
                          limits.maxStorageTexturesPerShaderStage, entry,
                          "maxStorageTexturesInVertexStage");
}

// Test that in compat we get an error if we use more than maxStorageBuffersInFragmentStage
// when it's lower than maxStorageBuffersPerShaderStage in createPipelineLayout
TEST_F(CompatLayoutLimitsTests, CanNotPassLimitOfStorageBuffersInFragmentStagePipelineLayout) {
    const auto& limits = GetSupportedLimits();
    wgpu::BindGroupLayoutEntry entry;
    entry.visibility = wgpu::ShaderStage::Fragment;
    entry.buffer.type = wgpu::BufferBindingType::ReadOnlyStorage;
    DoPipelineLayoutTest(limits.maxStorageBuffersInFragmentStage,
                         limits.maxStorageBuffersPerShaderStage, entry,
                         "maxStorageBuffersInFragmentStage");
}

// Test that in compat we get an error if we use more than maxStorageBuffersInVertexStage
// when it's lower than maxStorageBuffersPerShaderStage in createPipelineLayout
TEST_F(CompatLayoutLimitsTests, CanNotPassLimitOfStorageBuffersInVertexStagePipelineLayout) {
    const auto& limits = GetSupportedLimits();
    wgpu::BindGroupLayoutEntry entry;
    entry.visibility = wgpu::ShaderStage::Vertex;
    entry.buffer.type = wgpu::BufferBindingType::ReadOnlyStorage;
    DoPipelineLayoutTest(limits.maxStorageBuffersInVertexStage,
                         limits.maxStorageBuffersPerShaderStage, entry,
                         "maxStorageBuffersInVertexStage");
}

// Test that in compat we get an error if we use more than maxStorageTexturesInVertexStage
// when it's lower than maxStorageTexturesPerShaderStage in createPipelineLayout
TEST_F(CompatLayoutLimitsTests, CanNotPassLimitOfStorageTexturesInVertexStagePipelineLayout) {
    const auto& limits = GetSupportedLimits();
    wgpu::BindGroupLayoutEntry entry;
    entry.visibility = wgpu::ShaderStage::Vertex;
    entry.storageTexture.format = wgpu::TextureFormat::R32Float;
    entry.storageTexture.access = wgpu::StorageTextureAccess::ReadOnly;
    DoPipelineLayoutTest(limits.maxStorageTexturesInVertexStage,
                         limits.maxStorageTexturesPerShaderStage, entry,
                         "maxStorageTexturesInVertexStage");
}

}  // anonymous namespace
}  // namespace dawn
