Add WGPUDawnEncoderInternalUsageDescriptor

This descriptor, when chained on WGPUCommandEncoderDescriptor makes
internal usages visible to validation.

This CL is to help implement WebGPU Swiftshader support in Chrome.

Bug: chromium:1266550
Change-Id: I7253fe45003e9ad5ac4d8ddd2d4782989e9b5c27
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/76440
Reviewed-by: Loko Kung <lokokung@google.com>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Commit-Queue: Austin Eng <enga@chromium.org>
diff --git a/dawn.json b/dawn.json
index e7e5b61..40c8cf7 100644
--- a/dawn.json
+++ b/dawn.json
@@ -2369,7 +2369,8 @@
             {"value": 11, "name": "surface descriptor from windows swap chain panel", "tags": ["dawn"]},
             {"value": 1000, "name": "dawn texture internal usage descriptor", "tags": ["dawn"]},
             {"value": 1001, "name": "primitive depth clamping state", "tags": ["dawn", "emscripten"]},
-            {"value": 1002, "name": "dawn toggles device descriptor", "tags": ["dawn", "native"]}
+            {"value": 1002, "name": "dawn toggles device descriptor", "tags": ["dawn", "native"]},
+            {"value": 1003, "name": "dawn encoder internal usage descriptor", "tags": ["dawn"]}
         ]
     },
     "texture": {
@@ -2733,5 +2734,13 @@
         "members": [
             {"name": "internal usage", "type": "texture usage", "default": "none"}
         ]
+    },
+    "dawn encoder internal usage descriptor": {
+        "category": "structure",
+        "chained": "in",
+        "tags": ["dawn"],
+        "members": [
+            {"name": "use internal usages", "type": "bool", "default": "false"}
+        ]
     }
 }
diff --git a/docs/features/dawn_internal_usages.md b/docs/features/dawn_internal_usages.md
index ec06637..521a3ac 100644
--- a/docs/features/dawn_internal_usages.md
+++ b/docs/features/dawn_internal_usages.md
@@ -1,12 +1,11 @@
 # Dawn Internal Usages
 
-The `dawn-internal-usages` feature allows adding additional usage which affects how a texture is allocated, but does not affect frontend validation.
+The `dawn-internal-usages` feature allows adding additional usage which affects how a texture is allocated, but does not affect normal frontend validation.
 
-One use case for this is so that Chromium can use an internal copyTextureToTexture command to implement copies from a WebGPU texture-backed canvas to other Web platform primitives when the swapchain texture was not explicitly created with CopySrc usage in Javascript.
+Adds `WGPUDawnTextureInternalUsageDescriptor` for specifying additional internal usages to create a texture with.
 
+Example Usage:
 ```
-Usage:
-
 wgpu::DawnTextureInternalUsageDescriptor internalDesc = {};
 internalDesc.internalUsage = wgpu::TextureUsage::CopySrc;
 
@@ -14,5 +13,26 @@
 // set properties of desc.
 desc.nextInChain = &internalDesc;
 
-device.createTexture(&desc);
+device.CreateTexture(&desc);
 ```
+
+Adds `WGPUDawnEncoderInternalUsageDescriptor` which may be chained on `WGPUCommandEncoderDescriptor`. Setting `WGPUDawnEncoderInternalUsageDescriptor::useInternalUsages` to `true` means that internal resource usages will be visible during validation. ex.) A texture that has `WGPUTextureUsage_CopySrc` in `WGPUDawnEncoderInternalUsageDescriptor::internalUsage`, but not in `WGPUTextureDescriptor::usage` may be used as the source of a copy command.
+
+
+Example Usage:
+```
+wgpu::DawnEncoderInternalUsageDescriptor internalEncoderDesc = { true };
+wgpu::CommandEncoderDescriptor encoderDesc = {};
+encoderDesc.nextInChain = &internalEncoderDesc;
+
+wgpu::CommandEncoder encoder = device.CreateCommandEncoder(&encoderDesc);
+
+// This will be valid
+wgpu::ImageCopyTexture src = {};
+src.texture = texture;
+encoder.CopyTextureToBuffer(&src, ...);
+```
+
+One use case for this is so that Chromium can use an internal copyTextureToTexture command to implement copies from a WebGPU texture-backed canvas to other Web platform primitives when the swapchain texture was not explicitly created with CopySrc usage in Javascript.
+
+Note: copyTextureToTextureInternal will be removed in favor of `WGPUDawnEncoderInternalUsageDescriptor`.
diff --git a/src/dawn_native/CommandEncoder.cpp b/src/dawn_native/CommandEncoder.cpp
index d781962..0001555 100644
--- a/src/dawn_native/CommandEncoder.cpp
+++ b/src/dawn_native/CommandEncoder.cpp
@@ -18,6 +18,7 @@
 #include "common/Math.h"
 #include "dawn_native/BindGroup.h"
 #include "dawn_native/Buffer.h"
+#include "dawn_native/ChainUtils_autogen.h"
 #include "dawn_native/CommandBuffer.h"
 #include "dawn_native/CommandBufferStateTracker.h"
 #include "dawn_native/CommandValidation.h"
@@ -159,7 +160,8 @@
         }
 
         MaybeError ValidateResolveTarget(const DeviceBase* device,
-                                         const RenderPassColorAttachment& colorAttachment) {
+                                         const RenderPassColorAttachment& colorAttachment,
+                                         UsageValidationMode usageValidationMode) {
             if (colorAttachment.resolveTarget == nullptr) {
                 return {};
             }
@@ -168,7 +170,7 @@
             const TextureViewBase* attachment = colorAttachment.view;
             DAWN_TRY(device->ValidateObject(colorAttachment.resolveTarget));
             DAWN_TRY(ValidateCanUseAs(colorAttachment.resolveTarget->GetTexture(),
-                                      wgpu::TextureUsage::RenderAttachment));
+                                      wgpu::TextureUsage::RenderAttachment, usageValidationMode));
 
             DAWN_INVALID_IF(
                 !attachment->GetTexture()->IsMultisampledTexture(),
@@ -216,11 +218,12 @@
             const RenderPassColorAttachment& colorAttachment,
             uint32_t* width,
             uint32_t* height,
-            uint32_t* sampleCount) {
+            uint32_t* sampleCount,
+            UsageValidationMode usageValidationMode) {
             TextureViewBase* attachment = colorAttachment.view;
             DAWN_TRY(device->ValidateObject(attachment));
-            DAWN_TRY(
-                ValidateCanUseAs(attachment->GetTexture(), wgpu::TextureUsage::RenderAttachment));
+            DAWN_TRY(ValidateCanUseAs(attachment->GetTexture(),
+                                      wgpu::TextureUsage::RenderAttachment, usageValidationMode));
 
             DAWN_INVALID_IF(!(attachment->GetAspects() & Aspect::Color) ||
                                 !attachment->GetFormat().isRenderable,
@@ -241,7 +244,7 @@
 
             DAWN_TRY(ValidateOrSetColorAttachmentSampleCount(attachment, sampleCount));
 
-            DAWN_TRY(ValidateResolveTarget(device, colorAttachment));
+            DAWN_TRY(ValidateResolveTarget(device, colorAttachment, usageValidationMode));
 
             DAWN_TRY(ValidateAttachmentArrayLayersAndLevelCount(attachment));
             DAWN_TRY(ValidateOrSetAttachmentSize(attachment, width, height));
@@ -254,13 +257,14 @@
             const RenderPassDepthStencilAttachment* depthStencilAttachment,
             uint32_t* width,
             uint32_t* height,
-            uint32_t* sampleCount) {
+            uint32_t* sampleCount,
+            UsageValidationMode usageValidationMode) {
             DAWN_ASSERT(depthStencilAttachment != nullptr);
 
             TextureViewBase* attachment = depthStencilAttachment->view;
             DAWN_TRY(device->ValidateObject(attachment));
-            DAWN_TRY(
-                ValidateCanUseAs(attachment->GetTexture(), wgpu::TextureUsage::RenderAttachment));
+            DAWN_TRY(ValidateCanUseAs(attachment->GetTexture(),
+                                      wgpu::TextureUsage::RenderAttachment, usageValidationMode));
 
             const Format& format = attachment->GetFormat();
             DAWN_INVALID_IF(
@@ -333,24 +337,25 @@
                                                 const RenderPassDescriptor* descriptor,
                                                 uint32_t* width,
                                                 uint32_t* height,
-                                                uint32_t* sampleCount) {
+                                                uint32_t* sampleCount,
+                                                UsageValidationMode usageValidationMode) {
             DAWN_INVALID_IF(
                 descriptor->colorAttachmentCount > kMaxColorAttachments,
                 "Color attachment count (%u) exceeds the maximum number of color attachments (%u).",
                 descriptor->colorAttachmentCount, kMaxColorAttachments);
 
             for (uint32_t i = 0; i < descriptor->colorAttachmentCount; ++i) {
-                DAWN_TRY_CONTEXT(
-                    ValidateRenderPassColorAttachment(device, descriptor->colorAttachments[i],
-                                                      width, height, sampleCount),
-                    "validating colorAttachments[%u].", i);
+                DAWN_TRY_CONTEXT(ValidateRenderPassColorAttachment(
+                                     device, descriptor->colorAttachments[i], width, height,
+                                     sampleCount, usageValidationMode),
+                                 "validating colorAttachments[%u].", i);
             }
 
             if (descriptor->depthStencilAttachment != nullptr) {
-                DAWN_TRY_CONTEXT(
-                    ValidateRenderPassDepthStencilAttachment(
-                        device, descriptor->depthStencilAttachment, width, height, sampleCount),
-                    "validating depthStencilAttachment.");
+                DAWN_TRY_CONTEXT(ValidateRenderPassDepthStencilAttachment(
+                                     device, descriptor->depthStencilAttachment, width, height,
+                                     sampleCount, usageValidationMode),
+                                 "validating depthStencilAttachment.");
             }
 
             if (descriptor->occlusionQuerySet != nullptr) {
@@ -468,9 +473,50 @@
 
     }  // namespace
 
+    MaybeError ValidateCommandEncoderDescriptor(const DeviceBase* device,
+                                                const CommandEncoderDescriptor* descriptor) {
+        DAWN_TRY(ValidateSingleSType(descriptor->nextInChain,
+                                     wgpu::SType::DawnEncoderInternalUsageDescriptor));
+
+        const DawnEncoderInternalUsageDescriptor* internalUsageDesc = nullptr;
+        FindInChain(descriptor->nextInChain, &internalUsageDesc);
+
+        DAWN_INVALID_IF(internalUsageDesc != nullptr &&
+                            !device->APIHasFeature(wgpu::FeatureName::DawnInternalUsages),
+                        "%s is not available.", wgpu::FeatureName::DawnInternalUsages);
+        return {};
+    }
+
+    // static
+    Ref<CommandEncoder> CommandEncoder::Create(DeviceBase* device,
+                                               const CommandEncoderDescriptor* descriptor) {
+        return AcquireRef(new CommandEncoder(device, descriptor));
+    }
+
+    // static
+    CommandEncoder* CommandEncoder::MakeError(DeviceBase* device) {
+        return new CommandEncoder(device, ObjectBase::kError);
+    }
+
     CommandEncoder::CommandEncoder(DeviceBase* device, const CommandEncoderDescriptor* descriptor)
         : ApiObjectBase(device, descriptor->label), mEncodingContext(device, this) {
         TrackInDevice();
+
+        const DawnEncoderInternalUsageDescriptor* internalUsageDesc = nullptr;
+        FindInChain(descriptor->nextInChain, &internalUsageDesc);
+
+        if (internalUsageDesc != nullptr && internalUsageDesc->useInternalUsages) {
+            mUsageValidationMode = UsageValidationMode::Internal;
+        } else {
+            mUsageValidationMode = UsageValidationMode::Default;
+        }
+    }
+
+    CommandEncoder::CommandEncoder(DeviceBase* device, ObjectBase::ErrorTag tag)
+        : ApiObjectBase(device, tag),
+          mEncodingContext(device, this),
+          mUsageValidationMode(UsageValidationMode::Default) {
+        mEncodingContext.HandleError(DAWN_FORMAT_VALIDATION_ERROR("%s is invalid.", this));
     }
 
     ObjectType CommandEncoder::GetType() const {
@@ -554,7 +600,7 @@
                 uint32_t sampleCount = 0;
 
                 DAWN_TRY(ValidateRenderPassDescriptor(device, descriptor, &width, &height,
-                                                      &sampleCount));
+                                                      &sampleCount, mUsageValidationMode));
 
                 ASSERT(width > 0 && height > 0 && sampleCount > 0);
 
@@ -703,7 +749,8 @@
 
                     DAWN_TRY(ValidateImageCopyTexture(GetDevice(), *destination, *copySize));
                     DAWN_TRY_CONTEXT(
-                        ValidateCanUseAs(destination->texture, wgpu::TextureUsage::CopyDst),
+                        ValidateCanUseAs(destination->texture, wgpu::TextureUsage::CopyDst,
+                                         mUsageValidationMode),
                         "validating destination %s usage.", destination->texture);
                     DAWN_TRY(ValidateTextureSampleCountInBufferCopyCommands(destination->texture));
 
@@ -757,7 +804,8 @@
             [&](CommandAllocator* allocator) -> MaybeError {
                 if (GetDevice()->IsValidationEnabled()) {
                     DAWN_TRY(ValidateImageCopyTexture(GetDevice(), *source, *copySize));
-                    DAWN_TRY_CONTEXT(ValidateCanUseAs(source->texture, wgpu::TextureUsage::CopySrc),
+                    DAWN_TRY_CONTEXT(ValidateCanUseAs(source->texture, wgpu::TextureUsage::CopySrc,
+                                                      mUsageValidationMode),
                                      "validating source %s usage.", source->texture);
                     DAWN_TRY(ValidateTextureSampleCountInBufferCopyCommands(source->texture));
                     DAWN_TRY(ValidateTextureDepthStencilToBufferCopyRestrictions(*source));
@@ -846,14 +894,15 @@
                     // For internal usages (CopyToCopyInternal) we don't care if the user has added
                     // CopySrc as a usage for this texture, but we will always add it internally.
                     if (Internal) {
-                        DAWN_TRY(
-                            ValidateInternalCanUseAs(source->texture, wgpu::TextureUsage::CopySrc));
-                        DAWN_TRY(ValidateInternalCanUseAs(destination->texture,
-                                                          wgpu::TextureUsage::CopyDst));
+                        DAWN_TRY(ValidateCanUseAs(source->texture, wgpu::TextureUsage::CopySrc,
+                                                  UsageValidationMode::Internal));
+                        DAWN_TRY(ValidateCanUseAs(destination->texture, wgpu::TextureUsage::CopyDst,
+                                                  UsageValidationMode::Internal));
                     } else {
-                        DAWN_TRY(ValidateCanUseAs(source->texture, wgpu::TextureUsage::CopySrc));
-                        DAWN_TRY(
-                            ValidateCanUseAs(destination->texture, wgpu::TextureUsage::CopyDst));
+                        DAWN_TRY(ValidateCanUseAs(source->texture, wgpu::TextureUsage::CopySrc,
+                                                  mUsageValidationMode));
+                        DAWN_TRY(ValidateCanUseAs(destination->texture, wgpu::TextureUsage::CopyDst,
+                                                  mUsageValidationMode));
                     }
 
                     mTopLevelTextures.insert(source->texture);
diff --git a/src/dawn_native/CommandEncoder.h b/src/dawn_native/CommandEncoder.h
index e773ed2..14f81ed 100644
--- a/src/dawn_native/CommandEncoder.h
+++ b/src/dawn_native/CommandEncoder.h
@@ -26,9 +26,16 @@
 
 namespace dawn::native {
 
+    enum class UsageValidationMode;
+
+    MaybeError ValidateCommandEncoderDescriptor(const DeviceBase* device,
+                                                const CommandEncoderDescriptor* descriptor);
+
     class CommandEncoder final : public ApiObjectBase {
       public:
-        CommandEncoder(DeviceBase* device, const CommandEncoderDescriptor* descriptor);
+        static Ref<CommandEncoder> Create(DeviceBase* device,
+                                          const CommandEncoderDescriptor* descriptor);
+        static CommandEncoder* MakeError(DeviceBase* device);
 
         ObjectType GetType() const override;
 
@@ -80,6 +87,9 @@
         CommandBufferBase* APIFinish(const CommandBufferDescriptor* descriptor = nullptr);
 
       private:
+        CommandEncoder(DeviceBase* device, const CommandEncoderDescriptor* descriptor);
+        CommandEncoder(DeviceBase* device, ObjectBase::ErrorTag tag);
+
         void DestroyImpl() override;
         ResultOrError<Ref<CommandBufferBase>> FinishInternal(
             const CommandBufferDescriptor* descriptor);
@@ -100,6 +110,8 @@
         std::set<QuerySetBase*> mUsedQuerySets;
 
         uint64_t mDebugGroupStackSize = 0;
+
+        UsageValidationMode mUsageValidationMode;
     };
 
 }  // namespace dawn::native
diff --git a/src/dawn_native/CommandValidation.cpp b/src/dawn_native/CommandValidation.cpp
index 1e307e5..d3474f4 100644
--- a/src/dawn_native/CommandValidation.cpp
+++ b/src/dawn_native/CommandValidation.cpp
@@ -447,19 +447,21 @@
         return ValidateTextureToTextureCopyCommonRestrictions(src, dst, copySize);
     }
 
-    MaybeError ValidateCanUseAs(const TextureBase* texture, wgpu::TextureUsage usage) {
+    MaybeError ValidateCanUseAs(const TextureBase* texture,
+                                wgpu::TextureUsage usage,
+                                UsageValidationMode mode) {
         ASSERT(wgpu::HasZeroOrOneBits(usage));
-        DAWN_INVALID_IF(!(texture->GetUsage() & usage), "%s usage (%s) doesn't include %s.",
-                        texture, texture->GetUsage(), usage);
-
-        return {};
-    }
-
-    MaybeError ValidateInternalCanUseAs(const TextureBase* texture, wgpu::TextureUsage usage) {
-        ASSERT(wgpu::HasZeroOrOneBits(usage));
-        DAWN_INVALID_IF(!(texture->GetInternalUsage() & usage),
-                        "%s internal usage (%s) doesn't include %s.", texture,
-                        texture->GetInternalUsage(), usage);
+        switch (mode) {
+            case UsageValidationMode::Default:
+                DAWN_INVALID_IF(!(texture->GetUsage() & usage), "%s usage (%s) doesn't include %s.",
+                                texture, texture->GetUsage(), usage);
+                break;
+            case UsageValidationMode::Internal:
+                DAWN_INVALID_IF(!(texture->GetInternalUsage() & usage),
+                                "%s internal usage (%s) doesn't include %s.", texture,
+                                texture->GetInternalUsage(), usage);
+                break;
+        }
 
         return {};
     }
@@ -468,7 +470,6 @@
         ASSERT(wgpu::HasZeroOrOneBits(usage));
         DAWN_INVALID_IF(!(buffer->GetUsage() & usage), "%s usage (%s) doesn't include %s.", buffer,
                         buffer->GetUsage(), usage);
-
         return {};
     }
 
diff --git a/src/dawn_native/CommandValidation.h b/src/dawn_native/CommandValidation.h
index 92b61e6..b17a003 100644
--- a/src/dawn_native/CommandValidation.h
+++ b/src/dawn_native/CommandValidation.h
@@ -73,10 +73,14 @@
                                                         const ImageCopyTexture& dst,
                                                         const Extent3D& copySize);
 
-    MaybeError ValidateCanUseAs(const TextureBase* texture, wgpu::TextureUsage usage);
+    enum class UsageValidationMode {
+        Default,
+        Internal,
+    };
 
-    MaybeError ValidateInternalCanUseAs(const TextureBase* texture, wgpu::TextureUsage usage);
-
+    MaybeError ValidateCanUseAs(const TextureBase* texture,
+                                wgpu::TextureUsage usage,
+                                UsageValidationMode mode);
     MaybeError ValidateCanUseAs(const BufferBase* buffer, wgpu::BufferUsage usage);
 
 }  // namespace dawn::native
diff --git a/src/dawn_native/CopyTextureForBrowserHelper.cpp b/src/dawn_native/CopyTextureForBrowserHelper.cpp
index 6cfd74c..2ccaf66 100644
--- a/src/dawn_native/CopyTextureForBrowserHelper.cpp
+++ b/src/dawn_native/CopyTextureForBrowserHelper.cpp
@@ -350,11 +350,15 @@
             "not 1.",
             source->texture->GetSampleCount(), destination->texture->GetSampleCount());
 
-        DAWN_TRY(ValidateCanUseAs(source->texture, wgpu::TextureUsage::CopySrc));
-        DAWN_TRY(ValidateCanUseAs(source->texture, wgpu::TextureUsage::TextureBinding));
+        DAWN_TRY(ValidateCanUseAs(source->texture, wgpu::TextureUsage::CopySrc,
+                                  UsageValidationMode::Default));
+        DAWN_TRY(ValidateCanUseAs(source->texture, wgpu::TextureUsage::TextureBinding,
+                                  UsageValidationMode::Default));
 
-        DAWN_TRY(ValidateCanUseAs(destination->texture, wgpu::TextureUsage::CopyDst));
-        DAWN_TRY(ValidateCanUseAs(destination->texture, wgpu::TextureUsage::RenderAttachment));
+        DAWN_TRY(ValidateCanUseAs(destination->texture, wgpu::TextureUsage::CopyDst,
+                                  UsageValidationMode::Default));
+        DAWN_TRY(ValidateCanUseAs(destination->texture, wgpu::TextureUsage::RenderAttachment,
+                                  UsageValidationMode::Default));
 
         DAWN_TRY(ValidateCopyTextureFormatConversion(source->texture->GetFormat().format,
                                                      destination->texture->GetFormat().format));
diff --git a/src/dawn_native/Device.cpp b/src/dawn_native/Device.cpp
index 1775574..cdd7197 100644
--- a/src/dawn_native/Device.cpp
+++ b/src/dawn_native/Device.cpp
@@ -953,7 +953,13 @@
         if (descriptor == nullptr) {
             descriptor = &defaultDescriptor;
         }
-        return new CommandEncoder(this, descriptor);
+
+        Ref<CommandEncoder> result;
+        if (ConsumedError(CreateCommandEncoder(descriptor), &result,
+                          "calling %s.CreateCommandEncoder(%s).", this, descriptor)) {
+            return CommandEncoder::MakeError(this);
+        }
+        return result.Detach();
     }
     ComputePipelineBase* DeviceBase::APICreateComputePipeline(
         const ComputePipelineDescriptor* descriptor) {
@@ -1313,6 +1319,15 @@
         return AddOrGetCachedComputePipeline(std::move(uninitializedComputePipeline));
     }
 
+    ResultOrError<Ref<CommandEncoder>> DeviceBase::CreateCommandEncoder(
+        const CommandEncoderDescriptor* descriptor) {
+        DAWN_TRY(ValidateIsAlive());
+        if (IsValidationEnabled()) {
+            DAWN_TRY(ValidateCommandEncoderDescriptor(this, descriptor));
+        }
+        return CommandEncoder::Create(this, descriptor);
+    }
+
     MaybeError DeviceBase::CreateComputePipelineAsync(
         const ComputePipelineDescriptor* descriptor,
         WGPUCreateComputePipelineAsyncCallback callback,
diff --git a/src/dawn_native/Device.h b/src/dawn_native/Device.h
index 89ddcf5..75e293f 100644
--- a/src/dawn_native/Device.h
+++ b/src/dawn_native/Device.h
@@ -198,6 +198,8 @@
             const BindGroupLayoutDescriptor* descriptor,
             bool allowInternalBinding = false);
         ResultOrError<Ref<BufferBase>> CreateBuffer(const BufferDescriptor* descriptor);
+        ResultOrError<Ref<CommandEncoder>> CreateCommandEncoder(
+            const CommandEncoderDescriptor* descriptor);
         ResultOrError<Ref<ComputePipelineBase>> CreateComputePipeline(
             const ComputePipelineDescriptor* descriptor);
         MaybeError CreateComputePipelineAsync(
diff --git a/src/tests/unittests/validation/InternalUsageValidationTests.cpp b/src/tests/unittests/validation/InternalUsageValidationTests.cpp
index 49ebc24..d175276 100644
--- a/src/tests/unittests/validation/InternalUsageValidationTests.cpp
+++ b/src/tests/unittests/validation/InternalUsageValidationTests.cpp
@@ -16,10 +16,11 @@
 
 #include "utils/WGPUHelpers.h"
 
-class TextureInternalUsageValidationDisabledTest : public ValidationTest {};
+class InternalUsageValidationDisabledTest : public ValidationTest {};
 
-// Test that using the feature is an error if it is not enabled
-TEST_F(TextureInternalUsageValidationDisabledTest, RequiresFeature) {
+// Test that using DawnTextureInternalUsageDescriptor is an error if DawnInternalUsages is not
+// enabled
+TEST_F(InternalUsageValidationDisabledTest, TextureDescriptorRequiresFeature) {
     wgpu::TextureDescriptor textureDesc = {};
     textureDesc.size = {1, 1};
     textureDesc.usage = wgpu::TextureUsage::CopySrc;
@@ -42,6 +43,26 @@
     ASSERT_DEVICE_ERROR(device.CreateTexture(&textureDesc));
 }
 
+// Test that using DawnEncoderInternalUsageDescriptor is an error if DawnInternalUsages is not
+// enabled
+TEST_F(InternalUsageValidationDisabledTest, CommandEncoderDescriptorRequiresFeature) {
+    wgpu::CommandEncoderDescriptor encoderDesc = {};
+
+    // Control case: Normal encoder creation works
+    device.CreateCommandEncoder(&encoderDesc);
+
+    wgpu::DawnEncoderInternalUsageDescriptor internalDesc = {};
+    encoderDesc.nextInChain = &internalDesc;
+
+    // Error with chained DawnEncoderInternalUsageDescriptor.
+    ASSERT_DEVICE_ERROR(wgpu::CommandEncoder encoder = device.CreateCommandEncoder(&encoderDesc));
+
+    // Check that the encoder records that it is invalid, and not any other errors.
+    encoder.InjectValidationError("injected error");
+    ASSERT_DEVICE_ERROR(encoder.Finish(),
+                        testing::HasSubstr("[Invalid CommandEncoder] is invalid"));
+}
+
 class TextureInternalUsageValidationTest : public ValidationTest {
     WGPUDevice CreateTestDevice() override {
         wgpu::DeviceDescriptor descriptor;
@@ -117,7 +138,7 @@
 // Test that internal usage does not add to the validated usage
 // for command encoding
 // This test also test the internal copy
-TEST_F(TextureInternalUsageValidationTest, CommandValidation) {
+TEST_F(TextureInternalUsageValidationTest, DeprecatedCommandValidation) {
     wgpu::TextureDescriptor textureDesc = {};
     textureDesc.size = {1, 1};
     textureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
@@ -182,3 +203,96 @@
         encoder.Finish();
     }
 }
+
+TEST_F(TextureInternalUsageValidationTest, CommandValidation) {
+    wgpu::TextureDescriptor textureDesc = {};
+    textureDesc.size = {1, 1};
+    textureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
+
+    textureDesc.usage = wgpu::TextureUsage::CopyDst;
+    wgpu::Texture dst = device.CreateTexture(&textureDesc);
+
+    textureDesc.usage = wgpu::TextureUsage::CopySrc;
+    wgpu::Texture src = device.CreateTexture(&textureDesc);
+
+    textureDesc.usage = wgpu::TextureUsage::None;
+
+    wgpu::DawnTextureInternalUsageDescriptor internalDesc = {};
+    textureDesc.nextInChain = &internalDesc;
+    internalDesc.internalUsage = wgpu::TextureUsage::CopySrc;
+
+    wgpu::Texture srcInternal = device.CreateTexture(&textureDesc);
+
+    // Control: src -> dst
+    {
+        wgpu::ImageCopyTexture srcImageCopyTexture = utils::CreateImageCopyTexture(src, 0, {0, 0});
+        wgpu::ImageCopyTexture dstImageCopyTexture = utils::CreateImageCopyTexture(dst, 0, {0, 0});
+        wgpu::Extent3D extent3D = {1, 1};
+
+        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+        encoder.CopyTextureToTexture(&srcImageCopyTexture, &dstImageCopyTexture, &extent3D);
+        encoder.Finish();
+    }
+
+    // Invalid: src internal -> dst
+    {
+        wgpu::ImageCopyTexture srcImageCopyTexture =
+            utils::CreateImageCopyTexture(srcInternal, 0, {0, 0});
+        wgpu::ImageCopyTexture dstImageCopyTexture = utils::CreateImageCopyTexture(dst, 0, {0, 0});
+        wgpu::Extent3D extent3D = {1, 1};
+
+        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+        encoder.CopyTextureToTexture(&srcImageCopyTexture, &dstImageCopyTexture, &extent3D);
+        ASSERT_DEVICE_ERROR(encoder.Finish());
+    }
+
+    // Invalid: src internal -> dst, with internal descriptor, but useInternalUsages set to false.
+    {
+        wgpu::ImageCopyTexture srcImageCopyTexture =
+            utils::CreateImageCopyTexture(srcInternal, 0, {0, 0});
+        wgpu::ImageCopyTexture dstImageCopyTexture = utils::CreateImageCopyTexture(dst, 0, {0, 0});
+        wgpu::Extent3D extent3D = {1, 1};
+
+        wgpu::CommandEncoderDescriptor encoderDesc = {};
+        wgpu::DawnEncoderInternalUsageDescriptor internalDesc = {};
+        internalDesc.useInternalUsages = false;
+        encoderDesc.nextInChain = &internalDesc;
+        wgpu::CommandEncoder encoder = device.CreateCommandEncoder(&encoderDesc);
+
+        encoder.CopyTextureToTexture(&srcImageCopyTexture, &dstImageCopyTexture, &extent3D);
+        ASSERT_DEVICE_ERROR(encoder.Finish());
+    }
+
+    // Control with internal copy: src -> dst
+    {
+        wgpu::ImageCopyTexture srcImageCopyTexture = utils::CreateImageCopyTexture(src, 0, {0, 0});
+        wgpu::ImageCopyTexture dstImageCopyTexture = utils::CreateImageCopyTexture(dst, 0, {0, 0});
+        wgpu::Extent3D extent3D = {1, 1};
+
+        wgpu::CommandEncoderDescriptor encoderDesc = {};
+        wgpu::DawnEncoderInternalUsageDescriptor internalDesc = {};
+        internalDesc.useInternalUsages = true;
+        encoderDesc.nextInChain = &internalDesc;
+        wgpu::CommandEncoder encoder = device.CreateCommandEncoder(&encoderDesc);
+
+        encoder.CopyTextureToTexture(&srcImageCopyTexture, &dstImageCopyTexture, &extent3D);
+        encoder.Finish();
+    }
+
+    // Valid with internal copy: src internal -> dst
+    {
+        wgpu::ImageCopyTexture srcImageCopyTexture =
+            utils::CreateImageCopyTexture(srcInternal, 0, {0, 0});
+        wgpu::ImageCopyTexture dstImageCopyTexture = utils::CreateImageCopyTexture(dst, 0, {0, 0});
+        wgpu::Extent3D extent3D = {1, 1};
+
+        wgpu::CommandEncoderDescriptor encoderDesc = {};
+        wgpu::DawnEncoderInternalUsageDescriptor internalDesc = {};
+        internalDesc.useInternalUsages = true;
+        encoderDesc.nextInChain = &internalDesc;
+        wgpu::CommandEncoder encoder = device.CreateCommandEncoder(&encoderDesc);
+
+        encoder.CopyTextureToTexture(&srcImageCopyTexture, &dstImageCopyTexture, &extent3D);
+        encoder.Finish();
+    }
+}