vulkan/wsi/x11: document implementation
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>
This commit is contained in:
parent
978ea32acf
commit
6018d5c44a
|
@ -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.
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue