Vulkan: Decouple EGLSync from renderer serial

To support future work where RendererVk functionality is moved to
ContextVk.  Given multiple contexts, EGLSync can no longer rely on a
single serial as it can be used in multiple contexts.  Instead, the
fence corresponding to the submission in which the EGLSync object
signals is kept so it can be waited on.

Introduces a `vk::Shared` class that includes a ref-counted reference to
a Vulkan object (vk::Fence in this case).  This is specially made to
`destroy()` object when reference count reaches zero.

Bug: angleproject:2464
Change-Id: I68c8229eea8df77974e28fcc2a9563dae5d204f9
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1493131
Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>
Reviewed-by: Jamie Madill (use @chromium please) <jmadill@google.com>
Reviewed-by: Yuly Novikov <ynovikov@chromium.org>
diff --git a/src/libANGLE/renderer/vulkan/RendererVk.cpp b/src/libANGLE/renderer/vulkan/RendererVk.cpp
index 177c2f3..9e14e4b 100644
--- a/src/libANGLE/renderer/vulkan/RendererVk.cpp
+++ b/src/libANGLE/renderer/vulkan/RendererVk.cpp
@@ -468,8 +468,9 @@
 RendererVk::CommandBatch::~CommandBatch() = default;
 
 RendererVk::CommandBatch::CommandBatch(CommandBatch &&other)
-    : commandPool(std::move(other.commandPool)), fence(std::move(other.fence)), serial(other.serial)
-{}
+{
+    *this = std::move(other);
+}
 
 RendererVk::CommandBatch &RendererVk::CommandBatch::operator=(CommandBatch &&other)
 {
@@ -482,7 +483,7 @@
 void RendererVk::CommandBatch::destroy(VkDevice device)
 {
     commandPool.destroy(device);
-    fence.destroy(device);
+    fence.reset(device);
 }
 
 // RendererVk implementation.
@@ -1371,12 +1372,12 @@
         // On device loss we need to wait for fence to be signaled before destroying it
         if (mDeviceLost)
         {
-            VkResult status = batch.fence.wait(mDevice, kMaxFenceWaitTimeNs);
+            VkResult status = batch.fence.get().wait(mDevice, kMaxFenceWaitTimeNs);
             // If wait times out, it is probably not possible to recover from lost device
             ASSERT(status == VK_SUCCESS || status == VK_ERROR_DEVICE_LOST);
         }
-        batch.fence.destroy(mDevice);
         batch.commandPool.destroy(mDevice);
+        batch.fence.reset(mDevice);
     }
     mInFlightCommands.clear();
 
@@ -1395,7 +1396,7 @@
 
     for (CommandBatch &batch : mInFlightCommands)
     {
-        VkResult result = batch.fence.getStatus(mDevice);
+        VkResult result = batch.fence.get().getStatus(mDevice);
         if (result == VK_NOT_READY)
         {
             break;
@@ -1405,7 +1406,7 @@
         ASSERT(batch.serial > mLastCompletedQueueSerial);
         mLastCompletedQueueSerial = batch.serial;
 
-        batch.fence.destroy(mDevice);
+        batch.fence.reset(mDevice);
         TRACE_EVENT0("gpu.angle", "commandPool.destroy");
         batch.commandPool.destroy(mDevice);
         ++finishedCount;
@@ -1434,15 +1435,12 @@
                                       vk::CommandBuffer &&commandBuffer)
 {
     TRACE_EVENT0("gpu.angle", "RendererVk::submitFrame");
-    VkFenceCreateInfo fenceInfo = {};
-    fenceInfo.sType             = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
-    fenceInfo.flags             = 0;
 
     vk::Scoped<CommandBatch> scopedBatch(mDevice);
     CommandBatch &batch = scopedBatch.get();
-    ANGLE_VK_TRY(context, batch.fence.init(mDevice, fenceInfo));
+    ANGLE_TRY(getSubmitFence(context, &batch.fence));
 
-    ANGLE_VK_TRY(context, vkQueueSubmit(mQueue, 1, &submitInfo, batch.fence.getHandle()));
+    ANGLE_VK_TRY(context, vkQueueSubmit(mQueue, 1, &submitInfo, batch.fence.get().getHandle()));
 
     // Store this command buffer in the in-flight list.
     batch.commandPool = std::move(mCommandPool);
@@ -1450,10 +1448,12 @@
 
     mInFlightCommands.emplace_back(scopedBatch.release());
 
+    // Make sure a new fence is created for the next submission.
+    mSubmitFence.reset(mDevice);
+
     // CPU should be throttled to avoid mInFlightCommands from growing too fast.  That is done on
     // swap() though, and there could be multiple submissions in between (through glFlush() calls),
-    // so the limit is larger than the expected number of images.  The
-    // InterleavedAttributeDataBenchmark perf test for example issues a large number of flushes.
+    // so the limit is larger than the expected number of images.
     ASSERT(mInFlightCommands.size() <= kInFlightCommandsLimit);
 
     nextSerial();
@@ -1505,24 +1505,6 @@
 
 angle::Result RendererVk::finishToSerial(vk::Context *context, Serial serial)
 {
-    bool timedOut        = false;
-    angle::Result result = finishToSerialOrTimeout(context, serial, kMaxFenceWaitTimeNs, &timedOut);
-
-    // Don't tolerate timeout.  If such a large wait time results in timeout, something's wrong.
-    if (timedOut)
-    {
-        result = angle::Result::Stop;
-    }
-    return result;
-}
-
-angle::Result RendererVk::finishToSerialOrTimeout(vk::Context *context,
-                                                  Serial serial,
-                                                  uint64_t timeout,
-                                                  bool *outTimedOut)
-{
-    *outTimedOut = false;
-
     if (!isSerialInUse(serial) || mInFlightCommands.empty())
     {
         return angle::Result::Continue;
@@ -1542,15 +1524,9 @@
     const CommandBatch &batch = mInFlightCommands[batchIndex];
 
     // Wait for it finish
-    VkResult status = batch.fence.wait(mDevice, kMaxFenceWaitTimeNs);
+    VkResult status = batch.fence.get().wait(mDevice, kMaxFenceWaitTimeNs);
 
-    // If timed out, report it as such.
-    if (status == VK_TIMEOUT)
-    {
-        *outTimedOut = true;
-        return angle::Result::Continue;
-    }
-
+    // Don't tolerate timeout.  If such a large wait time results in timeout, something's wrong.
     ANGLE_VK_TRY(context, status);
 
     // Clean up finished batches.
@@ -1714,6 +1690,26 @@
     return semaphore;
 }
 
+angle::Result RendererVk::getSubmitFence(vk::Context *context,
+                                         vk::Shared<vk::Fence> *sharedFenceOut)
+{
+    if (!mSubmitFence.isReferenced())
+    {
+        vk::Fence fence;
+
+        VkFenceCreateInfo fenceCreateInfo = {};
+        fenceCreateInfo.sType             = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
+        fenceCreateInfo.flags             = 0;
+
+        ANGLE_VK_TRY(context, fence.init(mDevice, fenceCreateInfo));
+
+        mSubmitFence.assign(mDevice, std::move(fence));
+    }
+
+    sharedFenceOut->copy(mDevice, mSubmitFence);
+    return angle::Result::Continue;
+}
+
 angle::Result RendererVk::getTimestamp(vk::Context *context, uint64_t *timestampOut)
 {
     // The intent of this function is to query the timestamp without stalling the GPU.  Currently,
diff --git a/src/libANGLE/renderer/vulkan/RendererVk.h b/src/libANGLE/renderer/vulkan/RendererVk.h
index 8b85822..48a1fe5 100644
--- a/src/libANGLE/renderer/vulkan/RendererVk.h
+++ b/src/libANGLE/renderer/vulkan/RendererVk.h
@@ -113,11 +113,6 @@
 
     // Wait for completion of batches until (at least) batch with given serial is finished.
     angle::Result finishToSerial(vk::Context *context, Serial serial);
-    // A variant of finishToSerial that can time out.  Timeout status returned in outTimedOut.
-    angle::Result finishToSerialOrTimeout(vk::Context *context,
-                                          Serial serial,
-                                          uint64_t timeout,
-                                          bool *outTimedOut);
 
     uint32_t getQueueFamilyIndex() const { return mCurrentQueueFamilyIndex; }
 
@@ -163,6 +158,9 @@
     // by next submission.
     const vk::Semaphore *getSubmitLastSignaledSemaphore(vk::Context *context);
 
+    // Get (or allocate) the fence that will be signaled on next submission.
+    angle::Result getSubmitFence(vk::Context *context, vk::Shared<vk::Fence> *sharedFenceOut);
+
     // This should only be called from ResourceVk.
     // TODO(jmadill): Keep in ContextVk to enable threaded rendering.
     vk::CommandGraph *getCommandGraph();
@@ -290,7 +288,7 @@
         void destroy(VkDevice device);
 
         vk::CommandPool commandPool;
-        vk::Fence fence;
+        vk::Shared<vk::Fence> fence;
         Serial serial;
     };
 
@@ -328,6 +326,14 @@
     // A pool of semaphores used to support the aforementioned mid-frame submissions.
     vk::DynamicSemaphorePool mSubmitSemaphorePool;
 
+    // mSubmitFence is the fence that's going to be signaled at the next submission.  This is used
+    // to support SyncVk objects, which may outlive the context (as EGLSync objects).
+    //
+    // TODO(geofflang): this is in preparation for moving RendererVk functionality to ContextVk, and
+    // is otherwise unnecessary as the SyncVk objects don't actually outlive the renderer currently.
+    // http://anglebug.com/2701
+    vk::Shared<vk::Fence> mSubmitFence;
+
     // See CommandGraph.h for a desription of the Command Graph.
     vk::CommandGraph mCommandGraph;
 
diff --git a/src/libANGLE/renderer/vulkan/SyncVk.cpp b/src/libANGLE/renderer/vulkan/SyncVk.cpp
index fb2beba..b248ffb 100644
--- a/src/libANGLE/renderer/vulkan/SyncVk.cpp
+++ b/src/libANGLE/renderer/vulkan/SyncVk.cpp
@@ -27,6 +27,8 @@
     {
         renderer->releaseObject(renderer->getCurrentQueueSerial(), &mEvent);
     }
+
+    mFence.reset(renderer->getDevice());
 }
 
 angle::Result FenceSyncVk::initialize(vk::Context *context)
@@ -43,8 +45,9 @@
     vk::Scoped<vk::Event> event(device);
     ANGLE_VK_TRY(context, event.get().init(device, eventCreateInfo));
 
+    ANGLE_TRY(renderer->getSubmitFence(context, &mFence));
+
     mEvent        = event.release();
-    mSignalSerial = renderer->getCurrentQueueSerial();
 
     renderer->getCommandGraph()->setFenceSync(mEvent);
     return angle::Result::Continue;
@@ -73,23 +76,22 @@
         return angle::Result::Continue;
     }
 
-    // If asked to flush, only do so if the queue serial hasn't changed (as otherwise the event
-    // signal is already flushed). If not asked to flush, do the flush anyway!  This is because
-    // there's no cpu-side wait on the event and there's no fence yet inserted to wait on.  We could
-    // test the event in a loop with a sleep, which can only ever not timeout if another thread
-    // performs the flush.  Instead, we perform the flush for simplicity.
-    if (hasPendingWork(renderer))
+    if (flushCommands)
     {
         ANGLE_TRY(renderer->flush(context));
     }
 
-    // Wait on the fence that's implicitly inserted at the end of every submission.
-    bool timedOut = false;
-    angle::Result result =
-        renderer->finishToSerialOrTimeout(context, mSignalSerial, timeout, &timedOut);
-    ANGLE_TRY(result);
+    // Wait on the fence that's expected to be signaled on the first vkQueueSubmit after
+    // `initialize` was called.
+    VkResult status = mFence.get().wait(renderer->getDevice(), timeout);
 
-    *outResult = timedOut ? VK_TIMEOUT : VK_SUCCESS;
+    // Check for errors, but don't consider timeout as such.
+    if (status != VK_TIMEOUT)
+    {
+        ANGLE_VK_TRY(context, status);
+    }
+
+    *outResult = status;
     return angle::Result::Continue;
 }
 
@@ -110,11 +112,6 @@
     return angle::Result::Continue;
 }
 
-bool FenceSyncVk::hasPendingWork(RendererVk *renderer)
-{
-    return mSignalSerial == renderer->getCurrentQueueSerial();
-}
-
 SyncVk::SyncVk() : SyncImpl() {}
 
 SyncVk::~SyncVk() {}
diff --git a/src/libANGLE/renderer/vulkan/SyncVk.h b/src/libANGLE/renderer/vulkan/SyncVk.h
index 4b46626..79ff9bd 100644
--- a/src/libANGLE/renderer/vulkan/SyncVk.h
+++ b/src/libANGLE/renderer/vulkan/SyncVk.h
@@ -41,15 +41,12 @@
     angle::Result getStatus(vk::Context *context, bool *signaled);
 
   private:
-    bool hasPendingWork(RendererVk *renderer);
-
     // The vkEvent that's signaled on `init` and can be waited on in `serverWait`, or queried with
     // `getStatus`.
     vk::Event mEvent;
-    // The serial in which the event was inserted.  Used in `clientWait` to know whether flush is
-    // necessary, and to be able to wait on the vkFence that's automatically inserted at the end of
-    // each submissions.
-    Serial mSignalSerial;
+    // The vkFence that's signaled once the command buffer including the `init` signal is executed.
+    // `clientWait` waits on this fence.
+    vk::Shared<vk::Fence> mFence;
 };
 
 class SyncVk final : public SyncImpl
diff --git a/src/libANGLE/renderer/vulkan/vk_utils.h b/src/libANGLE/renderer/vulkan/vk_utils.h
index 9e553ad..b270878 100644
--- a/src/libANGLE/renderer/vulkan/vk_utils.h
+++ b/src/libANGLE/renderer/vulkan/vk_utils.h
@@ -319,6 +319,7 @@
 
     RefCounted(RefCounted &&copy) : mRefCount(copy.mRefCount), mObject(std::move(copy.mObject))
     {
+        ASSERT(this != &copy);
         copy.mRefCount = 0;
     }
 
@@ -384,6 +385,76 @@
   private:
     RefCounted<T> *mRefCounted;
 };
+
+// Helper class to share ref-counted Vulkan objects.  Requires that T have a destroy method
+// that takes a VkDevice and returns void.
+template <typename T>
+class Shared final : angle::NonCopyable
+{
+  public:
+    Shared() : mRefCounted(nullptr) {}
+    ~Shared() { ASSERT(mRefCounted == nullptr); }
+
+    Shared(Shared &&other) { *this = std::move(other); }
+    Shared &operator=(Shared &&other)
+    {
+        ASSERT(this != &other);
+        mRefCounted       = other.mRefCounted;
+        other.mRefCounted = nullptr;
+        return *this;
+    }
+
+    void set(VkDevice device, RefCounted<T> *refCounted)
+    {
+        if (mRefCounted)
+        {
+            mRefCounted->releaseRef();
+            if (!mRefCounted->isReferenced())
+            {
+                mRefCounted->get().destroy(device);
+                SafeDelete(mRefCounted);
+            }
+        }
+
+        mRefCounted = refCounted;
+
+        if (mRefCounted)
+        {
+            mRefCounted->addRef();
+        }
+    }
+
+    void assign(VkDevice device, T &&newObject)
+    {
+        set(device, new RefCounted<T>(std::move(newObject)));
+    }
+
+    void copy(VkDevice device, const Shared<T> &other) { set(device, other.mRefCounted); }
+
+    void reset(VkDevice device) { set(device, nullptr); }
+
+    bool isReferenced() const
+    {
+        // If reference is zero, the object should have been deleted.  I.e. if the object is not
+        // nullptr, it should have a reference.
+        ASSERT(!mRefCounted || mRefCounted->isReferenced());
+        return mRefCounted != nullptr;
+    }
+
+    T &get()
+    {
+        ASSERT(mRefCounted && mRefCounted->isReferenced());
+        return mRefCounted->get();
+    }
+    const T &get() const
+    {
+        ASSERT(mRefCounted && mRefCounted->isReferenced());
+        return mRefCounted->get();
+    }
+
+  private:
+    RefCounted<T> *mRefCounted;
+};
 }  // namespace vk
 
 // List of function pointers for used extensions.