Relax ExternalTexture format validation

This allows planes to be any 1/2 component filterable formats. This
also addes some test cases for Norm16 external textures.

Bug: chromium:1471362
Change-Id: I261b0867c84b0ef4ccee2284c471a55d7b62ea09
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/147861
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Jie A Chen <jie.a.chen@intel.com>
Reviewed-by: Austin Eng <enga@chromium.org>
Reviewed-by: Peng Huang <penghuang@chromium.org>
diff --git a/src/dawn/native/ExternalTexture.cpp b/src/dawn/native/ExternalTexture.cpp
index 8b082fc..c20ea90 100644
--- a/src/dawn/native/ExternalTexture.cpp
+++ b/src/dawn/native/ExternalTexture.cpp
@@ -55,8 +55,6 @@
 
     DAWN_TRY(device->ValidateObject(descriptor->plane0));
 
-    wgpu::TextureFormat plane0Format = descriptor->plane0->GetFormat().format;
-
     DAWN_INVALID_IF(!descriptor->gamutConversionMatrix,
                     "The gamut conversion matrix must be non-null.");
 
@@ -66,37 +64,39 @@
     DAWN_INVALID_IF(!descriptor->dstTransferFunctionParameters,
                     "The destination transfer function parameters must be non-null.");
 
+    DAWN_TRY(ValidateExternalTexturePlane(descriptor->plane0));
+
+    auto CheckPlaneFormat = [](const DeviceBase* device, const Format& format,
+                               uint32_t requiredComponentCount) -> MaybeError {
+        DAWN_INVALID_IF(format.aspects != Aspect::Color, "The format (%s) is not a color format.",
+                        format.format);
+        DAWN_INVALID_IF(!IsSubset(SampleTypeBit::Float,
+                                  format.GetAspectInfo(Aspect::Color).supportedSampleTypes),
+                        "The format (%s) is not filterable float.", format.format);
+        DAWN_INVALID_IF(format.componentCount != requiredComponentCount,
+                        "The format (%s) component count (%u) is not %u.", format.format,
+                        requiredComponentCount, format.componentCount);
+        return {};
+    };
+
     if (descriptor->plane1) {
         DAWN_INVALID_IF(
             !descriptor->yuvToRgbConversionMatrix,
             "When more than one plane is set, the YUV-to-RGB conversion matrix must be non-null.");
 
         DAWN_TRY(device->ValidateObject(descriptor->plane1));
-        wgpu::TextureFormat plane1Format = descriptor->plane1->GetFormat().format;
+        DAWN_TRY(ValidateExternalTexturePlane(descriptor->plane1));
 
-        DAWN_INVALID_IF(plane0Format != wgpu::TextureFormat::R8Unorm,
-                        "The bi-planar external texture plane (%s) format (%s) is not %s.",
-                        descriptor->plane0, plane0Format, wgpu::TextureFormat::R8Unorm);
-        DAWN_INVALID_IF(plane1Format != wgpu::TextureFormat::RG8Unorm,
-                        "The bi-planar external texture plane (%s) format (%s) is not %s.",
-                        descriptor->plane1, plane1Format, wgpu::TextureFormat::RG8Unorm);
-
-        DAWN_TRY(ValidateExternalTexturePlane(descriptor->plane0));
+        // Y + UV case.
+        DAWN_TRY_CONTEXT(CheckPlaneFormat(device, descriptor->plane0->GetFormat(), 1),
+                         "validating the format of plane 0 (%s)", descriptor->plane0);
+        DAWN_TRY_CONTEXT(CheckPlaneFormat(device, descriptor->plane1->GetFormat(), 2),
+                         "validating the format of plane 1 (%s)", descriptor->plane1);
         DAWN_TRY(ValidateExternalTexturePlane(descriptor->plane1));
     } else {
-        switch (plane0Format) {
-            case wgpu::TextureFormat::RGBA8Unorm:
-            case wgpu::TextureFormat::BGRA8Unorm:
-            case wgpu::TextureFormat::RGBA16Float:
-                DAWN_TRY(ValidateExternalTexturePlane(descriptor->plane0));
-                break;
-            default:
-                return DAWN_VALIDATION_ERROR(
-                    "The external texture plane (%s) format (%s) is not a supported format "
-                    "(%s, %s, %s).",
-                    descriptor->plane0, plane0Format, wgpu::TextureFormat::RGBA8Unorm,
-                    wgpu::TextureFormat::BGRA8Unorm, wgpu::TextureFormat::RGBA16Float);
-        }
+        // RGBA case.
+        DAWN_TRY_CONTEXT(CheckPlaneFormat(device, descriptor->plane0->GetFormat(), 4),
+                         "validating the format of plane 0 (%s)", descriptor->plane0);
     }
 
     DAWN_INVALID_IF(descriptor->visibleSize.width == 0 || descriptor->visibleSize.height == 0,
diff --git a/src/dawn/tests/end2end/ExternalTextureTests.cpp b/src/dawn/tests/end2end/ExternalTextureTests.cpp
index 46690c9..06cb928 100644
--- a/src/dawn/tests/end2end/ExternalTextureTests.cpp
+++ b/src/dawn/tests/end2end/ExternalTextureTests.cpp
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <vector>
+
 #include "dawn/tests/DawnTest.h"
 #include "dawn/utils/ComboRenderPipelineDescriptor.h"
 #include "dawn/utils/WGPUHelpers.h"
@@ -98,6 +100,17 @@
         }
     }
 
+    std::vector<wgpu::FeatureName> GetRequiredFeatures() override {
+        std::vector<wgpu::FeatureName> requiredFeatures = {};
+        if (SupportsFeatures({wgpu::FeatureName::Norm16TextureFormats})) {
+            mIsNorm16TextureFormatsSupported = true;
+            requiredFeatures.push_back(wgpu::FeatureName::Norm16TextureFormats);
+        }
+        return requiredFeatures;
+    }
+
+    bool IsNorm16TextureFormatsSupported() { return mIsNorm16TextureFormatsSupported; }
+
     static constexpr uint32_t kWidth = 4;
     static constexpr uint32_t kHeight = 4;
     static constexpr wgpu::TextureFormat kFormat = wgpu::TextureFormat::RGBA8Unorm;
@@ -107,6 +120,8 @@
 
     wgpu::ShaderModule vsModule;
     wgpu::ShaderModule fsSampleExternalTextureModule;
+
+    bool mIsNorm16TextureFormatsSupported = false;
 };
 
 TEST_P(ExternalTextureTests, CreateExternalTextureSuccess) {
@@ -291,6 +306,103 @@
     }
 }
 
+TEST_P(ExternalTextureTests, SampleMultiplanarExternalTextureNorm16) {
+    DAWN_TEST_UNSUPPORTED_IF(!IsNorm16TextureFormatsSupported());
+
+    // TODO(crbug.com/tint/1774): Tint has an issue compiling shaders that use external textures on
+    // OpenGL/OpenGLES.
+    DAWN_SUPPRESS_TEST_IF(IsOpenGL() || IsOpenGLES());
+
+    wgpu::Texture sampledTexturePlane0 =
+        Create2DTexture(device, kWidth, kHeight, wgpu::TextureFormat::R16Unorm,
+                        wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment);
+    wgpu::Texture sampledTexturePlane1 =
+        Create2DTexture(device, kWidth, kHeight, wgpu::TextureFormat::RG16Unorm,
+                        wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment);
+
+    wgpu::Texture renderTexture =
+        Create2DTexture(device, kWidth, kHeight, kFormat,
+                        wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment);
+
+    // Create a texture view for the external texture
+    wgpu::TextureView externalViewPlane0 = sampledTexturePlane0.CreateView();
+    wgpu::TextureView externalViewPlane1 = sampledTexturePlane1.CreateView();
+
+    struct ConversionExpectation {
+        double y;
+        double u;
+        double v;
+        utils::RGBA8 rgba;
+    };
+
+    // Conversion expectations for BT.709 YUV source and sRGB destination.
+    std::array<ConversionExpectation, 7> expectations = {
+        {{0.0, .5, .5, utils::RGBA8::kBlack},
+         {0.2126, 0.4172, 1.0, utils::RGBA8::kRed},
+         {0.7152, 0.1402, 0.0175, utils::RGBA8::kGreen},
+         {0.0722, 1.0, 0.4937, utils::RGBA8::kBlue},
+         {0.6382, 0.3232, 0.6644, {246, 169, 90, 255}},
+         {0.5423, 0.5323, 0.4222, {120, 162, 169, 255}},
+         {0.2345, 0.4383, 0.6342, {125, 53, 32, 255}}}};
+
+    for (const ConversionExpectation& expectation : expectations) {
+        // Initialize the texture planes with YUV data
+        {
+            utils::ComboRenderPassDescriptor renderPass({externalViewPlane0, externalViewPlane1},
+                                                        nullptr);
+            renderPass.cColorAttachments[0].clearValue = {expectation.y, 0.0f, 0.0f, 0.0f};
+            renderPass.cColorAttachments[1].clearValue = {expectation.u, expectation.v, 0.0f, 0.0f};
+            wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+            wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
+            pass.End();
+
+            wgpu::CommandBuffer commands = encoder.Finish();
+            queue.Submit(1, &commands);
+        }
+
+        // Pipeline Creation
+        utils::ComboRenderPipelineDescriptor descriptor;
+        descriptor.vertex.module = vsModule;
+        descriptor.cFragment.module = fsSampleExternalTextureModule;
+        descriptor.cTargets[0].format = kFormat;
+        wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor);
+
+        // Create an ExternalTextureDescriptor from the texture views
+        wgpu::ExternalTextureDescriptor externalDesc = CreateDefaultExternalTextureDescriptor();
+        externalDesc.plane0 = externalViewPlane0;
+        externalDesc.plane1 = externalViewPlane1;
+        externalDesc.visibleOrigin = {0, 0};
+        externalDesc.visibleSize = {kWidth, kHeight};
+
+        // Import the external texture
+        wgpu::ExternalTexture externalTexture = device.CreateExternalTexture(&externalDesc);
+
+        // Create a sampler and bind group
+        wgpu::Sampler sampler = device.CreateSampler();
+
+        wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0),
+                                                         {{0, sampler}, {1, externalTexture}});
+
+        // Run the shader, which should sample from the external texture and draw a triangle into
+        // the upper left corner of the render texture.
+        wgpu::TextureView renderView = renderTexture.CreateView();
+        utils::ComboRenderPassDescriptor renderPass({renderView}, nullptr);
+        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
+        {
+            pass.SetPipeline(pipeline);
+            pass.SetBindGroup(0, bindGroup);
+            pass.Draw(3);
+            pass.End();
+        }
+
+        wgpu::CommandBuffer commands = encoder.Finish();
+        queue.Submit(1, &commands);
+
+        EXPECT_PIXEL_RGBA8_EQ(expectation.rgba, renderTexture, 0, 0);
+    }
+}
+
 // Test draws a green square in the upper left quadrant, a black square in the upper right, a red
 // square in the lower left and a blue square in the lower right. The image is then sampled as an
 // external texture and rotated 0, 90, 180, and 270 degrees with and without the y-axis flipped.
diff --git a/src/dawn/tests/unittests/validation/BindGroupValidationTests.cpp b/src/dawn/tests/unittests/validation/BindGroupValidationTests.cpp
index cdc5680..a786922 100644
--- a/src/dawn/tests/unittests/validation/BindGroupValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/BindGroupValidationTests.cpp
@@ -407,15 +407,15 @@
 
     // Setting the external texture to an error external texture is an error.
     {
-        wgpu::Texture errorTexture = CreateTexture(wgpu::TextureUsage::TextureBinding,
-                                                   wgpu::TextureFormat::RGBA8UnormSrgb, 1);
-        wgpu::ExternalTextureDescriptor errorExternalDesciptor =
+        wgpu::Texture errorTexture =
+            CreateTexture(wgpu::TextureUsage::TextureBinding, wgpu::TextureFormat::R8Unorm, 1);
+        wgpu::ExternalTextureDescriptor errorExternalDescriptor =
             CreateDefaultExternalTextureDescriptor();
-        errorExternalDesciptor.plane0 = errorTexture.CreateView();
+        errorExternalDescriptor.plane0 = errorTexture.CreateView();
 
         wgpu::ExternalTexture errorExternalTexture;
         ASSERT_DEVICE_ERROR(errorExternalTexture =
-                                device.CreateExternalTexture(&errorExternalDesciptor));
+                                device.CreateExternalTexture(&errorExternalDescriptor));
 
         wgpu::ExternalTextureBindingEntry errorExternalBindingEntry;
         errorExternalBindingEntry.externalTexture = errorExternalTexture;
diff --git a/src/dawn/tests/unittests/validation/ExternalTextureTests.cpp b/src/dawn/tests/unittests/validation/ExternalTextureTests.cpp
index a6dc44d..9e8d02d 100644
--- a/src/dawn/tests/unittests/validation/ExternalTextureTests.cpp
+++ b/src/dawn/tests/unittests/validation/ExternalTextureTests.cpp
@@ -175,15 +175,32 @@
         ASSERT_DEVICE_ERROR(device.CreateExternalTexture(&externalDesc));
     }
 
-    // Creating an external texture with an unsupported format should fail.
+    // Creating an external texture with a non 4-component format should fail.
     {
-        wgpu::TextureDescriptor textureDescriptor = CreateTextureDescriptor();
-        textureDescriptor.format = wgpu::TextureFormat::R8Uint;
-        wgpu::Texture internalTexture = device.CreateTexture(&textureDescriptor);
+        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));
+            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.
@@ -298,38 +315,125 @@
         device.CreateExternalTexture(&externalDesc);
     }
 
-    // Creating a multiplanar external texture with an unsupported format for plane0 should
-    // result in an error.
+    // Creating a multiplanar external texture with an 1-component float-filterable format for
+    // plane0 should succeed.
     {
-        wgpu::TextureDescriptor plane0TextureDescriptor =
-            CreateTextureDescriptor(kDefaultTextureFormat);
-        wgpu::TextureDescriptor plane1TextureDescriptor =
-            CreateTextureDescriptor(kBiplanarPlane1Format);
-        wgpu::Texture texture0 = device.CreateTexture(&plane0TextureDescriptor);
-        wgpu::Texture texture1 = device.CreateTexture(&plane1TextureDescriptor);
+        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();
+            wgpu::ExternalTextureDescriptor externalDesc = CreateDefaultExternalTextureDescriptor();
+            externalDesc.plane0 = texture0.CreateView();
+            externalDesc.plane1 = texture1.CreateView();
 
-        ASSERT_DEVICE_ERROR(device.CreateExternalTexture(&externalDesc));
+            device.CreateExternalTexture(&externalDesc);
+        }
     }
 
-    // Creating a multiplanar external texture with an unsupported format for plane1 should
-    // result in an error.
+    // Creating a multiplanar external texture with a 2-component float-filterable format for
+    // plane1 should succeed.
     {
-        wgpu::TextureDescriptor plane0TextureDescriptor =
-            CreateTextureDescriptor(kBiplanarPlane0Format);
-        wgpu::TextureDescriptor plane1TextureDescriptor =
-            CreateTextureDescriptor(kDefaultTextureFormat);
-        wgpu::Texture texture0 = device.CreateTexture(&plane0TextureDescriptor);
-        wgpu::Texture texture1 = device.CreateTexture(&plane1TextureDescriptor);
+        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();
+            wgpu::ExternalTextureDescriptor externalDesc = CreateDefaultExternalTextureDescriptor();
+            externalDesc.plane0 = texture0.CreateView();
+            externalDesc.plane1 = texture1.CreateView();
 
-        ASSERT_DEVICE_ERROR(device.CreateExternalTexture(&externalDesc));
+            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));
+        }
     }
 }
 
@@ -643,5 +747,43 @@
     }
 }
 
+class ExternalTextureNorm16Test : public ExternalTextureTest {
+  protected:
+    void SetUp() override { ExternalTextureTest::SetUp(); }
+
+    WGPUDevice CreateTestDevice(native::Adapter dawnAdapter,
+                                wgpu::DeviceDescriptor descriptor) override {
+        wgpu::FeatureName requiredFeatures[1] = {wgpu::FeatureName::Norm16TextureFormats};
+        descriptor.requiredFeatures = requiredFeatures;
+        descriptor.requiredFeatureCount = 1;
+        return dawnAdapter.CreateDevice(&descriptor);
+    }
+
+    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);
+    }
+}
+
 }  // anonymous namespace
 }  // namespace dawn