Reland "Add CopyExternalTextureForBrowser()"

This is a reland of commit f392c38b67ca8456d448076b70dca87db02bb990

The new added structure ImageCopyExternalTexture used in dawn only. Adding
tags : ["dawn"] to fix compile error.

Original change's description:
> Add CopyExternalTextureForBrowser()
>
> This API accept ExternalTexture object as copy source and a dawn 2D texture
> as destination. It has similar functions as CopyTextureForBrowser().
> The API is used to support cases that source images are multi-planar format
> and want to do conversion and uploading to a dawn 2D texture.
>
> Bug: chromium:1361363
> Change-Id: Ie390acfb95b47d417f4a8faa2d1e19163d549154
> Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/105880
> Commit-Queue: Shaobo Yan <shaobo.yan@intel.com>
> Reviewed-by: Austin Eng <enga@chromium.org>
> Reviewed-by: Corentin Wallez <cwallez@chromium.org>

Bug: chromium:1361363
Change-Id: I213c3dc7fe81ccc35050592e491995d0d5425f6e
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/106883
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Shaobo Yan <shaobo.yan@intel.com>
diff --git a/dawn.json b/dawn.json
index 4b2f150..554a95e 100644
--- a/dawn.json
+++ b/dawn.json
@@ -916,7 +916,6 @@
         "category": "structure",
         "extensible": "in",
         "tags": ["dawn"],
-        "_TODO": "support number as length input",
         "members": [
             {"name": "flip y", "type": "bool", "default": "false"},
             {"name": "needs color space conversion", "type": "bool", "default": "false"},
@@ -1367,7 +1366,6 @@
         "category": "structure",
         "extensible": "in",
         "tags": ["dawn"],
-        "_TODO": "crbug.com/1316671: Mark 'visible rect' as must have after chromium side changes landed",
         "members": [
             {"name": "label", "type": "char", "annotation": "const*", "length": "strlen", "optional": true},
             {"name": "plane 0", "type": "texture view"},
@@ -1440,6 +1438,15 @@
             {"name": "aspect", "type": "texture aspect", "default": "all"}
         ]
     },
+    "image copy external texture": {
+        "category": "structure",
+        "extensible": "in",
+        "tags": ["dawn"],
+        "members": [
+            {"name": "external texture", "type": "external texture"},
+            {"name": "origin", "type": "origin 3D"}
+        ]
+    },
     "index format": {
         "category": "enum",
         "values": [
@@ -1722,6 +1729,17 @@
                 ]
             },
             {
+                "name": "copy external texture for browser",
+                "extensible": "in",
+                "tags": ["dawn"],
+                "args": [
+                    {"name": "source", "type": "image copy external texture", "annotation": "const*"},
+                    {"name": "destination", "type": "image copy texture", "annotation": "const*"},
+                    {"name": "copy size", "type": "extent 3D", "annotation": "const*"},
+                    {"name": "options", "type": "copy texture for browser options", "annotation": "const*"}
+                ]
+            },
+            {
                 "name": "set label",
                 "returns": "void",
                 "args": [
diff --git a/src/dawn/native/CopyTextureForBrowserHelper.cpp b/src/dawn/native/CopyTextureForBrowserHelper.cpp
index 6f89842..fb585f9 100644
--- a/src/dawn/native/CopyTextureForBrowserHelper.cpp
+++ b/src/dawn/native/CopyTextureForBrowserHelper.cpp
@@ -25,6 +25,7 @@
 #include "dawn/native/CommandEncoder.h"
 #include "dawn/native/CommandValidation.h"
 #include "dawn/native/Device.h"
+#include "dawn/native/ExternalTexture.h"
 #include "dawn/native/InternalPipelineStore.h"
 #include "dawn/native/Queue.h"
 #include "dawn/native/RenderPassEncoder.h"
@@ -36,9 +37,8 @@
 
 namespace dawn::native {
 namespace {
-
-static const char sCopyTextureForBrowserShader[] = R"(
-            struct GammaTransferParams {
+static const char sCopyForBrowserShader[] = R"(
+                struct GammaTransferParamsInternal {
                 G: f32,
                 A: f32,
                 B: f32,
@@ -49,15 +49,15 @@
                 padding: u32,
             };
 
-            struct Uniforms {                                            // offset   align   size
-                scale: vec2<f32>,                                        // 0        8       8
-                offset: vec2<f32>,                                       // 8        8       8
-                steps_mask: u32,                                         // 16       4       4
-                // implicit padding;                                     // 20               12
-                conversion_matrix: mat3x3<f32>,                          // 32       16      48
-                gamma_decoding_params: GammaTransferParams,              // 80       4       32
-                gamma_encoding_params: GammaTransferParams,              // 112      4       32
-                gamma_decoding_for_dst_srgb_params: GammaTransferParams, // 144      4       32
+            struct Uniforms {                                                    // offset   align   size
+                scale: vec2<f32>,                                                // 0        8       8
+                offset: vec2<f32>,                                               // 8        8       8
+                steps_mask: u32,                                                 // 16       4       4
+                // implicit padding;                                             // 20               12
+                conversion_matrix: mat3x3<f32>,                                  // 32       16      48
+                gamma_decoding_params: GammaTransferParamsInternal,              // 80       4       32
+                gamma_encoding_params: GammaTransferParamsInternal,              // 112      4       32
+                gamma_decoding_for_dst_srgb_params: GammaTransferParamsInternal, // 144      4       32
             };
 
             @binding(0) @group(0) var<uniform> uniforms : Uniforms;
@@ -75,7 +75,7 @@
             //  nonlinear = pow(A * x + B, G) + E
             // (https://source.chromium.org/chromium/chromium/src/+/main:ui/gfx/color_transform.cc;l=541)
             // Expand the equation with sign() to make it handle all gamma conversions.
-            fn gamma_conversion(v: f32, params: GammaTransferParams) -> f32 {
+            fn gamma_conversion(v: f32, params: GammaTransferParamsInternal) -> f32 {
                 // Linear part: C * x + F
                 if (abs(v) < params.D) {
                     return sign(v) * (params.C * abs(v) + params.F);
@@ -121,24 +121,23 @@
             }
 
             @binding(1) @group(0) var mySampler: sampler;
-            @binding(2) @group(0) var myTexture: texture_2d<f32>;
 
-            @fragment
-            fn fs_main(
-                @location(0) texcoord : vec2<f32>
-            ) -> @location(0) vec4<f32> {
-                // Clamp the texcoord and discard the out-of-bound pixels.
+            // Resource used in copyTexture entry point only.
+            @binding(2) @group(0) var mySourceTexture: texture_2d<f32>;
+
+            // Resource used in copyExternalTexture entry point only.
+            @binding(2) @group(0) var mySourceExternalTexture: texture_external;
+
+            fn discardIfOutsideOfCopy(texcoord : vec2<f32>) {
                 var clampedTexcoord =
                     clamp(texcoord, vec2<f32>(0.0, 0.0), vec2<f32>(1.0, 1.0));
-
-                // Swizzling of texture formats when sampling / rendering is handled by the
-                // hardware so we don't need special logic in this shader. This is covered by tests.
-                var color = textureSample(myTexture, mySampler, texcoord);
-
                 if (!all(clampedTexcoord == texcoord)) {
                     discard;
                 }
+            }
 
+            fn transform(srcColor : vec4<f32>) -> vec4<f32> {
+                var color = srcColor;
                 let kUnpremultiplyStep = 0x01u;
                 let kDecodeToLinearStep = 0x02u;
                 let kConvertToDstGamutStep = 0x04u;
@@ -203,11 +202,33 @@
 
                 return color;
             }
+
+            @fragment
+            fn copyTexture(@location(0) texcoord : vec2<f32>
+            ) -> @location(0) vec4<f32> {
+                var color = textureSample(mySourceTexture, mySampler, texcoord);
+
+                // TODO(crbug.com/tint/1723): Discard before sampling should be valid.
+                discardIfOutsideOfCopy(texcoord);
+
+                return transform(color);
+            }
+
+            @fragment
+            fn copyExternalTexture(@location(0) texcoord : vec2<f32>
+            ) -> @location(0) vec4<f32> {
+                var color = textureSampleBaseClampToEdge(mySourceExternalTexture, mySampler, texcoord);
+
+                // TODO(crbug.com/tint/1723): Discard before sampling should be valid.
+                discardIfOutsideOfCopy(texcoord);
+
+                return transform(color);
+            }
         )";
 
 // Follow the same order of skcms_TransferFunction
 // https://source.chromium.org/chromium/chromium/src/+/main:third_party/skia/include/third_party/skcms/skcms.h;l=46;
-struct GammaTransferParams {
+struct GammaTransferParamsInternal {
     float G = 0.0;
     float A = 0.0;
     float B = 0.0;
@@ -226,16 +247,22 @@
     uint32_t stepsMask = 0;
     const std::array<uint32_t, 3> padding = {};  // 12 bytes padding
     std::array<float, 12> conversionMatrix = {};
-    GammaTransferParams gammaDecodingParams = {};
-    GammaTransferParams gammaEncodingParams = {};
-    GammaTransferParams gammaDecodingForDstSrgbParams = {};
+    GammaTransferParamsInternal gammaDecodingParams = {};
+    GammaTransferParamsInternal gammaEncodingParams = {};
+    GammaTransferParamsInternal gammaDecodingForDstSrgbParams = {};
 };
 static_assert(sizeof(Uniform) == 176);
 
+enum class SourceTextureType { Texture2D, ExternalTexture };
+
+struct TextureInfo {
+    Origin3D origin;
+    Extent3D size;
+};
+
 // TODO(crbug.com/dawn/856): Expand copyTextureForBrowser to support any
 // non-depth, non-stencil, non-compressed texture format pair copy.
-MaybeError ValidateCopyTextureFormatConversion(const wgpu::TextureFormat srcFormat,
-                                               const wgpu::TextureFormat dstFormat) {
+MaybeError ValidateCopyTextureSourceFormat(const wgpu::TextureFormat srcFormat) {
     switch (srcFormat) {
         case wgpu::TextureFormat::BGRA8Unorm:
         case wgpu::TextureFormat::RGBA8Unorm:
@@ -245,6 +272,10 @@
             return DAWN_VALIDATION_ERROR("Source texture format (%s) is not supported.", srcFormat);
     }
 
+    return {};
+}
+
+MaybeError ValidateCopyForBrowserDestinationFormat(const wgpu::TextureFormat dstFormat) {
     switch (dstFormat) {
         case wgpu::TextureFormat::R8Unorm:
         case wgpu::TextureFormat::R16Float:
@@ -268,7 +299,8 @@
     return {};
 }
 
-RenderPipelineBase* GetCachedPipeline(InternalPipelineStore* store, wgpu::TextureFormat dstFormat) {
+RenderPipelineBase* GetCachedCopyTexturePipeline(InternalPipelineStore* store,
+                                                 wgpu::TextureFormat dstFormat) {
     auto pipeline = store->copyTextureForBrowserPipelines.find(dstFormat);
     if (pipeline != store->copyTextureForBrowserPipelines.end()) {
         return pipeline->second.Get();
@@ -276,120 +308,92 @@
     return nullptr;
 }
 
+RenderPipelineBase* GetCachedCopyExternalTexturePipeline(InternalPipelineStore* store,
+                                                         wgpu::TextureFormat dstFormat) {
+    auto pipeline = store->copyExternalTextureForBrowserPipelines.find(dstFormat);
+    if (pipeline != store->copyExternalTextureForBrowserPipelines.end()) {
+        return pipeline->second.Get();
+    }
+    return nullptr;
+}
+
+ResultOrError<Ref<RenderPipelineBase>> CreateCopyForBrowserPipeline(
+    DeviceBase* device,
+    wgpu::TextureFormat dstFormat,
+    ShaderModuleBase* shaderModule,
+    const char* fragmentEntryPoint) {
+    // Prepare vertex stage.
+    VertexState vertex = {};
+    vertex.module = shaderModule;
+    vertex.entryPoint = "vs_main";
+
+    // Prepare frgament stage.
+    FragmentState fragment = {};
+    fragment.module = shaderModule;
+    fragment.entryPoint = fragmentEntryPoint;
+
+    // Prepare color state.
+    ColorTargetState target = {};
+    target.format = dstFormat;
+
+    // Create RenderPipeline.
+    RenderPipelineDescriptor renderPipelineDesc = {};
+
+    // Generate the layout based on shader modules.
+    renderPipelineDesc.layout = nullptr;
+
+    renderPipelineDesc.vertex = vertex;
+    renderPipelineDesc.fragment = &fragment;
+
+    renderPipelineDesc.primitive.topology = wgpu::PrimitiveTopology::TriangleList;
+
+    fragment.targetCount = 1;
+    fragment.targets = &target;
+
+    return device->CreateRenderPipeline(&renderPipelineDesc);
+}
+
+ResultOrError<ShaderModuleBase*> GetOrCreateCopyForBrowserShaderModule(
+    DeviceBase* device,
+    InternalPipelineStore* store) {
+    if (store->copyForBrowser == nullptr) {
+        DAWN_TRY_ASSIGN(store->copyForBrowser,
+                        utils::CreateShaderModule(device, sCopyForBrowserShader));
+    }
+
+    return store->copyForBrowser.Get();
+}
+
 ResultOrError<RenderPipelineBase*> GetOrCreateCopyTextureForBrowserPipeline(
     DeviceBase* device,
     wgpu::TextureFormat dstFormat) {
     InternalPipelineStore* store = device->GetInternalPipelineStore();
-
-    if (GetCachedPipeline(store, dstFormat) == nullptr) {
-        // Create vertex shader module if not cached before.
-        if (store->copyTextureForBrowser == nullptr) {
-            DAWN_TRY_ASSIGN(store->copyTextureForBrowser,
-                            utils::CreateShaderModule(device, sCopyTextureForBrowserShader));
-        }
-
-        ShaderModuleBase* shaderModule = store->copyTextureForBrowser.Get();
-
-        // Prepare vertex stage.
-        VertexState vertex = {};
-        vertex.module = shaderModule;
-        vertex.entryPoint = "vs_main";
-
-        // Prepare frgament stage.
-        FragmentState fragment = {};
-        fragment.module = shaderModule;
-        fragment.entryPoint = "fs_main";
-
-        // Prepare color state.
-        ColorTargetState target = {};
-        target.format = dstFormat;
-
-        // Create RenderPipeline.
-        RenderPipelineDescriptor renderPipelineDesc = {};
-
-        // Generate the layout based on shader modules.
-        renderPipelineDesc.layout = nullptr;
-
-        renderPipelineDesc.vertex = vertex;
-        renderPipelineDesc.fragment = &fragment;
-
-        renderPipelineDesc.primitive.topology = wgpu::PrimitiveTopology::TriangleList;
-
-        fragment.targetCount = 1;
-        fragment.targets = &target;
-
+    if (GetCachedCopyTexturePipeline(store, dstFormat) == nullptr) {
+        ShaderModuleBase* shaderModule;
+        DAWN_TRY_ASSIGN(shaderModule, GetOrCreateCopyForBrowserShaderModule(device, store));
         Ref<RenderPipelineBase> pipeline;
-        DAWN_TRY_ASSIGN(pipeline, device->CreateRenderPipeline(&renderPipelineDesc));
+        DAWN_TRY_ASSIGN(
+            pipeline, CreateCopyForBrowserPipeline(device, dstFormat, shaderModule, "copyTexture"));
         store->copyTextureForBrowserPipelines.insert({dstFormat, std::move(pipeline)});
     }
 
-    return GetCachedPipeline(store, dstFormat);
+    return GetCachedCopyTexturePipeline(store, dstFormat);
 }
-}  // anonymous namespace
 
-MaybeError ValidateCopyTextureForBrowser(DeviceBase* device,
-                                         const ImageCopyTexture* source,
-                                         const ImageCopyTexture* destination,
-                                         const Extent3D* copySize,
-                                         const CopyTextureForBrowserOptions* options) {
-    DAWN_TRY(device->ValidateObject(source->texture));
-    DAWN_TRY(device->ValidateObject(destination->texture));
-
-    DAWN_INVALID_IF(source->texture->GetTextureState() == TextureBase::TextureState::Destroyed,
-                    "Source texture %s is destroyed.", source->texture);
-
-    DAWN_INVALID_IF(destination->texture->GetTextureState() == TextureBase::TextureState::Destroyed,
-                    "Destination texture %s is destroyed.", destination->texture);
-
-    DAWN_TRY_CONTEXT(ValidateImageCopyTexture(device, *source, *copySize),
-                     "validating the ImageCopyTexture for the source");
-    DAWN_TRY_CONTEXT(ValidateImageCopyTexture(device, *destination, *copySize),
-                     "validating the ImageCopyTexture for the destination");
-
-    DAWN_TRY_CONTEXT(ValidateTextureCopyRange(device, *source, *copySize),
-                     "validating that the copy fits in the source");
-    DAWN_TRY_CONTEXT(ValidateTextureCopyRange(device, *destination, *copySize),
-                     "validating that the copy fits in the destination");
-
-    DAWN_TRY(ValidateTextureToTextureCopyCommonRestrictions(*source, *destination, *copySize));
-
-    DAWN_INVALID_IF(source->origin.z > 0, "Source has a non-zero z origin (%u).", source->origin.z);
-    DAWN_INVALID_IF(copySize->depthOrArrayLayers > 1, "Copy is for more than one array layer (%u)",
-                    copySize->depthOrArrayLayers);
-
-    DAWN_INVALID_IF(
-        source->texture->GetSampleCount() > 1 || destination->texture->GetSampleCount() > 1,
-        "The source texture sample count (%u) or the destination texture sample count (%u) is "
-        "not 1.",
-        source->texture->GetSampleCount(), destination->texture->GetSampleCount());
-
-    DAWN_INVALID_IF(
-        options->internalUsage && !device->HasFeature(Feature::DawnInternalUsages),
-        "The internalUsage is true while the dawn-internal-usages feature is not enabled.");
-    UsageValidationMode mode =
-        options->internalUsage ? UsageValidationMode::Internal : UsageValidationMode::Default;
-    DAWN_TRY(ValidateCanUseAs(source->texture, wgpu::TextureUsage::CopySrc, mode));
-    DAWN_TRY(ValidateCanUseAs(source->texture, wgpu::TextureUsage::TextureBinding, mode));
-    DAWN_TRY(ValidateCanUseAs(destination->texture, wgpu::TextureUsage::CopyDst, mode));
-    DAWN_TRY(ValidateCanUseAs(destination->texture, wgpu::TextureUsage::RenderAttachment, mode));
-
-    DAWN_TRY(ValidateCopyTextureFormatConversion(source->texture->GetFormat().format,
-                                                 destination->texture->GetFormat().format));
-
-    DAWN_INVALID_IF(options->nextInChain != nullptr, "nextInChain must be nullptr");
-
-    DAWN_TRY(ValidateAlphaMode(options->srcAlphaMode));
-    DAWN_TRY(ValidateAlphaMode(options->dstAlphaMode));
-
-    if (options->needsColorSpaceConversion) {
-        DAWN_INVALID_IF(options->srcTransferFunctionParameters == nullptr,
-                        "srcTransferFunctionParameters is nullptr when doing color conversion");
-        DAWN_INVALID_IF(options->conversionMatrix == nullptr,
-                        "conversionMatrix is nullptr when doing color conversion");
-        DAWN_INVALID_IF(options->dstTransferFunctionParameters == nullptr,
-                        "dstTransferFunctionParameters is nullptr when doing color conversion");
+ResultOrError<RenderPipelineBase*> GetOrCreateCopyExternalTextureForBrowserPipeline(
+    DeviceBase* device,
+    wgpu::TextureFormat dstFormat) {
+    InternalPipelineStore* store = device->GetInternalPipelineStore();
+    if (GetCachedCopyExternalTexturePipeline(store, dstFormat) == nullptr) {
+        ShaderModuleBase* shaderModule;
+        DAWN_TRY_ASSIGN(shaderModule, GetOrCreateCopyForBrowserShaderModule(device, store));
+        Ref<RenderPipelineBase> pipeline;
+        DAWN_TRY_ASSIGN(pipeline, CreateCopyForBrowserPipeline(device, dstFormat, shaderModule,
+                                                               "copyExternalTexture"));
+        store->copyExternalTextureForBrowserPipelines.insert({dstFormat, std::move(pipeline)});
     }
-    return {};
+
+    return GetCachedCopyExternalTexturePipeline(store, dstFormat);
 }
 
 // Whether the format of dst texture of CopyTextureForBrowser() is srgb or non-srgb.
@@ -403,11 +407,14 @@
     }
 }
 
-MaybeError DoCopyTextureForBrowser(DeviceBase* device,
-                                   const ImageCopyTexture* source,
-                                   const ImageCopyTexture* destination,
-                                   const Extent3D* copySize,
-                                   const CopyTextureForBrowserOptions* options) {
+template <typename T>
+MaybeError DoCopyForBrowser(DeviceBase* device,
+                            const TextureInfo* sourceInfo,
+                            T* sourceResource,
+                            const ImageCopyTexture* destination,
+                            const Extent3D* copySize,
+                            const CopyTextureForBrowserOptions* options,
+                            RenderPipelineBase* pipeline) {
     // TODO(crbug.com/dawn/856): In D3D12 and Vulkan, compatible texture format can directly
     // copy to each other. This can be a potential fast path.
 
@@ -416,23 +423,16 @@
         return {};
     }
 
-    bool isSrgbDstFormat = IsSrgbDstFormat(destination->texture->GetFormat().format);
-    RenderPipelineBase* pipeline;
-    DAWN_TRY_ASSIGN(pipeline, GetOrCreateCopyTextureForBrowserPipeline(
-                                  device, destination->texture->GetFormat().format));
-
     // Prepare bind group layout.
     Ref<BindGroupLayoutBase> layout;
     DAWN_TRY_ASSIGN(layout, pipeline->GetBindGroupLayout(0));
 
-    Extent3D srcTextureSize = source->texture->GetSize();
-
     // Prepare binding 0 resource: uniform buffer.
     Uniform uniformData = {
-        copySize->width / static_cast<float>(srcTextureSize.width),
-        copySize->height / static_cast<float>(srcTextureSize.height),  // scale
-        source->origin.x / static_cast<float>(srcTextureSize.width),
-        source->origin.y / static_cast<float>(srcTextureSize.height)  // offset
+        copySize->width / static_cast<float>(sourceInfo->size.width),
+        copySize->height / static_cast<float>(sourceInfo->size.height),  // scale
+        sourceInfo->origin.x / static_cast<float>(sourceInfo->size.width),
+        sourceInfo->origin.y / static_cast<float>(sourceInfo->size.height)  // offset
     };
 
     // Handle flipY. FlipY here means we flip the source texture firstly and then
@@ -440,7 +440,7 @@
     // need to unpack the flip.
     if (options->flipY) {
         uniformData.scaleY *= -1.0;
-        uniformData.offsetY += copySize->height / static_cast<float>(srcTextureSize.height);
+        uniformData.offsetY += copySize->height / static_cast<float>(sourceInfo->size.height);
     }
 
     uint32_t stepsMask = 0u;
@@ -528,6 +528,7 @@
     // and use it as render attachment when possible.
     // TODO(crbug.com/dawn/1195): Opt the condition for this extra step. It is possible to
     // bypass this extra step in some cases.
+    bool isSrgbDstFormat = IsSrgbDstFormat(destination->texture->GetFormat().format);
     if (isSrgbDstFormat) {
         stepsMask |= kDecodeForSrgbDstFormat;
         // Get gamma-linear conversion params from https://en.wikipedia.org/wiki/SRGB with some
@@ -536,6 +537,7 @@
             2.4, 1.0 / 1.055, 0.055 / 1.055, 1.0 / 12.92, 4.045e-02, 0.0, 0.0};
     }
 
+    // Upload uniform data
     uniformData.stepsMask = stepsMask;
 
     Ref<BufferBase> uniformBuffer;
@@ -550,23 +552,13 @@
     Ref<SamplerBase> sampler;
     DAWN_TRY_ASSIGN(sampler, device->CreateSampler(&samplerDesc));
 
-    // Prepare binding 2 resource: sampled texture
-    TextureViewDescriptor srcTextureViewDesc = {};
-    srcTextureViewDesc.dimension = wgpu::TextureViewDimension::e2D;
-    srcTextureViewDesc.baseMipLevel = source->mipLevel;
-    srcTextureViewDesc.mipLevelCount = 1;
-    srcTextureViewDesc.arrayLayerCount = 1;
-    Ref<TextureViewBase> srcTextureView;
-    DAWN_TRY_ASSIGN(srcTextureView,
-                    device->CreateTextureView(source->texture, &srcTextureViewDesc));
-
     // Create bind group after all binding entries are set.
     UsageValidationMode mode =
         options->internalUsage ? UsageValidationMode::Internal : UsageValidationMode::Default;
     Ref<BindGroupBase> bindGroup;
     DAWN_TRY_ASSIGN(bindGroup, utils::MakeBindGroup(
                                    device, layout,
-                                   {{0, uniformBuffer}, {1, sampler}, {2, srcTextureView}}, mode));
+                                   {{0, uniformBuffer}, {1, sampler}, {2, sourceResource}}, mode));
 
     // Create command encoder.
     CommandEncoderDescriptor commandEncoderDesc;
@@ -621,4 +613,166 @@
     return {};
 }
 
+MaybeError ValidateCopyForBrowserCommonRestrictions(DeviceBase* device,
+                                                    const ImageCopyTexture& destination,
+                                                    const Extent3D& copySize,
+                                                    const CopyTextureForBrowserOptions& options) {
+    DAWN_TRY(device->ValidateObject(destination.texture));
+    DAWN_INVALID_IF(destination.texture->GetTextureState() == TextureBase::TextureState::Destroyed,
+                    "Destination texture %s is destroyed.", destination.texture);
+    DAWN_TRY_CONTEXT(ValidateImageCopyTexture(device, destination, copySize),
+                     "validating the ImageCopyTexture for the destination");
+    DAWN_TRY_CONTEXT(ValidateTextureCopyRange(device, destination, copySize),
+                     "validating that the copy fits in the destination");
+
+    UsageValidationMode mode =
+        options.internalUsage ? UsageValidationMode::Internal : UsageValidationMode::Default;
+    DAWN_TRY(ValidateCanUseAs(destination.texture, wgpu::TextureUsage::CopyDst, mode));
+    DAWN_TRY(ValidateCanUseAs(destination.texture, wgpu::TextureUsage::RenderAttachment, mode));
+
+    DAWN_INVALID_IF(copySize.depthOrArrayLayers > 1, "Copy is for more than one array layer (%u)",
+                    copySize.depthOrArrayLayers);
+
+    DAWN_INVALID_IF(destination.texture->GetSampleCount() > 1,
+                    "The destination texture sample count (%u) is not 1.",
+                    destination.texture->GetSampleCount());
+
+    DAWN_TRY(ValidateCopyForBrowserDestinationFormat(destination.texture->GetFormat().format));
+
+    // The valid destination formats are all color formats.
+    DAWN_INVALID_IF(
+        destination.aspect != wgpu::TextureAspect::All,
+        "Destination %s aspect (%s) doesn't select all the aspects of the destination format.",
+        destination.texture, destination.aspect);
+
+    DAWN_INVALID_IF(options.nextInChain != nullptr, "nextInChain must be nullptr");
+
+    DAWN_TRY(ValidateAlphaMode(options.srcAlphaMode));
+    DAWN_TRY(ValidateAlphaMode(options.dstAlphaMode));
+
+    if (options.needsColorSpaceConversion) {
+        DAWN_INVALID_IF(options.srcTransferFunctionParameters == nullptr,
+                        "srcTransferFunctionParameters is nullptr when doing color conversion");
+        DAWN_INVALID_IF(options.conversionMatrix == nullptr,
+                        "conversionMatrix is nullptr when doing color conversion");
+        DAWN_INVALID_IF(options.dstTransferFunctionParameters == nullptr,
+                        "dstTransferFunctionParameters is nullptr when doing color conversion");
+    }
+    return {};
+}
+}  // anonymous namespace
+
+MaybeError ValidateCopyTextureForBrowser(DeviceBase* device,
+                                         const ImageCopyTexture* source,
+                                         const ImageCopyTexture* destination,
+                                         const Extent3D* copySize,
+                                         const CopyTextureForBrowserOptions* options) {
+    DAWN_TRY(device->ValidateObject(source->texture));
+
+    DAWN_INVALID_IF(source->texture->GetTextureState() == TextureBase::TextureState::Destroyed,
+                    "Source texture %s is destroyed.", source->texture);
+
+    DAWN_TRY_CONTEXT(ValidateImageCopyTexture(device, *source, *copySize),
+                     "validating the ImageCopyTexture for the source");
+
+    DAWN_TRY_CONTEXT(ValidateTextureCopyRange(device, *source, *copySize),
+                     "validating that the copy fits in the source");
+
+    DAWN_TRY(ValidateTextureToTextureCopyCommonRestrictions(*source, *destination, *copySize));
+
+    DAWN_INVALID_IF(source->origin.z > 0, "Source has a non-zero z origin (%u).", source->origin.z);
+
+    DAWN_INVALID_IF(source->texture->GetSampleCount() > 1,
+                    "The source texture sample count (%u) is not 1. ",
+                    source->texture->GetSampleCount());
+
+    DAWN_INVALID_IF(
+        options->internalUsage && !device->HasFeature(Feature::DawnInternalUsages),
+        "The internalUsage is true while the dawn-internal-usages feature is not enabled.");
+    UsageValidationMode mode =
+        options->internalUsage ? UsageValidationMode::Internal : UsageValidationMode::Default;
+    DAWN_TRY(ValidateCanUseAs(source->texture, wgpu::TextureUsage::CopySrc, mode));
+    DAWN_TRY(ValidateCanUseAs(source->texture, wgpu::TextureUsage::TextureBinding, mode));
+
+    DAWN_TRY(ValidateCopyTextureSourceFormat(source->texture->GetFormat().format));
+
+    DAWN_TRY(ValidateCopyForBrowserCommonRestrictions(device, *destination, *copySize, *options));
+    return {};
+}
+
+MaybeError ValidateCopyExternalTextureForBrowser(DeviceBase* device,
+                                                 const ImageCopyExternalTexture* source,
+                                                 const ImageCopyTexture* destination,
+                                                 const Extent3D* copySize,
+                                                 const CopyTextureForBrowserOptions* options) {
+    DAWN_TRY(device->ValidateObject(source->externalTexture));
+
+    DAWN_TRY(source->externalTexture->ValidateCanUseInSubmitNow());
+
+    const Extent2D& sourceVisibleRect = source->externalTexture->GetVisibleRect();
+
+    // All texture dimensions are in uint32_t so by doing checks in uint64_t we avoid
+    // overflows.
+    DAWN_INVALID_IF(
+        static_cast<uint64_t>(source->origin.x) + static_cast<uint64_t>(copySize->width) >
+                static_cast<uint64_t>(sourceVisibleRect.width) ||
+            static_cast<uint64_t>(source->origin.y) + static_cast<uint64_t>(copySize->height) >
+                static_cast<uint64_t>(sourceVisibleRect.height) ||
+            static_cast<uint64_t>(source->origin.z) > 0,
+        "Texture copy range (origin: %s, copySize: %s) touches outside of %s visible size (%s).",
+        &source->origin, copySize, source->externalTexture, &sourceVisibleRect);
+
+    DAWN_INVALID_IF(source->origin.z > 0, "Source has a non-zero z origin (%u).", source->origin.z);
+
+    DAWN_INVALID_IF(
+        options->internalUsage && !device->HasFeature(Feature::DawnInternalUsages),
+        "The internalUsage is true while the dawn-internal-usages feature is not enabled.");
+
+    DAWN_TRY(ValidateCopyForBrowserCommonRestrictions(device, *destination, *copySize, *options));
+
+    return {};
+}
+
+MaybeError DoCopyExternalTextureForBrowser(DeviceBase* device,
+                                           const ImageCopyExternalTexture* source,
+                                           const ImageCopyTexture* destination,
+                                           const Extent3D* copySize,
+                                           const CopyTextureForBrowserOptions* options) {
+    TextureInfo info;
+    info.origin = source->origin;
+    const Extent2D& visibleRect = source->externalTexture->GetVisibleRect();
+    info.size = {visibleRect.width, visibleRect.height, 1};
+
+    RenderPipelineBase* pipeline;
+    DAWN_TRY_ASSIGN(pipeline, GetOrCreateCopyExternalTextureForBrowserPipeline(
+                                  device, destination->texture->GetFormat().format));
+    return DoCopyForBrowser<ExternalTextureBase>(device, &info, source->externalTexture,
+                                                 destination, copySize, options, pipeline);
+}
+
+MaybeError DoCopyTextureForBrowser(DeviceBase* device,
+                                   const ImageCopyTexture* source,
+                                   const ImageCopyTexture* destination,
+                                   const Extent3D* copySize,
+                                   const CopyTextureForBrowserOptions* options) {
+    TextureInfo info;
+    info.origin = source->origin;
+    info.size = source->texture->GetSize();
+
+    Ref<TextureViewBase> srcTextureView = nullptr;
+    TextureViewDescriptor srcTextureViewDesc = {};
+    srcTextureViewDesc.dimension = wgpu::TextureViewDimension::e2D;
+    srcTextureViewDesc.baseMipLevel = source->mipLevel;
+    srcTextureViewDesc.mipLevelCount = 1;
+    srcTextureViewDesc.arrayLayerCount = 1;
+    DAWN_TRY_ASSIGN(srcTextureView,
+                    device->CreateTextureView(source->texture, &srcTextureViewDesc));
+
+    RenderPipelineBase* pipeline;
+    DAWN_TRY_ASSIGN(pipeline, GetOrCreateCopyTextureForBrowserPipeline(
+                                  device, destination->texture->GetFormat().format));
+
+    return DoCopyForBrowser<TextureViewBase>(device, &info, srcTextureView.Get(), destination,
+                                             copySize, options, pipeline);
+}
 }  // namespace dawn::native
diff --git a/src/dawn/native/CopyTextureForBrowserHelper.h b/src/dawn/native/CopyTextureForBrowserHelper.h
index 0e427ba..0d496bb 100644
--- a/src/dawn/native/CopyTextureForBrowserHelper.h
+++ b/src/dawn/native/CopyTextureForBrowserHelper.h
@@ -30,12 +30,23 @@
                                          const Extent3D* copySize,
                                          const CopyTextureForBrowserOptions* options);
 
+MaybeError ValidateCopyExternalTextureForBrowser(DeviceBase* device,
+                                                 const ImageCopyExternalTexture* source,
+                                                 const ImageCopyTexture* destination,
+                                                 const Extent3D* copySize,
+                                                 const CopyTextureForBrowserOptions* options);
+
 MaybeError DoCopyTextureForBrowser(DeviceBase* device,
                                    const ImageCopyTexture* source,
                                    const ImageCopyTexture* destination,
                                    const Extent3D* copySize,
                                    const CopyTextureForBrowserOptions* options);
 
+MaybeError DoCopyExternalTextureForBrowser(DeviceBase* device,
+                                           const ImageCopyExternalTexture* source,
+                                           const ImageCopyTexture* destination,
+                                           const Extent3D* copySize,
+                                           const CopyTextureForBrowserOptions* options);
 }  // namespace dawn::native
 
 #endif  // SRC_DAWN_NATIVE_COPYTEXTUREFORBROWSERHELPER_H_
diff --git a/src/dawn/native/InternalPipelineStore.h b/src/dawn/native/InternalPipelineStore.h
index b8386d5..6234ec0 100644
--- a/src/dawn/native/InternalPipelineStore.h
+++ b/src/dawn/native/InternalPipelineStore.h
@@ -35,8 +35,10 @@
     ~InternalPipelineStore();
 
     std::unordered_map<wgpu::TextureFormat, Ref<RenderPipelineBase>> copyTextureForBrowserPipelines;
+    std::unordered_map<wgpu::TextureFormat, Ref<RenderPipelineBase>>
+        copyExternalTextureForBrowserPipelines;
 
-    Ref<ShaderModuleBase> copyTextureForBrowser;
+    Ref<ShaderModuleBase> copyForBrowser;
 
     Ref<ComputePipelineBase> timestampComputePipeline;
     Ref<ShaderModuleBase> timestampCS;
diff --git a/src/dawn/native/Queue.cpp b/src/dawn/native/Queue.cpp
index 9881d65..f8dd673 100644
--- a/src/dawn/native/Queue.cpp
+++ b/src/dawn/native/Queue.cpp
@@ -370,6 +370,14 @@
         CopyTextureForBrowserInternal(source, destination, copySize, options));
 }
 
+void QueueBase::APICopyExternalTextureForBrowser(const ImageCopyExternalTexture* source,
+                                                 const ImageCopyTexture* destination,
+                                                 const Extent3D* copySize,
+                                                 const CopyTextureForBrowserOptions* options) {
+    GetDevice()->ConsumedError(
+        CopyExternalTextureForBrowserInternal(source, destination, copySize, options));
+}
+
 MaybeError QueueBase::CopyTextureForBrowserInternal(const ImageCopyTexture* source,
                                                     const ImageCopyTexture* destination,
                                                     const Extent3D* copySize,
@@ -384,6 +392,21 @@
     return DoCopyTextureForBrowser(GetDevice(), source, destination, copySize, options);
 }
 
+MaybeError QueueBase::CopyExternalTextureForBrowserInternal(
+    const ImageCopyExternalTexture* source,
+    const ImageCopyTexture* destination,
+    const Extent3D* copySize,
+    const CopyTextureForBrowserOptions* options) {
+    if (GetDevice()->IsValidationEnabled()) {
+        DAWN_TRY_CONTEXT(ValidateCopyExternalTextureForBrowser(GetDevice(), source, destination,
+                                                               copySize, options),
+                         "validating CopyExternalTextureForBrowser from %s to %s",
+                         source->externalTexture, destination->texture);
+    }
+
+    return DoCopyExternalTextureForBrowser(GetDevice(), source, destination, copySize, options);
+}
+
 MaybeError QueueBase::ValidateSubmit(uint32_t commandCount,
                                      CommandBufferBase* const* commands) const {
     TRACE_EVENT0(GetDevice()->GetPlatform(), Validation, "Queue::ValidateSubmit");
diff --git a/src/dawn/native/Queue.h b/src/dawn/native/Queue.h
index 5bfd9a0..8495987 100644
--- a/src/dawn/native/Queue.h
+++ b/src/dawn/native/Queue.h
@@ -58,6 +58,10 @@
                                   const ImageCopyTexture* destination,
                                   const Extent3D* copySize,
                                   const CopyTextureForBrowserOptions* options);
+    void APICopyExternalTextureForBrowser(const ImageCopyExternalTexture* source,
+                                          const ImageCopyTexture* destination,
+                                          const Extent3D* copySize,
+                                          const CopyTextureForBrowserOptions* options);
 
     MaybeError WriteBuffer(BufferBase* buffer,
                            uint64_t bufferOffset,
@@ -82,6 +86,10 @@
                                              const ImageCopyTexture* destination,
                                              const Extent3D* copySize,
                                              const CopyTextureForBrowserOptions* options);
+    MaybeError CopyExternalTextureForBrowserInternal(const ImageCopyExternalTexture* source,
+                                                     const ImageCopyTexture* destination,
+                                                     const Extent3D* copySize,
+                                                     const CopyTextureForBrowserOptions* options);
 
     virtual MaybeError SubmitImpl(uint32_t commandCount, CommandBufferBase* const* commands) = 0;
     virtual MaybeError WriteBufferImpl(BufferBase* buffer,
diff --git a/src/dawn/native/utils/WGPUHelpers.cpp b/src/dawn/native/utils/WGPUHelpers.cpp
index 2a7a5e0..ffa2980 100644
--- a/src/dawn/native/utils/WGPUHelpers.cpp
+++ b/src/dawn/native/utils/WGPUHelpers.cpp
@@ -26,6 +26,7 @@
 #include "dawn/native/BindGroupLayout.h"
 #include "dawn/native/Buffer.h"
 #include "dawn/native/Device.h"
+#include "dawn/native/ExternalTexture.h"
 #include "dawn/native/PipelineLayout.h"
 #include "dawn/native/Queue.h"
 #include "dawn/native/Sampler.h"
@@ -140,6 +141,12 @@
 BindingInitializationHelper::BindingInitializationHelper(uint32_t binding,
                                                          const Ref<TextureViewBase>& textureView)
     : binding(binding), textureView(textureView) {}
+BindingInitializationHelper::BindingInitializationHelper(
+    uint32_t binding,
+    const Ref<ExternalTextureBase>& externalTexture)
+    : binding(binding), externalTexture(externalTexture) {
+    externalBindingEntry.externalTexture = externalTexture.Get();
+}
 
 BindingInitializationHelper::BindingInitializationHelper(uint32_t binding,
                                                          const Ref<BufferBase>& buffer,
@@ -159,6 +166,10 @@
     result.offset = offset;
     result.size = size;
 
+    if (externalTexture != nullptr) {
+        result.nextInChain = &externalBindingEntry;
+    }
+
     return result;
 }
 
diff --git a/src/dawn/native/utils/WGPUHelpers.h b/src/dawn/native/utils/WGPUHelpers.h
index 45cfa46..036d00d 100644
--- a/src/dawn/native/utils/WGPUHelpers.h
+++ b/src/dawn/native/utils/WGPUHelpers.h
@@ -95,6 +95,7 @@
 struct BindingInitializationHelper {
     BindingInitializationHelper(uint32_t binding, const Ref<SamplerBase>& sampler);
     BindingInitializationHelper(uint32_t binding, const Ref<TextureViewBase>& textureView);
+    BindingInitializationHelper(uint32_t binding, const Ref<ExternalTextureBase>& externalTexture);
     BindingInitializationHelper(uint32_t binding,
                                 const Ref<BufferBase>& buffer,
                                 uint64_t offset = 0,
@@ -107,6 +108,8 @@
     Ref<SamplerBase> sampler;
     Ref<TextureViewBase> textureView;
     Ref<BufferBase> buffer;
+    Ref<ExternalTextureBase> externalTexture;
+    ExternalTextureBindingEntry externalBindingEntry;
     uint64_t offset = 0;
     uint64_t size = 0;
 };
diff --git a/src/dawn/native/webgpu_absl_format.cpp b/src/dawn/native/webgpu_absl_format.cpp
index 8eddf46..a33ab42 100644
--- a/src/dawn/native/webgpu_absl_format.cpp
+++ b/src/dawn/native/webgpu_absl_format.cpp
@@ -46,6 +46,18 @@
 }
 
 absl::FormatConvertResult<absl::FormatConversionCharSet::kString> AbslFormatConvert(
+    const Extent2D* value,
+    const absl::FormatConversionSpec& spec,
+    absl::FormatSink* s) {
+    if (value == nullptr) {
+        s->Append("[null]");
+        return {true};
+    }
+    s->Append(absl::StrFormat("[Extent2D width:%u, height:%u]", value->width, value->height));
+    return {true};
+}
+
+absl::FormatConvertResult<absl::FormatConversionCharSet::kString> AbslFormatConvert(
     const Extent3D* value,
     const absl::FormatConversionSpec& spec,
     absl::FormatSink* s) {
diff --git a/src/dawn/native/webgpu_absl_format.h b/src/dawn/native/webgpu_absl_format.h
index 4c0c667..23964ad 100644
--- a/src/dawn/native/webgpu_absl_format.h
+++ b/src/dawn/native/webgpu_absl_format.h
@@ -29,6 +29,12 @@
 absl::FormatConvertResult<absl::FormatConversionCharSet::kString>
 AbslFormatConvert(const Color* value, const absl::FormatConversionSpec& spec, absl::FormatSink* s);
 
+struct Extent2D;
+absl::FormatConvertResult<absl::FormatConversionCharSet::kString> AbslFormatConvert(
+    const Extent2D* value,
+    const absl::FormatConversionSpec& spec,
+    absl::FormatSink* s);
+
 struct Extent3D;
 absl::FormatConvertResult<absl::FormatConversionCharSet::kString> AbslFormatConvert(
     const Extent3D* value,
diff --git a/src/dawn/tests/BUILD.gn b/src/dawn/tests/BUILD.gn
index 6f5ba74..c1e0137 100644
--- a/src/dawn/tests/BUILD.gn
+++ b/src/dawn/tests/BUILD.gn
@@ -438,6 +438,7 @@
     "end2end/ComputeLayoutMemoryBufferTests.cpp",
     "end2end/ComputeSharedMemoryTests.cpp",
     "end2end/ComputeStorageBufferBarrierTests.cpp",
+    "end2end/CopyExternalTextureForBrowserTests.cpp",
     "end2end/CopyTests.cpp",
     "end2end/CopyTextureForBrowserTests.cpp",
     "end2end/CreatePipelineAsyncTests.cpp",
diff --git a/src/dawn/tests/end2end/CopyExternalTextureForBrowserTests.cpp b/src/dawn/tests/end2end/CopyExternalTextureForBrowserTests.cpp
new file mode 100644
index 0000000..549e712
--- /dev/null
+++ b/src/dawn/tests/end2end/CopyExternalTextureForBrowserTests.cpp
@@ -0,0 +1,207 @@
+// Copyright 2022 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// 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"
+
+namespace {
+
+wgpu::Texture Create2DTexture(wgpu::Device device,
+                              uint32_t width,
+                              uint32_t height,
+                              wgpu::TextureFormat format,
+                              wgpu::TextureUsage usage) {
+    wgpu::TextureDescriptor descriptor;
+    descriptor.dimension = wgpu::TextureDimension::e2D;
+    descriptor.size.width = width;
+    descriptor.size.height = height;
+    descriptor.size.depthOrArrayLayers = 1;
+    descriptor.sampleCount = 1;
+    descriptor.format = format;
+    descriptor.mipLevelCount = 1;
+    descriptor.usage = usage;
+    return device.CreateTexture(&descriptor);
+}
+
+static constexpr uint32_t kWidth = 4;
+static constexpr uint32_t kHeight = 4;
+
+std::array<std::array<utils::RGBA8, 4>, 4> kDefaultSourceRGBA = {
+    std::array<utils::RGBA8, 4>(
+        {utils::RGBA8::kBlack, utils::RGBA8::kBlack, utils::RGBA8::kRed, utils::RGBA8::kRed}),
+    std::array<utils::RGBA8, 4>(
+        {utils::RGBA8::kBlack, utils::RGBA8::kBlack, utils::RGBA8::kRed, utils::RGBA8::kRed}),
+    std::array<utils::RGBA8, 4>(
+        {utils::RGBA8::kGreen, utils::RGBA8::kGreen, utils::RGBA8::kBlue, utils::RGBA8::kBlue}),
+    std::array<utils::RGBA8, 4>(
+        {utils::RGBA8::kGreen, utils::RGBA8::kGreen, utils::RGBA8::kBlue, utils::RGBA8::kBlue})};
+
+template <typename Parent>
+class CopyExternalTextureForBrowserTests : public Parent {
+  protected:
+    wgpu::ExternalTexture CreateDefaultExternalTexture() {
+        // y plane
+        wgpu::TextureDescriptor externalTexturePlane0Desc = {};
+        externalTexturePlane0Desc.size = {kWidth, kHeight, 1};
+        externalTexturePlane0Desc.usage = wgpu::TextureUsage::TextureBinding |
+                                          wgpu::TextureUsage::CopyDst |
+                                          wgpu::TextureUsage::RenderAttachment;
+        externalTexturePlane0Desc.format = wgpu::TextureFormat::R8Unorm;
+        wgpu::Texture externalTexturePlane0 =
+            this->device.CreateTexture(&externalTexturePlane0Desc);
+
+        // The value Ref to ExternalTextureTest.cpp:
+        //  {0.0, .5, .5, utils::RGBA8::kBlack, 0.0f},
+        //  {0.2126, 0.4172, 1.0, utils::RGBA8::kRed, 1.0f},
+        //  {0.7152, 0.1402, 0.0175, utils::RGBA8::kGreen, 0.0f},
+        //  {0.0722, 1.0, 0.4937, utils::RGBA8::kBlue, 0.0f},
+        wgpu::ImageCopyTexture plane0 = {};
+        plane0.texture = externalTexturePlane0;
+        std::array<uint8_t, 16> yPlaneData = {0,   0,   54, 54, 0,   0,   54, 54,
+                                              182, 182, 18, 18, 182, 182, 18, 18};
+
+        wgpu::TextureDataLayout externalTexturePlane0DataLayout = {};
+        externalTexturePlane0DataLayout.bytesPerRow = 4;
+
+        this->queue.WriteTexture(&plane0, yPlaneData.data(), yPlaneData.size() * sizeof(float),
+                                 &externalTexturePlane0DataLayout, &externalTexturePlane0Desc.size);
+
+        // uv plane
+        wgpu::TextureDescriptor externalTexturePlane1Desc = {};
+        externalTexturePlane1Desc.size = {kWidth / 2, kHeight / 2, 1};
+        externalTexturePlane1Desc.usage = wgpu::TextureUsage::TextureBinding |
+                                          wgpu::TextureUsage::CopyDst |
+                                          wgpu::TextureUsage::RenderAttachment;
+        externalTexturePlane1Desc.format = wgpu::TextureFormat::RG8Unorm;
+        wgpu::Texture externalTexturePlane1 =
+            this->device.CreateTexture(&externalTexturePlane1Desc);
+
+        wgpu::ImageCopyTexture plane1 = {};
+        plane1.texture = externalTexturePlane1;
+        std::array<uint8_t, 8> uvPlaneData = {
+            128, 128, 106, 255, 36, 4, 255, 126,
+        };
+
+        wgpu::TextureDataLayout externalTexturePlane1DataLayout = {};
+        externalTexturePlane1DataLayout.bytesPerRow = 4;
+
+        this->queue.WriteTexture(&plane1, uvPlaneData.data(), uvPlaneData.size() * sizeof(float),
+                                 &externalTexturePlane1DataLayout, &externalTexturePlane1Desc.size);
+
+        // Create an ExternalTextureDescriptor from the texture views
+        wgpu::ExternalTextureDescriptor externalDesc;
+        utils::ColorSpaceConversionInfo info =
+            utils::GetYUVBT709ToRGBSRGBColorSpaceConversionInfo();
+        externalDesc.yuvToRgbConversionMatrix = info.yuvToRgbConversionMatrix.data();
+        externalDesc.gamutConversionMatrix = info.gamutConversionMatrix.data();
+        externalDesc.srcTransferFunctionParameters = info.srcTransferFunctionParameters.data();
+        externalDesc.dstTransferFunctionParameters = info.dstTransferFunctionParameters.data();
+
+        externalDesc.plane0 = externalTexturePlane0.CreateView();
+        externalDesc.plane1 = externalTexturePlane1.CreateView();
+
+        externalDesc.visibleRect = {kWidth, kHeight};
+
+        // Import the external texture
+        return this->device.CreateExternalTexture(&externalDesc);
+    }
+
+    std::vector<utils::RGBA8> GetDefaultExpectedData(bool flipY,
+                                                     wgpu::Origin3D origin,
+                                                     wgpu::Extent3D rect) {
+        std::vector<utils::RGBA8> expected;
+        for (uint32_t row = origin.y; row < origin.y + rect.height; ++row) {
+            for (uint32_t col = origin.x; col < origin.x + rect.width; ++col) {
+                if (flipY) {
+                    uint32_t flippedRow = kHeight - row - 1;
+                    expected.push_back(kDefaultSourceRGBA[flippedRow][col]);
+                } else {
+                    expected.push_back(kDefaultSourceRGBA[row][col]);
+                }
+            }
+        }
+
+        return expected;
+    }
+};
+
+using FlipY = bool;
+using SrcOrigin = wgpu::Origin3D;
+using DstOrigin = wgpu::Origin3D;
+
+std::ostream& operator<<(std::ostream& o, wgpu::Origin3D origin) {
+    o << origin.x << ", " << origin.y << ", " << origin.z;
+    return o;
+}
+
+DAWN_TEST_PARAM_STRUCT(CopyTestParams, SrcOrigin, DstOrigin, FlipY);
+
+class CopyExternalTextureForBrowserTests_Basic
+    : public CopyExternalTextureForBrowserTests<DawnTestWithParams<CopyTestParams>> {
+  protected:
+    void DoBasicCopyTest(const wgpu::Origin3D& srcOrigin,
+                         const wgpu::Origin3D& dstOrigin,
+                         const wgpu::Extent3D& copySize,
+                         const wgpu::CopyTextureForBrowserOptions options = {}) {
+        wgpu::ExternalTexture externalTexture = CreateDefaultExternalTexture();
+        wgpu::ImageCopyExternalTexture srcImageCopyExternalTexture;
+        srcImageCopyExternalTexture.externalTexture = externalTexture;
+        srcImageCopyExternalTexture.origin = srcOrigin;
+
+        wgpu::Texture dstTexture =
+            Create2DTexture(device, kWidth, kHeight, wgpu::TextureFormat::RGBA8Unorm,
+                            wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc |
+                                wgpu::TextureUsage::CopyDst);
+        wgpu::ImageCopyTexture dstImageCopyTexture =
+            utils::CreateImageCopyTexture(dstTexture, 0, dstOrigin);
+
+        queue.CopyExternalTextureForBrowser(&srcImageCopyExternalTexture, &dstImageCopyTexture,
+                                            &copySize, &options);
+        std::vector<utils::RGBA8> expected =
+            GetDefaultExpectedData(options.flipY, srcOrigin, copySize);
+
+        EXPECT_TEXTURE_EQ(expected.data(), dstTexture, dstOrigin, copySize);
+    }
+};
+}  // anonymous namespace
+
+TEST_P(CopyExternalTextureForBrowserTests_Basic, FullCopy) {
+    DAWN_SUPPRESS_TEST_IF(IsOpenGLES());
+    DAWN_SUPPRESS_TEST_IF(IsOpenGL() && IsLinux());
+
+    wgpu::CopyTextureForBrowserOptions options = {};
+    options.flipY = GetParam().mFlipY;
+
+    wgpu::Origin3D srcOrigin = GetParam().mSrcOrigin;
+    wgpu::Origin3D dstOrigin = GetParam().mDstOrigin;
+
+    wgpu::Extent3D copySize = {kWidth, kHeight};
+
+    if (srcOrigin.x != 0 || srcOrigin.y != 0 || dstOrigin.x != 0 || dstOrigin.y != 0) {
+        copySize.width = kWidth / 2;
+        copySize.height = kHeight / 2;
+    }
+
+    DoBasicCopyTest(srcOrigin, dstOrigin, copySize, options);
+}
+
+DAWN_INSTANTIATE_TEST_P(CopyExternalTextureForBrowserTests_Basic,
+                        {D3D12Backend(), MetalBackend(), OpenGLBackend(), OpenGLESBackend(),
+                         VulkanBackend()},
+                        std::vector<wgpu::Origin3D>({{0, 0}, {2, 0}, {0, 2}, {2, 2}}),
+                        std::vector<wgpu::Origin3D>({{0, 0}, {2, 0}, {0, 2}, {2, 2}}),
+                        std::vector<bool>({false, true}));
diff --git a/src/dawn/tests/end2end/ExternalTextureTests.cpp b/src/dawn/tests/end2end/ExternalTextureTests.cpp
index 5511437..065836b 100644
--- a/src/dawn/tests/end2end/ExternalTextureTests.cpp
+++ b/src/dawn/tests/end2end/ExternalTextureTests.cpp
@@ -39,10 +39,10 @@
   protected:
     wgpu::ExternalTextureDescriptor CreateDefaultExternalTextureDescriptor() {
         wgpu::ExternalTextureDescriptor desc;
-        desc.yuvToRgbConversionMatrix = kYuvToRGBMatrixBT709.data();
-        desc.gamutConversionMatrix = kGamutConversionMatrixBT709ToSrgb.data();
-        desc.srcTransferFunctionParameters = kGammaDecodeBT709.data();
-        desc.dstTransferFunctionParameters = kGammaEncodeSrgb.data();
+        desc.yuvToRgbConversionMatrix = yuvBT709ToRGBSRGB.yuvToRgbConversionMatrix.data();
+        desc.gamutConversionMatrix = yuvBT709ToRGBSRGB.gamutConversionMatrix.data();
+        desc.srcTransferFunctionParameters = yuvBT709ToRGBSRGB.srcTransferFunctionParameters.data();
+        desc.dstTransferFunctionParameters = yuvBT709ToRGBSRGB.dstTransferFunctionParameters.data();
 
         return desc;
     }
@@ -51,14 +51,8 @@
     static constexpr uint32_t kHeight = 4;
     static constexpr wgpu::TextureFormat kFormat = wgpu::TextureFormat::RGBA8Unorm;
     static constexpr wgpu::TextureUsage kSampledUsage = wgpu::TextureUsage::TextureBinding;
-    std::array<float, 12> kYuvToRGBMatrixBT709 = {1.164384f, 0.0f,       1.792741f,  -0.972945f,
-                                                  1.164384f, -0.213249f, -0.532909f, 0.301483f,
-                                                  1.164384f, 2.112402f,  0.0f,       -1.133402f};
-    std::array<float, 9> kGamutConversionMatrixBT709ToSrgb = {1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-                                                              0.0f, 0.0f, 0.0f, 1.0f};
-    std::array<float, 7> kGammaDecodeBT709 = {2.2, 1.0 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081,
-                                              0.0, 0.0};
-    std::array<float, 7> kGammaEncodeSrgb = {1 / 2.4, 1.137119, 0.0, 12.92, 0.0031308, -0.055, 0.0};
+    utils::ColorSpaceConversionInfo yuvBT709ToRGBSRGB =
+        utils::GetYUVBT709ToRGBSRGBColorSpaceConversionInfo();
 };
 }  // anonymous namespace
 
@@ -99,7 +93,7 @@
 
         @fragment fn main(@builtin(position) FragCoord : vec4<f32>)
                                  -> @location(0) vec4<f32> {
-            return textureSampleLevel(t, s, FragCoord.xy / vec2<f32>(4.0, 4.0));
+            return textureSampleBaseClampToEdge(t, s, FragCoord.xy / vec2<f32>(4.0, 4.0));
         })");
 
     wgpu::Texture sampledTexture =
@@ -184,7 +178,7 @@
 
         @fragment fn main(@builtin(position) FragCoord : vec4<f32>)
                                  -> @location(0) vec4<f32> {
-            return textureSampleLevel(t, s, FragCoord.xy / vec2<f32>(4.0, 4.0));
+            return textureSampleBaseClampToEdge(t, s, FragCoord.xy / vec2<f32>(4.0, 4.0));
         })");
 
     wgpu::Texture sampledTexturePlane0 =
diff --git a/src/dawn/tests/unittests/validation/CopyTextureForBrowserTests.cpp b/src/dawn/tests/unittests/validation/CopyTextureForBrowserTests.cpp
index 06c1b3d..96fc533 100644
--- a/src/dawn/tests/unittests/validation/CopyTextureForBrowserTests.cpp
+++ b/src/dawn/tests/unittests/validation/CopyTextureForBrowserTests.cpp
@@ -20,31 +20,59 @@
 #include "dawn/utils/TextureUtils.h"
 #include "dawn/utils/WGPUHelpers.h"
 
+namespace {
+wgpu::Texture Create2DTexture(
+    wgpu::Device device,
+    uint32_t width,
+    uint32_t height,
+    uint32_t mipLevelCount,
+    uint32_t arrayLayerCount,
+    wgpu::TextureFormat format,
+    wgpu::TextureUsage usage,
+    uint32_t sampleCount = 1,
+    const wgpu::DawnTextureInternalUsageDescriptor* internalDesc = nullptr) {
+    wgpu::TextureDescriptor descriptor;
+    descriptor.nextInChain = internalDesc;
+    descriptor.dimension = wgpu::TextureDimension::e2D;
+    descriptor.size.width = width;
+    descriptor.size.height = height;
+    descriptor.size.depthOrArrayLayers = arrayLayerCount;
+    descriptor.sampleCount = sampleCount;
+    descriptor.format = format;
+    descriptor.mipLevelCount = mipLevelCount;
+    descriptor.usage = usage;
+    wgpu::Texture tex = device.CreateTexture(&descriptor);
+    return tex;
+}
+
+wgpu::ExternalTexture CreateExternalTexture(wgpu::Device device, uint32_t width, uint32_t height) {
+    wgpu::Texture texture =
+        Create2DTexture(device, width, height, 1, 1, wgpu::TextureFormat::RGBA8Unorm,
+                        wgpu::TextureUsage::TextureBinding);
+
+    // Create a texture view for the external texture
+    wgpu::TextureView view = texture.CreateView();
+
+    // Create an ExternalTextureDescriptor from the texture view
+    wgpu::ExternalTextureDescriptor externalDesc;
+    utils::ColorSpaceConversionInfo info = utils::GetYUVBT709ToRGBSRGBColorSpaceConversionInfo();
+    externalDesc.yuvToRgbConversionMatrix = info.yuvToRgbConversionMatrix.data();
+    externalDesc.gamutConversionMatrix = info.gamutConversionMatrix.data();
+    externalDesc.srcTransferFunctionParameters = info.srcTransferFunctionParameters.data();
+    externalDesc.dstTransferFunctionParameters = info.dstTransferFunctionParameters.data();
+
+    externalDesc.plane0 = view;
+
+    externalDesc.visibleRect = {width, height};
+
+    // Import the external texture
+    return device.CreateExternalTexture(&externalDesc);
+}
+
+}  // namespace
+
 class CopyTextureForBrowserTest : public ValidationTest {
   protected:
-    wgpu::Texture Create2DTexture(
-        uint32_t width,
-        uint32_t height,
-        uint32_t mipLevelCount,
-        uint32_t arrayLayerCount,
-        wgpu::TextureFormat format,
-        wgpu::TextureUsage usage,
-        uint32_t sampleCount = 1,
-        const wgpu::DawnTextureInternalUsageDescriptor* internalDesc = nullptr) {
-        wgpu::TextureDescriptor descriptor;
-        descriptor.nextInChain = internalDesc;
-        descriptor.dimension = wgpu::TextureDimension::e2D;
-        descriptor.size.width = width;
-        descriptor.size.height = height;
-        descriptor.size.depthOrArrayLayers = arrayLayerCount;
-        descriptor.sampleCount = sampleCount;
-        descriptor.format = format;
-        descriptor.mipLevelCount = mipLevelCount;
-        descriptor.usage = usage;
-        wgpu::Texture tex = device.CreateTexture(&descriptor);
-        return tex;
-    }
-
     void TestCopyTextureForBrowser(utils::Expectation expectation,
                                    wgpu::Texture srcTexture,
                                    uint32_t srcLevel,
@@ -81,13 +109,52 @@
     }
 };
 
+class CopyExternalTextureForBrowserTest : public ValidationTest {
+  protected:
+    void TestCopyExternalTextureForBrowser(utils::Expectation expectation,
+                                           wgpu::ExternalTexture srcExternalTexture,
+                                           wgpu::Origin3D srcOrigin,
+                                           wgpu::Texture dstTexture,
+                                           uint32_t dstLevel,
+                                           wgpu::Origin3D dstOrigin,
+                                           wgpu::Extent3D extent3D,
+                                           wgpu::TextureAspect aspect = wgpu::TextureAspect::All,
+                                           wgpu::CopyTextureForBrowserOptions options = {}) {
+        wgpu::ImageCopyExternalTexture srcImageCopyExternalTexture;
+        srcImageCopyExternalTexture.externalTexture = srcExternalTexture;
+        srcImageCopyExternalTexture.origin = srcOrigin;
+
+        wgpu::ImageCopyTexture dstImageCopyTexture =
+            utils::CreateImageCopyTexture(dstTexture, dstLevel, dstOrigin, aspect);
+
+        if (expectation == utils::Expectation::Success) {
+            device.GetQueue().CopyExternalTextureForBrowser(
+                &srcImageCopyExternalTexture, &dstImageCopyTexture, &extent3D, &options);
+        } else {
+            ASSERT_DEVICE_ERROR(device.GetQueue().CopyExternalTextureForBrowser(
+                &srcImageCopyExternalTexture, &dstImageCopyTexture, &extent3D, &options));
+        }
+    }
+};
+
+class CopyExternalTextureForBrowserInternalUsageTest : public CopyExternalTextureForBrowserTest {
+  protected:
+    WGPUDevice CreateTestDevice(dawn::native::Adapter dawnAdapter) override {
+        wgpu::DeviceDescriptor descriptor;
+        wgpu::FeatureName feature = wgpu::FeatureName::DawnInternalUsages;
+        descriptor.requiredFeatures = &feature;
+        descriptor.requiredFeaturesCount = 1;
+        return dawnAdapter.CreateDevice(&descriptor);
+    }
+};
+
 // Tests should be Success
 TEST_F(CopyTextureForBrowserTest, Success) {
     wgpu::Texture source =
-        Create2DTexture(16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
+        Create2DTexture(device, 16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
                         wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::TextureBinding);
     wgpu::Texture destination =
-        Create2DTexture(16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
+        Create2DTexture(device, 16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
                         wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment);
 
     // Different copies, including some that touch the OOB condition
@@ -140,19 +207,20 @@
 // Test source or destination texture has wrong usages
 TEST_F(CopyTextureForBrowserTest, IncorrectUsage) {
     wgpu::Texture validSource =
-        Create2DTexture(16, 16, 5, 1, wgpu::TextureFormat::RGBA8Unorm,
+        Create2DTexture(device, 16, 16, 5, 1, wgpu::TextureFormat::RGBA8Unorm,
                         wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::TextureBinding);
     wgpu::Texture validDestination =
-        Create2DTexture(16, 16, 5, 1, wgpu::TextureFormat::RGBA8Unorm,
+        Create2DTexture(device, 16, 16, 5, 1, wgpu::TextureFormat::RGBA8Unorm,
                         wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment);
-    wgpu::Texture noSampledUsageSource =
-        Create2DTexture(16, 16, 5, 1, wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureUsage::CopySrc);
-    wgpu::Texture noRenderAttachmentUsageDestination =
-        Create2DTexture(16, 16, 5, 2, wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureUsage::CopyDst);
+    wgpu::Texture noSampledUsageSource = Create2DTexture(
+        device, 16, 16, 5, 1, wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureUsage::CopySrc);
+    wgpu::Texture noRenderAttachmentUsageDestination = Create2DTexture(
+        device, 16, 16, 5, 2, wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureUsage::CopyDst);
     wgpu::Texture noCopySrcUsageSource = Create2DTexture(
-        16, 16, 5, 1, wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureUsage::TextureBinding);
-    wgpu::Texture noCopyDstUsageSource = Create2DTexture(
-        16, 16, 5, 2, wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureUsage::RenderAttachment);
+        device, 16, 16, 5, 1, wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureUsage::TextureBinding);
+    wgpu::Texture noCopyDstUsageSource =
+        Create2DTexture(device, 16, 16, 5, 2, wgpu::TextureFormat::RGBA8Unorm,
+                        wgpu::TextureUsage::RenderAttachment);
 
     // Incorrect source usage causes failure : lack |Sampled| usage
     TestCopyTextureForBrowser(utils::Expectation::Failure, noSampledUsageSource, 0, {0, 0, 0},
@@ -178,10 +246,10 @@
     // Valid src and dst textures.
     {
         wgpu::Texture source =
-            Create2DTexture(16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
+            Create2DTexture(device, 16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
                             wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::TextureBinding);
         wgpu::Texture destination =
-            Create2DTexture(16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
+            Create2DTexture(device, 16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
                             wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment);
         TestCopyTextureForBrowser(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, 0,
                                   {0, 0, 0}, {4, 4, 1}, wgpu::TextureAspect::All, options);
@@ -194,10 +262,10 @@
     // Destroyed src texture.
     {
         wgpu::Texture source =
-            Create2DTexture(16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
+            Create2DTexture(device, 16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
                             wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::TextureBinding);
         wgpu::Texture destination =
-            Create2DTexture(16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
+            Create2DTexture(device, 16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
                             wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment);
         source.Destroy();
         TestCopyTextureForBrowser(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0,
@@ -211,10 +279,10 @@
     // Destroyed dst texture.
     {
         wgpu::Texture source =
-            Create2DTexture(16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
+            Create2DTexture(device, 16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
                             wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::TextureBinding);
         wgpu::Texture destination =
-            Create2DTexture(16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
+            Create2DTexture(device, 16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
                             wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment);
 
         destination.Destroy();
@@ -230,10 +298,10 @@
 // Test non-zero value origin in source and OOB copy rects.
 TEST_F(CopyTextureForBrowserTest, OutOfBounds) {
     wgpu::Texture source =
-        Create2DTexture(16, 16, 5, 1, wgpu::TextureFormat::RGBA8Unorm,
+        Create2DTexture(device, 16, 16, 5, 1, wgpu::TextureFormat::RGBA8Unorm,
                         wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::TextureBinding);
     wgpu::Texture destination =
-        Create2DTexture(16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
+        Create2DTexture(device, 16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
                         wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment);
 
     // OOB on source
@@ -290,10 +358,10 @@
 // Test destination texture has format that not supported by CopyTextureForBrowser().
 TEST_F(CopyTextureForBrowserTest, InvalidDstFormat) {
     wgpu::Texture source =
-        Create2DTexture(16, 16, 5, 1, wgpu::TextureFormat::RGBA8Unorm,
+        Create2DTexture(device, 16, 16, 5, 1, wgpu::TextureFormat::RGBA8Unorm,
                         wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::TextureBinding);
     wgpu::Texture destination =
-        Create2DTexture(16, 16, 5, 2, wgpu::TextureFormat::RG8Uint,
+        Create2DTexture(device, 16, 16, 5, 2, wgpu::TextureFormat::RG8Uint,
                         wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment);
 
     // Not supported dst texture format.
@@ -304,18 +372,18 @@
 // Test source or destination texture are multisampled.
 TEST_F(CopyTextureForBrowserTest, InvalidSampleCount) {
     wgpu::Texture sourceMultiSampled1x =
-        Create2DTexture(16, 16, 1, 1, wgpu::TextureFormat::RGBA8Unorm,
+        Create2DTexture(device, 16, 16, 1, 1, wgpu::TextureFormat::RGBA8Unorm,
                         wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::TextureBinding, 1);
     wgpu::Texture destinationMultiSampled1x =
-        Create2DTexture(16, 16, 1, 1, wgpu::TextureFormat::RGBA8Unorm,
+        Create2DTexture(device, 16, 16, 1, 1, wgpu::TextureFormat::RGBA8Unorm,
                         wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment, 1);
     wgpu::Texture sourceMultiSampled4x =
-        Create2DTexture(16, 16, 1, 1, wgpu::TextureFormat::RGBA8Unorm,
+        Create2DTexture(device, 16, 16, 1, 1, wgpu::TextureFormat::RGBA8Unorm,
                         wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::TextureBinding |
                             wgpu::TextureUsage::RenderAttachment,
                         4);
     wgpu::Texture destinationMultiSampled4x =
-        Create2DTexture(16, 16, 1, 1, wgpu::TextureFormat::RGBA8Unorm,
+        Create2DTexture(device, 16, 16, 1, 1, wgpu::TextureFormat::RGBA8Unorm,
                         wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment, 4);
 
     // An empty copy with dst texture sample count > 1 failure.
@@ -330,10 +398,10 @@
 // Test color space conversion related attributes in CopyTextureForBrowserOptions.
 TEST_F(CopyTextureForBrowserTest, ColorSpaceConversion_ColorSpace) {
     wgpu::Texture source =
-        Create2DTexture(16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
+        Create2DTexture(device, 16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
                         wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::TextureBinding);
     wgpu::Texture destination =
-        Create2DTexture(16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
+        Create2DTexture(device, 16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
                         wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment);
 
     wgpu::CopyTextureForBrowserOptions options = {};
@@ -413,10 +481,10 @@
 // Test option.srcAlphaMode/dstAlphaMode
 TEST_F(CopyTextureForBrowserTest, ColorSpaceConversion_TextureAlphaState) {
     wgpu::Texture source =
-        Create2DTexture(16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
+        Create2DTexture(device, 16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
                         wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::TextureBinding);
     wgpu::Texture destination =
-        Create2DTexture(16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
+        Create2DTexture(device, 16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
                         wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment);
 
     wgpu::CopyTextureForBrowserOptions options = {};
@@ -449,6 +517,342 @@
     }
 }
 
+// Tests should be Success
+TEST_F(CopyExternalTextureForBrowserTest, Success) {
+    wgpu::ExternalTexture source = CreateExternalTexture(device, 16, 16);
+    wgpu::Texture destination =
+        Create2DTexture(device, 16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
+                        wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment);
+
+    // Different copies, including some that touch the OOB condition
+    {
+        // Copy a region along top left boundary
+        TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0},
+                                          destination, 0, {0, 0, 0}, {4, 4, 1});
+
+        // Copy entire texture
+        TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0},
+                                          destination, 0, {0, 0, 0}, {16, 16, 1});
+
+        // Copy a region along bottom right boundary
+        TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {8, 8, 0},
+                                          destination, 0, {8, 8, 0}, {8, 8, 1});
+
+        // Copy region into mip
+        TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0},
+                                          destination, 2, {0, 0, 0}, {4, 4, 1});
+
+        // Copy between slices
+        TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0},
+                                          destination, 0, {0, 0, 1}, {16, 16, 1});
+    }
+
+    // Empty copies are valid
+    {
+        // An empty copy
+        TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0},
+                                          destination, 0, {0, 0, 0}, {0, 0, 1});
+
+        // An empty copy with depth = 0
+        TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0},
+                                          destination, 0, {0, 0, 0}, {0, 0, 0});
+
+        // An empty copy touching the side of the source texture
+        TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0},
+                                          destination, 0, {16, 16, 0}, {0, 0, 1});
+
+        // An empty copy touching the side of the destination texture
+        TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0},
+                                          destination, 0, {16, 16, 0}, {0, 0, 1});
+    }
+}
+
+// Test destination texture has wrong usages
+TEST_F(CopyExternalTextureForBrowserTest, IncorrectUsage) {
+    wgpu::ExternalTexture validSource = CreateExternalTexture(device, 16, 16);
+
+    wgpu::Texture validDestination =
+        Create2DTexture(device, 16, 16, 5, 1, wgpu::TextureFormat::RGBA8Unorm,
+                        wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment);
+    wgpu::Texture noRenderAttachmentUsageDestination = Create2DTexture(
+        device, 16, 16, 5, 2, wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureUsage::CopyDst);
+    wgpu::Texture noCopyDstUsageSource =
+        Create2DTexture(device, 16, 16, 5, 2, wgpu::TextureFormat::RGBA8Unorm,
+                        wgpu::TextureUsage::RenderAttachment);
+
+    // Incorrect destination usage causes failure: lack |RenderAttachement| usage.
+    TestCopyExternalTextureForBrowser(utils::Expectation::Failure, validSource, {0, 0, 0},
+                                      noRenderAttachmentUsageDestination, 0, {0, 0, 0},
+                                      {16, 16, 1});
+
+    // Incorrect destination usage causes failure: lack |CopyDst| usage.
+    TestCopyExternalTextureForBrowser(utils::Expectation::Failure, validSource, {0, 0, 0},
+                                      noCopyDstUsageSource, 0, {0, 0, 0}, {16, 16, 1});
+}
+
+// Test source or destination texture is destroyed.
+TEST_F(CopyExternalTextureForBrowserTest, DestroyedTexture) {
+    wgpu::CopyTextureForBrowserOptions options = {};
+
+    // Valid src and dst textures.
+    {
+        wgpu::ExternalTexture source = CreateExternalTexture(device, 16, 16);
+        wgpu::Texture destination =
+            Create2DTexture(device, 16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
+                            wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment);
+        TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0},
+                                          destination, 0, {0, 0, 0}, {4, 4, 1},
+                                          wgpu::TextureAspect::All, options);
+
+        // Check noop copy
+        TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0},
+                                          destination, 0, {0, 0, 0}, {0, 0, 0},
+                                          wgpu::TextureAspect::All, options);
+    }
+
+    // Destroyed src texture.
+    {
+        wgpu::ExternalTexture source = CreateExternalTexture(device, 16, 16);
+        wgpu::Texture destination =
+            Create2DTexture(device, 16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
+                            wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment);
+        source.Destroy();
+        TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0},
+                                          destination, 0, {0, 0, 0}, {4, 4, 1},
+                                          wgpu::TextureAspect::All, options);
+
+        // Check noop copy
+        TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0},
+                                          destination, 0, {0, 0, 0}, {0, 0, 0},
+                                          wgpu::TextureAspect::All, options);
+    }
+
+    // Destroyed dst texture.
+    {
+        wgpu::ExternalTexture source = CreateExternalTexture(device, 16, 16);
+        wgpu::Texture destination =
+            Create2DTexture(device, 16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
+                            wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment);
+
+        destination.Destroy();
+        TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0},
+                                          destination, 0, {0, 0, 0}, {4, 4, 1},
+                                          wgpu::TextureAspect::All, options);
+
+        // Check noop copy
+        TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0},
+                                          destination, 0, {0, 0, 0}, {0, 0, 0},
+                                          wgpu::TextureAspect::All, options);
+    }
+}
+
+// Test non-zero value origin in source and OOB copy rects.
+TEST_F(CopyExternalTextureForBrowserTest, OutOfBounds) {
+    wgpu::ExternalTexture source = CreateExternalTexture(device, 16, 16);
+    wgpu::Texture destination =
+        Create2DTexture(device, 16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
+                        wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment);
+
+    // OOB on source
+    {
+        // x + width overflows
+        TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {1, 0, 0},
+                                          destination, 0, {0, 0, 0}, {16, 16, 1});
+
+        // y + height overflows
+        TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 1, 0},
+                                          destination, 0, {0, 0, 0}, {16, 16, 1});
+
+        // copy to multiple slices
+        TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0},
+                                          destination, 0, {0, 0, 2}, {16, 16, 2});
+
+        // copy origin z value is non-zero.
+        TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 1},
+                                          destination, 0, {0, 0, 2}, {16, 16, 1});
+    }
+
+    // OOB on destination
+    {
+        // x + width overflows
+        TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0},
+                                          destination, 0, {1, 0, 0}, {16, 16, 1});
+
+        // y + height overflows
+        TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0},
+                                          destination, 0, {0, 1, 0}, {16, 16, 1});
+
+        // non-zero mip overflows
+        TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0},
+                                          destination, 1, {0, 0, 0}, {9, 9, 1});
+
+        // arrayLayer + depth OOB
+        TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0},
+                                          destination, 0, {0, 0, 4}, {16, 16, 1});
+
+        // empty copy on non-existent mip fails
+        TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0},
+                                          destination, 6, {0, 0, 0}, {0, 0, 1});
+
+        // empty copy on non-existent slice fails
+        TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0},
+                                          destination, 0, {0, 0, 4}, {0, 0, 1});
+    }
+}
+
+// Test destination texture has format that not supported by CopyTextureForBrowser().
+TEST_F(CopyExternalTextureForBrowserTest, InvalidDstFormat) {
+    wgpu::ExternalTexture source = CreateExternalTexture(device, 16, 16);
+    wgpu::Texture destination =
+        Create2DTexture(device, 16, 16, 5, 2, wgpu::TextureFormat::RG8Uint,
+                        wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment);
+
+    // Not supported dst texture format.
+    TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, destination,
+                                      0, {0, 0, 0}, {0, 0, 1});
+}
+
+// Test destination texture are multisampled.
+TEST_F(CopyExternalTextureForBrowserTest, InvalidSampleCount) {
+    wgpu::ExternalTexture source = CreateExternalTexture(device, 16, 16);
+    wgpu::Texture destinationMultiSampled4x =
+        Create2DTexture(device, 16, 16, 1, 1, wgpu::TextureFormat::RGBA8Unorm,
+                        wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment, 4);
+
+    // An empty copy with dst texture sample count > 1 failure.
+    TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0},
+                                      destinationMultiSampled4x, 0, {0, 0, 0}, {0, 0, 1});
+}
+
+// Test color space conversion related attributes in CopyTextureForBrowserOptions.
+TEST_F(CopyExternalTextureForBrowserTest, ColorSpaceConversion_ColorSpace) {
+    wgpu::ExternalTexture source = CreateExternalTexture(device, 16, 16);
+    wgpu::Texture destination =
+        Create2DTexture(device, 16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
+                        wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment);
+
+    wgpu::CopyTextureForBrowserOptions options = {};
+    options.needsColorSpaceConversion = true;
+
+    // Valid cases
+    {
+        wgpu::CopyTextureForBrowserOptions validOptions = options;
+        std::array<float, 7> srcTransferFunctionParameters = {};
+        std::array<float, 7> dstTransferFunctionParameters = {};
+        std::array<float, 9> conversionMatrix = {};
+        validOptions.srcTransferFunctionParameters = srcTransferFunctionParameters.data();
+        validOptions.dstTransferFunctionParameters = dstTransferFunctionParameters.data();
+        validOptions.conversionMatrix = conversionMatrix.data();
+        TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0},
+                                          destination, 0, {0, 0, 0}, {4, 4, 1},
+                                          wgpu::TextureAspect::All, validOptions);
+
+        // if no color space conversion, no need to validate related attributes
+        wgpu::CopyTextureForBrowserOptions noColorSpaceConversion = options;
+        noColorSpaceConversion.needsColorSpaceConversion = false;
+        TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0},
+                                          destination, 0, {0, 0, 0}, {4, 4, 1},
+                                          wgpu::TextureAspect::All, noColorSpaceConversion);
+    }
+
+    // Invalid cases: srcTransferFunctionParameters, dstTransferFunctionParameters or
+    // conversionMatrix is nullptr or not set
+    {
+        // not set srcTransferFunctionParameters
+        wgpu::CopyTextureForBrowserOptions invalidOptions = options;
+        std::array<float, 7> dstTransferFunctionParameters = {};
+        std::array<float, 9> conversionMatrix = {};
+        invalidOptions.dstTransferFunctionParameters = dstTransferFunctionParameters.data();
+        invalidOptions.conversionMatrix = conversionMatrix.data();
+        TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0},
+                                          destination, 0, {0, 0, 0}, {4, 4, 1},
+                                          wgpu::TextureAspect::All, invalidOptions);
+
+        // set to nullptr
+        invalidOptions.srcTransferFunctionParameters = nullptr;
+        TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0},
+                                          destination, 0, {0, 0, 0}, {4, 4, 1},
+                                          wgpu::TextureAspect::All, invalidOptions);
+    }
+
+    {
+        // not set dstTransferFunctionParameters
+        wgpu::CopyTextureForBrowserOptions invalidOptions = options;
+        std::array<float, 7> srcTransferFunctionParameters = {};
+        std::array<float, 9> conversionMatrix = {};
+        invalidOptions.srcTransferFunctionParameters = srcTransferFunctionParameters.data();
+        invalidOptions.conversionMatrix = conversionMatrix.data();
+        TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0},
+                                          destination, 0, {0, 0, 0}, {4, 4, 1},
+                                          wgpu::TextureAspect::All, invalidOptions);
+
+        // set to nullptr
+        invalidOptions.dstTransferFunctionParameters = nullptr;
+        TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0},
+                                          destination, 0, {0, 0, 0}, {4, 4, 1},
+                                          wgpu::TextureAspect::All, invalidOptions);
+    }
+
+    {
+        // not set conversionMatrix
+        wgpu::CopyTextureForBrowserOptions invalidOptions = options;
+        std::array<float, 7> srcTransferFunctionParameters = {};
+        std::array<float, 7> dstTransferFunctionParameters = {};
+        invalidOptions.srcTransferFunctionParameters = srcTransferFunctionParameters.data();
+        invalidOptions.dstTransferFunctionParameters = dstTransferFunctionParameters.data();
+        TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0},
+                                          destination, 0, {0, 0, 0}, {4, 4, 1},
+                                          wgpu::TextureAspect::All, invalidOptions);
+
+        // set to nullptr
+        invalidOptions.conversionMatrix = nullptr;
+        TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0},
+                                          destination, 0, {0, 0, 0}, {4, 4, 1},
+                                          wgpu::TextureAspect::All, invalidOptions);
+    }
+}
+
+// Test option.srcAlphaMode/dstAlphaMode
+TEST_F(CopyExternalTextureForBrowserTest, ColorSpaceConversion_TextureAlphaState) {
+    wgpu::ExternalTexture source = CreateExternalTexture(device, 16, 16);
+    wgpu::Texture destination =
+        Create2DTexture(device, 16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
+                        wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment);
+
+    wgpu::CopyTextureForBrowserOptions options = {};
+
+    // Valid src texture alpha state and valid dst texture alpha state
+    {
+        options.srcAlphaMode = wgpu::AlphaMode::Premultiplied;
+        options.dstAlphaMode = wgpu::AlphaMode::Premultiplied;
+
+        TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0},
+                                          destination, 0, {0, 0, 0}, {4, 4, 1},
+                                          wgpu::TextureAspect::All, options);
+
+        options.srcAlphaMode = wgpu::AlphaMode::Premultiplied;
+        options.dstAlphaMode = wgpu::AlphaMode::Unpremultiplied;
+
+        TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0},
+                                          destination, 0, {0, 0, 0}, {4, 4, 1},
+                                          wgpu::TextureAspect::All, options);
+
+        options.srcAlphaMode = wgpu::AlphaMode::Unpremultiplied;
+        options.dstAlphaMode = wgpu::AlphaMode::Premultiplied;
+
+        TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0},
+                                          destination, 0, {0, 0, 0}, {4, 4, 1},
+                                          wgpu::TextureAspect::All, options);
+
+        options.srcAlphaMode = wgpu::AlphaMode::Unpremultiplied;
+        options.dstAlphaMode = wgpu::AlphaMode::Unpremultiplied;
+
+        TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0},
+                                          destination, 0, {0, 0, 0}, {4, 4, 1},
+                                          wgpu::TextureAspect::All, options);
+    }
+}
+
 // Test that the internal usage can only be set to true when the device internal usage feature is
 // enabled
 TEST_F(CopyTextureForBrowserTest, InternalUsage) {
@@ -456,15 +860,15 @@
     internalDesc.internalUsage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::TextureBinding;
 
     // Validation should fail because internal descriptor is not empty.
-    ASSERT_DEVICE_ERROR(Create2DTexture(16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
+    ASSERT_DEVICE_ERROR(Create2DTexture(device, 16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
                                         wgpu::TextureUsage::CopySrc, 1, &internalDesc));
 
     wgpu::Texture source =
-        Create2DTexture(16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
+        Create2DTexture(device, 16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
                         wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::TextureBinding);
 
     wgpu::Texture destination =
-        Create2DTexture(16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
+        Create2DTexture(device, 16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
                         wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment);
 
     // Validation should fail because of device internal usage feature is missing when internal
@@ -480,14 +884,15 @@
     wgpu::DawnTextureInternalUsageDescriptor internalDesc1 = {};
     internalDesc1.internalUsage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::TextureBinding;
 
-    wgpu::Texture source = Create2DTexture(16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
+    wgpu::Texture source = Create2DTexture(device, 16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
                                            wgpu::TextureUsage::CopySrc, 1, &internalDesc1);
 
     wgpu::DawnTextureInternalUsageDescriptor internalDesc2 = {};
     internalDesc2.internalUsage =
         wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment;
-    wgpu::Texture destination = Create2DTexture(16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
-                                                wgpu::TextureUsage::CopyDst, 1, &internalDesc2);
+    wgpu::Texture destination =
+        Create2DTexture(device, 16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
+                        wgpu::TextureUsage::CopyDst, 1, &internalDesc2);
 
     // Without internal usage option should fail usage validation
     TestCopyTextureForBrowser(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0,
@@ -499,3 +904,48 @@
     TestCopyTextureForBrowser(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, 0,
                               {0, 0, 0}, {16, 16, 1}, wgpu::TextureAspect::All, options);
 }
+
+// Test that the internal usage can only be set to true when the device internal usage feature is
+// enabled
+TEST_F(CopyExternalTextureForBrowserTest, InternalUsage) {
+    wgpu::DawnTextureInternalUsageDescriptor internalDesc = {};
+    internalDesc.internalUsage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::TextureBinding;
+
+    // Validation should fail because internal descriptor is not empty.
+    ASSERT_DEVICE_ERROR(Create2DTexture(device, 16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
+                                        wgpu::TextureUsage::CopySrc, 1, &internalDesc));
+
+    wgpu::ExternalTexture source = CreateExternalTexture(device, 16, 16);
+
+    wgpu::Texture destination =
+        Create2DTexture(device, 16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
+                        wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment);
+
+    // Validation should fail because of device internal usage feature is missing when internal
+    // usage option is on
+    wgpu::CopyTextureForBrowserOptions options = {};
+    options.internalUsage = true;
+    TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, destination,
+                                      0, {0, 0, 0}, {16, 16, 1}, wgpu::TextureAspect::All, options);
+}
+
+// Test that the internal usages are taken into account when interalUsage = true
+TEST_F(CopyExternalTextureForBrowserInternalUsageTest, InternalUsage) {
+    wgpu::ExternalTexture source = CreateExternalTexture(device, 16, 16);
+
+    wgpu::DawnTextureInternalUsageDescriptor internalDesc = {};
+    internalDesc.internalUsage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment;
+    wgpu::Texture destination =
+        Create2DTexture(device, 16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
+                        wgpu::TextureUsage::CopyDst, 1, &internalDesc);
+
+    // Without internal usage option should fail usage validation
+    TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, destination,
+                                      0, {0, 0, 0}, {16, 16, 1});
+
+    // With internal usage option should pass usage validation
+    wgpu::CopyTextureForBrowserOptions options = {};
+    options.internalUsage = true;
+    TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0}, destination,
+                                      0, {0, 0, 0}, {16, 16, 1}, wgpu::TextureAspect::All, options);
+}
diff --git a/src/dawn/utils/WGPUHelpers.cpp b/src/dawn/utils/WGPUHelpers.cpp
index dda2cab..d016620 100644
--- a/src/dawn/utils/WGPUHelpers.cpp
+++ b/src/dawn/utils/WGPUHelpers.cpp
@@ -26,6 +26,17 @@
 
 #include "spirv-tools/optimizer.hpp"
 
+namespace {
+std::array<float, 12> kYuvToRGBMatrixBT709 = {1.164384f, 0.0f,       1.792741f,  -0.972945f,
+                                              1.164384f, -0.213249f, -0.532909f, 0.301483f,
+                                              1.164384f, 2.112402f,  0.0f,       -1.133402f};
+std::array<float, 9> kGamutConversionMatrixBT709ToSrgb = {1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
+                                                          0.0f, 0.0f, 0.0f, 1.0f};
+std::array<float, 7> kGammaDecodeBT709 = {2.2, 1.0 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081,
+                                          0.0, 0.0};
+std::array<float, 7> kGammaEncodeSrgb = {1 / 2.4, 1.137119, 0.0, 12.92, 0.0031308, -0.055, 0.0};
+}  // namespace
+
 namespace utils {
 wgpu::ShaderModule CreateShaderModuleFromASM(const wgpu::Device& device, const char* source) {
     // Use SPIRV-Tools's C API to assemble the SPIR-V assembly text to binary. Because the types
@@ -389,4 +400,14 @@
     return device.CreateBindGroup(&descriptor);
 }
 
+ColorSpaceConversionInfo GetYUVBT709ToRGBSRGBColorSpaceConversionInfo() {
+    ColorSpaceConversionInfo info;
+    info.yuvToRgbConversionMatrix = kYuvToRGBMatrixBT709;
+    info.gamutConversionMatrix = kGamutConversionMatrixBT709ToSrgb;
+    info.srcTransferFunctionParameters = kGammaDecodeBT709;
+    info.dstTransferFunctionParameters = kGammaEncodeSrgb;
+
+    return info;
+}
+
 }  // namespace utils
diff --git a/src/dawn/utils/WGPUHelpers.h b/src/dawn/utils/WGPUHelpers.h
index 24864e6..9217ca9 100644
--- a/src/dawn/utils/WGPUHelpers.h
+++ b/src/dawn/utils/WGPUHelpers.h
@@ -178,6 +178,15 @@
     const wgpu::BindGroupLayout& layout,
     std::initializer_list<BindingInitializationHelper> entriesInitializer);
 
+struct ColorSpaceConversionInfo {
+    std::array<float, 12> yuvToRgbConversionMatrix;
+    std::array<float, 9> gamutConversionMatrix;
+    std::array<float, 7> srcTransferFunctionParameters;
+    std::array<float, 7> dstTransferFunctionParameters;
+};
+
+ColorSpaceConversionInfo GetYUVBT709ToRGBSRGBColorSpaceConversionInfo();
+
 }  // namespace utils
 
 #endif  // SRC_DAWN_UTILS_WGPUHELPERS_H_