Refactor Serial tracking to be owned by Device frontend.

Move mCompletedSerial and mLastSubmittedSerial to Device frontend and
add getters and setters for the device backend to access such.
This is to aid the Device in taking more ownership of Serials and Ticking.

Bug: dawn:400
Change-Id: Ifa53ac294a871e484716842a3d212373b57847c4
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/20480
Commit-Queue: Natasha Lee <natlee@microsoft.com>
Reviewed-by: Austin Eng <enga@chromium.org>
diff --git a/src/dawn_native/Device.cpp b/src/dawn_native/Device.cpp
index 90536c7..4bbca1d 100644
--- a/src/dawn_native/Device.cpp
+++ b/src/dawn_native/Device.cpp
@@ -124,6 +124,7 @@
                 // complete before proceeding with destruction.
                 // Assert that errors are device loss so that we can continue with destruction
                 AssertAndIgnoreDeviceLossError(WaitForIdleForDestruction());
+                ASSERT(mCompletedSerial == mLastSubmittedSerial);
                 break;
 
             case State::BeingDisconnected:
@@ -173,6 +174,7 @@
 
             // Assert that errors are device losses so that we can continue with destruction.
             AssertAndIgnoreDeviceLossError(WaitForIdleForDestruction());
+            ASSERT(mCompletedSerial == mLastSubmittedSerial);
             mState = State::Disconnected;
 
             // Now everything is as if the device was lost.
@@ -292,6 +294,45 @@
         return mFenceSignalTracker.get();
     }
 
+    Serial DeviceBase::GetCompletedCommandSerial() const {
+        return mCompletedSerial;
+    }
+
+    Serial DeviceBase::GetLastSubmittedCommandSerial() const {
+        return mLastSubmittedSerial;
+    }
+
+    void DeviceBase::IncrementLastSubmittedCommandSerial() {
+        mLastSubmittedSerial++;
+    }
+
+    void DeviceBase::ArtificiallyIncrementSerials() {
+        mCompletedSerial++;
+        mLastSubmittedSerial++;
+    }
+
+    void DeviceBase::AssumeCommandsComplete() {
+        mLastSubmittedSerial++;
+        mCompletedSerial = mLastSubmittedSerial;
+    }
+
+    Serial DeviceBase::GetPendingCommandSerial() const {
+        return mLastSubmittedSerial + 1;
+    }
+
+    void DeviceBase::CheckPassedSerials() {
+        Serial completedSerial = CheckAndUpdateCompletedSerials();
+
+        ASSERT(completedSerial <= mLastSubmittedSerial);
+        // completedSerial should not be less than mCompletedSerial unless it is 0.
+        // It can be 0 when there's no fences to check.
+        ASSERT(completedSerial >= mCompletedSerial || completedSerial == 0);
+
+        if (completedSerial > mCompletedSerial) {
+            mCompletedSerial = completedSerial;
+        }
+    }
+
     ResultOrError<const Format*> DeviceBase::GetInternalFormat(wgpu::TextureFormat format) const {
         size_t index = ComputeFormatIndex(format);
         if (index >= mFormatTable.size()) {
diff --git a/src/dawn_native/Device.h b/src/dawn_native/Device.h
index 5187427..0a270a5 100644
--- a/src/dawn_native/Device.h
+++ b/src/dawn_native/Device.h
@@ -86,9 +86,9 @@
             CommandEncoder* encoder,
             const CommandBufferDescriptor* descriptor) = 0;
 
-        virtual Serial GetCompletedCommandSerial() const = 0;
-        virtual Serial GetLastSubmittedCommandSerial() const = 0;
-        virtual Serial GetPendingCommandSerial() const = 0;
+        Serial GetCompletedCommandSerial() const;
+        Serial GetLastSubmittedCommandSerial() const;
+        Serial GetPendingCommandSerial() const;
         virtual MaybeError TickImpl() = 0;
 
         // Many Dawn objects are completely immutable once created which means that if two
@@ -220,6 +220,18 @@
         MaybeError Initialize(QueueBase* defaultQueue);
         void ShutDownBase();
 
+        // Incrememt mLastSubmittedSerial when we submit the next serial
+        void IncrementLastSubmittedCommandSerial();
+        // If there's no GPU work in flight we still need to artificially increment the serial
+        // so that CPU operations waiting on GPU completion can know they don't have to wait.
+        void ArtificiallyIncrementSerials();
+        // During shut down of device, some operations might have been started since the last submit
+        // and waiting on a serial that doesn't have a corresponding fence enqueued. Fake serials to
+        // make all commands look completed.
+        void AssumeCommandsComplete();
+        // Check for passed fences and set the new completed serial
+        void CheckPassedSerials();
+
       private:
         virtual ResultOrError<BindGroupBase*> CreateBindGroupImpl(
             const BindGroupDescriptor* descriptor) = 0;
@@ -281,6 +293,18 @@
 
         void ConsumeError(std::unique_ptr<ErrorData> error);
 
+        // Each backend should implement to check their passed fences if there are any and return a
+        // completed serial. Return 0 should indicate no fences to check.
+        virtual Serial CheckAndUpdateCompletedSerials() = 0;
+        // mCompletedSerial tracks the last completed command serial that the fence has returned.
+        // mLastSubmittedSerial tracks the last submitted command serial.
+        // During device removal, the serials could be artificially incremented
+        // to make it appear as if commands have been compeleted. They can also be artificially
+        // incremented when no work is being done in the GPU so CPU operations don't have to wait on
+        // stale serials.
+        Serial mCompletedSerial = 0;
+        Serial mLastSubmittedSerial = 0;
+
         // ShutDownImpl is used to clean up and release resources used by device, does not wait for
         // GPU or check errors.
         virtual void ShutDownImpl() = 0;
diff --git a/src/dawn_native/d3d12/DeviceD3D12.cpp b/src/dawn_native/d3d12/DeviceD3D12.cpp
index fdd620f..ce935a9 100644
--- a/src/dawn_native/d3d12/DeviceD3D12.cpp
+++ b/src/dawn_native/d3d12/DeviceD3D12.cpp
@@ -72,9 +72,10 @@
         // value.
         mCommandQueue.As(&mD3d12SharingContract);
 
-        DAWN_TRY(CheckHRESULT(mD3d12Device->CreateFence(mLastSubmittedSerial, D3D12_FENCE_FLAG_NONE,
-                                                        IID_PPV_ARGS(&mFence)),
-                              "D3D12 create fence"));
+        DAWN_TRY(
+            CheckHRESULT(mD3d12Device->CreateFence(GetLastSubmittedCommandSerial(),
+                                                   D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&mFence)),
+                         "D3D12 create fence"));
 
         mFenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
         ASSERT(mFenceEvent != nullptr);
@@ -198,44 +199,35 @@
         return &mPendingCommands;
     }
 
-    Serial Device::GetCompletedCommandSerial() const {
-        return mCompletedSerial;
-    }
-
-    Serial Device::GetLastSubmittedCommandSerial() const {
-        return mLastSubmittedSerial;
-    }
-
-    Serial Device::GetPendingCommandSerial() const {
-        return mLastSubmittedSerial + 1;
-    }
-
     MaybeError Device::TickImpl() {
-        // Perform cleanup operations to free unused objects
-        mCompletedSerial = mFence->GetCompletedValue();
+        CheckPassedSerials();
 
-        mResourceAllocatorManager->Tick(mCompletedSerial);
-        DAWN_TRY(mCommandAllocatorManager->Tick(mCompletedSerial));
-        mViewShaderVisibleDescriptorAllocator->Tick(mCompletedSerial);
-        mSamplerShaderVisibleDescriptorAllocator->Tick(mCompletedSerial);
-        mRenderTargetViewAllocator->Tick(mCompletedSerial);
-        mDepthStencilViewAllocator->Tick(mCompletedSerial);
-        mMapRequestTracker->Tick(mCompletedSerial);
-        mUsedComObjectRefs.ClearUpTo(mCompletedSerial);
+        // Perform cleanup operations to free unused objects
+        Serial completedSerial = GetCompletedCommandSerial();
+
+        mResourceAllocatorManager->Tick(completedSerial);
+        DAWN_TRY(mCommandAllocatorManager->Tick(completedSerial));
+        mViewShaderVisibleDescriptorAllocator->Tick(completedSerial);
+        mSamplerShaderVisibleDescriptorAllocator->Tick(completedSerial);
+        mRenderTargetViewAllocator->Tick(completedSerial);
+        mDepthStencilViewAllocator->Tick(completedSerial);
+        mMapRequestTracker->Tick(completedSerial);
+        mUsedComObjectRefs.ClearUpTo(completedSerial);
         DAWN_TRY(ExecutePendingCommandContext());
         DAWN_TRY(NextSerial());
         return {};
     }
 
     MaybeError Device::NextSerial() {
-        mLastSubmittedSerial++;
-        return CheckHRESULT(mCommandQueue->Signal(mFence.Get(), mLastSubmittedSerial),
+        IncrementLastSubmittedCommandSerial();
+
+        return CheckHRESULT(mCommandQueue->Signal(mFence.Get(), GetLastSubmittedCommandSerial()),
                             "D3D12 command queue signal fence");
     }
 
     MaybeError Device::WaitForSerial(uint64_t serial) {
-        mCompletedSerial = mFence->GetCompletedValue();
-        if (mCompletedSerial < serial) {
+        CheckPassedSerials();
+        if (GetCompletedCommandSerial() < serial) {
             DAWN_TRY(CheckHRESULT(mFence->SetEventOnCompletion(serial, mFenceEvent),
                                   "D3D12 set event on completion"));
             WaitForSingleObject(mFenceEvent, INFINITE);
@@ -243,6 +235,10 @@
         return {};
     }
 
+    Serial Device::CheckAndUpdateCompletedSerials() {
+        return mFence->GetCompletedValue();
+    }
+
     void Device::ReferenceUntilUnused(ComPtr<IUnknown> object) {
         mUsedComObjectRefs.Enqueue(object, GetPendingCommandSerial());
     }
@@ -447,11 +443,13 @@
 
         DAWN_TRY(NextSerial());
         // Wait for all in-flight commands to finish executing
-        DAWN_TRY(WaitForSerial(mLastSubmittedSerial));
+        DAWN_TRY(WaitForSerial(GetLastSubmittedCommandSerial()));
 
         // Call tick one last time so resources are cleaned up.
         DAWN_TRY(TickImpl());
 
+        // Force all operations to look as if they were completed
+        AssumeCommandsComplete();
         return {};
     }
 
@@ -461,16 +459,16 @@
         // Immediately forget about all pending commands
         mPendingCommands.Release();
 
-        // GPU is no longer executing commands. Existing objects do not get freed until the device
-        // is destroyed. To ensure objects are always released, force the completed serial to be
-        // MAX.
-        mCompletedSerial = std::numeric_limits<Serial>::max();
+        // Some operations might have been started since the last submit and waiting
+        // on a serial that doesn't have a corresponding fence enqueued. Force all
+        // operations to look as if they were completed (because they were).
+        AssumeCommandsComplete();
 
         if (mFenceEvent != nullptr) {
             ::CloseHandle(mFenceEvent);
         }
 
-        mUsedComObjectRefs.ClearUpTo(mCompletedSerial);
+        mUsedComObjectRefs.ClearUpTo(GetCompletedCommandSerial());
 
         ASSERT(mUsedComObjectRefs.Empty());
         ASSERT(!mPendingCommands.IsOpen());
diff --git a/src/dawn_native/d3d12/DeviceD3D12.h b/src/dawn_native/d3d12/DeviceD3D12.h
index 569acbf..4505230 100644
--- a/src/dawn_native/d3d12/DeviceD3D12.h
+++ b/src/dawn_native/d3d12/DeviceD3D12.h
@@ -55,8 +55,6 @@
         CommandBufferBase* CreateCommandBuffer(CommandEncoder* encoder,
                                                const CommandBufferDescriptor* descriptor) override;
 
-        Serial GetCompletedCommandSerial() const final override;
-        Serial GetLastSubmittedCommandSerial() const final override;
         MaybeError TickImpl() override;
 
         ID3D12Device* GetD3D12Device() const;
@@ -75,7 +73,6 @@
         ComPtr<IDXGIFactory4> GetFactory() const;
 
         ResultOrError<CommandRecordingContext*> GetPendingCommandContext();
-        Serial GetPendingCommandSerial() const override;
 
         const D3D12DeviceInfo& GetDeviceInfo() const;
 
@@ -156,10 +153,9 @@
         void ShutDownImpl() override;
         MaybeError WaitForIdleForDestruction() override;
 
-        Serial mCompletedSerial = 0;
-        Serial mLastSubmittedSerial = 0;
         ComPtr<ID3D12Fence> mFence;
         HANDLE mFenceEvent = nullptr;
+        Serial CheckAndUpdateCompletedSerials() override;
 
         ComPtr<ID3D12Device> mD3d12Device;  // Device is owned by adapter and will not be outlived.
         ComPtr<ID3D12CommandQueue> mCommandQueue;
diff --git a/src/dawn_native/metal/DeviceMTL.h b/src/dawn_native/metal/DeviceMTL.h
index bc75591..b09926a 100644
--- a/src/dawn_native/metal/DeviceMTL.h
+++ b/src/dawn_native/metal/DeviceMTL.h
@@ -46,15 +46,12 @@
         CommandBufferBase* CreateCommandBuffer(CommandEncoder* encoder,
                                                const CommandBufferDescriptor* descriptor) override;
 
-        Serial GetCompletedCommandSerial() const final override;
-        Serial GetLastSubmittedCommandSerial() const final override;
         MaybeError TickImpl() override;
 
         id<MTLDevice> GetMTLDevice();
         id<MTLCommandQueue> GetMTLQueue();
 
         CommandRecordingContext* GetPendingCommandContext();
-        Serial GetPendingCommandSerial() const override;
         void SubmitPendingCommandBuffer();
 
         MapRequestTracker* GetMapTracker() const;
@@ -103,12 +100,12 @@
         void InitTogglesFromDriver();
         void ShutDownImpl() override;
         MaybeError WaitForIdleForDestruction() override;
+        Serial CheckAndUpdateCompletedSerials() override;
 
         id<MTLDevice> mMtlDevice = nil;
         id<MTLCommandQueue> mCommandQueue = nil;
         std::unique_ptr<MapRequestTracker> mMapTracker;
 
-        Serial mLastSubmittedSerial = 0;
         CommandRecordingContext mCommandContext;
 
         // The completed serial is updated in a Metal completion handler that can be fired on a
diff --git a/src/dawn_native/metal/DeviceMTL.mm b/src/dawn_native/metal/DeviceMTL.mm
index 30521b8..ba24d06 100644
--- a/src/dawn_native/metal/DeviceMTL.mm
+++ b/src/dawn_native/metal/DeviceMTL.mm
@@ -154,31 +154,30 @@
         return new TextureView(texture, descriptor);
     }
 
-    Serial Device::GetCompletedCommandSerial() const {
+    Serial Device::CheckAndUpdateCompletedSerials() {
+        if (GetCompletedCommandSerial() > mCompletedSerial) {
+            // sometimes we artificially increase the serials, in which case the completed serial in
+            // the device base will surpass the completed serial we have in the metal backend, so we
+            // must update ours when we see that the completed serial from the frontend has
+            // increased.
+            mCompletedSerial = GetCompletedCommandSerial();
+        }
         static_assert(std::is_same<Serial, uint64_t>::value, "");
         return mCompletedSerial.load();
     }
 
-    Serial Device::GetLastSubmittedCommandSerial() const {
-        return mLastSubmittedSerial;
-    }
-
-    Serial Device::GetPendingCommandSerial() const {
-        return mLastSubmittedSerial + 1;
-    }
-
     MaybeError Device::TickImpl() {
+        CheckPassedSerials();
         Serial completedSerial = GetCompletedCommandSerial();
 
         mMapTracker->Tick(completedSerial);
 
         if (mCommandContext.GetCommands() != nil) {
             SubmitPendingCommandBuffer();
-        } else if (completedSerial == mLastSubmittedSerial) {
+        } else if (completedSerial == GetLastSubmittedCommandSerial()) {
             // If there's no GPU work in flight we still need to artificially increment the serial
             // so that CPU operations waiting on GPU completion can know they don't have to wait.
-            mCompletedSerial++;
-            mLastSubmittedSerial++;
+            ArtificiallyIncrementSerials();
         }
 
         return {};
@@ -208,7 +207,7 @@
             return;
         }
 
-        mLastSubmittedSerial++;
+        IncrementLastSubmittedCommandSerial();
 
         // Acquire the pending command buffer, which is retained. It must be released later.
         id<MTLCommandBuffer> pendingCommands = mCommandContext.AcquireCommands();
@@ -231,7 +230,8 @@
 
         // Update the completed serial once the completed handler is fired. Make a local copy of
         // mLastSubmittedSerial so it is captured by value.
-        Serial pendingSerial = mLastSubmittedSerial;
+        Serial pendingSerial = GetLastSubmittedCommandSerial();
+        // this ObjC block runs on a different thread
         [pendingCommands addCompletedHandler:^(id<MTLCommandBuffer>) {
             TRACE_EVENT_ASYNC_END0(GetPlatform(), GPUWork, "DeviceMTL::SubmitPendingCommandBuffer",
                                    pendingSerial);
@@ -299,17 +299,22 @@
 
     MaybeError Device::WaitForIdleForDestruction() {
         [mCommandContext.AcquireCommands() release];
+        CheckPassedSerials();
 
         // Wait for all commands to be finished so we can free resources
-        while (GetCompletedCommandSerial() != mLastSubmittedSerial) {
+        while (GetCompletedCommandSerial() != GetLastSubmittedCommandSerial()) {
             usleep(100);
+            CheckPassedSerials();
         }
 
         // Artificially increase the serials so work that was pending knows it can complete.
-        mCompletedSerial++;
-        mLastSubmittedSerial++;
+        ArtificiallyIncrementSerials();
 
         DAWN_TRY(TickImpl());
+
+        // Force all operations to look as if they were completed
+        AssumeCommandsComplete();
+
         return {};
     }
 
diff --git a/src/dawn_native/null/DeviceNull.cpp b/src/dawn_native/null/DeviceNull.cpp
index 1f75cf9..0dac43e 100644
--- a/src/dawn_native/null/DeviceNull.cpp
+++ b/src/dawn_native/null/DeviceNull.cpp
@@ -186,7 +186,7 @@
 
     MaybeError Device::WaitForIdleForDestruction() {
         // Fake all commands being completed
-        mCompletedSerial = mLastSubmittedSerial;
+        AssumeCommandsComplete();
         return {};
     }
 
@@ -221,23 +221,15 @@
         mMemoryUsage -= bytes;
     }
 
-    Serial Device::GetCompletedCommandSerial() const {
-        return mCompletedSerial;
-    }
-
-    Serial Device::GetLastSubmittedCommandSerial() const {
-        return mLastSubmittedSerial;
-    }
-
-    Serial Device::GetPendingCommandSerial() const {
-        return mLastSubmittedSerial + 1;
-    }
-
     MaybeError Device::TickImpl() {
         SubmitPendingOperations();
         return {};
     }
 
+    Serial Device::CheckAndUpdateCompletedSerials() {
+        return GetLastSubmittedCommandSerial();
+    }
+
     void Device::AddPendingOperation(std::unique_ptr<PendingOperation> operation) {
         mPendingOperations.emplace_back(std::move(operation));
     }
@@ -247,8 +239,8 @@
         }
         mPendingOperations.clear();
 
-        mCompletedSerial = mLastSubmittedSerial;
-        mLastSubmittedSerial++;
+        CheckPassedSerials();
+        IncrementLastSubmittedCommandSerial();
     }
 
     // BindGroupDataHolder
diff --git a/src/dawn_native/null/DeviceNull.h b/src/dawn_native/null/DeviceNull.h
index fc67316..a78c8a3 100644
--- a/src/dawn_native/null/DeviceNull.h
+++ b/src/dawn_native/null/DeviceNull.h
@@ -91,9 +91,6 @@
         CommandBufferBase* CreateCommandBuffer(CommandEncoder* encoder,
                                                const CommandBufferDescriptor* descriptor) override;
 
-        Serial GetCompletedCommandSerial() const final override;
-        Serial GetLastSubmittedCommandSerial() const final override;
-        Serial GetPendingCommandSerial() const override;
         MaybeError TickImpl() override;
 
         void AddPendingOperation(std::unique_ptr<PendingOperation> operation);
@@ -138,11 +135,11 @@
             TextureBase* texture,
             const TextureViewDescriptor* descriptor) override;
 
+        Serial CheckAndUpdateCompletedSerials() override;
+
         void ShutDownImpl() override;
         MaybeError WaitForIdleForDestruction() override;
 
-        Serial mCompletedSerial = 0;
-        Serial mLastSubmittedSerial = 0;
         std::vector<std::unique_ptr<PendingOperation>> mPendingOperations;
 
         static constexpr size_t kMaxMemoryUsage = 256 * 1024 * 1024;
diff --git a/src/dawn_native/opengl/DeviceGL.cpp b/src/dawn_native/opengl/DeviceGL.cpp
index 47d2eec..4508176 100644
--- a/src/dawn_native/opengl/DeviceGL.cpp
+++ b/src/dawn_native/opengl/DeviceGL.cpp
@@ -147,46 +147,37 @@
 
     void Device::SubmitFenceSync() {
         GLsync sync = gl.FenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
-        mLastSubmittedSerial++;
-        mFencesInFlight.emplace(sync, mLastSubmittedSerial);
-    }
-
-    Serial Device::GetCompletedCommandSerial() const {
-        return mCompletedSerial;
-    }
-
-    Serial Device::GetLastSubmittedCommandSerial() const {
-        return mLastSubmittedSerial;
-    }
-
-    Serial Device::GetPendingCommandSerial() const {
-        return mLastSubmittedSerial + 1;
+        IncrementLastSubmittedCommandSerial();
+        mFencesInFlight.emplace(sync, GetLastSubmittedCommandSerial());
     }
 
     MaybeError Device::TickImpl() {
-        CheckPassedFences();
+        CheckPassedSerials();
         return {};
     }
 
-    void Device::CheckPassedFences() {
+    Serial Device::CheckAndUpdateCompletedSerials() {
+        Serial fenceSerial = 0;
         while (!mFencesInFlight.empty()) {
             GLsync sync = mFencesInFlight.front().first;
-            Serial fenceSerial = mFencesInFlight.front().second;
+            Serial tentativeSerial = mFencesInFlight.front().second;
 
             // Fence are added in order, so we can stop searching as soon
             // as we see one that's not ready.
             GLenum result = gl.ClientWaitSync(sync, GL_SYNC_FLUSH_COMMANDS_BIT, 0);
             if (result == GL_TIMEOUT_EXPIRED) {
-                continue;
+                return fenceSerial;
             }
+            // Update fenceSerial since fence is ready.
+            fenceSerial = tentativeSerial;
 
             gl.DeleteSync(sync);
 
             mFencesInFlight.pop();
 
-            ASSERT(fenceSerial > mCompletedSerial);
-            mCompletedSerial = fenceSerial;
+            ASSERT(fenceSerial > GetCompletedCommandSerial());
         }
+        return fenceSerial;
     }
 
     ResultOrError<std::unique_ptr<StagingBufferBase>> Device::CreateStagingBuffer(size_t size) {
@@ -207,14 +198,17 @@
         // Some operations might have been started since the last submit and waiting
         // on a serial that doesn't have a corresponding fence enqueued. Force all
         // operations to look as if they were completed (because they were).
-        mCompletedSerial = mLastSubmittedSerial + 1;
+        AssumeCommandsComplete();
     }
 
     MaybeError Device::WaitForIdleForDestruction() {
         gl.Finish();
-        CheckPassedFences();
+        CheckPassedSerials();
         ASSERT(mFencesInFlight.empty());
         Tick();
+
+        // Force all operations to look as if they were completed
+        AssumeCommandsComplete();
         return {};
     }
 
diff --git a/src/dawn_native/opengl/DeviceGL.h b/src/dawn_native/opengl/DeviceGL.h
index a768c89..c38fcf6 100644
--- a/src/dawn_native/opengl/DeviceGL.h
+++ b/src/dawn_native/opengl/DeviceGL.h
@@ -52,9 +52,6 @@
         CommandBufferBase* CreateCommandBuffer(CommandEncoder* encoder,
                                                const CommandBufferDescriptor* descriptor) override;
 
-        Serial GetCompletedCommandSerial() const final override;
-        Serial GetLastSubmittedCommandSerial() const final override;
-        Serial GetPendingCommandSerial() const override;
         MaybeError TickImpl() override;
 
         ResultOrError<std::unique_ptr<StagingBufferBase>> CreateStagingBuffer(size_t size) override;
@@ -96,12 +93,10 @@
             const TextureViewDescriptor* descriptor) override;
 
         void InitTogglesFromDriver();
-        void CheckPassedFences();
+        Serial CheckAndUpdateCompletedSerials() override;
         void ShutDownImpl() override;
         MaybeError WaitForIdleForDestruction() override;
 
-        Serial mCompletedSerial = 0;
-        Serial mLastSubmittedSerial = 0;
         std::queue<std::pair<GLsync, Serial>> mFencesInFlight;
 
         GLFormatTable mFormatTable;
diff --git a/src/dawn_native/vulkan/DeviceVk.cpp b/src/dawn_native/vulkan/DeviceVk.cpp
index 4d2040a..a2ba72a 100644
--- a/src/dawn_native/vulkan/DeviceVk.cpp
+++ b/src/dawn_native/vulkan/DeviceVk.cpp
@@ -155,39 +155,28 @@
         return TextureView::Create(texture, descriptor);
     }
 
-    Serial Device::GetCompletedCommandSerial() const {
-        return mCompletedSerial;
-    }
-
-    Serial Device::GetLastSubmittedCommandSerial() const {
-        return mLastSubmittedSerial;
-    }
-
-    Serial Device::GetPendingCommandSerial() const {
-        return mLastSubmittedSerial + 1;
-    }
-
     MaybeError Device::TickImpl() {
-        CheckPassedFences();
+        CheckPassedSerials();
         RecycleCompletedCommands();
 
-        for (Ref<BindGroupLayout>& bgl :
-             mBindGroupLayoutsPendingDeallocation.IterateUpTo(mCompletedSerial)) {
-            bgl->FinishDeallocation(mCompletedSerial);
-        }
-        mBindGroupLayoutsPendingDeallocation.ClearUpTo(mCompletedSerial);
+        Serial completedSerial = GetCompletedCommandSerial();
 
-        mMapRequestTracker->Tick(mCompletedSerial);
-        mResourceMemoryAllocator->Tick(mCompletedSerial);
-        mDeleter->Tick(mCompletedSerial);
+        for (Ref<BindGroupLayout>& bgl :
+             mBindGroupLayoutsPendingDeallocation.IterateUpTo(completedSerial)) {
+            bgl->FinishDeallocation(completedSerial);
+        }
+        mBindGroupLayoutsPendingDeallocation.ClearUpTo(completedSerial);
+
+        mMapRequestTracker->Tick(completedSerial);
+        mResourceMemoryAllocator->Tick(completedSerial);
+        mDeleter->Tick(completedSerial);
 
         if (mRecordingContext.used) {
             DAWN_TRY(SubmitPendingCommands());
-        } else if (mCompletedSerial == mLastSubmittedSerial) {
+        } else if (completedSerial == GetLastSubmittedCommandSerial()) {
             // If there's no GPU work in flight we still need to artificially increment the serial
             // so that CPU operations waiting on GPU completion can know they don't have to wait.
-            mCompletedSerial++;
-            mLastSubmittedSerial++;
+            ArtificiallyIncrementSerials();
         }
 
         return {};
@@ -271,12 +260,13 @@
             mDeleter->DeleteWhenUnused(semaphore);
         }
 
-        mLastSubmittedSerial++;
-        mFencesInFlight.emplace(fence, mLastSubmittedSerial);
+        IncrementLastSubmittedCommandSerial();
+        Serial lastSubmittedSerial = GetLastSubmittedCommandSerial();
+        mFencesInFlight.emplace(fence, lastSubmittedSerial);
 
         CommandPoolAndBuffer submittedCommands = {mRecordingContext.commandPool,
                                                   mRecordingContext.commandBuffer};
-        mCommandsInFlight.Enqueue(submittedCommands, mLastSubmittedSerial);
+        mCommandsInFlight.Enqueue(submittedCommands, lastSubmittedSerial);
         mRecordingContext = CommandRecordingContext();
         DAWN_TRY(PrepareRecordingContext());
 
@@ -468,11 +458,11 @@
         return fence;
     }
 
-    void Device::CheckPassedFences() {
+    Serial Device::CheckAndUpdateCompletedSerials() {
+        Serial fenceSerial = 0;
         while (!mFencesInFlight.empty()) {
             VkFence fence = mFencesInFlight.front().first;
-            Serial fenceSerial = mFencesInFlight.front().second;
-
+            Serial tentativeSerial = mFencesInFlight.front().second;
             VkResult result = VkResult::WrapUnsafe(
                 INJECT_ERROR_OR_RUN(fn.GetFenceStatus(mVkDevice, fence), VK_ERROR_DEVICE_LOST));
             // TODO: Handle DeviceLost error.
@@ -481,15 +471,17 @@
             // Fence are added in order, so we can stop searching as soon
             // as we see one that's not ready.
             if (result == VK_NOT_READY) {
-                return;
+                return fenceSerial;
             }
+            // Update fenceSerial since fence is ready.
+            fenceSerial = tentativeSerial;
 
             mUnusedFences.push_back(fence);
-            mFencesInFlight.pop();
 
-            ASSERT(fenceSerial > mCompletedSerial);
-            mCompletedSerial = fenceSerial;
+            ASSERT(fenceSerial > GetCompletedCommandSerial());
+            mFencesInFlight.pop();
         }
+        return fenceSerial;
     }
 
     MaybeError Device::PrepareRecordingContext() {
@@ -542,10 +534,10 @@
     }
 
     void Device::RecycleCompletedCommands() {
-        for (auto& commands : mCommandsInFlight.IterateUpTo(mCompletedSerial)) {
+        for (auto& commands : mCommandsInFlight.IterateUpTo(GetCompletedCommandSerial())) {
             mUnusedCommands.push_back(commands);
         }
-        mCommandsInFlight.ClearUpTo(mCompletedSerial);
+        mCommandsInFlight.ClearUpTo(GetCompletedCommandSerial());
     }
 
     ResultOrError<std::unique_ptr<StagingBufferBase>> Device::CreateStagingBuffer(size_t size) {
@@ -726,13 +718,11 @@
         // (so they are as good as waited on) or success.
         DAWN_UNUSED(waitIdleResult);
 
-        CheckPassedFences();
-
         // Make sure all fences are complete by explicitly waiting on them all
         while (!mFencesInFlight.empty()) {
             VkFence fence = mFencesInFlight.front().first;
             Serial fenceSerial = mFencesInFlight.front().second;
-            ASSERT(fenceSerial > mCompletedSerial);
+            ASSERT(fenceSerial > GetCompletedCommandSerial());
 
             VkResult result = VkResult::WrapUnsafe(VK_TIMEOUT);
             do {
@@ -746,8 +736,10 @@
             fn.DestroyFence(mVkDevice, fence, nullptr);
 
             mFencesInFlight.pop();
-            mCompletedSerial = fenceSerial;
         }
+
+        // Force all operations to look as if they were completed
+        AssumeCommandsComplete();
         return {};
     }
 
@@ -790,7 +782,7 @@
         // Some operations might have been started since the last submit and waiting
         // on a serial that doesn't have a corresponding fence enqueued. Force all
         // operations to look as if they were completed (because they were).
-        mCompletedSerial = mLastSubmittedSerial + 1;
+        AssumeCommandsComplete();
 
         // Assert that errors are device loss so that we can continue with destruction
         AssertAndIgnoreDeviceLossError(TickImpl());
@@ -808,7 +800,7 @@
 
         // Releasing the uploader enqueues buffers to be released.
         // Call Tick() again to clear them before releasing the deleter.
-        mDeleter->Tick(mCompletedSerial);
+        mDeleter->Tick(GetCompletedCommandSerial());
 
         mMapRequestTracker = nullptr;
 
diff --git a/src/dawn_native/vulkan/DeviceVk.h b/src/dawn_native/vulkan/DeviceVk.h
index cd08aed..585d7f0 100644
--- a/src/dawn_native/vulkan/DeviceVk.h
+++ b/src/dawn_native/vulkan/DeviceVk.h
@@ -63,7 +63,6 @@
         RenderPassCache* GetRenderPassCache() const;
 
         CommandRecordingContext* GetPendingRecordingContext();
-        Serial GetPendingCommandSerial() const override;
         MaybeError SubmitPendingCommands();
 
         void EnqueueDeferredDeallocation(BindGroupLayout* bindGroupLayout);
@@ -82,8 +81,6 @@
         CommandBufferBase* CreateCommandBuffer(CommandEncoder* encoder,
                                                const CommandBufferDescriptor* descriptor) override;
 
-        Serial GetCompletedCommandSerial() const final override;
-        Serial GetLastSubmittedCommandSerial() const final override;
         MaybeError TickImpl() override;
 
         ResultOrError<std::unique_ptr<StagingBufferBase>> CreateStagingBuffer(size_t size) override;
@@ -158,7 +155,7 @@
         std::unique_ptr<external_semaphore::Service> mExternalSemaphoreService;
 
         ResultOrError<VkFence> GetUnusedFence();
-        void CheckPassedFences();
+        Serial CheckAndUpdateCompletedSerials() override;
 
         // We track which operations are in flight on the GPU with an increasing serial.
         // This works only because we have a single queue. Each submit to a queue is associated
@@ -167,8 +164,6 @@
         std::queue<std::pair<VkFence, Serial>> mFencesInFlight;
         // Fences in the unused list aren't reset yet.
         std::vector<VkFence> mUnusedFences;
-        Serial mCompletedSerial = 0;
-        Serial mLastSubmittedSerial = 0;
 
         MaybeError PrepareRecordingContext();
         void RecycleCompletedCommands();