D3D12: Enable external texture reuse

This change allows multiple Dawn textures to be created from the same
D3D11 resource. This avoids re-opening the shared handle by caching the
D3D12 resource outside of the Dawn texture.

Re-opening the handle costs 5-10% of CPU cycles per frame, which far
exceeded syncronization costs.

In a future change, WrapSharedHandle will be depreciated.

BUG=dawn:625

Change-Id: If0d2dc9b7445ec3ae718bc5305164db88057c4ea
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/42140
Commit-Queue: Bryan Bernhart <bryan.bernhart@intel.com>
Reviewed-by: Austin Eng <enga@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/dawn_native/d3d12/D3D12Backend.cpp b/src/dawn_native/d3d12/D3D12Backend.cpp
index aac8968..8d992f4 100644
--- a/src/dawn_native/d3d12/D3D12Backend.cpp
+++ b/src/dawn_native/d3d12/D3D12Backend.cpp
@@ -51,6 +51,83 @@
         : ExternalImageDescriptor(ExternalImageType::DXGISharedHandle) {
     }
 
+    ExternalImageDXGI::ExternalImageDXGI(ComPtr<ID3D12Resource> d3d12Resource,
+                                         const WGPUTextureDescriptor* descriptor)
+        : mD3D12Resource(std::move(d3d12Resource)),
+          mUsage(descriptor->usage),
+          mDimension(descriptor->dimension),
+          mSize(descriptor->size),
+          mFormat(descriptor->format),
+          mMipLevelCount(descriptor->mipLevelCount),
+          mSampleCount(descriptor->sampleCount) {
+        ASSERT(descriptor->nextInChain == nullptr);
+    }
+
+    WGPUTexture ExternalImageDXGI::ProduceTexture(
+        WGPUDevice device,
+        const ExternalImageAccessDescriptorDXGIKeyedMutex* descriptor) {
+        Device* backendDevice = reinterpret_cast<Device*>(device);
+
+        TextureDescriptor textureDescriptor = {};
+        textureDescriptor.usage = static_cast<wgpu::TextureUsage>(mUsage);
+        textureDescriptor.dimension = static_cast<wgpu::TextureDimension>(mDimension);
+        textureDescriptor.size = {mSize.width, mSize.height, mSize.depth};
+        textureDescriptor.format = static_cast<wgpu::TextureFormat>(mFormat);
+        textureDescriptor.mipLevelCount = mMipLevelCount;
+        textureDescriptor.sampleCount = mSampleCount;
+
+        Ref<TextureBase> texture = backendDevice->CreateExternalTexture(
+            &textureDescriptor, mD3D12Resource, ExternalMutexSerial(descriptor->acquireMutexKey),
+            descriptor->isSwapChainTexture, descriptor->isInitialized);
+        return reinterpret_cast<WGPUTexture>(texture.Detach());
+    }
+
+    // static
+    std::unique_ptr<ExternalImageDXGI> ExternalImageDXGI::Create(
+        WGPUDevice device,
+        const ExternalImageDescriptorDXGISharedHandle* descriptor) {
+        Device* backendDevice = reinterpret_cast<Device*>(device);
+
+        Microsoft::WRL::ComPtr<ID3D12Resource> d3d12Resource;
+        if (FAILED(backendDevice->GetD3D12Device()->OpenSharedHandle(
+                descriptor->sharedHandle, IID_PPV_ARGS(&d3d12Resource)))) {
+            return nullptr;
+        }
+
+        const TextureDescriptor* textureDescriptor =
+            reinterpret_cast<const TextureDescriptor*>(descriptor->cTextureDescriptor);
+
+        if (backendDevice->ConsumedError(
+                ValidateTextureDescriptor(backendDevice, textureDescriptor))) {
+            return nullptr;
+        }
+
+        if (backendDevice->ConsumedError(
+                ValidateTextureDescriptorCanBeWrapped(textureDescriptor))) {
+            return nullptr;
+        }
+
+        if (backendDevice->ConsumedError(
+                ValidateD3D12TextureCanBeWrapped(d3d12Resource.Get(), textureDescriptor))) {
+            return nullptr;
+        }
+
+        // Shared handle is assumed to support resource sharing capability. The resource
+        // shared capability tier must agree to share resources between D3D devices.
+        const Format* format =
+            backendDevice->GetInternalFormat(textureDescriptor->format).AcquireSuccess();
+        if (format->IsMultiPlanar()) {
+            if (backendDevice->ConsumedError(ValidateD3D12VideoTextureCanBeShared(
+                    backendDevice, D3D12TextureFormat(textureDescriptor->format)))) {
+                return nullptr;
+            }
+        }
+
+        std::unique_ptr<ExternalImageDXGI> result(
+            new ExternalImageDXGI(std::move(d3d12Resource), descriptor->cTextureDescriptor));
+        return result;
+    }
+
     uint64_t SetExternalMemoryReservation(WGPUDevice device,
                                           uint64_t requestedReservationSize,
                                           MemorySegment memorySegment) {
@@ -62,11 +139,18 @@
 
     WGPUTexture WrapSharedHandle(WGPUDevice device,
                                  const ExternalImageDescriptorDXGISharedHandle* descriptor) {
-        Device* backendDevice = reinterpret_cast<Device*>(device);
-        Ref<TextureBase> texture = backendDevice->WrapSharedHandle(
-            descriptor, descriptor->sharedHandle, ExternalMutexSerial(descriptor->acquireMutexKey),
-            descriptor->isSwapChainTexture);
-        return reinterpret_cast<WGPUTexture>(texture.Detach());
+        std::unique_ptr<ExternalImageDXGI> externalImage =
+            ExternalImageDXGI::Create(device, descriptor);
+        if (externalImage == nullptr) {
+            return nullptr;
+        }
+
+        ExternalImageAccessDescriptorDXGIKeyedMutex externalAccessDesc = {};
+        externalAccessDesc.isInitialized = descriptor->isInitialized;
+        externalAccessDesc.isSwapChainTexture = descriptor->isSwapChainTexture;
+        externalAccessDesc.acquireMutexKey = descriptor->acquireMutexKey;
+
+        return externalImage->ProduceTexture(device, &externalAccessDesc);
     }
 
     AdapterDiscoveryOptions::AdapterDiscoveryOptions(ComPtr<IDXGIAdapter> adapter)
diff --git a/src/dawn_native/d3d12/DeviceD3D12.cpp b/src/dawn_native/d3d12/DeviceD3D12.cpp
index 9d5041f..6b66900 100644
--- a/src/dawn_native/d3d12/DeviceD3D12.cpp
+++ b/src/dawn_native/d3d12/DeviceD3D12.cpp
@@ -436,14 +436,16 @@
                                                          initialUsage);
     }
 
-    Ref<TextureBase> Device::WrapSharedHandle(const ExternalImageDescriptor* descriptor,
-                                              HANDLE sharedHandle,
-                                              ExternalMutexSerial acquireMutexKey,
-                                              bool isSwapChainTexture) {
+    Ref<TextureBase> Device::CreateExternalTexture(const TextureDescriptor* descriptor,
+                                                   ComPtr<ID3D12Resource> d3d12Texture,
+                                                   ExternalMutexSerial acquireMutexKey,
+                                                   bool isSwapChainTexture,
+                                                   bool isInitialized) {
         Ref<Texture> dawnTexture;
-        if (ConsumedError(Texture::Create(this, descriptor, sharedHandle, acquireMutexKey,
-                                          isSwapChainTexture),
-                          &dawnTexture)) {
+        if (ConsumedError(
+                Texture::CreateExternalImage(this, descriptor, std::move(d3d12Texture),
+                                             acquireMutexKey, isSwapChainTexture, isInitialized),
+                &dawnTexture)) {
             return nullptr;
         }
         return {dawnTexture};
diff --git a/src/dawn_native/d3d12/DeviceD3D12.h b/src/dawn_native/d3d12/DeviceD3D12.h
index 113f819..40f029f 100644
--- a/src/dawn_native/d3d12/DeviceD3D12.h
+++ b/src/dawn_native/d3d12/DeviceD3D12.h
@@ -121,10 +121,11 @@
 
         StagingDescriptorAllocator* GetDepthStencilViewAllocator() const;
 
-        Ref<TextureBase> WrapSharedHandle(const ExternalImageDescriptor* descriptor,
-                                          HANDLE sharedHandle,
-                                          ExternalMutexSerial acquireMutexKey,
-                                          bool isSwapChainTexture);
+        Ref<TextureBase> CreateExternalTexture(const TextureDescriptor* descriptor,
+                                               ComPtr<ID3D12Resource> d3d12Texture,
+                                               ExternalMutexSerial acquireMutexKey,
+                                               bool isSwapChainTexture,
+                                               bool isInitialized);
         ResultOrError<ComPtr<IDXGIKeyedMutex>> CreateKeyedMutexForTexture(
             ID3D12Resource* d3d12Resource);
         void ReleaseKeyedMutexForTexture(ComPtr<IDXGIKeyedMutex> dxgiKeyedMutex);
diff --git a/src/dawn_native/d3d12/TextureD3D12.cpp b/src/dawn_native/d3d12/TextureD3D12.cpp
index 960b09a..4ef7175 100644
--- a/src/dawn_native/d3d12/TextureD3D12.cpp
+++ b/src/dawn_native/d3d12/TextureD3D12.cpp
@@ -418,27 +418,25 @@
     }
 
     // static
-    ResultOrError<Ref<Texture>> Texture::Create(Device* device,
-                                                const ExternalImageDescriptor* descriptor,
-                                                HANDLE sharedHandle,
-                                                ExternalMutexSerial acquireMutexKey,
-                                                bool isSwapChainTexture) {
-        const TextureDescriptor* textureDescriptor =
-            reinterpret_cast<const TextureDescriptor*>(descriptor->cTextureDescriptor);
-
+    ResultOrError<Ref<Texture>> Texture::CreateExternalImage(Device* device,
+                                                             const TextureDescriptor* descriptor,
+                                                             ComPtr<ID3D12Resource> d3d12Texture,
+                                                             ExternalMutexSerial acquireMutexKey,
+                                                             bool isSwapChainTexture,
+                                                             bool isInitialized) {
         Ref<Texture> dawnTexture =
-            AcquireRef(new Texture(device, textureDescriptor, TextureState::OwnedExternal));
-        DAWN_TRY(dawnTexture->InitializeAsExternalTexture(textureDescriptor, sharedHandle,
+            AcquireRef(new Texture(device, descriptor, TextureState::OwnedExternal));
+        DAWN_TRY(dawnTexture->InitializeAsExternalTexture(descriptor, std::move(d3d12Texture),
                                                           acquireMutexKey, isSwapChainTexture));
 
         // Importing a multi-planar format must be initialized. This is required because
         // a shared multi-planar format cannot be initialized by Dawn.
-        if (!descriptor->isInitialized && dawnTexture->GetFormat().IsMultiPlanar()) {
+        if (!isInitialized && dawnTexture->GetFormat().IsMultiPlanar()) {
             return DAWN_VALIDATION_ERROR(
                 "Cannot create a multi-planar formatted texture without being initialized");
         }
 
-        dawnTexture->SetIsSubresourceContentInitialized(descriptor->isInitialized,
+        dawnTexture->SetIsSubresourceContentInitialized(isInitialized,
                                                         dawnTexture->GetAllSubresources());
         return std::move(dawnTexture);
     }
@@ -454,30 +452,13 @@
     }
 
     MaybeError Texture::InitializeAsExternalTexture(const TextureDescriptor* descriptor,
-                                                    HANDLE sharedHandle,
+                                                    ComPtr<ID3D12Resource> d3d12Texture,
                                                     ExternalMutexSerial acquireMutexKey,
                                                     bool isSwapChainTexture) {
         Device* dawnDevice = ToBackend(GetDevice());
-        DAWN_TRY(ValidateTextureDescriptor(dawnDevice, descriptor));
-        DAWN_TRY(ValidateTextureDescriptorCanBeWrapped(descriptor));
-
-        ComPtr<ID3D12Resource> d3d12Resource;
-        DAWN_TRY(CheckHRESULT(dawnDevice->GetD3D12Device()->OpenSharedHandle(
-                                  sharedHandle, IID_PPV_ARGS(&d3d12Resource)),
-                              "D3D12 opening shared handle"));
-
-        DAWN_TRY(ValidateD3D12TextureCanBeWrapped(d3d12Resource.Get(), descriptor));
-
-        // Shared handle is assumed to support resource sharing capability. The resource
-        // shared capability tier must agree to share resources between D3D devices.
-        if (GetFormat().IsMultiPlanar()) {
-            DAWN_TRY(ValidateD3D12VideoTextureCanBeShared(ToBackend(GetDevice()),
-                                                          D3D12TextureFormat(descriptor->format)));
-        }
 
         ComPtr<IDXGIKeyedMutex> dxgiKeyedMutex;
-        DAWN_TRY_ASSIGN(dxgiKeyedMutex,
-                        dawnDevice->CreateKeyedMutexForTexture(d3d12Resource.Get()));
+        DAWN_TRY_ASSIGN(dxgiKeyedMutex, dawnDevice->CreateKeyedMutexForTexture(d3d12Texture.Get()));
 
         DAWN_TRY(CheckHRESULT(dxgiKeyedMutex->AcquireSync(uint64_t(acquireMutexKey), INFINITE),
                               "D3D12 acquiring shared mutex"));
@@ -491,7 +472,7 @@
         // When creating the ResourceHeapAllocation, the resource heap is set to nullptr because the
         // texture is owned externally. The texture's owning entity must remain responsible for
         // memory management.
-        mResourceAllocation = {info, 0, std::move(d3d12Resource), nullptr};
+        mResourceAllocation = {info, 0, std::move(d3d12Texture), nullptr};
 
         return {};
     }
diff --git a/src/dawn_native/d3d12/TextureD3D12.h b/src/dawn_native/d3d12/TextureD3D12.h
index 2e2e089..8c55b3e 100644
--- a/src/dawn_native/d3d12/TextureD3D12.h
+++ b/src/dawn_native/d3d12/TextureD3D12.h
@@ -33,16 +33,18 @@
     MaybeError ValidateD3D12TextureCanBeWrapped(ID3D12Resource* d3d12Resource,
                                                 const TextureDescriptor* descriptor);
     MaybeError ValidateTextureDescriptorCanBeWrapped(const TextureDescriptor* descriptor);
+    MaybeError ValidateD3D12VideoTextureCanBeShared(Device* device, DXGI_FORMAT textureFormat);
 
     class Texture final : public TextureBase {
       public:
         static ResultOrError<Ref<Texture>> Create(Device* device,
                                                   const TextureDescriptor* descriptor);
-        static ResultOrError<Ref<Texture>> Create(Device* device,
-                                                  const ExternalImageDescriptor* descriptor,
-                                                  HANDLE sharedHandle,
-                                                  ExternalMutexSerial acquireMutexKey,
-                                                  bool isSwapChainTexture);
+        static ResultOrError<Ref<Texture>> CreateExternalImage(Device* device,
+                                                               const TextureDescriptor* descriptor,
+                                                               ComPtr<ID3D12Resource> d3d12Texture,
+                                                               ExternalMutexSerial acquireMutexKey,
+                                                               bool isSwapChainTexture,
+                                                               bool isInitialized);
         static ResultOrError<Ref<Texture>> Create(Device* device,
                                                   const TextureDescriptor* descriptor,
                                                   ComPtr<ID3D12Resource> d3d12Texture);
@@ -85,7 +87,7 @@
 
         MaybeError InitializeAsInternalTexture();
         MaybeError InitializeAsExternalTexture(const TextureDescriptor* descriptor,
-                                               HANDLE sharedHandle,
+                                               ComPtr<ID3D12Resource> d3d12Texture,
                                                ExternalMutexSerial acquireMutexKey,
                                                bool isSwapChainTexture);
         MaybeError InitializeAsSwapChainTexture(ComPtr<ID3D12Resource> d3d12Texture);
diff --git a/src/include/dawn_native/D3D12Backend.h b/src/include/dawn_native/D3D12Backend.h
index 035ad96..e713623 100644
--- a/src/include/dawn_native/D3D12Backend.h
+++ b/src/include/dawn_native/D3D12Backend.h
@@ -19,10 +19,14 @@
 #include <dawn_native/DawnNative.h>
 
 #include <DXGI1_4.h>
+#include <d3d12.h>
 #include <windows.h>
 #include <wrl/client.h>
 
+#include <memory>
+
 struct ID3D12Device;
+struct ID3D12Resource;
 
 namespace dawn_native { namespace d3d12 {
     DAWN_NATIVE_EXPORT Microsoft::WRL::ComPtr<ID3D12Device> GetD3D12Device(WGPUDevice device);
@@ -45,10 +49,46 @@
         ExternalImageDescriptorDXGISharedHandle();
 
         HANDLE sharedHandle;
+
+        // Warning: depreciated, replaced by ExternalImageAccessDescriptorDXGIKeyedMutex.
         uint64_t acquireMutexKey;
         bool isSwapChainTexture = false;
     };
 
+    struct DAWN_NATIVE_EXPORT ExternalImageAccessDescriptorDXGIKeyedMutex
+        : ExternalImageAccessDescriptor {
+      public:
+        uint64_t acquireMutexKey;
+        bool isSwapChainTexture = false;
+    };
+
+    class DAWN_NATIVE_EXPORT ExternalImageDXGI {
+      public:
+        // Note: SharedHandle must be a handle to a texture object.
+        static std::unique_ptr<ExternalImageDXGI> Create(
+            WGPUDevice device,
+            const ExternalImageDescriptorDXGISharedHandle* descriptor);
+
+        WGPUTexture ProduceTexture(WGPUDevice device,
+                                   const ExternalImageAccessDescriptorDXGIKeyedMutex* descriptor);
+
+      private:
+        ExternalImageDXGI(Microsoft::WRL::ComPtr<ID3D12Resource> d3d12Resource,
+                          const WGPUTextureDescriptor* descriptor);
+
+        Microsoft::WRL::ComPtr<ID3D12Resource> mD3D12Resource;
+
+        // Contents of WGPUTextureDescriptor are stored individually since the descriptor
+        // could outlive this image.
+        WGPUTextureUsageFlags mUsage;
+        WGPUTextureDimension mDimension;
+        WGPUExtent3D mSize;
+        WGPUTextureFormat mFormat;
+        uint32_t mMipLevelCount;
+        uint32_t mSampleCount;
+    };
+
+    // Warning: depreciated, replaced by ExternalImageDXGI::Create.
     // Note: SharedHandle must be a handle to a texture object.
     DAWN_NATIVE_EXPORT WGPUTexture
     WrapSharedHandle(WGPUDevice device, const ExternalImageDescriptorDXGISharedHandle* descriptor);
diff --git a/src/include/dawn_native/DawnNative.h b/src/include/dawn_native/DawnNative.h
index f161d4a..68e2feb 100644
--- a/src/include/dawn_native/DawnNative.h
+++ b/src/include/dawn_native/DawnNative.h
@@ -228,6 +228,11 @@
         ExternalImageDescriptor(ExternalImageType type);
     };
 
+    struct DAWN_NATIVE_EXPORT ExternalImageAccessDescriptor {
+      public:
+        bool isInitialized;  // Whether the texture is initialized on import
+    };
+
     struct DAWN_NATIVE_EXPORT ExternalImageExportInfo {
       public:
         const ExternalImageType type;
diff --git a/src/tests/end2end/D3D12ResourceWrappingTests.cpp b/src/tests/end2end/D3D12ResourceWrappingTests.cpp
index 003d011..72de44c 100644
--- a/src/tests/end2end/D3D12ResourceWrappingTests.cpp
+++ b/src/tests/end2end/D3D12ResourceWrappingTests.cpp
@@ -86,7 +86,9 @@
         void WrapSharedHandle(const wgpu::TextureDescriptor* dawnDesc,
                               const D3D11_TEXTURE2D_DESC* baseD3dDescriptor,
                               wgpu::Texture* dawnTexture,
-                              ID3D11Texture2D** d3d11TextureOut) const {
+                              ID3D11Texture2D** d3d11TextureOut,
+                              std::unique_ptr<dawn_native::d3d12::ExternalImageDXGI>*
+                                  externalImageOut = nullptr) const {
             ComPtr<ID3D11Texture2D> d3d11Texture;
             HRESULT hr = mD3d11Device->CreateTexture2D(baseD3dDescriptor, nullptr, &d3d11Texture);
             ASSERT_EQ(hr, S_OK);
@@ -101,19 +103,33 @@
                 &sharedHandle);
             ASSERT_EQ(hr, S_OK);
 
-            dawn_native::d3d12::ExternalImageDescriptorDXGISharedHandle externDesc;
-            externDesc.cTextureDescriptor =
+            dawn_native::d3d12::ExternalImageDescriptorDXGISharedHandle externalImageDesc;
+            externalImageDesc.cTextureDescriptor =
                 reinterpret_cast<const WGPUTextureDescriptor*>(dawnDesc);
-            externDesc.sharedHandle = sharedHandle;
-            externDesc.acquireMutexKey = 0;
-            WGPUTexture texture = dawn_native::d3d12::WrapSharedHandle(device.Get(), &externDesc);
+            externalImageDesc.sharedHandle = sharedHandle;
+
+            std::unique_ptr<dawn_native::d3d12::ExternalImageDXGI> externalImage =
+                dawn_native::d3d12::ExternalImageDXGI::Create(device.Get(), &externalImageDesc);
 
             // Now that we've created all of our resources, we can close the handle
             // since we no longer need it.
             ::CloseHandle(sharedHandle);
 
-            *dawnTexture = wgpu::Texture::Acquire(texture);
+            // Cannot access a non-existent external image (ex. validation error).
+            if (externalImage == nullptr) {
+                return;
+            }
+
+            dawn_native::d3d12::ExternalImageAccessDescriptorDXGIKeyedMutex externalAccessDesc;
+            externalAccessDesc.acquireMutexKey = 0;
+
+            *dawnTexture = wgpu::Texture::Acquire(
+                externalImage->ProduceTexture(device.Get(), &externalAccessDesc));
             *d3d11TextureOut = d3d11Texture.Detach();
+
+            if (externalImageOut != nullptr) {
+                *externalImageOut = std::move(externalImage);
+            }
         }
 
         static constexpr size_t kTestWidth = 10;
@@ -334,15 +350,20 @@
         hr = dxgiKeyedMutex->ReleaseSync(1);
         ASSERT_EQ(hr, S_OK);
 
-        dawn_native::d3d12::ExternalImageDescriptorDXGISharedHandle externDesc;
-        externDesc.cTextureDescriptor =
+        dawn_native::d3d12::ExternalImageDescriptorDXGISharedHandle externalImageDesc = {};
+        externalImageDesc.sharedHandle = sharedHandle;
+        externalImageDesc.cTextureDescriptor =
             reinterpret_cast<const WGPUTextureDescriptor*>(dawnDescriptor);
-        externDesc.sharedHandle = sharedHandle;
-        externDesc.acquireMutexKey = 1;
-        externDesc.isInitialized = isInitialized;
-        WGPUTexture dawnTexture = dawn_native::d3d12::WrapSharedHandle(device.Get(), &externDesc);
 
-        *dawnTextureOut = wgpu::Texture::Acquire(dawnTexture);
+        std::unique_ptr<dawn_native::d3d12::ExternalImageDXGI> externalImage =
+            dawn_native::d3d12::ExternalImageDXGI::Create(device.Get(), &externalImageDesc);
+
+        dawn_native::d3d12::ExternalImageAccessDescriptorDXGIKeyedMutex externalAccessDesc;
+        externalAccessDesc.acquireMutexKey = 1;
+        externalAccessDesc.isInitialized = isInitialized;
+
+        *dawnTextureOut = wgpu::Texture::Acquire(
+            externalImage->ProduceTexture(device.Get(), &externalAccessDesc));
         *d3d11TextureOut = d3d11Texture.Detach();
         *dxgiKeyedMutexOut = dxgiKeyedMutex.Detach();
     }
@@ -519,5 +540,50 @@
     EXPECT_PIXEL_RGBA8_EQ(RGBA8(0, 0, 0, 0), dawnTexture, 0, 0);
 }
 
+// 1. Create an external image from the DX11 texture.
+// 2. Produce two Dawn textures from the external image.
+// 3. Clear each Dawn texture and verify the texture was cleared to a unique color.
+TEST_P(D3D12SharedHandleUsageTests, ReuseExternalImage) {
+    DAWN_SKIP_TEST_IF(UsesWire());
+
+    // Create the first Dawn texture then clear it to red.
+    wgpu::Texture texture;
+    ComPtr<ID3D11Texture2D> d3d11Texture;
+    std::unique_ptr<dawn_native::d3d12::ExternalImageDXGI> externalImage;
+    WrapSharedHandle(&baseDawnDescriptor, &baseD3dDescriptor, &texture, &d3d11Texture,
+                     &externalImage);
+    {
+        const wgpu::Color solidRed{1.0f, 0.0f, 0.0f, 1.0f};
+        ASSERT_NE(texture.Get(), nullptr);
+        ClearImage(texture.Get(), solidRed);
+
+        EXPECT_PIXEL_RGBA8_EQ(RGBA8(0xFF, 0, 0, 0xFF), texture.Get(), 0, 0);
+    }
+
+    // Once finished with the first texture, destroy it so we may re-acquire the external image
+    // again.
+    texture.Destroy();
+
+    // Create another Dawn texture then clear it with another color.
+    dawn_native::d3d12::ExternalImageAccessDescriptorDXGIKeyedMutex externalAccessDesc;
+    externalAccessDesc.acquireMutexKey = 1;
+    externalAccessDesc.isInitialized = true;
+
+    texture =
+        wgpu::Texture::Acquire(externalImage->ProduceTexture(device.Get(), &externalAccessDesc));
+
+    // Check again that the new texture is still red
+    EXPECT_PIXEL_RGBA8_EQ(RGBA8(0xFF, 0, 0, 0xFF), texture.Get(), 0, 0);
+
+    // Clear the new texture to blue
+    {
+        const wgpu::Color solidBlue{0.0f, 0.0f, 1.0f, 1.0f};
+        ASSERT_NE(texture.Get(), nullptr);
+        ClearImage(texture.Get(), solidBlue);
+
+        EXPECT_PIXEL_RGBA8_EQ(RGBA8(0, 0, 0xFF, 0xFF), texture.Get(), 0, 0);
+    }
+}
+
 DAWN_INSTANTIATE_TEST(D3D12SharedHandleValidation, D3D12Backend());
 DAWN_INSTANTIATE_TEST(D3D12SharedHandleUsageTests, D3D12Backend());
diff --git a/src/tests/end2end/D3D12VideoViewsTests.cpp b/src/tests/end2end/D3D12VideoViewsTests.cpp
index a3f6551..6f7938d 100644
--- a/src/tests/end2end/D3D12VideoViewsTests.cpp
+++ b/src/tests/end2end/D3D12VideoViewsTests.cpp
@@ -139,9 +139,10 @@
             }
         }
 
-        wgpu::Texture CreateVideoTextureForTest(wgpu::TextureFormat format,
-                                                wgpu::TextureUsage usage,
-                                                bool isCheckerboard = false) {
+        void CreateVideoTextureForTest(wgpu::TextureFormat format,
+                                       wgpu::TextureUsage usage,
+                                       bool isCheckerboard,
+                                       wgpu::Texture* dawnTextureOut) {
             wgpu::TextureDescriptor textureDesc;
             textureDesc.format = format;
             textureDesc.dimension = wgpu::TextureDimension::e2D;
@@ -171,24 +172,17 @@
 
             ComPtr<ID3D11Texture2D> d3d11Texture;
             HRESULT hr = mD3d11Device->CreateTexture2D(&d3dDescriptor, &subres, &d3d11Texture);
-            EXPECT_EQ(hr, S_OK);
+            ASSERT_EQ(hr, S_OK);
 
             ComPtr<IDXGIResource1> dxgiResource;
             hr = d3d11Texture.As(&dxgiResource);
-            EXPECT_EQ(hr, S_OK);
+            ASSERT_EQ(hr, S_OK);
 
             HANDLE sharedHandle;
             hr = dxgiResource->CreateSharedHandle(
                 nullptr, DXGI_SHARED_RESOURCE_READ | DXGI_SHARED_RESOURCE_WRITE, nullptr,
                 &sharedHandle);
-            EXPECT_EQ(hr, S_OK);
-
-            dawn_native::d3d12::ExternalImageDescriptorDXGISharedHandle externDesc;
-            externDesc.cTextureDescriptor =
-                reinterpret_cast<const WGPUTextureDescriptor*>(&textureDesc);
-            externDesc.sharedHandle = sharedHandle;
-            externDesc.acquireMutexKey = 1;
-            externDesc.isInitialized = true;
+            ASSERT_EQ(hr, S_OK);
 
             // DX11 texture should be initialized upon CreateTexture2D. However, if we do not
             // acquire/release the keyed mutex before using the wrapped WebGPU texture, the WebGPU
@@ -205,13 +199,23 @@
 
             // Open the DX11 texture in Dawn from the shared handle and return it as a WebGPU
             // texture.
-            wgpu::Texture wgpuTexture = wgpu::Texture::Acquire(
-                dawn_native::d3d12::WrapSharedHandle(device.Get(), &externDesc));
+            dawn_native::d3d12::ExternalImageDescriptorDXGISharedHandle externalImageDesc;
+            externalImageDesc.cTextureDescriptor =
+                reinterpret_cast<const WGPUTextureDescriptor*>(&textureDesc);
+            externalImageDesc.sharedHandle = sharedHandle;
+
+            std::unique_ptr<dawn_native::d3d12::ExternalImageDXGI> externalImage =
+                dawn_native::d3d12::ExternalImageDXGI::Create(device.Get(), &externalImageDesc);
 
             // Handle is no longer needed once resources are created.
             ::CloseHandle(sharedHandle);
 
-            return wgpuTexture;
+            dawn_native::d3d12::ExternalImageAccessDescriptorDXGIKeyedMutex externalAccessDesc;
+            externalAccessDesc.acquireMutexKey = 1;
+            externalAccessDesc.isInitialized = true;
+
+            *dawnTextureOut = wgpu::Texture::Acquire(
+                externalImage->ProduceTexture(device.Get(), &externalAccessDesc));
         }
 
         // Vertex shader used to render a sampled texture into a quad.
@@ -259,8 +263,9 @@
 // Samples the luminance (Y) plane from an imported NV12 texture into a single channel of an RGBA
 // output attachment and checks for the expected pixel value in the rendered quad.
 TEST_P(D3D12VideoViewsTests, NV12SampleYtoR) {
-    wgpu::Texture wgpuTexture = CreateVideoTextureForTest(
-        wgpu::TextureFormat::R8BG8Biplanar420Unorm, wgpu::TextureUsage::Sampled);
+    wgpu::Texture wgpuTexture;
+    CreateVideoTextureForTest(wgpu::TextureFormat::R8BG8Biplanar420Unorm,
+                              wgpu::TextureUsage::Sampled, /*isCheckerboard*/ false, &wgpuTexture);
 
     wgpu::TextureViewDescriptor viewDesc;
     viewDesc.aspect = wgpu::TextureAspect::Plane0Only;
@@ -310,8 +315,9 @@
 // Samples the chrominance (UV) plane from an imported texture into two channels of an RGBA output
 // attachment and checks for the expected pixel value in the rendered quad.
 TEST_P(D3D12VideoViewsTests, NV12SampleUVtoRG) {
-    wgpu::Texture wgpuTexture = CreateVideoTextureForTest(
-        wgpu::TextureFormat::R8BG8Biplanar420Unorm, wgpu::TextureUsage::Sampled);
+    wgpu::Texture wgpuTexture;
+    CreateVideoTextureForTest(wgpu::TextureFormat::R8BG8Biplanar420Unorm,
+                              wgpu::TextureUsage::Sampled, /*isCheckerboard*/ false, &wgpuTexture);
 
     wgpu::TextureViewDescriptor viewDesc;
     viewDesc.aspect = wgpu::TextureAspect::Plane1Only;
@@ -362,8 +368,9 @@
 // Renders a NV12 "checkerboard" texture into a RGB quad then checks the color at specific
 // points to ensure the image has not been flipped.
 TEST_P(D3D12VideoViewsTests, NV12SampleYUVtoRGB) {
-    wgpu::Texture wgpuTexture = CreateVideoTextureForTest(
-        wgpu::TextureFormat::R8BG8Biplanar420Unorm, wgpu::TextureUsage::Sampled, true);
+    wgpu::Texture wgpuTexture;
+    CreateVideoTextureForTest(wgpu::TextureFormat::R8BG8Biplanar420Unorm,
+                              wgpu::TextureUsage::Sampled, /*isCheckerboard*/ true, &wgpuTexture);
 
     wgpu::TextureViewDescriptor lumaViewDesc;
     lumaViewDesc.aspect = wgpu::TextureAspect::Plane0Only;