mirror of
https://gitlab.freedesktop.org/mesa/mesa.git
synced 2025-12-30 12:10:09 +01:00
wsi/x11: Implement VK_KHR_present_wait on X11.
Based on original implementation by Keith Packard in https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/12086. Also fixes the implementation to work on IMMEDIATE and MAILBOX presentation modes and Xwayland. Signed-off-by: Hans-Kristian Arntzen <post@arntzen-software.no> Reviewed-by: Joshua Ashton <joshua@froggi.es> Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/19279>
This commit is contained in:
parent
c888da3d48
commit
8fc7927787
1 changed files with 302 additions and 5 deletions
|
|
@ -45,6 +45,7 @@
|
|||
#include "util/u_debug.h"
|
||||
#include "util/u_thread.h"
|
||||
#include "util/xmlconfig.h"
|
||||
#include "util/timespec.h"
|
||||
|
||||
#include "vk_instance.h"
|
||||
#include "vk_physical_device.h"
|
||||
|
|
@ -937,6 +938,7 @@ struct x11_image {
|
|||
xcb_shm_seg_t shmseg;
|
||||
int shmid;
|
||||
uint8_t * shmaddr;
|
||||
uint64_t present_id;
|
||||
};
|
||||
|
||||
struct x11_swapchain {
|
||||
|
|
@ -966,11 +968,55 @@ struct x11_swapchain {
|
|||
struct wsi_queue acquire_queue;
|
||||
pthread_t queue_manager;
|
||||
|
||||
pthread_mutex_t present_id_mutex;
|
||||
pthread_cond_t present_id_cond;
|
||||
pthread_mutex_t present_id_poll_mutex;
|
||||
uint64_t present_id;
|
||||
uint64_t present_id_pending;
|
||||
VkResult present_id_error;
|
||||
|
||||
struct x11_image images[0];
|
||||
};
|
||||
VK_DEFINE_NONDISP_HANDLE_CASTS(x11_swapchain, base.base, VkSwapchainKHR,
|
||||
VK_OBJECT_TYPE_SWAPCHAIN_KHR)
|
||||
|
||||
static void x11_present_complete(struct x11_swapchain *swapchain,
|
||||
struct x11_image *image)
|
||||
{
|
||||
if (image->present_id) {
|
||||
pthread_mutex_lock(&swapchain->present_id_mutex);
|
||||
if (image->present_id > swapchain->present_id) {
|
||||
swapchain->present_id = image->present_id;
|
||||
pthread_cond_broadcast(&swapchain->present_id_cond);
|
||||
}
|
||||
pthread_mutex_unlock(&swapchain->present_id_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
static void x11_notify_pending_present(struct x11_swapchain *swapchain,
|
||||
struct x11_image *image)
|
||||
{
|
||||
if (image->present_id) {
|
||||
pthread_mutex_lock(&swapchain->present_id_mutex);
|
||||
if (image->present_id > swapchain->present_id_pending) {
|
||||
/* Unblock any thread waiting for a presentID out of order. */
|
||||
swapchain->present_id_pending = image->present_id;
|
||||
pthread_cond_broadcast(&swapchain->present_id_cond);
|
||||
}
|
||||
pthread_mutex_unlock(&swapchain->present_id_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
static void x11_swapchain_notify_error(struct x11_swapchain *swapchain, VkResult result)
|
||||
{
|
||||
pthread_mutex_lock(&swapchain->present_id_mutex);
|
||||
swapchain->present_id = UINT64_MAX;
|
||||
swapchain->present_id_pending = UINT64_MAX;
|
||||
swapchain->present_id_error = result;
|
||||
pthread_cond_broadcast(&swapchain->present_id_cond);
|
||||
pthread_mutex_unlock(&swapchain->present_id_mutex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the swapchain status with the result of an operation, and return
|
||||
* the combined status. The chain status will eventually be returned from
|
||||
|
|
@ -984,6 +1030,9 @@ static VkResult
|
|||
_x11_swapchain_result(struct x11_swapchain *chain, VkResult result,
|
||||
const char *file, int line)
|
||||
{
|
||||
if (result < 0)
|
||||
x11_swapchain_notify_error(chain, result);
|
||||
|
||||
/* Prioritise returning existing errors for consistency. */
|
||||
if (chain->status < 0)
|
||||
return chain->status;
|
||||
|
|
@ -1070,8 +1119,10 @@ x11_handle_dri3_present_event(struct x11_swapchain *chain,
|
|||
unsigned i;
|
||||
for (i = 0; i < chain->base.image_count; i++) {
|
||||
struct x11_image *image = &chain->images[i];
|
||||
if (image->present_queued && image->serial == complete->serial)
|
||||
if (image->present_queued && image->serial == complete->serial) {
|
||||
x11_present_complete(chain, &chain->images[i]);
|
||||
image->present_queued = false;
|
||||
}
|
||||
}
|
||||
chain->last_present_msc = complete->msc;
|
||||
}
|
||||
|
|
@ -1359,9 +1410,18 @@ static VkResult
|
|||
x11_present_to_x11(struct x11_swapchain *chain, uint32_t image_index,
|
||||
uint64_t target_msc)
|
||||
{
|
||||
VkResult result;
|
||||
if (chain->base.wsi->sw && !chain->has_mit_shm)
|
||||
return x11_present_to_x11_sw(chain, image_index, target_msc);
|
||||
return x11_present_to_x11_dri3(chain, image_index, target_msc);
|
||||
result = x11_present_to_x11_sw(chain, image_index, target_msc);
|
||||
else
|
||||
result = x11_present_to_x11_dri3(chain, image_index, target_msc);
|
||||
|
||||
if (result < 0)
|
||||
x11_swapchain_notify_error(chain, result);
|
||||
else
|
||||
x11_notify_pending_present(chain, &chain->images[image_index]);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1410,7 +1470,10 @@ x11_acquire_next_image(struct wsi_swapchain *anv_chain,
|
|||
if (chain->has_acquire_queue) {
|
||||
return x11_acquire_next_image_from_queue(chain, image_index, timeout);
|
||||
} else {
|
||||
return x11_acquire_next_image_poll_x11(chain, image_index, timeout);
|
||||
pthread_mutex_lock(&chain->present_id_poll_mutex);
|
||||
VkResult result = x11_acquire_next_image_poll_x11(chain, image_index, timeout);
|
||||
pthread_mutex_unlock(&chain->present_id_poll_mutex);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1452,6 +1515,7 @@ x11_queue_present(struct wsi_swapchain *anv_chain,
|
|||
xcb_xfixes_set_region(chain->conn, update_area, damage->rectangleCount, rects);
|
||||
}
|
||||
chain->images[image_index].update_area = update_area;
|
||||
chain->images[image_index].present_id = present_id;
|
||||
|
||||
chain->images[image_index].busy = true;
|
||||
if (chain->has_present_queue) {
|
||||
|
|
@ -1459,7 +1523,10 @@ x11_queue_present(struct wsi_swapchain *anv_chain,
|
|||
return chain->status;
|
||||
} else {
|
||||
/* No present queue means immedate mode, so we present immediately. */
|
||||
return x11_present_to_x11(chain, image_index, 0);
|
||||
pthread_mutex_lock(&chain->present_id_poll_mutex);
|
||||
VkResult result = x11_present_to_x11(chain, image_index, 0);
|
||||
pthread_mutex_unlock(&chain->present_id_poll_mutex);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1571,7 +1638,15 @@ x11_manage_fifo_queues(void *state)
|
|||
if (chain->has_acquire_queue)
|
||||
target_msc = chain->last_present_msc + 1;
|
||||
|
||||
/* Locking here is only relevant if we don't have an acquire queue.
|
||||
* WaitForPresentKHR will pump the message queue on its own unless
|
||||
* has_acquire_queue and has_present_queue are both true. */
|
||||
if (!chain->has_acquire_queue)
|
||||
pthread_mutex_lock(&chain->present_id_poll_mutex);
|
||||
result = x11_present_to_x11(chain, image_index, target_msc);
|
||||
if (!chain->has_acquire_queue)
|
||||
pthread_mutex_unlock(&chain->present_id_poll_mutex);
|
||||
|
||||
if (result < 0)
|
||||
goto fail;
|
||||
|
||||
|
|
@ -1923,6 +1998,10 @@ x11_swapchain_destroy(struct wsi_swapchain *anv_chain,
|
|||
XCB_PRESENT_EVENT_MASK_NO_EVENT);
|
||||
xcb_discard_reply(chain->conn, cookie.sequence);
|
||||
|
||||
pthread_mutex_destroy(&chain->present_id_poll_mutex);
|
||||
pthread_mutex_destroy(&chain->present_id_mutex);
|
||||
pthread_cond_destroy(&chain->present_id_cond);
|
||||
|
||||
wsi_swapchain_finish(&chain->base);
|
||||
|
||||
vk_free(pAllocator, chain);
|
||||
|
|
@ -1956,6 +2035,202 @@ wsi_x11_set_adaptive_sync_property(xcb_connection_t *conn,
|
|||
free(reply);
|
||||
}
|
||||
|
||||
static VkResult x11_wait_for_present_queued(
|
||||
struct x11_swapchain *chain,
|
||||
uint64_t waitValue, uint64_t timeout)
|
||||
{
|
||||
struct timespec abs_timespec;
|
||||
uint64_t abs_timeout = 0;
|
||||
if (timeout != 0)
|
||||
abs_timeout = os_time_get_absolute_timeout(timeout);
|
||||
|
||||
/* Need to observe that the swapchain semaphore has been unsignalled,
|
||||
* as this is guaranteed when a present is complete. */
|
||||
VkResult result = wsi_swapchain_wait_for_present_semaphore(
|
||||
&chain->base, waitValue, timeout);
|
||||
if (result != VK_SUCCESS)
|
||||
return result;
|
||||
|
||||
timespec_from_nsec(&abs_timespec, abs_timeout);
|
||||
|
||||
pthread_mutex_lock(&chain->present_id_mutex);
|
||||
while (chain->present_id < waitValue) {
|
||||
int ret = pthread_cond_timedwait(&chain->present_id_cond,
|
||||
&chain->present_id_mutex,
|
||||
&abs_timespec);
|
||||
if (ret == ETIMEDOUT) {
|
||||
result = VK_TIMEOUT;
|
||||
break;
|
||||
}
|
||||
if (ret) {
|
||||
result = VK_ERROR_DEVICE_LOST;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (result == VK_SUCCESS && chain->present_id_error)
|
||||
result = chain->present_id_error;
|
||||
pthread_mutex_unlock(&chain->present_id_mutex);
|
||||
return result;
|
||||
}
|
||||
|
||||
static VkResult x11_wait_for_present_polled(
|
||||
struct x11_swapchain *chain,
|
||||
uint64_t waitValue, uint64_t timeout)
|
||||
{
|
||||
struct timespec rel_timeout, abs_timespec_realtime, start_time;
|
||||
struct timespec abs_timespec_monotonic;
|
||||
uint64_t abs_timeout_monotonic = 0;
|
||||
|
||||
if (timeout != 0)
|
||||
abs_timeout_monotonic = os_time_get_absolute_timeout(timeout);
|
||||
|
||||
/* Mutex abs_timeout is in REALTIME timebase. */
|
||||
timespec_from_nsec(&rel_timeout, timeout);
|
||||
clock_gettime(CLOCK_REALTIME, &start_time);
|
||||
timespec_add(&abs_timespec_realtime, &rel_timeout, &start_time);
|
||||
|
||||
/* Need to observe that the swapchain semaphore has been unsignalled,
|
||||
* as this is guaranteed when a present is complete. */
|
||||
VkResult result = wsi_swapchain_wait_for_present_semaphore(
|
||||
&chain->base, waitValue, timeout);
|
||||
if (result != VK_SUCCESS)
|
||||
return result;
|
||||
|
||||
/* If we have satisfied the present ID right away, just return early. */
|
||||
pthread_mutex_lock(&chain->present_id_mutex);
|
||||
if (chain->present_id >= waitValue) {
|
||||
result = chain->present_id_error;
|
||||
} else {
|
||||
result = VK_TIMEOUT;
|
||||
}
|
||||
|
||||
if (result != VK_TIMEOUT) {
|
||||
pthread_mutex_unlock(&chain->present_id_mutex);
|
||||
return result;
|
||||
}
|
||||
|
||||
timespec_from_nsec(&abs_timespec_monotonic, abs_timeout_monotonic);
|
||||
|
||||
/* In a situation of wait-before-signal, we need to ensure that a presentID of at least
|
||||
* waitValue has been submitted before we're allowed to lock the XCB connection.
|
||||
* Even if the user does not use wait-before-signal we can still hit this scenario on Xwayland
|
||||
* where we have a present queue, but no acquire queue. We need to observe that the present queue
|
||||
* has actually submitted the present to XCB before we're guaranteed forward progress. */
|
||||
while (chain->present_id_pending < waitValue) {
|
||||
int ret = pthread_cond_timedwait(&chain->present_id_cond,
|
||||
&chain->present_id_mutex,
|
||||
&abs_timespec_monotonic);
|
||||
if (chain->present_id_error || ret == ETIMEDOUT || ret) {
|
||||
pthread_mutex_unlock(&chain->present_id_mutex);
|
||||
|
||||
if (chain->present_id_error)
|
||||
return chain->present_id_error;
|
||||
else if (ret == ETIMEDOUT)
|
||||
return VK_TIMEOUT;
|
||||
else
|
||||
return VK_ERROR_DEVICE_LOST;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&chain->present_id_mutex);
|
||||
|
||||
/* This scheme of taking the message queue lock is not optimal,
|
||||
* but it is only problematic in meaningless situations.
|
||||
* - This path can only be hit by IMMEDIATE or MAILBOX mode.
|
||||
* Using present wait for IMMEDIATE and MAILBOX is not particularly useful except
|
||||
* for safe teardown purposes and recycling semaphores.
|
||||
* - There is contention with multiple threads waiting for PresentWait,
|
||||
* where the first thread to wait is blocking with no timeout and hogs the message queue until
|
||||
* that present is processed. */
|
||||
int ret;
|
||||
if (timeout == UINT64_MAX)
|
||||
ret = pthread_mutex_lock(&chain->present_id_poll_mutex);
|
||||
else
|
||||
ret = pthread_mutex_timedlock(&chain->present_id_poll_mutex, &abs_timespec_realtime);
|
||||
|
||||
if (ret) {
|
||||
if (ret == ETIMEDOUT)
|
||||
return VK_TIMEOUT;
|
||||
else
|
||||
return VK_ERROR_DEVICE_LOST;
|
||||
}
|
||||
|
||||
result = chain->present_id_error;
|
||||
|
||||
while (result == VK_SUCCESS && chain->present_id < waitValue) {
|
||||
xcb_generic_event_t *event;
|
||||
xcb_flush(chain->conn);
|
||||
|
||||
if (timeout == UINT64_MAX) {
|
||||
event = xcb_wait_for_special_event(chain->conn, chain->special_event);
|
||||
if (!event) {
|
||||
result = x11_swapchain_result(chain, VK_ERROR_SURFACE_LOST_KHR);
|
||||
goto fail;
|
||||
}
|
||||
} else {
|
||||
event = xcb_poll_for_special_event(chain->conn, chain->special_event);
|
||||
if (!event) {
|
||||
if (timeout == 0) {
|
||||
result = x11_swapchain_result(chain, VK_TIMEOUT);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
uint64_t current_time = os_time_get_nano();
|
||||
if (abs_timeout_monotonic > current_time)
|
||||
timeout = abs_timeout_monotonic - current_time;
|
||||
else
|
||||
timeout = 0;
|
||||
|
||||
struct pollfd pfds;
|
||||
pfds.fd = xcb_get_file_descriptor(chain->conn);
|
||||
pfds.events = POLLIN;
|
||||
ret = poll(&pfds, 1, timeout / 1000 / 1000);
|
||||
|
||||
if (ret == 0) {
|
||||
result = x11_swapchain_result(chain, VK_TIMEOUT);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (ret == -1) {
|
||||
result = x11_swapchain_result(chain, VK_ERROR_OUT_OF_DATE_KHR);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (event) {
|
||||
result = x11_handle_dri3_present_event(chain, (void *)event);
|
||||
/* Ensure that VK_SUBOPTIMAL_KHR is reported to the application */
|
||||
result = x11_swapchain_result(chain, result);
|
||||
free(event);
|
||||
}
|
||||
}
|
||||
|
||||
fail:
|
||||
pthread_mutex_unlock(&chain->present_id_poll_mutex);
|
||||
return result;
|
||||
}
|
||||
|
||||
static VkResult x11_wait_for_present(struct wsi_swapchain *wsi_chain,
|
||||
uint64_t waitValue,
|
||||
uint64_t timeout)
|
||||
{
|
||||
struct x11_swapchain *chain = (struct x11_swapchain *)wsi_chain;
|
||||
VkResult result;
|
||||
|
||||
if (chain->has_present_queue && chain->has_acquire_queue) {
|
||||
/* In this style we have guaranteed forward progress in the present queue thread,
|
||||
* so we don't need to do anything.
|
||||
* This path is hit for FIFO presentation modes. */
|
||||
result = x11_wait_for_present_queued(chain, waitValue, timeout);
|
||||
} else {
|
||||
/* In this style we don't necessarily have forward progress, so we need to pump the message queue ourselves.
|
||||
* This blocks the message queue for other threads that want to present.
|
||||
* In practice, we'll only end up blocking on swapchain teardown, so this isn't a big deal. */
|
||||
result = x11_wait_for_present_polled(chain, waitValue, timeout);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the swapchain.
|
||||
*
|
||||
|
|
@ -2021,6 +2296,27 @@ x11_surface_create_swapchain(VkIcdSurfaceBase *icd_surface,
|
|||
if (chain == NULL)
|
||||
return VK_ERROR_OUT_OF_HOST_MEMORY;
|
||||
|
||||
int ret = pthread_mutex_init(&chain->present_id_mutex, NULL);
|
||||
if (ret != 0) {
|
||||
vk_free(pAllocator, chain);
|
||||
return VK_ERROR_OUT_OF_HOST_MEMORY;
|
||||
}
|
||||
|
||||
ret = pthread_mutex_init(&chain->present_id_poll_mutex, NULL);
|
||||
if (ret != 0) {
|
||||
pthread_mutex_destroy(&chain->present_id_mutex);
|
||||
vk_free(pAllocator, chain);
|
||||
return VK_ERROR_OUT_OF_HOST_MEMORY;
|
||||
}
|
||||
|
||||
bool bret = wsi_init_pthread_cond_monotonic(&chain->present_id_cond);
|
||||
if (!bret) {
|
||||
pthread_mutex_destroy(&chain->present_id_mutex);
|
||||
pthread_mutex_destroy(&chain->present_id_poll_mutex);
|
||||
vk_free(pAllocator, chain);
|
||||
return VK_ERROR_OUT_OF_HOST_MEMORY;
|
||||
}
|
||||
|
||||
struct wsi_base_image_params *image_params = NULL;
|
||||
struct wsi_cpu_image_params cpu_image_params;
|
||||
struct wsi_drm_image_params drm_image_params;
|
||||
|
|
@ -2062,6 +2358,7 @@ x11_surface_create_swapchain(VkIcdSurfaceBase *icd_surface,
|
|||
chain->base.get_wsi_image = x11_get_wsi_image;
|
||||
chain->base.acquire_next_image = x11_acquire_next_image;
|
||||
chain->base.queue_present = x11_queue_present;
|
||||
chain->base.wait_for_present = x11_wait_for_present;
|
||||
chain->base.present_mode = present_mode;
|
||||
chain->base.image_count = num_images;
|
||||
chain->conn = conn;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue