wsi/wl: Improve fallback for present_wait.

When presentation feedback protocol is not supported,
fallback to using frame callbacks.
In some sense, frame callbacks functions like
present complete + latch delay, so it's a reasonable approach, given the
alternative.

Xwl uses frame callback for COMPLETE events, so it's not a new approach.

To guard against lack of forward progress guarantee,
add a timeout for present complete to avoid deadlocking applications.

Signed-off-by: Hans-Kristian Arntzen <post@arntzen-software.no>
Reviewed-by: Joshua Ashton <joshua@froggi.es>
Reviewed-by: Sebastian Wick <sebastian.wick@redhat.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/27275>
This commit is contained in:
Hans-Kristian Arntzen
2024-01-25 16:16:04 +01:00
committed by Marge Bot
parent 1af7f41174
commit 0d51cd4808

View File

@@ -183,6 +183,8 @@ struct wsi_wl_swapchain {
pthread_cond_t list_advanced;
struct wl_event_queue *queue;
struct wp_presentation *wp_presentation;
/* Fallback when wp_presentation is not supported */
struct wl_surface *surface;
bool dispatch_in_progress;
} present_ids;
@@ -1631,6 +1633,12 @@ wsi_CreateWaylandSurfaceKHR(VkInstance _instance,
struct wsi_wl_present_id {
struct wp_presentation_feedback *feedback;
/* Fallback when wp_presentation is not supported.
* Using frame callback is not the intended way to achieve
* this, but it is the best effort alternative when the proper interface is
* not available. This approach also matches Xwayland,
* which uses frame callback to signal DRI3 COMPLETE. */
struct wl_callback *frame;
uint64_t present_id;
const VkAllocationCallbacks *alloc;
struct wsi_wl_swapchain *chain;
@@ -1684,8 +1692,6 @@ wsi_wl_swapchain_wait_for_present(struct wsi_swapchain *wsi_chain,
else
atimeout = os_time_get_absolute_timeout(timeout);
timespec_from_nsec(&end_time, atimeout);
/* 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(
@@ -1693,12 +1699,23 @@ wsi_wl_swapchain_wait_for_present(struct wsi_swapchain *wsi_chain,
if (result != VK_SUCCESS)
return result;
if (!chain->present_ids.wp_presentation) {
/* If we're enabling present wait despite the protocol not being supported,
* use best effort not to crash, even if result will not be correct.
* For correctness, we must at least wait for the timeline semaphore to complete. */
return VK_SUCCESS;
}
/* If using frame callback, guard against lack of forward progress
* of the frame callback in some situations,
* e.g. the surface might not be visible.
* If rendering has completed on GPU,
* and we still haven't received a callback after 100ms, unblock the application.
* 100ms is chosen arbitrarily.
* The queue depth in WL WSI is just one frame due to frame callback in FIFO mode,
* so from the time a frame has completed render to when it should be considered presented
* will not exceed 100ms except in contrived edge cases. */
uint64_t assumed_success_at = UINT64_MAX;
if (!chain->present_ids.wp_presentation)
assumed_success_at = os_time_get_absolute_timeout(100 * 1000 * 1000);
/* If app timeout is beyond the deadline we set for reply,
* always treat the timeout as successful. */
VkResult timeout_result = assumed_success_at < atimeout ? VK_SUCCESS : VK_TIMEOUT;
timespec_from_nsec(&end_time, MIN2(atimeout, assumed_success_at));
/* PresentWait can be called concurrently.
* If there is contention on this mutex, it means there is currently a dispatcher in flight holding the lock.
@@ -1724,7 +1741,7 @@ wsi_wl_swapchain_wait_for_present(struct wsi_swapchain *wsi_chain,
if (err == ETIMEDOUT) {
pthread_mutex_unlock(&chain->present_ids.lock);
return VK_TIMEOUT;
return timeout_result;
} else if (err != 0) {
pthread_mutex_unlock(&chain->present_ids.lock);
return VK_ERROR_OUT_OF_DATE_KHR;
@@ -1772,7 +1789,7 @@ wsi_wl_swapchain_wait_for_present(struct wsi_swapchain *wsi_chain,
* if we're over our budget. */
uint64_t current_time_nsec = os_time_get_nano();
if (current_time_nsec > atimeout) {
ret = VK_TIMEOUT;
ret = timeout_result;
goto relinquish_dispatch;
}
@@ -1960,6 +1977,18 @@ static const struct wp_presentation_feedback_listener
presentation_handle_discarded,
};
static void
presentation_frame_handle_done(void *data, struct wl_callback *callback, uint32_t serial)
{
struct wsi_wl_present_id *id = data;
wsi_wl_presentation_update_present_id(id);
wl_callback_destroy(callback);
}
static const struct wl_callback_listener pres_frame_listener = {
presentation_frame_handle_done,
};
static void
frame_handle_done(void *data, struct wl_callback *callback, uint32_t serial)
{
@@ -2024,7 +2053,7 @@ wsi_wl_swapchain_queue_present(struct wsi_swapchain *wsi_chain,
chain->fifo_ready = true;
}
if (present_id > 0 && chain->present_ids.wp_presentation) {
if (present_id > 0) {
struct wsi_wl_present_id *id =
vk_zalloc(chain->wsi_wl_surface->display->wsi_wl->alloc, sizeof(*id), sizeof(uintptr_t),
VK_SYSTEM_ALLOCATION_SCOPE_OBJECT);
@@ -2033,11 +2062,18 @@ wsi_wl_swapchain_queue_present(struct wsi_swapchain *wsi_chain,
id->alloc = chain->wsi_wl_surface->display->wsi_wl->alloc;
pthread_mutex_lock(&chain->present_ids.lock);
id->feedback = wp_presentation_feedback(chain->present_ids.wp_presentation,
chain->wsi_wl_surface->surface);
wp_presentation_feedback_add_listener(id->feedback,
&pres_feedback_listener,
id);
if (chain->present_ids.wp_presentation) {
id->feedback = wp_presentation_feedback(chain->present_ids.wp_presentation,
chain->wsi_wl_surface->surface);
wp_presentation_feedback_add_listener(id->feedback,
&pres_feedback_listener,
id);
} else {
id->frame = wl_surface_frame(chain->present_ids.surface);
wl_callback_add_listener(id->frame, &pres_frame_listener, id);
}
wl_list_insert(&chain->present_ids.outstanding_list, &id->link);
pthread_mutex_unlock(&chain->present_ids.lock);
}
@@ -2193,24 +2229,28 @@ wsi_wl_swapchain_chain_free(struct wsi_wl_swapchain *chain,
if (chain->wsi_wl_surface)
chain->wsi_wl_surface->chain = NULL;
if (chain->present_ids.wp_presentation) {
assert(!chain->present_ids.dispatch_in_progress);
assert(!chain->present_ids.dispatch_in_progress);
/* In VK_EXT_swapchain_maintenance1 there is no requirement to wait for all present IDs to be complete.
* Waiting for the swapchain fence is enough.
* Just clean up anything user did not wait for. */
struct wsi_wl_present_id *id, *tmp;
wl_list_for_each_safe(id, tmp, &chain->present_ids.outstanding_list, link) {
/* In VK_EXT_swapchain_maintenance1 there is no requirement to wait for all present IDs to be complete.
* Waiting for the swapchain fence is enough.
* Just clean up anything user did not wait for. */
struct wsi_wl_present_id *id, *tmp;
wl_list_for_each_safe(id, tmp, &chain->present_ids.outstanding_list, link) {
if (id->feedback)
wp_presentation_feedback_destroy(id->feedback);
wl_list_remove(&id->link);
vk_free(id->alloc, id);
}
wl_proxy_wrapper_destroy(chain->present_ids.wp_presentation);
pthread_cond_destroy(&chain->present_ids.list_advanced);
pthread_mutex_destroy(&chain->present_ids.lock);
if (id->frame)
wl_callback_destroy(id->frame);
wl_list_remove(&id->link);
vk_free(id->alloc, id);
}
if (chain->present_ids.wp_presentation)
wl_proxy_wrapper_destroy(chain->present_ids.wp_presentation);
if (chain->present_ids.surface)
wl_proxy_wrapper_destroy(chain->present_ids.surface);
pthread_cond_destroy(&chain->present_ids.list_advanced);
pthread_mutex_destroy(&chain->present_ids.lock);
if (chain->present_ids.queue)
wl_event_queue_destroy(chain->present_ids.queue);
@@ -2385,20 +2425,29 @@ wsi_wl_surface_create_swapchain(VkIcdSurfaceBase *icd_surface,
chain->drm_modifiers = drm_modifiers_copy;
}
if (chain->wsi_wl_surface->display->wp_presentation_notwrapped) {
if (!wsi_init_pthread_cond_monotonic(&chain->present_ids.list_advanced)) {
result = VK_ERROR_OUT_OF_HOST_MEMORY;
goto fail_free_wl_chain;
}
pthread_mutex_init(&chain->present_ids.lock, NULL);
if (!wsi_init_pthread_cond_monotonic(&chain->present_ids.list_advanced)) {
result = VK_ERROR_OUT_OF_HOST_MEMORY;
goto fail_free_wl_chain;
}
pthread_mutex_init(&chain->present_ids.lock, NULL);
wl_list_init(&chain->present_ids.outstanding_list);
chain->present_ids.queue =
wl_display_create_queue(chain->wsi_wl_surface->display->wl_display);
wl_list_init(&chain->present_ids.outstanding_list);
chain->present_ids.queue =
wl_display_create_queue(chain->wsi_wl_surface->display->wl_display);
if (chain->wsi_wl_surface->display->wp_presentation_notwrapped) {
chain->present_ids.wp_presentation =
wl_proxy_create_wrapper(chain->wsi_wl_surface->display->wp_presentation_notwrapped);
wl_proxy_set_queue((struct wl_proxy *) chain->present_ids.wp_presentation,
chain->present_ids.queue);
} else {
/* Fallback to frame callbacks when presentation protocol is not available.
* We already have a proxy for the surface, but need another since
* presentID is pumped through a different queue to not disrupt
* QueuePresentKHR frame callback's queue. */
chain->present_ids.surface = wl_proxy_create_wrapper(wsi_wl_surface->base.surface);
wl_proxy_set_queue((struct wl_proxy *) chain->present_ids.surface,
chain->present_ids.queue);
}
chain->fifo_ready = true;