wsi/wayland: Support VK_KHR_present_wait

Use the wp_presentation extension to detect when a given presentation
has taken effect.

Since this protocol is not guaranteed to be supported,
it must be enabled through driconf (vk_khr_present_wait=true) for the time being.

Signed-off-by: Daniel Stone <daniels@collabora.com>
Signed-off-by: Hans-Kristian Arntzen <post@arntzen-software.no>
Co-authored-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/22682>
This commit is contained in:
Daniel Stone 2022-05-11 19:47:52 +01:00 committed by Marge Bot
parent 47cd010931
commit 43f868d175
3 changed files with 292 additions and 0 deletions

View file

@ -60,6 +60,7 @@ libwayland_drm = static_library(
wp_dir = dep_wl_protocols.get_variable(pkgconfig : 'pkgdatadir')
wp_protos = {
'linux-dmabuf-unstable-v1': 'unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml',
'presentation-time': 'stable/presentation-time/presentation-time.xml',
}
wp_files = {}
foreach name, xml : wp_protos

View file

@ -32,6 +32,7 @@ endif
if with_platform_wayland
files_vulkan_wsi += files('wsi_common_wayland.c')
files_vulkan_wsi += wp_files['linux-dmabuf-unstable-v1']
files_vulkan_wsi += wp_files['presentation-time']
endif
if with_platform_windows

View file

@ -42,6 +42,7 @@
#include "wsi_common_entrypoints.h"
#include "wsi_common_private.h"
#include "linux-dmabuf-unstable-v1-client-protocol.h"
#include "presentation-time-client-protocol.h"
#include <util/compiler.h>
#include <util/hash_table.h>
@ -101,6 +102,9 @@ struct wsi_wl_display {
struct dmabuf_feedback_format_table format_table;
/* users want per-chain wsi_wl_swapchain->present_ids.wp_presentation */
struct wp_presentation *wp_presentation_notwrapped;
struct wsi_wayland *wsi_wl;
/* Formats populated by zwp_linux_dmabuf_v1 or wl_shm interfaces */
@ -168,6 +172,16 @@ struct wsi_wl_swapchain {
VkPresentModeKHR present_mode;
bool fifo_ready;
struct {
pthread_mutex_t lock; /* protects all members */
uint64_t max_completed;
struct wl_list outstanding_list;
pthread_cond_t list_advanced;
struct wl_event_queue *queue;
struct wp_presentation *wp_presentation;
bool dispatch_in_progress;
} present_ids;
struct wsi_wl_image images[0];
};
VK_DEFINE_NONDISP_HANDLE_CASTS(wsi_wl_swapchain, base.base, VkSwapchainKHR,
@ -748,6 +762,11 @@ registry_handle_global(void *data, struct wl_registry *registry,
zwp_linux_dmabuf_v1_add_listener(display->wl_dmabuf,
&dmabuf_listener, display);
}
if (strcmp(interface, wp_presentation_interface.name) == 0) {
display->wp_presentation_notwrapped =
wl_registry_bind(registry, name, &wp_presentation_interface, 1);
}
}
static void
@ -771,6 +790,8 @@ wsi_wl_display_finish(struct wsi_wl_display *display)
wl_shm_destroy(display->wl_shm);
if (display->wl_dmabuf)
zwp_linux_dmabuf_v1_destroy(display->wl_dmabuf);
if (display->wp_presentation_notwrapped)
wp_presentation_destroy(display->wp_presentation_notwrapped);
if (display->wl_display_wrapper)
wl_proxy_wrapper_destroy(display->wl_display_wrapper);
if (display->queue)
@ -1531,6 +1552,14 @@ wsi_CreateWaylandSurfaceKHR(VkInstance _instance,
return VK_SUCCESS;
}
struct wsi_wl_present_id {
struct wp_presentation_feedback *feedback;
uint64_t present_id;
const VkAllocationCallbacks *alloc;
struct wsi_wl_swapchain *chain;
struct wl_list link;
};
static struct wsi_image *
wsi_wl_swapchain_get_wsi_image(struct wsi_swapchain *wsi_chain,
uint32_t image_index)
@ -1560,6 +1589,173 @@ wsi_wl_swapchain_set_present_mode(struct wsi_swapchain *wsi_chain,
chain->base.present_mode = mode;
}
static VkResult
wsi_wl_swapchain_wait_for_present(struct wsi_swapchain *wsi_chain,
uint64_t present_id,
uint64_t timeout)
{
struct wsi_wl_swapchain *chain = (struct wsi_wl_swapchain *)wsi_chain;
struct wl_display *wl_display = chain->wsi_wl_surface->display->wl_display;
struct timespec start_time, end_time;
struct timespec rel_timeout;
int wl_fd = wl_display_get_fd(wl_display);
VkResult ret;
int err;
timespec_from_nsec(&rel_timeout, timeout);
/* pthread_mutex_timedlock uses CLOCK_REALTIME, most of the time;
* there is no way to check if it uses this or the clock that time()
* uses (?!), so we just guess. */
clock_gettime(CLOCK_REALTIME, &start_time);
timespec_add(&end_time, &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, present_id, timeout);
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;
}
err = pthread_mutex_timedlock(&chain->present_ids.lock, &end_time);
if (err == ETIMEDOUT)
return VK_TIMEOUT;
else if (err != 0)
return VK_ERROR_OUT_OF_DATE_KHR;
if (chain->present_ids.max_completed >= present_id) {
pthread_mutex_unlock(&chain->present_ids.lock);
return VK_SUCCESS;
}
/* Someone else is dispatching events; wait for them to update the chain
* status and wake us up. */
while (chain->present_ids.dispatch_in_progress) {
/* We only own the lock when the wait succeeds. */
err = pthread_cond_timedwait(&chain->present_ids.list_advanced,
&chain->present_ids.lock, &end_time);
if (err == ETIMEDOUT) {
pthread_mutex_unlock(&chain->present_ids.lock);
return VK_TIMEOUT;
} else if (err != 0) {
pthread_mutex_unlock(&chain->present_ids.lock);
return VK_ERROR_OUT_OF_DATE_KHR;
}
if (chain->present_ids.max_completed >= present_id) {
pthread_mutex_unlock(&chain->present_ids.lock);
return VK_SUCCESS;
}
/* Whoever was previously dispatching the events isn't anymore, so we
* will take over and fall through below. */
if (!chain->present_ids.dispatch_in_progress)
break;
}
assert(!chain->present_ids.dispatch_in_progress);
chain->present_ids.dispatch_in_progress = true;
/* Whether or not we were dispatching the events before, we are now: pull
* all the new events from our event queue, post them, and wake up everyone
* else who might be waiting. */
while (1) {
ret = wl_display_dispatch_queue_pending(wl_display, chain->present_ids.queue);
if (ret < 0) {
ret = VK_ERROR_OUT_OF_DATE_KHR;
goto relinquish_dispatch;
}
/* Some events dispatched: check the new completions. */
if (ret > 0) {
/* Completed our own present; stop our own dispatching and let
* someone else pick it up. */
if (chain->present_ids.max_completed >= present_id) {
ret = VK_SUCCESS;
goto relinquish_dispatch;
}
/* Wake up other waiters who may have been unblocked by the events
* we just read. */
pthread_cond_broadcast(&chain->present_ids.list_advanced);
}
/* Check for timeout, and relinquish the dispatch to another thread
* if we're over our budget. */
struct timespec current_time;
clock_gettime(CLOCK_REALTIME, &current_time);
if (timespec_after(&current_time, &end_time)) {
ret = VK_TIMEOUT;
goto relinquish_dispatch;
}
/* To poll and read from WL fd safely, we must be cooperative.
* See wl_display_prepare_read_queue in https://wayland.freedesktop.org/docs/html/apb.html */
/* Try to read events from the server. */
ret = wl_display_prepare_read_queue(wl_display, chain->present_ids.queue);
if (ret < 0) {
/* Another thread might have read events for our queue already. Go
* back to dispatch them.
*/
if (errno == EAGAIN)
continue;
ret = VK_ERROR_OUT_OF_DATE_KHR;
goto relinquish_dispatch;
}
/* Drop the lock around poll, so people can wait whilst we sleep. */
pthread_mutex_unlock(&chain->present_ids.lock);
struct pollfd pollfd = {
.fd = wl_fd,
.events = POLLIN
};
timespec_sub(&rel_timeout, &end_time, &current_time);
ret = ppoll(&pollfd, 1, &rel_timeout, NULL);
/* Re-lock after poll; either we're dispatching events under the lock or
* bouncing out from an error also under the lock. We can't use timedlock
* here because we need to acquire to clear dispatch_in_progress. */
pthread_mutex_lock(&chain->present_ids.lock);
if (ret <= 0) {
int lerrno = errno;
wl_display_cancel_read(wl_display);
if (ret < 0) {
/* If ppoll() was interrupted, try again. */
if (lerrno == EINTR || lerrno == EAGAIN)
continue;
ret = VK_ERROR_OUT_OF_DATE_KHR;
goto relinquish_dispatch;
}
assert(ret == 0);
continue;
}
ret = wl_display_read_events(wl_display);
if (ret < 0) {
ret = VK_ERROR_OUT_OF_DATE_KHR;
goto relinquish_dispatch;
}
}
relinquish_dispatch:
assert(chain->present_ids.dispatch_in_progress);
chain->present_ids.dispatch_in_progress = false;
pthread_cond_broadcast(&chain->present_ids.list_advanced);
pthread_mutex_unlock(&chain->present_ids.lock);
return ret;
}
static VkResult
wsi_wl_swapchain_acquire_next_image(struct wsi_swapchain *wsi_chain,
const VkAcquireNextImageInfoKHR *info,
@ -1636,6 +1832,54 @@ wsi_wl_swapchain_acquire_next_image(struct wsi_swapchain *wsi_chain,
}
}
static void
presentation_handle_sync_output(void *data,
struct wp_presentation_feedback *feedback,
struct wl_output *output)
{
}
static void
presentation_handle_presented(void *data,
struct wp_presentation_feedback *feedback,
uint32_t tv_sec_hi, uint32_t tv_sec_lo,
uint32_t tv_nsec, uint32_t refresh,
uint32_t seq_hi, uint32_t seq_lo,
uint32_t flags)
{
struct wsi_wl_present_id *id = data;
/* present_ids.lock already held around dispatch */
if (id->present_id > id->chain->present_ids.max_completed)
id->chain->present_ids.max_completed = id->present_id;
wp_presentation_feedback_destroy(feedback);
wl_list_remove(&id->link);
vk_free(id->alloc, id);
}
static void
presentation_handle_discarded(void *data,
struct wp_presentation_feedback *feedback)
{
struct wsi_wl_present_id *id = data;
/* present_ids.lock already held around dispatch */
if (id->present_id > id->chain->present_ids.max_completed)
id->chain->present_ids.max_completed = id->present_id;
wp_presentation_feedback_destroy(feedback);
wl_list_remove(&id->link);
vk_free(id->alloc, id);
}
static const struct wp_presentation_feedback_listener
pres_feedback_listener = {
presentation_handle_sync_output,
presentation_handle_presented,
presentation_handle_discarded,
};
static void
frame_handle_done(void *data, struct wl_callback *callback, uint32_t serial)
{
@ -1700,6 +1944,24 @@ wsi_wl_swapchain_queue_present(struct wsi_swapchain *wsi_chain,
chain->fifo_ready = true;
}
if (present_id > 0 && chain->present_ids.wp_presentation) {
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);
id->chain = chain;
id->present_id = present_id;
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);
wl_list_insert(&chain->present_ids.outstanding_list, &id->link);
pthread_mutex_unlock(&chain->present_ids.lock);
}
chain->images[image_index].busy = true;
wl_surface_commit(wsi_wl_surface->surface);
wl_display_flush(wsi_wl_surface->display->wl_display);
@ -1849,6 +2111,14 @@ 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(wl_list_empty(&chain->present_ids.outstanding_list));
wl_proxy_wrapper_destroy(chain->present_ids.wp_presentation);
pthread_cond_destroy(&chain->present_ids.list_advanced);
pthread_mutex_destroy(&chain->present_ids.lock);
}
wsi_swapchain_finish(&chain->base);
vk_free(pAllocator, chain);
@ -1975,6 +2245,7 @@ wsi_wl_surface_create_swapchain(VkIcdSurfaceBase *icd_surface,
chain->base.queue_present = wsi_wl_swapchain_queue_present;
chain->base.release_images = wsi_wl_swapchain_release_images;
chain->base.set_present_mode = wsi_wl_swapchain_set_present_mode;
chain->base.wait_for_present = wsi_wl_swapchain_wait_for_present;
chain->base.present_mode = wsi_swapchain_get_present_mode(wsi_device, pCreateInfo);
chain->base.image_count = num_images;
chain->extent = pCreateInfo->imageExtent;
@ -1987,6 +2258,25 @@ wsi_wl_surface_create_swapchain(VkIcdSurfaceBase *icd_surface,
}
chain->num_drm_modifiers = num_drm_modifiers;
chain->drm_modifiers = drm_modifiers;
if (chain->wsi_wl_surface->display->wp_presentation_notwrapped) {
pthread_mutex_init(&chain->present_ids.lock, NULL);
pthread_condattr_t condattr;
pthread_condattr_init(&condattr);
pthread_condattr_setclock(&condattr, CLOCK_REALTIME);
pthread_cond_init(&chain->present_ids.list_advanced, &condattr);
pthread_condattr_destroy(&condattr);
wl_list_init(&chain->present_ids.outstanding_list);
chain->present_ids.queue =
wl_display_create_queue(chain->wsi_wl_surface->display->wl_display);
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);
}
chain->fifo_ready = true;
for (uint32_t i = 0; i < chain->base.image_count; i++) {