Move Device's serial management to ExecutionQueueBase

The serial management will eventually move to be part of the backend's
ExecutionQueues. First off split the relevant Device functionality in
its own class without any functional change.

Also removes AssumeCommandsCompleteForTesting now that
AssumeCommandsComplete is public.

Bug: dawn:1413

Change-Id: I92c2a9aa8757f0ac85e616fcab2f82f3f9b5a594
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/136205
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
Reviewed-by: Loko Kung <lokokung@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/dawn/native/BUILD.gn b/src/dawn/native/BUILD.gn
index a22f69a..1a5d683 100644
--- a/src/dawn/native/BUILD.gn
+++ b/src/dawn/native/BUILD.gn
@@ -261,6 +261,8 @@
     "ErrorInjector.h",
     "ErrorScope.cpp",
     "ErrorScope.h",
+    "ExecutionQueue.cpp",
+    "ExecutionQueue.h",
     "ExternalTexture.cpp",
     "ExternalTexture.h",
     "Features.cpp",
diff --git a/src/dawn/native/CMakeLists.txt b/src/dawn/native/CMakeLists.txt
index 52ba6b6..6809257 100644
--- a/src/dawn/native/CMakeLists.txt
+++ b/src/dawn/native/CMakeLists.txt
@@ -117,6 +117,8 @@
     "Features.h"
     "ExternalTexture.cpp"
     "ExternalTexture.h"
+    "ExecutionQueue.cpp"
+    "ExecutionQueue.h"
     "IndirectDrawMetadata.cpp"
     "IndirectDrawMetadata.h"
     "IndirectDrawValidationEncoder.cpp"
diff --git a/src/dawn/native/Device.cpp b/src/dawn/native/Device.cpp
index 72e81e6..b4bad4b 100644
--- a/src/dawn/native/Device.cpp
+++ b/src/dawn/native/Device.cpp
@@ -479,7 +479,7 @@
             UNREACHABLE();
             break;
     }
-    ASSERT(mCompletedSerial == mLastSubmittedSerial);
+    ASSERT(GetCompletedCommandSerial() == GetLastSubmittedCommandSerial());
 
     if (mState != State::BeingCreated) {
         // The GPU timeline is finished.
@@ -757,28 +757,10 @@
     return GetPhysicalDevice()->GetInstance()->GetPlatform();
 }
 
-ExecutionSerial DeviceBase::GetCompletedCommandSerial() const {
-    return mCompletedSerial;
-}
-
-ExecutionSerial DeviceBase::GetLastSubmittedCommandSerial() const {
-    return mLastSubmittedSerial;
-}
-
 InternalPipelineStore* DeviceBase::GetInternalPipelineStore() {
     return mInternalPipelineStore.get();
 }
 
-void DeviceBase::IncrementLastSubmittedCommandSerial() {
-    mLastSubmittedSerial++;
-}
-
-void DeviceBase::AssumeCommandsComplete() {
-    // Bump serials so any pending callbacks can be fired.
-    mLastSubmittedSerial++;
-    mCompletedSerial = mLastSubmittedSerial;
-}
-
 bool DeviceBase::HasPendingTasks() {
     return mAsyncTaskManager->HasPendingTasks() || !mCallbackTaskManager->IsEmpty();
 }
@@ -790,26 +772,6 @@
     return !HasScheduledCommands();
 }
 
-ExecutionSerial DeviceBase::GetPendingCommandSerial() const {
-    return mLastSubmittedSerial + ExecutionSerial(1);
-}
-
-MaybeError DeviceBase::CheckPassedSerials() {
-    ExecutionSerial completedSerial;
-    DAWN_TRY_ASSIGN(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 == ExecutionSerial(0));
-
-    if (completedSerial > mCompletedSerial) {
-        mCompletedSerial = completedSerial;
-    }
-
-    return {};
-}
-
 ResultOrError<const Format*> DeviceBase::GetInternalFormat(wgpu::TextureFormat format) const {
     FormatIndex index = ComputeFormatIndex(format);
     DAWN_INVALID_IF(index >= mFormatTable.size(), "Unknown texture format %s.", format);
@@ -1362,8 +1324,8 @@
     // TODO(crbug.com/dawn/833): decouple TickImpl from updating the serial so that we can
     // tick the dynamic uploader before the backend resource allocators. This would allow
     // reclaiming resources one tick earlier.
-    mDynamicUploader->Deallocate(mCompletedSerial);
-    mQueue->Tick(mCompletedSerial);
+    mDynamicUploader->Deallocate(GetCompletedCommandSerial());
+    mQueue->Tick(GetCompletedCommandSerial());
 
     return {};
 }
@@ -2014,30 +1976,6 @@
     return 4u;
 }
 
-bool DeviceBase::HasScheduledCommands() const {
-    return mLastSubmittedSerial > mCompletedSerial || HasPendingCommands();
-}
-
-void DeviceBase::AssumeCommandsCompleteForTesting() {
-    AssumeCommandsComplete();
-}
-
-// All prevously submitted works at the moment will supposedly complete at this serial.
-// Internally the serial is computed according to whether frontend and backend have pending
-// commands. There are 4 cases of combination:
-//   1) Frontend(No), Backend(No)
-//   2) Frontend(No), Backend(Yes)
-//   3) Frontend(Yes), Backend(No)
-//   4) Frontend(Yes), Backend(Yes)
-// For case 1, we don't need the serial to track the task as we can ack it right now.
-// For case 2 and 4, there will be at least an eventual submission, so we can use
-// 'GetPendingCommandSerial' as the serial.
-// For case 3, we can't use 'GetPendingCommandSerial' as it won't be submitted surely. Instead we
-// use 'GetLastSubmittedCommandSerial', which must be fired eventually.
-ExecutionSerial DeviceBase::GetScheduledWorkDoneSerial() const {
-    return HasPendingCommands() ? GetPendingCommandSerial() : GetLastSubmittedCommandSerial();
-}
-
 MaybeError DeviceBase::CopyFromStagingToBuffer(BufferBase* source,
                                                uint64_t sourceOffset,
                                                BufferBase* destination,
diff --git a/src/dawn/native/Device.h b/src/dawn/native/Device.h
index 03ee648..684c40f 100644
--- a/src/dawn/native/Device.h
+++ b/src/dawn/native/Device.h
@@ -27,6 +27,7 @@
 #include "dawn/native/Commands.h"
 #include "dawn/native/ComputePipeline.h"
 #include "dawn/native/Error.h"
+#include "dawn/native/ExecutionQueue.h"
 #include "dawn/native/Features.h"
 #include "dawn/native/Format.h"
 #include "dawn/native/Forward.h"
@@ -60,7 +61,7 @@
 
 using WGSLExtensionSet = std::unordered_set<std::string>;
 
-class DeviceBase : public RefCountedWithExternalCount {
+class DeviceBase : public RefCountedWithExternalCount, public ExecutionQueueBase {
   public:
     DeviceBase(AdapterBase* adapter,
                const DeviceDescriptor* descriptor,
@@ -174,10 +175,6 @@
         CommandEncoder* encoder,
         const CommandBufferDescriptor* descriptor) = 0;
 
-    ExecutionSerial GetCompletedCommandSerial() const;
-    ExecutionSerial GetLastSubmittedCommandSerial() const;
-    ExecutionSerial GetPendingCommandSerial() const;
-
     // Many Dawn objects are completely immutable once created which means that if two
     // creations are given the same arguments, they can return the same object. Reusing
     // objects will help make comparisons between objects by a single pointer comparison.
@@ -369,9 +366,6 @@
 
     friend class IgnoreLazyClearCountScope;
 
-    // Check for passed fences and set the new completed serial
-    MaybeError CheckPassedSerials();
-
     MaybeError Tick();
 
     // TODO(crbug.com/dawn/839): Organize the below backend-specific parameters into the struct
@@ -431,20 +425,6 @@
 
     virtual void AppendDebugLayerMessages(ErrorData* error) {}
 
-    void AssumeCommandsCompleteForTesting();
-
-    // Whether the device is having scheduled commands to be submitted or executed.
-    // There are "Scheduled" "Pending" and "Executing" commands. Frontend knows "Executing" commands
-    // and backend knows "Pending" commands. "Scheduled" commands are either "Pending" or
-    // "Executing".
-    bool HasScheduledCommands() const;
-    // The serial by which time all currently submitted or pending operations will be completed.
-    ExecutionSerial GetScheduledWorkDoneSerial() const;
-
-    // For the commands being internally recorded in backend, that were not urgent to submit, this
-    // method makes them to be submitted as soon as possbile in next ticks.
-    virtual void ForceEventualFlushOfCommands() = 0;
-
     // It is guaranteed that the wrapped mutex will outlive the Device (if the Device is deleted
     // before the AutoLockAndHoldRef).
     [[nodiscard]] Mutex::AutoLockAndHoldRef GetScopedLockSafeForDelete();
@@ -473,9 +453,6 @@
     void DestroyObjects();
     void Destroy();
 
-    // Incrememt mLastSubmittedSerial when we submit the next serial
-    void IncrementLastSubmittedCommandSerial();
-
   private:
     void WillDropLastExternalRef() override;
 
@@ -544,36 +521,13 @@
     void ConsumeError(std::unique_ptr<ErrorData> error,
                       InternalErrorType additionalAllowedErrors = InternalErrorType::None);
 
-    // 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 ResultOrError<ExecutionSerial> CheckAndUpdateCompletedSerials() = 0;
-    // 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();
     bool HasPendingTasks();
     bool IsDeviceIdle();
 
-    // 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.
-    ExecutionSerial mCompletedSerial = ExecutionSerial(0);
-    ExecutionSerial mLastSubmittedSerial = ExecutionSerial(0);
-
     // DestroyImpl is used to clean up and release resources used by device, does not wait for
     // GPU or check errors.
     virtual void DestroyImpl() = 0;
 
-    // WaitForIdleForDestruction waits for GPU to finish, checks errors and gets ready for
-    // destruction. This is only used when properly destructing the device. For a real
-    // device loss, this function doesn't need to be called since the driver already closed all
-    // resources.
-    virtual MaybeError WaitForIdleForDestruction() = 0;
-
-    // Indicates whether the backend has pending commands to be submitted as soon as possible.
-    virtual bool HasPendingCommands() const = 0;
-
     virtual MaybeError CopyFromStagingToBufferImpl(BufferBase* source,
                                                    uint64_t sourceOffset,
                                                    BufferBase* destination,
diff --git a/src/dawn/native/ExecutionQueue.cpp b/src/dawn/native/ExecutionQueue.cpp
new file mode 100644
index 0000000..c913b31
--- /dev/null
+++ b/src/dawn/native/ExecutionQueue.cpp
@@ -0,0 +1,77 @@
+// Copyright 2023 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "dawn/native/ExecutionQueue.h"
+
+namespace dawn::native {
+
+ExecutionSerial ExecutionQueueBase::GetPendingCommandSerial() const {
+    return mLastSubmittedSerial + ExecutionSerial(1);
+}
+
+ExecutionSerial ExecutionQueueBase::GetLastSubmittedCommandSerial() const {
+    return mLastSubmittedSerial;
+}
+
+ExecutionSerial ExecutionQueueBase::GetCompletedCommandSerial() const {
+    return mCompletedSerial;
+}
+
+MaybeError ExecutionQueueBase::CheckPassedSerials() {
+    ExecutionSerial completedSerial;
+    DAWN_TRY_ASSIGN(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 == ExecutionSerial(0));
+
+    if (completedSerial > mCompletedSerial) {
+        mCompletedSerial = completedSerial;
+    }
+
+    return {};
+}
+
+void ExecutionQueueBase::AssumeCommandsComplete() {
+    // Bump serials so any pending callbacks can be fired.
+    mLastSubmittedSerial++;
+    mCompletedSerial = mLastSubmittedSerial;
+}
+
+void ExecutionQueueBase::IncrementLastSubmittedCommandSerial() {
+    mLastSubmittedSerial++;
+}
+
+bool ExecutionQueueBase::HasScheduledCommands() const {
+    return mLastSubmittedSerial > mCompletedSerial || HasPendingCommands();
+}
+
+// All prevously submitted works at the moment will supposedly complete at this serial.
+// Internally the serial is computed according to whether frontend and backend have pending
+// commands. There are 4 cases of combination:
+//   1) Frontend(No), Backend(No)
+//   2) Frontend(No), Backend(Yes)
+//   3) Frontend(Yes), Backend(No)
+//   4) Frontend(Yes), Backend(Yes)
+// For case 1, we don't need the serial to track the task as we can ack it right now.
+// For case 2 and 4, there will be at least an eventual submission, so we can use
+// 'GetPendingCommandSerial' as the serial.
+// For case 3, we can't use 'GetPendingCommandSerial' as it won't be submitted surely. Instead we
+// use 'GetLastSubmittedCommandSerial', which must be fired eventually.
+ExecutionSerial ExecutionQueueBase::GetScheduledWorkDoneSerial() const {
+    return HasPendingCommands() ? GetPendingCommandSerial() : GetLastSubmittedCommandSerial();
+}
+
+}  // namespace dawn::native
diff --git a/src/dawn/native/ExecutionQueue.h b/src/dawn/native/ExecutionQueue.h
new file mode 100644
index 0000000..2c5c062
--- /dev/null
+++ b/src/dawn/native/ExecutionQueue.h
@@ -0,0 +1,77 @@
+// Copyright 2023 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_DAWN_NATIVE_EXECUTIONQUEUE_H_
+#define SRC_DAWN_NATIVE_EXECUTIONQUEUE_H_
+
+#include "dawn/native/Error.h"
+#include "dawn/native/IntegerTypes.h"
+
+namespace dawn::native {
+
+// Represents an engine which processes a stream of GPU work. It handles the tracking and
+// update of the various ExecutionSerials related to that work.
+class ExecutionQueueBase {
+  public:
+    // The latest serial known to have completed execution on the queue.
+    ExecutionSerial GetCompletedCommandSerial() const;
+    // The serial of the latest batch of work sent for execution.
+    ExecutionSerial GetLastSubmittedCommandSerial() const;
+    // The serial of the batch that is currently pending submission.
+    ExecutionSerial GetPendingCommandSerial() const;
+    // The serial by which time all currently submitted or pending operations will be completed.
+    ExecutionSerial GetScheduledWorkDoneSerial() const;
+    // Whether the execution queue has scheduled commands to be submitted or executing.
+    bool HasScheduledCommands() const;
+
+    // Check for passed fences and set the new completed serial.
+    MaybeError CheckPassedSerials();
+
+    // For the commands being internally recorded in backend, that were not urgent to submit, this
+    // method makes them to be submitted as soon as possible in next ticks.
+    virtual void ForceEventualFlushOfCommands() = 0;
+
+    // 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();
+
+  protected:
+    // Increment mLastSubmittedSerial when we submit the next serial
+    void IncrementLastSubmittedCommandSerial();
+
+    // WaitForIdleForDestruction waits for GPU to finish, checks errors and gets ready for
+    // destruction. This is only used when properly destructing the device. For a real
+    // device loss, this function doesn't need to be called since the driver already closed all
+    // resources.
+    virtual MaybeError WaitForIdleForDestruction() = 0;
+
+  private:
+    // 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 ResultOrError<ExecutionSerial> 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.
+    ExecutionSerial mCompletedSerial = ExecutionSerial(0);
+    ExecutionSerial mLastSubmittedSerial = ExecutionSerial(0);
+
+    // Indicates whether the backend has pending commands to be submitted as soon as possible.
+    virtual bool HasPendingCommands() const = 0;
+};
+
+}  // namespace dawn::native
+
+#endif  // SRC_DAWN_NATIVE_EXECUTIONQUEUE_H_
diff --git a/src/dawn/tests/white_box/D3D12DescriptorHeapTests.cpp b/src/dawn/tests/white_box/D3D12DescriptorHeapTests.cpp
index f5dcf95..1222fa1 100644
--- a/src/dawn/tests/white_box/D3D12DescriptorHeapTests.cpp
+++ b/src/dawn/tests/white_box/D3D12DescriptorHeapTests.cpp
@@ -324,7 +324,7 @@
     EXPECT_EQ(allocator->GetShaderVisiblePoolSizeForTesting(), kNumOfSwitches);
 
     // Ensure switched-over heaps can be recycled by advancing the GPU.
-    mD3DDevice->AssumeCommandsCompleteForTesting();
+    mD3DDevice->AssumeCommandsComplete();
 
     // Switch-over |kNumOfSwitches| again reusing the same heaps.
     for (uint32_t i = 0; i < kNumOfSwitches; i++) {
@@ -413,7 +413,7 @@
     EXPECT_EQ(allocator->GetShaderVisiblePoolSizeForTesting(), kNumOfPooledHeaps);
 
     // Ensure switched-over heaps can be recycled by advancing the GPU.
-    mD3DDevice->AssumeCommandsCompleteForTesting();
+    mD3DDevice->AssumeCommandsComplete();
 
     // Switch-over the pool-allocated heaps.
     for (uint32_t i = 0; i < kNumOfPooledHeaps; i++) {