Revert "D3D12: Move ExecutionQueue logic to the Queue."

This reverts commit a3dc7f4aff1c0c06046697a3531d175c63040067.

Reason for revert: Fails in CI only test suites.

Original change's description:
> D3D12: Move ExecutionQueue logic to the Queue.
>
> No functional changes intended but the Queue methods have been made to
> do slightly more than the equivalent methods on the device (for example
> checking if we actually need to do a wait or a submission).
>
> Bug: dawn:1413
> Change-Id: I56e1f3b996487e79aceeccb78aa5a2f676902f47
> Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/140401
> Kokoro: Kokoro <noreply+kokoro@google.com>
> Reviewed-by: Austin Eng <enga@chromium.org>
> Commit-Queue: Corentin Wallez <cwallez@chromium.org>
> Reviewed-by: Loko Kung <lokokung@google.com>

TBR=cwallez@chromium.org,enga@chromium.org,noreply+kokoro@google.com,dawn-scoped@luci-project-accounts.iam.gserviceaccount.com,lokokung@google.com

Change-Id: I2c1dde3bb098b2fa710adb0db84554b10ccaa2c6
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: dawn:1413
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/165660
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Austin Eng <enga@chromium.org>
Kokoro: Austin Eng <enga@chromium.org>
diff --git a/src/dawn/native/d3d12/CommandAllocatorManager.cpp b/src/dawn/native/d3d12/CommandAllocatorManager.cpp
index f3cd0ac..09e144b 100644
--- a/src/dawn/native/d3d12/CommandAllocatorManager.cpp
+++ b/src/dawn/native/d3d12/CommandAllocatorManager.cpp
@@ -29,14 +29,14 @@
 
 #include "dawn/native/d3d/D3DError.h"
 #include "dawn/native/d3d12/DeviceD3D12.h"
-#include "dawn/native/d3d12/QueueD3D12.h"
 
 #include "dawn/common/Assert.h"
 #include "dawn/common/BitSetIterator.h"
 
 namespace dawn::native::d3d12 {
 
-CommandAllocatorManager::CommandAllocatorManager(Queue* queue) : mQueue(queue), mAllocatorCount(0) {
+CommandAllocatorManager::CommandAllocatorManager(Device* device)
+    : device(device), mAllocatorCount(0) {
     mFreeAllocators.set();
 }
 
@@ -44,7 +44,7 @@
     // If there are no free allocators, get the oldest serial in flight and wait on it
     if (mFreeAllocators.none()) {
         const ExecutionSerial firstSerial = mInFlightCommandAllocators.FirstSerial();
-        DAWN_TRY(mQueue->WaitForSerial(firstSerial));
+        DAWN_TRY(device->WaitForSerial(firstSerial));
         DAWN_TRY(Tick(firstSerial));
     }
 
@@ -56,11 +56,9 @@
     if (firstFreeIndex >= mAllocatorCount) {
         DAWN_ASSERT(firstFreeIndex == mAllocatorCount);
         mAllocatorCount++;
-
-        ID3D12Device* d3d12Device = ToBackend(mQueue->GetDevice())->GetD3D12Device();
         DAWN_TRY(CheckHRESULT(
-            d3d12Device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT,
-                                                IID_PPV_ARGS(&mCommandAllocators[firstFreeIndex])),
+            device->GetD3D12Device()->CreateCommandAllocator(
+                D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&mCommandAllocators[firstFreeIndex])),
             "D3D12 create command allocator"));
     }
 
@@ -70,7 +68,7 @@
     // Enqueue the command allocator. It will be scheduled for reset after the next
     // ExecuteCommandLists
     mInFlightCommandAllocators.Enqueue({mCommandAllocators[firstFreeIndex], firstFreeIndex},
-                                       mQueue->GetPendingCommandSerial());
+                                       device->GetPendingCommandSerial());
     return mCommandAllocators[firstFreeIndex].Get();
 }
 
diff --git a/src/dawn/native/d3d12/CommandAllocatorManager.h b/src/dawn/native/d3d12/CommandAllocatorManager.h
index b0a29ed..f8e9d0d 100644
--- a/src/dawn/native/d3d12/CommandAllocatorManager.h
+++ b/src/dawn/native/d3d12/CommandAllocatorManager.h
@@ -38,11 +38,11 @@
 
 namespace dawn::native::d3d12 {
 
-class Queue;
+class Device;
 
 class CommandAllocatorManager {
   public:
-    explicit CommandAllocatorManager(Queue* queue);
+    explicit CommandAllocatorManager(Device* device);
 
     // A CommandAllocator that is reserved must be used on the next ExecuteCommandLists
     // otherwise its commands may be reset before execution has completed on the GPU
@@ -50,8 +50,7 @@
     MaybeError Tick(ExecutionSerial lastCompletedSerial);
 
   private:
-    // The allocator manager is owned by the queue so the queue outlives it.
-    Queue* mQueue;
+    Device* device;
 
     // This must be at least 2 because the Device and Queue use separate command allocators
     static constexpr unsigned int kMaxCommandAllocators = 32;
diff --git a/src/dawn/native/d3d12/CommandRecordingContext.cpp b/src/dawn/native/d3d12/CommandRecordingContext.cpp
index ba2b37b..6f88e28 100644
--- a/src/dawn/native/d3d12/CommandRecordingContext.cpp
+++ b/src/dawn/native/d3d12/CommandRecordingContext.cpp
@@ -78,73 +78,70 @@
     return {};
 }
 
-MaybeError CommandRecordingContext::ExecuteCommandList(Device* device,
-                                                       ID3D12CommandQueue* commandQueue) {
-    if (!IsOpen()) {
-        return {};
+MaybeError CommandRecordingContext::ExecuteCommandList(Device* device) {
+    if (IsOpen()) {
+        for (Texture* texture : mSharedTextures) {
+            DAWN_TRY(texture->SynchronizeImportedTextureBeforeUse());
+        }
+
+        MaybeError error =
+            CheckHRESULT(mD3d12CommandList->Close(), "D3D12 closing pending command list");
+        if (error.IsError()) {
+            Release();
+            DAWN_TRY(std::move(error));
+        }
+        DAWN_TRY(device->GetResidencyManager()->EnsureHeapsAreResident(mHeapsPendingUsage.data(),
+                                                                       mHeapsPendingUsage.size()));
+
+        if (device->IsToggleEnabled(Toggle::RecordDetailedTimingInTraceEvents)) {
+            uint64_t gpuTimestamp;
+            uint64_t cpuTimestamp;
+            FILETIME fileTimeNonPrecise;
+            SYSTEMTIME systemTimeNonPrecise;
+
+            // Both supported since Windows 2000, have a accuracy of 1ms
+            GetSystemTimeAsFileTime(&fileTimeNonPrecise);
+            GetSystemTime(&systemTimeNonPrecise);
+            // Query CPU and GPU timestamps at almost the same time
+            device->GetCommandQueue()->GetClockCalibration(&gpuTimestamp, &cpuTimestamp);
+
+            uint64_t gpuFrequency;
+            uint64_t cpuFrequency;
+            LARGE_INTEGER cpuFrequencyLargeInteger;
+            device->GetCommandQueue()->GetTimestampFrequency(&gpuFrequency);
+            QueryPerformanceFrequency(&cpuFrequencyLargeInteger);  // Supported since Windows 2000
+            cpuFrequency = cpuFrequencyLargeInteger.QuadPart;
+
+            std::string timingInfo = absl::StrFormat(
+                "UTC Time: %u/%u/%u %02u:%02u:%02u.%03u, File Time: %u, CPU "
+                "Timestamp: %u, GPU Timestamp: %u, CPU Tick Frequency: %u, GPU Tick Frequency: "
+                "%u",
+                systemTimeNonPrecise.wYear, systemTimeNonPrecise.wMonth, systemTimeNonPrecise.wDay,
+                systemTimeNonPrecise.wHour, systemTimeNonPrecise.wMinute,
+                systemTimeNonPrecise.wSecond, systemTimeNonPrecise.wMilliseconds,
+                (static_cast<uint64_t>(fileTimeNonPrecise.dwHighDateTime) << 32) +
+                    fileTimeNonPrecise.dwLowDateTime,
+                cpuTimestamp, gpuTimestamp, cpuFrequency, gpuFrequency);
+
+            TRACE_EVENT_INSTANT1(
+                device->GetPlatform(), General,
+                "d3d12::CommandRecordingContext::ExecuteCommandList Detailed Timing", "Timing",
+                timingInfo.c_str());
+        }
+
+        ID3D12CommandList* d3d12CommandList = GetCommandList();
+        device->GetCommandQueue()->ExecuteCommandLists(1, &d3d12CommandList);
+
+        for (Texture* texture : mSharedTextures) {
+            DAWN_TRY(texture->SynchronizeImportedTextureAfterUse());
+        }
+
+        mIsOpen = false;
+        mNeedsSubmit = false;
+        mSharedTextures.clear();
+        mHeapsPendingUsage.clear();
+        mTempBuffers.clear();
     }
-
-    for (Texture* texture : mSharedTextures) {
-        DAWN_TRY(texture->SynchronizeImportedTextureBeforeUse());
-    }
-
-    MaybeError error =
-        CheckHRESULT(mD3d12CommandList->Close(), "D3D12 closing pending command list");
-    if (error.IsError()) {
-        Release();
-        DAWN_TRY(std::move(error));
-    }
-    DAWN_TRY(device->GetResidencyManager()->EnsureHeapsAreResident(mHeapsPendingUsage.data(),
-                                                                   mHeapsPendingUsage.size()));
-
-    if (device->IsToggleEnabled(Toggle::RecordDetailedTimingInTraceEvents)) {
-        uint64_t gpuTimestamp;
-        uint64_t cpuTimestamp;
-        FILETIME fileTimeNonPrecise;
-        SYSTEMTIME systemTimeNonPrecise;
-
-        // Both supported since Windows 2000, have a accuracy of 1ms
-        GetSystemTimeAsFileTime(&fileTimeNonPrecise);
-        GetSystemTime(&systemTimeNonPrecise);
-        // Query CPU and GPU timestamps at almost the same time
-        commandQueue->GetClockCalibration(&gpuTimestamp, &cpuTimestamp);
-
-        uint64_t gpuFrequency;
-        uint64_t cpuFrequency;
-        LARGE_INTEGER cpuFrequencyLargeInteger;
-        commandQueue->GetTimestampFrequency(&gpuFrequency);
-        QueryPerformanceFrequency(&cpuFrequencyLargeInteger);  // Supported since Windows 2000
-        cpuFrequency = cpuFrequencyLargeInteger.QuadPart;
-
-        std::string timingInfo = absl::StrFormat(
-            "UTC Time: %u/%u/%u %02u:%02u:%02u.%03u, File Time: %u, CPU "
-            "Timestamp: %u, GPU Timestamp: %u, CPU Tick Frequency: %u, GPU Tick Frequency: "
-            "%u",
-            systemTimeNonPrecise.wYear, systemTimeNonPrecise.wMonth, systemTimeNonPrecise.wDay,
-            systemTimeNonPrecise.wHour, systemTimeNonPrecise.wMinute, systemTimeNonPrecise.wSecond,
-            systemTimeNonPrecise.wMilliseconds,
-            (static_cast<uint64_t>(fileTimeNonPrecise.dwHighDateTime) << 32) +
-                fileTimeNonPrecise.dwLowDateTime,
-            cpuTimestamp, gpuTimestamp, cpuFrequency, gpuFrequency);
-
-        TRACE_EVENT_INSTANT1(device->GetPlatform(), General,
-                             "d3d12::CommandRecordingContext::ExecuteCommandList Detailed Timing",
-                             "Timing", timingInfo.c_str());
-    }
-
-    ID3D12CommandList* d3d12CommandList = GetCommandList();
-    commandQueue->ExecuteCommandLists(1, &d3d12CommandList);
-
-    for (Texture* texture : mSharedTextures) {
-        DAWN_TRY(texture->SynchronizeImportedTextureAfterUse());
-    }
-
-    mIsOpen = false;
-    mNeedsSubmit = false;
-    mSharedTextures.clear();
-    mHeapsPendingUsage.clear();
-    mTempBuffers.clear();
-
     return {};
 }
 
diff --git a/src/dawn/native/d3d12/CommandRecordingContext.h b/src/dawn/native/d3d12/CommandRecordingContext.h
index 1ba4d47..1778813 100644
--- a/src/dawn/native/d3d12/CommandRecordingContext.h
+++ b/src/dawn/native/d3d12/CommandRecordingContext.h
@@ -53,7 +53,7 @@
     bool NeedsSubmit() const;
     void SetNeedsSubmit();
 
-    MaybeError ExecuteCommandList(Device* device, ID3D12CommandQueue* commandQueue);
+    MaybeError ExecuteCommandList(Device* device);
 
     void TrackHeapUsage(Heap* heap, ExecutionSerial serial);
 
diff --git a/src/dawn/native/d3d12/DeviceD3D12.cpp b/src/dawn/native/d3d12/DeviceD3D12.cpp
index ba296c6..90e448f 100644
--- a/src/dawn/native/d3d12/DeviceD3D12.cpp
+++ b/src/dawn/native/d3d12/DeviceD3D12.cpp
@@ -91,8 +91,13 @@
 
     DAWN_ASSERT(mD3d12Device != nullptr);
 
-    Ref<Queue> queue;
-    DAWN_TRY_ASSIGN(queue, Queue::Create(this, &descriptor->defaultQueue));
+    // Create device-global objects
+    D3D12_COMMAND_QUEUE_DESC queueDesc = {};
+    queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
+    queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
+    DAWN_TRY(
+        CheckHRESULT(mD3d12Device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&mCommandQueue)),
+                     "D3D12 create command queue"));
 
     if ((HasFeature(Feature::TimestampQuery) ||
          HasFeature(Feature::ChromiumExperimentalTimestampQueryInsidePasses)) &&
@@ -102,18 +107,30 @@
         // always support timestamps except where there are bugs in Windows container and vGPU
         // implementations.
         uint64_t frequency;
-        DAWN_TRY(CheckHRESULT(queue->GetCommandQueue()->GetTimestampFrequency(&frequency),
+        DAWN_TRY(CheckHRESULT(mCommandQueue->GetTimestampFrequency(&frequency),
                               "D3D12 get timestamp frequency"));
         // Calculate the period in nanoseconds by the frequency.
         mTimestampPeriod = static_cast<float>(1e9) / frequency;
     }
 
-    DAWN_TRY(CheckHRESULT(mD3d12Device->CreateSharedHandle(queue->GetFence(), nullptr, GENERIC_ALL,
+    // If PIX is not attached, the QueryInterface fails. Hence, no need to check the return
+    // value.
+    mCommandQueue.As(&mD3d12SharingContract);
+
+    DAWN_TRY(CheckHRESULT(mD3d12Device->CreateFence(uint64_t(kBeginningOfGPUTime),
+                                                    D3D12_FENCE_FLAG_SHARED, IID_PPV_ARGS(&mFence)),
+                          "D3D12 create fence"));
+
+    mFenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
+    DAWN_ASSERT(mFenceEvent != nullptr);
+
+    DAWN_TRY(CheckHRESULT(mD3d12Device->CreateSharedHandle(mFence.Get(), nullptr, GENERIC_ALL,
                                                            nullptr, &mFenceHandle),
                           "D3D12 create fence handle"));
     DAWN_ASSERT(mFenceHandle != nullptr);
 
     // Initialize backend services
+    mCommandAllocatorManager = std::make_unique<CommandAllocatorManager>(this);
 
     // Zero sized allocator is never requested and does not need to exist.
     for (uint32_t countIndex = 0; countIndex < kNumViewDescriptorAllocators; countIndex++) {
@@ -174,7 +191,10 @@
     GetD3D12Device()->CreateCommandSignature(&programDesc, nullptr,
                                              IID_PPV_ARGS(&mDrawIndexedIndirectSignature));
 
-    DAWN_TRY(DeviceBase::Initialize(std::move(queue)));
+    DAWN_TRY(DeviceBase::Initialize(Queue::Create(this, &descriptor->defaultQueue)));
+    // Device shouldn't be used until after DeviceBase::Initialize so we must wait until after
+    // device initialization to call NextSerial
+    DAWN_TRY(NextSerial());
 
     // Ensure DXC if use_dxc toggle is set.
     DAWN_TRY(EnsureDXCIfRequired());
@@ -197,6 +217,18 @@
     return mD3d12Device.Get();
 }
 
+ID3D12Fence* Device::GetD3D12Fence() const {
+    return mFence.Get();
+}
+
+ComPtr<ID3D12CommandQueue> Device::GetCommandQueue() const {
+    return mCommandQueue;
+}
+
+ID3D12SharingContract* Device::GetSharingContract() const {
+    return mD3d12SharingContract.Get();
+}
+
 ComPtr<ID3D12CommandSignature> Device::GetDispatchIndirectSignature() const {
     return mDispatchIndirectSignature;
 }
@@ -225,14 +257,25 @@
     return ToBackend(GetPhysicalDevice())->GetBackend()->GetFunctions();
 }
 
+CommandAllocatorManager* Device::GetCommandAllocatorManager() const {
+    return mCommandAllocatorManager.get();
+}
+
 MutexProtected<ResidencyManager>& Device::GetResidencyManager() const {
     return *mResidencyManager;
 }
 
 ResultOrError<CommandRecordingContext*> Device::GetPendingCommandContext(
     Device::SubmitMode submitMode) {
-    // TODO(dawn:1413): Make callers of this method use the queue directly.
-    return ToBackend(GetQueue())->GetPendingCommandContext(submitMode);
+    // Callers of GetPendingCommandList do so to record commands. Only reserve a command
+    // allocator when it is needed so we don't submit empty command lists
+    if (!mPendingCommands.IsOpen()) {
+        DAWN_TRY(mPendingCommands.Open(mD3d12Device.Get(), mCommandAllocatorManager.get()));
+    }
+    if (submitMode == Device::SubmitMode::Normal) {
+        mPendingCommands.SetNeedsSubmit();
+    }
+    return &mPendingCommands;
 }
 
 MaybeError Device::CreateZeroBuffer() {
@@ -291,23 +334,83 @@
     ExecutionSerial completedSerial = GetQueue()->GetCompletedCommandSerial();
 
     (*mResourceAllocatorManager)->Tick(completedSerial);
+    DAWN_TRY(mCommandAllocatorManager->Tick(completedSerial));
     (*mViewShaderVisibleDescriptorAllocator)->Tick(completedSerial);
     (*mSamplerShaderVisibleDescriptorAllocator)->Tick(completedSerial);
     (*mRenderTargetViewAllocator)->Tick(completedSerial);
     (*mDepthStencilViewAllocator)->Tick(completedSerial);
     mUsedComObjectRefs->ClearUpTo(completedSerial);
 
-    DAWN_TRY(ToBackend(GetQueue())->SubmitPendingCommands());
+    if (mPendingCommands.IsOpen() && mPendingCommands.NeedsSubmit()) {
+        DAWN_TRY(ExecutePendingCommandContext());
+        DAWN_TRY(NextSerial());
+    }
 
     DAWN_TRY(CheckDebugLayerAndGenerateErrors());
 
     return {};
 }
 
+MaybeError Device::NextSerial() {
+    GetQueue()->IncrementLastSubmittedCommandSerial();
+
+    TRACE_EVENT1(GetPlatform(), General, "D3D12Device::SignalFence", "serial",
+                 uint64_t(GetLastSubmittedCommandSerial()));
+
+    return CheckHRESULT(
+        mCommandQueue->Signal(mFence.Get(), uint64_t(GetLastSubmittedCommandSerial())),
+        "D3D12 command queue signal fence");
+}
+
+MaybeError Device::WaitForSerial(ExecutionSerial serial) {
+    DAWN_TRY(GetQueue()->CheckPassedSerials());
+    if (GetQueue()->GetCompletedCommandSerial() < serial) {
+        DAWN_TRY(CheckHRESULT(mFence->SetEventOnCompletion(uint64_t(serial), mFenceEvent),
+                              "D3D12 set event on completion"));
+        WaitForSingleObject(mFenceEvent, INFINITE);
+        DAWN_TRY(GetQueue()->CheckPassedSerials());
+    }
+    return {};
+}
+
+ResultOrError<ExecutionSerial> Device::CheckAndUpdateCompletedSerials() {
+    ExecutionSerial completedSerial = ExecutionSerial(mFence->GetCompletedValue());
+    if (DAWN_UNLIKELY(completedSerial == ExecutionSerial(UINT64_MAX))) {
+        // GetCompletedValue returns UINT64_MAX if the device was removed.
+        // Try to query the failure reason.
+        DAWN_TRY(CheckHRESULT(mD3d12Device->GetDeviceRemovedReason(),
+                              "ID3D12Device::GetDeviceRemovedReason"));
+        // Otherwise, return a generic device lost error.
+        return DAWN_DEVICE_LOST_ERROR("Device lost");
+    }
+
+    if (completedSerial <= GetQueue()->GetCompletedCommandSerial()) {
+        return ExecutionSerial(0);
+    }
+
+    return completedSerial;
+}
+
 void Device::ReferenceUntilUnused(ComPtr<IUnknown> object) {
     mUsedComObjectRefs->Enqueue(std::move(object), GetPendingCommandSerial());
 }
 
+bool Device::HasPendingCommands() const {
+    return mPendingCommands.NeedsSubmit();
+}
+
+void Device::ForceEventualFlushOfCommands() {
+    if (mPendingCommands.IsOpen()) {
+        mPendingCommands.SetNeedsSubmit();
+    }
+}
+
+MaybeError Device::ExecutePendingCommandContext() {
+    DAWN_ASSERT(IsLockedByCurrentThreadIfNeeded());
+
+    return mPendingCommands.ExecuteCommandList(this);
+}
+
 ResultOrError<Ref<BindGroupBase>> Device::CreateBindGroupImpl(
     const BindGroupDescriptor* descriptor) {
     return BindGroup::Create(this, descriptor);
@@ -562,6 +665,17 @@
     return ToBackend(GetPhysicalDevice())->GetDeviceInfo();
 }
 
+MaybeError Device::WaitForIdleForDestruction() {
+    // Immediately forget about all pending commands
+    mPendingCommands.Release();
+
+    DAWN_TRY(NextSerial());
+    // Wait for all in-flight commands to finish executing
+    DAWN_TRY(WaitForSerial(GetLastSubmittedCommandSerial()));
+
+    return {};
+}
+
 void AppendDebugLayerMessagesToError(ID3D12InfoQueue* infoQueue,
                                      uint64_t totalErrors,
                                      ErrorData* error) {
@@ -666,6 +780,14 @@
 
     mZeroBuffer = nullptr;
 
+    // Immediately forget about all pending commands for the case where device is lost on its
+    // own and WaitForIdleForDestruction isn't called.
+    mPendingCommands.Release();
+
+    if (mFenceEvent != nullptr) {
+        ::CloseHandle(mFenceEvent);
+    }
+
     // Release recycled resource heaps and all other objects waiting for deletion in the resource
     // allocation manager.
     mResourceAllocatorManager.reset();
@@ -674,6 +796,11 @@
     mUsedComObjectRefs->ClearUpTo(std::numeric_limits<ExecutionSerial>::max());
 
     DAWN_ASSERT(mUsedComObjectRefs->Empty());
+    DAWN_ASSERT(!mPendingCommands.IsOpen());
+
+    // Now that we've cleared out pending work from the queue, we can safely release it and reclaim
+    // memory.
+    mCommandQueue.Reset();
 }
 
 MutexProtected<ShaderVisibleDescriptorAllocator>& Device::GetViewShaderVisibleDescriptorAllocator()
diff --git a/src/dawn/native/d3d12/DeviceD3D12.h b/src/dawn/native/d3d12/DeviceD3D12.h
index edf0c8d..810e5ea 100644
--- a/src/dawn/native/d3d12/DeviceD3D12.h
+++ b/src/dawn/native/d3d12/DeviceD3D12.h
@@ -76,11 +76,15 @@
     MaybeError TickImpl() override;
 
     ID3D12Device* GetD3D12Device() const;
+    ID3D12Fence* GetD3D12Fence() const;
+    ComPtr<ID3D12CommandQueue> GetCommandQueue() const;
+    ID3D12SharingContract* GetSharingContract() const;
 
     ComPtr<ID3D12CommandSignature> GetDispatchIndirectSignature() const;
     ComPtr<ID3D12CommandSignature> GetDrawIndirectSignature() const;
     ComPtr<ID3D12CommandSignature> GetDrawIndexedIndirectSignature() const;
 
+    CommandAllocatorManager* GetCommandAllocatorManager() const;
     MutexProtected<ResidencyManager>& GetResidencyManager() const;
 
     const PlatformFunctions* GetFunctions() const;
@@ -95,8 +99,13 @@
 
     const D3D12DeviceInfo& GetDeviceInfo() const;
 
+    MaybeError NextSerial();
+    MaybeError WaitForSerial(ExecutionSerial serial);
+
     void ReferenceUntilUnused(ComPtr<IUnknown> object);
 
+    MaybeError ExecutePendingCommandContext();
+
     MaybeError CopyFromStagingToBufferImpl(BufferBase* source,
                                            uint64_t sourceOffset,
                                            BufferBase* destination,
@@ -171,6 +180,12 @@
     // Dawn APIs
     void SetLabelImpl() override;
 
+    // TODO(dawn:1413) move these methods to the d3d12::Queue.
+    void ForceEventualFlushOfCommands();
+    bool HasPendingCommands() const;
+    ResultOrError<ExecutionSerial> CheckAndUpdateCompletedSerials();
+    MaybeError WaitForIdleForDestruction();
+
     // Those DXC methods are needed by d3d12::ShaderModule
     ComPtr<IDxcLibrary> GetDxcLibrary() const;
     ComPtr<IDxcCompiler3> GetDxcCompiler() const;
@@ -231,14 +246,22 @@
 
     MaybeError CreateZeroBuffer();
 
+    ComPtr<ID3D12Fence> mFence;
+    HANDLE mFenceEvent = nullptr;
+
     ComPtr<ID3D12Device> mD3d12Device;  // Device is owned by adapter and will not be outlived.
+    ComPtr<ID3D12CommandQueue> mCommandQueue;
+    ComPtr<ID3D12SharingContract> mD3d12SharingContract;
 
     ComPtr<ID3D12CommandSignature> mDispatchIndirectSignature;
     ComPtr<ID3D12CommandSignature> mDrawIndirectSignature;
     ComPtr<ID3D12CommandSignature> mDrawIndexedIndirectSignature;
 
+    CommandRecordingContext mPendingCommands;
+
     MutexProtected<SerialQueue<ExecutionSerial, ComPtr<IUnknown>>> mUsedComObjectRefs;
 
+    std::unique_ptr<CommandAllocatorManager> mCommandAllocatorManager;
     std::unique_ptr<MutexProtected<ResourceAllocatorManager>> mResourceAllocatorManager;
     std::unique_ptr<MutexProtected<ResidencyManager>> mResidencyManager;
 
diff --git a/src/dawn/native/d3d12/QueueD3D12.cpp b/src/dawn/native/d3d12/QueueD3D12.cpp
index 18ddf5c..4d2dc9f 100644
--- a/src/dawn/native/d3d12/QueueD3D12.cpp
+++ b/src/dawn/native/d3d12/QueueD3D12.cpp
@@ -34,7 +34,6 @@
 #include "dawn/native/Commands.h"
 #include "dawn/native/DynamicUploader.h"
 #include "dawn/native/d3d/D3DError.h"
-#include "dawn/native/d3d12/CommandAllocatorManager.h"
 #include "dawn/native/d3d12/CommandBufferD3D12.h"
 #include "dawn/native/d3d12/DeviceD3D12.h"
 #include "dawn/native/d3d12/UtilsD3D12.h"
@@ -44,68 +43,21 @@
 namespace dawn::native::d3d12 {
 
 // static
-ResultOrError<Ref<Queue>> Queue::Create(Device* device, const QueueDescriptor* descriptor) {
+Ref<Queue> Queue::Create(Device* device, const QueueDescriptor* descriptor) {
     Ref<Queue> queue = AcquireRef(new Queue(device, descriptor));
-    DAWN_TRY(queue->Initialize());
+    queue->Initialize();
     return queue;
 }
 
-Queue::~Queue() {}
-
-MaybeError Queue::Initialize() {
+void Queue::Initialize() {
     SetLabelImpl();
-
-    ID3D12Device* d3d12Device = ToBackend(GetDevice())->GetD3D12Device();
-
-    D3D12_COMMAND_QUEUE_DESC queueDesc = {};
-    queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
-    queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
-    DAWN_TRY(CheckHRESULT(d3d12Device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&mCommandQueue)),
-                          "D3D12 create command queue"));
-
-    // If PIX is not attached, the QueryInterface fails. Hence, no need to check the return
-    // value.
-    mCommandQueue.As(&mD3d12SharingContract);
-
-    DAWN_TRY(CheckHRESULT(d3d12Device->CreateFence(uint64_t(kBeginningOfGPUTime),
-                                                   D3D12_FENCE_FLAG_SHARED, IID_PPV_ARGS(&mFence)),
-                          "D3D12 create fence"));
-
-    mFenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
-    DAWN_ASSERT(mFenceEvent != nullptr);
-
-    // TODO(dawn:1413): Consider folding the command allocator manager in this class.
-    mCommandAllocatorManager = std::make_unique<CommandAllocatorManager>(this);
-    return {};
-}
-
-void Queue::Destroy() {
-    // Immediately forget about all pending commands for the case where device is lost on its
-    // own and WaitForIdleForDestruction isn't called.
-    DAWN_ASSERT(!mPendingCommands.IsOpen());
-    mPendingCommands.Release();
-
-    if (mFenceEvent != nullptr) {
-        ::CloseHandle(mFenceEvent);
-    }
-    mCommandQueue.Reset();
-}
-
-ID3D12Fence* Queue::GetFence() const {
-    return mFence.Get();
-}
-
-ID3D12CommandQueue* Queue::GetCommandQueue() const {
-    return mCommandQueue.Get();
-}
-
-ID3D12SharingContract* Queue::GetSharingContract() const {
-    return mD3d12SharingContract.Get();
 }
 
 MaybeError Queue::SubmitImpl(uint32_t commandCount, CommandBufferBase* const* commands) {
+    Device* device = ToBackend(GetDevice());
+
     CommandRecordingContext* commandContext;
-    DAWN_TRY_ASSIGN(commandContext, GetPendingCommandContext());
+    DAWN_TRY_ASSIGN(commandContext, device->GetPendingCommandContext());
 
     TRACE_EVENT_BEGIN1(GetDevice()->GetPlatform(), Recording, "CommandBufferD3D12::RecordCommands",
                        "serial", uint64_t(GetDevice()->GetPendingCommandSerial()));
@@ -115,111 +67,40 @@
     TRACE_EVENT_END1(GetDevice()->GetPlatform(), Recording, "CommandBufferD3D12::RecordCommands",
                      "serial", uint64_t(GetDevice()->GetPendingCommandSerial()));
 
-    return SubmitPendingCommands();
-}
+    DAWN_TRY(device->ExecutePendingCommandContext());
 
-MaybeError Queue::SubmitPendingCommands() {
-    Device* device = ToBackend(GetDevice());
-    DAWN_ASSERT(device->IsLockedByCurrentThreadIfNeeded());
+    DAWN_TRY(device->NextSerial());
 
-    if (!mPendingCommands.IsOpen() || !mPendingCommands.NeedsSubmit()) {
-        return {};
-    }
-
-    DAWN_TRY(mCommandAllocatorManager->Tick(GetCompletedCommandSerial()));
-    DAWN_TRY(mPendingCommands.ExecuteCommandList(device, mCommandQueue.Get()););
-    return NextSerial();
-}
-
-MaybeError Queue::NextSerial() {
-    ForceEventualFlushOfCommands();
-    DAWN_TRY(SubmitPendingCommands());
-
-    IncrementLastSubmittedCommandSerial();
-
-    TRACE_EVENT1(GetDevice()->GetPlatform(), General, "D3D12Device::SignalFence", "serial",
-                 uint64_t(GetLastSubmittedCommandSerial()));
-
-    return CheckHRESULT(
-        mCommandQueue->Signal(mFence.Get(), uint64_t(GetLastSubmittedCommandSerial())),
-        "D3D12 command queue signal fence");
-}
-
-MaybeError Queue::WaitForSerial(ExecutionSerial serial) {
-    if (GetCompletedCommandSerial() >= serial) {
-        return {};
-    }
-    DAWN_TRY(CheckHRESULT(mFence->SetEventOnCompletion(uint64_t(serial), mFenceEvent),
-                          "D3D12 set event on completion"));
-    WaitForSingleObject(mFenceEvent, INFINITE);
-    DAWN_TRY(CheckPassedSerials());
     return {};
 }
 
 bool Queue::HasPendingCommands() const {
-    return mPendingCommands.NeedsSubmit();
+    return ToBackend(GetDevice())->HasPendingCommands();
 }
 
 ResultOrError<ExecutionSerial> Queue::CheckAndUpdateCompletedSerials() {
-    ExecutionSerial completedSerial = ExecutionSerial(mFence->GetCompletedValue());
-    if (DAWN_UNLIKELY(completedSerial == ExecutionSerial(UINT64_MAX))) {
-        // GetCompletedValue returns UINT64_MAX if the device was removed.
-        // Try to query the failure reason.
-        ID3D12Device* d3d12Device = ToBackend(GetDevice())->GetD3D12Device();
-        DAWN_TRY(CheckHRESULT(d3d12Device->GetDeviceRemovedReason(),
-                              "ID3D12Device::GetDeviceRemovedReason"));
-        // Otherwise, return a generic device lost error.
-        return DAWN_DEVICE_LOST_ERROR("Device lost");
-    }
-
-    if (completedSerial <= GetCompletedCommandSerial()) {
-        return ExecutionSerial(0);
-    }
-
-    return completedSerial;
+    return ToBackend(GetDevice())->CheckAndUpdateCompletedSerials();
 }
 
 void Queue::ForceEventualFlushOfCommands() {
-    if (mPendingCommands.IsOpen()) {
-        mPendingCommands.SetNeedsSubmit();
-    }
+    return ToBackend(GetDevice())->ForceEventualFlushOfCommands();
 }
 
 MaybeError Queue::WaitForIdleForDestruction() {
-    // Immediately forget about all pending commands
-    mPendingCommands.Release();
-
-    DAWN_TRY(NextSerial());
-    // Wait for all in-flight commands to finish executing
-    DAWN_TRY(WaitForSerial(GetLastSubmittedCommandSerial()));
-
-    return {};
-}
-
-ResultOrError<CommandRecordingContext*> Queue::GetPendingCommandContext(SubmitMode submitMode) {
-    Device* device = ToBackend(GetDevice());
-    ID3D12Device* d3d12Device = device->GetD3D12Device();
-
-    // Callers of GetPendingCommandList do so to record commands. Only reserve a command
-    // allocator when it is needed so we don't submit empty command lists
-    if (!mPendingCommands.IsOpen()) {
-        DAWN_TRY(mPendingCommands.Open(d3d12Device, mCommandAllocatorManager.get()));
-    }
-    if (submitMode == SubmitMode::Normal) {
-        mPendingCommands.SetNeedsSubmit();
-    }
-    return &mPendingCommands;
+    return ToBackend(GetDevice())->WaitForIdleForDestruction();
 }
 
 void Queue::SetLabelImpl() {
     Device* device = ToBackend(GetDevice());
     // TODO(crbug.com/dawn/1344): When we start using multiple queues this needs to be adjusted
     // so it doesn't always change the default queue's label.
-    SetDebugName(device, mCommandQueue.Get(), "Dawn_Queue", GetLabel());
+    SetDebugName(device, device->GetCommandQueue().Get(), "Dawn_Queue", GetLabel());
 }
 
 void Queue::SetEventOnCompletion(ExecutionSerial serial, HANDLE event) {
-    mFence->SetEventOnCompletion(static_cast<uint64_t>(serial), event);
+    ToBackend(GetDevice())
+        ->GetD3D12Fence()
+        ->SetEventOnCompletion(static_cast<uint64_t>(serial), event);
 }
 
 }  // namespace dawn::native::d3d12
diff --git a/src/dawn/native/d3d12/QueueD3D12.h b/src/dawn/native/d3d12/QueueD3D12.h
index f545ee7..2d00586 100644
--- a/src/dawn/native/d3d12/QueueD3D12.h
+++ b/src/dawn/native/d3d12/QueueD3D12.h
@@ -28,8 +28,6 @@
 #ifndef SRC_DAWN_NATIVE_D3D12_QUEUED3D12_H_
 #define SRC_DAWN_NATIVE_D3D12_QUEUED3D12_H_
 
-#include <memory>
-
 #include "dawn/common/MutexProtected.h"
 #include "dawn/common/SerialMap.h"
 #include "dawn/native/SystemEvent.h"
@@ -43,24 +41,12 @@
 
 class Queue final : public d3d::Queue {
   public:
-    static ResultOrError<Ref<Queue>> Create(Device* device, const QueueDescriptor* descriptor);
-
-    void Destroy();
-
-    MaybeError NextSerial();
-    MaybeError WaitForSerial(ExecutionSerial serial);
-    ResultOrError<CommandRecordingContext*> GetPendingCommandContext(
-        SubmitMode submitMode = SubmitMode::Normal);
-    ID3D12Fence* GetFence() const;
-    ID3D12CommandQueue* GetCommandQueue() const;
-    ID3D12SharingContract* GetSharingContract() const;
-    MaybeError SubmitPendingCommands();
+    static Ref<Queue> Create(Device* device, const QueueDescriptor* descriptor);
 
   private:
     using d3d::Queue::Queue;
-    ~Queue() override;
 
-    MaybeError Initialize();
+    void Initialize();
 
     MaybeError SubmitImpl(uint32_t commandCount, CommandBufferBase* const* commands) override;
     bool HasPendingCommands() const override;
@@ -72,15 +58,6 @@
 
     // Dawn API
     void SetLabelImpl() override;
-
-    ComPtr<ID3D12Fence> mFence;
-    HANDLE mFenceEvent = nullptr;
-
-    CommandRecordingContext mPendingCommands;
-    ComPtr<ID3D12CommandQueue> mCommandQueue;
-    ComPtr<ID3D12SharingContract> mD3d12SharingContract;
-
-    std::unique_ptr<CommandAllocatorManager> mCommandAllocatorManager;
 };
 
 }  // namespace dawn::native::d3d12
diff --git a/src/dawn/native/d3d12/ResidencyManagerD3D12.cpp b/src/dawn/native/d3d12/ResidencyManagerD3D12.cpp
index b1b848e..c1b83b1 100644
--- a/src/dawn/native/d3d12/ResidencyManagerD3D12.cpp
+++ b/src/dawn/native/d3d12/ResidencyManagerD3D12.cpp
@@ -183,7 +183,9 @@
 
     // We must ensure that any previous use of a resource has completed before the resource can
     // be evicted.
-    DAWN_TRY(ToBackend(mDevice->GetQueue())->WaitForSerial(lastSubmissionSerial));
+    if (lastSubmissionSerial > mDevice->GetQueue()->GetCompletedCommandSerial()) {
+        DAWN_TRY(mDevice->WaitForSerial(lastSubmissionSerial));
+    }
 
     pageable->RemoveFromList();
     return pageable;
diff --git a/src/dawn/native/d3d12/SwapChainD3D12.cpp b/src/dawn/native/d3d12/SwapChainD3D12.cpp
index 65c1d55..e5ae0f8 100644
--- a/src/dawn/native/d3d12/SwapChainD3D12.cpp
+++ b/src/dawn/native/d3d12/SwapChainD3D12.cpp
@@ -38,7 +38,6 @@
 #include "dawn/native/d3d/D3DError.h"
 #include "dawn/native/d3d/UtilsD3D.h"
 #include "dawn/native/d3d12/DeviceD3D12.h"
-#include "dawn/native/d3d12/QueueD3D12.h"
 #include "dawn/native/d3d12/TextureD3D12.h"
 
 namespace dawn::native::d3d12 {
@@ -55,7 +54,7 @@
 SwapChain::~SwapChain() = default;
 
 IUnknown* SwapChain::GetD3DDeviceForCreatingSwapChain() {
-    return ToBackend(GetDevice()->GetQueue())->GetCommandQueue();
+    return ToBackend(GetDevice())->GetCommandQueue().Get();
 }
 
 void SwapChain::ReuseBuffers(SwapChainBase* previousSwapChain) {
@@ -83,22 +82,22 @@
 }
 
 MaybeError SwapChain::PresentImpl() {
-    Queue* queue = ToBackend(GetDevice()->GetQueue());
+    Device* device = ToBackend(GetDevice());
 
     // Transition the texture to the present state as required by IDXGISwapChain1::Present()
     // TODO(crbug.com/dawn/269): Remove the need for this by eagerly transitioning the
     // presentable texture to present at the end of submits that use them.
     CommandRecordingContext* commandContext;
-    DAWN_TRY_ASSIGN(commandContext, queue->GetPendingCommandContext());
+    DAWN_TRY_ASSIGN(commandContext, device->GetPendingCommandContext());
     mApiTexture->TrackUsageAndTransitionNow(commandContext, kPresentTextureUsage,
                                             mApiTexture->GetAllSubresources());
-    DAWN_TRY(queue->SubmitPendingCommands());
+    DAWN_TRY(device->ExecutePendingCommandContext());
 
     DAWN_TRY(PresentDXGISwapChain());
 
     // Record that "new" is the last time the buffer has been used.
-    DAWN_TRY(queue->NextSerial());
-    mBufferLastUsedSerials[mCurrentBuffer] = queue->GetPendingCommandSerial();
+    DAWN_TRY(device->NextSerial());
+    mBufferLastUsedSerials[mCurrentBuffer] = device->GetPendingCommandSerial();
 
     mApiTexture->APIDestroy();
     mApiTexture = nullptr;
@@ -107,14 +106,14 @@
 }
 
 ResultOrError<Ref<TextureBase>> SwapChain::GetCurrentTextureImpl() {
-    Queue* queue = ToBackend(GetDevice()->GetQueue());
+    Device* device = ToBackend(GetDevice());
 
     // Synchronously wait until previous operations on the next swapchain buffer are finished.
     // This is the logic that performs frame pacing.
     // TODO(crbug.com/dawn/269): Consider whether this should  be lifted for Mailbox so that
     // there is not frame pacing.
     mCurrentBuffer = GetDXGISwapChain()->GetCurrentBackBufferIndex();
-    DAWN_TRY(queue->WaitForSerial(mBufferLastUsedSerials[mCurrentBuffer]));
+    DAWN_TRY(device->WaitForSerial(mBufferLastUsedSerials[mCurrentBuffer]));
 
     // Create the API side objects for this use of the swapchain's buffer.
     TextureDescriptor descriptor = GetSwapChainBaseTextureDescriptor(this);
@@ -130,10 +129,10 @@
     // SerialQueue with the current "pending serial" so that we don't destroy the texture
     // before it is finished being used. Flush the commands and wait for that serial to be
     // passed, then Tick the device to make sure the reference to the D3D12 texture is removed.
-    Queue* queue = ToBackend(GetDevice()->GetQueue());
-    DAWN_TRY(queue->NextSerial());
-    DAWN_TRY(queue->WaitForSerial(queue->GetLastSubmittedCommandSerial()));
-    return ToBackend(GetDevice())->TickImpl();
+    Device* device = ToBackend(GetDevice());
+    DAWN_TRY(device->NextSerial());
+    DAWN_TRY(device->WaitForSerial(device->GetLastSubmittedCommandSerial()));
+    return device->TickImpl();
 }
 
 void SwapChain::DetachFromSurfaceImpl() {
diff --git a/src/dawn/native/d3d12/TextureD3D12.cpp b/src/dawn/native/d3d12/TextureD3D12.cpp
index 8d1b810..c0e00ed 100644
--- a/src/dawn/native/d3d12/TextureD3D12.cpp
+++ b/src/dawn/native/d3d12/TextureD3D12.cpp
@@ -46,7 +46,6 @@
 #include "dawn/native/d3d12/DeviceD3D12.h"
 #include "dawn/native/d3d12/Forward.h"
 #include "dawn/native/d3d12/HeapD3D12.h"
-#include "dawn/native/d3d12/QueueD3D12.h"
 #include "dawn/native/d3d12/ResourceAllocatorManagerD3D12.h"
 #include "dawn/native/d3d12/SharedFenceD3D12.h"
 #include "dawn/native/d3d12/SharedTextureMemoryD3D12.h"
@@ -365,22 +364,23 @@
 ResultOrError<ExecutionSerial> Texture::EndAccess() {
     DAWN_ASSERT(mD3D12ResourceFlags & D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS);
 
-    Queue* queue = ToBackend(GetDevice()->GetQueue());
-
+    Device* device = ToBackend(GetDevice());
     // Synchronize if texture access wasn't synchronized already due to ExecuteCommandLists.
     if (!mSignalFenceValue.has_value()) {
+        // Needed to ensure that command allocator doesn't get destroyed before pending commands
+        // are submitted due to calling NextSerial(). No-op if there are no pending commands.
+        DAWN_TRY(device->ExecutePendingCommandContext());
         // If there were pending commands that used this texture mSignalFenceValue will be set,
         // but if it's still not set, generate a signal fence after waiting on wait fences.
         if (!mSignalFenceValue.has_value()) {
             DAWN_TRY(SynchronizeImportedTextureBeforeUse());
             DAWN_TRY(SynchronizeImportedTextureAfterUse());
         }
-        // Make the queue signal the fence in finite time.
-        DAWN_TRY(queue->NextSerial());
+        DAWN_TRY(device->NextSerial());
+        DAWN_ASSERT(mSignalFenceValue.has_value());
     }
-
     ExecutionSerial ret = mSignalFenceValue.value();
-    DAWN_ASSERT(ret <= queue->GetLastSubmittedCommandSerial());
+    DAWN_ASSERT(ret <= device->GetLastSubmittedCommandSerial());
     // Explicitly call reset() since std::move() on optional doesn't make it std::nullopt.
     mSignalFenceValue.reset();
     return ret;
@@ -422,12 +422,11 @@
 MaybeError Texture::SynchronizeImportedTextureBeforeUse() {
     // Perform the wait only on the first call.
     Device* device = ToBackend(GetDevice());
-    ID3D12CommandQueue* commandQueue = ToBackend(device->GetQueue())->GetCommandQueue();
-
     for (Ref<d3d::Fence>& fence : mWaitFences) {
-        DAWN_TRY(CheckHRESULT(commandQueue->Wait(static_cast<Fence*>(fence.Get())->GetD3D12Fence(),
-                                                 fence->GetFenceValue()),
-                              "D3D12 fence wait"););
+        DAWN_TRY(CheckHRESULT(
+                     device->GetCommandQueue()->Wait(
+                         static_cast<Fence*>(fence.Get())->GetD3D12Fence(), fence->GetFenceValue()),
+                     "D3D12 fence wait"););
         // Keep D3D12 fence alive since we'll clear the waitFences list below.
         device->ReferenceUntilUnused(static_cast<Fence*>(fence.Get())->GetD3D12Fence());
     }
@@ -441,9 +440,9 @@
     }
 
     for (const auto& fence : fences) {
-        DAWN_TRY(CheckHRESULT(
-            commandQueue->Wait(ToBackend(fence.object)->GetD3DFence(), fence.signaledValue),
-            "D3D12 fence wait"));
+        DAWN_TRY(CheckHRESULT(device->GetCommandQueue()->Wait(
+                                  ToBackend(fence.object)->GetD3DFence(), fence.signaledValue),
+                              "D3D12 fence wait"));
         // Keep D3D12 fence alive until commands complete.
         device->ReferenceUntilUnused(ToBackend(fence.object)->GetD3DFence());
     }
@@ -457,15 +456,15 @@
     // If we know we're dealing with a swapbuffer texture, inform PIX we've
     // "presented" the texture so it can determine frame boundaries and use its
     // contents for the UI.
-    Queue* queue = ToBackend(GetDevice()->GetQueue());
+    Device* device = ToBackend(GetDevice());
     if (mSwapChainTexture) {
-        ID3D12SharingContract* d3dSharingContract = queue->GetSharingContract();
+        ID3D12SharingContract* d3dSharingContract = device->GetSharingContract();
         if (d3dSharingContract != nullptr) {
             d3dSharingContract->Present(mResourceAllocation.GetD3D12Resource(), 0, 0);
         }
     }
     // NextSerial() will be called after this - this is also checked in EndAccess().
-    mSignalFenceValue = queue->GetPendingCommandSerial();
+    mSignalFenceValue = device->GetPendingCommandSerial();
     return {};
 }
 
diff --git a/src/dawn/tests/white_box/D3D12DescriptorHeapTests.cpp b/src/dawn/tests/white_box/D3D12DescriptorHeapTests.cpp
index 37eae0c..921a851 100644
--- a/src/dawn/tests/white_box/D3D12DescriptorHeapTests.cpp
+++ b/src/dawn/tests/white_box/D3D12DescriptorHeapTests.cpp
@@ -257,7 +257,7 @@
         // CheckPassedSerials() will update the last internally completed serial.
         EXPECT_TRUE(mD3DQueue->CheckPassedSerials().IsSuccess());
         // NextSerial() will increment the last internally submitted serial.
-        EXPECT_TRUE(mD3DQueue->NextSerial().IsSuccess());
+        EXPECT_TRUE(mD3DDevice->NextSerial().IsSuccess());
     }
 
     // Repeat up to |kFrameDepth| again but ensure heaps are the same in the expected order
@@ -269,7 +269,7 @@
         EXPECT_TRUE(heaps.front() == heap);
         heaps.pop_front();
         EXPECT_TRUE(mD3DQueue->CheckPassedSerials().IsSuccess());
-        EXPECT_TRUE(mD3DQueue->NextSerial().IsSuccess());
+        EXPECT_TRUE(mD3DDevice->NextSerial().IsSuccess());
     }
 
     EXPECT_TRUE(heaps.empty());
@@ -1064,7 +1064,7 @@
 
     EXPECT_TRUE(gpuAllocator->IsAllocationStillValid(gpuHeapDescAllocation));
 
-    EXPECT_TRUE(mD3DQueue->NextSerial().IsSuccess());
+    EXPECT_TRUE(d3dDevice->NextSerial().IsSuccess());
 
     EXPECT_FALSE(gpuAllocator->IsAllocationStillValid(gpuHeapDescAllocation));
 }
diff --git a/src/dawn/tests/white_box/GPUTimestampCalibrationTests_D3D12.cpp b/src/dawn/tests/white_box/GPUTimestampCalibrationTests_D3D12.cpp
index 07ca216..7df95ed 100644
--- a/src/dawn/tests/white_box/GPUTimestampCalibrationTests_D3D12.cpp
+++ b/src/dawn/tests/white_box/GPUTimestampCalibrationTests_D3D12.cpp
@@ -28,7 +28,6 @@
 #include <memory>
 
 #include "dawn/native/d3d12/DeviceD3D12.h"
-#include "dawn/native/d3d12/QueueD3D12.h"
 #include "dawn/tests/white_box/GPUTimestampCalibrationTests.h"
 
 namespace dawn {
@@ -38,20 +37,18 @@
   public:
     explicit GPUTimestampCalibrationTestsD3D12(const wgpu::Device& device) {
         mBackendDevice = native::d3d12::ToBackend(native::FromAPI(device.Get()));
-        mBackendQueue = native::d3d12::ToBackend(mBackendDevice->GetQueue());
     }
 
     bool IsSupported() const override { return true; }
 
     void GetTimestampCalibration(uint64_t* gpuTimestamp, uint64_t* cpuTimestamp) override {
-        mBackendQueue->GetCommandQueue()->GetClockCalibration(gpuTimestamp, cpuTimestamp);
+        mBackendDevice->GetCommandQueue()->GetClockCalibration(gpuTimestamp, cpuTimestamp);
     }
 
     float GetTimestampPeriod() const override { return mBackendDevice->GetTimestampPeriodInNS(); }
 
   private:
     native::d3d12::Device* mBackendDevice;
-    native::d3d12::Queue* mBackendQueue;
 };
 
 }  // anonymous namespace