d3d11: add Buffer::FinalizeMap

This postpones the actual d3d11 buffer map call from MapAsyncImpl to
FinalizeMap, when its mLastUsageSerial has passed, to avoid the
potential map stalls.

Bug: dawn:2357
Change-Id: I15152cca55c2f0bb52f5d55108e5524747de9d79
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/175486
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Austin Eng <enga@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Jie A Chen <jie.a.chen@intel.com>
Reviewed-by: Peng Huang <penghuang@chromium.org>
diff --git a/src/dawn/native/d3d11/BufferD3D11.cpp b/src/dawn/native/d3d11/BufferD3D11.cpp
index 5f6c915..9f463f4 100644
--- a/src/dawn/native/d3d11/BufferD3D11.cpp
+++ b/src/dawn/native/d3d11/BufferD3D11.cpp
@@ -297,23 +297,43 @@
 MaybeError Buffer::MapAsyncImpl(wgpu::MapMode mode, size_t offset, size_t size) {
     DAWN_ASSERT(mD3d11NonConstantBuffer);
 
-    auto commandContext = ToBackend(GetDevice()->GetQueue())
-                              ->GetScopedPendingCommandContext(QueueBase::SubmitMode::Normal);
+    mMapReadySerial = mLastUsageSerial;
+    const ExecutionSerial completedSerial = GetDevice()->GetQueue()->GetCompletedCommandSerial();
+    // We may run into map stall in case that the buffer is still being used by previous submitted
+    // commands. To avoid that, instead we ask Queue to do the map later when mLastUsageSerial has
+    // passed.
+    if (mMapReadySerial > completedSerial) {
+        ToBackend(GetDevice()->GetQueue())->TrackPendingMapBuffer({this}, mMapReadySerial);
+    } else {
+        auto commandContext = ToBackend(GetDevice()->GetQueue())
+                                  ->GetScopedPendingCommandContext(QueueBase::SubmitMode::Normal);
+        DAWN_TRY(FinalizeMap(&commandContext, completedSerial));
+    }
 
-    // TODO(dawn:1705): make sure the map call is not blocked by the GPU operations.
-    DAWN_TRY(MapInternal(&commandContext));
+    return {};
+}
 
-    DAWN_TRY(EnsureDataInitialized(&commandContext));
+MaybeError Buffer::FinalizeMap(ScopedCommandRecordingContext* commandContext,
+                               ExecutionSerial completedSerial) {
+    // Needn't map the buffer if this is for a previous mapAsync that was cancelled.
+    if (completedSerial >= mMapReadySerial) {
+        // TODO(dawn:1705): make sure the map call is not blocked by the GPU operations.
+        DAWN_TRY(MapInternal(commandContext));
+
+        DAWN_TRY(EnsureDataInitialized(commandContext));
+    }
 
     return {};
 }
 
 void Buffer::UnmapImpl() {
     DAWN_ASSERT(mD3d11NonConstantBuffer);
-    DAWN_ASSERT(mMappedData);
-    auto commandContext = ToBackend(GetDevice()->GetQueue())
-                              ->GetScopedPendingCommandContext(QueueBase::SubmitMode::Normal);
-    UnmapInternal(&commandContext);
+    mMapReadySerial = kMaxExecutionSerial;
+    if (mMappedData) {
+        auto commandContext = ToBackend(GetDevice()->GetQueue())
+                                  ->GetScopedPendingCommandContext(QueueBase::SubmitMode::Normal);
+        UnmapInternal(&commandContext);
+    }
 }
 
 void* Buffer::GetMappedPointer() {
diff --git a/src/dawn/native/d3d11/BufferD3D11.h b/src/dawn/native/d3d11/BufferD3D11.h
index 09decf0..7c185fb 100644
--- a/src/dawn/native/d3d11/BufferD3D11.h
+++ b/src/dawn/native/d3d11/BufferD3D11.h
@@ -87,6 +87,10 @@
                            Buffer* destination,
                            uint64_t destinationOffset);
 
+    // Actually map the buffer when its last usage serial has passed.
+    MaybeError FinalizeMap(ScopedCommandRecordingContext* commandContext,
+                           ExecutionSerial completedSerial);
+
     class ScopedMap : public NonCopyable {
       public:
         // Map buffer and return a ScopedMap object. If the buffer is not mappable,
@@ -156,6 +160,7 @@
     ComPtr<ID3D11Buffer> mD3d11NonConstantBuffer;
     bool mConstantBufferIsUpdated = true;
     raw_ptr<uint8_t, AllowPtrArithmetic> mMappedData = nullptr;
+    ExecutionSerial mMapReadySerial = kMaxExecutionSerial;
 };
 
 }  // namespace dawn::native::d3d11
diff --git a/src/dawn/native/d3d11/QueueD3D11.cpp b/src/dawn/native/d3d11/QueueD3D11.cpp
index 5690978..b39c17d 100644
--- a/src/dawn/native/d3d11/QueueD3D11.cpp
+++ b/src/dawn/native/d3d11/QueueD3D11.cpp
@@ -156,6 +156,20 @@
     return {};
 }
 
+MaybeError Queue::CheckAndMapReadyBuffers(ExecutionSerial completedSerial) {
+    auto commandContext = GetScopedPendingCommandContext(QueueBase::SubmitMode::Normal);
+    for (auto buffer : mPendingMapBuffers.IterateUpTo(completedSerial)) {
+        DAWN_TRY(buffer->FinalizeMap(&commandContext, completedSerial));
+    }
+    mPendingMapBuffers.ClearUpTo(completedSerial);
+
+    return {};
+}
+
+void Queue::TrackPendingMapBuffer(Ref<Buffer>&& buffer, ExecutionSerial readySerial) {
+    mPendingMapBuffers.Enqueue(buffer, readySerial);
+}
+
 MaybeError Queue::WriteBufferImpl(BufferBase* buffer,
                                   uint64_t bufferOffset,
                                   const void* data,
@@ -215,6 +229,8 @@
         return ExecutionSerial(0);
     }
 
+    DAWN_TRY(CheckAndMapReadyBuffers(completedSerial));
+
     return completedSerial;
 }
 
diff --git a/src/dawn/native/d3d11/QueueD3D11.h b/src/dawn/native/d3d11/QueueD3D11.h
index c029b34..ca265c7 100644
--- a/src/dawn/native/d3d11/QueueD3D11.h
+++ b/src/dawn/native/d3d11/QueueD3D11.h
@@ -30,6 +30,7 @@
 
 #include "dawn/common/MutexProtected.h"
 #include "dawn/common/SerialMap.h"
+#include "dawn/common/SerialQueue.h"
 #include "dawn/native/SystemEvent.h"
 #include "dawn/native/d3d/QueueD3D.h"
 
@@ -56,6 +57,9 @@
     // DeviceBase is fully created.
     MaybeError InitializePendingContext();
 
+    // Register the pending map buffer to be checked.
+    void TrackPendingMapBuffer(Ref<Buffer>&& buffer, ExecutionSerial readySerial);
+
   private:
     using d3d::Queue::Queue;
 
@@ -83,11 +87,15 @@
     ResultOrError<Ref<d3d::SharedFence>> GetOrCreateSharedFence() override;
     void SetEventOnCompletion(ExecutionSerial serial, HANDLE event) override;
 
+    // Check all pending map buffers, and actually map the ready ones.
+    MaybeError CheckAndMapReadyBuffers(ExecutionSerial completedSerial);
+
     ComPtr<ID3D11Fence> mFence;
     HANDLE mFenceEvent = nullptr;
     Ref<SharedFence> mSharedFence;
     MutexProtected<CommandRecordingContext, CommandRecordingContextGuard> mPendingCommands;
     std::atomic<bool> mPendingCommandsNeedSubmit = false;
+    SerialQueue<ExecutionSerial, Ref<Buffer>> mPendingMapBuffers;
 };
 
 }  // namespace dawn::native::d3d11