[YCbCr Samplers] Add wgpu::TextureFormat::External

Add wgpu::TextureFormat::External to be used with creating YCbCr
samplers. Also add useExternalFormat bool for picking externalFormat
vs vkFormat when creating vkImage. Add necessary tests and validations.

Change-Id: I2f82d591fccf9786d11d554a0a23cec070653565
Bug: dawn:2476
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/187361
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Saifuddin Hitawala <hitawala@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
diff --git a/src/dawn/dawn.json b/src/dawn/dawn.json
index 551eac1..f1cf9a6 100644
--- a/src/dawn/dawn.json
+++ b/src/dawn/dawn.json
@@ -2089,7 +2089,8 @@
         "chain roots": ["shared texture memory descriptor"],
         "tags": ["dawn", "native"],
         "members": [
-            {"name": "handle", "type": "void *"}
+            {"name": "handle", "type": "void *"},
+            {"name": "use external format", "type" : "bool"}
         ]
     },
     "shared texture memory dma buf plane": {
@@ -4216,7 +4217,8 @@
             {"value": 105, "name": "R8 BG8 Biplanar 422 unorm", "tags": ["dawn"]},
             {"value": 106, "name": "R8 BG8 Biplanar 444 unorm", "tags": ["dawn"]},
             {"value": 107, "name": "R10X6 BG10X6 Biplanar 422 unorm", "tags": ["dawn"]},
-            {"value": 108, "name": "R10X6 BG10X6 Biplanar 444 unorm", "tags": ["dawn"]}
+            {"value": 108, "name": "R10X6 BG10X6 Biplanar 444 unorm", "tags": ["dawn"]},
+            {"value": 109, "name": "External", "tags": ["dawn"]}
         ]
     },
     "texture usage": {
diff --git a/src/dawn/native/Format.cpp b/src/dawn/native/Format.cpp
index 75c54d9..029f474 100644
--- a/src/dawn/native/Format.cpp
+++ b/src/dawn/native/Format.cpp
@@ -462,6 +462,9 @@
     AddColorFormat(wgpu::TextureFormat::RGBA8Uint, Cap::Renderable | Cap::StorageROrW | Cap::Multisample, ByteSize(4), SampleTypeBit::Uint, ComponentCount(4), RenderTargetPixelByteCost(4), RenderTargetComponentAlignment(1));
     AddColorFormat(wgpu::TextureFormat::RGBA8Sint, Cap::Renderable | Cap::StorageROrW | Cap::Multisample, ByteSize(4), SampleTypeBit::Sint, ComponentCount(4), RenderTargetPixelByteCost(4), RenderTargetComponentAlignment(1));
 
+    const UnsupportedReason externalUnsupportedReason = device->HasFeature(Feature::YCbCrVulkanSamplers) ?  Format::supported : RequiresFeature{wgpu::FeatureName::YCbCrVulkanSamplers};
+    AddConditionalColorFormat(wgpu::TextureFormat::External, externalUnsupportedReason, Cap::None, ByteSize(1), SampleTypeBit::External, ComponentCount(0));
+
     auto BGRA8UnormSupportsStorageUsage = device->HasFeature(Feature::BGRA8UnormStorage) ? Cap::StorageROrW : Cap::None;
     AddColorFormat(wgpu::TextureFormat::BGRA8Unorm, Cap::Renderable | BGRA8UnormSupportsStorageUsage | Cap::Multisample | Cap::Resolve, ByteSize(4), kAnyFloat, ComponentCount(4), RenderTargetPixelByteCost(8), RenderTargetComponentAlignment(1));
     AddConditionalColorFormat(wgpu::TextureFormat::BGRA8UnormSrgb, device->IsCompatibilityMode() ? UnsupportedReason(CompatibilityMode{}) : Format::supported, Cap::Renderable |  Cap::Multisample | Cap::Resolve, ByteSize(4), kAnyFloat, ComponentCount(4), RenderTargetPixelByteCost(8), RenderTargetComponentAlignment(1), wgpu::TextureFormat::BGRA8Unorm);
diff --git a/src/dawn/native/Format.h b/src/dawn/native/Format.h
index ca94056..3efb5b4 100644
--- a/src/dawn/native/Format.h
+++ b/src/dawn/native/Format.h
@@ -67,6 +67,7 @@
 // NOTE: SampleTypeBit::External does not have an equivalent TextureSampleType. All future
 // additions to SampleTypeBit that have an equivalent TextureSampleType should use
 // SampleTypeBit::External's value and update SampleTypeBit::External to a higher value.
+// TODO(crbug.com/dawn/2476): Validate SampleTypeBit::External is compatible with Sampler.
 enum class SampleTypeBit : uint8_t {
     None = 0x0,
     Float = 0x1,
@@ -117,7 +118,7 @@
 
 // The number of formats Dawn knows about. Asserts in BuildFormatTable ensure that this is the
 // exact number of known format.
-static constexpr uint32_t kKnownFormatCount = 108;
+static constexpr uint32_t kKnownFormatCount = 109;
 
 using FormatIndex = TypedInteger<struct FormatIndexT, uint32_t>;
 
diff --git a/src/dawn/native/SharedTextureMemory.cpp b/src/dawn/native/SharedTextureMemory.cpp
index 041b3e5..eafb189 100644
--- a/src/dawn/native/SharedTextureMemory.cpp
+++ b/src/dawn/native/SharedTextureMemory.cpp
@@ -87,16 +87,19 @@
     : SharedResourceMemory(device, label), mProperties(properties) {
     // Reify properties to ensure we don't expose capabilities not supported by the device.
     const Format& internalFormat = device->GetValidInternalFormat(mProperties.format);
-    if (!internalFormat.supportsStorageUsage || internalFormat.IsMultiPlanar()) {
-        mProperties.usage = mProperties.usage & ~wgpu::TextureUsage::StorageBinding;
-    }
-    if (!internalFormat.isRenderable || (internalFormat.IsMultiPlanar() &&
-                                         !device->HasFeature(Feature::MultiPlanarRenderTargets))) {
-        mProperties.usage = mProperties.usage & ~wgpu::TextureUsage::RenderAttachment;
-    }
-    if (internalFormat.IsMultiPlanar() &&
-        !device->HasFeature(Feature::MultiPlanarFormatExtendedUsages)) {
-        mProperties.usage = mProperties.usage & ~wgpu::TextureUsage::CopyDst;
+    if (internalFormat.format != wgpu::TextureFormat::External) {
+        if (!internalFormat.supportsStorageUsage || internalFormat.IsMultiPlanar()) {
+            mProperties.usage = mProperties.usage & ~wgpu::TextureUsage::StorageBinding;
+        }
+        if (!internalFormat.isRenderable ||
+            (internalFormat.IsMultiPlanar() &&
+             !device->HasFeature(Feature::MultiPlanarRenderTargets))) {
+            mProperties.usage = mProperties.usage & ~wgpu::TextureUsage::RenderAttachment;
+        }
+        if (internalFormat.IsMultiPlanar() &&
+            !device->HasFeature(Feature::MultiPlanarFormatExtendedUsages)) {
+            mProperties.usage = mProperties.usage & ~wgpu::TextureUsage::CopyDst;
+        }
     }
 
     GetObjectTrackingList()->Track(this);
diff --git a/src/dawn/native/Subresource.cpp b/src/dawn/native/Subresource.cpp
index 2429029..974cde3 100644
--- a/src/dawn/native/Subresource.cpp
+++ b/src/dawn/native/Subresource.cpp
@@ -82,8 +82,6 @@
 }
 
 Aspect SelectFormatAspects(const Format& format, wgpu::TextureAspect aspect) {
-    // TODO(crbug.com/dawn/2476): Return Aspect::Color for TextureFormat::External if aspect is
-    // present else None.
     switch (aspect) {
         case wgpu::TextureAspect::All:
             return format.aspects;
diff --git a/src/dawn/native/Texture.cpp b/src/dawn/native/Texture.cpp
index 405da76..a467029 100644
--- a/src/dawn/native/Texture.cpp
+++ b/src/dawn/native/Texture.cpp
@@ -604,7 +604,6 @@
     DAWN_ASSERT(!texture->IsError());
 
     DAWN_TRY(ValidateTextureViewDimension(descriptor->dimension));
-    // TODO(crbug.com/dawn/2476): Add necessary validation for TextureFormat::External.
     DAWN_TRY(ValidateTextureFormat(descriptor->format));
     DAWN_TRY(ValidateTextureAspect(descriptor->aspect));
 
@@ -635,16 +634,20 @@
         "texture's mip level count (%u).",
         descriptor->baseMipLevel, descriptor->mipLevelCount, texture->GetNumMipLevels());
 
-    // TODO(crbug.com/dawn/2476): Add necessary validations to CanViewTextureAs for
-    // TextureFormat::External.
-    DAWN_TRY(ValidateCanViewTextureAs(device, texture, *viewFormat, descriptor->aspect));
-    DAWN_TRY(ValidateTextureViewDimensionCompatibility(device, texture, descriptor));
-
     if (descriptor.Get<YCbCrVkDescriptor>()) {
         DAWN_INVALID_IF(!device->HasFeature(Feature::YCbCrVulkanSamplers), "%s is not enabled.",
                         wgpu::FeatureName::YCbCrVulkanSamplers);
+        DAWN_INVALID_IF(format.format != wgpu::TextureFormat::External,
+                        "Texture format (%s) is not (%s).", format.format,
+                        wgpu::TextureFormat::External);
+    } else if (format.format == wgpu::TextureFormat::External) {
+        return DAWN_VALIDATION_ERROR("Invalid TextureViewDescriptor with Texture format (%s).",
+                                     wgpu::TextureFormat::External);
     }
 
+    DAWN_TRY(ValidateCanViewTextureAs(device, texture, *viewFormat, descriptor->aspect));
+    DAWN_TRY(ValidateTextureViewDimensionCompatibility(device, texture, descriptor));
+
     return {};
 }
 
@@ -683,7 +686,6 @@
         }
     }
 
-    // TODO(crbug.com/dawn/2476): Add TextureFormat::External validation.
     if (desc.format == wgpu::TextureFormat::Undefined) {
         const Format& format = texture->GetFormat();
 
diff --git a/src/dawn/native/d3d/UtilsD3D.cpp b/src/dawn/native/d3d/UtilsD3D.cpp
index 52a5c95..9d0123a 100644
--- a/src/dawn/native/d3d/UtilsD3D.cpp
+++ b/src/dawn/native/d3d/UtilsD3D.cpp
@@ -255,6 +255,7 @@
         case wgpu::TextureFormat::R10X6BG10X6Biplanar420Unorm:
         case wgpu::TextureFormat::R10X6BG10X6Biplanar422Unorm:
         case wgpu::TextureFormat::R10X6BG10X6Biplanar444Unorm:
+        case wgpu::TextureFormat::External:
         case wgpu::TextureFormat::Undefined:
             DAWN_UNREACHABLE();
     }
@@ -405,6 +406,7 @@
         case wgpu::TextureFormat::R8BG8A8Triplanar420Unorm:
         case wgpu::TextureFormat::R10X6BG10X6Biplanar422Unorm:
         case wgpu::TextureFormat::R10X6BG10X6Biplanar444Unorm:
+        case wgpu::TextureFormat::External:
 
         case wgpu::TextureFormat::Undefined:
             DAWN_UNREACHABLE();
diff --git a/src/dawn/native/metal/UtilsMetal.mm b/src/dawn/native/metal/UtilsMetal.mm
index abefea9..69d6de2 100644
--- a/src/dawn/native/metal/UtilsMetal.mm
+++ b/src/dawn/native/metal/UtilsMetal.mm
@@ -550,6 +550,7 @@
         case wgpu::TextureFormat::R10X6BG10X6Biplanar422Unorm:
         case wgpu::TextureFormat::R10X6BG10X6Biplanar444Unorm:
         case wgpu::TextureFormat::R8BG8A8Triplanar420Unorm:
+        case wgpu::TextureFormat::External:
         case wgpu::TextureFormat::Undefined:
             DAWN_UNREACHABLE();
     }
diff --git a/src/dawn/native/vulkan/SharedTextureMemoryVk.cpp b/src/dawn/native/vulkan/SharedTextureMemoryVk.cpp
index 66b8ef8..6560fad 100644
--- a/src/dawn/native/vulkan/SharedTextureMemoryVk.cpp
+++ b/src/dawn/native/vulkan/SharedTextureMemoryVk.cpp
@@ -492,6 +492,8 @@
 
     auto* aHardwareBuffer = static_cast<struct AHardwareBuffer*>(descriptor->handle);
 
+    bool useExternalFormat = descriptor->useExternalFormat;
+
     const VkExternalMemoryHandleTypeFlagBits handleType =
         VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID;
 
@@ -502,12 +504,19 @@
     SharedTextureMemoryProperties properties;
     properties.size = {aHardwareBufferDesc.width, aHardwareBufferDesc.height,
                        aHardwareBufferDesc.layers};
-    properties.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst;
-    if (aHardwareBufferDesc.usage & AHARDWAREBUFFER_USAGE_GPU_FRAMEBUFFER) {
-        properties.usage |= wgpu::TextureUsage::RenderAttachment;
-    }
-    if (aHardwareBufferDesc.usage & AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE) {
-        properties.usage |= wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::StorageBinding;
+    if (useExternalFormat) {
+        if (aHardwareBufferDesc.usage & AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE) {
+            properties.usage = wgpu::TextureUsage::TextureBinding;
+        }
+    } else {
+        properties.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst;
+        if (aHardwareBufferDesc.usage & AHARDWAREBUFFER_USAGE_GPU_FRAMEBUFFER) {
+            properties.usage |= wgpu::TextureUsage::RenderAttachment;
+        }
+        if (aHardwareBufferDesc.usage & AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE) {
+            properties.usage |=
+                wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::StorageBinding;
+        }
     }
 
     VkFormat vkFormat;
@@ -515,6 +524,9 @@
     VkAndroidHardwareBufferPropertiesANDROID bufferProperties = {
         .sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID,
     };
+    VkExternalFormatANDROID externalFormatAndroid = {
+        .sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID,
+    };
 
     // Query the properties to find the appropriate VkFormat and memory type.
     {
@@ -528,11 +540,22 @@
                                     vkDevice, aHardwareBuffer, &bufferProperties),
                                 "vkGetAndroidHardwareBufferPropertiesANDROID"));
 
-        vkFormat = bufferFormatProperties.format;
+        // TODO(crbug.com/dawn/2476): Validate more as per
+        // https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImageCreateInfo.html
+        if (useExternalFormat) {
+            DAWN_ASSERT(bufferFormatProperties.externalFormat != 0);
+            vkFormat = VK_FORMAT_UNDEFINED;
+            externalFormatAndroid.externalFormat = bufferFormatProperties.externalFormat;
+            properties.format = wgpu::TextureFormat::External;
+        } else {
+            vkFormat = bufferFormatProperties.format;
+            externalFormatAndroid.externalFormat = 0;
+            DAWN_TRY_ASSIGN(properties.format, FormatFromVkFormat(device, vkFormat));
+        }
 
         // Populate the YCbCr info.
-        yCbCrAHBInfo.externalFormat = bufferFormatProperties.externalFormat;
-        yCbCrAHBInfo.vkFormat = bufferFormatProperties.format;
+        yCbCrAHBInfo.externalFormat = externalFormatAndroid.externalFormat;
+        yCbCrAHBInfo.vkFormat = vkFormat;
         yCbCrAHBInfo.vkYCbCrModel = bufferFormatProperties.suggestedYcbcrModel;
         yCbCrAHBInfo.vkYCbCrRange = bufferFormatProperties.suggestedYcbcrRange;
         yCbCrAHBInfo.vkComponentSwizzleRed =
@@ -555,7 +578,6 @@
             formatFeatures &
             VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_CHROMA_RECONSTRUCTION_EXPLICIT_BIT;
     }
-    DAWN_TRY_ASSIGN(properties.format, FormatFromVkFormat(device, vkFormat));
 
     const Format* internalFormat = nullptr;
     DAWN_TRY_ASSIGN(internalFormat, device->GetInternalFormat(properties.format));
@@ -596,53 +618,57 @@
         imageFormatInfo.usage = vkUsageFlags;
         imageFormatInfo.flags = 0;
 
-        constexpr wgpu::TextureUsage kUsageRequiringView = wgpu::TextureUsage::RenderAttachment |
-                                                           wgpu::TextureUsage::TextureBinding |
-                                                           wgpu::TextureUsage::StorageBinding;
-        const bool mayNeedViewReinterpretation =
-            (properties.usage & kUsageRequiringView) != 0 && !compatibleViewFormats.empty();
-        const bool needsBGRA8UnormStoragePolyfill =
-            properties.format == wgpu::TextureFormat::BGRA8Unorm &&
-            (properties.usage & wgpu::TextureUsage::StorageBinding);
-        if (mayNeedViewReinterpretation || needsBGRA8UnormStoragePolyfill) {
-            // Add the mutable format bit for view reinterpretation.
-            imageFormatInfo.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
+        if (!useExternalFormat) {
+            constexpr wgpu::TextureUsage kUsageRequiringView =
+                wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding |
+                wgpu::TextureUsage::StorageBinding;
+            const bool mayNeedViewReinterpretation =
+                (properties.usage & kUsageRequiringView) != 0 && !compatibleViewFormats.empty();
+            const bool needsBGRA8UnormStoragePolyfill =
+                properties.format == wgpu::TextureFormat::BGRA8Unorm &&
+                (properties.usage & wgpu::TextureUsage::StorageBinding);
+            if (mayNeedViewReinterpretation || needsBGRA8UnormStoragePolyfill) {
+                // Add the mutable format bit for view reinterpretation.
+                imageFormatInfo.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
 
-            if (properties.usage & wgpu::TextureUsage::StorageBinding) {
-                // Don't use an image format list because it becomes impossible to make an
-                // rgba8unorm storage texture which may be reinterpreted as rgba8unorm-srgb,
-                // because the srgb format doesn't support storage. Creation with an explicit
-                // format list that includes srgb will fail.
-                // This is the same issue seen with the DMA buf import path which has a workaround
-                // to bypass the support check.
-                // TODO(crbug.com/dawn/2304): If the dma buf import is resolved in a better way,
-                // apply the same fix here.
-            } else if (device->GetDeviceInfo().HasExt(DeviceExt::ImageFormatList)) {
-                // Set the list of view formats the image can be compatible with.
-                DAWN_ASSERT(compatibleViewFormats.size() == 1u);
-                viewFormats[0] = vkFormat;
-                viewFormats[1] = VulkanImageFormat(device, compatibleViewFormats[0]->format);
-                imageFormatListInfo.viewFormatCount = 2;
-                imageFormatListInfo.pViewFormats = viewFormats.data();
+                if (properties.usage & wgpu::TextureUsage::StorageBinding) {
+                    // Don't use an image format list because it becomes impossible to make an
+                    // rgba8unorm storage texture which may be reinterpreted as rgba8unorm-srgb,
+                    // because the srgb format doesn't support storage. Creation with an explicit
+                    // format list that includes srgb will fail.
+                    // This is the same issue seen with the DMA buf import path which has a
+                    // workaround to bypass the support check.
+                    // TODO(crbug.com/dawn/2304): If the dma buf import is resolved in a better way,
+                    // apply the same fix here.
+                } else if (device->GetDeviceInfo().HasExt(DeviceExt::ImageFormatList)) {
+                    // Set the list of view formats the image can be compatible with.
+                    DAWN_ASSERT(compatibleViewFormats.size() == 1u);
+                    viewFormats[0] = vkFormat;
+                    viewFormats[1] = VulkanImageFormat(device, compatibleViewFormats[0]->format);
+                    imageFormatListInfo.viewFormatCount = 2;
+                    imageFormatListInfo.pViewFormats = viewFormats.data();
+                }
             }
-        }
 
-        if (imageFormatListInfo.viewFormatCount > 0) {
-            DAWN_TRY_CONTEXT(CheckExternalImageFormatSupport(device, properties, &imageFormatInfo,
-                                                             handleType, &imageFormatListInfo),
-                             "checking import support of AHardwareBuffer");
-        } else {
-            DAWN_TRY_CONTEXT(
-                CheckExternalImageFormatSupport(device, properties, &imageFormatInfo, handleType),
-                "checking import support of AHardwareBuffer");
+            if (imageFormatListInfo.viewFormatCount > 0) {
+                DAWN_TRY_CONTEXT(
+                    CheckExternalImageFormatSupport(device, properties, &imageFormatInfo,
+                                                    handleType, &imageFormatListInfo),
+                    "checking import support of AHardwareBuffer");
+            } else {
+                DAWN_TRY_CONTEXT(CheckExternalImageFormatSupport(device, properties,
+                                                                 &imageFormatInfo, handleType),
+                                 "checking import support of AHardwareBuffer");
+            }
         }
     }
 
     // Create the VkImage for the import.
     {
         VkImage vkImage;
-        DAWN_TRY_ASSIGN(vkImage, CreateExternalVkImage(device, properties, imageFormatInfo,
-                                                       handleType, &imageFormatListInfo));
+        DAWN_TRY_ASSIGN(vkImage,
+                        CreateExternalVkImage(device, properties, imageFormatInfo, handleType,
+                                              &imageFormatListInfo, &externalFormatAndroid));
 
         sharedTextureMemory->mVkImage =
             AcquireRef(new RefCountedVkHandle<VkImage>(device, vkImage));
@@ -967,6 +993,7 @@
     TextureBase* texture,
     const UnpackedPtr<BeginAccessDescriptor>& descriptor) {
     // TODO(dawn/2276): support concurrent read access.
+    // TODO(dawn/2476): Validate texture with TextureFormat::External is initialized.
     DAWN_INVALID_IF(descriptor->concurrentRead, "Vulkan backend doesn't support concurrent read.");
 
     wgpu::SType type;
diff --git a/src/dawn/native/vulkan/TextureVk.cpp b/src/dawn/native/vulkan/TextureVk.cpp
index 29958df..ff47906 100644
--- a/src/dawn/native/vulkan/TextureVk.cpp
+++ b/src/dawn/native/vulkan/TextureVk.cpp
@@ -524,6 +524,10 @@
                 return VulkanImageFormat(device, wgpu::TextureFormat::Depth24PlusStencil8);
             }
 
+        case wgpu::TextureFormat::External:
+            // The VkFormat is Undefined when TextureFormat::External is passed for YCbCr samplers.
+            return VK_FORMAT_UNDEFINED;
+
         // R8BG8A8Triplanar420Unorm format is only supported on macOS.
         case wgpu::TextureFormat::R8BG8A8Triplanar420Unorm:
         case wgpu::TextureFormat::Undefined:
@@ -564,6 +568,7 @@
                 return wgpu::TextureFormat::Depth24PlusStencil8;
             }
             break;
+
         default:
             break;
     }
@@ -1768,6 +1773,7 @@
     if (auto* yCbCrVkDescriptor = descriptor.Get<YCbCrVkDescriptor>()) {
         mYCbCrVkDescriptor = *yCbCrVkDescriptor;
         mYCbCrVkDescriptor.nextInChain = nullptr;
+
         DAWN_TRY_ASSIGN(mSamplerYCbCrConversion,
                         CreateSamplerYCbCrConversionCreateInfo(mYCbCrVkDescriptor, device));
 
diff --git a/src/dawn/node/binding/Converter.cpp b/src/dawn/node/binding/Converter.cpp
index a0ed1fe..d562bd7 100644
--- a/src/dawn/node/binding/Converter.cpp
+++ b/src/dawn/node/binding/Converter.cpp
@@ -666,6 +666,7 @@
         case wgpu::TextureFormat::RG16Unorm:
         case wgpu::TextureFormat::RGBA16Snorm:
         case wgpu::TextureFormat::RGBA16Unorm:
+        case wgpu::TextureFormat::External:
 
         case wgpu::TextureFormat::Undefined:
             return false;
diff --git a/src/dawn/tests/end2end/YCbCrInfoTests.cpp b/src/dawn/tests/end2end/YCbCrInfoTests.cpp
index 7e8fa4d7..38972ac 100644
--- a/src/dawn/tests/end2end/YCbCrInfoTests.cpp
+++ b/src/dawn/tests/end2end/YCbCrInfoTests.cpp
@@ -37,24 +37,53 @@
 namespace dawn {
 namespace {
 
-constexpr uint32_t kWidth = 32u;
-constexpr uint32_t kHeight = 32u;
-constexpr uint32_t kDefaultMipLevels = 6u;
+constexpr uint32_t kDefaultMipLevels = 1u;
 constexpr uint32_t kDefaultLayerCount = 1u;
-constexpr uint32_t kDefaultSampleCount = 1u;
-constexpr wgpu::TextureFormat kDefaultTextureFormat = wgpu::TextureFormat::RGBA8Unorm;
+constexpr wgpu::TextureFormat kDefaultTextureFormat = wgpu::TextureFormat::External;
 
-wgpu::Texture Create2DTexture(wgpu::Device& device) {
+wgpu::Texture Create2DTexture(wgpu::Device& device,
+                              wgpu::TextureFormat format = kDefaultTextureFormat) {
+#if DAWN_PLATFORM_IS(ANDROID)
+    constexpr uint32_t kWidth = 32u;
+    constexpr uint32_t kHeight = 32u;
+    AHardwareBuffer_Desc aHardwareBufferDesc = {
+        .width = kWidth,
+        .height = kHeight,
+        .layers = 1,
+        .format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+        .usage = AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE,
+    };
+    AHardwareBuffer* aHardwareBuffer;
+    EXPECT_EQ(AHardwareBuffer_allocate(&aHardwareBufferDesc, &aHardwareBuffer), 0);
+
+    // Get actual desc for allocated buffer so we know the stride for cpu data.
+    AHardwareBuffer_describe(aHardwareBuffer, &aHardwareBufferDesc);
+
+    wgpu::SharedTextureMemoryAHardwareBufferDescriptor stmAHardwareBufferDesc;
+    stmAHardwareBufferDesc.handle = aHardwareBuffer;
+    stmAHardwareBufferDesc.useExternalFormat = true;
+
+    wgpu::SharedTextureMemoryDescriptor desc;
+    desc.nextInChain = &stmAHardwareBufferDesc;
+
+    wgpu::SharedTextureMemory memory = device.ImportSharedTextureMemory(&desc);
+
     wgpu::TextureDescriptor descriptor;
     descriptor.dimension = wgpu::TextureDimension::e2D;
     descriptor.size.width = kWidth;
     descriptor.size.height = kHeight;
     descriptor.size.depthOrArrayLayers = kDefaultLayerCount;
-    descriptor.sampleCount = kDefaultSampleCount;
+    descriptor.sampleCount = 1u;
     descriptor.format = kDefaultTextureFormat;
     descriptor.mipLevelCount = kDefaultMipLevels;
-    descriptor.usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment;
-    return device.CreateTexture(&descriptor);
+    descriptor.usage = wgpu::TextureUsage::TextureBinding;
+
+    auto texture = memory.CreateTexture(&descriptor);
+    AHardwareBuffer_release(aHardwareBuffer);
+    return texture;
+#else
+    return {};
+#endif
 }
 
 wgpu::TextureViewDescriptor CreateDefaultViewDescriptor(wgpu::TextureViewDimension dimension) {
@@ -83,8 +112,10 @@
     std::vector<wgpu::FeatureName> GetRequiredFeatures() override {
         std::vector<wgpu::FeatureName> requiredFeatures = {};
         if (SupportsFeatures({wgpu::FeatureName::StaticSamplers}) &&
-            SupportsFeatures({wgpu::FeatureName::YCbCrVulkanSamplers})) {
+            SupportsFeatures({wgpu::FeatureName::YCbCrVulkanSamplers}) &&
+            SupportsFeatures({wgpu::FeatureName::SharedTextureMemoryAHardwareBuffer})) {
             requiredFeatures.push_back(wgpu::FeatureName::YCbCrVulkanSamplers);
+            requiredFeatures.push_back(wgpu::FeatureName::SharedTextureMemoryAHardwareBuffer);
         }
         return requiredFeatures;
     }
@@ -138,8 +169,25 @@
     ASSERT_DEVICE_ERROR(device.CreateSampler(&samplerDesc));
 }
 
+// Test that it is invalid to create texture view with formats other than External.
+TEST_P(YCbCrInfoTest, YCbCrTextureViewInvalidWithoutWgpuFormatExternal) {
+    wgpu::Texture texture = Create2DTexture(device);
+
+    wgpu::TextureViewDescriptor descriptor =
+        CreateDefaultViewDescriptor(wgpu::TextureViewDimension::e2D);
+    // Pass RGBA8Unorm instead of External format.
+    descriptor.format = wgpu::TextureFormat::RGBA8Unorm;
+    descriptor.arrayLayerCount = 1;
+
+    wgpu::YCbCrVkDescriptor yCbCrDesc = {};
+    yCbCrDesc.vkFormat = VK_FORMAT_R8G8B8A8_UNORM;
+    descriptor.nextInChain = &yCbCrDesc;
+
+    ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
+}
+
 // Test that it is possible to create texture view with ycbcr vulkan descriptor.
-TEST_P(YCbCrInfoTest, YCbCrTextureViewValidWhenFeatureEnabled) {
+TEST_P(YCbCrInfoTest, YCbCrTextureViewValidWithWgpuFormatExternal) {
     wgpu::Texture texture = Create2DTexture(device);
 
     wgpu::TextureViewDescriptor descriptor =
@@ -206,6 +254,18 @@
     ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
 }
 
+// Test that it is NOT possible to create texture view from texture created with
+// TextureFormat::External but NO ycbcr vulkan descriptor passed.
+TEST_P(YCbCrInfoTest, YCbCrTextureViewInvalidWithNoYCbCrDescriptor) {
+    wgpu::Texture texture = Create2DTexture(device);
+
+    wgpu::TextureViewDescriptor descriptor =
+        CreateDefaultViewDescriptor(wgpu::TextureViewDimension::e2D);
+    descriptor.arrayLayerCount = 1;
+
+    ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
+}
+
 DAWN_INSTANTIATE_TEST(YCbCrInfoTest, VulkanBackend());
 
 }  // anonymous namespace
diff --git a/src/dawn/tests/unittests/validation/YCbCrInfoValidationTests.cpp b/src/dawn/tests/unittests/validation/YCbCrInfoValidationTests.cpp
index 8f4ad11..46b6a22 100644
--- a/src/dawn/tests/unittests/validation/YCbCrInfoValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/YCbCrInfoValidationTests.cpp
@@ -39,18 +39,19 @@
 constexpr uint32_t kDefaultMipLevels = 1u;
 constexpr uint32_t kDefaultLayerCount = 1u;
 constexpr uint32_t kDefaultSampleCount = 1u;
-constexpr wgpu::TextureFormat kDefaultTextureFormat = wgpu::TextureFormat::RGBA8Unorm;
+constexpr wgpu::TextureFormat kDefaultTextureFormat = wgpu::TextureFormat::External;
 
-wgpu::Texture Create2DTexture(wgpu::Device& device) {
+wgpu::Texture Create2DTexture(wgpu::Device& device,
+                              wgpu::TextureFormat format = kDefaultTextureFormat) {
     wgpu::TextureDescriptor descriptor;
     descriptor.dimension = wgpu::TextureDimension::e2D;
     descriptor.size.width = kWidth;
     descriptor.size.height = kHeight;
     descriptor.size.depthOrArrayLayers = kDefaultLayerCount;
     descriptor.sampleCount = kDefaultSampleCount;
-    descriptor.format = kDefaultTextureFormat;
+    descriptor.format = format;
     descriptor.mipLevelCount = kDefaultMipLevels;
-    descriptor.usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment;
+    descriptor.usage = wgpu::TextureUsage::TextureBinding;
     return device.CreateTexture(&descriptor);
 }
 
@@ -84,10 +85,16 @@
     ASSERT_DEVICE_ERROR(device.CreateSampler(&samplerDesc));
 }
 
+// Tests that creating a texture with External format raises an error if the required feature is not
+// enabled.
+TEST_F(YCbCrInfoWithoutFeatureValidationTest, ExternalTextureNotSupported) {
+    ASSERT_DEVICE_ERROR(Create2DTexture(device));
+}
+
 // Tests that creating a texture view with a valid ycbcr vulkan descriptor raises an error
 // if the required feature is not enabled.
 TEST_F(YCbCrInfoWithoutFeatureValidationTest, YCbCrTextureViewNotSupported) {
-    wgpu::Texture texture = Create2DTexture(device);
+    wgpu::Texture texture = Create2DTexture(device, wgpu::TextureFormat::RGBA8Unorm);
 
     wgpu::TextureViewDescriptor descriptor =
         CreateDefaultViewDescriptor(wgpu::TextureViewDimension::e2D);
diff --git a/src/dawn/tests/white_box/SharedTextureMemoryTests_android.cpp b/src/dawn/tests/white_box/SharedTextureMemoryTests_android.cpp
index 17d3533..6e97075 100644
--- a/src/dawn/tests/white_box/SharedTextureMemoryTests_android.cpp
+++ b/src/dawn/tests/white_box/SharedTextureMemoryTests_android.cpp
@@ -52,7 +52,8 @@
 
     std::vector<wgpu::FeatureName> RequiredFeatures(const wgpu::Adapter&) const override {
         return {wgpu::FeatureName::SharedTextureMemoryAHardwareBuffer,
-                wgpu::FeatureName::SharedFenceVkSemaphoreSyncFD};
+                wgpu::FeatureName::SharedFenceVkSemaphoreSyncFD,
+                wgpu::FeatureName::YCbCrVulkanSamplers};
     }
 
     static std::string MakeLabel(const AHardwareBuffer_Desc& desc) {
@@ -409,8 +410,8 @@
     EXPECT_EQ(bufferFormatProperties.externalFormat, yCbCrInfo.externalFormat);
 }
 
-// Test querying YCbCr info from the SharedTextureMemory.
-TEST_P(SharedTextureMemoryTests, QueryYCbCrInfo) {
+// Test querying YCbCr info from the SharedTextureMemory without external format.
+TEST_P(SharedTextureMemoryTests, QueryYCbCrInfoWithoutExternalFormat) {
     AHardwareBuffer_Desc aHardwareBufferDesc = {
         .width = 4,
         .height = 4,
@@ -441,6 +442,7 @@
     // AHB.
     wgpu::SharedTextureMemoryAHardwareBufferDescriptor stmAHardwareBufferDesc;
     stmAHardwareBufferDesc.handle = aHardwareBuffer;
+    stmAHardwareBufferDesc.useExternalFormat = false;
 
     wgpu::SharedTextureMemoryDescriptor desc;
     desc.nextInChain = &stmAHardwareBufferDesc;
@@ -478,6 +480,82 @@
         formatFeatures &
             VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_CHROMA_RECONSTRUCTION_EXPLICIT_BIT,
         yCbCrInfo.forceExplicitReconstruction);
+    uint64_t expectedExternalFormat = 0u;
+    EXPECT_EQ(expectedExternalFormat, yCbCrInfo.externalFormat);
+}
+
+// Test querying YCbCr info from the SharedTextureMemory with external format.
+TEST_P(SharedTextureMemoryTests, QueryYCbCrInfoWithExternalFormat) {
+    AHardwareBuffer_Desc aHardwareBufferDesc = {
+        .width = 4,
+        .height = 4,
+        .layers = 1,
+        .format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+        .usage = AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE,
+    };
+    AHardwareBuffer* aHardwareBuffer;
+    EXPECT_EQ(AHardwareBuffer_allocate(&aHardwareBufferDesc, &aHardwareBuffer), 0);
+
+    // Query the YCbCr properties of the AHardwareBuffer.
+    auto deviceVk = native::vulkan::ToBackend(native::FromAPI(device.Get()));
+
+    VkAndroidHardwareBufferPropertiesANDROID bufferProperties = {
+        .sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID,
+    };
+
+    VkAndroidHardwareBufferFormatPropertiesANDROID bufferFormatProperties;
+    native::vulkan::PNextChainBuilder bufferPropertiesChain(&bufferProperties);
+    bufferPropertiesChain.Add(&bufferFormatProperties,
+                              VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_FORMAT_PROPERTIES_ANDROID);
+
+    VkDevice vkDevice = deviceVk->GetVkDevice();
+    EXPECT_EQ(deviceVk->fn.GetAndroidHardwareBufferPropertiesANDROID(vkDevice, aHardwareBuffer,
+                                                                     &bufferProperties),
+              VK_SUCCESS);
+
+    // Query the YCbCr properties of a SharedTextureMemory created from this
+    // AHB.
+    wgpu::SharedTextureMemoryAHardwareBufferDescriptor stmAHardwareBufferDesc;
+    stmAHardwareBufferDesc.handle = aHardwareBuffer;
+    stmAHardwareBufferDesc.useExternalFormat = true;
+
+    wgpu::SharedTextureMemoryDescriptor desc;
+    desc.nextInChain = &stmAHardwareBufferDesc;
+
+    wgpu::SharedTextureMemory memory = device.ImportSharedTextureMemory(&desc);
+
+    wgpu::SharedTextureMemoryProperties properties;
+    wgpu::SharedTextureMemoryAHardwareBufferProperties ahbProperties = {};
+    properties.nextInChain = &ahbProperties;
+    memory.GetProperties(&properties);
+    auto yCbCrInfo = ahbProperties.yCbCrInfo;
+    uint32_t formatFeatures = bufferFormatProperties.formatFeatures;
+
+    // Verify that the YCbCr properties match.
+    VkFormat expectedVkFormat = VK_FORMAT_UNDEFINED;
+    EXPECT_EQ(expectedVkFormat, yCbCrInfo.vkFormat);
+    EXPECT_EQ(bufferFormatProperties.suggestedYcbcrModel, yCbCrInfo.vkYCbCrModel);
+    EXPECT_EQ(bufferFormatProperties.suggestedYcbcrRange, yCbCrInfo.vkYCbCrRange);
+    EXPECT_EQ(bufferFormatProperties.samplerYcbcrConversionComponents.r,
+              yCbCrInfo.vkComponentSwizzleRed);
+    EXPECT_EQ(bufferFormatProperties.samplerYcbcrConversionComponents.g,
+              yCbCrInfo.vkComponentSwizzleGreen);
+    EXPECT_EQ(bufferFormatProperties.samplerYcbcrConversionComponents.b,
+              yCbCrInfo.vkComponentSwizzleBlue);
+    EXPECT_EQ(bufferFormatProperties.samplerYcbcrConversionComponents.a,
+              yCbCrInfo.vkComponentSwizzleAlpha);
+    EXPECT_EQ(bufferFormatProperties.suggestedXChromaOffset, yCbCrInfo.vkXChromaOffset);
+    EXPECT_EQ(bufferFormatProperties.suggestedYChromaOffset, yCbCrInfo.vkYChromaOffset);
+
+    wgpu::FilterMode expectedFilter =
+        (formatFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_LINEAR_FILTER_BIT)
+            ? wgpu::FilterMode::Linear
+            : wgpu::FilterMode::Nearest;
+    EXPECT_EQ(expectedFilter, yCbCrInfo.vkChromaFilter);
+    EXPECT_EQ(
+        formatFeatures &
+            VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_CHROMA_RECONSTRUCTION_EXPLICIT_BIT,
+        yCbCrInfo.forceExplicitReconstruction);
     EXPECT_EQ(bufferFormatProperties.externalFormat, yCbCrInfo.externalFormat);
 }
 
diff --git a/src/dawn/utils/TextureUtils.cpp b/src/dawn/utils/TextureUtils.cpp
index f356afc..7036d32 100644
--- a/src/dawn/utils/TextureUtils.cpp
+++ b/src/dawn/utils/TextureUtils.cpp
@@ -527,6 +527,7 @@
         case wgpu::TextureFormat::R10X6BG10X6Biplanar422Unorm:
         case wgpu::TextureFormat::R10X6BG10X6Biplanar444Unorm:
         case wgpu::TextureFormat::R8BG8A8Triplanar420Unorm:
+        case wgpu::TextureFormat::External:
 
         case wgpu::TextureFormat::Undefined:
             break;
@@ -656,6 +657,7 @@
         case wgpu::TextureFormat::R10X6BG10X6Biplanar422Unorm:
         case wgpu::TextureFormat::R10X6BG10X6Biplanar444Unorm:
         case wgpu::TextureFormat::R8BG8A8Triplanar420Unorm:
+        case wgpu::TextureFormat::External:
 
         case wgpu::TextureFormat::Undefined:
             break;
@@ -785,6 +787,7 @@
         case wgpu::TextureFormat::R10X6BG10X6Biplanar422Unorm:
         case wgpu::TextureFormat::R10X6BG10X6Biplanar444Unorm:
         case wgpu::TextureFormat::R8BG8A8Triplanar420Unorm:
+        case wgpu::TextureFormat::External:
 
         case wgpu::TextureFormat::Undefined:
             break;