Vulkan: Allow for multiple frames in-flight.

This should give us the ability to send off more than one frame to
the presentation engine at once. Instead of using a single pair of
Semaphores to lock each surface, we make a Semaphore pair per
Swapchain image.

BUG=angleproject:1898

Change-Id: I9e833ed9969a79617d0a8968b0d5a25c27139e87
Reviewed-on: https://chromium-review.googlesource.com/672149
Commit-Queue: Jamie Madill <jmadill@chromium.org>
Reviewed-by: Frank Henigman <fjhenigman@chromium.org>
diff --git a/src/libANGLE/renderer/vulkan/SurfaceVk.cpp b/src/libANGLE/renderer/vulkan/SurfaceVk.cpp
index 87f6ba8..cbac9c0 100644
--- a/src/libANGLE/renderer/vulkan/SurfaceVk.cpp
+++ b/src/libANGLE/renderer/vulkan/SurfaceVk.cpp
@@ -37,7 +37,7 @@
     ASSERT(!presentModes.empty());
 
     // Use FIFO mode for v-sync, since it throttles you to the display rate. Mailbox is more
-    // similar to triple-buffering. For now we hard-code Mailbox for perf tseting.
+    // similar to triple-buffering. For now we hard-code Mailbox for perf testing.
     // TODO(jmadill): Properly select present mode and re-create display if changed.
     VkPresentModeKHR bestChoice = VK_PRESENT_MODE_MAILBOX_KHR;
 
@@ -183,23 +183,17 @@
 
     rendererVk->finish();
 
-    mImageAvailableSemaphore.destroy(device);
-    mRenderingCompleteSemaphore.destroy(device);
+    mAcquireNextImageSemaphore.destroy(device);
 
-    for (auto &imageView : mSwapchainImageViews)
+    for (auto &swapchainImage : mSwapchainImages)
     {
-        imageView.destroy(device);
-    }
+        // Although we don't own the swapchain image handles, we need to keep our shutdown clean.
+        swapchainImage.image.reset();
 
-    // Although we don't own the swapchain image handles, we need to keep our shutdown clean.
-    for (auto &image : mSwapchainImages)
-    {
-        image.reset();
-    }
-
-    for (auto &framebuffer : mSwapchainFramebuffers)
-    {
-        framebuffer.destroy(device);
+        swapchainImage.imageView.destroy(device);
+        swapchainImage.framebuffer.destroy(device);
+        swapchainImage.imageAcquiredSemaphore.destroy(device);
+        swapchainImage.commandsCompleteSemaphore.destroy(device);
     }
 
     if (mSwapchain)
@@ -365,7 +359,8 @@
     transparentBlack.float32[3] = 0.0f;
 
     mSwapchainImages.resize(imageCount);
-    mSwapchainImageViews.resize(imageCount);
+
+    ANGLE_TRY(mAcquireNextImageSemaphore.init(device));
 
     for (uint32_t imageIndex = 0; imageIndex < imageCount; ++imageIndex)
     {
@@ -397,15 +392,16 @@
                               commandBuffer);
         commandBuffer->clearSingleColorImage(image, transparentBlack);
 
-        mSwapchainImages[imageIndex].retain(device, std::move(image));
-        mSwapchainImageViews[imageIndex].retain(device, std::move(imageView));
+        auto &member = mSwapchainImages[imageIndex];
+
+        member.image.retain(device, std::move(image));
+        member.imageView.retain(device, std::move(imageView));
+        ANGLE_TRY(member.imageAcquiredSemaphore.init(device));
+        ANGLE_TRY(member.commandsCompleteSemaphore.init(device));
     }
 
     ANGLE_TRY(renderer->submitAndFinishCommandBuffer(commandBuffer));
 
-    ANGLE_TRY(mImageAvailableSemaphore.init(device));
-    ANGLE_TRY(mRenderingCompleteSemaphore.init(device));
-
     // Get the first available swapchain iamge.
     ANGLE_TRY(nextSwapchainImage(renderer));
 
@@ -429,20 +425,20 @@
     FramebufferVk *framebufferVk = GetImplAs<FramebufferVk>(mState.defaultFramebuffer);
     framebufferVk->endRenderPass(currentCB);
 
-    auto *image = &mSwapchainImages[mCurrentSwapchainImageIndex];
+    auto &image = mSwapchainImages[mCurrentSwapchainImageIndex];
 
-    image->changeLayoutWithStages(VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
-                                  VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
-                                  VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, currentCB);
+    image.image.changeLayoutWithStages(VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
+                                       VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
+                                       VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, currentCB);
 
-    ANGLE_TRY(renderer->submitCommandsWithSync(currentCB, mImageAvailableSemaphore,
-                                               mRenderingCompleteSemaphore));
+    ANGLE_TRY(renderer->submitCommandsWithSync(currentCB, image.imageAcquiredSemaphore,
+                                               image.commandsCompleteSemaphore));
 
     VkPresentInfoKHR presentInfo;
     presentInfo.sType              = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
     presentInfo.pNext              = nullptr;
     presentInfo.waitSemaphoreCount = 1;
-    presentInfo.pWaitSemaphores    = mRenderingCompleteSemaphore.ptr();
+    presentInfo.pWaitSemaphores    = image.commandsCompleteSemaphore.ptr();
     presentInfo.swapchainCount     = 1;
     presentInfo.pSwapchains        = &mSwapchain;
     presentInfo.pImageIndices      = &mCurrentSwapchainImageIndex;
@@ -460,13 +456,20 @@
 {
     VkDevice device = renderer->getDevice();
 
-    ANGLE_VK_TRY(vkAcquireNextImageKHR(device, mSwapchain, std::numeric_limits<uint64_t>::max(),
-                                       mImageAvailableSemaphore.getHandle(), VK_NULL_HANDLE,
+    // Use a timeout of zero for AcquireNextImage so we don't actually block.
+    // TODO(jmadill): We should handle VK_NOT_READY and block until we can acquire the image.
+    ANGLE_VK_TRY(vkAcquireNextImageKHR(device, mSwapchain, 0,
+                                       mAcquireNextImageSemaphore.getHandle(), VK_NULL_HANDLE,
                                        &mCurrentSwapchainImageIndex));
 
+    auto &image = mSwapchainImages[mCurrentSwapchainImageIndex];
+
+    // Swap the unused swapchain semaphore and the now active spare semaphore.
+    std::swap(image.imageAcquiredSemaphore, mAcquireNextImageSemaphore);
+
     // Update RenderTarget pointers.
-    mRenderTarget.image     = &mSwapchainImages[mCurrentSwapchainImageIndex];
-    mRenderTarget.imageView = &mSwapchainImageViews[mCurrentSwapchainImageIndex];
+    mRenderTarget.image     = &image.image;
+    mRenderTarget.imageView = &image.imageView;
 
     return vk::NoError();
 }
@@ -544,10 +547,12 @@
     VkDevice device,
     const vk::RenderPass &compatibleRenderPass)
 {
-    if (!mSwapchainFramebuffers.empty())
+    auto &currentFramebuffer = mSwapchainImages[mCurrentSwapchainImageIndex].framebuffer;
+
+    if (currentFramebuffer.valid())
     {
         // Validation layers should detect if the render pass is really compatible.
-        return &mSwapchainFramebuffers[mCurrentSwapchainImageIndex];
+        return &currentFramebuffer;
     }
 
     VkFramebufferCreateInfo framebufferInfo;
@@ -563,22 +568,18 @@
     framebufferInfo.height          = static_cast<uint32_t>(mRenderTarget.extents.height);
     framebufferInfo.layers          = 1;
 
-    mSwapchainFramebuffers.resize(mSwapchainImageViews.size());
-    for (size_t imageIndex = 0; imageIndex < mSwapchainFramebuffers.size(); ++imageIndex)
+    for (auto &swapchainImage : mSwapchainImages)
     {
-        const auto &imageView        = mSwapchainImageViews[imageIndex];
-        VkImageView imageViewHandle  = imageView.getHandle();
-        framebufferInfo.pAttachments = &imageViewHandle;
+        framebufferInfo.pAttachments = swapchainImage.imageView.ptr();
 
         vk::Framebuffer framebuffer;
         ANGLE_TRY(framebuffer.init(device, framebufferInfo));
 
-        mSwapchainFramebuffers[imageIndex].retain(device, std::move(framebuffer));
+        swapchainImage.framebuffer.retain(device, std::move(framebuffer));
     }
 
-    // We should only initialize framebuffers on the first swap.
-    ASSERT(mCurrentSwapchainImageIndex == 0u);
-    return &mSwapchainFramebuffers[mCurrentSwapchainImageIndex];
+    ASSERT(currentFramebuffer.valid());
+    return &currentFramebuffer;
 }
 
 }  // namespace rx
diff --git a/src/libANGLE/renderer/vulkan/SurfaceVk.h b/src/libANGLE/renderer/vulkan/SurfaceVk.h
index f1b73cd..d50d9ca 100644
--- a/src/libANGLE/renderer/vulkan/SurfaceVk.h
+++ b/src/libANGLE/renderer/vulkan/SurfaceVk.h
@@ -111,13 +111,26 @@
     VkSwapchainKHR mSwapchain;
 
     RenderTargetVk mRenderTarget;
-    vk::Semaphore mImageAvailableSemaphore;
-    vk::Semaphore mRenderingCompleteSemaphore;
 
     uint32_t mCurrentSwapchainImageIndex;
-    std::vector<vk::Image> mSwapchainImages;
-    std::vector<vk::ImageView> mSwapchainImageViews;
-    std::vector<vk::Framebuffer> mSwapchainFramebuffers;
+
+    // When acquiring a new image for rendering, we keep a 'spare' semaphore. We pass this extra
+    // semaphore to VkAcquireNextImage, then hand it to the next available SwapchainImage when
+    // the command completes. We then make the old semaphore in the new SwapchainImage the spare
+    // semaphore, since we know the image is no longer using it. This avoids the chicken and egg
+    // problem with needing to know the next available image index before we acquire it.
+    vk::Semaphore mAcquireNextImageSemaphore;
+
+    struct SwapchainImage
+    {
+        vk::Image image;
+        vk::ImageView imageView;
+        vk::Framebuffer framebuffer;
+        vk::Semaphore imageAcquiredSemaphore;
+        vk::Semaphore commandsCompleteSemaphore;
+    };
+
+    std::vector<SwapchainImage> mSwapchainImages;
 };
 
 }  // namespace rx