Let DeviceBase know about Completed and LastSubmitted command Serials

This is needed to implement the timeline fence signal tracker in the frontend

Bug: dawn:26
Change-Id: Id6eb2afb81385de5093b57c5cb23ace93c8aab1b
Reviewed-on: https://dawn-review.googlesource.com/c/2741
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Commit-Queue: Austin Eng <enga@chromium.org>
diff --git a/src/dawn_native/Device.h b/src/dawn_native/Device.h
index 59c0272..16b15d4 100644
--- a/src/dawn_native/Device.h
+++ b/src/dawn_native/Device.h
@@ -15,6 +15,7 @@
 #ifndef DAWNNATIVE_DEVICEBASE_H_
 #define DAWNNATIVE_DEVICEBASE_H_
 
+#include "common/Serial.h"
 #include "dawn_native/Error.h"
 #include "dawn_native/Forward.h"
 #include "dawn_native/ObjectBase.h"
@@ -58,6 +59,8 @@
         virtual RenderPipelineBase* CreateRenderPipeline(RenderPipelineBuilder* builder) = 0;
         virtual SwapChainBase* CreateSwapChain(SwapChainBuilder* builder) = 0;
 
+        virtual Serial GetCompletedCommandSerial() const = 0;
+        virtual Serial GetLastSubmittedCommandSerial() const = 0;
         virtual void TickImpl() = 0;
 
         // Many Dawn objects are completely immutable once created which means that if two
@@ -99,6 +102,7 @@
                                            const TextureViewDescriptor* descriptor);
 
         void Tick();
+
         void SetErrorCallback(dawn::DeviceErrorCallback callback, dawn::CallbackUserdata userdata);
         void Reference();
         void Release();
diff --git a/src/dawn_native/d3d12/BufferD3D12.cpp b/src/dawn_native/d3d12/BufferD3D12.cpp
index 71b6b40..7483e3c 100644
--- a/src/dawn_native/d3d12/BufferD3D12.cpp
+++ b/src/dawn_native/d3d12/BufferD3D12.cpp
@@ -237,7 +237,7 @@
         request.data = data;
         request.isWrite = isWrite;
 
-        mInflightRequests.Enqueue(std::move(request), mDevice->GetSerial());
+        mInflightRequests.Enqueue(std::move(request), mDevice->GetPendingCommandSerial());
     }
 
     void MapRequestTracker::Tick(Serial finishedSerial) {
diff --git a/src/dawn_native/d3d12/CommandAllocatorManager.cpp b/src/dawn_native/d3d12/CommandAllocatorManager.cpp
index 47072fc..90f7a5a 100644
--- a/src/dawn_native/d3d12/CommandAllocatorManager.cpp
+++ b/src/dawn_native/d3d12/CommandAllocatorManager.cpp
@@ -52,7 +52,7 @@
         // Enqueue the command allocator. It will be scheduled for reset after the next
         // ExecuteCommandLists
         mInFlightCommandAllocators.Enqueue({mCommandAllocators[firstFreeIndex], firstFreeIndex},
-                                           device->GetSerial());
+                                           device->GetPendingCommandSerial());
 
         return mCommandAllocators[firstFreeIndex];
     }
diff --git a/src/dawn_native/d3d12/CommandBufferD3D12.cpp b/src/dawn_native/d3d12/CommandBufferD3D12.cpp
index d2aff42..8b90046 100644
--- a/src/dawn_native/d3d12/CommandBufferD3D12.cpp
+++ b/src/dawn_native/d3d12/CommandBufferD3D12.cpp
@@ -71,7 +71,7 @@
 
                 // Descriptors don't need to be recorded if they have already been recorded in
                 // the heap. Indices are only updated when descriptors are recorded
-                const uint64_t serial = device->GetSerial();
+                const uint64_t serial = device->GetPendingCommandSerial();
                 if (group->GetHeapSerial() != serial ||
                     group->GetIndexInSubmit() != indexInSubmit) {
                     group->RecordDescriptors(cbvSrvUavCPUDescriptorHeap, &cbvSrvUavDescriptorIndex,
diff --git a/src/dawn_native/d3d12/DescriptorHeapAllocator.cpp b/src/dawn_native/d3d12/DescriptorHeapAllocator.cpp
index 9374dae..75d188d 100644
--- a/src/dawn_native/d3d12/DescriptorHeapAllocator.cpp
+++ b/src/dawn_native/d3d12/DescriptorHeapAllocator.cpp
@@ -127,6 +127,6 @@
     }
 
     void DescriptorHeapAllocator::Release(DescriptorHeapHandle handle) {
-        mReleasedHandles.Enqueue(handle, mDevice->GetSerial());
+        mReleasedHandles.Enqueue(handle, mDevice->GetPendingCommandSerial());
     }
 }}  // namespace dawn_native::d3d12
diff --git a/src/dawn_native/d3d12/DeviceD3D12.cpp b/src/dawn_native/d3d12/DeviceD3D12.cpp
index 1d1f741..6d47920 100644
--- a/src/dawn_native/d3d12/DeviceD3D12.cpp
+++ b/src/dawn_native/d3d12/DeviceD3D12.cpp
@@ -144,8 +144,8 @@
         queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
         ASSERT_SUCCESS(mD3d12Device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&mCommandQueue)));
 
-        ASSERT_SUCCESS(
-            mD3d12Device->CreateFence(mSerial, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&mFence)));
+        ASSERT_SUCCESS(mD3d12Device->CreateFence(mLastSubmittedSerial, D3D12_FENCE_FLAG_NONE,
+                                                 IID_PPV_ARGS(&mFence)));
         mFenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
         ASSERT(mFenceEvent != nullptr);
 
@@ -160,9 +160,8 @@
     }
 
     Device::~Device() {
-        const uint64_t currentSerial = GetSerial();
         NextSerial();
-        WaitForSerial(currentSerial);  // Wait for all in-flight commands to finish executing
+        WaitForSerial(mLastSubmittedSerial);  // Wait for all in-flight commands to finish executing
         TickImpl();                    // Call tick one last time so resources are cleaned up
 
         ASSERT(mUsedComObjectRefs.Empty());
@@ -224,14 +223,26 @@
         return mPendingCommands.commandList;
     }
 
+    Serial Device::GetCompletedCommandSerial() const {
+        return mCompletedSerial;
+    }
+
+    Serial Device::GetLastSubmittedCommandSerial() const {
+        return mLastSubmittedSerial;
+    }
+
+    Serial Device::GetPendingCommandSerial() const {
+        return mLastSubmittedSerial + 1;
+    }
+
     void Device::TickImpl() {
         // Perform cleanup operations to free unused objects
-        const uint64_t lastCompletedSerial = mFence->GetCompletedValue();
-        mResourceAllocator->Tick(lastCompletedSerial);
-        mCommandAllocatorManager->Tick(lastCompletedSerial);
-        mDescriptorHeapAllocator->Tick(lastCompletedSerial);
-        mMapRequestTracker->Tick(lastCompletedSerial);
-        mUsedComObjectRefs.ClearUpTo(lastCompletedSerial);
+        mCompletedSerial = mFence->GetCompletedValue();
+        mResourceAllocator->Tick(mCompletedSerial);
+        mCommandAllocatorManager->Tick(mCompletedSerial);
+        mDescriptorHeapAllocator->Tick(mCompletedSerial);
+        mMapRequestTracker->Tick(mCompletedSerial);
+        mUsedComObjectRefs.ClearUpTo(mCompletedSerial);
         ExecuteCommandLists({});
         NextSerial();
     }
@@ -240,24 +251,21 @@
         return mPCIInfo;
     }
 
-    uint64_t Device::GetSerial() const {
-        return mSerial;
-    }
-
     void Device::NextSerial() {
-        ASSERT_SUCCESS(mCommandQueue->Signal(mFence.Get(), mSerial++));
+        mLastSubmittedSerial++;
+        ASSERT_SUCCESS(mCommandQueue->Signal(mFence.Get(), mLastSubmittedSerial));
     }
 
     void Device::WaitForSerial(uint64_t serial) {
-        const uint64_t lastCompletedSerial = mFence->GetCompletedValue();
-        if (lastCompletedSerial < serial) {
+        mCompletedSerial = mFence->GetCompletedValue();
+        if (mCompletedSerial < serial) {
             ASSERT_SUCCESS(mFence->SetEventOnCompletion(serial, mFenceEvent));
             WaitForSingleObject(mFenceEvent, INFINITE);
         }
     }
 
     void Device::ReferenceUntilUnused(ComPtr<IUnknown> object) {
-        mUsedComObjectRefs.Enqueue(object, mSerial);
+        mUsedComObjectRefs.Enqueue(object, GetPendingCommandSerial());
     }
 
     void Device::ExecuteCommandLists(std::initializer_list<ID3D12CommandList*> commandLists) {
diff --git a/src/dawn_native/d3d12/DeviceD3D12.h b/src/dawn_native/d3d12/DeviceD3D12.h
index 3717e24..dfed566 100644
--- a/src/dawn_native/d3d12/DeviceD3D12.h
+++ b/src/dawn_native/d3d12/DeviceD3D12.h
@@ -52,6 +52,8 @@
         RenderPipelineBase* CreateRenderPipeline(RenderPipelineBuilder* builder) override;
         SwapChainBase* CreateSwapChain(SwapChainBuilder* builder) override;
 
+        Serial GetCompletedCommandSerial() const final override;
+        Serial GetLastSubmittedCommandSerial() const final override;
         void TickImpl() override;
 
         const dawn_native::PCIInfo& GetPCIInfo() const override;
@@ -68,10 +70,10 @@
 
         void OpenCommandList(ComPtr<ID3D12GraphicsCommandList>* commandList);
         ComPtr<ID3D12GraphicsCommandList> GetPendingCommandList();
+        Serial GetPendingCommandSerial() const;
 
-        uint64_t GetSerial() const;
         void NextSerial();
-        void WaitForSerial(uint64_t serial);
+        void WaitForSerial(Serial serial);
 
         void ReferenceUntilUnused(ComPtr<IUnknown> object);
 
@@ -99,7 +101,8 @@
         // D3D12 DLLs are unloaded before we are done using it.
         std::unique_ptr<PlatformFunctions> mFunctions;
 
-        uint64_t mSerial = 0;
+        Serial mCompletedSerial = 0;
+        Serial mLastSubmittedSerial = 0;
         ComPtr<ID3D12Fence> mFence;
         HANDLE mFenceEvent;
 
diff --git a/src/dawn_native/d3d12/NativeSwapChainImplD3D12.cpp b/src/dawn_native/d3d12/NativeSwapChainImplD3D12.cpp
index 99ac8f8..577d8fa 100644
--- a/src/dawn_native/d3d12/NativeSwapChainImplD3D12.cpp
+++ b/src/dawn_native/d3d12/NativeSwapChainImplD3D12.cpp
@@ -107,7 +107,7 @@
         // TODO(cwallez@chromium.org): Make the serial ticking implicit.
         mDevice->NextSerial();
 
-        mBufferSerials[mCurrentBuffer] = mDevice->GetSerial();
+        mBufferSerials[mCurrentBuffer] = mDevice->GetPendingCommandSerial();
         return DAWN_SWAP_CHAIN_NO_ERROR;
     }
 
diff --git a/src/dawn_native/d3d12/ResourceAllocator.cpp b/src/dawn_native/d3d12/ResourceAllocator.cpp
index c50de5b..e2822e6 100644
--- a/src/dawn_native/d3d12/ResourceAllocator.cpp
+++ b/src/dawn_native/d3d12/ResourceAllocator.cpp
@@ -67,7 +67,7 @@
     void ResourceAllocator::Release(ComPtr<ID3D12Resource> resource) {
         // Resources may still be in use on the GPU. Enqueue them so that we hold onto them until
         // GPU execution has completed
-        mReleasedResources.Enqueue(resource, mDevice->GetSerial());
+        mReleasedResources.Enqueue(resource, mDevice->GetPendingCommandSerial());
     }
 
     void ResourceAllocator::Tick(uint64_t lastCompletedSerial) {
diff --git a/src/dawn_native/metal/DeviceMTL.h b/src/dawn_native/metal/DeviceMTL.h
index da586ee..5833588 100644
--- a/src/dawn_native/metal/DeviceMTL.h
+++ b/src/dawn_native/metal/DeviceMTL.h
@@ -48,6 +48,8 @@
         RenderPipelineBase* CreateRenderPipeline(RenderPipelineBuilder* builder) override;
         SwapChainBase* CreateSwapChain(SwapChainBuilder* builder) override;
 
+        Serial GetCompletedCommandSerial() const final override;
+        Serial GetLastSubmittedCommandSerial() const final override;
         void TickImpl() override;
 
         const dawn_native::PCIInfo& GetPCIInfo() const override;
@@ -55,8 +57,8 @@
         id<MTLDevice> GetMTLDevice();
 
         id<MTLCommandBuffer> GetPendingCommandBuffer();
+        Serial GetPendingCommandSerial() const;
         void SubmitPendingCommandBuffer();
-        Serial GetPendingCommandSerial();
 
         MapRequestTracker* GetMapTracker() const;
         ResourceUploader* GetResourceUploader() const;
@@ -86,8 +88,8 @@
         std::unique_ptr<MapRequestTracker> mMapTracker;
         std::unique_ptr<ResourceUploader> mResourceUploader;
 
-        Serial mFinishedCommandSerial = 0;
-        Serial mPendingCommandSerial = 1;
+        Serial mCompletedSerial = 0;
+        Serial mLastSubmittedSerial = 0;
         id<MTLCommandBuffer> mPendingCommands = nil;
 
         dawn_native::PCIInfo mPCIInfo;
diff --git a/src/dawn_native/metal/DeviceMTL.mm b/src/dawn_native/metal/DeviceMTL.mm
index a01ab6a..0484e7a 100644
--- a/src/dawn_native/metal/DeviceMTL.mm
+++ b/src/dawn_native/metal/DeviceMTL.mm
@@ -144,7 +144,7 @@
         // store the pendingSerial before SubmitPendingCommandBuffer then wait for it to be passed.
         // Instead we submit and wait for the serial before the next pendingCommandSerial.
         SubmitPendingCommandBuffer();
-        while (mFinishedCommandSerial != mPendingCommandSerial - 1) {
+        while (mCompletedSerial != mLastSubmittedSerial) {
             usleep(100);
         }
         Tick();
@@ -224,13 +224,30 @@
         return new TextureView(texture, descriptor);
     }
 
-    void Device::TickImpl() {
-        mResourceUploader->Tick(mFinishedCommandSerial);
-        mMapTracker->Tick(mFinishedCommandSerial);
+    Serial Device::GetCompletedCommandSerial() const {
+        return mCompletedSerial;
+    }
 
-        // Code above might have added GPU work, submit it. This also makes sure
-        // that even when no GPU work is happening, the serial number keeps incrementing.
-        SubmitPendingCommandBuffer();
+    Serial Device::GetLastSubmittedCommandSerial() const {
+        return mLastSubmittedSerial;
+    }
+
+    Serial Device::GetPendingCommandSerial() const {
+        return mLastSubmittedSerial + 1;
+    }
+
+    void Device::TickImpl() {
+        mResourceUploader->Tick(mCompletedSerial);
+        mMapTracker->Tick(mCompletedSerial);
+
+        if (mPendingCommands != nil) {
+            SubmitPendingCommandBuffer();
+        } else if (mCompletedSerial == mLastSubmittedSerial) {
+            // 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++;
+        }
     }
 
     const dawn_native::PCIInfo& Device::GetPCIInfo() const {
@@ -258,24 +275,15 @@
         // so this-> works as expected. However it is unclear how members are captured, (are they
         // captured using this-> or by value?) so we make a copy of the pendingCommandSerial on the
         // stack.
-        Serial pendingSerial = mPendingCommandSerial;
+        mLastSubmittedSerial++;
+        Serial pendingSerial = mLastSubmittedSerial;
         [mPendingCommands addCompletedHandler:^(id<MTLCommandBuffer>) {
-            this->mFinishedCommandSerial = pendingSerial;
+            this->mCompletedSerial = pendingSerial;
         }];
 
         [mPendingCommands commit];
         [mPendingCommands release];
         mPendingCommands = nil;
-        mPendingCommandSerial++;
-    }
-
-    uint64_t Device::GetPendingCommandSerial() {
-        // If this is called, then it means some piece of code somewhere will wait for this serial
-        // to complete. Make sure the pending command buffer is created so that it is on the worst
-        // case enqueued on the next Tick() and eventually increments the serial. Otherwise if no
-        // GPU work happens we could be waiting for this serial forever.
-        GetPendingCommandBuffer();
-        return mPendingCommandSerial;
     }
 
     MapRequestTracker* Device::GetMapTracker() const {
diff --git a/src/dawn_native/null/NullBackend.cpp b/src/dawn_native/null/NullBackend.cpp
index 86fbca7..9512f0f 100644
--- a/src/dawn_native/null/NullBackend.cpp
+++ b/src/dawn_native/null/NullBackend.cpp
@@ -109,14 +109,29 @@
         return mPCIInfo;
     }
 
+    Serial Device::GetCompletedCommandSerial() const {
+        return mCompletedSerial;
+    }
+
+    Serial Device::GetLastSubmittedCommandSerial() const {
+        return mLastSubmittedSerial;
+    }
+
     void Device::TickImpl() {
+        SubmitPendingOperations();
     }
 
     void Device::AddPendingOperation(std::unique_ptr<PendingOperation> operation) {
         mPendingOperations.emplace_back(std::move(operation));
     }
-    std::vector<std::unique_ptr<PendingOperation>> Device::AcquirePendingOperations() {
-        return std::move(mPendingOperations);
+    void Device::SubmitPendingOperations() {
+        for (auto& operation : mPendingOperations) {
+            operation->Execute();
+        }
+        mPendingOperations.clear();
+
+        mCompletedSerial = mLastSubmittedSerial;
+        mLastSubmittedSerial++;
     }
 
     // Buffer
@@ -200,13 +215,7 @@
     }
 
     void Queue::SubmitImpl(uint32_t, CommandBufferBase* const*) {
-        auto operations = ToBackend(GetDevice())->AcquirePendingOperations();
-
-        for (auto& operation : operations) {
-            operation->Execute();
-        }
-
-        operations.clear();
+        ToBackend(GetDevice())->SubmitPendingOperations();
     }
 
     // SwapChain
diff --git a/src/dawn_native/null/NullBackend.h b/src/dawn_native/null/NullBackend.h
index 0c50ae9..b77f6cf 100644
--- a/src/dawn_native/null/NullBackend.h
+++ b/src/dawn_native/null/NullBackend.h
@@ -106,12 +106,14 @@
         RenderPipelineBase* CreateRenderPipeline(RenderPipelineBuilder* builder) override;
         SwapChainBase* CreateSwapChain(SwapChainBuilder* builder) override;
 
+        Serial GetCompletedCommandSerial() const final override;
+        Serial GetLastSubmittedCommandSerial() const final override;
         void TickImpl() override;
 
         const dawn_native::PCIInfo& GetPCIInfo() const override;
 
         void AddPendingOperation(std::unique_ptr<PendingOperation> operation);
-        std::vector<std::unique_ptr<PendingOperation>> AcquirePendingOperations();
+        void SubmitPendingOperations();
 
       private:
         ResultOrError<BindGroupLayoutBase*> CreateBindGroupLayoutImpl(
@@ -131,6 +133,8 @@
             const TextureViewDescriptor* descriptor) override;
         void InitFakePCIInfo();
 
+        Serial mCompletedSerial = 0;
+        Serial mLastSubmittedSerial = 0;
         std::vector<std::unique_ptr<PendingOperation>> mPendingOperations;
         dawn_native::PCIInfo mPCIInfo;
     };
diff --git a/src/dawn_native/opengl/DeviceGL.cpp b/src/dawn_native/opengl/DeviceGL.cpp
index f2dc8bc..e58fb02 100644
--- a/src/dawn_native/opengl/DeviceGL.cpp
+++ b/src/dawn_native/opengl/DeviceGL.cpp
@@ -50,6 +50,17 @@
         CollectPCIInfo();
     }
 
+    Device::~Device() {
+        CheckPassedFences();
+        ASSERT(mFencesInFlight.empty());
+
+        // 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;
+        Tick();
+    }
+
     BindGroupBase* Device::CreateBindGroup(BindGroupBuilder* builder) {
         return new BindGroup(builder);
     }
@@ -112,7 +123,47 @@
         return new TextureView(texture, descriptor);
     }
 
+    void Device::SubmitFenceSync() {
+        GLsync sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
+        mLastSubmittedSerial++;
+        mFencesInFlight.emplace(sync, mLastSubmittedSerial);
+    }
+
+    Serial Device::GetCompletedCommandSerial() const {
+        return mCompletedSerial;
+    }
+
+    Serial Device::GetLastSubmittedCommandSerial() const {
+        return mLastSubmittedSerial;
+    }
+
     void Device::TickImpl() {
+        CheckPassedFences();
+    }
+
+    void Device::CheckPassedFences() {
+        while (!mFencesInFlight.empty()) {
+            GLsync sync = mFencesInFlight.front().first;
+            Serial fenceSerial = mFencesInFlight.front().second;
+
+            GLint status = 0;
+            GLsizei length;
+            glGetSynciv(sync, GL_SYNC_CONDITION, sizeof(GLint), &length, &status);
+            ASSERT(length == 1);
+
+            // Fence are added in order, so we can stop searching as soon
+            // as we see one that's not ready.
+            if (!status) {
+                return;
+            }
+
+            glDeleteSync(sync);
+
+            mFencesInFlight.pop();
+
+            ASSERT(fenceSerial > mCompletedSerial);
+            mCompletedSerial = fenceSerial;
+        }
     }
 
     const dawn_native::PCIInfo& Device::GetPCIInfo() const {
diff --git a/src/dawn_native/opengl/DeviceGL.h b/src/dawn_native/opengl/DeviceGL.h
index 2b39db5..90b14ac 100644
--- a/src/dawn_native/opengl/DeviceGL.h
+++ b/src/dawn_native/opengl/DeviceGL.h
@@ -23,6 +23,8 @@
 
 #include "glad/glad.h"
 
+#include <queue>
+
 // Remove windows.h macros after glad's include of windows.h
 #if defined(DAWN_PLATFORM_WINDOWS)
 #    include "common/windows_with_undefs.h"
@@ -33,6 +35,11 @@
     class Device : public DeviceBase {
       public:
         Device();
+        ~Device();
+
+        void SubmitFenceSync();
+
+        // Dawn API
         BindGroupBase* CreateBindGroup(BindGroupBuilder* builder) override;
         BlendStateBase* CreateBlendState(BlendStateBuilder* builder) override;
         BufferViewBase* CreateBufferView(BufferViewBuilder* builder) override;
@@ -44,6 +51,8 @@
         RenderPipelineBase* CreateRenderPipeline(RenderPipelineBuilder* builder) override;
         SwapChainBase* CreateSwapChain(SwapChainBuilder* builder) override;
 
+        Serial GetCompletedCommandSerial() const final override;
+        Serial GetLastSubmittedCommandSerial() const final override;
         void TickImpl() override;
 
         const dawn_native::PCIInfo& GetPCIInfo() const override;
@@ -66,6 +75,12 @@
             const TextureViewDescriptor* descriptor) override;
         void CollectPCIInfo();
 
+        void CheckPassedFences();
+
+        Serial mCompletedSerial = 0;
+        Serial mLastSubmittedSerial = 0;
+        std::queue<std::pair<GLsync, Serial>> mFencesInFlight;
+
         dawn_native::PCIInfo mPCIInfo;
     };
 
diff --git a/src/dawn_native/opengl/QueueGL.cpp b/src/dawn_native/opengl/QueueGL.cpp
index ad17160..a03fb18 100644
--- a/src/dawn_native/opengl/QueueGL.cpp
+++ b/src/dawn_native/opengl/QueueGL.cpp
@@ -23,9 +23,13 @@
     }
 
     void Queue::SubmitImpl(uint32_t numCommands, CommandBufferBase* const* commands) {
+        Device* device = ToBackend(GetDevice());
+
         for (uint32_t i = 0; i < numCommands; ++i) {
             ToBackend(commands[i])->Execute();
         }
+
+        device->SubmitFenceSync();
     }
 
 }}  // namespace dawn_native::opengl
diff --git a/src/dawn_native/vulkan/BufferVk.cpp b/src/dawn_native/vulkan/BufferVk.cpp
index 3c7d137..73e1538 100644
--- a/src/dawn_native/vulkan/BufferVk.cpp
+++ b/src/dawn_native/vulkan/BufferVk.cpp
@@ -252,7 +252,7 @@
         request.data = data;
         request.isWrite = isWrite;
 
-        mInflightRequests.Enqueue(std::move(request), mDevice->GetSerial());
+        mInflightRequests.Enqueue(std::move(request), mDevice->GetPendingCommandSerial());
     }
 
     void MapRequestTracker::Tick(Serial finishedSerial) {
diff --git a/src/dawn_native/vulkan/DeviceVk.cpp b/src/dawn_native/vulkan/DeviceVk.cpp
index 5b3b976..7b1cdd5 100644
--- a/src/dawn_native/vulkan/DeviceVk.cpp
+++ b/src/dawn_native/vulkan/DeviceVk.cpp
@@ -170,7 +170,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 = mNextSerial;
+        mCompletedSerial = mLastSubmittedSerial + 1;
         Tick();
 
         ASSERT(mCommandsInFlight.Empty());
@@ -277,6 +277,18 @@
         return new TextureView(texture, descriptor);
     }
 
+    Serial Device::GetCompletedCommandSerial() const {
+        return mCompletedSerial;
+    }
+
+    Serial Device::GetLastSubmittedCommandSerial() const {
+        return mLastSubmittedSerial;
+    }
+
+    Serial Device::GetPendingCommandSerial() const {
+        return mLastSubmittedSerial + 1;
+    }
+
     void Device::TickImpl() {
         CheckPassedFences();
         RecycleCompletedCommands();
@@ -289,11 +301,11 @@
 
         if (mPendingCommands.pool != VK_NULL_HANDLE) {
             SubmitPendingCommands();
-        } else if (mCompletedSerial == mNextSerial - 1) {
+        } else if (mCompletedSerial == mLastSubmittedSerial) {
             // 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++;
-            mNextSerial++;
+            mLastSubmittedSerial++;
         }
     }
 
@@ -345,10 +357,6 @@
         return mRenderPassCache.get();
     }
 
-    Serial Device::GetSerial() const {
-        return mNextSerial;
-    }
-
     VkCommandBuffer Device::GetPendingCommandBuffer() {
         if (mPendingCommands.pool == VK_NULL_HANDLE) {
             mPendingCommands = GetUnusedCommands();
@@ -395,16 +403,15 @@
             ASSERT(false);
         }
 
-        mCommandsInFlight.Enqueue(mPendingCommands, mNextSerial);
+        mLastSubmittedSerial++;
+        mCommandsInFlight.Enqueue(mPendingCommands, mLastSubmittedSerial);
         mPendingCommands = CommandPoolAndBuffer();
-        mFencesInFlight.emplace(fence, mNextSerial);
+        mFencesInFlight.emplace(fence, mLastSubmittedSerial);
 
         for (VkSemaphore semaphore : mWaitSemaphores) {
             mDeleter->DeleteWhenUnused(semaphore);
         }
         mWaitSemaphores.clear();
-
-        mNextSerial++;
     }
 
     void Device::AddWaitSemaphore(VkSemaphore semaphore) {
diff --git a/src/dawn_native/vulkan/DeviceVk.h b/src/dawn_native/vulkan/DeviceVk.h
index c11427c..35fd591 100644
--- a/src/dawn_native/vulkan/DeviceVk.h
+++ b/src/dawn_native/vulkan/DeviceVk.h
@@ -57,9 +57,8 @@
         MemoryAllocator* GetMemoryAllocator() const;
         RenderPassCache* GetRenderPassCache() const;
 
-        Serial GetSerial() const;
-
         VkCommandBuffer GetPendingCommandBuffer();
+        Serial GetPendingCommandSerial() const;
         void SubmitPendingCommands();
         void AddWaitSemaphore(VkSemaphore semaphore);
 
@@ -75,6 +74,8 @@
         RenderPipelineBase* CreateRenderPipeline(RenderPipelineBuilder* builder) override;
         SwapChainBase* CreateSwapChain(SwapChainBuilder* builder) override;
 
+        Serial GetCompletedCommandSerial() const final override;
+        Serial GetLastSubmittedCommandSerial() const final override;
         void TickImpl() override;
 
         const dawn_native::PCIInfo& GetPCIInfo() const override;
@@ -143,8 +144,8 @@
         // have finished.
         std::queue<std::pair<VkFence, Serial>> mFencesInFlight;
         std::vector<VkFence> mUnusedFences;
-        Serial mNextSerial = 1;
         Serial mCompletedSerial = 0;
+        Serial mLastSubmittedSerial = 0;
 
         struct CommandPoolAndBuffer {
             VkCommandPool pool = VK_NULL_HANDLE;
diff --git a/src/dawn_native/vulkan/FencedDeleter.cpp b/src/dawn_native/vulkan/FencedDeleter.cpp
index 6503f73..729ea29 100644
--- a/src/dawn_native/vulkan/FencedDeleter.cpp
+++ b/src/dawn_native/vulkan/FencedDeleter.cpp
@@ -39,59 +39,59 @@
     }
 
     void FencedDeleter::DeleteWhenUnused(VkBuffer buffer) {
-        mBuffersToDelete.Enqueue(buffer, mDevice->GetSerial());
+        mBuffersToDelete.Enqueue(buffer, mDevice->GetPendingCommandSerial());
     }
 
     void FencedDeleter::DeleteWhenUnused(VkDescriptorPool pool) {
-        mDescriptorPoolsToDelete.Enqueue(pool, mDevice->GetSerial());
+        mDescriptorPoolsToDelete.Enqueue(pool, mDevice->GetPendingCommandSerial());
     }
 
     void FencedDeleter::DeleteWhenUnused(VkDeviceMemory memory) {
-        mMemoriesToDelete.Enqueue(memory, mDevice->GetSerial());
+        mMemoriesToDelete.Enqueue(memory, mDevice->GetPendingCommandSerial());
     }
 
     void FencedDeleter::DeleteWhenUnused(VkFramebuffer framebuffer) {
-        mFramebuffersToDelete.Enqueue(framebuffer, mDevice->GetSerial());
+        mFramebuffersToDelete.Enqueue(framebuffer, mDevice->GetPendingCommandSerial());
     }
 
     void FencedDeleter::DeleteWhenUnused(VkImage image) {
-        mImagesToDelete.Enqueue(image, mDevice->GetSerial());
+        mImagesToDelete.Enqueue(image, mDevice->GetPendingCommandSerial());
     }
 
     void FencedDeleter::DeleteWhenUnused(VkImageView view) {
-        mImageViewsToDelete.Enqueue(view, mDevice->GetSerial());
+        mImageViewsToDelete.Enqueue(view, mDevice->GetPendingCommandSerial());
     }
 
     void FencedDeleter::DeleteWhenUnused(VkPipeline pipeline) {
-        mPipelinesToDelete.Enqueue(pipeline, mDevice->GetSerial());
+        mPipelinesToDelete.Enqueue(pipeline, mDevice->GetPendingCommandSerial());
     }
 
     void FencedDeleter::DeleteWhenUnused(VkPipelineLayout layout) {
-        mPipelineLayoutsToDelete.Enqueue(layout, mDevice->GetSerial());
+        mPipelineLayoutsToDelete.Enqueue(layout, mDevice->GetPendingCommandSerial());
     }
 
     void FencedDeleter::DeleteWhenUnused(VkRenderPass renderPass) {
-        mRenderPassesToDelete.Enqueue(renderPass, mDevice->GetSerial());
+        mRenderPassesToDelete.Enqueue(renderPass, mDevice->GetPendingCommandSerial());
     }
 
     void FencedDeleter::DeleteWhenUnused(VkSampler sampler) {
-        mSamplersToDelete.Enqueue(sampler, mDevice->GetSerial());
+        mSamplersToDelete.Enqueue(sampler, mDevice->GetPendingCommandSerial());
     }
 
     void FencedDeleter::DeleteWhenUnused(VkSemaphore semaphore) {
-        mSemaphoresToDelete.Enqueue(semaphore, mDevice->GetSerial());
+        mSemaphoresToDelete.Enqueue(semaphore, mDevice->GetPendingCommandSerial());
     }
 
     void FencedDeleter::DeleteWhenUnused(VkShaderModule module) {
-        mShaderModulesToDelete.Enqueue(module, mDevice->GetSerial());
+        mShaderModulesToDelete.Enqueue(module, mDevice->GetPendingCommandSerial());
     }
 
     void FencedDeleter::DeleteWhenUnused(VkSurfaceKHR surface) {
-        mSurfacesToDelete.Enqueue(surface, mDevice->GetSerial());
+        mSurfacesToDelete.Enqueue(surface, mDevice->GetPendingCommandSerial());
     }
 
     void FencedDeleter::DeleteWhenUnused(VkSwapchainKHR swapChain) {
-        mSwapChainsToDelete.Enqueue(swapChain, mDevice->GetSerial());
+        mSwapChainsToDelete.Enqueue(swapChain, mDevice->GetPendingCommandSerial());
     }
 
     void FencedDeleter::Tick(Serial completedSerial) {