[Ganesh] Add support for VK_EXT_frame_boundary.

Clients can now pass in a request to mark a frame boundary when we
submit GPU work to the queue in Vulkan. This is useful for debugging
tools that want to capture all the API calls made during a frame.

Bug: b/367717718
Change-Id: I54c225225224abf6434f0456ad6a1d875a2b3732
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/907486
Commit-Queue: Greg Daniel <egdaniel@google.com>
Reviewed-by: Nicolette Prevost <nicolettep@google.com>
diff --git a/include/gpu/ganesh/GrDirectContext.h b/include/gpu/ganesh/GrDirectContext.h
index 5caf3fe..c60bb4d 100644
--- a/include/gpu/ganesh/GrDirectContext.h
+++ b/include/gpu/ganesh/GrDirectContext.h
@@ -479,9 +479,18 @@
      * If it returns false, then those same semaphores will not have been submitted and we will not
      * try to submit them again. The caller is free to delete the semaphores at any time.
      *
-     * If sync flag is GrSyncCpu::kYes, this function will return once the gpu has finished with all
-     * submitted work.
+     * If GrSubmitInfo::fSync flag is GrSyncCpu::kYes, this function will return once the gpu has
+     * finished with all submitted work.
+     *
+     * If GrSubmitInfo::fMarkBoundary flag is GrMarkFrameBoundary::kYes and the GPU supports a way
+     * to be notified about frame boundaries, then we will notify the GPU during/after the
+     * submission of work to the GPU. GrSubmitInfo::fFrameID is a frame ID that is passed to the
+     * GPU when marking a boundary. Ideally this value should be unique for each frame. Currently
+     * marking frame boundaries is only supported with the Vulkan backend and only if the
+     * VK_EXT_frame_boudnary extenstion is available.
      */
+    bool submit(const GrSubmitInfo&);
+
     bool submit(GrSyncCpu sync = GrSyncCpu::kNo) {
         GrSubmitInfo info;
         info.fSync = sync;
@@ -489,7 +498,6 @@
         return this->submit(info);
     }
 
-    bool submit(const GrSubmitInfo&);
 
     /**
      * Checks whether any asynchronous work is complete and if so calls related callbacks.
diff --git a/include/gpu/ganesh/GrTypes.h b/include/gpu/ganesh/GrTypes.h
index 7943e91..0b23a72 100644
--- a/include/gpu/ganesh/GrTypes.h
+++ b/include/gpu/ganesh/GrTypes.h
@@ -174,18 +174,15 @@
     kYes = true,
 };
 
-// TODO: Add the ability to mark frame boundaries during submit
-/**
 enum class GrMarkFrameBoundary : bool {
     kNo = false,
     kYes = true,
 };
-*/
 
 struct GrSubmitInfo {
     GrSyncCpu fSync = GrSyncCpu::kNo;
-    // TODO: Add this functionality
-    // GrMarkFrameBoundary fSync = GrMarkFrameBoundary::kNo;
+    GrMarkFrameBoundary fMarkBoundary = GrMarkFrameBoundary::kNo;
+    uint64_t fFrameID = 0;
 };
 
 #endif
diff --git a/src/gpu/ganesh/vk/GrVkCaps.cpp b/src/gpu/ganesh/vk/GrVkCaps.cpp
index e268229..b32d696 100644
--- a/src/gpu/ganesh/vk/GrVkCaps.cpp
+++ b/src/gpu/ganesh/vk/GrVkCaps.cpp
@@ -414,6 +414,10 @@
         fSupportsDeviceFaultInfo = true;
     }
 
+    if (extensions.hasExtension(VK_EXT_FRAME_BOUNDARY_EXTENSION_NAME, 1)) {
+        fSupportsFrameBoundary = true;
+    }
+
     fMaxInputAttachmentDescriptors = properties.limits.maxDescriptorSetInputAttachments;
 
     fMaxSamplerAnisotropy = properties.limits.maxSamplerAnisotropy;
diff --git a/src/gpu/ganesh/vk/GrVkCaps.h b/src/gpu/ganesh/vk/GrVkCaps.h
index fcee64d..c4388e7 100644
--- a/src/gpu/ganesh/vk/GrVkCaps.h
+++ b/src/gpu/ganesh/vk/GrVkCaps.h
@@ -197,6 +197,8 @@
 
     bool supportsDeviceFaultInfo() const { return fSupportsDeviceFaultInfo; }
 
+    bool supportsFrameBoundary() const { return fSupportsFrameBoundary; }
+
     // Returns whether we prefer to record draws directly into a primary command buffer.
     bool preferPrimaryOverSecondaryCommandBuffers() const {
         return fPreferPrimaryOverSecondaryCommandBuffers;
@@ -485,6 +487,8 @@
 
     bool fSupportsDeviceFaultInfo = false;
 
+    bool fSupportsFrameBoundary = false;
+
     bool fPreferPrimaryOverSecondaryCommandBuffers = true;
     bool fMustInvalidatePrimaryCmdBufferStateAfterClearAttachments = false;
 
diff --git a/src/gpu/ganesh/vk/GrVkCommandBuffer.cpp b/src/gpu/ganesh/vk/GrVkCommandBuffer.cpp
index 868dc79..c3315b3 100644
--- a/src/gpu/ganesh/vk/GrVkCommandBuffer.cpp
+++ b/src/gpu/ganesh/vk/GrVkCommandBuffer.cpp
@@ -556,19 +556,36 @@
                                 const VkCommandBuffer* commandBuffers,
                                 uint32_t signalCount,
                                 const VkSemaphore* signalSemaphores,
-                                GrProtected protectedContext) {
+                                GrProtected protectedContext,
+                                const GrSubmitInfo& info) {
+    void* pNext = nullptr;
+
     VkProtectedSubmitInfo protectedSubmitInfo;
     if (protectedContext == GrProtected::kYes) {
         memset(&protectedSubmitInfo, 0, sizeof(VkProtectedSubmitInfo));
         protectedSubmitInfo.sType = VK_STRUCTURE_TYPE_PROTECTED_SUBMIT_INFO;
-        protectedSubmitInfo.pNext = nullptr;
+        protectedSubmitInfo.pNext = pNext;
         protectedSubmitInfo.protectedSubmit = VK_TRUE;
+
+        pNext = &protectedSubmitInfo;
+    }
+
+    VkFrameBoundaryEXT frameBoundary;
+    if (info.fMarkBoundary == GrMarkFrameBoundary::kYes &&
+        gpu->vkCaps().supportsFrameBoundary()) {
+        memset(&frameBoundary, 0, sizeof(VkFrameBoundaryEXT));
+        frameBoundary.sType = VK_STRUCTURE_TYPE_FRAME_BOUNDARY_EXT;
+        frameBoundary.pNext = pNext;
+        frameBoundary.flags = VK_FRAME_BOUNDARY_FRAME_END_BIT_EXT;
+        frameBoundary.frameID = info.fFrameID;
+
+        pNext = &frameBoundary;
     }
 
     VkSubmitInfo submitInfo;
     memset(&submitInfo, 0, sizeof(VkSubmitInfo));
     submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
-    submitInfo.pNext = protectedContext == GrProtected::kYes ? &protectedSubmitInfo : nullptr;
+    submitInfo.pNext = pNext;
     submitInfo.waitSemaphoreCount = waitCount;
     submitInfo.pWaitSemaphores = waitSemaphores;
     submitInfo.pWaitDstStageMask = waitStages;
@@ -585,7 +602,8 @@
         GrVkGpu* gpu,
         VkQueue queue,
         TArray<GrVkSemaphore::Resource*>& signalSemaphores,
-        TArray<GrVkSemaphore::Resource*>& waitSemaphores) {
+        TArray<GrVkSemaphore::Resource*>& waitSemaphores,
+        const GrSubmitInfo& submitInfo) {
     SkASSERT(!fIsActive);
 
     VkResult err;
@@ -615,7 +633,7 @@
         // queue with no worries.
         submitResult = submit_to_queue(
                 gpu, queue, fSubmitFence, 0, nullptr, nullptr, 1, &fCmdBuffer, 0, nullptr,
-                GrProtected(gpu->protectedContext()));
+                GrProtected(gpu->protectedContext()), submitInfo);
     } else {
         TArray<VkSemaphore> vkSignalSems(signalCount);
         for (int i = 0; i < signalCount; ++i) {
@@ -642,7 +660,7 @@
         submitResult = submit_to_queue(gpu, queue, fSubmitFence, vkWaitSems.size(),
                                        vkWaitSems.begin(), vkWaitStages.begin(), 1, &fCmdBuffer,
                                        vkSignalSems.size(), vkSignalSems.begin(),
-                                       GrProtected(gpu->protectedContext()));
+                                       GrProtected(gpu->protectedContext()), submitInfo);
         if (submitResult == VK_SUCCESS) {
             for (int i = 0; i < signalCount; ++i) {
                 signalSemaphores[i]->markAsSignaled();
diff --git a/src/gpu/ganesh/vk/GrVkCommandBuffer.h b/src/gpu/ganesh/vk/GrVkCommandBuffer.h
index 57ea754..82ef3b4 100644
--- a/src/gpu/ganesh/vk/GrVkCommandBuffer.h
+++ b/src/gpu/ganesh/vk/GrVkCommandBuffer.h
@@ -33,6 +33,7 @@
 class GrVkImage;
 class GrVkPipeline;
 class GrVkRenderPass;
+struct GrSubmitInfo;
 struct SkIRect;
 
 class GrVkCommandBuffer {
@@ -324,9 +325,11 @@
                       uint32_t regionCount,
                       const VkImageResolve* regions);
 
-    bool submitToQueue(GrVkGpu* gpu, VkQueue queue,
+    bool submitToQueue(GrVkGpu* gpu,
+                       VkQueue queue,
                        skia_private::TArray<GrVkSemaphore::Resource*>& signalSemaphores,
-                       skia_private::TArray<GrVkSemaphore::Resource*>& waitSemaphores);
+                       skia_private::TArray<GrVkSemaphore::Resource*>& waitSemaphores,
+                       const GrSubmitInfo&);
 
     void forceSync(GrVkGpu* gpu);
 
diff --git a/src/gpu/ganesh/vk/GrVkGpu.cpp b/src/gpu/ganesh/vk/GrVkGpu.cpp
index d35d90b..f1e8eea 100644
--- a/src/gpu/ganesh/vk/GrVkGpu.cpp
+++ b/src/gpu/ganesh/vk/GrVkGpu.cpp
@@ -363,14 +363,14 @@
     return fCachedOpsRenderPass.get();
 }
 
-bool GrVkGpu::submitCommandBuffer(SyncQueue sync) {
+bool GrVkGpu::submitCommandBuffer(const GrSubmitInfo& submitInfo) {
     TRACE_EVENT0("skia.gpu", TRACE_FUNC);
     if (!this->currentCommandBuffer()) {
         return false;
     }
     SkASSERT(!fCachedOpsRenderPass || !fCachedOpsRenderPass->isActive());
 
-    if (!this->currentCommandBuffer()->hasWork() && kForce_SyncQueue != sync &&
+    if (!this->currentCommandBuffer()->hasWork() && submitInfo.fSync == GrSyncCpu::kNo &&
         fSemaphoresToSignal.empty() && fSemaphoresToWaitOn.empty()) {
         // We may have added finished procs during the flush call. Since there is no actual work
         // we are not submitting the command buffer and may never come back around to submit it.
@@ -386,9 +386,9 @@
     SkASSERT(fMainCmdPool);
     fMainCmdPool->close();
     bool didSubmit = fMainCmdBuffer->submitToQueue(this, fQueue, fSemaphoresToSignal,
-                                                   fSemaphoresToWaitOn);
+                                                   fSemaphoresToWaitOn, submitInfo);
 
-    if (didSubmit && sync == kForce_SyncQueue) {
+    if (didSubmit && submitInfo.fSync == GrSyncCpu::kYes) {
         fMainCmdBuffer->forceSync(this);
     }
 
@@ -492,7 +492,9 @@
                                      VK_ACCESS_HOST_WRITE_BIT,
                                      VK_PIPELINE_STAGE_HOST_BIT,
                                      false);
-            if (!this->submitCommandBuffer(kForce_SyncQueue)) {
+            GrSubmitInfo submitInfo;
+            submitInfo.fSync = GrSyncCpu::kYes;
+            if (!this->submitCommandBuffer(submitInfo)) {
                 return false;
             }
         }
@@ -2100,7 +2102,9 @@
     GrVkImageInfo info;
     if (GrBackendRenderTargets::GetVkImageInfo(rt, &info)) {
         // something in the command buffer may still be using this, so force submit
-        SkAssertResult(this->submitCommandBuffer(kForce_SyncQueue));
+        GrSubmitInfo submitInfo;
+        submitInfo.fSync = GrSyncCpu::kYes;
+        SkAssertResult(this->submitCommandBuffer(submitInfo));
         GrVkImage::DestroyImageInfo(this, const_cast<GrVkImageInfo*>(&info));
     }
 }
@@ -2221,11 +2225,7 @@
 }
 
 bool GrVkGpu::onSubmitToGpu(const GrSubmitInfo& info) {
-    if (info.fSync == GrSyncCpu::kYes) {
-        return this->submitCommandBuffer(kForce_SyncQueue);
-    } else {
-        return this->submitCommandBuffer(kSkip_SyncQueue);
-    }
+    return this->submitCommandBuffer(info);
 }
 
 void GrVkGpu::finishOutstandingGpuWork() {
@@ -2588,7 +2588,9 @@
 
     // We need to submit the current command buffer to the Queue and make sure it finishes before
     // we can copy the data out of the buffer.
-    if (!this->submitCommandBuffer(kForce_SyncQueue)) {
+    GrSubmitInfo submitInfo;
+    submitInfo.fSync = GrSyncCpu::kYes;
+    if (!this->submitCommandBuffer(submitInfo)) {
         return false;
     }
     void* mappedMemory = transferBuffer->map();
diff --git a/src/gpu/ganesh/vk/GrVkGpu.h b/src/gpu/ganesh/vk/GrVkGpu.h
index e566edd..82928bb 100644
--- a/src/gpu/ganesh/vk/GrVkGpu.h
+++ b/src/gpu/ganesh/vk/GrVkGpu.h
@@ -242,11 +242,6 @@
     bool checkVkResult(VkResult);
 
 private:
-    enum SyncQueue {
-        kForce_SyncQueue,
-        kSkip_SyncQueue
-    };
-
     GrVkGpu(GrDirectContext*,
             const skgpu::VulkanBackendContext&,
             const sk_sp<GrVkCaps> caps,
@@ -385,12 +380,15 @@
     void onReportSubmitHistograms() override;
 
     // Ends and submits the current command buffer to the queue and then creates a new command
-    // buffer and begins it. If sync is set to kForce_SyncQueue, the function will wait for all
-    // work in the queue to finish before returning. If this GrVkGpu object has any semaphores in
-    // fSemaphoreToSignal, we will add those signal semaphores to the submission of this command
-    // buffer. If this GrVkGpu object has any semaphores in fSemaphoresToWaitOn, we will add those
-    // wait semaphores to the submission of this command buffer.
-    bool submitCommandBuffer(SyncQueue sync);
+    // buffer and begins it. If fSync in the submitInfo is set to GrSyncCpu::kYes, the function will
+    // wait for all work in the queue to finish before returning. If this GrVkGpu object has any
+    // semaphores in fSemaphoreToSignal, we will add those signal semaphores to the submission of
+    // this command buffer. If this GrVkGpu object has any semaphores in fSemaphoresToWaitOn, we
+    // will add those wait semaphores to the submission of this command buffer.
+    //
+    // If fMarkBoundary in submitInfo is GrMarkFrameBoundary::kYes, then we will mark the end of a
+    // frame if the VK_EXT_frame_boundary extension is available.
+    bool submitCommandBuffer(const GrSubmitInfo& submitInfo);
 
     void copySurfaceAsCopyImage(GrSurface* dst,
                                 GrSurface* src,
diff --git a/tools/gpu/vk/VkTestUtils.cpp b/tools/gpu/vk/VkTestUtils.cpp
index 1cfce1f..edb3267 100644
--- a/tools/gpu/vk/VkTestUtils.cpp
+++ b/tools/gpu/vk/VkTestUtils.cpp
@@ -168,11 +168,12 @@
     const char* kValidExtensions[] = {
             // single merged layer
             VK_EXT_BLEND_OPERATION_ADVANCED_EXTENSION_NAME,
-            VK_EXT_DEVICE_FAULT_EXTENSION_NAME,
             VK_EXT_CONSERVATIVE_RASTERIZATION_EXTENSION_NAME,
+            VK_EXT_DEVICE_FAULT_EXTENSION_NAME,
+            VK_EXT_FRAME_BOUNDARY_EXTENSION_NAME,
             VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME,
-            VK_EXT_RGBA10X6_FORMATS_EXTENSION_NAME,
             VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME,
+            VK_EXT_RGBA10X6_FORMATS_EXTENSION_NAME,
             VK_KHR_BIND_MEMORY_2_EXTENSION_NAME,
             VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME,
             VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME,