dawn::native::vulkan: Handle Texture semaphores in Texture

Instead of gathering the various possible wait / signal semaphores in
QueueVk::SubmitPendingCommands as well as exporting semaphores etc.

This makes all special synchronization textures expose hooks to modify
the CommandRecordingContext before a submit, and cleanup hooks after the
submit. It keeps the semaphore logic close together in TextureVk and
will help make SwapChainTexture handle it's semaphore internally in
future CLs.

Bug:
Change-Id: I8c67cb5678335c94b820d1c43ec317280665357a
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/206615
Reviewed-by: Geoff Lang <geofflang@chromium.org>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/dawn/native/vulkan/CommandRecordingContextVk.h b/src/dawn/native/vulkan/CommandRecordingContextVk.h
index 7c3f096..fc4bea3 100644
--- a/src/dawn/native/vulkan/CommandRecordingContextVk.h
+++ b/src/dawn/native/vulkan/CommandRecordingContextVk.h
@@ -37,7 +37,7 @@
 
 namespace dawn::native::vulkan {
 
-class ImportedTextureBase;
+class Texture;
 
 // Wrapping class that currently associates a command buffer to it's corresponding pool.
 // TODO(dawn:1601) Revisit this structure since it is where the 1:1 mapping is implied.
@@ -67,7 +67,7 @@
     //  - Acquiring extra semaphores or fences.
     //  - Exporting extra semaphore or fences.
     //  - and more!
-    absl::flat_hash_set<raw_ptr<ImportedTextureBase>> specialSyncTextures;
+    absl::flat_hash_set<raw_ptr<Texture>> specialSyncTextures;
 
     // Mappable buffers which will be eagerly transitioned to usage MapRead or MapWrite after
     // VkSubmit.
diff --git a/src/dawn/native/vulkan/QueueVk.cpp b/src/dawn/native/vulkan/QueueVk.cpp
index d5d12d2..14a3ac6 100644
--- a/src/dawn/native/vulkan/QueueVk.cpp
+++ b/src/dawn/native/vulkan/QueueVk.cpp
@@ -40,7 +40,6 @@
 #include "dawn/native/vulkan/CommandRecordingContextVk.h"
 #include "dawn/native/vulkan/DeviceVk.h"
 #include "dawn/native/vulkan/FencedDeleter.h"
-#include "dawn/native/vulkan/SharedFenceVk.h"
 #include "dawn/native/vulkan/TextureVk.h"
 #include "dawn/native/vulkan/UniqueVkHandle.h"
 #include "dawn/native/vulkan/UtilsVulkan.h"
@@ -332,42 +331,8 @@
             device, &mRecordingContext, mRecordingContext.mappableBuffersForEagerTransition);
     }
 
-    // Create an external semaphore for each external textures used in the pending submit.
-    std::vector<UniqueVkHandle<VkSemaphore>> externalTextureSemaphores(
-        mRecordingContext.specialSyncTextures.size());
-    for (size_t i = 0; i < mRecordingContext.specialSyncTextures.size(); ++i) {
-        VkSemaphore semaphore;
-        DAWN_TRY_ASSIGN(semaphore,
-                        device->GetExternalSemaphoreService()->CreateExportableSemaphore());
-        externalTextureSemaphores[i] = {device, semaphore};
-    }
-
-    // Transition eagerly all used external textures for export.
     for (auto texture : mRecordingContext.specialSyncTextures) {
-        texture->TransitionEagerlyForExport(&mRecordingContext);
-
-        // TODO(330385376): Remove once ExternalImageDescriptorVk is removed.
-        std::vector<VkSemaphore> waitRequirements = texture->AcquireWaitRequirements();
-        mRecordingContext.waitSemaphores.insert(mRecordingContext.waitSemaphores.end(),
-                                                waitRequirements.begin(), waitRequirements.end());
-
-        SharedResourceMemoryContents* contents = texture->GetSharedResourceMemoryContents();
-        if (contents != nullptr) {
-            SharedTextureMemoryBase::PendingFenceList fences;
-            contents->AcquirePendingFences(&fences);
-
-            for (const auto& fence : fences) {
-                // All semaphores are binary semaphores.
-                DAWN_ASSERT(fence.signaledValue == 1u);
-                ExternalSemaphoreHandle semaphoreHandle =
-                    ToBackend(fence.object)->GetHandle().Get();
-
-                VkSemaphore semaphore;
-                DAWN_TRY_ASSIGN(semaphore, device->GetExternalSemaphoreService()->ImportSemaphore(
-                                               semaphoreHandle));
-                mRecordingContext.waitSemaphores.push_back(semaphore);
-            }
-        }
+        DAWN_TRY(texture->OnBeforeSubmit(&mRecordingContext));
     }
 
     DAWN_TRY(CheckVkSuccess(device->fn.EndCommandBuffer(mRecordingContext.commandBuffer),
@@ -376,10 +341,6 @@
     std::vector<VkPipelineStageFlags> dstStageMasks(mRecordingContext.waitSemaphores.size(),
                                                     VK_PIPELINE_STAGE_ALL_COMMANDS_BIT);
 
-    for (auto& externalTextureSemaphore : externalTextureSemaphores) {
-        mRecordingContext.signalSemaphores.push_back(externalTextureSemaphore.Get());
-    }
-
     VkSubmitInfo submitInfo;
     submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
     submitInfo.pNext = nullptr;
@@ -419,19 +380,9 @@
         mCommandsInFlight.Enqueue(submittedCommands, lastSubmittedSerial);
     }
 
-    auto externalTextureSemaphoreIter = externalTextureSemaphores.begin();
     for (auto texture : mRecordingContext.specialSyncTextures) {
-        // Export the signal semaphore.
-        ExternalSemaphoreHandle semaphoreHandle;
-        DAWN_TRY_ASSIGN(semaphoreHandle, device->GetExternalSemaphoreService()->ExportSemaphore(
-                                             externalTextureSemaphoreIter->Get()));
-        ++externalTextureSemaphoreIter;
-
-        // Update all external textures, eagerly transitioned in the submit, with the exported
-        // handles.
-        texture->UpdateExternalSemaphoreHandle(semaphoreHandle);
+        DAWN_TRY(texture->OnAfterSubmit());
     }
-    DAWN_ASSERT(externalTextureSemaphoreIter == externalTextureSemaphores.end());
 
     mRecordingContext = CommandRecordingContext();
     DAWN_TRY(PrepareRecordingContext());
diff --git a/src/dawn/native/vulkan/TextureVk.cpp b/src/dawn/native/vulkan/TextureVk.cpp
index 0b8f8c5..2cabce5 100644
--- a/src/dawn/native/vulkan/TextureVk.cpp
+++ b/src/dawn/native/vulkan/TextureVk.cpp
@@ -45,6 +45,7 @@
 #include "dawn/native/vulkan/QueueVk.h"
 #include "dawn/native/vulkan/ResourceHeapVk.h"
 #include "dawn/native/vulkan/ResourceMemoryAllocatorVk.h"
+#include "dawn/native/vulkan/SharedFenceVk.h"
 #include "dawn/native/vulkan/UtilsVulkan.h"
 #include "dawn/native/vulkan/VulkanError.h"
 
@@ -1269,6 +1270,13 @@
                               std::vector<VkImageMemoryBarrier>* barriers,
                               size_t transitionBarrierStart) {}
 
+MaybeError Texture::OnBeforeSubmit(CommandRecordingContext*) {
+    DAWN_UNREACHABLE();
+}
+MaybeError Texture::OnAfterSubmit() {
+    DAWN_UNREACHABLE();
+}
+
 //
 // InternalTexture
 //
@@ -1464,8 +1472,23 @@
         ToBackend(GetDevice())
             ->GetExternalSemaphoreService()
             ->CloseHandle(mExternalSemaphoreHandle);
+        mExternalSemaphoreHandle = kNullExternalSemaphoreHandle;
     }
-    mExternalSemaphoreHandle = kNullExternalSemaphoreHandle;
+}
+
+void ImportedTextureBase::DestroyImpl() {
+    // TODO(crbug.com/dawn/831): DestroyImpl is called from two places.
+    // - It may be called if the texture is explicitly destroyed with APIDestroy.
+    //   This case is NOT thread-safe and needs proper synchronization with other
+    //   simultaneous uses of the texture.
+    // - It may be called when the last ref to the texture is dropped and the texture
+    //   is implicitly destroyed. This case is thread-safe because there are no
+    //   other threads using the texture since there are no other live refs.
+    if (mPendingSemaphore != VK_NULL_HANDLE) {
+        ToBackend(GetDevice())->GetFencedDeleter()->DeleteWhenUnused(mPendingSemaphore);
+        mPendingSemaphore = VK_NULL_HANDLE;
+    }
+    Texture::DestroyImpl();
 }
 
 void ImportedTextureBase::TransitionEagerlyForExport(CommandRecordingContext* recordingContext) {
@@ -1507,15 +1530,6 @@
                                   nullptr, 0, nullptr, 1, &barrier);
 }
 
-void ImportedTextureBase::UpdateExternalSemaphoreHandle(ExternalSemaphoreHandle handle) {
-    if (mExternalSemaphoreHandle != kNullExternalSemaphoreHandle) {
-        ToBackend(GetDevice())
-            ->GetExternalSemaphoreService()
-            ->CloseHandle(mExternalSemaphoreHandle);
-    }
-    mExternalSemaphoreHandle = handle;
-}
-
 bool ImportedTextureBase::CanReuseWithoutBarrier(wgpu::TextureUsage lastUsage,
                                                  wgpu::TextureUsage usage,
                                                  wgpu::ShaderStage lastShaderStage,
@@ -1655,6 +1669,40 @@
     return {};
 }
 
+MaybeError ImportedTextureBase::OnBeforeSubmit(CommandRecordingContext* context) {
+    // On every submit using an exportable texture, we eagerly prepare for an export. This avoids
+    // the need to schedule an almost empty submit just for exporting later. To export we both need
+    // to transition the resource to export it, and need an exportable semaphore for future
+    // synchronization.
+    TransitionEagerlyForExport(context);
+
+    // Create the external semaphore and add it to be signaled, but only mark it pending: if
+    // anything fails during the submit we still keep the previously signaled exportable semaphore.
+    // Note that VUID-VkSemaphoreGetFdInfoKHR-handleType-01135 requires that only signaled
+    // semaphores be exported, so the export must be done in OnAfterSubmit.
+    auto semaphoreService = ToBackend(GetDevice())->GetExternalSemaphoreService();
+    DAWN_TRY_ASSIGN(mPendingSemaphore, semaphoreService->CreateExportableSemaphore());
+    context->signalSemaphores.push_back(mPendingSemaphore);
+
+    return {};
+}
+
+MaybeError ImportedTextureBase::OnAfterSubmit() {
+    Device* device = ToBackend(GetDevice());
+
+    // The submit succeeded, we can replace the previous external semaphore with the pending one.
+    if (mExternalSemaphoreHandle != kNullExternalSemaphoreHandle) {
+        device->GetExternalSemaphoreService()->CloseHandle(mExternalSemaphoreHandle);
+    }
+
+    DAWN_TRY_ASSIGN(mExternalSemaphoreHandle,
+                    device->GetExternalSemaphoreService()->ExportSemaphore(mPendingSemaphore));
+    ToBackend(GetDevice())->GetFencedDeleter()->DeleteWhenUnused(mPendingSemaphore);
+    mPendingSemaphore = VK_NULL_HANDLE;
+
+    return {};
+}
+
 //
 // ExternalVkImageTexture
 //
@@ -1689,7 +1737,7 @@
         mExternalAllocation = VK_NULL_HANDLE;
     }
 
-    Texture::DestroyImpl();
+    ImportedTextureBase::DestroyImpl();
 }
 
 MaybeError ExternalVkImageTexture::Initialize(const ExternalImageDescriptorVk* descriptor,
@@ -1793,8 +1841,11 @@
     return {};
 }
 
-std::vector<VkSemaphore> ExternalVkImageTexture::AcquireWaitRequirements() {
-    return std::move(mWaitRequirements);
+MaybeError ExternalVkImageTexture::OnBeforeSubmit(CommandRecordingContext* context) {
+    context->waitSemaphores.insert(context->waitSemaphores.end(), mWaitRequirements.begin(),
+                                   mWaitRequirements.end());
+    mWaitRequirements.clear();
+    return ImportedTextureBase::OnBeforeSubmit(context);
 }
 
 //
@@ -1821,7 +1872,7 @@
     //   other threads using the texture since there are no other live refs.
     mSharedTextureMemoryObjects = {};
 
-    Texture::DestroyImpl();
+    ImportedTextureBase::DestroyImpl();
 }
 
 void SharedTexture::Initialize(SharedTextureMemory* memory) {
@@ -1841,6 +1892,27 @@
     mPendingAcquireNewLayout = pendingAcquireNewLayout;
 }
 
+MaybeError SharedTexture::OnBeforeSubmit(CommandRecordingContext* context) {
+    Device* device = ToBackend(GetDevice());
+    SharedResourceMemoryContents* contents = GetSharedResourceMemoryContents();
+
+    SharedTextureMemoryBase::PendingFenceList fences;
+    contents->AcquirePendingFences(&fences);
+
+    for (const auto& fence : fences) {
+        // All semaphores are binary semaphores.
+        DAWN_ASSERT(fence.signaledValue == 1u);
+        ExternalSemaphoreHandle semaphoreHandle = ToBackend(fence.object)->GetHandle().Get();
+
+        VkSemaphore semaphore;
+        DAWN_TRY_ASSIGN(semaphore,
+                        device->GetExternalSemaphoreService()->ImportSemaphore(semaphoreHandle));
+        context->waitSemaphores.push_back(semaphore);
+    }
+
+    return ImportedTextureBase::OnBeforeSubmit(context);
+}
+
 //
 // TextureView
 //
diff --git a/src/dawn/native/vulkan/TextureVk.h b/src/dawn/native/vulkan/TextureVk.h
index 33ae96f..88398b9 100644
--- a/src/dawn/native/vulkan/TextureVk.h
+++ b/src/dawn/native/vulkan/TextureVk.h
@@ -102,6 +102,11 @@
     MaybeError EnsureSubresourceContentInitialized(CommandRecordingContext* recordingContext,
                                                    const SubresourceRange& range);
 
+    // Adds any special synchronization once for the current submit.
+    virtual MaybeError OnBeforeSubmit(CommandRecordingContext* context);
+    // Cleans up after the submit.
+    virtual MaybeError OnAfterSubmit();
+
     void SetLabelHelper(const char* prefix);
 
     // Dawn API
@@ -191,14 +196,6 @@
 // TODO(330385376): Merge in SharedTexture once ExternalImageDescriptorVk is fully removed.
 class ImportedTextureBase : public Texture {
   public:
-    virtual std::vector<VkSemaphore> AcquireWaitRequirements() { return {}; }
-
-    // Eagerly transition the texture for export.
-    void TransitionEagerlyForExport(CommandRecordingContext* recordingContext);
-
-    // Update the 'ExternalSemaphoreHandle' to be used for export with the newly submitted one.
-    void UpdateExternalSemaphoreHandle(ExternalSemaphoreHandle handle);
-
     // If needed, modifies the VkImageMemoryBarrier to perform a queue ownership transfer etc.
     void TweakTransition(CommandRecordingContext* recordingContext,
                          std::vector<VkImageMemoryBarrier>* barriers,
@@ -214,9 +211,18 @@
                          VkImageLayout* releasedOldLayout,
                          VkImageLayout* releasedNewLayout);
 
+    MaybeError OnBeforeSubmit(CommandRecordingContext* context) override;
+    MaybeError OnAfterSubmit() override;
+
   protected:
     using Texture::Texture;
     ~ImportedTextureBase() override;
+
+    void DestroyImpl() override;
+
+    // Eagerly transition the texture for export.
+    void TransitionEagerlyForExport(CommandRecordingContext* recordingContext);
+
     // The states of an external texture:
     //   PendingAcquire: Initialized as an external texture already, but unavailable for access yet.
     //   Acquired: Ready for access.
@@ -242,6 +248,8 @@
     // If the texture was ever used, represents a semaphore signaled once operations on the texture
     // are done so that the receiver of the export can synchronize properly.
     ExternalSemaphoreHandle mExternalSemaphoreHandle = kNullExternalSemaphoreHandle;
+    // The pending semaphore
+    VkSemaphore mPendingSemaphore = VK_NULL_HANDLE;
 };
 
 // A texture created from an VkImage that references an external memory object.
@@ -264,7 +272,7 @@
                                      VkImageLayout* releasedOldLayout,
                                      VkImageLayout* releasedNewLayout);
 
-    std::vector<VkSemaphore> AcquireWaitRequirements() override;
+    MaybeError OnBeforeSubmit(CommandRecordingContext* context) override;
 
   private:
     using ImportedTextureBase::ImportedTextureBase;
@@ -283,6 +291,8 @@
         SharedTextureMemory* memory,
         const UnpackedPtr<TextureDescriptor>& textureDescriptor);
 
+    MaybeError OnBeforeSubmit(CommandRecordingContext* context) override;
+
     void SetPendingAcquire(VkImageLayout pendingAcquireOldLayout,
                            VkImageLayout pendingAcquireNewLayout);