Add chained DawnTextureInternalUsageDescriptor

This chained struct can be used for internally adding usages to
Dawn textures. It will affect how the texture is allocated, but
not affect 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.

Usage:

wgpu::DawnTextureInternalUsageDescriptor internalDesc = {};
internalDesc.internalUsage = wgpu::TextureUsage::CopySrc;

wgpu::TextureDescriptor desc = {};
// set properties of desc.
desc.nextInChain = &internalDesc;

device.createTexture(&desc);

Fixed: dawn:1027
Change-Id: Id4d08b5588d4960d150d559aa11502c69f40a674
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/58140
Commit-Queue: Austin Eng <enga@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/dawn.json b/dawn.json
index 074f51f..8d5b24d 100644
--- a/dawn.json
+++ b/dawn.json
@@ -880,7 +880,8 @@
             {"name": "timestamp query", "type": "bool", "default": "false"},
             {"name": "multi planar formats", "type": "bool", "default": "false"},
             {"name": "depth clamping", "type": "bool", "default": "false"},
-            {"name": "invalid extension", "type": "bool", "default": "false"}
+            {"name": "invalid extension", "type": "bool", "default": "false"},
+            {"name": "dawn internal usages", "type": "bool", "default": "false"}
         ]
     },
     "double": {
@@ -1852,7 +1853,8 @@
             {"value": 8, "name": "surface descriptor from windows core window"},
             {"value": 9, "name": "external texture binding entry"},
             {"value": 10, "name": "external texture binding layout"},
-            {"value": 11, "name": "surface descriptor from windows swap chain panel"}
+            {"value": 11, "name": "surface descriptor from windows swap chain panel"},
+            {"value": 1000, "name": "dawn texture internal usage descriptor"}
         ]
     },
     "texture": {
@@ -2105,5 +2107,12 @@
     },
     "uint8_t": {
         "category": "native"
+    },
+    "dawn texture internal usage descriptor": {
+        "category": "structure",
+        "chained": true,
+        "members": [
+            {"name": "internal usage", "type": "texture usage", "default": "none"}
+        ]
     }
 }
diff --git a/docs/extensions/dawn_internal_usages.md b/docs/extensions/dawn_internal_usages.md
new file mode 100644
index 0000000..5655b9a
--- /dev/null
+++ b/docs/extensions/dawn_internal_usages.md
@@ -0,0 +1,18 @@
+# Dawn Internal Usages
+
+The `dawn-internal-usages` extension allows adding additional usage which affects how a texture is allocated, but does not affect 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.
+
+```
+Usage:
+
+wgpu::DawnTextureInternalUsageDescriptor internalDesc = {};
+internalDesc.internalUsage = wgpu::TextureUsage::CopySrc;
+
+wgpu::TextureDescriptor desc = {};
+// set properties of desc.
+desc.nextInChain = &internalDesc;
+
+device.createTexture(&desc);
+```
diff --git a/src/dawn_native/Adapter.cpp b/src/dawn_native/Adapter.cpp
index ff95534..5f84306 100644
--- a/src/dawn_native/Adapter.cpp
+++ b/src/dawn_native/Adapter.cpp
@@ -20,6 +20,7 @@
 
     AdapterBase::AdapterBase(InstanceBase* instance, wgpu::BackendType backend)
         : mInstance(instance), mBackend(backend) {
+        mSupportedExtensions.EnableExtension(Extension::DawnInternalUsages);
     }
 
     wgpu::BackendType AdapterBase::GetBackendType() const {
diff --git a/src/dawn_native/Extensions.cpp b/src/dawn_native/Extensions.cpp
index 47b2481..bd11c82 100644
--- a/src/dawn_native/Extensions.cpp
+++ b/src/dawn_native/Extensions.cpp
@@ -56,7 +56,14 @@
              {Extension::DepthClamping,
               {"depth_clamping", "Clamp depth to [0, 1] in NDC space instead of clipping",
                "https://bugs.chromium.org/p/dawn/issues/detail?id=716"},
-              &WGPUDeviceProperties::depthClamping}}};
+              &WGPUDeviceProperties::depthClamping},
+             {Extension::DawnInternalUsages,
+              {"dawn-internal-usages",
+               "Add internal usages to resources to affect how the texture is allocated, but not "
+               "frontend validation. Other internal commands may access this usage.",
+               "https://dawn.googlesource.com/dawn/+/refs/heads/main/docs/extensions/"
+               "dawn_internal_usages.md"},
+              &WGPUDeviceProperties::dawnInternalUsages}}};
 
     }  // anonymous namespace
 
diff --git a/src/dawn_native/Extensions.h b/src/dawn_native/Extensions.h
index 00dd639..16ac807 100644
--- a/src/dawn_native/Extensions.h
+++ b/src/dawn_native/Extensions.h
@@ -31,6 +31,9 @@
         MultiPlanarFormats,
         DepthClamping,
 
+        // Dawn-specific
+        DawnInternalUsages,
+
         EnumCount,
         InvalidEnum = EnumCount,
         ExtensionMin = TextureCompressionBC,
diff --git a/src/dawn_native/Texture.cpp b/src/dawn_native/Texture.cpp
index ebbabb7..a5e91d2 100644
--- a/src/dawn_native/Texture.cpp
+++ b/src/dawn_native/Texture.cpp
@@ -20,6 +20,7 @@
 #include "common/Constants.h"
 #include "common/Math.h"
 #include "dawn_native/Adapter.h"
+#include "dawn_native/ChainUtils_autogen.h"
 #include "dawn_native/Device.h"
 #include "dawn_native/EnumMaskIterator.h"
 #include "dawn_native/PassResourceUsage.h"
@@ -104,7 +105,9 @@
             }
         }
 
-        MaybeError ValidateSampleCount(const TextureDescriptor* descriptor, const Format* format) {
+        MaybeError ValidateSampleCount(const TextureDescriptor* descriptor,
+                                       wgpu::TextureUsage usage,
+                                       const Format* format) {
             if (!IsValidSampleCount(descriptor->sampleCount)) {
                 return DAWN_VALIDATION_ERROR("The sample count of the texture is not supported.");
             }
@@ -132,7 +135,7 @@
                 // Compressed formats are not renderable. They cannot support multisample.
                 ASSERT(!format->isCompressed);
 
-                if (descriptor->usage & wgpu::TextureUsage::Storage) {
+                if (usage & wgpu::TextureUsage::Storage) {
                     return DAWN_VALIDATION_ERROR(
                         "The sample counts of the storage textures must be 1.");
                 }
@@ -217,30 +220,30 @@
             return {};
         }
 
-        MaybeError ValidateTextureUsage(const TextureDescriptor* descriptor, const Format* format) {
-            DAWN_TRY(dawn_native::ValidateTextureUsage(descriptor->usage));
+        MaybeError ValidateTextureUsage(const TextureDescriptor* descriptor,
+                                        wgpu::TextureUsage usage,
+                                        const Format* format) {
+            DAWN_TRY(dawn_native::ValidateTextureUsage(usage));
 
             constexpr wgpu::TextureUsage kValidCompressedUsages = wgpu::TextureUsage::Sampled |
                                                                   wgpu::TextureUsage::CopySrc |
                                                                   wgpu::TextureUsage::CopyDst;
-            if (format->isCompressed && !IsSubset(descriptor->usage, kValidCompressedUsages)) {
+            if (format->isCompressed && !IsSubset(usage, kValidCompressedUsages)) {
                 return DAWN_VALIDATION_ERROR(
                     "Compressed texture format is incompatible with the texture usage");
             }
 
-            if (!format->isRenderable &&
-                (descriptor->usage & wgpu::TextureUsage::RenderAttachment)) {
+            if (!format->isRenderable && (usage & wgpu::TextureUsage::RenderAttachment)) {
                 return DAWN_VALIDATION_ERROR(
                     "Non-renderable format used with RenderAttachment usage");
             }
 
-            if (!format->supportsStorageUsage &&
-                (descriptor->usage & wgpu::TextureUsage::Storage)) {
+            if (!format->supportsStorageUsage && (usage & wgpu::TextureUsage::Storage)) {
                 return DAWN_VALIDATION_ERROR("Format cannot be used in storage textures");
             }
 
             constexpr wgpu::TextureUsage kValidMultiPlanarUsages = wgpu::TextureUsage::Sampled;
-            if (format->IsMultiPlanar() && !IsSubset(descriptor->usage, kValidMultiPlanarUsages)) {
+            if (format->IsMultiPlanar() && !IsSubset(usage, kValidMultiPlanarUsages)) {
                 return DAWN_VALIDATION_ERROR("Multi-planar format doesn't have valid usage.");
             }
 
@@ -251,19 +254,32 @@
 
     MaybeError ValidateTextureDescriptor(const DeviceBase* device,
                                          const TextureDescriptor* descriptor) {
-        if (descriptor->nextInChain != nullptr) {
-            return DAWN_VALIDATION_ERROR("nextInChain must be nullptr");
-        }
+        DAWN_TRY(ValidateSingleSType(descriptor->nextInChain,
+                                     wgpu::SType::DawnTextureInternalUsageDescriptor));
+
+        const DawnTextureInternalUsageDescriptor* internalUsageDesc = nullptr;
+        FindInChain(descriptor->nextInChain, &internalUsageDesc);
+
         if (descriptor->dimension == wgpu::TextureDimension::e1D) {
             return DAWN_VALIDATION_ERROR("1D textures aren't supported (yet).");
         }
 
+        if (internalUsageDesc != nullptr &&
+            !device->IsExtensionEnabled(Extension::DawnInternalUsages)) {
+            return DAWN_VALIDATION_ERROR("The dawn-internal-usages feature is not enabled");
+        }
+
         const Format* format;
         DAWN_TRY_ASSIGN(format, device->GetInternalFormat(descriptor->format));
 
-        DAWN_TRY(ValidateTextureUsage(descriptor, format));
+        wgpu::TextureUsage usage = descriptor->usage;
+        if (internalUsageDesc != nullptr) {
+            usage |= internalUsageDesc->internalUsage;
+        }
+
+        DAWN_TRY(ValidateTextureUsage(descriptor, usage, format));
         DAWN_TRY(ValidateTextureDimension(descriptor->dimension));
-        DAWN_TRY(ValidateSampleCount(descriptor, format));
+        DAWN_TRY(ValidateSampleCount(descriptor, usage, format));
 
         if (descriptor->size.width == 0 || descriptor->size.height == 0 ||
             descriptor->size.depthOrArrayLayers == 0 || descriptor->mipLevelCount == 0) {
@@ -426,15 +442,22 @@
           mMipLevelCount(descriptor->mipLevelCount),
           mSampleCount(descriptor->sampleCount),
           mUsage(descriptor->usage),
+          mInternalUsage(mUsage),
           mState(state) {
         uint32_t subresourceCount =
             mMipLevelCount * GetArrayLayers() * GetAspectCount(mFormat.aspects);
         mIsSubresourceContentInitializedAtIndex = std::vector<bool>(subresourceCount, false);
 
+        const DawnTextureInternalUsageDescriptor* internalUsageDesc = nullptr;
+        FindInChain(descriptor->nextInChain, &internalUsageDesc);
+        if (internalUsageDesc != nullptr) {
+            mInternalUsage |= internalUsageDesc->internalUsage;
+        }
+
         // Add readonly storage usage if the texture has a storage usage. The validation rules in
         // ValidateSyncScopeResourceUsage will make sure we don't use both at the same time.
-        if (mUsage & wgpu::TextureUsage::Storage) {
-            mUsage |= kReadOnlyStorageTexture;
+        if (mInternalUsage & wgpu::TextureUsage::Storage) {
+            mInternalUsage |= kReadOnlyStorageTexture;
         }
     }
 
@@ -505,6 +528,10 @@
         ASSERT(!IsError());
         return mUsage;
     }
+    wgpu::TextureUsage TextureBase::GetInternalUsage() const {
+        ASSERT(!IsError());
+        return mInternalUsage;
+    }
 
     TextureBase::TextureState TextureBase::GetTextureState() const {
         ASSERT(!IsError());
diff --git a/src/dawn_native/Texture.h b/src/dawn_native/Texture.h
index d26053c..093a94d 100644
--- a/src/dawn_native/Texture.h
+++ b/src/dawn_native/Texture.h
@@ -61,7 +61,13 @@
         SubresourceRange GetAllSubresources() const;
         uint32_t GetSampleCount() const;
         uint32_t GetSubresourceCount() const;
+
+        // |GetUsage| returns the usage with which the texture was created using the base WebGPU
+        // API. The dawn-internal-usages extension may add additional usages. |GetInternalUsage|
+        // returns the union of base usage and the usages added by the extension.
         wgpu::TextureUsage GetUsage() const;
+        wgpu::TextureUsage GetInternalUsage() const;
+
         TextureState GetTextureState() const;
         uint32_t GetSubresourceIndex(uint32_t mipLevel, uint32_t arraySlice, Aspect aspect) const;
         bool IsSubresourceContentInitialized(const SubresourceRange& range) const;
@@ -100,6 +106,7 @@
         uint32_t mMipLevelCount;
         uint32_t mSampleCount;
         wgpu::TextureUsage mUsage = wgpu::TextureUsage::None;
+        wgpu::TextureUsage mInternalUsage = wgpu::TextureUsage::None;
         TextureState mState;
 
         // TODO(crbug.com/dawn/845): Use a more optimized data structure to save space
diff --git a/src/dawn_native/d3d12/TextureD3D12.cpp b/src/dawn_native/d3d12/TextureD3D12.cpp
index 61f9411..d76e021 100644
--- a/src/dawn_native/d3d12/TextureD3D12.cpp
+++ b/src/dawn_native/d3d12/TextureD3D12.cpp
@@ -496,8 +496,8 @@
 
         // This will need to be much more nuanced when WebGPU has
         // texture view compatibility rules.
-        const bool needsTypelessFormat =
-            GetFormat().HasDepthOrStencil() && (GetUsage() & wgpu::TextureUsage::Sampled) != 0;
+        const bool needsTypelessFormat = GetFormat().HasDepthOrStencil() &&
+                                         (GetInternalUsage() & wgpu::TextureUsage::Sampled) != 0;
 
         DXGI_FORMAT dxgiFormat = needsTypelessFormat
                                      ? D3D12TypelessTextureFormat(GetFormat().format)
@@ -509,7 +509,7 @@
         resourceDescriptor.SampleDesc.Quality = 0;
         resourceDescriptor.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
         resourceDescriptor.Flags =
-            D3D12ResourceFlags(GetUsage(), GetFormat(), IsMultisampledTexture());
+            D3D12ResourceFlags(GetInternalUsage(), GetFormat(), IsMultisampledTexture());
         mD3D12ResourceFlags = resourceDescriptor.Flags;
 
         DAWN_TRY_ASSIGN(mResourceAllocation,
diff --git a/src/dawn_native/metal/TextureMTL.h b/src/dawn_native/metal/TextureMTL.h
index 07ffe23..bfcf02f 100644
--- a/src/dawn_native/metal/TextureMTL.h
+++ b/src/dawn_native/metal/TextureMTL.h
@@ -56,6 +56,8 @@
         using TextureBase::TextureBase;
         ~Texture() override;
 
+        NSRef<MTLTextureDescriptor> CreateMetalTextureDescriptor() const;
+
         MaybeError InitializeAsInternalTexture(const TextureDescriptor* descriptor);
         MaybeError InitializeFromIOSurface(const ExternalImageDescriptor* descriptor,
                                            const TextureDescriptor* textureDescriptor,
diff --git a/src/dawn_native/metal/TextureMTL.mm b/src/dawn_native/metal/TextureMTL.mm
index 3567738..fbce964 100644
--- a/src/dawn_native/metal/TextureMTL.mm
+++ b/src/dawn_native/metal/TextureMTL.mm
@@ -302,42 +302,38 @@
         return {};
     }
 
-    NSRef<MTLTextureDescriptor> CreateMetalTextureDescriptor(DeviceBase* device,
-                                                             const TextureDescriptor* descriptor) {
+    NSRef<MTLTextureDescriptor> Texture::CreateMetalTextureDescriptor() const {
         NSRef<MTLTextureDescriptor> mtlDescRef = AcquireNSRef([MTLTextureDescriptor new]);
         MTLTextureDescriptor* mtlDesc = mtlDescRef.Get();
 
-        mtlDesc.width = descriptor->size.width;
-        mtlDesc.height = descriptor->size.height;
-        mtlDesc.sampleCount = descriptor->sampleCount;
+        mtlDesc.width = GetWidth();
+        mtlDesc.height = GetHeight();
+        mtlDesc.sampleCount = GetSampleCount();
         // TODO: add MTLTextureUsagePixelFormatView when needed when we support format
         // reinterpretation.
-        mtlDesc.usage = MetalTextureUsage(device->GetValidInternalFormat(descriptor->format),
-                                          descriptor->usage, descriptor->sampleCount);
-        mtlDesc.pixelFormat = MetalPixelFormat(descriptor->format);
-        mtlDesc.mipmapLevelCount = descriptor->mipLevelCount;
+        mtlDesc.usage = MetalTextureUsage(GetFormat(), GetInternalUsage(), GetSampleCount());
+        mtlDesc.pixelFormat = MetalPixelFormat(GetFormat().format);
+        mtlDesc.mipmapLevelCount = GetNumMipLevels();
         mtlDesc.storageMode = MTLStorageModePrivate;
 
         // Choose the correct MTLTextureType and paper over differences in how the array layer count
         // is specified.
-        mtlDesc.depth = descriptor->size.depthOrArrayLayers;
-        mtlDesc.arrayLength = 1;
-        switch (descriptor->dimension) {
+        switch (GetDimension()) {
             case wgpu::TextureDimension::e2D:
-                if (mtlDesc.depth > 1) {
+                mtlDesc.depth = 1;
+                mtlDesc.arrayLength = GetArrayLayers();
+                if (mtlDesc.arrayLength > 1) {
                     ASSERT(mtlDesc.sampleCount == 1);
                     mtlDesc.textureType = MTLTextureType2DArray;
-                    mtlDesc.arrayLength = mtlDesc.depth;
-                    mtlDesc.depth = 1;
+                } else if (mtlDesc.sampleCount > 1) {
+                    mtlDesc.textureType = MTLTextureType2DMultisample;
                 } else {
-                    if (mtlDesc.sampleCount > 1) {
-                        mtlDesc.textureType = MTLTextureType2DMultisample;
-                    } else {
-                        mtlDesc.textureType = MTLTextureType2D;
-                    }
+                    mtlDesc.textureType = MTLTextureType2D;
                 }
                 break;
             case wgpu::TextureDimension::e3D:
+                mtlDesc.depth = GetDepth();
+                mtlDesc.arrayLength = 1;
                 ASSERT(mtlDesc.sampleCount == 1);
                 mtlDesc.textureType = MTLTextureType3D;
                 break;
@@ -386,7 +382,7 @@
     MaybeError Texture::InitializeAsInternalTexture(const TextureDescriptor* descriptor) {
         Device* device = ToBackend(GetDevice());
 
-        NSRef<MTLTextureDescriptor> mtlDesc = CreateMetalTextureDescriptor(device, descriptor);
+        NSRef<MTLTextureDescriptor> mtlDesc = CreateMetalTextureDescriptor();
         mMtlUsage = [*mtlDesc usage];
         mMtlTexture =
             AcquireNSPRef([device->GetMTLDevice() newTextureWithDescriptor:mtlDesc.Get()]);
@@ -405,7 +401,7 @@
 
     void Texture::InitializeAsWrapping(const TextureDescriptor* descriptor,
                                        NSPRef<id<MTLTexture>> wrapped) {
-        NSRef<MTLTextureDescriptor> mtlDesc = CreateMetalTextureDescriptor(GetDevice(), descriptor);
+        NSRef<MTLTextureDescriptor> mtlDesc = CreateMetalTextureDescriptor();
         mMtlUsage = [*mtlDesc usage];
         mMtlTexture = std::move(wrapped);
     }
@@ -416,8 +412,7 @@
                                                 uint32_t plane) {
         Device* device = ToBackend(GetDevice());
 
-        NSRef<MTLTextureDescriptor> mtlDesc =
-            CreateMetalTextureDescriptor(device, textureDescriptor);
+        NSRef<MTLTextureDescriptor> mtlDesc = CreateMetalTextureDescriptor();
         [*mtlDesc setStorageMode:kIOSurfaceStorageMode];
 
         mMtlUsage = [*mtlDesc usage];
@@ -667,7 +662,7 @@
         Texture* texture = ToBackend(GetTexture());
         id<MTLTexture> mtlTexture = texture->GetMTLTexture();
 
-        if (!UsageNeedsTextureView(texture->GetUsage())) {
+        if (!UsageNeedsTextureView(texture->GetInternalUsage())) {
             mMtlTextureView = nullptr;
         } else if (!RequiresCreatingNewTextureView(texture, descriptor)) {
             mMtlTextureView = mtlTexture;
diff --git a/src/dawn_native/null/DeviceNull.cpp b/src/dawn_native/null/DeviceNull.cpp
index 240df53..6ad06ee 100644
--- a/src/dawn_native/null/DeviceNull.cpp
+++ b/src/dawn_native/null/DeviceNull.cpp
@@ -31,7 +31,7 @@
         mAdapterType = wgpu::AdapterType::CPU;
 
         // Enable all extensions by default for the convenience of tests.
-        mSupportedExtensions.extensionsBitSet.flip();
+        mSupportedExtensions.extensionsBitSet.set();
     }
 
     Adapter::~Adapter() = default;
diff --git a/src/dawn_native/vulkan/DeviceVk.cpp b/src/dawn_native/vulkan/DeviceVk.cpp
index d692caa..ad9dce7 100644
--- a/src/dawn_native/vulkan/DeviceVk.cpp
+++ b/src/dawn_native/vulkan/DeviceVk.cpp
@@ -16,6 +16,7 @@
 
 #include "common/Platform.h"
 #include "dawn_native/BackendConnection.h"
+#include "dawn_native/ChainUtils_autogen.h"
 #include "dawn_native/Error.h"
 #include "dawn_native/ErrorData.h"
 #include "dawn_native/VulkanBackend.h"
@@ -698,6 +699,14 @@
         const TextureDescriptor* textureDescriptor =
             reinterpret_cast<const TextureDescriptor*>(descriptor->cTextureDescriptor);
 
+        const DawnTextureInternalUsageDescriptor* internalUsageDesc = nullptr;
+        FindInChain(textureDescriptor->nextInChain, &internalUsageDesc);
+
+        wgpu::TextureUsage usage = textureDescriptor->usage;
+        if (internalUsageDesc != nullptr) {
+            usage |= internalUsageDesc->internalUsage;
+        }
+
         // Check services support this combination of handle type / image info
         if (!mExternalSemaphoreService->Supported()) {
             return DAWN_VALIDATION_ERROR("External semaphore usage not supported");
@@ -705,8 +714,7 @@
         if (!mExternalMemoryService->SupportsImportMemory(
                 VulkanImageFormat(this, textureDescriptor->format), VK_IMAGE_TYPE_2D,
                 VK_IMAGE_TILING_OPTIMAL,
-                VulkanImageUsage(textureDescriptor->usage,
-                                 GetValidInternalFormat(textureDescriptor->format)),
+                VulkanImageUsage(usage, GetValidInternalFormat(textureDescriptor->format)),
                 VK_IMAGE_CREATE_ALIAS_BIT_KHR)) {
             return DAWN_VALIDATION_ERROR("External memory usage not supported");
         }
diff --git a/src/dawn_native/vulkan/TextureVk.cpp b/src/dawn_native/vulkan/TextureVk.cpp
index cb6da68..348861a 100644
--- a/src/dawn_native/vulkan/TextureVk.cpp
+++ b/src/dawn_native/vulkan/TextureVk.cpp
@@ -398,7 +398,7 @@
                 // happen so we must prepare for the pessimistic case and always use the GENERAL
                 // layout.
             case wgpu::TextureUsage::Sampled:
-                if (texture->GetUsage() & wgpu::TextureUsage::Storage) {
+                if (texture->GetInternalUsage() & wgpu::TextureUsage::Storage) {
                     return VK_IMAGE_LAYOUT_GENERAL;
                 } else {
                     return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
@@ -538,7 +538,7 @@
         createInfo.flags = 0;
         createInfo.format = VulkanImageFormat(device, GetFormat().format);
         createInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
-        createInfo.usage = VulkanImageUsage(GetUsage(), GetFormat()) | extraUsages;
+        createInfo.usage = VulkanImageUsage(GetInternalUsage(), GetFormat()) | extraUsages;
         createInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
         createInfo.queueFamilyIndexCount = 0;
         createInfo.pQueueFamilyIndices = nullptr;
@@ -584,7 +584,7 @@
     MaybeError Texture::InitializeFromExternal(const ExternalImageDescriptorVk* descriptor,
                                                external_memory::Service* externalMemoryService) {
         VkFormat format = VulkanImageFormat(ToBackend(GetDevice()), GetFormat().format);
-        VkImageUsageFlags usage = VulkanImageUsage(GetUsage(), GetFormat());
+        VkImageUsageFlags usage = VulkanImageUsage(GetInternalUsage(), GetFormat());
         if (!externalMemoryService->SupportsCreateImage(descriptor, format, usage)) {
             return DAWN_VALIDATION_ERROR("Creating an image from external memory is not supported");
         }
diff --git a/src/tests/BUILD.gn b/src/tests/BUILD.gn
index c97727a..3ff1698 100644
--- a/src/tests/BUILD.gn
+++ b/src/tests/BUILD.gn
@@ -202,6 +202,7 @@
     "unittests/validation/ExternalTextureTests.cpp",
     "unittests/validation/GetBindGroupLayoutValidationTests.cpp",
     "unittests/validation/IndexBufferValidationTests.cpp",
+    "unittests/validation/InternalUsageValidationTests.cpp",
     "unittests/validation/MinimumBufferSizeValidationTests.cpp",
     "unittests/validation/MultipleDeviceTests.cpp",
     "unittests/validation/QueryValidationTests.cpp",
diff --git a/src/tests/unittests/validation/InternalUsageValidationTests.cpp b/src/tests/unittests/validation/InternalUsageValidationTests.cpp
new file mode 100644
index 0000000..a053585
--- /dev/null
+++ b/src/tests/unittests/validation/InternalUsageValidationTests.cpp
@@ -0,0 +1,159 @@
+// Copyright 2021 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 "tests/unittests/validation/ValidationTest.h"
+
+#include "utils/WGPUHelpers.h"
+
+class TextureInternalUsageValidationDisabledTest : public ValidationTest {};
+
+// Test that using the extension is an error if it is not enabled
+TEST_F(TextureInternalUsageValidationDisabledTest, RequiresExtension) {
+    wgpu::TextureDescriptor textureDesc = {};
+    textureDesc.size = {1, 1};
+    textureDesc.usage = wgpu::TextureUsage::CopySrc;
+    textureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
+
+    // Control case: Normal texture creation works
+    device.CreateTexture(&textureDesc);
+
+    wgpu::DawnTextureInternalUsageDescriptor internalDesc = {};
+    textureDesc.nextInChain = &internalDesc;
+
+    // Error with chained extension struct.
+    ASSERT_DEVICE_ERROR(device.CreateTexture(&textureDesc));
+
+    // Also does not work with various internal usages.
+    internalDesc.internalUsage = wgpu::TextureUsage::CopySrc;
+    ASSERT_DEVICE_ERROR(device.CreateTexture(&textureDesc));
+
+    internalDesc.internalUsage = wgpu::TextureUsage::CopyDst;
+    ASSERT_DEVICE_ERROR(device.CreateTexture(&textureDesc));
+}
+
+class TextureInternalUsageValidationTest : public ValidationTest {
+    WGPUDevice CreateTestDevice() override {
+        dawn_native::DeviceDescriptor descriptor;
+        descriptor.requiredExtensions.push_back("dawn-internal-usages");
+
+        return adapter.CreateDevice(&descriptor);
+    }
+};
+
+// Test that internal usages can be passed in a chained descriptor.
+TEST_F(TextureInternalUsageValidationTest, Basic) {
+    wgpu::TextureDescriptor textureDesc = {};
+    textureDesc.size = {1, 1};
+    textureDesc.usage = wgpu::TextureUsage::CopySrc;
+    textureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
+
+    wgpu::DawnTextureInternalUsageDescriptor internalDesc = {};
+    textureDesc.nextInChain = &internalDesc;
+
+    // Internal usage: none
+    device.CreateTexture(&textureDesc);
+
+    // Internal usage is the same as the base usage.
+    internalDesc.internalUsage = wgpu::TextureUsage::CopySrc;
+    device.CreateTexture(&textureDesc);
+
+    // Internal usage adds to the base usage.
+    internalDesc.internalUsage = wgpu::TextureUsage::CopyDst;
+    device.CreateTexture(&textureDesc);
+}
+
+// Test that internal usages takes part in other validation that
+// depends on the usage.
+TEST_F(TextureInternalUsageValidationTest, UsageValidation) {
+    {
+        wgpu::TextureDescriptor textureDesc = {};
+        textureDesc.size = {1, 1};
+        textureDesc.usage = wgpu::TextureUsage::CopySrc;
+        textureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
+
+        wgpu::DawnTextureInternalUsageDescriptor internalDesc = {};
+        textureDesc.nextInChain = &internalDesc;
+
+        // Internal usage adds an invalid usage.
+        internalDesc.internalUsage = static_cast<wgpu::TextureUsage>(-1);
+        ASSERT_DEVICE_ERROR(device.CreateTexture(&textureDesc));
+    }
+
+    {
+        wgpu::TextureDescriptor textureDesc = {};
+        textureDesc.size = {1, 1};
+        textureDesc.usage = wgpu::TextureUsage::CopySrc;
+        textureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
+        textureDesc.sampleCount = 4;
+
+        // Control case: multisampled texture
+        device.CreateTexture(&textureDesc);
+
+        wgpu::DawnTextureInternalUsageDescriptor internalDesc = {};
+        textureDesc.nextInChain = &internalDesc;
+
+        // OK: internal usage adds nothing.
+        device.CreateTexture(&textureDesc);
+
+        // Internal usage adds storage usage which is invalid
+        // with multisampling.
+        internalDesc.internalUsage = wgpu::TextureUsage::Storage;
+        ASSERT_DEVICE_ERROR(device.CreateTexture(&textureDesc));
+    }
+}
+
+// Test that internal usage does not add to the validated usage
+// for command encoding
+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());
+    }
+}