vulkan/wsi/x11: document implementation
authorRoman Gilg <subdiff@gmail.com>
Mon, 31 Aug 2020 00:35:57 +0000 (02:35 +0200)
committerMarge Bot <emma+marge@anholt.net>
Thu, 27 Jan 2022 09:14:29 +0000 (09:14 +0000)
To extend the shared understanding of our code base and ease contributing
document purpose and flow for many of our internal functions.

Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/6536>

src/vulkan/wsi/wsi_common_x11.c

index 921ad86..d7281eb 100644 (file)
@@ -75,9 +75,8 @@ struct wsi_x11 {
 };
 
 
-/** wsi_dri3_open
- *
- * Wrapper around xcb_dri3_open
+/**
+ * Wrapper around xcb_dri3_open. Returns the opened fd or -1 on error.
  */
 static int
 wsi_dri3_open(xcb_connection_t *conn,
@@ -96,6 +95,7 @@ wsi_dri3_open(xcb_connection_t *conn,
    if (!reply)
       return -1;
 
+   /* According to DRI3 extension nfd must equal one. */
    if (reply->nfd != 1) {
       free(reply);
       return -1;
@@ -108,6 +108,13 @@ wsi_dri3_open(xcb_connection_t *conn,
    return fd;
 }
 
+/**
+ * Checks compatibility of the device wsi_dev with the device the X server
+ * provides via DRI3.
+ *
+ * This returns true when no device could be retrieved from the X server or when
+ * the information for the X server device indicate that it is the same device.
+ */
 static bool
 wsi_x11_check_dri3_compatible(const struct wsi_device *wsi_dev,
                               xcb_connection_t *conn)
@@ -116,6 +123,9 @@ wsi_x11_check_dri3_compatible(const struct wsi_device *wsi_dev,
       xcb_setup_roots_iterator(xcb_get_setup(conn));
    xcb_screen_t *screen = screen_iter.data;
 
+   /* Open the DRI3 device from the X server. If we do not retrieve one we
+    * assume our local device is compatible.
+    */
    int dri3_fd = wsi_dri3_open(conn, screen->root, None);
    if (dri3_fd == -1)
       return true;
@@ -322,6 +332,14 @@ wsi_x11_check_for_dri3(struct wsi_x11_connection *wsi_conn)
   return false;
 }
 
+/**
+ * Get internal struct representing an xcb_connection_t.
+ *
+ * This can allocate the struct but the caller does not own the struct. It is
+ * deleted on wsi_x11_finish_wsi by the hash table it is inserted.
+ *
+ * If the allocation fails NULL is returned.
+ */
 static struct wsi_x11_connection *
 wsi_x11_get_connection(struct wsi_device *wsi_dev,
                        xcb_connection_t *conn)
@@ -1059,6 +1077,10 @@ static uint64_t wsi_get_absolute_timeout(uint64_t timeout)
    return current_time + timeout;
 }
 
+/**
+ * Acquire a ready-to-use image directly from our swapchain. If all images are
+ * busy wait until one is not anymore or till timeout.
+ */
 static VkResult
 x11_acquire_next_image_poll_x11(struct x11_swapchain *chain,
                                 uint32_t *image_index, uint64_t timeout)
@@ -1124,6 +1146,10 @@ x11_acquire_next_image_poll_x11(struct x11_swapchain *chain,
    }
 }
 
+/**
+ * Acquire a ready-to-use image from the acquire-queue. Only relevant in fifo
+ * presentation mode.
+ */
 static VkResult
 x11_acquire_next_image_from_queue(struct x11_swapchain *chain,
                                   uint32_t *image_index_out, uint64_t timeout)
@@ -1151,6 +1177,9 @@ x11_acquire_next_image_from_queue(struct x11_swapchain *chain,
    return chain->status;
 }
 
+/**
+ * Send image to X server via Present extension.
+ */
 static VkResult
 x11_present_to_x11_dri3(struct x11_swapchain *chain, uint32_t image_index,
                         uint64_t target_msc)
@@ -1226,6 +1255,9 @@ x11_present_to_x11_dri3(struct x11_swapchain *chain, uint32_t image_index,
    return x11_swapchain_result(chain, VK_SUCCESS);
 }
 
+/**
+ * Send image to X server unaccelerated (software drivers).
+ */
 static VkResult
 x11_present_to_x11_sw(struct x11_swapchain *chain, uint32_t image_index,
                       uint64_t target_msc)
@@ -1252,6 +1284,10 @@ x11_present_to_x11_sw(struct x11_swapchain *chain, uint32_t image_index,
    xcb_flush(chain->conn);
    return x11_swapchain_result(chain, VK_SUCCESS);
 }
+
+/**
+ * Send image to the X server for presentation at target_msc.
+ */
 static VkResult
 x11_present_to_x11(struct x11_swapchain *chain, uint32_t image_index,
                    uint64_t target_msc)
@@ -1261,6 +1297,12 @@ x11_present_to_x11(struct x11_swapchain *chain, uint32_t image_index,
    return x11_present_to_x11_dri3(chain, image_index, target_msc);
 }
 
+/**
+ * Acquire a ready-to-use image from the swapchain.
+ *
+ * This means usually that the image is not waiting on presentation and that the
+ * image has been released by the X server to be used again by the consumer.
+ */
 static VkResult
 x11_acquire_next_image(struct wsi_swapchain *anv_chain,
                        const VkAcquireNextImageInfoKHR *info,
@@ -1273,10 +1315,12 @@ x11_acquire_next_image(struct wsi_swapchain *anv_chain,
    if (chain->status < 0)
       return chain->status;
 
+   /* For software drivers and without shared memory we only render to a single image. */
    if (chain->base.wsi->sw && !chain->has_mit_shm) {
       *image_index = 0;
       return VK_SUCCESS;
    }
+
    if (chain->has_acquire_queue) {
       return x11_acquire_next_image_from_queue(chain, image_index, timeout);
    } else {
@@ -1284,6 +1328,13 @@ x11_acquire_next_image(struct wsi_swapchain *anv_chain,
    }
 }
 
+/**
+ * Queue a new presentation of an image that was previously acquired by the
+ * consumer.
+ *
+ * Note that in immediate presentation mode this does not really queue the
+ * presentation but directly asks the X server to show it.
+ */
 static VkResult
 x11_queue_present(struct wsi_swapchain *anv_chain,
                   uint32_t image_index,
@@ -1300,10 +1351,22 @@ x11_queue_present(struct wsi_swapchain *anv_chain,
       wsi_queue_push(&chain->present_queue, image_index);
       return chain->status;
    } else {
+      /* No present queue means immedate mode, so we present immediately. */
       return x11_present_to_x11(chain, image_index, 0);
    }
 }
 
+/**
+ * Decides if an early wait on buffer fences before buffer submission is required. That is for:
+ *   - Mailbox mode, as otherwise the latest image in the queue might not be fully rendered at
+ *     present time, what could lead to missing a frame.
+ *   - Immediate mode under Xwayland, as it works practically the same as mailbox mode using the
+ *     mailbox mechanism of Wayland. Sending a buffer with fences not yet signalled can make the
+ *     compositor miss a frame when compositing the final image with this buffer.
+ *
+ * Note though that early waits can be disabled in general on Xwayland by setting the
+ * 'vk_xwayland_wait_ready' DRIConf option to false.
+ */
 static bool
 x11_needs_wait_for_fences(const struct wsi_device *wsi_device,
                           struct wsi_x11_connection *wsi_conn,
@@ -1323,6 +1386,21 @@ x11_needs_wait_for_fences(const struct wsi_device *wsi_device,
    }
 }
 
+/**
+ * Our queue manager. Albeit called x11_manage_fifo_queues only directly
+ * manages the present-queue and does this in general in fifo and mailbox presentation
+ * modes (there is no present-queue in immediate mode with the exception of Xwayland).
+ *
+ * Runs in a separate thread, blocks and reacts to queued images on the
+ * present-queue
+ *
+ * In mailbox mode the queue management is simplified since we only need to
+ * pull new images from the present queue and can directly present them.
+ *
+ * In fifo mode images can only be presented one after the other. For that after
+ * sending the image to the X server we wait until the image either has been
+ * presented or released and only then pull a new image from the present-queue.
+ */
 static void *
 x11_manage_fifo_queues(void *state)
 {
@@ -1343,6 +1421,7 @@ x11_manage_fifo_queues(void *state)
       uint32_t image_index = 0;
       result = wsi_queue_pull(&chain->present_queue, &image_index, INT64_MAX);
       assert(result != VK_TIMEOUT);
+
       if (result < 0) {
          goto fail;
       } else if (chain->status < 0) {
@@ -1352,6 +1431,9 @@ x11_manage_fifo_queues(void *state)
          return NULL;
       }
 
+      /* Waiting for the GPU work to finish at this point in time is required in certain usage
+       * scenarios. Otherwise we wait as usual in wsi_common_queue_present.
+       */
       if (x11_needs_wait_for_fences(chain->base.wsi, wsi_conn,
                                     chain->base.present_mode)) {
          result = chain->base.wsi->WaitForFences(chain->base.device, 1,
@@ -1716,7 +1798,12 @@ wsi_x11_set_adaptive_sync_property(xcb_connection_t *conn,
    free(reply);
 }
 
-
+/**
+ * Create the swapchain.
+ *
+ * Supports immediate, fifo and mailbox presentation mode.
+ *
+ */
 static VkResult
 x11_surface_create_swapchain(VkIcdSurfaceBase *icd_surface,
                              VkDevice device,
@@ -1732,12 +1819,20 @@ x11_surface_create_swapchain(VkIcdSurfaceBase *icd_surface,
 
    assert(pCreateInfo->sType == VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR);
 
+   /* Get xcb connection from the icd_surface and from that our internal struct
+    * representing it.
+    */
    xcb_connection_t *conn = x11_surface_get_connection(icd_surface);
    struct wsi_x11_connection *wsi_conn =
       wsi_x11_get_connection(wsi_device, conn);
    if (!wsi_conn)
       return VK_ERROR_OUT_OF_HOST_MEMORY;
 
+   /* Get number of images in our swapchain. This count depends on:
+    * - requested minimal image count
+    * - device characteristics
+    * - presentation mode.
+    */
    unsigned num_images = pCreateInfo->minImageCount;
    if (wsi_device->x11.strict_imageCount)
       num_images = pCreateInfo->minImageCount;
@@ -1746,8 +1841,12 @@ x11_surface_create_swapchain(VkIcdSurfaceBase *icd_surface,
    else if (wsi_device->x11.ensure_minImageCount)
       num_images = MAX2(num_images, x11_get_min_image_count(wsi_device));
 
-   /* Check for whether or not we have a window up-front */
+   /* Check that we have a window up-front. It is an error to not have one. */
    xcb_window_t window = x11_surface_get_window(icd_surface);
+
+   /* Get the geometry of that window. The bit depth of the swapchain will be fitted and the
+    * chain's images extents should fit it for performance-optimizing flips.
+    */
    xcb_get_geometry_reply_t *geometry =
       xcb_get_geometry_reply(conn, xcb_get_geometry(conn, window), NULL);
    if (geometry == NULL)
@@ -1757,12 +1856,16 @@ x11_surface_create_swapchain(VkIcdSurfaceBase *icd_surface,
    const uint16_t cur_height = geometry->height;
    free(geometry);
 
+   /* Allocate the actual swapchain. The size depends on image count. */
    size_t size = sizeof(*chain) + num_images * sizeof(chain->images[0]);
    chain = vk_zalloc(pAllocator, size, 8,
                       VK_SYSTEM_ALLOCATION_SCOPE_OBJECT);
    if (chain == NULL)
       return VK_ERROR_OUT_OF_HOST_MEMORY;
 
+   /* When our local device is not compatible with the DRI3 device provided by
+    * the X server we assume this is a PRIME system.
+    */
    bool use_prime_blit = false;
    if (!wsi_device->sw)
       if (!wsi_x11_check_dri3_compatible(wsi_device, conn))
@@ -1792,19 +1895,39 @@ x11_surface_create_swapchain(VkIcdSurfaceBase *icd_surface,
    chain->has_dri3_modifiers = wsi_conn->has_dri3_modifiers;
    chain->has_mit_shm = wsi_conn->has_mit_shm;
 
+   /* When images in the swapchain don't fit the window, X can still present them, but it won't
+    * happen by flip, only by copy. So this is a suboptimal copy, because if the client would change
+    * the chain extents X may be able to flip
+    */
    if (chain->extent.width != cur_width || chain->extent.height != cur_height)
        chain->status = VK_SUBOPTIMAL_KHR;
 
-   /* We used to inherit copy_is_suboptimal from pCreateInfo->oldSwapchain.
-    * When it was true, and when the next present was completed with copying,
-    * we would return VK_SUBOPTIMAL_KHR and hint the app to reallocate again
-    * for no good reason.  If all following presents on the surface were
-    * completed with copying because of some surface state change, we would
-    * always return VK_SUBOPTIMAL_KHR no matter how many times the app had
-    * reallocated.
+   /* On a new swapchain this helper variable is set to false. Once we present it will have an
+    * impact once we ever do at least one flip and go back to copying afterwards. It is presumed
+    * that in this case here is a high likelihood X could do flips again if the client reallocates a
+    * new swapchain.
+    *
+    * Note that we used to inheritted this property from 'pCreateInfo->oldSwapchain'. But when it
+    * was true, and when the next present was completed with copying, we would return
+    * VK_SUBOPTIMAL_KHR and hint the app to reallocate again for no good reason. If all following
+    * presents on the surface were completed with copying because of some surface state change, we
+    * would always return VK_SUBOPTIMAL_KHR no matter how many times the app had reallocated.
+    *
+    * Note also that is is questionable in general if that mechanism is really useful. It ist not
+    * clear why on a change from flipping to copying we can assume a reallocation has a high chance
+    * of making flips work again per se. In other words it is not clear why there is need for
+    * another way to inform clients about suboptimal copies besides forwarding the
+    * 'PresentOptionSuboptimal' complete mode.
     */
    chain->copy_is_suboptimal = false;
 
+   /* For our swapchain we need to listen to following Present extension events:
+    * - Configure: Window dimensions changed. Images in the swapchain might need
+    *              to be reallocated.
+    * - Complete: An image from our swapchain was presented on the output.
+    * - Idle: An image from our swapchain is not anymore accessed by the X
+    *         server and can be reused.
+    */
    chain->event_id = xcb_generate_id(chain->conn);
    xcb_present_select_input(chain->conn, chain->event_id, chain->window,
                             XCB_PRESENT_EVENT_MASK_CONFIGURE_NOTIFY |
@@ -1818,6 +1941,7 @@ x11_surface_create_swapchain(VkIcdSurfaceBase *icd_surface,
       xcb_register_for_special_xge(chain->conn, &xcb_present_id,
                                    chain->event_id, NULL);
 
+   /* Create the graphics context. */
    chain->gc = xcb_generate_id(chain->conn);
    if (!chain->gc) {
       /* FINISHME: Choose a better error. */
@@ -1851,6 +1975,17 @@ x11_surface_create_swapchain(VkIcdSurfaceBase *icd_surface,
          goto fail_init_images;
    }
 
+   /* Initialize queues for images in our swapchain. Possible queues are:
+    * - Present queue: for images sent to the X server but not yet presented.
+    * - Acquire queue: for images already presented but not yet released by the
+    *                  X server.
+    *
+    * In general queues are not used on software drivers, otherwise which queues
+    * are used depends on our presentation mode:
+    * - Fifo: present and acquire
+    * - Mailbox: present only
+    * - Immediate: present when we wait on fences before buffer submission (Xwayland)
+    */
    if ((chain->base.present_mode == VK_PRESENT_MODE_FIFO_KHR ||
         chain->base.present_mode == VK_PRESENT_MODE_FIFO_RELAXED_KHR ||
         x11_needs_wait_for_fences(wsi_device, wsi_conn,
@@ -1858,7 +1993,7 @@ x11_surface_create_swapchain(VkIcdSurfaceBase *icd_surface,
        !chain->base.wsi->sw) {
       chain->has_present_queue = true;
 
-      /* Initialize our queues.  We make them base.image_count + 1 because we will
+      /* The queues have a length of base.image_count + 1 because we will
        * occasionally use UINT32_MAX to signal the other thread that an error
        * has occurred and we don't want an overflow.
        */