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>
diff --git a/src/dawn/native/d3d12/CommandAllocatorManager.cpp b/src/dawn/native/d3d12/CommandAllocatorManager.cpp
index 09e144b..f3cd0ac 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(Device* device)
-    : device(device), mAllocatorCount(0) {
+CommandAllocatorManager::CommandAllocatorManager(Queue* queue) : mQueue(queue), 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(device->WaitForSerial(firstSerial));
+        DAWN_TRY(mQueue->WaitForSerial(firstSerial));
         DAWN_TRY(Tick(firstSerial));
     }
 
@@ -56,9 +56,11 @@
     if (firstFreeIndex >= mAllocatorCount) {
         DAWN_ASSERT(firstFreeIndex == mAllocatorCount);
         mAllocatorCount++;
+
+        ID3D12Device* d3d12Device = ToBackend(mQueue->GetDevice())->GetD3D12Device();
         DAWN_TRY(CheckHRESULT(
-            device->GetD3D12Device()->CreateCommandAllocator(
-                D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&mCommandAllocators[firstFreeIndex])),
+            d3d12Device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT,
+                                                IID_PPV_ARGS(&mCommandAllocators[firstFreeIndex])),
             "D3D12 create command allocator"));
     }
 
@@ -68,7 +70,7 @@
     // Enqueue the command allocator. It will be scheduled for reset after the next
     // ExecuteCommandLists
     mInFlightCommandAllocators.Enqueue({mCommandAllocators[firstFreeIndex], firstFreeIndex},
-                                       device->GetPendingCommandSerial());
+                                       mQueue->GetPendingCommandSerial());
     return mCommandAllocators[firstFreeIndex].Get();
 }
 
diff --git a/src/dawn/native/d3d12/CommandAllocatorManager.h b/src/dawn/native/d3d12/CommandAllocatorManager.h
index f8e9d0d..b0a29ed 100644
--- a/src/dawn/native/d3d12/CommandAllocatorManager.h
+++ b/src/dawn/native/d3d12/CommandAllocatorManager.h
@@ -38,11 +38,11 @@
 
 namespace dawn::native::d3d12 {
 
-class Device;
+class Queue;
 
 class CommandAllocatorManager {
   public:
-    explicit CommandAllocatorManager(Device* device);
+    explicit CommandAllocatorManager(Queue* queue);
 
     // 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,7 +50,8 @@
     MaybeError Tick(ExecutionSerial lastCompletedSerial);
 
   private:
-    Device* device;
+    // The allocator manager is owned by the queue so the queue outlives it.
+    Queue* mQueue;
 
     // 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 6f88e28..ba2b37b 100644
--- a/src/dawn/native/d3d12/CommandRecordingContext.cpp
+++ b/src/dawn/native/d3d12/CommandRecordingContext.cpp
@@ -78,70 +78,73 @@
     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();
+MaybeError CommandRecordingContext::ExecuteCommandList(Device* device,
+                                                       ID3D12CommandQueue* commandQueue) {
+    if (!IsOpen()) {
+        return {};
     }
+
+    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 1778813..1ba4d47 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);
+    MaybeError ExecuteCommandList(Device* device, ID3D12CommandQueue* commandQueue);
 
     void TrackHeapUsage(Heap* heap, ExecutionSerial serial);
 
diff --git a/src/dawn/native/d3d12/DeviceD3D12.cpp b/src/dawn/native/d3d12/DeviceD3D12.cpp
index 90e448f..ba296c6 100644
--- a/src/dawn/native/d3d12/DeviceD3D12.cpp
+++ b/src/dawn/native/d3d12/DeviceD3D12.cpp
@@ -91,13 +91,8 @@
 
     DAWN_ASSERT(mD3d12Device != nullptr);
 
-    // 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"));
+    Ref<Queue> queue;
+    DAWN_TRY_ASSIGN(queue, Queue::Create(this, &descriptor->defaultQueue));
 
     if ((HasFeature(Feature::TimestampQuery) ||
          HasFeature(Feature::ChromiumExperimentalTimestampQueryInsidePasses)) &&
@@ -107,30 +102,18 @@
         // always support timestamps except where there are bugs in Windows container and vGPU
         // implementations.
         uint64_t frequency;
-        DAWN_TRY(CheckHRESULT(mCommandQueue->GetTimestampFrequency(&frequency),
+        DAWN_TRY(CheckHRESULT(queue->GetCommandQueue()->GetTimestampFrequency(&frequency),
                               "D3D12 get timestamp frequency"));
         // Calculate the period in nanoseconds by the frequency.
         mTimestampPeriod = static_cast<float>(1e9) / frequency;
     }
 
-    // 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,
+    DAWN_TRY(CheckHRESULT(mD3d12Device->CreateSharedHandle(queue->GetFence(), 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++) {
@@ -191,10 +174,7 @@
     GetD3D12Device()->CreateCommandSignature(&programDesc, nullptr,
                                              IID_PPV_ARGS(&mDrawIndexedIndirectSignature));
 
-    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());
+    DAWN_TRY(DeviceBase::Initialize(std::move(queue)));
 
     // Ensure DXC if use_dxc toggle is set.
     DAWN_TRY(EnsureDXCIfRequired());
@@ -217,18 +197,6 @@
     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;
 }
@@ -257,25 +225,14 @@
     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) {
-    // 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;
+    // TODO(dawn:1413): Make callers of this method use the queue directly.
+    return ToBackend(GetQueue())->GetPendingCommandContext(submitMode);
 }
 
 MaybeError Device::CreateZeroBuffer() {
@@ -334,83 +291,23 @@
     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);
 
-    if (mPendingCommands.IsOpen() && mPendingCommands.NeedsSubmit()) {
-        DAWN_TRY(ExecutePendingCommandContext());
-        DAWN_TRY(NextSerial());
-    }
+    DAWN_TRY(ToBackend(GetQueue())->SubmitPendingCommands());
 
     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);
@@ -665,17 +562,6 @@
     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) {
@@ -780,14 +666,6 @@
 
     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();
@@ -796,11 +674,6 @@
     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 810e5ea..edf0c8d 100644
--- a/src/dawn/native/d3d12/DeviceD3D12.h
+++ b/src/dawn/native/d3d12/DeviceD3D12.h
@@ -76,15 +76,11 @@
     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;
@@ -99,13 +95,8 @@
 
     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,
@@ -180,12 +171,6 @@
     // 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;
@@ -246,22 +231,14 @@
 
     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 4d2dc9f..18ddf5c 100644
--- a/src/dawn/native/d3d12/QueueD3D12.cpp
+++ b/src/dawn/native/d3d12/QueueD3D12.cpp
@@ -34,6 +34,7 @@
 #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"
@@ -43,21 +44,68 @@
 namespace dawn::native::d3d12 {
 
 // static
-Ref<Queue> Queue::Create(Device* device, const QueueDescriptor* descriptor) {
+ResultOrError<Ref<Queue>> Queue::Create(Device* device, const QueueDescriptor* descriptor) {
     Ref<Queue> queue = AcquireRef(new Queue(device, descriptor));
-    queue->Initialize();
+    DAWN_TRY(queue->Initialize());
     return queue;
 }
 
-void Queue::Initialize() {
+Queue::~Queue() {}
+
+MaybeError 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, device->GetPendingCommandContext());
+    DAWN_TRY_ASSIGN(commandContext, GetPendingCommandContext());
 
     TRACE_EVENT_BEGIN1(GetDevice()->GetPlatform(), Recording, "CommandBufferD3D12::RecordCommands",
                        "serial", uint64_t(GetDevice()->GetPendingCommandSerial()));
@@ -67,40 +115,111 @@
     TRACE_EVENT_END1(GetDevice()->GetPlatform(), Recording, "CommandBufferD3D12::RecordCommands",
                      "serial", uint64_t(GetDevice()->GetPendingCommandSerial()));
 
-    DAWN_TRY(device->ExecutePendingCommandContext());
+    return SubmitPendingCommands();
+}
 
-    DAWN_TRY(device->NextSerial());
+MaybeError Queue::SubmitPendingCommands() {
+    Device* device = ToBackend(GetDevice());
+    DAWN_ASSERT(device->IsLockedByCurrentThreadIfNeeded());
 
+    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 ToBackend(GetDevice())->HasPendingCommands();
+    return mPendingCommands.NeedsSubmit();
 }
 
 ResultOrError<ExecutionSerial> Queue::CheckAndUpdateCompletedSerials() {
-    return ToBackend(GetDevice())->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;
 }
 
 void Queue::ForceEventualFlushOfCommands() {
-    return ToBackend(GetDevice())->ForceEventualFlushOfCommands();
+    if (mPendingCommands.IsOpen()) {
+        mPendingCommands.SetNeedsSubmit();
+    }
 }
 
 MaybeError Queue::WaitForIdleForDestruction() {
-    return ToBackend(GetDevice())->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;
 }
 
 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, device->GetCommandQueue().Get(), "Dawn_Queue", GetLabel());
+    SetDebugName(device, mCommandQueue.Get(), "Dawn_Queue", GetLabel());
 }
 
 void Queue::SetEventOnCompletion(ExecutionSerial serial, HANDLE event) {
-    ToBackend(GetDevice())
-        ->GetD3D12Fence()
-        ->SetEventOnCompletion(static_cast<uint64_t>(serial), event);
+    mFence->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 2d00586..f545ee7 100644
--- a/src/dawn/native/d3d12/QueueD3D12.h
+++ b/src/dawn/native/d3d12/QueueD3D12.h
@@ -28,6 +28,8 @@
 #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"
@@ -41,12 +43,24 @@
 
 class Queue final : public d3d::Queue {
   public:
-    static Ref<Queue> Create(Device* device, const QueueDescriptor* descriptor);
+    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();
 
   private:
     using d3d::Queue::Queue;
+    ~Queue() override;
 
-    void Initialize();
+    MaybeError Initialize();
 
     MaybeError SubmitImpl(uint32_t commandCount, CommandBufferBase* const* commands) override;
     bool HasPendingCommands() const override;
@@ -58,6 +72,15 @@
 
     // 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 c1b83b1..b1b848e 100644
--- a/src/dawn/native/d3d12/ResidencyManagerD3D12.cpp
+++ b/src/dawn/native/d3d12/ResidencyManagerD3D12.cpp
@@ -183,9 +183,7 @@
 
     // We must ensure that any previous use of a resource has completed before the resource can
     // be evicted.
-    if (lastSubmissionSerial > mDevice->GetQueue()->GetCompletedCommandSerial()) {
-        DAWN_TRY(mDevice->WaitForSerial(lastSubmissionSerial));
-    }
+    DAWN_TRY(ToBackend(mDevice->GetQueue())->WaitForSerial(lastSubmissionSerial));
 
     pageable->RemoveFromList();
     return pageable;
diff --git a/src/dawn/native/d3d12/SwapChainD3D12.cpp b/src/dawn/native/d3d12/SwapChainD3D12.cpp
index e5ae0f8..65c1d55 100644
--- a/src/dawn/native/d3d12/SwapChainD3D12.cpp
+++ b/src/dawn/native/d3d12/SwapChainD3D12.cpp
@@ -38,6 +38,7 @@
 #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 {
@@ -54,7 +55,7 @@
 SwapChain::~SwapChain() = default;
 
 IUnknown* SwapChain::GetD3DDeviceForCreatingSwapChain() {
-    return ToBackend(GetDevice())->GetCommandQueue().Get();
+    return ToBackend(GetDevice()->GetQueue())->GetCommandQueue();
 }
 
 void SwapChain::ReuseBuffers(SwapChainBase* previousSwapChain) {
@@ -82,22 +83,22 @@
 }
 
 MaybeError SwapChain::PresentImpl() {
-    Device* device = ToBackend(GetDevice());
+    Queue* queue = ToBackend(GetDevice()->GetQueue());
 
     // 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, device->GetPendingCommandContext());
+    DAWN_TRY_ASSIGN(commandContext, queue->GetPendingCommandContext());
     mApiTexture->TrackUsageAndTransitionNow(commandContext, kPresentTextureUsage,
                                             mApiTexture->GetAllSubresources());
-    DAWN_TRY(device->ExecutePendingCommandContext());
+    DAWN_TRY(queue->SubmitPendingCommands());
 
     DAWN_TRY(PresentDXGISwapChain());
 
     // Record that "new" is the last time the buffer has been used.
-    DAWN_TRY(device->NextSerial());
-    mBufferLastUsedSerials[mCurrentBuffer] = device->GetPendingCommandSerial();
+    DAWN_TRY(queue->NextSerial());
+    mBufferLastUsedSerials[mCurrentBuffer] = queue->GetPendingCommandSerial();
 
     mApiTexture->APIDestroy();
     mApiTexture = nullptr;
@@ -106,14 +107,14 @@
 }
 
 ResultOrError<Ref<TextureBase>> SwapChain::GetCurrentTextureImpl() {
-    Device* device = ToBackend(GetDevice());
+    Queue* queue = ToBackend(GetDevice()->GetQueue());
 
     // 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(device->WaitForSerial(mBufferLastUsedSerials[mCurrentBuffer]));
+    DAWN_TRY(queue->WaitForSerial(mBufferLastUsedSerials[mCurrentBuffer]));
 
     // Create the API side objects for this use of the swapchain's buffer.
     TextureDescriptor descriptor = GetSwapChainBaseTextureDescriptor(this);
@@ -129,10 +130,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.
-    Device* device = ToBackend(GetDevice());
-    DAWN_TRY(device->NextSerial());
-    DAWN_TRY(device->WaitForSerial(device->GetLastSubmittedCommandSerial()));
-    return device->TickImpl();
+    Queue* queue = ToBackend(GetDevice()->GetQueue());
+    DAWN_TRY(queue->NextSerial());
+    DAWN_TRY(queue->WaitForSerial(queue->GetLastSubmittedCommandSerial()));
+    return ToBackend(GetDevice())->TickImpl();
 }
 
 void SwapChain::DetachFromSurfaceImpl() {
diff --git a/src/dawn/native/d3d12/TextureD3D12.cpp b/src/dawn/native/d3d12/TextureD3D12.cpp
index c0e00ed..8d1b810 100644
--- a/src/dawn/native/d3d12/TextureD3D12.cpp
+++ b/src/dawn/native/d3d12/TextureD3D12.cpp
@@ -46,6 +46,7 @@
 #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"
@@ -364,23 +365,22 @@
 ResultOrError<ExecutionSerial> Texture::EndAccess() {
     DAWN_ASSERT(mD3D12ResourceFlags & D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS);
 
-    Device* device = ToBackend(GetDevice());
+    Queue* queue = ToBackend(GetDevice()->GetQueue());
+
     // 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());
         }
-        DAWN_TRY(device->NextSerial());
-        DAWN_ASSERT(mSignalFenceValue.has_value());
+        // Make the queue signal the fence in finite time.
+        DAWN_TRY(queue->NextSerial());
     }
+
     ExecutionSerial ret = mSignalFenceValue.value();
-    DAWN_ASSERT(ret <= device->GetLastSubmittedCommandSerial());
+    DAWN_ASSERT(ret <= queue->GetLastSubmittedCommandSerial());
     // Explicitly call reset() since std::move() on optional doesn't make it std::nullopt.
     mSignalFenceValue.reset();
     return ret;
@@ -422,11 +422,12 @@
 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(
-                     device->GetCommandQueue()->Wait(
-                         static_cast<Fence*>(fence.Get())->GetD3D12Fence(), fence->GetFenceValue()),
-                     "D3D12 fence wait"););
+        DAWN_TRY(CheckHRESULT(commandQueue->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());
     }
@@ -440,9 +441,9 @@
     }
 
     for (const auto& fence : fences) {
-        DAWN_TRY(CheckHRESULT(device->GetCommandQueue()->Wait(
-                                  ToBackend(fence.object)->GetD3DFence(), fence.signaledValue),
-                              "D3D12 fence wait"));
+        DAWN_TRY(CheckHRESULT(
+            commandQueue->Wait(ToBackend(fence.object)->GetD3DFence(), fence.signaledValue),
+            "D3D12 fence wait"));
         // Keep D3D12 fence alive until commands complete.
         device->ReferenceUntilUnused(ToBackend(fence.object)->GetD3DFence());
     }
@@ -456,15 +457,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.
-    Device* device = ToBackend(GetDevice());
+    Queue* queue = ToBackend(GetDevice()->GetQueue());
     if (mSwapChainTexture) {
-        ID3D12SharingContract* d3dSharingContract = device->GetSharingContract();
+        ID3D12SharingContract* d3dSharingContract = queue->GetSharingContract();
         if (d3dSharingContract != nullptr) {
             d3dSharingContract->Present(mResourceAllocation.GetD3D12Resource(), 0, 0);
         }
     }
     // NextSerial() will be called after this - this is also checked in EndAccess().
-    mSignalFenceValue = device->GetPendingCommandSerial();
+    mSignalFenceValue = queue->GetPendingCommandSerial();
     return {};
 }
 
diff --git a/src/dawn/tests/white_box/D3D12DescriptorHeapTests.cpp b/src/dawn/tests/white_box/D3D12DescriptorHeapTests.cpp
index 921a851..37eae0c 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(mD3DDevice->NextSerial().IsSuccess());
+        EXPECT_TRUE(mD3DQueue->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(mD3DDevice->NextSerial().IsSuccess());
+        EXPECT_TRUE(mD3DQueue->NextSerial().IsSuccess());
     }
 
     EXPECT_TRUE(heaps.empty());
@@ -1064,7 +1064,7 @@
 
     EXPECT_TRUE(gpuAllocator->IsAllocationStillValid(gpuHeapDescAllocation));
 
-    EXPECT_TRUE(d3dDevice->NextSerial().IsSuccess());
+    EXPECT_TRUE(mD3DQueue->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 7df95ed..07ca216 100644
--- a/src/dawn/tests/white_box/GPUTimestampCalibrationTests_D3D12.cpp
+++ b/src/dawn/tests/white_box/GPUTimestampCalibrationTests_D3D12.cpp
@@ -28,6 +28,7 @@
 #include <memory>
 
 #include "dawn/native/d3d12/DeviceD3D12.h"
+#include "dawn/native/d3d12/QueueD3D12.h"
 #include "dawn/tests/white_box/GPUTimestampCalibrationTests.h"
 
 namespace dawn {
@@ -37,18 +38,20 @@
   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 {
-        mBackendDevice->GetCommandQueue()->GetClockCalibration(gpuTimestamp, cpuTimestamp);
+        mBackendQueue->GetCommandQueue()->GetClockCalibration(gpuTimestamp, cpuTimestamp);
     }
 
     float GetTimestampPeriod() const override { return mBackendDevice->GetTimestampPeriodInNS(); }
 
   private:
     native::d3d12::Device* mBackendDevice;
+    native::d3d12::Queue* mBackendQueue;
 };
 
 }  // anonymous namespace