Mesa (main): venus: reland wsi image ownership transfer for Android

GitLab Mirror gitlab-mirror at kemper.freedesktop.org
Thu Jun 3 06:13:17 UTC 2021


Module: Mesa
Branch: main
Commit: 676abf12dc6f1d03dbf73cf5370f78578576e0ed
URL:    http://cgit.freedesktop.org/mesa/mesa/commit/?id=676abf12dc6f1d03dbf73cf5370f78578576e0ed

Author: Chia-I Wu <olvaffe at gmail.com>
Date:   Wed May  5 15:58:39 2021 -0700

venus: reland wsi image ownership transfer for Android

This takes a different approach.  It turns out we can patch
vkCmd{Begin,End}RenderPass, vkCmdWaitEvents, and vkCmdPipelineBarrier to
do queue family ownership transfers without tracking image ownerships.

v2: check the value of VN_PRESENT_SRC_INTERNAL_LAYOUT and keep this
    Android-only

Signed-off-by: Chia-I Wu <olvaffe at gmail.com>
Reviewed-by: Yiwei Zhang <zzyiwei at chromium.org> (v1)
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/10709>

---

 src/virtio/vulkan/vn_command_buffer.c | 326 ++++++++++++++++++++++++++++++++--
 1 file changed, 312 insertions(+), 14 deletions(-)

diff --git a/src/virtio/vulkan/vn_command_buffer.c b/src/virtio/vulkan/vn_command_buffer.c
index 73f10f6c504..655a071d7fb 100644
--- a/src/virtio/vulkan/vn_command_buffer.c
+++ b/src/virtio/vulkan/vn_command_buffer.c
@@ -50,31 +50,211 @@ vn_cmd_get_image_memory_barriers(struct vn_command_buffer *cmd,
    return cmd->builder.image_barriers;
 }
 
+/* About VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, the spec says
+ *
+ *    VK_IMAGE_LAYOUT_PRESENT_SRC_KHR must only be used for presenting a
+ *    presentable image for display. A swapchain's image must be transitioned
+ *    to this layout before calling vkQueuePresentKHR, and must be
+ *    transitioned away from this layout after calling vkAcquireNextImageKHR.
+ *
+ * That allows us to treat the layout internally as
+ *
+ *  - VK_IMAGE_LAYOUT_GENERAL
+ *  - VK_QUEUE_FAMILY_FOREIGN_EXT has the ownership, if the image is not a
+ *    prime blit source
+ *
+ * while staying performant.
+ *
+ * About queue family ownerships, the spec says
+ *
+ *    A queue family can take ownership of an image subresource or buffer
+ *    range of a resource created with VK_SHARING_MODE_EXCLUSIVE, without an
+ *    ownership transfer, in the same way as for a resource that was just
+ *    created; however, taking ownership in this way has the effect that the
+ *    contents of the image subresource or buffer range are undefined.
+ *
+ * It is unclear if that is applicable to external resources, which supposedly
+ * have the same semantics
+ *
+ *    Binding a resource to a memory object shared between multiple Vulkan
+ *    instances or other APIs does not change the ownership of the underlying
+ *    memory. The first entity to access the resource implicitly acquires
+ *    ownership. Accessing a resource backed by memory that is owned by a
+ *    particular instance or API has the same semantics as accessing a
+ *    VK_SHARING_MODE_EXCLUSIVE resource[...]
+ *
+ * We should get the spec clarified, or get rid of this completely broken code
+ * (TODO).
+ *
+ * Assuming a queue family can acquire the ownership implicitly when the
+ * contents are not needed, we do not need to worry about
+ * VK_IMAGE_LAYOUT_UNDEFINED.  We can use VK_IMAGE_LAYOUT_PRESENT_SRC_KHR as
+ * the sole signal to trigger queue family ownership transfers.
+ *
+ * When the image has VK_SHARING_MODE_CONCURRENT, we can, and are required to,
+ * use VK_QUEUE_FAMILY_IGNORED as the other queue family whether we are
+ * transitioning to or from VK_IMAGE_LAYOUT_PRESENT_SRC_KHR.
+ *
+ * When the image has VK_SHARING_MODE_EXCLUSIVE, we have to work out who the
+ * other queue family is.  It is easier when the barrier does not also define
+ * a queue family ownership transfer (i.e., srcQueueFamilyIndex equals to
+ * dstQueueFamilyIndex).  The other queue family must be the queue family the
+ * command buffer was allocated for.
+ *
+ * When the barrier also defines a queue family ownership transfer, it is
+ * submitted both to the source queue family to release the ownership and to
+ * the destination queue family to acquire the ownership.  Depending on
+ * whether the barrier transitions to or from VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
+ * we are only interested in the ownership release or acquire respectively and
+ * should be careful to avoid double releases/acquires.
+ *
+ * I haven't followed all transition paths mentally to verify the correctness.
+ * I likely also violate some VUs or miss some cases below.  They are
+ * hopefully fixable and are left as TODOs.
+ */
 static void
 vn_cmd_fix_image_memory_barrier(const struct vn_command_buffer *cmd,
                                 const VkImageMemoryBarrier *src_barrier,
                                 VkImageMemoryBarrier *out_barrier)
 {
+   const struct vn_image *img = vn_image_from_handle(src_barrier->image);
+
    *out_barrier = *src_barrier;
 
-   /* XXX drop the #ifdef after fixing common wsi */
-#ifdef ANDROID
-   for (uint32_t i = 0; i < count; i++) {
+   /* no fix needed */
+   if (out_barrier->oldLayout != VK_IMAGE_LAYOUT_PRESENT_SRC_KHR &&
+       out_barrier->newLayout != VK_IMAGE_LAYOUT_PRESENT_SRC_KHR)
+      return;
+
+   assert(img->is_wsi);
+
+   if (VN_PRESENT_SRC_INTERNAL_LAYOUT == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR)
+      return;
+
+   /* prime blit src or no layout transition */
+   if (img->prime_blit_buffer != VK_NULL_HANDLE ||
+       out_barrier->oldLayout == out_barrier->newLayout) {
       if (out_barrier->oldLayout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR)
-         out_barrier->oldLayout = VK_IMAGE_LAYOUT_GENERAL;
+         out_barrier->oldLayout = VN_PRESENT_SRC_INTERNAL_LAYOUT;
       if (out_barrier->newLayout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR)
-         out_barrier->newLayout = VK_IMAGE_LAYOUT_GENERAL;
+         out_barrier->newLayout = VN_PRESENT_SRC_INTERNAL_LAYOUT;
+      return;
+   }
+
+   if (out_barrier->oldLayout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR) {
+      out_barrier->oldLayout = VN_PRESENT_SRC_INTERNAL_LAYOUT;
+
+      /* no availability operation needed */
+      out_barrier->srcAccessMask = 0;
+
+      const uint32_t dst_qfi = out_barrier->dstQueueFamilyIndex;
+      if (img->sharing_mode == VK_SHARING_MODE_CONCURRENT) {
+         out_barrier->srcQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT;
+         out_barrier->dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+      } else if (dst_qfi == out_barrier->srcQueueFamilyIndex ||
+                 dst_qfi == cmd->queue_family_index) {
+         out_barrier->srcQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT;
+         out_barrier->dstQueueFamilyIndex = cmd->queue_family_index;
+      } else {
+         /* The barrier also defines a queue family ownership transfer, and
+          * this is the one that gets submitted to the source queue family to
+          * release the ownership.  Skip both the transfer and the transition.
+          */
+         out_barrier->srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+         out_barrier->dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+         out_barrier->newLayout = out_barrier->oldLayout;
+      }
+   } else {
+      out_barrier->newLayout = VN_PRESENT_SRC_INTERNAL_LAYOUT;
+
+      /* no visibility operation needed */
+      out_barrier->dstAccessMask = 0;
+
+      const uint32_t src_qfi = out_barrier->srcQueueFamilyIndex;
+      if (img->sharing_mode == VK_SHARING_MODE_CONCURRENT) {
+         out_barrier->srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+         out_barrier->dstQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT;
+      } else if (src_qfi == out_barrier->dstQueueFamilyIndex ||
+                 src_qfi == cmd->queue_family_index) {
+         out_barrier->srcQueueFamilyIndex = cmd->queue_family_index;
+         out_barrier->dstQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT;
+      } else {
+         /* The barrier also defines a queue family ownership transfer, and
+          * this is the one that gets submitted to the destination queue
+          * family to acquire the ownership.  Skip both the transfer and the
+          * transition.
+          */
+         out_barrier->srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+         out_barrier->dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+         out_barrier->oldLayout = out_barrier->newLayout;
+      }
    }
-#endif
 }
 
 static const VkImageMemoryBarrier *
 vn_cmd_wait_events_fix_image_memory_barriers(
+   struct vn_command_buffer *cmd,
+   const VkImageMemoryBarrier *src_barriers,
+   uint32_t count,
+   uint32_t *out_transfer_count)
+{
+   *out_transfer_count = 0;
+
+   if (cmd->builder.render_pass ||
+       !vn_image_memory_barrier_has_present_src(src_barriers, count))
+      return src_barriers;
+
+   VkImageMemoryBarrier *img_barriers =
+      vn_cmd_get_image_memory_barriers(cmd, count * 2);
+   if (!img_barriers) {
+      cmd->state = VN_COMMAND_BUFFER_STATE_INVALID;
+      return src_barriers;
+   }
+
+   /* vkCmdWaitEvents cannot be used for queue family ownership transfers.
+    * Nothing appears to be said about the submission order of image memory
+    * barriers in the same array.  We take the liberty to move queue family
+    * ownership transfers to the tail.
+    */
+   VkImageMemoryBarrier *transfer_barriers = img_barriers + count;
+   uint32_t transfer_count = 0;
+   uint32_t valid_count = 0;
+   for (uint32_t i = 0; i < count; i++) {
+      VkImageMemoryBarrier *img_barrier = &img_barriers[valid_count];
+      vn_cmd_fix_image_memory_barrier(cmd, &src_barriers[i], img_barrier);
+
+      if (VN_PRESENT_SRC_INTERNAL_LAYOUT == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR) {
+         valid_count++;
+         continue;
+      }
+
+      if (img_barrier->srcQueueFamilyIndex ==
+          img_barrier->dstQueueFamilyIndex) {
+         valid_count++;
+      } else {
+         transfer_barriers[transfer_count++] = *img_barrier;
+      }
+   }
+
+   assert(valid_count + transfer_count == count);
+   if (transfer_count) {
+      /* copy back to the tail */
+      memcpy(&img_barriers[valid_count], transfer_barriers,
+             sizeof(*transfer_barriers) * transfer_count);
+      *out_transfer_count = transfer_count;
+   }
+
+   return img_barriers;
+}
+
+static const VkImageMemoryBarrier *
+vn_cmd_pipeline_barrier_fix_image_memory_barriers(
    struct vn_command_buffer *cmd,
    const VkImageMemoryBarrier *src_barriers,
    uint32_t count)
 {
-   if (!vn_image_memory_barrier_has_present_src(src_barriers, count))
+   if (cmd->builder.render_pass ||
+       !vn_image_memory_barrier_has_present_src(src_barriers, count))
       return src_barriers;
 
    VkImageMemoryBarrier *img_barriers =
@@ -92,14 +272,85 @@ vn_cmd_wait_events_fix_image_memory_barriers(
    return img_barriers;
 }
 
-static const VkImageMemoryBarrier *
-vn_cmd_pipeline_barrier_fix_image_memory_barriers(
+static void
+vn_cmd_encode_memory_barriers(struct vn_command_buffer *cmd,
+                              VkPipelineStageFlags src_stage_mask,
+                              VkPipelineStageFlags dst_stage_mask,
+                              uint32_t buf_barrier_count,
+                              const VkBufferMemoryBarrier *buf_barriers,
+                              uint32_t img_barrier_count,
+                              const VkImageMemoryBarrier *img_barriers)
+{
+   const VkCommandBuffer cmd_handle = vn_command_buffer_to_handle(cmd);
+
+   const size_t cmd_size = vn_sizeof_vkCmdPipelineBarrier(
+      cmd_handle, src_stage_mask, dst_stage_mask, 0, 0, NULL,
+      buf_barrier_count, buf_barriers, img_barrier_count, img_barriers);
+   if (!vn_cs_encoder_reserve(&cmd->cs, cmd_size)) {
+      cmd->state = VN_COMMAND_BUFFER_STATE_INVALID;
+      return;
+   }
+
+   vn_encode_vkCmdPipelineBarrier(
+      &cmd->cs, 0, cmd_handle, src_stage_mask, dst_stage_mask, 0, 0, NULL,
+      buf_barrier_count, buf_barriers, img_barrier_count, img_barriers);
+}
+
+static void
+vn_present_src_attachment_to_image_memory_barrier(
+   const struct vn_image *img,
+   const struct vn_present_src_attachment *att,
+   VkImageMemoryBarrier *img_barrier)
+{
+   *img_barrier = (VkImageMemoryBarrier)
+   {
+      .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+      .srcAccessMask = att->src_access_mask,
+      .dstAccessMask = att->dst_access_mask,
+      .oldLayout = att->acquire ? VK_IMAGE_LAYOUT_PRESENT_SRC_KHR
+                                : VN_PRESENT_SRC_INTERNAL_LAYOUT,
+      .newLayout = att->acquire ? VN_PRESENT_SRC_INTERNAL_LAYOUT
+                                : VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
+      .image = vn_image_to_handle((struct vn_image *)img),
+      .subresourceRange = {
+         .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+         .levelCount = 1,
+         .layerCount = 1,
+      },
+   };
+}
+
+static void
+vn_cmd_transfer_present_src_images(
    struct vn_command_buffer *cmd,
-   const VkImageMemoryBarrier *src_barriers,
+   const struct vn_image *const *images,
+   const struct vn_present_src_attachment *atts,
    uint32_t count)
 {
-   return vn_cmd_wait_events_fix_image_memory_barriers(cmd, src_barriers,
-                                                       count);
+   VkImageMemoryBarrier *img_barriers =
+      vn_cmd_get_image_memory_barriers(cmd, count);
+   if (!img_barriers) {
+      cmd->state = VN_COMMAND_BUFFER_STATE_INVALID;
+      return;
+   }
+
+   VkPipelineStageFlags src_stage_mask = 0;
+   VkPipelineStageFlags dst_stage_mask = 0;
+   for (uint32_t i = 0; i < count; i++) {
+      src_stage_mask |= atts[i].src_stage_mask;
+      dst_stage_mask |= atts[i].dst_stage_mask;
+
+      vn_present_src_attachment_to_image_memory_barrier(images[i], &atts[i],
+                                                        &img_barriers[i]);
+      vn_cmd_fix_image_memory_barrier(cmd, &img_barriers[i],
+                                      &img_barriers[i]);
+   }
+
+   if (VN_PRESENT_SRC_INTERNAL_LAYOUT == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR)
+      return;
+
+   vn_cmd_encode_memory_barriers(cmd, src_stage_mask, dst_stage_mask, 0, NULL,
+                                 count, img_barriers);
 }
 
 static void
@@ -144,6 +395,11 @@ vn_cmd_begin_render_pass(struct vn_command_buffer *cmd,
       images[i] = vn_image_view_from_handle(views[index])->image;
    }
 
+   if (pass->acquire_count) {
+      vn_cmd_transfer_present_src_images(
+         cmd, images, pass->present_src_attachments, pass->acquire_count);
+   }
+
    cmd->builder.present_src_images = images;
 }
 
@@ -158,7 +414,17 @@ vn_cmd_end_render_pass(struct vn_command_buffer *cmd)
    if (!pass->present_src_count || !cmd->builder.present_src_images)
       return;
 
-   vk_free(&cmd->allocator, cmd->builder.present_src_images);
+   const struct vn_image **images = cmd->builder.present_src_images;
+   cmd->builder.present_src_images = NULL;
+
+   if (pass->release_count) {
+      vn_cmd_transfer_present_src_images(
+         cmd, images + pass->acquire_count,
+         pass->present_src_attachments + pass->acquire_count,
+         pass->release_count);
+   }
+
+   vk_free(&cmd->allocator, images);
 }
 
 /* command pool commands */
@@ -942,6 +1208,16 @@ vn_CmdCopyImageToBuffer(VkCommandBuffer commandBuffer,
       vn_command_buffer_from_handle(commandBuffer);
    size_t cmd_size;
 
+   bool prime_blit = false;
+   if (srcImageLayout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR &&
+       VN_PRESENT_SRC_INTERNAL_LAYOUT != VK_IMAGE_LAYOUT_PRESENT_SRC_KHR) {
+      srcImageLayout = VN_PRESENT_SRC_INTERNAL_LAYOUT;
+
+      const struct vn_image *img = vn_image_from_handle(srcImage);
+      prime_blit = img->is_wsi && img->prime_blit_buffer == dstBuffer;
+      assert(prime_blit);
+   }
+
    cmd_size = vn_sizeof_vkCmdCopyImageToBuffer(commandBuffer, srcImage,
                                                srcImageLayout, dstBuffer,
                                                regionCount, pRegions);
@@ -951,6 +1227,20 @@ vn_CmdCopyImageToBuffer(VkCommandBuffer commandBuffer,
    vn_encode_vkCmdCopyImageToBuffer(&cmd->cs, 0, commandBuffer, srcImage,
                                     srcImageLayout, dstBuffer, regionCount,
                                     pRegions);
+
+   if (prime_blit) {
+      const VkBufferMemoryBarrier buf_barrier = {
+         .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
+         .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+         .srcQueueFamilyIndex = cmd->queue_family_index,
+         .dstQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT,
+         .buffer = dstBuffer,
+         .size = VK_WHOLE_SIZE,
+      };
+      vn_cmd_encode_memory_barriers(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT,
+                                    VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 1,
+                                    &buf_barrier, 0, NULL);
+   }
 }
 
 void
@@ -1130,8 +1420,10 @@ vn_CmdWaitEvents(VkCommandBuffer commandBuffer,
       vn_command_buffer_from_handle(commandBuffer);
    size_t cmd_size;
 
+   uint32_t transfer_count;
    pImageMemoryBarriers = vn_cmd_wait_events_fix_image_memory_barriers(
-      cmd, pImageMemoryBarriers, imageMemoryBarrierCount);
+      cmd, pImageMemoryBarriers, imageMemoryBarrierCount, &transfer_count);
+   imageMemoryBarrierCount -= transfer_count;
 
    cmd_size = vn_sizeof_vkCmdWaitEvents(
       commandBuffer, eventCount, pEvents, srcStageMask, dstStageMask,
@@ -1145,6 +1437,12 @@ vn_CmdWaitEvents(VkCommandBuffer commandBuffer,
                              pMemoryBarriers, bufferMemoryBarrierCount,
                              pBufferMemoryBarriers, imageMemoryBarrierCount,
                              pImageMemoryBarriers);
+
+   if (transfer_count) {
+      pImageMemoryBarriers += imageMemoryBarrierCount;
+      vn_cmd_encode_memory_barriers(cmd, srcStageMask, dstStageMask, 0, NULL,
+                                    transfer_count, pImageMemoryBarriers);
+   }
 }
 
 void



More information about the mesa-commit mailing list