Add D3D12 keyed shared mutexes to Dawn

Dawn creates a keyed shared mutex for all wrapped resources and acquires
the mutex before the texture is used in an ExecuteCommandList call.

To coordinate with external clients, backend API has been extended
to allow clients to get and set the acquire key.

Pending and queue command lists have now been merged into one.

A future change will adjust GetPendingCommandContext to return a raw
command context without the need for error handling.

Bug:dawn:217
Change-Id: Ia96c449c305586407153f05ce75a40794b96027e
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/12220
Reviewed-by: Austin Eng <enga@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Rafael Cintron <rafael.cintron@microsoft.com>
diff --git a/src/dawn_native/Texture.cpp b/src/dawn_native/Texture.cpp
index bcaa923..9485ab6 100644
--- a/src/dawn_native/Texture.cpp
+++ b/src/dawn_native/Texture.cpp
@@ -481,9 +481,7 @@
     }
 
     void TextureBase::DestroyInternal() {
-        if (mState == TextureState::OwnedInternal) {
-            DestroyImpl();
-        }
+        DestroyImpl();
         mState = TextureState::Destroyed;
     }
 
diff --git a/src/dawn_native/d3d12/CommandRecordingContext.cpp b/src/dawn_native/d3d12/CommandRecordingContext.cpp
index 4d927b8..76ea3b0 100644
--- a/src/dawn_native/d3d12/CommandRecordingContext.cpp
+++ b/src/dawn_native/d3d12/CommandRecordingContext.cpp
@@ -17,6 +17,11 @@
 
 namespace dawn_native { namespace d3d12 {
 
+    void CommandRecordingContext::AddToSharedTextureList(Texture* texture) {
+        ASSERT(IsOpen());
+        mSharedTextures.insert(texture);
+    }
+
     MaybeError CommandRecordingContext::Open(ID3D12Device* d3d12Device,
                                              CommandAllocatorManager* commandAllocationManager) {
         ASSERT(!IsOpen());
@@ -43,16 +48,30 @@
         return {};
     }
 
-    ResultOrError<ID3D12GraphicsCommandList*> CommandRecordingContext::Close() {
-        ASSERT(IsOpen());
-        mIsOpen = false;
-        MaybeError error =
-            CheckHRESULT(mD3d12CommandList->Close(), "D3D12 closing pending command list");
-        if (error.IsError()) {
-            mD3d12CommandList.Reset();
-            DAWN_TRY(std::move(error));
+    MaybeError CommandRecordingContext::ExecuteCommandList(ID3D12CommandQueue* d3d12CommandQueue) {
+        if (IsOpen()) {
+            // Shared textures must be transitioned to common state after the last usage in order
+            // for them to be used by other APIs like D3D11. We ensure this by transitioning to the
+            // common state right before command list submission. TransitionUsageNow itself ensures
+            // no unnecessary transitions happen if the resources is already in the common state.
+            for (Texture* texture : mSharedTextures) {
+                texture->TransitionUsageNow(this, D3D12_RESOURCE_STATE_COMMON);
+            }
+
+            MaybeError error =
+                CheckHRESULT(mD3d12CommandList->Close(), "D3D12 closing pending command list");
+            if (error.IsError()) {
+                Release();
+                DAWN_TRY(std::move(error));
+            }
+
+            ID3D12CommandList* d3d12CommandList = GetCommandList();
+            d3d12CommandQueue->ExecuteCommandLists(1, &d3d12CommandList);
+
+            mIsOpen = false;
+            mSharedTextures.clear();
         }
-        return mD3d12CommandList.Get();
+        return {};
     }
 
     ID3D12GraphicsCommandList* CommandRecordingContext::GetCommandList() const {
@@ -64,6 +83,7 @@
     void CommandRecordingContext::Release() {
         mD3d12CommandList.Reset();
         mIsOpen = false;
+        mSharedTextures.clear();
     }
 
     bool CommandRecordingContext::IsOpen() const {
diff --git a/src/dawn_native/d3d12/CommandRecordingContext.h b/src/dawn_native/d3d12/CommandRecordingContext.h
index 544dae9..a0b0c83 100644
--- a/src/dawn_native/d3d12/CommandRecordingContext.h
+++ b/src/dawn_native/d3d12/CommandRecordingContext.h
@@ -15,23 +15,31 @@
 #define DAWNNATIVE_D3D12_COMMANDRECORDINGCONTEXT_H_
 
 #include "dawn_native/Error.h"
+#include "dawn_native/d3d12/TextureD3D12.h"
 #include "dawn_native/d3d12/d3d12_platform.h"
 
+#include <set>
+
 namespace dawn_native { namespace d3d12 {
     class CommandAllocatorManager;
+    class Texture;
 
     class CommandRecordingContext {
       public:
+        void AddToSharedTextureList(Texture* texture);
         MaybeError Open(ID3D12Device* d3d12Device,
                         CommandAllocatorManager* commandAllocationManager);
-        ResultOrError<ID3D12GraphicsCommandList*> Close();
+
         ID3D12GraphicsCommandList* GetCommandList() const;
         void Release();
         bool IsOpen() const;
 
+        MaybeError ExecuteCommandList(ID3D12CommandQueue* d3d12CommandQueue);
+
       private:
         ComPtr<ID3D12GraphicsCommandList> mD3d12CommandList;
         bool mIsOpen = false;
+        std::set<Texture*> mSharedTextures;
     };
 }}  // namespace dawn_native::d3d12
 
diff --git a/src/dawn_native/d3d12/D3D12Backend.cpp b/src/dawn_native/d3d12/D3D12Backend.cpp
index 5d96593..f7a4952 100644
--- a/src/dawn_native/d3d12/D3D12Backend.cpp
+++ b/src/dawn_native/d3d12/D3D12Backend.cpp
@@ -20,6 +20,7 @@
 #include "common/SwapChainUtils.h"
 #include "dawn_native/d3d12/DeviceD3D12.h"
 #include "dawn_native/d3d12/NativeSwapChainImplD3D12.h"
+#include "dawn_native/d3d12/TextureD3D12.h"
 
 namespace dawn_native { namespace d3d12 {
 
@@ -47,11 +48,13 @@
 
     DawnTexture WrapSharedHandle(DawnDevice device,
                                  const DawnTextureDescriptor* descriptor,
-                                 HANDLE sharedHandle) {
+                                 HANDLE sharedHandle,
+                                 uint64_t acquireMutexKey) {
         Device* backendDevice = reinterpret_cast<Device*>(device);
         const TextureDescriptor* backendDescriptor =
             reinterpret_cast<const TextureDescriptor*>(descriptor);
-        TextureBase* texture = backendDevice->WrapSharedHandle(backendDescriptor, sharedHandle);
+        TextureBase* texture =
+            backendDevice->WrapSharedHandle(backendDescriptor, sharedHandle, acquireMutexKey);
         return reinterpret_cast<DawnTexture>(texture);
     }
 }}  // namespace dawn_native::d3d12
diff --git a/src/dawn_native/d3d12/DeviceD3D12.cpp b/src/dawn_native/d3d12/DeviceD3D12.cpp
index 4a2e981..efb2038 100644
--- a/src/dawn_native/d3d12/DeviceD3D12.cpp
+++ b/src/dawn_native/d3d12/DeviceD3D12.cpp
@@ -218,7 +218,7 @@
         mDescriptorHeapAllocator->Deallocate(mCompletedSerial);
         mMapRequestTracker->Tick(mCompletedSerial);
         mUsedComObjectRefs.ClearUpTo(mCompletedSerial);
-        DAWN_TRY(ExecuteCommandContext(nullptr));
+        DAWN_TRY(ExecutePendingCommandContext());
         DAWN_TRY(NextSerial());
         return {};
     }
@@ -243,26 +243,8 @@
         mUsedComObjectRefs.Enqueue(object, GetPendingCommandSerial());
     }
 
-    MaybeError Device::ExecuteCommandContext(CommandRecordingContext* commandContext) {
-        UINT numLists = 0;
-        std::array<ID3D12CommandList*, 2> d3d12CommandLists;
-
-        // If there are pending commands, prepend them to ExecuteCommandLists
-        if (mPendingCommands.IsOpen()) {
-            ID3D12GraphicsCommandList* d3d12CommandList;
-            DAWN_TRY_ASSIGN(d3d12CommandList, mPendingCommands.Close());
-            d3d12CommandLists[numLists++] = d3d12CommandList;
-        }
-        if (commandContext != nullptr) {
-            ID3D12GraphicsCommandList* d3d12CommandList;
-            DAWN_TRY_ASSIGN(d3d12CommandList, commandContext->Close());
-            d3d12CommandLists[numLists++] = d3d12CommandList;
-        }
-        if (numLists > 0) {
-            mCommandQueue->ExecuteCommandLists(numLists, d3d12CommandLists.data());
-        }
-
-        return {};
+    MaybeError Device::ExecutePendingCommandContext() {
+        return mPendingCommands.ExecuteCommandList(mCommandQueue.Get());
     }
 
     ResultOrError<BindGroupBase*> Device::CreateBindGroupImpl(
@@ -355,26 +337,62 @@
     }
 
     TextureBase* Device::WrapSharedHandle(const TextureDescriptor* descriptor,
-                                          HANDLE sharedHandle) {
-        if (ConsumedError(ValidateTextureDescriptor(this, descriptor))) {
+                                          HANDLE sharedHandle,
+                                          uint64_t acquireMutexKey) {
+        TextureBase* dawnTexture;
+        if (ConsumedError(Texture::Create(this, descriptor, sharedHandle, acquireMutexKey),
+                          &dawnTexture))
             return nullptr;
-        }
 
-        if (ConsumedError(ValidateTextureDescriptorCanBeWrapped(descriptor))) {
-            return nullptr;
-        }
-
-        ComPtr<ID3D12Resource> d3d12Resource;
-        const HRESULT hr =
-            mD3d12Device->OpenSharedHandle(sharedHandle, IID_PPV_ARGS(&d3d12Resource));
-        if (FAILED(hr)) {
-            return nullptr;
-        }
-
-        if (ConsumedError(ValidateD3D12TextureCanBeWrapped(d3d12Resource.Get(), descriptor))) {
-            return nullptr;
-        }
-
-        return new Texture(this, descriptor, std::move(d3d12Resource));
+        return dawnTexture;
     }
+
+    // We use IDXGIKeyedMutexes to synchronize access between D3D11 and D3D12. D3D11/12 fences
+    // are a viable alternative but are, unfortunately, not available on all versions of Windows
+    // 10. Since D3D12 does not directly support keyed mutexes, we need to wrap the D3D12
+    // resource using 11on12 and QueryInterface the D3D11 representation for the keyed mutex.
+    ResultOrError<ComPtr<IDXGIKeyedMutex>> Device::CreateKeyedMutexForTexture(
+        ID3D12Resource* d3d12Resource) {
+        if (mD3d11On12Device == nullptr) {
+            ComPtr<ID3D11Device> d3d11Device;
+            ComPtr<ID3D11DeviceContext> d3d11DeviceContext;
+            D3D_FEATURE_LEVEL d3dFeatureLevel;
+            IUnknown* const iUnknownQueue = mCommandQueue.Get();
+            DAWN_TRY(CheckHRESULT(GetFunctions()->d3d11on12CreateDevice(
+                                      mD3d12Device.Get(), 0, nullptr, 0, &iUnknownQueue, 1, 1,
+                                      &d3d11Device, &d3d11DeviceContext, &d3dFeatureLevel),
+                                  "D3D12 11on12 device create"));
+            DAWN_TRY(CheckHRESULT(d3d11Device.As(&mD3d11On12Device),
+                                  "D3D12 QueryInterface ID3D11Device to ID3D11On12Device"));
+        }
+
+        ComPtr<ID3D11Texture2D> d3d11Texture;
+        D3D11_RESOURCE_FLAGS resourceFlags;
+        resourceFlags.BindFlags = 0;
+        resourceFlags.MiscFlags = D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX;
+        resourceFlags.CPUAccessFlags = 0;
+        resourceFlags.StructureByteStride = 0;
+        DAWN_TRY(CheckHRESULT(mD3d11On12Device->CreateWrappedResource(
+                                  d3d12Resource, &resourceFlags, D3D12_RESOURCE_STATE_COMMON,
+                                  D3D12_RESOURCE_STATE_COMMON, IID_PPV_ARGS(&d3d11Texture)),
+                              "D3D12 creating a wrapped resource"));
+
+        ComPtr<IDXGIKeyedMutex> dxgiKeyedMutex;
+        DAWN_TRY(CheckHRESULT(d3d11Texture.As(&dxgiKeyedMutex),
+                              "D3D12 QueryInterface ID3D11Texture2D to IDXGIKeyedMutex"));
+
+        return dxgiKeyedMutex;
+    }
+
+    void Device::ReleaseKeyedMutexForTexture(ComPtr<IDXGIKeyedMutex> dxgiKeyedMutex) {
+        ComPtr<ID3D11Resource> d3d11Resource;
+        HRESULT hr = dxgiKeyedMutex.As(&d3d11Resource);
+        if (FAILED(hr)) {
+            return;
+        }
+
+        ID3D11Resource* d3d11ResourceRaw = d3d11Resource.Get();
+        mD3d11On12Device->ReleaseWrappedResources(&d3d11ResourceRaw, 1);
+    }
+
 }}  // namespace dawn_native::d3d12
diff --git a/src/dawn_native/d3d12/DeviceD3D12.h b/src/dawn_native/d3d12/DeviceD3D12.h
index 4981c18..3a41c84 100644
--- a/src/dawn_native/d3d12/DeviceD3D12.h
+++ b/src/dawn_native/d3d12/DeviceD3D12.h
@@ -78,7 +78,7 @@
 
         void ReferenceUntilUnused(ComPtr<IUnknown> object);
 
-        MaybeError ExecuteCommandContext(CommandRecordingContext* commandContext);
+        MaybeError ExecutePendingCommandContext();
 
         ResultOrError<std::unique_ptr<StagingBufferBase>> CreateStagingBuffer(size_t size) override;
         MaybeError CopyFromStagingToBuffer(StagingBufferBase* source,
@@ -94,7 +94,12 @@
 
         void DeallocateMemory(ResourceHeapAllocation& allocation);
 
-        TextureBase* WrapSharedHandle(const TextureDescriptor* descriptor, HANDLE sharedHandle);
+        TextureBase* WrapSharedHandle(const TextureDescriptor* descriptor,
+                                      HANDLE sharedHandle,
+                                      uint64_t acquireMutexKey);
+        ResultOrError<ComPtr<IDXGIKeyedMutex>> CreateKeyedMutexForTexture(
+            ID3D12Resource* d3d12Resource);
+        void ReleaseKeyedMutexForTexture(ComPtr<IDXGIKeyedMutex> dxgiKeyedMutex);
 
       private:
         ResultOrError<BindGroupBase*> CreateBindGroupImpl(
@@ -126,6 +131,7 @@
 
         ComPtr<ID3D12Device> mD3d12Device;  // Device is owned by adapter and will not be outlived.
         ComPtr<ID3D12CommandQueue> mCommandQueue;
+        ComPtr<ID3D11On12Device> mD3d11On12Device;  // 11on12 device corresponding to mCommandQueue
 
         ComPtr<ID3D12CommandSignature> mDispatchIndirectSignature;
         ComPtr<ID3D12CommandSignature> mDrawIndirectSignature;
diff --git a/src/dawn_native/d3d12/PlatformFunctions.cpp b/src/dawn_native/d3d12/PlatformFunctions.cpp
index 747cb68..ae25dc1 100644
--- a/src/dawn_native/d3d12/PlatformFunctions.cpp
+++ b/src/dawn_native/d3d12/PlatformFunctions.cpp
@@ -27,6 +27,7 @@
         DAWN_TRY(LoadD3D12());
         DAWN_TRY(LoadDXGI());
         DAWN_TRY(LoadD3DCompiler());
+        DAWN_TRY(LoadD3D11());
         LoadPIXRuntime();
         return {};
     }
@@ -50,6 +51,16 @@
         return {};
     }
 
+    MaybeError PlatformFunctions::LoadD3D11() {
+        std::string error;
+        if (!mD3D11Lib.Open("d3d11.dll", &error) ||
+            !mD3D11Lib.GetProc(&d3d11on12CreateDevice, "D3D11On12CreateDevice", &error)) {
+            return DAWN_DEVICE_LOST_ERROR(error.c_str());
+        }
+
+        return {};
+    }
+
     MaybeError PlatformFunctions::LoadDXGI() {
         std::string error;
         if (!mDXGILib.Open("dxgi.dll", &error) ||
diff --git a/src/dawn_native/d3d12/PlatformFunctions.h b/src/dawn_native/d3d12/PlatformFunctions.h
index f367bfc..a9e85d2 100644
--- a/src/dawn_native/d3d12/PlatformFunctions.h
+++ b/src/dawn_native/d3d12/PlatformFunctions.h
@@ -77,13 +77,18 @@
 
         PFN_SET_MARKER_ON_COMMAND_LIST pixSetMarkerOnCommandList = nullptr;
 
+        // Functions from D3D11.dll
+        PFN_D3D11ON12_CREATE_DEVICE d3d11on12CreateDevice = nullptr;
+
       private:
         MaybeError LoadD3D12();
+        MaybeError LoadD3D11();
         MaybeError LoadDXGI();
         MaybeError LoadD3DCompiler();
         void LoadPIXRuntime();
 
         DynamicLib mD3D12Lib;
+        DynamicLib mD3D11Lib;
         DynamicLib mDXGILib;
         DynamicLib mD3DCompilerLib;
         DynamicLib mPIXEventRuntimeLib;
diff --git a/src/dawn_native/d3d12/QueueD3D12.cpp b/src/dawn_native/d3d12/QueueD3D12.cpp
index 8c50bd7..0043fda 100644
--- a/src/dawn_native/d3d12/QueueD3D12.cpp
+++ b/src/dawn_native/d3d12/QueueD3D12.cpp
@@ -28,13 +28,14 @@
 
         device->Tick();
 
-        DAWN_TRY(mCommandContext.Open(device->GetD3D12Device().Get(),
-                                      device->GetCommandAllocatorManager()));
+        CommandRecordingContext* commandContext;
+        DAWN_TRY_ASSIGN(commandContext, device->GetPendingCommandContext());
+
         for (uint32_t i = 0; i < commandCount; ++i) {
-            DAWN_TRY(ToBackend(commands[i])->RecordCommands(&mCommandContext, i));
+            DAWN_TRY(ToBackend(commands[i])->RecordCommands(commandContext, i));
         }
 
-        DAWN_TRY(device->ExecuteCommandContext(&mCommandContext));
+        DAWN_TRY(device->ExecutePendingCommandContext());
 
         DAWN_TRY(device->NextSerial());
         return {};
diff --git a/src/dawn_native/d3d12/QueueD3D12.h b/src/dawn_native/d3d12/QueueD3D12.h
index 121d19c..0e3c795 100644
--- a/src/dawn_native/d3d12/QueueD3D12.h
+++ b/src/dawn_native/d3d12/QueueD3D12.h
@@ -31,8 +31,6 @@
 
       private:
         MaybeError SubmitImpl(uint32_t commandCount, CommandBufferBase* const* commands) override;
-
-        CommandRecordingContext mCommandContext;
     };
 
 }}  // namespace dawn_native::d3d12
diff --git a/src/dawn_native/d3d12/SwapChainD3D12.cpp b/src/dawn_native/d3d12/SwapChainD3D12.cpp
index 7d24b35..ba38fb4 100644
--- a/src/dawn_native/d3d12/SwapChainD3D12.cpp
+++ b/src/dawn_native/d3d12/SwapChainD3D12.cpp
@@ -44,8 +44,8 @@
             return nullptr;
         }
 
-        ID3D12Resource* nativeTexture = static_cast<ID3D12Resource*>(next.texture.ptr);
-        return new Texture(ToBackend(GetDevice()), descriptor, nativeTexture);
+        ComPtr<ID3D12Resource> d3d12Texture = static_cast<ID3D12Resource*>(next.texture.ptr);
+        return new Texture(ToBackend(GetDevice()), descriptor, std::move(d3d12Texture));
     }
 
     MaybeError SwapChain::OnBeforePresent(TextureBase* texture) {
@@ -57,7 +57,7 @@
         // Perform the necessary transition for the texture to be presented.
         ToBackend(texture)->TransitionUsageNow(commandContext, mTextureUsage);
 
-        DAWN_TRY(device->ExecuteCommandContext(nullptr));
+        DAWN_TRY(device->ExecutePendingCommandContext());
 
         return {};
     }
diff --git a/src/dawn_native/d3d12/TextureD3D12.cpp b/src/dawn_native/d3d12/TextureD3D12.cpp
index a26c948..bcb3c39 100644
--- a/src/dawn_native/d3d12/TextureD3D12.cpp
+++ b/src/dawn_native/d3d12/TextureD3D12.cpp
@@ -19,6 +19,7 @@
 #include "dawn_native/DynamicUploader.h"
 #include "dawn_native/Error.h"
 #include "dawn_native/d3d12/BufferD3D12.h"
+#include "dawn_native/d3d12/D3D12Error.h"
 #include "dawn_native/d3d12/DescriptorHeapAllocator.h"
 #include "dawn_native/d3d12/DeviceD3D12.h"
 #include "dawn_native/d3d12/ResourceAllocator.h"
@@ -272,13 +273,52 @@
 
     ResultOrError<TextureBase*> Texture::Create(Device* device,
                                                 const TextureDescriptor* descriptor) {
-        Ref<Texture> dawnTexture = AcquireRef(new Texture(device, descriptor));
+        Ref<Texture> dawnTexture =
+            AcquireRef(new Texture(device, descriptor, TextureState::OwnedInternal));
         DAWN_TRY(dawnTexture->InitializeAsInternalTexture());
         return dawnTexture.Detach();
     }
 
-    Texture::Texture(Device* device, const TextureDescriptor* descriptor)
-        : TextureBase(device, descriptor, TextureState::OwnedInternal) {
+    ResultOrError<TextureBase*> Texture::Create(Device* device,
+                                                const TextureDescriptor* descriptor,
+                                                HANDLE sharedHandle,
+                                                uint64_t acquireMutexKey) {
+        Ref<Texture> dawnTexture =
+            AcquireRef(new Texture(device, descriptor, TextureState::OwnedExternal));
+        DAWN_TRY(
+            dawnTexture->InitializeAsExternalTexture(descriptor, sharedHandle, acquireMutexKey));
+        return dawnTexture.Detach();
+    }
+
+    MaybeError Texture::InitializeAsExternalTexture(const TextureDescriptor* descriptor,
+                                                    HANDLE sharedHandle,
+                                                    uint64_t acquireMutexKey) {
+        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));
+
+        ComPtr<IDXGIKeyedMutex> dxgiKeyedMutex;
+        DAWN_TRY_ASSIGN(dxgiKeyedMutex,
+                        dawnDevice->CreateKeyedMutexForTexture(d3d12Resource.Get()));
+
+        DAWN_TRY(CheckHRESULT(dxgiKeyedMutex->AcquireSync(acquireMutexKey, INFINITE),
+                              "D3D12 acquiring shared mutex"));
+
+        mAcquireMutexKey = acquireMutexKey;
+        mDxgiKeyedMutex = std::move(dxgiKeyedMutex);
+        mResource = std::move(d3d12Resource);
+
+        SetIsSubresourceContentInitialized(true, 0, descriptor->mipLevelCount, 0,
+                                           descriptor->arrayLayerCount);
+
+        return {};
     }
 
     MaybeError Texture::InitializeAsInternalTexture() {
@@ -318,7 +358,6 @@
         return {};
     }
 
-    // With this constructor, the lifetime of the ID3D12Resource is externally managed.
     Texture::Texture(Device* device,
                      const TextureDescriptor* descriptor,
                      ComPtr<ID3D12Resource> nativeTexture)
@@ -333,9 +372,13 @@
     }
 
     void Texture::DestroyImpl() {
-        // If we own the resource, release it.
-        ToBackend(GetDevice())->GetResourceAllocator()->Release(mResource);
-        mResource = nullptr;
+        Device* device = ToBackend(GetDevice());
+        device->GetResourceAllocator()->Release(std::move(mResource));
+
+        if (mDxgiKeyedMutex != nullptr) {
+            mDxgiKeyedMutex->ReleaseSync(mAcquireMutexKey + 1);
+            device->ReleaseKeyedMutexForTexture(std::move(mDxgiKeyedMutex));
+        }
     }
 
     DXGI_FORMAT Texture::GetD3D12Format() const {
@@ -371,6 +414,13 @@
     bool Texture::TransitionUsageAndGetResourceBarrier(CommandRecordingContext* commandContext,
                                                        D3D12_RESOURCE_BARRIER* barrier,
                                                        D3D12_RESOURCE_STATES newState) {
+        // Textures with keyed mutexes can be written from other graphics queues. Hence, they
+        // must be acquired before command list submission to ensure work from the other queues
+        // has finished. See Device::ExecuteCommandContext.
+        if (mDxgiKeyedMutex != nullptr) {
+            commandContext->AddToSharedTextureList(this);
+        }
+
         // Avoid transitioning the texture when it isn't needed.
         // TODO(cwallez@chromium.org): Need some form of UAV barriers at some point.
         if (mLastState == newState) {
diff --git a/src/dawn_native/d3d12/TextureD3D12.h b/src/dawn_native/d3d12/TextureD3D12.h
index 162b50e..adfe4d1 100644
--- a/src/dawn_native/d3d12/TextureD3D12.h
+++ b/src/dawn_native/d3d12/TextureD3D12.h
@@ -34,9 +34,14 @@
       public:
         static ResultOrError<TextureBase*> Create(Device* device,
                                                   const TextureDescriptor* descriptor);
+        static ResultOrError<TextureBase*> Create(Device* device,
+                                                  const TextureDescriptor* descriptor,
+                                                  HANDLE sharedHandle,
+                                                  uint64_t acquireMutexKey);
         Texture(Device* device,
                 const TextureDescriptor* descriptor,
-                ComPtr<ID3D12Resource> nativeTexture);
+                ComPtr<ID3D12Resource> d3d12Texture);
+
         ~Texture();
 
         DXGI_FORMAT GetD3D12Format() const;
@@ -59,8 +64,12 @@
                                                  uint32_t layerCount);
 
       private:
-        Texture(Device* device, const TextureDescriptor* descriptor);
+        using TextureBase::TextureBase;
+
         MaybeError InitializeAsInternalTexture();
+        MaybeError InitializeAsExternalTexture(const TextureDescriptor* descriptor,
+                                               HANDLE sharedHandle,
+                                               uint64_t acquireMutexKey);
 
         // Dawn API
         void DestroyImpl() override;
@@ -82,6 +91,9 @@
 
         Serial mLastUsedSerial = UINT64_MAX;
         bool mValidToDecay = false;
+
+        Serial mAcquireMutexKey = 0;
+        ComPtr<IDXGIKeyedMutex> mDxgiKeyedMutex;
     };
 
     class TextureView : public TextureViewBase {
diff --git a/src/dawn_native/d3d12/d3d12_platform.h b/src/dawn_native/d3d12/d3d12_platform.h
index 6dfa2fd..ea42c26 100644
--- a/src/dawn_native/d3d12/d3d12_platform.h
+++ b/src/dawn_native/d3d12/d3d12_platform.h
@@ -15,6 +15,7 @@
 #ifndef DAWNNATIVE_D3D12_D3D12PLATFORM_H_
 #define DAWNNATIVE_D3D12_D3D12PLATFORM_H_
 
+#include <d3d11on12.h>
 #include <d3d12.h>
 #include <dxgi1_4.h>
 #include <wrl.h>
diff --git a/src/dawn_native/metal/TextureMTL.mm b/src/dawn_native/metal/TextureMTL.mm
index 12bbc73..c0f3428 100644
--- a/src/dawn_native/metal/TextureMTL.mm
+++ b/src/dawn_native/metal/TextureMTL.mm
@@ -343,8 +343,10 @@
     }
 
     void Texture::DestroyImpl() {
-        [mMtlTexture release];
-        mMtlTexture = nil;
+        if (GetTextureState() == TextureState::OwnedInternal) {
+            [mMtlTexture release];
+            mMtlTexture = nil;
+        }
     }
 
     id<MTLTexture> Texture::GetMTLTexture() {
diff --git a/src/dawn_native/opengl/TextureGL.cpp b/src/dawn_native/opengl/TextureGL.cpp
index 4b4fdde..1eed793 100644
--- a/src/dawn_native/opengl/TextureGL.cpp
+++ b/src/dawn_native/opengl/TextureGL.cpp
@@ -163,8 +163,10 @@
     }
 
     void Texture::DestroyImpl() {
-        ToBackend(GetDevice())->gl.DeleteTextures(1, &mHandle);
-        mHandle = 0;
+        if (GetTextureState() == TextureState::OwnedInternal) {
+            ToBackend(GetDevice())->gl.DeleteTextures(1, &mHandle);
+            mHandle = 0;
+        }
     }
 
     GLuint Texture::GetHandle() const {
diff --git a/src/dawn_native/vulkan/TextureVk.cpp b/src/dawn_native/vulkan/TextureVk.cpp
index 0cd4d05..40d6ef7 100644
--- a/src/dawn_native/vulkan/TextureVk.cpp
+++ b/src/dawn_native/vulkan/TextureVk.cpp
@@ -578,28 +578,30 @@
     }
 
     void Texture::DestroyImpl() {
-        Device* device = ToBackend(GetDevice());
+        if (GetTextureState() == TextureState::OwnedInternal) {
+            Device* device = ToBackend(GetDevice());
 
-        // If we own the resource, release it.
-        if (mMemoryAllocation.GetMemory() != VK_NULL_HANDLE) {
-            // We need to free both the memory allocation and the container. Memory should be
-            // freed after the VkImage is destroyed and this is taken care of by the
-            // FencedDeleter.
-            device->GetMemoryAllocator()->Free(&mMemoryAllocation);
+            // If we own the resource, release it.
+            if (mMemoryAllocation.GetMemory() != VK_NULL_HANDLE) {
+                // We need to free both the memory allocation and the container. Memory should be
+                // freed after the VkImage is destroyed and this is taken care of by the
+                // FencedDeleter.
+                device->GetMemoryAllocator()->Free(&mMemoryAllocation);
+            }
+
+            if (mHandle != VK_NULL_HANDLE) {
+                device->GetFencedDeleter()->DeleteWhenUnused(mHandle);
+            }
+
+            if (mExternalAllocation != VK_NULL_HANDLE) {
+                device->GetFencedDeleter()->DeleteWhenUnused(mExternalAllocation);
+            }
+
+            mHandle = VK_NULL_HANDLE;
+            mExternalAllocation = VK_NULL_HANDLE;
+            // If a signal semaphore exists it should be requested before we delete the texture
+            ASSERT(mSignalSemaphore == VK_NULL_HANDLE);
         }
-
-        if (mHandle != VK_NULL_HANDLE) {
-            device->GetFencedDeleter()->DeleteWhenUnused(mHandle);
-        }
-
-        if (mExternalAllocation != VK_NULL_HANDLE) {
-            device->GetFencedDeleter()->DeleteWhenUnused(mExternalAllocation);
-        }
-
-        mHandle = VK_NULL_HANDLE;
-        mExternalAllocation = VK_NULL_HANDLE;
-        // If a signal semaphore exists it should be requested before we delete the texture
-        ASSERT(mSignalSemaphore == VK_NULL_HANDLE);
     }
 
     VkImage Texture::GetHandle() const {
diff --git a/src/include/dawn_native/D3D12Backend.h b/src/include/dawn_native/D3D12Backend.h
index 5a87ab8..14c0ffe 100644
--- a/src/include/dawn_native/D3D12Backend.h
+++ b/src/include/dawn_native/D3D12Backend.h
@@ -30,9 +30,11 @@
     DAWN_NATIVE_EXPORT DawnTextureFormat
     GetNativeSwapChainPreferredFormat(const DawnSwapChainImplementation* swapChain);
 
+    // Note: SharedHandle must be a handle to a texture object.
     DAWN_NATIVE_EXPORT DawnTexture WrapSharedHandle(DawnDevice device,
                                                     const DawnTextureDescriptor* descriptor,
-                                                    HANDLE sharedHandle);
+                                                    HANDLE sharedHandle,
+                                                    uint64_t acquireMutexKey);
 }}  // namespace dawn_native::d3d12
 
 #endif  // DAWNNATIVE_D3D12BACKEND_H_
diff --git a/src/tests/end2end/D3D12ResourceWrappingTests.cpp b/src/tests/end2end/D3D12ResourceWrappingTests.cpp
index 5bb4fa3..eed8e14 100644
--- a/src/tests/end2end/D3D12ResourceWrappingTests.cpp
+++ b/src/tests/end2end/D3D12ResourceWrappingTests.cpp
@@ -58,12 +58,36 @@
 
             mD3d11Device = std::move(d3d11Device);
             mD3d11DeviceContext = std::move(d3d11DeviceContext);
+
+            dawnDescriptor.dimension = dawn::TextureDimension::e2D;
+            dawnDescriptor.format = dawn::TextureFormat::RGBA8Unorm;
+            dawnDescriptor.size = {kTestWidth, kTestHeight, 1};
+            dawnDescriptor.sampleCount = 1;
+            dawnDescriptor.arrayLayerCount = 1;
+            dawnDescriptor.mipLevelCount = 1;
+            dawnDescriptor.usage = dawn::TextureUsage::Sampled | dawn::TextureUsage::CopySrc |
+                                   dawn::TextureUsage::OutputAttachment |
+                                   dawn::TextureUsage::CopyDst;
+
+            d3dDescriptor.Width = kTestWidth;
+            d3dDescriptor.Height = kTestHeight;
+            d3dDescriptor.MipLevels = 1;
+            d3dDescriptor.ArraySize = 1;
+            d3dDescriptor.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
+            d3dDescriptor.SampleDesc.Count = 1;
+            d3dDescriptor.SampleDesc.Quality = 0;
+            d3dDescriptor.Usage = D3D11_USAGE_DEFAULT;
+            d3dDescriptor.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
+            d3dDescriptor.CPUAccessFlags = 0;
+            d3dDescriptor.MiscFlags =
+                D3D11_RESOURCE_MISC_SHARED_NTHANDLE | D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX;
         }
 
       protected:
         void WrapSharedHandle(const dawn::TextureDescriptor* dawnDescriptor,
                               const D3D11_TEXTURE2D_DESC* d3dDescriptor,
-                              dawn::Texture* dawnTexture) const {
+                              dawn::Texture* dawnTexture,
+                              ID3D11Texture2D** d3d11TextureOut) const {
             ComPtr<ID3D11Texture2D> d3d11Texture;
             HRESULT hr = mD3d11Device->CreateTexture2D(d3dDescriptor, nullptr, &d3d11Texture);
             ASSERT_EQ(hr, S_OK);
@@ -80,12 +104,13 @@
 
             DawnTexture texture = dawn_native::d3d12::WrapSharedHandle(
                 device.Get(), reinterpret_cast<const DawnTextureDescriptor*>(dawnDescriptor),
-                sharedHandle);
+                sharedHandle, 0);
             // Now that we've created all of our resources, we can close the handle
             // since we no longer need it.
             ::CloseHandle(sharedHandle);
 
             *dawnTexture = dawn::Texture::Acquire(texture);
+            *d3d11TextureOut = d3d11Texture.Detach();
         }
 
         static constexpr size_t kTestWidth = 10;
@@ -93,6 +118,9 @@
 
         ComPtr<ID3D11Device> mD3d11Device;
         ComPtr<ID3D11DeviceContext> mD3d11DeviceContext;
+
+        D3D11_TEXTURE2D_DESC d3dDescriptor;
+        dawn::TextureDescriptor dawnDescriptor;
     };
 
 }  // anonymous namespace
@@ -100,35 +128,6 @@
 // A small fixture used to initialize default data for the D3D12Resource validation tests.
 // These tests are skipped if the harness is using the wire.
 class D3D12SharedHandleValidation : public D3D12ResourceTestBase {
-  public:
-    void TestSetUp() override {
-        D3D12ResourceTestBase::TestSetUp();
-
-        dawnDescriptor.dimension = dawn::TextureDimension::e2D;
-        dawnDescriptor.format = dawn::TextureFormat::BGRA8Unorm;
-        dawnDescriptor.size = {kTestWidth, kTestHeight, 1};
-        dawnDescriptor.sampleCount = 1;
-        dawnDescriptor.arrayLayerCount = 1;
-        dawnDescriptor.mipLevelCount = 1;
-        dawnDescriptor.usage = dawn::TextureUsage::OutputAttachment;
-
-        d3dDescriptor.Width = kTestWidth;
-        d3dDescriptor.Height = kTestHeight;
-        d3dDescriptor.MipLevels = 1;
-        d3dDescriptor.ArraySize = 1;
-        d3dDescriptor.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
-        d3dDescriptor.SampleDesc.Count = 1;
-        d3dDescriptor.SampleDesc.Quality = 0;
-        d3dDescriptor.Usage = D3D11_USAGE_DEFAULT;
-        d3dDescriptor.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
-        d3dDescriptor.CPUAccessFlags = 0;
-        d3dDescriptor.MiscFlags =
-            D3D11_RESOURCE_MISC_SHARED_NTHANDLE | D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX;
-    }
-
-  protected:
-    D3D11_TEXTURE2D_DESC d3dDescriptor;
-    dawn::TextureDescriptor dawnDescriptor;
 };
 
 // Test a successful wrapping of an D3D12Resource in a texture
@@ -136,7 +135,8 @@
     DAWN_SKIP_TEST_IF(UsesWire());
 
     dawn::Texture texture;
-    WrapSharedHandle(&dawnDescriptor, &d3dDescriptor, &texture);
+    ComPtr<ID3D11Texture2D> d3d11Texture;
+    WrapSharedHandle(&dawnDescriptor, &d3dDescriptor, &texture, &d3d11Texture);
 
     ASSERT_NE(texture.Get(), nullptr);
 }
@@ -147,7 +147,8 @@
     dawnDescriptor.nextInChain = this;
 
     dawn::Texture texture;
-    ASSERT_DEVICE_ERROR(WrapSharedHandle(&dawnDescriptor, &d3dDescriptor, &texture));
+    ComPtr<ID3D11Texture2D> d3d11Texture;
+    ASSERT_DEVICE_ERROR(WrapSharedHandle(&dawnDescriptor, &d3dDescriptor, &texture, &d3d11Texture));
 
     ASSERT_EQ(texture.Get(), nullptr);
 }
@@ -158,7 +159,8 @@
     dawnDescriptor.mipLevelCount = 2;
 
     dawn::Texture texture;
-    ASSERT_DEVICE_ERROR(WrapSharedHandle(&dawnDescriptor, &d3dDescriptor, &texture));
+    ComPtr<ID3D11Texture2D> d3d11Texture;
+    ASSERT_DEVICE_ERROR(WrapSharedHandle(&dawnDescriptor, &d3dDescriptor, &texture, &d3d11Texture));
 
     ASSERT_EQ(texture.Get(), nullptr);
 }
@@ -169,7 +171,8 @@
     dawnDescriptor.arrayLayerCount = 2;
 
     dawn::Texture texture;
-    ASSERT_DEVICE_ERROR(WrapSharedHandle(&dawnDescriptor, &d3dDescriptor, &texture));
+    ComPtr<ID3D11Texture2D> d3d11Texture;
+    ASSERT_DEVICE_ERROR(WrapSharedHandle(&dawnDescriptor, &d3dDescriptor, &texture, &d3d11Texture));
 
     ASSERT_EQ(texture.Get(), nullptr);
 }
@@ -180,7 +183,8 @@
     dawnDescriptor.sampleCount = 4;
 
     dawn::Texture texture;
-    ASSERT_DEVICE_ERROR(WrapSharedHandle(&dawnDescriptor, &d3dDescriptor, &texture));
+    ComPtr<ID3D11Texture2D> d3d11Texture;
+    ASSERT_DEVICE_ERROR(WrapSharedHandle(&dawnDescriptor, &d3dDescriptor, &texture, &d3d11Texture));
 
     ASSERT_EQ(texture.Get(), nullptr);
 }
@@ -191,7 +195,8 @@
     dawnDescriptor.size.width = kTestWidth + 1;
 
     dawn::Texture texture;
-    ASSERT_DEVICE_ERROR(WrapSharedHandle(&dawnDescriptor, &d3dDescriptor, &texture));
+    ComPtr<ID3D11Texture2D> d3d11Texture;
+    ASSERT_DEVICE_ERROR(WrapSharedHandle(&dawnDescriptor, &d3dDescriptor, &texture, &d3d11Texture));
 
     ASSERT_EQ(texture.Get(), nullptr);
 }
@@ -202,7 +207,8 @@
     dawnDescriptor.size.height = kTestHeight + 1;
 
     dawn::Texture texture;
-    ASSERT_DEVICE_ERROR(WrapSharedHandle(&dawnDescriptor, &d3dDescriptor, &texture));
+    ComPtr<ID3D11Texture2D> d3d11Texture;
+    ASSERT_DEVICE_ERROR(WrapSharedHandle(&dawnDescriptor, &d3dDescriptor, &texture, &d3d11Texture));
 
     ASSERT_EQ(texture.Get(), nullptr);
 }
@@ -213,7 +219,8 @@
     dawnDescriptor.format = dawn::TextureFormat::R8Unorm;
 
     dawn::Texture texture;
-    ASSERT_DEVICE_ERROR(WrapSharedHandle(&dawnDescriptor, &d3dDescriptor, &texture));
+    ComPtr<ID3D11Texture2D> d3d11Texture;
+    ASSERT_DEVICE_ERROR(WrapSharedHandle(&dawnDescriptor, &d3dDescriptor, &texture, &d3d11Texture));
 
     ASSERT_EQ(texture.Get(), nullptr);
 }
@@ -224,7 +231,8 @@
     d3dDescriptor.MipLevels = 2;
 
     dawn::Texture texture;
-    ASSERT_DEVICE_ERROR(WrapSharedHandle(&dawnDescriptor, &d3dDescriptor, &texture));
+    ComPtr<ID3D11Texture2D> d3d11Texture;
+    ASSERT_DEVICE_ERROR(WrapSharedHandle(&dawnDescriptor, &d3dDescriptor, &texture, &d3d11Texture));
 
     ASSERT_EQ(texture.Get(), nullptr);
 }
@@ -235,9 +243,252 @@
     d3dDescriptor.ArraySize = 2;
 
     dawn::Texture texture;
-    ASSERT_DEVICE_ERROR(WrapSharedHandle(&dawnDescriptor, &d3dDescriptor, &texture));
+    ComPtr<ID3D11Texture2D> d3d11Texture;
+    ASSERT_DEVICE_ERROR(WrapSharedHandle(&dawnDescriptor, &d3dDescriptor, &texture, &d3d11Texture));
 
     ASSERT_EQ(texture.Get(), nullptr);
 }
 
+class D3D12SharedHandleUsageTests : public D3D12ResourceTestBase {
+  protected:
+    // Submits a 1x1x1 copy from source to destination
+    void SimpleCopyTextureToTexture(dawn::Texture source, dawn::Texture destination) {
+        dawn::TextureCopyView copySrc;
+        copySrc.texture = source;
+        copySrc.mipLevel = 0;
+        copySrc.arrayLayer = 0;
+        copySrc.origin = {0, 0, 0};
+
+        dawn::TextureCopyView copyDst;
+        copyDst.texture = destination;
+        copyDst.mipLevel = 0;
+        copyDst.arrayLayer = 0;
+        copyDst.origin = {0, 0, 0};
+
+        dawn::Extent3D copySize = {1, 1, 1};
+
+        dawn::CommandEncoder encoder = device.CreateCommandEncoder();
+        encoder.CopyTextureToTexture(&copySrc, &copyDst, &copySize);
+        dawn::CommandBuffer commands = encoder.Finish();
+
+        queue.Submit(1, &commands);
+    }
+
+    // Clear a texture on a given device
+    void ClearImage(dawn::Texture wrappedTexture, const dawn::Color& clearColor) {
+        dawn::TextureView wrappedView = wrappedTexture.CreateView();
+
+        // Submit a clear operation
+        utils::ComboRenderPassDescriptor renderPassDescriptor({wrappedView}, {});
+        renderPassDescriptor.cColorAttachments[0].clearColor = clearColor;
+
+        dawn::CommandEncoder encoder = device.CreateCommandEncoder();
+        dawn::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDescriptor);
+        pass.EndPass();
+
+        dawn::CommandBuffer commands = encoder.Finish();
+        queue.Submit(1, &commands);
+    }
+
+    void WrapAndClearD3D11Texture(const dawn::TextureDescriptor* dawnDescriptor,
+                                  const D3D11_TEXTURE2D_DESC* d3dDescriptor,
+                                  dawn::Texture* dawnTextureOut,
+                                  const dawn::Color& clearColor,
+                                  ID3D11Texture2D** d3d11TextureOut,
+                                  IDXGIKeyedMutex** dxgiKeyedMutexOut) const {
+        ComPtr<ID3D11Texture2D> d3d11Texture;
+        HRESULT hr = mD3d11Device->CreateTexture2D(d3dDescriptor, nullptr, &d3d11Texture);
+        ASSERT_EQ(hr, S_OK);
+
+        ComPtr<IDXGIResource1> dxgiResource;
+        hr = d3d11Texture.As(&dxgiResource);
+        ASSERT_EQ(hr, S_OK);
+
+        HANDLE sharedHandle;
+        hr = dxgiResource->CreateSharedHandle(
+            nullptr, DXGI_SHARED_RESOURCE_READ | DXGI_SHARED_RESOURCE_WRITE, nullptr,
+            &sharedHandle);
+        ASSERT_EQ(hr, S_OK);
+
+        ComPtr<IDXGIKeyedMutex> dxgiKeyedMutex;
+        hr = d3d11Texture.As(&dxgiKeyedMutex);
+        ASSERT_EQ(hr, S_OK);
+
+        ComPtr<ID3D11RenderTargetView> d3d11RTV;
+        hr = mD3d11Device->CreateRenderTargetView(d3d11Texture.Get(), nullptr, &d3d11RTV);
+        ASSERT_EQ(hr, S_OK);
+
+        hr = dxgiKeyedMutex->AcquireSync(0, INFINITE);
+        ASSERT_EQ(hr, S_OK);
+
+        const float colorRGBA[] = {clearColor.r, clearColor.g, clearColor.b, clearColor.a};
+        mD3d11DeviceContext->ClearRenderTargetView(d3d11RTV.Get(), colorRGBA);
+
+        hr = dxgiKeyedMutex->ReleaseSync(1);
+        ASSERT_EQ(hr, S_OK);
+
+        DawnTexture dawnTexture = dawn_native::d3d12::WrapSharedHandle(
+            device.Get(), reinterpret_cast<const DawnTextureDescriptor*>(dawnDescriptor),
+            sharedHandle, 1);
+
+        *dawnTextureOut = dawn::Texture::Acquire(dawnTexture);
+        *d3d11TextureOut = d3d11Texture.Detach();
+        *dxgiKeyedMutexOut = dxgiKeyedMutex.Detach();
+    }
+
+    void ExpectPixelRGBA8EQ(UINT64 acquireKey,
+                            ID3D11Texture2D* d3d11Texture,
+                            IDXGIKeyedMutex* dxgiKeyedMutex,
+                            const dawn::Color& color) {
+        HRESULT hr = dxgiKeyedMutex->AcquireSync(acquireKey, INFINITE);
+        ASSERT_EQ(hr, S_OK);
+
+        D3D11_TEXTURE2D_DESC texture2DDesc;
+        d3d11Texture->GetDesc(&texture2DDesc);
+
+        const CD3D11_TEXTURE2D_DESC texture2DStagingDesc(
+            texture2DDesc.Format,                             // Format
+            texture2DDesc.Width,                              // Width
+            texture2DDesc.Height,                             // Height
+            1,                                                // ArraySize
+            1,                                                // MipLevels
+            0,                                                // BindFlags
+            D3D11_USAGE_STAGING,                              // Usage
+            D3D11_CPU_ACCESS_READ | D3D11_CPU_ACCESS_WRITE);  // CPUAccessFlags
+
+        ComPtr<ID3D11Texture2D> spD3DTextureStaging;
+        hr = mD3d11Device->CreateTexture2D(&texture2DStagingDesc, nullptr, &spD3DTextureStaging);
+        ASSERT_EQ(hr, S_OK);
+
+        D3D11_BOX d3dRc;
+        d3dRc.back = 1;
+        d3dRc.front = 0;
+        d3dRc.top = 0;
+        d3dRc.left = 0;
+        d3dRc.bottom = texture2DDesc.Height;
+        d3dRc.right = texture2DDesc.Width;
+
+        mD3d11DeviceContext->CopySubresourceRegion(spD3DTextureStaging.Get(),  // pDstResource
+                                                   0,                          // DstSubresource
+                                                   0,                          // DstX
+                                                   0,                          // DstY
+                                                   0,                          // DstZ
+                                                   d3d11Texture,               // pSrcResource
+                                                   0,                          // SrcSubresource
+                                                   &d3dRc);                    // pSrcBox
+
+        D3D11_MAPPED_SUBRESOURCE mappedResource;
+        hr = mD3d11DeviceContext->Map(spD3DTextureStaging.Get(), 0, D3D11_MAP_READ_WRITE, 0,
+                                      &mappedResource);
+        ASSERT_EQ(hr, S_OK);
+
+        const uint8_t* colorData = static_cast<uint8_t*>(mappedResource.pData);
+        EXPECT_EQ(colorData[0], color.r * 255u);
+        EXPECT_EQ(colorData[1], color.g * 255u);
+        EXPECT_EQ(colorData[2], color.b * 255u);
+        EXPECT_EQ(colorData[3], color.a * 255u);
+
+        mD3d11DeviceContext->Unmap(spD3DTextureStaging.Get(), 0);
+
+        hr = dxgiKeyedMutex->ReleaseSync(acquireKey + 1);
+        ASSERT_EQ(hr, S_OK);
+    }
+};
+
+// 1. Create and clear a D3D11 texture
+// 2. Copy the wrapped texture to another dawn texture
+// 3. Readback the copied texture and ensure the color matches the original clear color.
+TEST_P(D3D12SharedHandleUsageTests, ClearInD3D11CopyAndReadbackInD3D12) {
+    DAWN_SKIP_TEST_IF(UsesWire());
+
+    const dawn::Color clearColor{1.0f, 1.0f, 0.0f, 1.0f};
+    dawn::Texture dawnSrcTexture;
+    ComPtr<ID3D11Texture2D> d3d11Texture;
+    ComPtr<IDXGIKeyedMutex> dxgiKeyedMutex;
+    WrapAndClearD3D11Texture(&dawnDescriptor, &d3dDescriptor, &dawnSrcTexture, clearColor,
+                             &d3d11Texture, &dxgiKeyedMutex);
+
+    // Create a texture on the device and copy the source texture to it.
+    dawn::Texture dawnCopyDestTexture = device.CreateTexture(&dawnDescriptor);
+    SimpleCopyTextureToTexture(dawnSrcTexture, dawnCopyDestTexture);
+
+    // Readback the destination texture and ensure it contains the colors we used
+    // to clear the source texture on the D3D device.
+    EXPECT_PIXEL_RGBA8_EQ(
+        RGBA8(clearColor.r * 255u, clearColor.g * 255u, clearColor.b * 255u, clearColor.a * 255u),
+        dawnCopyDestTexture, 0, 0);
+}
+
+// 1. Create and clear a D3D11 texture
+// 2. Readback the wrapped texture and ensure the color matches the original clear color.
+TEST_P(D3D12SharedHandleUsageTests, ClearInD3D11ReadbackInD3D12) {
+    DAWN_SKIP_TEST_IF(UsesWire());
+
+    const dawn::Color clearColor{1.0f, 1.0f, 0.0f, 1.0f};
+    dawn::Texture dawnTexture;
+    ComPtr<ID3D11Texture2D> d3d11Texture;
+    ComPtr<IDXGIKeyedMutex> dxgiKeyedMutex;
+    WrapAndClearD3D11Texture(&dawnDescriptor, &d3dDescriptor, &dawnTexture, clearColor,
+                             &d3d11Texture, &dxgiKeyedMutex);
+
+    // Readback the destination texture and ensure it contains the colors we used
+    // to clear the source texture on the D3D device.
+    EXPECT_PIXEL_RGBA8_EQ(
+        RGBA8(clearColor.r * 255, clearColor.g * 255, clearColor.b * 255, clearColor.a * 255),
+        dawnTexture, 0, 0);
+}
+
+// 1. Create and clear a D3D11 texture
+// 2. Wrap it in a Dawn texture and clear it to a different color
+// 3. Readback the texture with D3D11 and ensure we receive the color we cleared with Dawn.
+TEST_P(D3D12SharedHandleUsageTests, ClearInD3D12ReadbackInD3D11) {
+    DAWN_SKIP_TEST_IF(UsesWire());
+
+    const dawn::Color d3d11ClearColor{1.0f, 1.0f, 0.0f, 1.0f};
+    dawn::Texture dawnTexture;
+    ComPtr<ID3D11Texture2D> d3d11Texture;
+    ComPtr<IDXGIKeyedMutex> dxgiKeyedMutex;
+    WrapAndClearD3D11Texture(&dawnDescriptor, &d3dDescriptor, &dawnTexture, d3d11ClearColor,
+                             &d3d11Texture, &dxgiKeyedMutex);
+
+    const dawn::Color d3d12ClearColor{0.0f, 0.0f, 1.0f, 1.0f};
+    ClearImage(dawnTexture, d3d12ClearColor);
+
+    dawnTexture.Destroy();
+
+    // Now that Dawn (via D3D12) has finished writing to the texture, we should be
+    // able to read it back by copying it to a staging texture and verifying the
+    // color matches the D3D12 clear color.
+    ExpectPixelRGBA8EQ(2, d3d11Texture.Get(), dxgiKeyedMutex.Get(), d3d12ClearColor);
+}
+
+// 1. Create and clear a D3D11 texture
+// 2. Wrap it in a Dawn texture and clear the texture to two different colors.
+// 3. Readback the texture with D3D11.
+// 4. Verify the readback color was the final color cleared.
+TEST_P(D3D12SharedHandleUsageTests, ClearTwiceInD3D12ReadbackInD3D11) {
+    DAWN_SKIP_TEST_IF(UsesWire());
+
+    const dawn::Color d3d11ClearColor{1.0f, 1.0f, 0.0f, 1.0f};
+    dawn::Texture dawnTexture;
+    ComPtr<ID3D11Texture2D> d3d11Texture;
+    ComPtr<IDXGIKeyedMutex> dxgiKeyedMutex;
+    WrapAndClearD3D11Texture(&dawnDescriptor, &d3dDescriptor, &dawnTexture, d3d11ClearColor,
+                             &d3d11Texture, &dxgiKeyedMutex);
+
+    const dawn::Color d3d12ClearColor1{0.0f, 0.0f, 1.0f, 1.0f};
+    ClearImage(dawnTexture, d3d12ClearColor1);
+
+    const dawn::Color d3d12ClearColor2{0.0f, 1.0f, 1.0f, 1.0f};
+    ClearImage(dawnTexture, d3d12ClearColor2);
+
+    dawnTexture.Destroy();
+
+    // Now that Dawn (via D3D12) has finished writing to the texture, we should be
+    // able to read it back by copying it to a staging texture and verifying the
+    // color matches the last D3D12 clear color.
+    ExpectPixelRGBA8EQ(2, d3d11Texture.Get(), dxgiKeyedMutex.Get(), d3d12ClearColor2);
+}
+
 DAWN_INSTANTIATE_TEST(D3D12SharedHandleValidation, D3D12Backend);
+DAWN_INSTANTIATE_TEST(D3D12SharedHandleUsageTests, D3D12Backend);