// Copyright 2018 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 <array>
#include <vector>

#include "src/dawn/tests/unittests/validation/ValidationTest.h"
#include "src/dawn/utils/WGPUHelpers.h"

namespace dawn {
namespace {

class TextureViewValidationTest : public ValidationTest {};

constexpr uint32_t kWidth = 32u;
constexpr uint32_t kHeight = 32u;
constexpr uint32_t kDepth = 6u;
constexpr uint32_t kDefaultMipLevels = 6u;

constexpr wgpu::TextureFormat kDefaultTextureFormat = wgpu::TextureFormat::RGBA8Unorm;

wgpu::Texture Create2DArrayTexture(wgpu::Device& device,
                                   uint32_t arrayLayerCount,
                                   uint32_t width = kWidth,
                                   uint32_t height = kHeight,
                                   uint32_t mipLevelCount = kDefaultMipLevels,
                                   uint32_t sampleCount = 1) {
    wgpu::TextureDescriptor descriptor;
    descriptor.dimension = wgpu::TextureDimension::e2D;
    descriptor.size.width = width;
    descriptor.size.height = height;
    descriptor.size.depthOrArrayLayers = arrayLayerCount;
    descriptor.sampleCount = sampleCount;
    descriptor.format = kDefaultTextureFormat;
    descriptor.mipLevelCount = mipLevelCount;
    descriptor.usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment;
    return device.CreateTexture(&descriptor);
}

wgpu::Texture Create3DTexture(wgpu::Device& device) {
    wgpu::TextureDescriptor descriptor;
    descriptor.dimension = wgpu::TextureDimension::e3D;
    descriptor.size = {kWidth, kHeight, kDepth};
    descriptor.sampleCount = 1;
    descriptor.format = kDefaultTextureFormat;
    descriptor.mipLevelCount = kDefaultMipLevels;
    descriptor.usage = wgpu::TextureUsage::TextureBinding;
    return device.CreateTexture(&descriptor);
}

wgpu::Texture Create1DTexture(wgpu::Device& device) {
    wgpu::TextureDescriptor descriptor;
    descriptor.dimension = wgpu::TextureDimension::e1D;
    descriptor.size = {kWidth, 1, 1};
    descriptor.format = kDefaultTextureFormat;
    descriptor.usage = wgpu::TextureUsage::TextureBinding;
    return device.CreateTexture(&descriptor);
}

wgpu::Texture CreateDepthStencilTexture(wgpu::Device& device, wgpu::TextureFormat format) {
    wgpu::TextureDescriptor descriptor = {};
    descriptor.size = {kWidth, kHeight, kDepth};
    descriptor.usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment;
    descriptor.mipLevelCount = kDefaultMipLevels;
    descriptor.format = format;
    return device.CreateTexture(&descriptor);
}

wgpu::TextureViewDescriptor CreateDefaultViewDescriptor(wgpu::TextureViewDimension dimension) {
    wgpu::TextureViewDescriptor descriptor;
    descriptor.format = kDefaultTextureFormat;
    descriptor.dimension = dimension;
    descriptor.baseMipLevel = 0;
    if (dimension != wgpu::TextureViewDimension::e1D) {
        descriptor.mipLevelCount = kDefaultMipLevels;
    }
    descriptor.baseArrayLayer = 0;
    descriptor.arrayLayerCount = 1;
    return descriptor;
}

// Test creating texture view on a 2D non-array texture
TEST_F(TextureViewValidationTest, CreateTextureViewOnTexture2D) {
    wgpu::Texture texture = Create2DArrayTexture(device, 1);

    wgpu::TextureViewDescriptor base2DTextureViewDescriptor =
        CreateDefaultViewDescriptor(wgpu::TextureViewDimension::e2D);

    // It is an error to create a view with zero 'arrayLayerCount'.
    {
        wgpu::TextureViewDescriptor descriptor = base2DTextureViewDescriptor;
        descriptor.arrayLayerCount = 0;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
    }

    // It is an error to create a view with zero 'mipLevelCount'.
    {
        wgpu::TextureViewDescriptor descriptor = base2DTextureViewDescriptor;
        descriptor.mipLevelCount = 0;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
    }

    // It is OK to create a 2D texture view on a 2D texture.
    {
        wgpu::TextureViewDescriptor descriptor = base2DTextureViewDescriptor;
        descriptor.arrayLayerCount = 1;
        texture.CreateView(&descriptor);
    }

    // It is an error to view a layer past the end of the texture.
    {
        wgpu::TextureViewDescriptor descriptor = base2DTextureViewDescriptor;
        descriptor.arrayLayerCount = 2;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
    }

    // It is OK to create a 1-layer 2D array texture view on a 2D texture.
    {
        wgpu::TextureViewDescriptor descriptor = base2DTextureViewDescriptor;
        descriptor.dimension = wgpu::TextureViewDimension::e2DArray;
        descriptor.arrayLayerCount = 1;
        texture.CreateView(&descriptor);
    }

    // It is an error to create a 3D texture view on a 2D texture.
    {
        wgpu::TextureViewDescriptor descriptor = base2DTextureViewDescriptor;
        descriptor.dimension = wgpu::TextureViewDimension::e3D;
        descriptor.arrayLayerCount = 1;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
    }

    // baseMipLevel == k && mipLevelCount == WGPU_MIP_LEVEL_COUNT_UNDEFINED means to use levels
    // k..end.
    {
        wgpu::TextureViewDescriptor descriptor = base2DTextureViewDescriptor;
        descriptor.mipLevelCount = WGPU_MIP_LEVEL_COUNT_UNDEFINED;

        descriptor.baseMipLevel = 0;
        texture.CreateView(&descriptor);
        descriptor.baseMipLevel = 1;
        texture.CreateView(&descriptor);
        descriptor.baseMipLevel = kDefaultMipLevels - 1;
        texture.CreateView(&descriptor);
        descriptor.baseMipLevel = kDefaultMipLevels;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
    }

    // It is an error to make the mip level out of range.
    {
        wgpu::TextureViewDescriptor descriptor = base2DTextureViewDescriptor;
        descriptor.baseMipLevel = 0;
        descriptor.mipLevelCount = kDefaultMipLevels + 1;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
        descriptor.baseMipLevel = 1;
        descriptor.mipLevelCount = kDefaultMipLevels;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
        descriptor.baseMipLevel = kDefaultMipLevels - 1;
        descriptor.mipLevelCount = 2;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
        descriptor.baseMipLevel = kDefaultMipLevels;
        descriptor.mipLevelCount = 1;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
    }
}

// Test creating texture view on a 2D array texture
TEST_F(TextureViewValidationTest, CreateTextureViewOnTexture2DArray) {
    constexpr uint32_t kDefaultArrayLayers = 6;

    wgpu::Texture texture = Create2DArrayTexture(device, kDefaultArrayLayers);

    wgpu::TextureViewDescriptor base2DArrayTextureViewDescriptor =
        CreateDefaultViewDescriptor(wgpu::TextureViewDimension::e2DArray);

    // It is an error to create a view with zero 'arrayLayerCount'.
    {
        wgpu::TextureViewDescriptor descriptor = base2DArrayTextureViewDescriptor;
        descriptor.dimension = wgpu::TextureViewDimension::e2D;
        descriptor.arrayLayerCount = 0;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
    }

    // It is an error to create a view with zero 'mipLevelCount'.
    {
        wgpu::TextureViewDescriptor descriptor = base2DArrayTextureViewDescriptor;
        descriptor.dimension = wgpu::TextureViewDimension::e2D;
        descriptor.mipLevelCount = 0;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
    }

    // It is OK to create a 2D texture view on a 2D array texture.
    {
        wgpu::TextureViewDescriptor descriptor = base2DArrayTextureViewDescriptor;
        descriptor.dimension = wgpu::TextureViewDimension::e2D;
        descriptor.arrayLayerCount = 1;
        texture.CreateView(&descriptor);
    }

    // It is OK to create a 2D array texture view on a 2D array texture.
    {
        wgpu::TextureViewDescriptor descriptor = base2DArrayTextureViewDescriptor;
        descriptor.arrayLayerCount = kDefaultArrayLayers;
        texture.CreateView(&descriptor);
    }

    // It is an error to create a 3D texture view on a 2D array texture.
    {
        wgpu::TextureViewDescriptor descriptor = base2DArrayTextureViewDescriptor;
        descriptor.dimension = wgpu::TextureViewDimension::e3D;
        descriptor.arrayLayerCount = 1;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
    }

    // It is an error to create a 1D texture view on a 2D array texture.
    {
        wgpu::TextureViewDescriptor descriptor = base2DArrayTextureViewDescriptor;
        descriptor.dimension = wgpu::TextureViewDimension::e1D;
        descriptor.arrayLayerCount = 1;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
    }

    // baseArrayLayer == k && arrayLayerCount == wgpu::kArrayLayerCountUndefined means to use
    // layers k..end.
    {
        wgpu::TextureViewDescriptor descriptor = base2DArrayTextureViewDescriptor;
        descriptor.arrayLayerCount = wgpu::kArrayLayerCountUndefined;

        descriptor.baseArrayLayer = 0;
        texture.CreateView(&descriptor);
        descriptor.baseArrayLayer = 1;
        texture.CreateView(&descriptor);
        descriptor.baseArrayLayer = kDefaultArrayLayers - 1;
        texture.CreateView(&descriptor);
        descriptor.baseArrayLayer = kDefaultArrayLayers;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
    }

    // It is an error for the array layer range of the view to exceed that of the texture.
    {
        wgpu::TextureViewDescriptor descriptor = base2DArrayTextureViewDescriptor;
        descriptor.baseArrayLayer = 0;
        descriptor.arrayLayerCount = kDefaultArrayLayers + 1;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
        descriptor.baseArrayLayer = 1;
        descriptor.arrayLayerCount = kDefaultArrayLayers;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
        descriptor.baseArrayLayer = kDefaultArrayLayers - 1;
        descriptor.arrayLayerCount = 2;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
        descriptor.baseArrayLayer = kDefaultArrayLayers;
        descriptor.arrayLayerCount = 1;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
    }
}

// Test creating texture view on a 3D texture
TEST_F(TextureViewValidationTest, CreateTextureViewOnTexture3D) {
    wgpu::Texture texture = Create3DTexture(device);

    wgpu::TextureViewDescriptor base3DTextureViewDescriptor =
        CreateDefaultViewDescriptor(wgpu::TextureViewDimension::e3D);

    // It is an error to create a view with zero 'arrayLayerCount'.
    {
        wgpu::TextureViewDescriptor descriptor = base3DTextureViewDescriptor;
        descriptor.arrayLayerCount = 0;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
    }

    // It is an error to create a view with zero 'mipLevelCount'.
    {
        wgpu::TextureViewDescriptor descriptor = base3DTextureViewDescriptor;
        descriptor.mipLevelCount = 0;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
    }

    // It is OK to create a 3D texture view on a 3D texture.
    {
        wgpu::TextureViewDescriptor descriptor = base3DTextureViewDescriptor;
        texture.CreateView(&descriptor);
    }

    // It is an error to create a 1D/2D/2DArray/Cube/CubeArray texture view on a 3D texture.
    {
        wgpu::TextureViewDimension invalidDimensions[] = {
            wgpu::TextureViewDimension::e1D,       wgpu::TextureViewDimension::e2D,
            wgpu::TextureViewDimension::e2DArray,  wgpu::TextureViewDimension::Cube,
            wgpu::TextureViewDimension::CubeArray,
        };
        for (wgpu::TextureViewDimension dimension : invalidDimensions) {
            wgpu::TextureViewDescriptor descriptor = base3DTextureViewDescriptor;
            descriptor.dimension = dimension;
            if (dimension == wgpu::TextureViewDimension::Cube ||
                dimension == wgpu::TextureViewDimension::CubeArray) {
                descriptor.arrayLayerCount = 6;
            }
            ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
        }
    }

    // baseMipLevel == k && mipLevelCount == WGPU_MIP_LEVEL_COUNT_UNDEFINED means to use levels
    // k..end.
    {
        wgpu::TextureViewDescriptor descriptor = base3DTextureViewDescriptor;
        descriptor.mipLevelCount = WGPU_MIP_LEVEL_COUNT_UNDEFINED;

        descriptor.baseMipLevel = 0;
        texture.CreateView(&descriptor);
        descriptor.baseMipLevel = 1;
        texture.CreateView(&descriptor);
        descriptor.baseMipLevel = kDefaultMipLevels - 1;
        texture.CreateView(&descriptor);
        descriptor.baseMipLevel = kDefaultMipLevels;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
    }

    // It is an error to make the mip level out of range.
    {
        wgpu::TextureViewDescriptor descriptor = base3DTextureViewDescriptor;
        descriptor.baseMipLevel = 0;
        descriptor.mipLevelCount = kDefaultMipLevels + 1;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
        descriptor.baseMipLevel = 1;
        descriptor.mipLevelCount = kDefaultMipLevels;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
        descriptor.baseMipLevel = kDefaultMipLevels - 1;
        descriptor.mipLevelCount = 2;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
        descriptor.baseMipLevel = kDefaultMipLevels;
        descriptor.mipLevelCount = 1;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
    }

    // baseArrayLayer == k && arrayLayerCount == wgpu::kArrayLayerCountUndefined means to use
    // layers k..end. But baseArrayLayer must be 0, and arrayLayerCount must be 1 at most for 3D
    // texture view.
    {
        wgpu::TextureViewDescriptor descriptor = base3DTextureViewDescriptor;
        descriptor.arrayLayerCount = wgpu::kArrayLayerCountUndefined;
        descriptor.baseArrayLayer = 0;
        texture.CreateView(&descriptor);
        descriptor.baseArrayLayer = 1;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));

        descriptor.baseArrayLayer = 0;
        descriptor.arrayLayerCount = 1;
        texture.CreateView(&descriptor);
        descriptor.arrayLayerCount = 2;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
        descriptor.arrayLayerCount = kDepth;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
    }
}

// Test creating texture view on a 1D texture
TEST_F(TextureViewValidationTest, CreateTextureViewOnTexture1D) {
    wgpu::Texture texture = Create1DTexture(device);

    wgpu::TextureViewDescriptor base1DTextureViewDescriptor =
        CreateDefaultViewDescriptor(wgpu::TextureViewDimension::e1D);

    // It is an error to create a view with zero 'arrayLayerCount'.
    {
        wgpu::TextureViewDescriptor descriptor = base1DTextureViewDescriptor;
        descriptor.arrayLayerCount = 0;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
    }

    // It is an error to create a view with zero 'mipLevelCount'.
    {
        wgpu::TextureViewDescriptor descriptor = base1DTextureViewDescriptor;
        descriptor.mipLevelCount = 0;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
    }

    // It is OK to create a 1D texture view on a 1D texture.
    {
        wgpu::TextureViewDescriptor descriptor = base1DTextureViewDescriptor;
        texture.CreateView(&descriptor);
    }

    // It is an error to create a 2D/2DArray/Cube/CubeArray/3D texture view on a 1D texture.
    {
        wgpu::TextureViewDimension invalidDimensions[] = {
            wgpu::TextureViewDimension::e2D,  wgpu::TextureViewDimension::e2DArray,
            wgpu::TextureViewDimension::Cube, wgpu::TextureViewDimension::CubeArray,
            wgpu::TextureViewDimension::e3D,
        };
        for (wgpu::TextureViewDimension dimension : invalidDimensions) {
            wgpu::TextureViewDescriptor descriptor = base1DTextureViewDescriptor;
            descriptor.dimension = dimension;
            ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
        }
    }

    // No tests for setting mip levels / array layer ranges because 1D textures can only have
    // a single mip and layer.
}

// Test creating texture view on a multisampled 2D texture
TEST_F(TextureViewValidationTest, CreateTextureViewOnMultisampledTexture2D) {
    wgpu::Texture texture = Create2DArrayTexture(device, /* arrayLayerCount */ 1, kWidth, kHeight,
                                                 /* mipLevelCount */ 1, /* sampleCount */ 4);

    // It is OK to create a 2D texture view on a multisampled 2D texture.
    {
        wgpu::TextureViewDescriptor descriptor = {};
        texture.CreateView(&descriptor);
    }

    // It is an error to create a 1-layer 2D array texture view on a multisampled 2D texture.
    {
        wgpu::TextureViewDescriptor descriptor = {};
        descriptor.dimension = wgpu::TextureViewDimension::e2DArray;
        descriptor.arrayLayerCount = 1;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
    }

    // It is an error to create a 1D texture view on a multisampled 2D texture.
    {
        wgpu::TextureViewDescriptor descriptor = {};
        descriptor.dimension = wgpu::TextureViewDimension::e1D;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
    }

    // It is an error to create a 3D texture view on a multisampled 2D texture.
    {
        wgpu::TextureViewDescriptor descriptor = {};
        descriptor.dimension = wgpu::TextureViewDimension::e3D;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
    }
}

// Using the "none" ("default") values validates the same as explicitly
// specifying the values they're supposed to default to.
// Variant for a 2D texture with more than 1 array layer.
TEST_F(TextureViewValidationTest, TextureViewDescriptorDefaults2DArray) {
    constexpr uint32_t kDefaultArrayLayers = 8;
    wgpu::Texture texture = Create2DArrayTexture(device, kDefaultArrayLayers);

    {
        texture.CreateView();
    }
    {
        wgpu::TextureViewDescriptor descriptor;
        descriptor.format = wgpu::TextureFormat::Undefined;
        texture.CreateView(&descriptor);
        descriptor.format = wgpu::TextureFormat::RGBA8Unorm;
        texture.CreateView(&descriptor);
        descriptor.format = wgpu::TextureFormat::R8Unorm;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
    }
    {
        wgpu::TextureViewDescriptor descriptor;
        descriptor.dimension = wgpu::TextureViewDimension::Undefined;
        texture.CreateView(&descriptor);
        descriptor.dimension = wgpu::TextureViewDimension::e2DArray;
        texture.CreateView(&descriptor);
        // Setting view dimension to 2D, its arrayLayer will default to 1. And view creation
        // will success.
        descriptor.dimension = wgpu::TextureViewDimension::e2D;
        texture.CreateView(&descriptor);
        // Setting view dimension to Cube, its arrayLayer will default to 6.
        descriptor.dimension = wgpu::TextureViewDimension::Cube;
        texture.CreateView(&descriptor);
        descriptor.baseArrayLayer = 2;
        texture.CreateView(&descriptor);
        descriptor.baseArrayLayer = 3;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
        // Setting view dimension to CubeArray, its arrayLayer will default to
        // size.depthOrArrayLayers (kDefaultArrayLayers) - baseArrayLayer.
        descriptor.dimension = wgpu::TextureViewDimension::CubeArray;
        descriptor.baseArrayLayer = 0;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
        descriptor.baseArrayLayer = 2;
        texture.CreateView(&descriptor);
        descriptor.baseArrayLayer = 3;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
    }
    {
        wgpu::TextureViewDescriptor descriptor;

        // Setting array layers to > 1 with an explicit dimensionality of 2D will
        // causes an error.
        descriptor.arrayLayerCount = kDefaultArrayLayers;
        descriptor.dimension = wgpu::TextureViewDimension::e2D;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
        // Setting view dimension to Undefined will result in a dimension of 2DArray because the
        // underlying texture has > 1 array layers.
        descriptor.dimension = wgpu::TextureViewDimension::Undefined;
        texture.CreateView(&descriptor);
        descriptor.dimension = wgpu::TextureViewDimension::e2DArray;
        texture.CreateView(&descriptor);

        descriptor.mipLevelCount = kDefaultMipLevels;
        texture.CreateView(&descriptor);
    }
}

// Using the "none" ("default") values validates the same as explicitly
// specifying the values they're supposed to default to.
// Variant for a 2D texture with only 1 array layer.
TEST_F(TextureViewValidationTest, TextureViewDescriptorDefaults2DNonArray) {
    constexpr uint32_t kDefaultArrayLayers = 1;
    wgpu::Texture texture = Create2DArrayTexture(device, kDefaultArrayLayers);

    {
        texture.CreateView();
    }
    {
        wgpu::TextureViewDescriptor descriptor;
        descriptor.format = wgpu::TextureFormat::Undefined;
        texture.CreateView(&descriptor);
        descriptor.format = wgpu::TextureFormat::RGBA8Unorm;
        texture.CreateView(&descriptor);
        descriptor.format = wgpu::TextureFormat::R8Unorm;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
    }
    {
        wgpu::TextureViewDescriptor descriptor;
        descriptor.dimension = wgpu::TextureViewDimension::Undefined;
        texture.CreateView(&descriptor);
        descriptor.dimension = wgpu::TextureViewDimension::e2D;
        texture.CreateView(&descriptor);
        descriptor.dimension = wgpu::TextureViewDimension::e2DArray;
        texture.CreateView(&descriptor);
    }
    {
        wgpu::TextureViewDescriptor descriptor;
        descriptor.arrayLayerCount = wgpu::kArrayLayerCountUndefined;
        texture.CreateView(&descriptor);
        descriptor.arrayLayerCount = 1;
        texture.CreateView(&descriptor);
        descriptor.arrayLayerCount = 2;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
    }
    {
        wgpu::TextureViewDescriptor descriptor;
        descriptor.mipLevelCount = kDefaultMipLevels;
        texture.CreateView(&descriptor);
        descriptor.arrayLayerCount = kDefaultArrayLayers;
        texture.CreateView(&descriptor);
    }
}

// Using the "none" ("default") values validates the same as explicitly
// specifying the values they're supposed to default to.
// Variant for a 3D texture.
TEST_F(TextureViewValidationTest, TextureViewDescriptorDefaults3D) {
    wgpu::Texture texture = Create3DTexture(device);

    {
        texture.CreateView();
    }
    {
        wgpu::TextureViewDescriptor descriptor;
        descriptor.format = wgpu::TextureFormat::Undefined;
        texture.CreateView(&descriptor);
        descriptor.format = wgpu::TextureFormat::RGBA8Unorm;
        texture.CreateView(&descriptor);
        descriptor.format = wgpu::TextureFormat::R8Unorm;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
    }
    {
        wgpu::TextureViewDescriptor descriptor;
        descriptor.dimension = wgpu::TextureViewDimension::Undefined;
        texture.CreateView(&descriptor);
        descriptor.dimension = wgpu::TextureViewDimension::e3D;
        texture.CreateView(&descriptor);
        descriptor.dimension = wgpu::TextureViewDimension::e2DArray;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
        descriptor.dimension = wgpu::TextureViewDimension::e2D;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
    }
    {
        wgpu::TextureViewDescriptor descriptor;
        descriptor.arrayLayerCount = wgpu::kArrayLayerCountUndefined;
        texture.CreateView(&descriptor);
        descriptor.arrayLayerCount = 1;
        texture.CreateView(&descriptor);
        descriptor.arrayLayerCount = 2;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
    }
    {
        wgpu::TextureViewDescriptor descriptor;
        descriptor.mipLevelCount = kDefaultMipLevels;
        texture.CreateView(&descriptor);
        descriptor.arrayLayerCount = kDepth;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
    }
}

// Regression test for crbug.com/1314049. Format default depends on the aspect.
// Test that computing the default does not crash if the aspect is invalid.
TEST_F(TextureViewValidationTest, TextureViewDescriptorDefaultsInvalidAspect) {
    wgpu::Texture texture =
        CreateDepthStencilTexture(device, wgpu::TextureFormat::Depth24PlusStencil8);

    wgpu::TextureViewDescriptor viewDesc = {};
    viewDesc.aspect = static_cast<wgpu::TextureAspect>(-1);

    // Validation should catch the invalid aspect.
    ASSERT_DEVICE_ERROR(texture.CreateView(&viewDesc),
                        testing::HasSubstr("is invalid for WGPUTextureAspect"));
}

// Test creating cube map texture view
TEST_F(TextureViewValidationTest, CreateCubeMapTextureView) {
    constexpr uint32_t kDefaultArrayLayers = 16;

    wgpu::Texture texture = Create2DArrayTexture(device, kDefaultArrayLayers);

    wgpu::TextureViewDescriptor base2DArrayTextureViewDescriptor =
        CreateDefaultViewDescriptor(wgpu::TextureViewDimension::e2DArray);

    // It is an error to create a view with zero 'arrayLayerCount'.
    {
        wgpu::TextureViewDescriptor descriptor = base2DArrayTextureViewDescriptor;
        descriptor.dimension = wgpu::TextureViewDimension::Cube;
        descriptor.arrayLayerCount = 0;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
    }

    // It is an error to create a view with zero 'mipLevelCount'.
    {
        wgpu::TextureViewDescriptor descriptor = base2DArrayTextureViewDescriptor;
        descriptor.dimension = wgpu::TextureViewDimension::Cube;
        descriptor.mipLevelCount = 0;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
    }

    // It is OK to create a cube map texture view with arrayLayerCount == 6.
    {
        wgpu::TextureViewDescriptor descriptor = base2DArrayTextureViewDescriptor;
        descriptor.dimension = wgpu::TextureViewDimension::Cube;
        descriptor.arrayLayerCount = 6;
        texture.CreateView(&descriptor);
    }

    // It is an error to create a cube map texture view with arrayLayerCount != 6.
    {
        wgpu::TextureViewDescriptor descriptor = base2DArrayTextureViewDescriptor;
        descriptor.dimension = wgpu::TextureViewDimension::Cube;
        descriptor.arrayLayerCount = 3;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
    }

    // It is OK to create a cube map array texture view with arrayLayerCount % 6 == 0.
    {
        wgpu::TextureViewDescriptor descriptor = base2DArrayTextureViewDescriptor;
        descriptor.dimension = wgpu::TextureViewDimension::CubeArray;
        descriptor.arrayLayerCount = 12;
        texture.CreateView(&descriptor);
    }

    // It is an error to create a cube map array texture view with arrayLayerCount % 6 != 0.
    {
        wgpu::TextureViewDescriptor descriptor = base2DArrayTextureViewDescriptor;
        descriptor.dimension = wgpu::TextureViewDimension::CubeArray;
        descriptor.arrayLayerCount = 11;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
    }

    // It is an error to create a cube map texture view with width != height.
    {
        wgpu::Texture nonSquareTexture = Create2DArrayTexture(device, 18, 32, 16, 5);

        wgpu::TextureViewDescriptor descriptor = base2DArrayTextureViewDescriptor;
        descriptor.dimension = wgpu::TextureViewDimension::Cube;
        descriptor.arrayLayerCount = 6;
        ASSERT_DEVICE_ERROR(nonSquareTexture.CreateView(&descriptor));
    }

    // It is an error to create a cube map array texture view with width != height.
    {
        wgpu::Texture nonSquareTexture = Create2DArrayTexture(device, 18, 32, 16, 5);

        wgpu::TextureViewDescriptor descriptor = base2DArrayTextureViewDescriptor;
        descriptor.dimension = wgpu::TextureViewDimension::CubeArray;
        descriptor.arrayLayerCount = 12;
        ASSERT_DEVICE_ERROR(nonSquareTexture.CreateView(&descriptor));
    }
}

// Test the format compatibility rules when creating a texture view.
TEST_F(TextureViewValidationTest, TextureViewFormatCompatibility) {
    wgpu::TextureDescriptor textureDesc = {};
    textureDesc.size.width = 4;
    textureDesc.size.height = 4;
    textureDesc.usage = wgpu::TextureUsage::TextureBinding;

    wgpu::TextureViewDescriptor viewDesc = {};

    // It is an error to create an sRGB texture view from an RGB texture, without viewFormats.
    {
        textureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
        viewDesc.format = wgpu::TextureFormat::RGBA8UnormSrgb;
        wgpu::Texture texture = device.CreateTexture(&textureDesc);
        ASSERT_DEVICE_ERROR(texture.CreateView(&viewDesc));
    }

    // It is an error to create an RGB texture view from an sRGB texture, without viewFormats.
    {
        textureDesc.format = wgpu::TextureFormat::BGRA8UnormSrgb;
        viewDesc.format = wgpu::TextureFormat::BGRA8Unorm;
        wgpu::Texture texture = device.CreateTexture(&textureDesc);
        ASSERT_DEVICE_ERROR(texture.CreateView(&viewDesc));
    }

    // It is an error to create a texture view with a depth-stencil format of an RGBA texture.
    {
        textureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
        viewDesc.format = wgpu::TextureFormat::Depth24PlusStencil8;
        wgpu::Texture texture = device.CreateTexture(&textureDesc);
        ASSERT_DEVICE_ERROR(texture.CreateView(&viewDesc));
    }

    // It is an error to create a texture view with a depth format of a depth-stencil texture.
    {
        textureDesc.format = wgpu::TextureFormat::Depth24PlusStencil8;
        viewDesc.format = wgpu::TextureFormat::Depth24Plus;
        wgpu::Texture texture = device.CreateTexture(&textureDesc);
        ASSERT_DEVICE_ERROR(texture.CreateView(&viewDesc));
    }

    // It is invalid to create a texture view with a combined depth-stencil format if only
    // the depth aspect is selected.
    {
        textureDesc.format = wgpu::TextureFormat::Depth24PlusStencil8;
        viewDesc.format = wgpu::TextureFormat::Depth24PlusStencil8;
        viewDesc.aspect = wgpu::TextureAspect::DepthOnly;
        wgpu::Texture texture = device.CreateTexture(&textureDesc);
        ASSERT_DEVICE_ERROR(texture.CreateView(&viewDesc));
    }

    // It is invalid to create a texture view with a combined depth-stencil format if only
    // the stencil aspect is selected.
    {
        textureDesc.format = wgpu::TextureFormat::Depth24PlusStencil8;
        viewDesc.format = wgpu::TextureFormat::Depth24PlusStencil8;
        viewDesc.aspect = wgpu::TextureAspect::StencilOnly;
        wgpu::Texture texture = device.CreateTexture(&textureDesc);
        ASSERT_DEVICE_ERROR(texture.CreateView(&viewDesc));
    }

    // It is valid to create a texture view with a depth format of a depth-stencil texture
    // if the depth only aspect is selected.
    {
        textureDesc.format = wgpu::TextureFormat::Depth24PlusStencil8;
        viewDesc.format = wgpu::TextureFormat::Depth24Plus;
        viewDesc.aspect = wgpu::TextureAspect::DepthOnly;
        wgpu::Texture texture = device.CreateTexture(&textureDesc);
        texture.CreateView(&viewDesc);

        viewDesc = {};
    }

    // Prep for testing a single view format in viewFormats.
    wgpu::TextureFormat viewFormat;
    textureDesc.viewFormats = &viewFormat;
    textureDesc.viewFormatCount = 1;

    // An aspect format is not a valid view format of a depth-stencil texture.
    {
        textureDesc.format = wgpu::TextureFormat::Depth24PlusStencil8;
        viewFormat = wgpu::TextureFormat::Depth24Plus;
        ASSERT_DEVICE_ERROR(device.CreateTexture(&textureDesc));
    }

    // Test that a RGBA texture can be viewed as both RGBA and RGBASrgb, but not BGRA or
    // BGRASrgb
    {
        textureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
        viewFormat = wgpu::TextureFormat::RGBA8UnormSrgb;
        wgpu::Texture texture = device.CreateTexture(&textureDesc);

        viewDesc.format = wgpu::TextureFormat::RGBA8UnormSrgb;
        texture.CreateView(&viewDesc);

        viewDesc.format = wgpu::TextureFormat::RGBA8Unorm;
        texture.CreateView(&viewDesc);

        viewDesc.format = wgpu::TextureFormat::BGRA8Unorm;
        ASSERT_DEVICE_ERROR(texture.CreateView(&viewDesc));

        viewDesc.format = wgpu::TextureFormat::BGRA8UnormSrgb;
        ASSERT_DEVICE_ERROR(texture.CreateView(&viewDesc));
    }

    // Test that a BGRASrgb texture can be viewed as both BGRA and BGRASrgb, but not RGBA or
    // RGBASrgb
    {
        textureDesc.format = wgpu::TextureFormat::BGRA8UnormSrgb;
        viewFormat = wgpu::TextureFormat::BGRA8Unorm;
        wgpu::Texture texture = device.CreateTexture(&textureDesc);

        viewDesc.format = wgpu::TextureFormat::BGRA8Unorm;
        texture.CreateView(&viewDesc);

        viewDesc.format = wgpu::TextureFormat::BGRA8UnormSrgb;
        texture.CreateView(&viewDesc);

        viewDesc.format = wgpu::TextureFormat::RGBA8Unorm;
        ASSERT_DEVICE_ERROR(texture.CreateView(&viewDesc));

        viewDesc.format = wgpu::TextureFormat::RGBA8UnormSrgb;
        ASSERT_DEVICE_ERROR(texture.CreateView(&viewDesc));
    }

    // Test an RGBA format may be viewed as RGBA (same)
    {
        textureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
        viewFormat = wgpu::TextureFormat::RGBA8Unorm;
        wgpu::Texture texture = device.CreateTexture(&textureDesc);

        viewDesc.format = wgpu::TextureFormat::RGBA8Unorm;
        texture.CreateView(&viewDesc);

        viewDesc.format = wgpu::TextureFormat::RGBA8UnormSrgb;
        ASSERT_DEVICE_ERROR(texture.CreateView(&viewDesc));
    }

    // Test that duplicate, and multiple view formats are allowed.
    {
        std::array<wgpu::TextureFormat, 5> viewFormats = {
            wgpu::TextureFormat::RGBA8UnormSrgb, wgpu::TextureFormat::RGBA8Unorm,
            wgpu::TextureFormat::RGBA8Unorm,     wgpu::TextureFormat::RGBA8UnormSrgb,
            wgpu::TextureFormat::RGBA8Unorm,
        };
        textureDesc.viewFormats = viewFormats.data();
        textureDesc.viewFormatCount = viewFormats.size();

        textureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
        wgpu::Texture texture = device.CreateTexture(&textureDesc);

        viewDesc.format = wgpu::TextureFormat::RGBA8UnormSrgb;
        texture.CreateView(&viewDesc);

        viewDesc.format = wgpu::TextureFormat::RGBA8Unorm;
        texture.CreateView(&viewDesc);

        viewDesc.format = wgpu::TextureFormat::BGRA8Unorm;
        ASSERT_DEVICE_ERROR(texture.CreateView(&viewDesc));

        viewDesc.format = wgpu::TextureFormat::BGRA8UnormSrgb;
        ASSERT_DEVICE_ERROR(texture.CreateView(&viewDesc));
    }
}

// Test that it's valid to create a texture view from a destroyed texture
TEST_F(TextureViewValidationTest, DestroyCreateTextureView) {
    wgpu::Texture texture = Create2DArrayTexture(device, 1);
    wgpu::TextureViewDescriptor descriptor =
        CreateDefaultViewDescriptor(wgpu::TextureViewDimension::e2D);
    texture.Destroy();
    texture.CreateView(&descriptor);
}

// Test that the selected TextureAspects must exist in the texture format
TEST_F(TextureViewValidationTest, AspectMustExist) {
    wgpu::TextureDescriptor descriptor = {};
    descriptor.size = {1, 1, 1};
    descriptor.usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment;

    // Can select: All and DepthOnly from Depth32Float, but not StencilOnly
    {
        descriptor.format = wgpu::TextureFormat::Depth32Float;
        wgpu::Texture texture = device.CreateTexture(&descriptor);

        wgpu::TextureViewDescriptor viewDescriptor = {};
        viewDescriptor.aspect = wgpu::TextureAspect::All;
        texture.CreateView(&viewDescriptor);

        viewDescriptor.aspect = wgpu::TextureAspect::DepthOnly;
        texture.CreateView(&viewDescriptor);

        viewDescriptor.aspect = wgpu::TextureAspect::StencilOnly;
        ASSERT_DEVICE_ERROR(texture.CreateView(&viewDescriptor));
    }

    // Can select: All, DepthOnly, and StencilOnly from Depth24PlusStencil8
    {
        descriptor.format = wgpu::TextureFormat::Depth24PlusStencil8;
        wgpu::Texture texture = device.CreateTexture(&descriptor);

        wgpu::TextureViewDescriptor viewDescriptor = {};
        viewDescriptor.aspect = wgpu::TextureAspect::All;
        texture.CreateView(&viewDescriptor);

        viewDescriptor.aspect = wgpu::TextureAspect::DepthOnly;
        texture.CreateView(&viewDescriptor);

        viewDescriptor.aspect = wgpu::TextureAspect::StencilOnly;
        texture.CreateView(&viewDescriptor);
    }

    // Can select: All from RGBA8Unorm
    {
        descriptor.format = wgpu::TextureFormat::RGBA8Unorm;
        wgpu::Texture texture = device.CreateTexture(&descriptor);

        wgpu::TextureViewDescriptor viewDescriptor = {};
        viewDescriptor.aspect = wgpu::TextureAspect::All;
        texture.CreateView(&viewDescriptor);

        viewDescriptor.aspect = wgpu::TextureAspect::DepthOnly;
        ASSERT_DEVICE_ERROR(texture.CreateView(&viewDescriptor));

        viewDescriptor.aspect = wgpu::TextureAspect::StencilOnly;
        ASSERT_DEVICE_ERROR(texture.CreateView(&viewDescriptor));
    }
}

// Test that CreateErrorView creates an invalid texture view but doesn't produce an error.
TEST_F(TextureViewValidationTest, CreateErrorView) {
    wgpu::Texture texture = Create2DArrayTexture(device, 1);
    wgpu::TextureViewDescriptor descriptor =
        CreateDefaultViewDescriptor(wgpu::TextureViewDimension::e2D);

    // Creating the error texture view doesn't produce an error.
    wgpu::TextureView view = texture.CreateErrorView(&descriptor);

    // Using the error texture view will throw an error.
    wgpu::BindGroupLayout layout = utils::MakeBindGroupLayout(
        device, {{0, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float}});
    ASSERT_DEVICE_ERROR(utils::MakeBindGroup(device, layout, {{0, view}}));
}

// Tests that texture view usage is validated for the texture view format and is compatible with the
// source texture usages
TEST_F(TextureViewValidationTest, Usage) {
    wgpu::TextureFormat viewFormats[] = {wgpu::TextureFormat::RGBA8Unorm,
                                         wgpu::TextureFormat::RGBA8UnormSrgb};

    wgpu::TextureDescriptor textureDescriptor;
    textureDescriptor.dimension = wgpu::TextureDimension::e2D;
    textureDescriptor.size.width = kWidth;
    textureDescriptor.size.height = kHeight;
    textureDescriptor.sampleCount = 1;
    textureDescriptor.format = wgpu::TextureFormat::RGBA8Unorm;
    textureDescriptor.mipLevelCount = 1;
    textureDescriptor.usage = wgpu::TextureUsage::TextureBinding |
                              wgpu::TextureUsage::RenderAttachment |
                              wgpu::TextureUsage::StorageBinding;
    textureDescriptor.viewFormats = viewFormats;
    textureDescriptor.viewFormatCount = 2;
    wgpu::Texture texture = device.CreateTexture(&textureDescriptor);

    wgpu::TextureViewDescriptor base2DTextureViewDescriptor;
    base2DTextureViewDescriptor.format = kDefaultTextureFormat;
    base2DTextureViewDescriptor.dimension = wgpu::TextureViewDimension::e2D;
    base2DTextureViewDescriptor.baseMipLevel = 0;
    base2DTextureViewDescriptor.mipLevelCount = 1;
    base2DTextureViewDescriptor.baseArrayLayer = 0;
    base2DTextureViewDescriptor.arrayLayerCount = 1;

    // It is an error to request a usage outside of the source texture's usage
    {
        wgpu::TextureViewDescriptor descriptor = base2DTextureViewDescriptor;
        descriptor.usage |= wgpu::TextureUsage::CopyDst;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
    }

    // It is an error to create a view with RGBA8UnormSrgb and default usage which includes
    // StorageBinding
    {
        wgpu::TextureViewDescriptor descriptor = base2DTextureViewDescriptor;
        descriptor.format = wgpu::TextureFormat::RGBA8UnormSrgb;
        descriptor.usage = wgpu::TextureUsage::None;

        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
    }

    // A view can be created for RGBA8UnormSrgb with a compatible subset of usages
    {
        wgpu::TextureViewDescriptor descriptor = base2DTextureViewDescriptor;
        descriptor.format = wgpu::TextureFormat::RGBA8UnormSrgb;
        descriptor.usage =
            wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment;
        texture.CreateView(&descriptor);
    }
}

// Test setting swizzle when creating a texture view requires feature.
TEST_F(TextureViewValidationTest, SwizzleRequiresFeature) {
    wgpu::TextureDescriptor textureDesc = {};
    textureDesc.size = {kWidth, kHeight, kDepth};
    textureDesc.usage = wgpu::TextureUsage::StorageBinding | wgpu::TextureUsage::RenderAttachment;
    textureDesc.format = kDefaultTextureFormat;
    wgpu::Texture texture = device.CreateTexture(&textureDesc);

    wgpu::TextureViewDescriptor viewDesc = {};
    wgpu::TextureComponentSwizzleDescriptor swizzleDesc = {};
    viewDesc.nextInChain = &swizzleDesc;
    ASSERT_DEVICE_ERROR(texture.CreateView(&viewDesc));
}

enum class SwizzleChannel { R, G, B, A };

class ComponentSwizzleTextureViewValidationTests : public ValidationTest {
  protected:
    std::vector<wgpu::FeatureName> GetRequiredFeatures() override {
        return {wgpu::FeatureName::TextureComponentSwizzle};
    }

    wgpu::TextureComponentSwizzleDescriptor GetIdenticalButOneSwizzleDesc(
        SwizzleChannel channel,
        std::optional<wgpu::ComponentSwizzle> swizzle = std::nullopt) {
        wgpu::TextureComponentSwizzleDescriptor desc = {};
        switch (channel) {
            case (SwizzleChannel::R): {
                desc.swizzle.r = swizzle.value_or(wgpu::ComponentSwizzle::Zero);
                break;
            }
            case (SwizzleChannel::G): {
                desc.swizzle.g = swizzle.value_or(wgpu::ComponentSwizzle::One);
                break;
            }
            case (SwizzleChannel::B): {
                desc.swizzle.b = swizzle.value_or(wgpu::ComponentSwizzle::R);
                break;
            }
            case (SwizzleChannel::A): {
                desc.swizzle.a = swizzle.value_or(wgpu::ComponentSwizzle::G);
                break;
            }
        }
        return desc;
    }
};

// Test setting invalid component swizzle when creating a texture view fails.
TEST_F(ComponentSwizzleTextureViewValidationTests, InvalidComponentSwizzle) {
    wgpu::TextureDescriptor textureDesc = {};
    textureDesc.size = {kWidth, kHeight, kDepth};
    textureDesc.usage = wgpu::TextureUsage::StorageBinding | wgpu::TextureUsage::RenderAttachment;
    textureDesc.format = kDefaultTextureFormat;
    wgpu::Texture texture = device.CreateTexture(&textureDesc);

    // Control case: identity swizzle
    wgpu::TextureViewDescriptor viewDesc = {};
    wgpu::TextureComponentSwizzleDescriptor swizzleDesc = {};
    viewDesc.nextInChain = &swizzleDesc;
    texture.CreateView(&viewDesc);

    // Invalid component swizzle for each channel
    for (auto changedChannel :
         {SwizzleChannel::R, SwizzleChannel::G, SwizzleChannel::B, SwizzleChannel::A}) {
        swizzleDesc =
            GetIdenticalButOneSwizzleDesc(changedChannel, static_cast<wgpu::ComponentSwizzle>(-1));
        ASSERT_DEVICE_ERROR(texture.CreateView(&viewDesc));
    }
}

// Test storage binding requires identity swizzle.
TEST_F(ComponentSwizzleTextureViewValidationTests, StorageBindingRequiresIdentitySwizzle) {
    wgpu::TextureDescriptor textureDesc = {};
    textureDesc.size = {kWidth, kHeight, 1};
    textureDesc.usage = wgpu::TextureUsage::StorageBinding;
    textureDesc.format = kDefaultTextureFormat;
    wgpu::Texture texture = device.CreateTexture(&textureDesc);

    wgpu::TextureViewDescriptor viewDesc = {};
    wgpu::TextureComponentSwizzleDescriptor swizzleDesc = {};
    viewDesc.nextInChain = &swizzleDesc;

    wgpu::ComputePipelineDescriptor cpDesc;
    cpDesc.compute.module = utils::CreateShaderModule(device, R"(
        @group(0) @binding(0) var texture : texture_storage_2d<rgba8unorm, read>;

        @compute @workgroup_size(1) fn main() {
          var res : vec4f = textureLoad(texture, vec2i(0, 0));
        })");
    wgpu::ComputePipeline cp = device.CreateComputePipeline(&cpDesc);

    // Control case: identity swizzle
    utils::MakeBindGroup(device, cp.GetBindGroupLayout(0), {{0, texture.CreateView(&viewDesc)}});

    // Non-identical swizzle cases
    for (auto changedChannel :
         {SwizzleChannel::R, SwizzleChannel::G, SwizzleChannel::B, SwizzleChannel::A}) {
        swizzleDesc = GetIdenticalButOneSwizzleDesc(changedChannel);
        ASSERT_DEVICE_ERROR(utils::MakeBindGroup(device, cp.GetBindGroupLayout(0),
                                                 {{0, texture.CreateView(&viewDesc)}}),
                            testing::HasSubstr("must be identity"));
    }
}

// Test color attachment requires identity swizzle.
TEST_F(ComponentSwizzleTextureViewValidationTests, ColorAttachmentRequiresIdentitySwizzle) {
    wgpu::Texture texture = Create2DArrayTexture(device, /*arrayLayerCount=*/1, kWidth, kHeight,
                                                 /*mipLevelCount=*/1);
    wgpu::TextureViewDescriptor viewDesc = {};
    wgpu::TextureComponentSwizzleDescriptor swizzleDesc = {};
    viewDesc.nextInChain = &swizzleDesc;

    // Control case: identity swizzle
    {
        utils::ComboRenderPassDescriptor renderPassDesc({texture.CreateView(&viewDesc)});
        wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
        wgpu::RenderPassEncoder renderPassEncoder = commandEncoder.BeginRenderPass(&renderPassDesc);
        renderPassEncoder.End();
        commandEncoder.Finish();
    }

    // Non-identical swizzle cases
    for (auto changedChannel :
         {SwizzleChannel::R, SwizzleChannel::G, SwizzleChannel::B, SwizzleChannel::A}) {
        swizzleDesc = GetIdenticalButOneSwizzleDesc(changedChannel);
        utils::ComboRenderPassDescriptor renderPassDesc({texture.CreateView(&viewDesc)});
        wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
        wgpu::RenderPassEncoder renderPassEncoder = commandEncoder.BeginRenderPass(&renderPassDesc);
        renderPassEncoder.End();
        ASSERT_DEVICE_ERROR(commandEncoder.Finish(),
                            testing::HasSubstr("The color attachment swizzle must be identity"));
    }
}

// Test resolve target requires identity swizzle.
TEST_F(ComponentSwizzleTextureViewValidationTests, ResolveTargetRequiresIdentitySwizzle) {
    wgpu::Texture texture = Create2DArrayTexture(device, /*arrayLayerCount=*/1, kWidth, kHeight,
                                                 /*mipLevelCount=*/1, /*sampleCount=*/4);
    wgpu::TextureView view = texture.CreateView();

    wgpu::Texture resolveTargetTexture =
        Create2DArrayTexture(device, /*arrayLayerCount=*/1, kWidth, kHeight,
                             /*mipLevelCount=*/1);
    wgpu::TextureViewDescriptor viewDesc = {};
    wgpu::TextureComponentSwizzleDescriptor swizzleDesc = {};
    viewDesc.nextInChain = &swizzleDesc;

    // Control case: identity swizzle
    {
        utils::ComboRenderPassDescriptor renderPassDesc({view});
        renderPassDesc.cColorAttachments[0].resolveTarget =
            resolveTargetTexture.CreateView(&viewDesc);
        wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
        wgpu::RenderPassEncoder renderPassEncoder = commandEncoder.BeginRenderPass(&renderPassDesc);
        renderPassEncoder.End();
        commandEncoder.Finish();
    }

    // Non-identical swizzle cases
    for (auto changedChannel :
         {SwizzleChannel::R, SwizzleChannel::G, SwizzleChannel::B, SwizzleChannel::A}) {
        swizzleDesc = GetIdenticalButOneSwizzleDesc(changedChannel);
        utils::ComboRenderPassDescriptor renderPassDesc({view});
        renderPassDesc.cColorAttachments[0].resolveTarget =
            resolveTargetTexture.CreateView(&viewDesc);
        wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
        wgpu::RenderPassEncoder renderPassEncoder = commandEncoder.BeginRenderPass(&renderPassDesc);
        renderPassEncoder.End();
        ASSERT_DEVICE_ERROR(commandEncoder.Finish(),
                            testing::HasSubstr("The resolve target swizzle must be identity"));
    }
}

// Test depth stencil attachment requires identity swizzle.
TEST_F(ComponentSwizzleTextureViewValidationTests, DepthStencilAttachmentRequiresIdentitySwizzle) {
    wgpu::Texture texture = Create2DArrayTexture(device, /*arrayLayerCount=*/1, kWidth, kHeight,
                                                 /*mipLevelCount=*/1);
    wgpu::TextureView view = texture.CreateView();

    wgpu::TextureDescriptor textureDesc = {};
    textureDesc.size = {kWidth, kHeight, 1};
    textureDesc.usage = wgpu::TextureUsage::RenderAttachment;
    textureDesc.format = wgpu::TextureFormat::Depth24PlusStencil8;
    wgpu::Texture depthStencilTexture = device.CreateTexture(&textureDesc);

    wgpu::TextureViewDescriptor viewDesc = {};
    viewDesc.aspect = wgpu::TextureAspect::All;
    wgpu::TextureComponentSwizzleDescriptor swizzleDesc = {};
    viewDesc.nextInChain = &swizzleDesc;

    // Control case: identity swizzle
    {
        utils::ComboRenderPassDescriptor renderPassDesc({view},
                                                        depthStencilTexture.CreateView(&viewDesc));
        wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
        wgpu::RenderPassEncoder renderPassEncoder = commandEncoder.BeginRenderPass(&renderPassDesc);
        renderPassEncoder.End();
        commandEncoder.Finish();
    }

    // Non-identical swizzle cases
    for (auto changedChannel :
         {SwizzleChannel::R, SwizzleChannel::G, SwizzleChannel::B, SwizzleChannel::A}) {
        swizzleDesc = GetIdenticalButOneSwizzleDesc(changedChannel);
        utils::ComboRenderPassDescriptor renderPassDesc({view},
                                                        depthStencilTexture.CreateView(&viewDesc));
        wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
        wgpu::RenderPassEncoder renderPassEncoder = commandEncoder.BeginRenderPass(&renderPassDesc);
        renderPassEncoder.End();
        ASSERT_DEVICE_ERROR(
            commandEncoder.Finish(),
            testing::HasSubstr("The depth stencil attachment swizzle must be identity"));
    }
}

class D32S8TextureViewValidationTests : public ValidationTest {
  protected:
    std::vector<wgpu::FeatureName> GetRequiredFeatures() override {
        return {wgpu::FeatureName::Depth32FloatStencil8};
    }
};

// Test that the selected TextureAspects must exist in the Depth32FloatStencil8 texture format
TEST_F(D32S8TextureViewValidationTests, AspectMustExist) {
    wgpu::Texture texture =
        CreateDepthStencilTexture(device, wgpu::TextureFormat::Depth32FloatStencil8);

    // Can select: All, DepthOnly, and StencilOnly from Depth32FloatStencil8
    {
        wgpu::TextureViewDescriptor viewDescriptor = {};
        viewDescriptor.aspect = wgpu::TextureAspect::All;
        texture.CreateView(&viewDescriptor);

        viewDescriptor.aspect = wgpu::TextureAspect::DepthOnly;
        texture.CreateView(&viewDescriptor);

        viewDescriptor.aspect = wgpu::TextureAspect::StencilOnly;
        texture.CreateView(&viewDescriptor);
    }
}

// Test the format compatibility rules when creating a texture view.
TEST_F(D32S8TextureViewValidationTests, TextureViewFormatCompatibility) {
    wgpu::Texture texture =
        CreateDepthStencilTexture(device, wgpu::TextureFormat::Depth32FloatStencil8);

    wgpu::TextureViewDescriptor base2DTextureViewDescriptor =
        CreateDefaultViewDescriptor(wgpu::TextureViewDimension::e2D);

    // It is an error to create a texture view in color format on a depth-stencil texture.
    {
        wgpu::TextureViewDescriptor descriptor = base2DTextureViewDescriptor;
        descriptor.format = wgpu::TextureFormat::RGBA8Unorm;
        ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
    }
}

class TexelBufferViewValidationTest : public ValidationTest {
  protected:
    wgpu::Buffer CreateTexelBuffer(uint64_t size, wgpu::BufferUsage usage) {
        wgpu::BufferDescriptor desc;
        desc.size = size;
        desc.usage = usage;
        return device.CreateBuffer(&desc);
    }
};

// Valid texel buffer view creation
TEST_F(TexelBufferViewValidationTest, CreationSuccess) {
    constexpr uint64_t kSize = 4 * 4;  // 4 texels of RGBA8Uint
    wgpu::Buffer buffer =
        CreateTexelBuffer(kSize, wgpu::BufferUsage::TexelBuffer | wgpu::BufferUsage::CopySrc);

    wgpu::TexelBufferViewDescriptor viewDesc;
    viewDesc.format = wgpu::TextureFormat::RGBA8Uint;
    viewDesc.offset = 0;
    viewDesc.size = kSize;

    buffer.CreateTexelView(&viewDesc);
}

// Format must not be undefined
TEST_F(TexelBufferViewValidationTest, UndefinedFormat) {
    wgpu::Buffer buffer = CreateTexelBuffer(256, wgpu::BufferUsage::TexelBuffer);

    wgpu::TexelBufferViewDescriptor viewDesc;
    viewDesc.format = wgpu::TextureFormat::Undefined;
    viewDesc.offset = 0;
    viewDesc.size = 256;

    ASSERT_DEVICE_ERROR(buffer.CreateTexelView(&viewDesc));
}

// Offset must be aligned to the texel size
TEST_F(TexelBufferViewValidationTest, OffsetAlignment) {
    wgpu::Buffer buffer = CreateTexelBuffer(512, wgpu::BufferUsage::TexelBuffer);

    wgpu::TexelBufferViewDescriptor viewDesc;
    viewDesc.format = wgpu::TextureFormat::RGBA8Uint;
    viewDesc.offset = 2;  // Not aligned to 4-byte texel
    viewDesc.size = 256;

    ASSERT_DEVICE_ERROR(buffer.CreateTexelView(&viewDesc));
}

// Size must be multiple of texel size
TEST_F(TexelBufferViewValidationTest, SizeMustBeMultipleOfTexel) {
    wgpu::Buffer buffer = CreateTexelBuffer(512, wgpu::BufferUsage::TexelBuffer);

    wgpu::TexelBufferViewDescriptor viewDesc;
    viewDesc.format = wgpu::TextureFormat::RGBA8Uint;
    viewDesc.offset = 256;
    viewDesc.size = 3;  // Not multiple of 4-byte texel

    ASSERT_DEVICE_ERROR(buffer.CreateTexelView(&viewDesc));
}

// Range may not exceed buffer size
TEST_F(TexelBufferViewValidationTest, RangeExceedsBuffer) {
    wgpu::Buffer buffer = CreateTexelBuffer(256, wgpu::BufferUsage::TexelBuffer);

    wgpu::TexelBufferViewDescriptor viewDesc;
    viewDesc.format = wgpu::TextureFormat::RGBA8Uint;
    viewDesc.offset = 0;
    viewDesc.size = 512;  // Larger than buffer

    ASSERT_DEVICE_ERROR(buffer.CreateTexelView(&viewDesc));
}

// Depth formats are not allowed
TEST_F(TexelBufferViewValidationTest, DepthFormatNotAllowed) {
    wgpu::Buffer buffer = CreateTexelBuffer(256, wgpu::BufferUsage::TexelBuffer);

    wgpu::TexelBufferViewDescriptor viewDesc;
    viewDesc.format = wgpu::TextureFormat::Depth24Plus;
    viewDesc.offset = 0;
    viewDesc.size = 256;

    ASSERT_DEVICE_ERROR(buffer.CreateTexelView(&viewDesc));
}

// R32Float is allowed
TEST_F(TexelBufferViewValidationTest, R32FloatAllowed) {
    wgpu::Buffer buffer = CreateTexelBuffer(256, wgpu::BufferUsage::TexelBuffer);

    wgpu::TexelBufferViewDescriptor viewDesc;
    viewDesc.format = wgpu::TextureFormat::R32Float;
    viewDesc.offset = 0;
    viewDesc.size = 256;

    buffer.CreateTexelView(&viewDesc);
}

// RG8Uint is not a supported texel format
TEST_F(TexelBufferViewValidationTest, ColorFormatNotAllowed) {
    wgpu::Buffer buffer = CreateTexelBuffer(256, wgpu::BufferUsage::TexelBuffer);

    wgpu::TexelBufferViewDescriptor viewDesc;
    viewDesc.format = wgpu::TextureFormat::RG8Uint;
    viewDesc.offset = 0;
    viewDesc.size = 256;

    ASSERT_DEVICE_ERROR(buffer.CreateTexelView(&viewDesc));
}

}  // anonymous namespace
}  // namespace dawn
