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

#include <vector>

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

namespace dawn {
namespace {

class ExternalTextureTest : public ValidationTest {
  public:
    wgpu::TextureDescriptor CreateTextureDescriptor(
        wgpu::TextureFormat format = kDefaultTextureFormat) {
        wgpu::TextureDescriptor descriptor;
        descriptor.size.width = kWidth;
        descriptor.size.height = kHeight;
        descriptor.size.depthOrArrayLayers = kDefaultDepth;
        descriptor.mipLevelCount = kDefaultMipLevels;
        descriptor.sampleCount = kDefaultSampleCount;
        descriptor.dimension = wgpu::TextureDimension::e2D;
        descriptor.format = format;
        descriptor.usage = kDefaultUsage;
        return descriptor;
    }

    wgpu::ExternalTexture CreateDefaultExternalTexture() {
        wgpu::ExternalTextureDescriptor externalDesc = CreateDefaultExternalTextureDescriptor();
        externalDesc.plane0 = defaultTexture.CreateView();
        return device.CreateExternalTexture(&externalDesc);
    }

    void SubmitExternalTextureInDefaultRenderPass(wgpu::ExternalTexture externalTexture,
                                                  bool success) {
        // Create a bind group that contains the external texture.
        wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
            device, {{0, wgpu::ShaderStage::Fragment, &utils::kExternalTextureBindingLayout}});
        wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, bgl, {{0, externalTexture}});

        // Create another texture to use as a color attachment.
        wgpu::TextureDescriptor renderTextureDescriptor = CreateTextureDescriptor();
        wgpu::Texture renderTexture = device.CreateTexture(&renderTextureDescriptor);
        wgpu::TextureView renderView = renderTexture.CreateView();

        utils::ComboRenderPassDescriptor renderPass({renderView}, nullptr);
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
        pass.SetBindGroup(0, bindGroup);
        pass.End();
        wgpu::CommandBuffer commands = encoder.Finish();

        if (success) {
            queue.Submit(1, &commands);
        } else {
            ASSERT_DEVICE_ERROR(queue.Submit(1, &commands));
        }
    }

    void SubmitExternalTextureInDefaultRenderBundle(wgpu::ExternalTexture externalTexture,
                                                    bool success) {
        // Create a bind group that contains the external texture.
        wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
            device, {{0, wgpu::ShaderStage::Fragment, &utils::kExternalTextureBindingLayout}});
        wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, bgl, {{0, externalTexture}});

        // Create another texture to use as a color attachment.
        wgpu::TextureDescriptor renderTextureDescriptor = CreateTextureDescriptor();
        wgpu::Texture renderTexture = device.CreateTexture(&renderTextureDescriptor);
        wgpu::TextureView renderView = renderTexture.CreateView();

        // Create a RenderBundle using the bindgroup and doing nothing else.
        wgpu::RenderBundleEncoderDescriptor rbDesc;
        rbDesc.colorFormatCount = 1;
        rbDesc.colorFormats = &renderTextureDescriptor.format;

        wgpu::RenderBundleEncoder rbEncoder = device.CreateRenderBundleEncoder(&rbDesc);
        rbEncoder.SetBindGroup(0, bindGroup);
        wgpu::RenderBundle bundle = rbEncoder.Finish();

        // Use that render bundle in the render pass.
        utils::ComboRenderPassDescriptor renderPass({renderView}, nullptr);
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
        pass.ExecuteBundles(1, &bundle);
        pass.End();
        wgpu::CommandBuffer commands = encoder.Finish();

        if (success) {
            queue.Submit(1, &commands);
        } else {
            ASSERT_DEVICE_ERROR(queue.Submit(1, &commands));
        }
    }

    void SubmitExternalTextureInDefaultComputePass(wgpu::ExternalTexture externalTexture,
                                                   bool success) {
        // Create a bind group that contains the external texture.
        wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
            device, {{0, wgpu::ShaderStage::Fragment, &utils::kExternalTextureBindingLayout}});
        wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, bgl, {{0, externalTexture}});

        wgpu::ComputePassDescriptor computePass;

        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
        wgpu::ComputePassEncoder pass = encoder.BeginComputePass(&computePass);
        pass.SetBindGroup(0, bindGroup);
        pass.End();
        wgpu::CommandBuffer commands = encoder.Finish();

        if (success) {
            queue.Submit(1, &commands);
        } else {
            ASSERT_DEVICE_ERROR(queue.Submit(1, &commands));
        }
    }

  protected:
    void SetUp() override {
        ValidationTest::SetUp();

        queue = device.GetQueue();

        wgpu::TextureDescriptor textureDescriptor = CreateTextureDescriptor();
        defaultTexture = device.CreateTexture(&textureDescriptor);
    }

    wgpu::ExternalTextureDescriptor CreateDefaultExternalTextureDescriptor() {
        wgpu::ExternalTextureDescriptor desc;
        desc.yuvToRgbConversionMatrix = mPlaceholderConstantArray.data();
        desc.gamutConversionMatrix = mPlaceholderConstantArray.data();
        desc.srcTransferFunctionParameters = mPlaceholderConstantArray.data();
        desc.dstTransferFunctionParameters = mPlaceholderConstantArray.data();
        desc.cropSize = {kWidth, kHeight};
        desc.apparentSize = {kWidth, kHeight};

        return desc;
    }

    static constexpr uint32_t kWidth = 32;
    static constexpr uint32_t kHeight = 32;
    static constexpr uint32_t kDefaultDepth = 1;
    static constexpr uint32_t kDefaultMipLevels = 1;
    static constexpr uint32_t kDefaultSampleCount = 1;
    static constexpr wgpu::TextureUsage kDefaultUsage =
        wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment;

    static constexpr wgpu::TextureFormat kDefaultTextureFormat = wgpu::TextureFormat::RGBA8Unorm;
    static constexpr wgpu::TextureFormat kBiplanarPlane0Format = wgpu::TextureFormat::R8Unorm;
    static constexpr wgpu::TextureFormat kBiplanarPlane1Format = wgpu::TextureFormat::RG8Unorm;

    std::array<float, 12> mPlaceholderConstantArray;

    wgpu::Queue queue;
    wgpu::Texture defaultTexture;
};

TEST_F(ExternalTextureTest, CreateExternalTextureValidation) {
    // Creating an external texture from a 2D, single-subresource texture should succeed.
    {
        wgpu::TextureDescriptor textureDescriptor = CreateTextureDescriptor();
        wgpu::Texture texture = device.CreateTexture(&textureDescriptor);

        wgpu::ExternalTextureDescriptor externalDesc = CreateDefaultExternalTextureDescriptor();
        externalDesc.plane0 = texture.CreateView();
        device.CreateExternalTexture(&externalDesc);
    }

    // Creating an external texture from a non-2D texture should fail.
    {
        wgpu::TextureDescriptor textureDescriptor = CreateTextureDescriptor();
        textureDescriptor.dimension = wgpu::TextureDimension::e3D;
        textureDescriptor.usage = wgpu::TextureUsage::TextureBinding;
        wgpu::Texture internalTexture = device.CreateTexture(&textureDescriptor);

        wgpu::ExternalTextureDescriptor externalDesc = CreateDefaultExternalTextureDescriptor();
        externalDesc.plane0 = internalTexture.CreateView();
        ASSERT_DEVICE_ERROR(device.CreateExternalTexture(&externalDesc));
    }

    // Creating an external texture from a texture with mip count > 1 should fail.
    {
        wgpu::TextureDescriptor textureDescriptor = CreateTextureDescriptor();
        textureDescriptor.mipLevelCount = 2;
        wgpu::Texture internalTexture = device.CreateTexture(&textureDescriptor);

        wgpu::ExternalTextureDescriptor externalDesc = CreateDefaultExternalTextureDescriptor();
        externalDesc.plane0 = internalTexture.CreateView();
        ASSERT_DEVICE_ERROR(device.CreateExternalTexture(&externalDesc));
    }

    // Creating an external texture from a texture without TextureUsage::TextureBinding should
    // fail.
    {
        wgpu::TextureDescriptor textureDescriptor = {};
        textureDescriptor.usage = wgpu::TextureUsage::RenderAttachment;
        textureDescriptor.size = {kWidth, kHeight, kDefaultDepth};
        textureDescriptor.format = kDefaultTextureFormat;
        wgpu::Texture internalTexture = device.CreateTexture(&textureDescriptor);

        wgpu::ExternalTextureDescriptor externalDesc = CreateDefaultExternalTextureDescriptor();
        externalDesc.plane0 = internalTexture.CreateView();
        ASSERT_DEVICE_ERROR(device.CreateExternalTexture(&externalDesc));
    }

    // Creating an external texture with a non 4-component format should fail.
    {
        for (const auto& format : {wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::RG8Unorm}) {
            wgpu::TextureDescriptor textureDescriptor = CreateTextureDescriptor();
            textureDescriptor.format = format;
            wgpu::Texture internalTexture = device.CreateTexture(&textureDescriptor);

            wgpu::ExternalTextureDescriptor externalDesc = CreateDefaultExternalTextureDescriptor();
            externalDesc.plane0 = internalTexture.CreateView();
            ASSERT_DEVICE_ERROR(device.CreateExternalTexture(&externalDesc));
        }
    }

    // Creating an external texture with a non float-filterable format should fail.
    {
        for (const auto& format : {wgpu::TextureFormat::RGBA8Uint, wgpu::TextureFormat::RGBA8Sint,
                                   wgpu::TextureFormat::RGBA32Uint, wgpu::TextureFormat::RGBA32Sint,
                                   wgpu::TextureFormat::RGBA32Float}) {
            wgpu::TextureDescriptor textureDescriptor = CreateTextureDescriptor();
            textureDescriptor.format = format;
            wgpu::Texture internalTexture = device.CreateTexture(&textureDescriptor);

            wgpu::ExternalTextureDescriptor externalDesc = CreateDefaultExternalTextureDescriptor();
            externalDesc.plane0 = internalTexture.CreateView();
            ASSERT_DEVICE_ERROR(device.CreateExternalTexture(&externalDesc));
        }
    }

    // Creating an external texture with an multisampled texture should fail.
    {
        wgpu::TextureDescriptor textureDescriptor = CreateTextureDescriptor();
        textureDescriptor.sampleCount = 4;
        wgpu::Texture internalTexture = device.CreateTexture(&textureDescriptor);

        wgpu::ExternalTextureDescriptor externalDesc = CreateDefaultExternalTextureDescriptor();
        externalDesc.plane0 = internalTexture.CreateView();
        ASSERT_DEVICE_ERROR(device.CreateExternalTexture(&externalDesc));
    }

    // Creating an external texture with an error texture view should fail.
    {
        wgpu::TextureDescriptor textureDescriptor = CreateTextureDescriptor();
        wgpu::Texture internalTexture = device.CreateTexture(&textureDescriptor);

        wgpu::TextureViewDescriptor errorViewDescriptor;
        errorViewDescriptor.format = kDefaultTextureFormat;
        errorViewDescriptor.dimension = wgpu::TextureViewDimension::e2D;
        errorViewDescriptor.mipLevelCount = 1;
        errorViewDescriptor.arrayLayerCount = 2;
        ASSERT_DEVICE_ERROR(wgpu::TextureView errorTextureView =
                                internalTexture.CreateView(&errorViewDescriptor));

        wgpu::ExternalTextureDescriptor externalDesc = CreateDefaultExternalTextureDescriptor();
        externalDesc.plane0 = errorTextureView;
        ASSERT_DEVICE_ERROR(device.CreateExternalTexture(&externalDesc));
    }
}

TEST_F(ExternalTextureTest, CreateExternalTextureConstantValueValidation) {
    DAWN_SKIP_TEST_IF(UsesWire());
    // Creating a single plane external texture without a YUV-to-RGB matrix should pass.
    {
        wgpu::TextureDescriptor textureDescriptor = CreateTextureDescriptor();
        wgpu::Texture texture = device.CreateTexture(&textureDescriptor);

        wgpu::ExternalTextureDescriptor externalDesc = CreateDefaultExternalTextureDescriptor();
        externalDesc.plane0 = texture.CreateView();
        externalDesc.yuvToRgbConversionMatrix = nullptr;
        device.CreateExternalTexture(&externalDesc);
    }

    // Creating a multiplanar external texture without a YUV-to-RGB matrix should fail.
    {
        wgpu::TextureDescriptor plane0TextureDescriptor =
            CreateTextureDescriptor(kBiplanarPlane0Format);
        wgpu::TextureDescriptor plane1TextureDescriptor =
            CreateTextureDescriptor(kBiplanarPlane1Format);
        wgpu::Texture texture0 = device.CreateTexture(&plane0TextureDescriptor);
        wgpu::Texture texture1 = device.CreateTexture(&plane1TextureDescriptor);

        wgpu::ExternalTextureDescriptor externalDesc = CreateDefaultExternalTextureDescriptor();
        externalDesc.plane0 = texture0.CreateView();
        externalDesc.plane1 = texture1.CreateView();
        externalDesc.yuvToRgbConversionMatrix = nullptr;
        ASSERT_DEVICE_ERROR(device.CreateExternalTexture(&externalDesc));
    }

    // Creating an external texture without a gamut conversion matrix should fail.
    {
        wgpu::TextureDescriptor textureDescriptor = CreateTextureDescriptor();
        wgpu::Texture texture = device.CreateTexture(&textureDescriptor);

        wgpu::ExternalTextureDescriptor externalDesc = CreateDefaultExternalTextureDescriptor();
        externalDesc.plane0 = texture.CreateView();
        externalDesc.gamutConversionMatrix = nullptr;
        ASSERT_DEVICE_ERROR(device.CreateExternalTexture(&externalDesc));
    }

    // Creating an external texture without source transfer function constants should fail.
    {
        wgpu::TextureDescriptor textureDescriptor = CreateTextureDescriptor();
        wgpu::Texture texture = device.CreateTexture(&textureDescriptor);

        wgpu::ExternalTextureDescriptor externalDesc = CreateDefaultExternalTextureDescriptor();
        externalDesc.plane0 = texture.CreateView();
        externalDesc.srcTransferFunctionParameters = nullptr;
        ASSERT_DEVICE_ERROR(device.CreateExternalTexture(&externalDesc));
    }

    // Creating an external texture without destination transfer function constants should fail.
    {
        wgpu::TextureDescriptor textureDescriptor = CreateTextureDescriptor();
        wgpu::Texture texture = device.CreateTexture(&textureDescriptor);

        wgpu::ExternalTextureDescriptor externalDesc = CreateDefaultExternalTextureDescriptor();
        externalDesc.plane0 = texture.CreateView();
        externalDesc.dstTransferFunctionParameters = nullptr;
        ASSERT_DEVICE_ERROR(device.CreateExternalTexture(&externalDesc));
    }
}

// Test that external texture creation works as expected in multiplane scenarios.
TEST_F(ExternalTextureTest, CreateMultiplanarExternalTextureValidation) {
    // Creating an external texture from two 2D, single-subresource textures with a biplanar
    // format should succeed.
    {
        wgpu::TextureDescriptor plane0TextureDescriptor =
            CreateTextureDescriptor(kBiplanarPlane0Format);
        wgpu::TextureDescriptor plane1TextureDescriptor =
            CreateTextureDescriptor(kBiplanarPlane1Format);
        wgpu::Texture texture0 = device.CreateTexture(&plane0TextureDescriptor);
        wgpu::Texture texture1 = device.CreateTexture(&plane1TextureDescriptor);

        wgpu::ExternalTextureDescriptor externalDesc = CreateDefaultExternalTextureDescriptor();
        externalDesc.plane0 = texture0.CreateView();
        externalDesc.plane1 = texture1.CreateView();

        device.CreateExternalTexture(&externalDesc);
    }

    // Creating a multiplanar external texture with an 1-component float-filterable format for
    // plane0 should succeed.
    {
        for (const auto& format : {wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::R16Float}) {
            wgpu::TextureDescriptor plane0TextureDescriptor = CreateTextureDescriptor(format);
            wgpu::TextureDescriptor plane1TextureDescriptor =
                CreateTextureDescriptor(wgpu::TextureFormat::RG8Unorm);
            wgpu::Texture texture0 = device.CreateTexture(&plane0TextureDescriptor);
            wgpu::Texture texture1 = device.CreateTexture(&plane1TextureDescriptor);

            wgpu::ExternalTextureDescriptor externalDesc = CreateDefaultExternalTextureDescriptor();
            externalDesc.plane0 = texture0.CreateView();
            externalDesc.plane1 = texture1.CreateView();

            device.CreateExternalTexture(&externalDesc);
        }
    }

    // Creating a multiplanar external texture with a 2-component float-filterable format for
    // plane1 should succeed.
    {
        for (const auto& format : {wgpu::TextureFormat::RG8Unorm, wgpu::TextureFormat::RG16Float}) {
            wgpu::TextureDescriptor plane0TextureDescriptor =
                CreateTextureDescriptor(wgpu::TextureFormat::R8Unorm);
            wgpu::TextureDescriptor plane1TextureDescriptor = CreateTextureDescriptor(format);
            wgpu::Texture texture0 = device.CreateTexture(&plane0TextureDescriptor);
            wgpu::Texture texture1 = device.CreateTexture(&plane1TextureDescriptor);

            wgpu::ExternalTextureDescriptor externalDesc = CreateDefaultExternalTextureDescriptor();
            externalDesc.plane0 = texture0.CreateView();
            externalDesc.plane1 = texture1.CreateView();

            device.CreateExternalTexture(&externalDesc);
        }
    }

    // Creating a multiplanar external texture with an 1-component non float-filterable format for
    // plane0 should fail.
    {
        for (const auto& format : {wgpu::TextureFormat::R8Uint, wgpu::TextureFormat::R8Sint,
                                   wgpu::TextureFormat::R16Uint, wgpu::TextureFormat::R16Sint,
                                   wgpu::TextureFormat::R32Uint, wgpu::TextureFormat::R32Sint,
                                   wgpu::TextureFormat::R32Float}) {
            wgpu::TextureDescriptor plane0TextureDescriptor = CreateTextureDescriptor(format);
            wgpu::TextureDescriptor plane1TextureDescriptor =
                CreateTextureDescriptor(wgpu::TextureFormat::RG8Unorm);
            wgpu::Texture texture0 = device.CreateTexture(&plane0TextureDescriptor);
            wgpu::Texture texture1 = device.CreateTexture(&plane1TextureDescriptor);

            wgpu::ExternalTextureDescriptor externalDesc = CreateDefaultExternalTextureDescriptor();
            externalDesc.plane0 = texture0.CreateView();
            externalDesc.plane1 = texture1.CreateView();

            ASSERT_DEVICE_ERROR(device.CreateExternalTexture(&externalDesc));
        }
    }

    // Creating a multiplanar external texture with a 2-component non float-filterable format for
    // plane1 should fail.
    {
        for (const auto& format : {wgpu::TextureFormat::RG8Uint, wgpu::TextureFormat::RG8Sint,
                                   wgpu::TextureFormat::RG16Uint, wgpu::TextureFormat::RG16Sint,
                                   wgpu::TextureFormat::RG32Uint, wgpu::TextureFormat::RG32Sint,
                                   wgpu::TextureFormat::RG32Float}) {
            wgpu::TextureDescriptor plane0TextureDescriptor =
                CreateTextureDescriptor(wgpu::TextureFormat::R8Unorm);
            wgpu::TextureDescriptor plane1TextureDescriptor = CreateTextureDescriptor(format);
            wgpu::Texture texture0 = device.CreateTexture(&plane0TextureDescriptor);
            wgpu::Texture texture1 = device.CreateTexture(&plane1TextureDescriptor);

            wgpu::ExternalTextureDescriptor externalDesc = CreateDefaultExternalTextureDescriptor();
            externalDesc.plane0 = texture0.CreateView();
            externalDesc.plane1 = texture1.CreateView();

            ASSERT_DEVICE_ERROR(device.CreateExternalTexture(&externalDesc));
        }
    }

    // Creating a multiplanar external texture with a non 1-component format for
    // plane0 should fail.
    {
        for (const auto& format :
             {wgpu::TextureFormat::RG8Unorm, wgpu::TextureFormat::RGBA8Unorm,
              wgpu::TextureFormat::RG8Uint, wgpu::TextureFormat::RGBA8Uint,
              wgpu::TextureFormat::RG8Sint, wgpu::TextureFormat::RGBA8Sint,
              wgpu::TextureFormat::RG32Float, wgpu::TextureFormat::RGBA32Float}) {
            wgpu::TextureDescriptor plane0TextureDescriptor = CreateTextureDescriptor(format);
            wgpu::TextureDescriptor plane1TextureDescriptor =
                CreateTextureDescriptor(wgpu::TextureFormat::RG8Unorm);
            wgpu::Texture texture0 = device.CreateTexture(&plane0TextureDescriptor);
            wgpu::Texture texture1 = device.CreateTexture(&plane1TextureDescriptor);

            wgpu::ExternalTextureDescriptor externalDesc = CreateDefaultExternalTextureDescriptor();
            externalDesc.plane0 = texture0.CreateView();
            externalDesc.plane1 = texture1.CreateView();

            ASSERT_DEVICE_ERROR(device.CreateExternalTexture(&externalDesc));
        }
    }

    // Creating a multiplanar external texture with a non 2-component format for
    // plane1 should fail.
    {
        for (const auto& format :
             {wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::RGBA8Unorm,
              wgpu::TextureFormat::R8Uint, wgpu::TextureFormat::RGBA8Uint,
              wgpu::TextureFormat::R32Float, wgpu::TextureFormat::RGBA32Float}) {
            wgpu::TextureDescriptor plane0TextureDescriptor =
                CreateTextureDescriptor(wgpu::TextureFormat::R8Unorm);
            wgpu::TextureDescriptor plane1TextureDescriptor = CreateTextureDescriptor(format);
            wgpu::Texture texture0 = device.CreateTexture(&plane0TextureDescriptor);
            wgpu::Texture texture1 = device.CreateTexture(&plane1TextureDescriptor);

            wgpu::ExternalTextureDescriptor externalDesc = CreateDefaultExternalTextureDescriptor();
            externalDesc.plane0 = texture0.CreateView();
            externalDesc.plane1 = texture1.CreateView();

            ASSERT_DEVICE_ERROR(device.CreateExternalTexture(&externalDesc));
        }
    }
}

// Test that refresh on an expired/active external texture.
TEST_F(ExternalTextureTest, RefreshExternalTexture) {
    wgpu::ExternalTexture externalTexture = CreateDefaultExternalTexture();

    externalTexture.Refresh();
    externalTexture.Expire();
    externalTexture.Refresh();
}

// Test that refresh on a destroyed external texture results in an error.
TEST_F(ExternalTextureTest, RefreshDestroyedExternalTexture) {
    wgpu::ExternalTexture externalTexture = CreateDefaultExternalTexture();

    // Refresh on destroyed external texture should result in an error.
    externalTexture.Destroy();
    ASSERT_DEVICE_ERROR(externalTexture.Refresh());
}

// Test that expire on a destroyed external texture results in an error.
TEST_F(ExternalTextureTest, ExpireDestroyedExternalTexture) {
    wgpu::ExternalTexture externalTexture = CreateDefaultExternalTexture();

    externalTexture.Destroy();
    ASSERT_DEVICE_ERROR(externalTexture.Expire());
}

// Test that submitting a render pass that contains an active external texture.
TEST_F(ExternalTextureTest, SubmitActiveExternalTextureInRenderPass) {
    wgpu::ExternalTexture externalTexture = CreateDefaultExternalTexture();
    SubmitExternalTextureInDefaultRenderPass(externalTexture, true /* success = true */);
}

// Test that submitting a render pass that contains an expired external texture results in an error.
TEST_F(ExternalTextureTest, SubmitExpiredExternalTextureInRenderPass) {
    wgpu::ExternalTexture externalTexture = CreateDefaultExternalTexture();
    externalTexture.Expire();
    SubmitExternalTextureInDefaultRenderPass(externalTexture, false /* success = false */);
}

// Test that submitting a render pass that contains an destroyed external texture results in an
// error.
TEST_F(ExternalTextureTest, SubmitDestroyedExternalTextureInRenderPass) {
    wgpu::ExternalTexture externalTexture = CreateDefaultExternalTexture();
    externalTexture.Destroy();
    SubmitExternalTextureInDefaultRenderPass(externalTexture, false /* success = false */);
}

// Test that submitting a compute pass that contains an active external.
TEST_F(ExternalTextureTest, SubmitActiveExternalTextureInComputePass) {
    wgpu::ExternalTexture externalTexture = CreateDefaultExternalTexture();
    SubmitExternalTextureInDefaultComputePass(externalTexture, true /* success = true */);
}

// Test that submitting a compute pass that contains an expired external texture results in an
// error.
TEST_F(ExternalTextureTest, SubmitExpiredExternalTextureInComputePass) {
    wgpu::ExternalTexture externalTexture = CreateDefaultExternalTexture();
    externalTexture.Expire();
    SubmitExternalTextureInDefaultComputePass(externalTexture, false /* success = false */);
}

// Test that submitting a compute pass that contains a destroyed external texture should success.
TEST_F(ExternalTextureTest, SubmitDestroyedExternalTextureInComputePass) {
    wgpu::ExternalTexture externalTexture = CreateDefaultExternalTexture();
    externalTexture.Destroy();
    SubmitExternalTextureInDefaultComputePass(externalTexture, false /* success = false */);
}

// Test that submitting a render bundle that contains an active external.
TEST_F(ExternalTextureTest, SubmitActiveExternalTextureInRenderBundle) {
    wgpu::ExternalTexture externalTexture = CreateDefaultExternalTexture();
    SubmitExternalTextureInDefaultRenderBundle(externalTexture, true /* success = true */);
}

// Test that submitting a render bundle that contains an expired external texture results in an
// error.
TEST_F(ExternalTextureTest, SubmitExpiredExternalTextureInRenderBundle) {
    wgpu::ExternalTexture externalTexture = CreateDefaultExternalTexture();
    externalTexture.Expire();
    SubmitExternalTextureInDefaultRenderBundle(externalTexture, false /* success = false */);
}

// Test that submitting a render bundle that contains a destroyed external texture should success.
TEST_F(ExternalTextureTest, SubmitDestroyedExternalTextureInRenderBundle) {
    wgpu::ExternalTexture externalTexture = CreateDefaultExternalTexture();
    externalTexture.Destroy();
    SubmitExternalTextureInDefaultRenderBundle(externalTexture, false /* success = false */);
}

// Test that refresh an expired external texture and submit a compute pass with it.
TEST_F(ExternalTextureTest, RefreshExpiredExternalTexture) {
    wgpu::ExternalTexture externalTexture = CreateDefaultExternalTexture();
    externalTexture.Expire();

    // Submit with expired external texture results in error
    SubmitExternalTextureInDefaultComputePass(externalTexture, false /* success = false */);

    externalTexture.Refresh();
    // Refreshed external texture could be submitted.
    SubmitExternalTextureInDefaultComputePass(externalTexture, true /* success = true */);
}

// Test that submitting a render pass that contains a dereferenced external texture results in
// success
TEST_F(ExternalTextureTest, SubmitDereferencedExternalTextureInRenderPass) {
    wgpu::TextureDescriptor textureDescriptor = CreateTextureDescriptor();
    wgpu::Texture texture = device.CreateTexture(&textureDescriptor);

    wgpu::ExternalTextureDescriptor externalDesc = CreateDefaultExternalTextureDescriptor();
    externalDesc.plane0 = texture.CreateView();
    wgpu::ExternalTexture externalTexture = device.CreateExternalTexture(&externalDesc);

    // Create a bind group that contains the external texture.
    wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
        device, {{0, wgpu::ShaderStage::Fragment, &utils::kExternalTextureBindingLayout}});
    wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, bgl, {{0, externalTexture}});

    // Create another texture to use as a color attachment.
    wgpu::TextureDescriptor renderTextureDescriptor = CreateTextureDescriptor();
    wgpu::Texture renderTexture = device.CreateTexture(&renderTextureDescriptor);
    wgpu::TextureView renderView = renderTexture.CreateView();

    utils::ComboRenderPassDescriptor renderPass({renderView}, nullptr);

    // Control case should succeed.
    {
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
        {
            pass.SetBindGroup(0, bindGroup);
            pass.End();
        }

        wgpu::CommandBuffer commands = encoder.Finish();

        queue.Submit(1, &commands);
    }

    // Dereferencing the external texture should not result in a use-after-free error.
    {
        externalTexture = nullptr;
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
        {
            pass.SetBindGroup(0, bindGroup);
            pass.End();
        }

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

// Ensure that bind group validation catches mismatched entries for BGL external texture entries.
TEST_F(ExternalTextureTest, BindGroupDoesNotMatchLayout) {
    wgpu::TextureDescriptor textureDescriptor = CreateTextureDescriptor();
    wgpu::Texture texture = device.CreateTexture(&textureDescriptor);
    wgpu::TextureView textureView = texture.CreateView();

    wgpu::ExternalTextureDescriptor externalDesc = CreateDefaultExternalTextureDescriptor();
    externalDesc.plane0 = textureView;
    wgpu::ExternalTexture externalTexture = device.CreateExternalTexture(&externalDesc);

    // Control case for external texture should succeed.
    {
        wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
            device, {{0, wgpu::ShaderStage::Fragment, &utils::kExternalTextureBindingLayout}});
        utils::MakeBindGroup(device, bgl, {{0, externalTexture}});
    }

    // Control case for texture view should succeed.
    {
        wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
            device, {{0, wgpu::ShaderStage::Fragment, &utils::kExternalTextureBindingLayout}});
        utils::MakeBindGroup(device, bgl, {{0, textureView}});
    }

    // Bind group creation should fail when an external texture is not present in the
    // corresponding slot of the bind group layout.
    {
        wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
            device, {{0, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Uniform}});
        ASSERT_DEVICE_ERROR(utils::MakeBindGroup(device, bgl, {{0, externalTexture}}));
    }

    // Bind group creation should fail when a sampler is present in the
    // corresponding slot of the bind group layout.
    {
        wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
            device, {{0, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Uniform}});
        ASSERT_DEVICE_ERROR(utils::MakeBindGroup(device, bgl, {{0, device.CreateSampler()}}));
    }
}

// Ensure that bind group validation catches error external textures.
TEST_F(ExternalTextureTest, UseErrorExternalTextureInBindGroup) {
    // Control case should succeed.
    {
        wgpu::TextureDescriptor textureDescriptor = CreateTextureDescriptor();
        wgpu::Texture texture = device.CreateTexture(&textureDescriptor);

        wgpu::ExternalTextureDescriptor externalDesc = CreateDefaultExternalTextureDescriptor();
        externalDesc.plane0 = texture.CreateView();
        wgpu::ExternalTexture externalTexture = device.CreateExternalTexture(&externalDesc);
        wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
            device, {{0, wgpu::ShaderStage::Fragment, &utils::kExternalTextureBindingLayout}});
        utils::MakeBindGroup(device, bgl, {{0, externalTexture}});
    }

    // Bind group creation should fail when an error external texture is present.
    {
        wgpu::ExternalTexture errorExternalTexture = device.CreateErrorExternalTexture();
        wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
            device, {{0, wgpu::ShaderStage::Fragment, &utils::kExternalTextureBindingLayout}});
        ASSERT_DEVICE_ERROR(utils::MakeBindGroup(device, bgl, {{0, errorExternalTexture}}));
    }
}

// Test create external texture with too large crop rect results in error.
TEST_F(ExternalTextureTest, CreateExternalTextureWithErrorCropOriginOrSize) {
    // Control case should succeed.
    {
        wgpu::TextureDescriptor textureDescriptor = CreateTextureDescriptor();
        wgpu::Texture texture = device.CreateTexture(&textureDescriptor);

        wgpu::ExternalTextureDescriptor externalDesc = CreateDefaultExternalTextureDescriptor();
        externalDesc.plane0 = texture.CreateView();
        externalDesc.cropOrigin = {0, 0};
        externalDesc.cropSize = {texture.GetWidth(), texture.GetHeight()};
        externalDesc.apparentSize = {texture.GetWidth(), texture.GetHeight()};
        device.CreateExternalTexture(&externalDesc);
    }

    // cropOrigin is OOB on x
    {
        wgpu::TextureDescriptor textureDescriptor = CreateTextureDescriptor();
        wgpu::Texture texture = device.CreateTexture(&textureDescriptor);

        wgpu::ExternalTextureDescriptor externalDesc = CreateDefaultExternalTextureDescriptor();
        externalDesc.plane0 = texture.CreateView();
        externalDesc.cropOrigin = {1, 0};
        externalDesc.cropSize = {texture.GetWidth(), texture.GetHeight()};
        externalDesc.apparentSize = {texture.GetWidth(), texture.GetHeight()};
        ASSERT_DEVICE_ERROR(device.CreateExternalTexture(&externalDesc));
    }

    // cropOrigin is OOB on y
    {
        wgpu::TextureDescriptor textureDescriptor = CreateTextureDescriptor();
        wgpu::Texture texture = device.CreateTexture(&textureDescriptor);

        wgpu::ExternalTextureDescriptor externalDesc = CreateDefaultExternalTextureDescriptor();
        externalDesc.plane0 = texture.CreateView();
        externalDesc.cropOrigin = {0, 1};
        externalDesc.cropSize = {texture.GetWidth(), texture.GetHeight()};
        externalDesc.apparentSize = {texture.GetWidth(), texture.GetHeight()};
        ASSERT_DEVICE_ERROR(device.CreateExternalTexture(&externalDesc));
    }

    // cropSize is OOB on width
    {
        wgpu::TextureDescriptor textureDescriptor = CreateTextureDescriptor();
        wgpu::Texture texture = device.CreateTexture(&textureDescriptor);

        wgpu::ExternalTextureDescriptor externalDesc = CreateDefaultExternalTextureDescriptor();
        externalDesc.plane0 = texture.CreateView();
        externalDesc.cropOrigin = {0, 0};
        externalDesc.cropSize = {texture.GetWidth() + 1, texture.GetHeight()};
        externalDesc.apparentSize = {texture.GetWidth(), texture.GetHeight()};
        ASSERT_DEVICE_ERROR(device.CreateExternalTexture(&externalDesc));
    }

    // cropSize is OOB on height
    {
        wgpu::TextureDescriptor textureDescriptor = CreateTextureDescriptor();
        wgpu::Texture texture = device.CreateTexture(&textureDescriptor);

        wgpu::ExternalTextureDescriptor externalDesc = CreateDefaultExternalTextureDescriptor();
        externalDesc.plane0 = texture.CreateView();
        externalDesc.cropOrigin = {0, 0};
        externalDesc.cropSize = {texture.GetWidth(), texture.GetHeight() + 1};
        externalDesc.apparentSize = {texture.GetWidth(), texture.GetHeight()};
        ASSERT_DEVICE_ERROR(device.CreateExternalTexture(&externalDesc));
    }
}

// Test create external texture with too large apparent size results in error.
TEST_F(ExternalTextureTest, CreateExternalTextureWithApparentSizeTooLarge) {
    wgpu::Limits limits;
    device.GetLimits(&limits);

    // Control case should succeed.
    {
        wgpu::TextureDescriptor textureDescriptor = CreateTextureDescriptor();
        wgpu::Texture texture = device.CreateTexture(&textureDescriptor);

        wgpu::ExternalTextureDescriptor externalDesc = CreateDefaultExternalTextureDescriptor();
        externalDesc.plane0 = texture.CreateView();
        externalDesc.cropOrigin = {0, 0};
        externalDesc.cropSize = {texture.GetWidth(), texture.GetHeight()};
        externalDesc.apparentSize = {limits.maxTextureDimension2D, limits.maxTextureDimension2D};
        device.CreateExternalTexture(&externalDesc);
    }

    // Error: apparentSize.width is too large
    {
        wgpu::TextureDescriptor textureDescriptor = CreateTextureDescriptor();
        wgpu::Texture texture = device.CreateTexture(&textureDescriptor);

        wgpu::ExternalTextureDescriptor externalDesc = CreateDefaultExternalTextureDescriptor();
        externalDesc.plane0 = texture.CreateView();
        externalDesc.cropOrigin = {0, 0};
        externalDesc.cropSize = {texture.GetWidth(), texture.GetHeight()};
        externalDesc.apparentSize = {limits.maxTextureDimension2D + 1,
                                     limits.maxTextureDimension2D};
        ASSERT_DEVICE_ERROR(device.CreateExternalTexture(&externalDesc));
    }

    // Error: apparentSize.height is too large
    {
        wgpu::TextureDescriptor textureDescriptor = CreateTextureDescriptor();
        wgpu::Texture texture = device.CreateTexture(&textureDescriptor);

        wgpu::ExternalTextureDescriptor externalDesc = CreateDefaultExternalTextureDescriptor();
        externalDesc.plane0 = texture.CreateView();
        externalDesc.cropOrigin = {0, 0};
        externalDesc.cropSize = {texture.GetWidth(), texture.GetHeight()};
        externalDesc.apparentSize = {limits.maxTextureDimension2D,
                                     limits.maxTextureDimension2D + 1};
        ASSERT_DEVICE_ERROR(device.CreateExternalTexture(&externalDesc));
    }
}

// Test that submitting an external texture with a plane that is not submittable results in error.
TEST_F(ExternalTextureTest, SubmitExternalTextureWithDestroyedPlane) {
    wgpu::TextureDescriptor textureDescriptor = CreateTextureDescriptor();
    wgpu::Texture texture = device.CreateTexture(&textureDescriptor);

    wgpu::ExternalTextureDescriptor externalDesc = CreateDefaultExternalTextureDescriptor();
    externalDesc.plane0 = texture.CreateView();
    wgpu::ExternalTexture externalTexture = device.CreateExternalTexture(&externalDesc);

    // Create a bind group that contains the external texture.
    wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
        device, {{0, wgpu::ShaderStage::Fragment, &utils::kExternalTextureBindingLayout}});
    wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, bgl, {{0, externalTexture}});

    // Create another texture to use as a color attachment.
    wgpu::TextureDescriptor renderTextureDescriptor = CreateTextureDescriptor();
    wgpu::Texture renderTexture = device.CreateTexture(&renderTextureDescriptor);
    wgpu::TextureView renderView = renderTexture.CreateView();

    utils::ComboRenderPassDescriptor renderPass({renderView}, nullptr);

    // Control case should succeed.
    {
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
        {
            pass.SetBindGroup(0, bindGroup);
            pass.End();
        }

        wgpu::CommandBuffer commands = encoder.Finish();

        queue.Submit(1, &commands);
    }

    // Destroying the plane0 backed texture should result in an error.
    {
        texture.Destroy();
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
        {
            pass.SetBindGroup(0, bindGroup);
            pass.End();
        }

        wgpu::CommandBuffer commands = encoder.Finish();
        ASSERT_DEVICE_ERROR(queue.Submit(1, &commands));
    }
}

// Ensure that bind group validation catches invalid texture views.
TEST_F(ExternalTextureTest, TextureViewValidation) {
    wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
        device, {{0, wgpu::ShaderStage::Fragment, &utils::kExternalTextureBindingLayout}});

    // A texture view from a 2D, single-subresource texture should succeed.
    {
        wgpu::TextureDescriptor textureDescriptor = CreateTextureDescriptor();
        wgpu::Texture texture = device.CreateTexture(&textureDescriptor);

        wgpu::TextureView textureView = texture.CreateView();
        utils::MakeBindGroup(device, bgl, {{0, textureView}});
    }

    // A texture view from a non-2D texture should fail.
    {
        wgpu::TextureDescriptor textureDescriptor = CreateTextureDescriptor();
        textureDescriptor.dimension = wgpu::TextureDimension::e3D;
        textureDescriptor.usage = wgpu::TextureUsage::TextureBinding;
        wgpu::Texture internalTexture = device.CreateTexture(&textureDescriptor);

        wgpu::TextureView textureView = internalTexture.CreateView();
        ASSERT_DEVICE_ERROR(utils::MakeBindGroup(device, bgl, {{0, textureView}}));
    }

    // A texture view from a texture with mip count > 1 should fail.
    {
        wgpu::TextureDescriptor textureDescriptor = CreateTextureDescriptor();
        textureDescriptor.mipLevelCount = 2;
        wgpu::Texture internalTexture = device.CreateTexture(&textureDescriptor);

        wgpu::TextureView textureView = internalTexture.CreateView();
        ASSERT_DEVICE_ERROR(utils::MakeBindGroup(device, bgl, {{0, textureView}}));
    }

    // A texture view from a texture without TextureUsage::TextureBinding should
    // fail.
    {
        wgpu::TextureDescriptor textureDescriptor = CreateTextureDescriptor();
        textureDescriptor.usage = wgpu::TextureUsage::RenderAttachment;
        wgpu::Texture internalTexture = device.CreateTexture(&textureDescriptor);

        wgpu::TextureView textureView = internalTexture.CreateView();
        ASSERT_DEVICE_ERROR(utils::MakeBindGroup(device, bgl, {{0, textureView}}));
    }

    // A texture view from a multisampled texture should fail.
    {
        wgpu::TextureDescriptor textureDescriptor = CreateTextureDescriptor();
        textureDescriptor.sampleCount = 4;
        wgpu::Texture internalTexture = device.CreateTexture(&textureDescriptor);

        wgpu::TextureView textureView = internalTexture.CreateView();
        ASSERT_DEVICE_ERROR(utils::MakeBindGroup(device, bgl, {{0, textureView}}));
    }
}

// Ensure that bind group validation catches invalid texture views for non supported context
// formats.
TEST_F(ExternalTextureTest, TextureViewValidationForNonSupportedContextFormats) {
    wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
        device, {{0, wgpu::ShaderStage::Fragment, &utils::kExternalTextureBindingLayout}});

    for (wgpu::TextureFormat format : utils::kFormatsInCoreSpec) {
        if (!utils::IsRenderableFormat(device, format)) {
            continue;
        }
        wgpu::TextureDescriptor textureDescriptor = CreateTextureDescriptor(format);
        wgpu::Texture texture = device.CreateTexture(&textureDescriptor);
        if (utils::IsSupportedContextFormat(format)) {
            utils::MakeBindGroup(device, bgl, {{0, texture.CreateView()}});
        } else {
            ASSERT_DEVICE_ERROR(utils::MakeBindGroup(device, bgl, {{0, texture.CreateView()}}));
        }
    }
}

// Test that submitting a texture view with a destroyed texture results in error.
TEST_F(ExternalTextureTest, SubmitTextureViewWithDestroyedTexture) {
    wgpu::TextureDescriptor textureDescriptor = CreateTextureDescriptor();
    wgpu::Texture texture = device.CreateTexture(&textureDescriptor);

    wgpu::TextureView textureView = texture.CreateView();

    // Create a bind group that contains the texture view.
    wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
        device, {{0, wgpu::ShaderStage::Fragment, &utils::kExternalTextureBindingLayout}});
    wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, bgl, {{0, textureView}});

    // Create another texture to use as a color attachment.
    wgpu::TextureDescriptor renderTextureDescriptor = CreateTextureDescriptor();
    wgpu::Texture renderTexture = device.CreateTexture(&renderTextureDescriptor);
    wgpu::TextureView renderView = renderTexture.CreateView();

    utils::ComboRenderPassDescriptor renderPass({renderView}, nullptr);

    // Control case should succeed.
    {
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
        {
            pass.SetBindGroup(0, bindGroup);
            pass.End();
        }

        wgpu::CommandBuffer commands = encoder.Finish();

        queue.Submit(1, &commands);
    }

    // Destroying the texture should result in an error.
    {
        texture.Destroy();
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
        {
            pass.SetBindGroup(0, bindGroup);
            pass.End();
        }

        wgpu::CommandBuffer commands = encoder.Finish();
        ASSERT_DEVICE_ERROR(queue.Submit(1, &commands));
    }
}

class ExternalTextureNorm16Test : public ExternalTextureTest {
  protected:
    std::vector<wgpu::FeatureName> GetRequiredFeatures() override {
        return {wgpu::FeatureName::Unorm16TextureFormats};
    }

    static constexpr wgpu::TextureFormat kBiplanarPlane0FormatNorm16 =
        wgpu::TextureFormat::R16Unorm;
    static constexpr wgpu::TextureFormat kBiplanarPlane1FormatNorm16 =
        wgpu::TextureFormat::RG16Unorm;
};

// Test that norm16 external texture creation works as expected in multiplane scenarios.
TEST_F(ExternalTextureNorm16Test, CreateMultiplanarExternalTextureValidation) {
    // Creating an external texture from two 2D, single-subresource textures with a biplanar
    // format should succeed.
    {
        wgpu::TextureDescriptor plane0TextureDescriptor =
            CreateTextureDescriptor(kBiplanarPlane0FormatNorm16);
        wgpu::TextureDescriptor plane1TextureDescriptor =
            CreateTextureDescriptor(kBiplanarPlane1FormatNorm16);
        wgpu::Texture texture0 = device.CreateTexture(&plane0TextureDescriptor);
        wgpu::Texture texture1 = device.CreateTexture(&plane1TextureDescriptor);

        wgpu::ExternalTextureDescriptor externalDesc = CreateDefaultExternalTextureDescriptor();
        externalDesc.plane0 = texture0.CreateView();
        externalDesc.plane1 = texture1.CreateView();

        device.CreateExternalTexture(&externalDesc);
    }
}

class ExternalTextureUnorm16Test : public ExternalTextureTest {
  protected:
    std::vector<wgpu::FeatureName> GetRequiredFeatures() override {
        return {wgpu::FeatureName::Unorm16FormatsForExternalTexture,
                wgpu::FeatureName::MultiPlanarFormatP210,
                wgpu::FeatureName::MultiPlanarFormatExtendedUsages};
    }
};

// Test that with Unorm16FormatsForExternalTexture it is possible to create an ExternalTexture from
// a YUV HDR biplanar texture that has plane formats that are R/RG16Unorm. Normally this should not
// be allowed because the formats are not filterable-float, but the Unorm16FormatsForExternalTexture
// feature bypasses this check.
TEST_F(ExternalTextureUnorm16Test, CreateMultiPlanarTextureFromHDRYUV) {
    // Create the YUV HDR texture. Direct creation of the texture requires
    // MultiPlanarFormatExtendedUsages and the use of the YUV HDR format requires
    // MultiPlanarFormatP210.
    wgpu::TextureDescriptor yuvDesc = {
        .usage = wgpu::TextureUsage::TextureBinding,
        .size = {kWidth, kHeight},
        .format = wgpu::TextureFormat::R10X6BG10X6Biplanar422Unorm,
    };
    wgpu::Texture yuv = device.CreateTexture(&yuvDesc);

    // Create plane views with formats that are not normally allowed, but can be used only when
    // retrieving the planes.
    wgpu::TextureViewDescriptor plane0Desc = {
        .format = wgpu::TextureFormat::R16Unorm,
        .aspect = wgpu::TextureAspect::Plane0Only,
    };
    wgpu::TextureView plane0 = yuv.CreateView(&plane0Desc);

    wgpu::TextureViewDescriptor plane1Desc = {
        .format = wgpu::TextureFormat::RG16Unorm,
        .aspect = wgpu::TextureAspect::Plane1Only,
    };
    wgpu::TextureView plane1 = yuv.CreateView(&plane1Desc);

    // The ExternalTexture creation validation will allow these unfilterable-float formats to be
    // used.
    wgpu::ExternalTextureDescriptor externalDesc = CreateDefaultExternalTextureDescriptor();
    externalDesc.plane0 = plane0;
    externalDesc.plane1 = plane1;
    device.CreateExternalTexture(&externalDesc);
}

class ExternalTextureUnorm16Test_FeatureDisabled : public ExternalTextureTest {
  protected:
    std::vector<wgpu::FeatureName> GetRequiredFeatures() override {
        return {wgpu::FeatureName::MultiPlanarFormatP210,
                wgpu::FeatureName::MultiPlanarFormatExtendedUsages};
    }
};

// Check that without Unorm16FormatsForExternalTexture it's not possible to create the views from an
// HDR YUV texture.
TEST_F(ExternalTextureUnorm16Test_FeatureDisabled, CannotCreateViewsFromHDRYUV) {
    // Create the YUV HDR texture. Direct creation of the texture requires
    // MultiPlanarFormatExtendedUsages and the use of the YUV HDR format requires
    // MultiPlanarFormatP210.
    wgpu::TextureDescriptor yuvDesc = {
        .usage = wgpu::TextureUsage::TextureBinding,
        .size = {kWidth, kHeight},
        .format = wgpu::TextureFormat::R10X6BG10X6Biplanar422Unorm,
    };
    wgpu::Texture yuv = device.CreateTexture(&yuvDesc);

    // Create plane views with formats that are not normally allowed, but can be used only when
    // retrieving the planes.
    wgpu::TextureViewDescriptor plane0Desc = {
        .format = wgpu::TextureFormat::R16Unorm,
        .aspect = wgpu::TextureAspect::Plane0Only,
    };
    ASSERT_DEVICE_ERROR(yuv.CreateView(&plane0Desc));

    wgpu::TextureViewDescriptor plane1Desc = {
        .format = wgpu::TextureFormat::RG16Unorm,
        .aspect = wgpu::TextureAspect::Plane1Only,
    };
    ASSERT_DEVICE_ERROR(yuv.CreateView(&plane1Desc));
}

}  // anonymous namespace
}  // namespace dawn
