From 722c3ec3202ee3c8f7055fc04d40d431d45b05cd Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Wed, 20 Sep 2023 10:40:33 -0500 Subject: [PATCH 1/8] vulkan/wsi/wayland: refactor wayland dispatch code We currently have two similar but different bits of code to dispatch wayland event queues. Pull out as much common code as possible. Signed-off-by: Derek Foreman --- src/vulkan/wsi/wsi_common_wayland.c | 399 +++++++++++++++------------- 1 file changed, 208 insertions(+), 191 deletions(-) diff --git a/src/vulkan/wsi/wsi_common_wayland.c b/src/vulkan/wsi/wsi_common_wayland.c index 8d280340091..cfa1baf6127 100644 --- a/src/vulkan/wsi/wsi_common_wayland.c +++ b/src/vulkan/wsi/wsi_common_wayland.c @@ -95,6 +95,11 @@ struct wsi_wl_display { struct wl_display *wl_display; /* Actually a proxy wrapper around the event queue */ struct wl_display *wl_display_wrapper; + + pthread_mutex_t wl_fd_lock; + pthread_cond_t wl_fd_reader_finished; + bool wl_fd_read_in_progress; + struct wl_event_queue *queue; struct wl_shm *wl_shm; @@ -157,8 +166,12 @@ struct wsi_wl_surface { struct wsi_wl_swapchain { struct wsi_swapchain base; + struct wl_event_queue *queue; + struct wsi_wl_surface *wsi_wl_surface; struct wp_tearing_control_v1 *tearing_control; + struct gamescope_commit_queue_v1 *commit_queue; + bool can_timestamp; struct wl_callback *frame; @@ -177,10 +184,7 @@ struct wsi_wl_swapchain { 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]; @@ -205,6 +209,135 @@ find_format(struct u_vector *formats, VkFormat format) return NULL; } +static int +wsi_wl_display_read_queue_with_timeout_internal(struct wsi_wl_display *wsi_wl_display, + struct wl_event_queue *queue, + uint64_t atimeout) +{ + uint64_t current_time_nsec; + struct timespec rel_timeout, end_time, current_time; + int ret; + + if (wl_display_prepare_read_queue(wsi_wl_display->wl_display, queue) < 0) { + /* Another thread might have read events for our queue already. Go + * back to dispatch them. + */ + pthread_mutex_unlock(&wsi_wl_display->wl_fd_lock); + if (errno == EAGAIN) + return VK_SUCCESS; + + return VK_ERROR_OUT_OF_DATE_KHR; + } + + wsi_wl_display->wl_fd_read_in_progress = true; + pthread_mutex_unlock(&wsi_wl_display->wl_fd_lock); + + while (1) { + struct pollfd pollfd = { + .fd = wl_display_get_fd(wsi_wl_display->wl_display), + .events = POLLIN + }; + + current_time_nsec = os_time_get_nano(); + if (current_time_nsec > atimeout) { + rel_timeout.tv_sec = 0; + rel_timeout.tv_nsec = 0; + } else { + timespec_from_nsec(¤t_time, current_time_nsec); + timespec_from_nsec(&end_time, atimeout); + timespec_sub(&rel_timeout, &end_time, ¤t_time); + } + + ret = ppoll(&pollfd, 1, &rel_timeout, NULL); + if (ret < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + + ret = VK_ERROR_OUT_OF_DATE_KHR; + } else if (ret == 0) + ret = VK_TIMEOUT; + else + ret = VK_SUCCESS; + + break; + } + + if (ret != VK_SUCCESS) { + wl_display_cancel_read(wsi_wl_display->wl_display); + } else { + ret = wl_display_read_events(wsi_wl_display->wl_display); + if (ret != 0) + ret = VK_ERROR_OUT_OF_DATE_KHR; + } + + pthread_mutex_lock(&wsi_wl_display->wl_fd_lock); + wsi_wl_display->wl_fd_read_in_progress = false; + pthread_cond_broadcast(&wsi_wl_display->wl_fd_reader_finished); + return ret; +} + +static int +wsi_wl_display_dispatch_queue_with_timeout(struct wsi_wl_display *wsi_wl_display, + struct wl_event_queue *queue, + uint64_t timeout) +{ + int err; + int n_events; + uint64_t atimeout, now; + + if (timeout == UINT64_MAX) + atimeout = timeout; + else + atimeout = os_time_get_absolute_timeout(timeout); + + while (1) { + n_events = wl_display_dispatch_queue_pending(wsi_wl_display->wl_display, + queue); + if (n_events > 0) { + err = VK_SUCCESS; + break; + } + pthread_mutex_lock(&wsi_wl_display->wl_fd_lock); + + if (wsi_wl_display->wl_fd_read_in_progress) { + struct timespec end_time; + + timespec_from_nsec(&end_time, atimeout); + + err = pthread_cond_timedwait(&wsi_wl_display->wl_fd_reader_finished, + &wsi_wl_display->wl_fd_lock, + &end_time); + if (err) { + if (errno == ETIMEDOUT) + err = VK_TIMEOUT; + else + err = VK_ERROR_OUT_OF_DATE_KHR; + } else { + /* We don't know if the other thread actually + * dispatched anything, so let the caller decide + * whether it should continue. + */ + err = VK_INCOMPLETE; + } + } else { + err = wsi_wl_display_read_queue_with_timeout_internal(wsi_wl_display, + queue, + timeout); + } + + pthread_mutex_unlock(&wsi_wl_display->wl_fd_lock); + + now = os_time_get_nano(); + if (now > atimeout) { + err = VK_TIMEOUT; + break; + } + + } + + return err; +} + static struct wsi_wl_format * wsi_wl_display_add_vk_format(struct wsi_wl_display *display, struct u_vector *formats, @@ -825,6 +958,8 @@ wsi_wl_display_finish(struct wsi_wl_display *display) wl_proxy_wrapper_destroy(display->wl_display_wrapper); if (display->queue) wl_event_queue_destroy(display->queue); + pthread_mutex_destroy(&display->wl_fd_lock); + pthread_cond_destroy(&display->wl_fd_reader_finished); } static VkResult @@ -843,6 +978,11 @@ wsi_wl_display_init(struct wsi_wayland *wsi_wl, display->wl_display = wl_display; display->sw = sw; + display->wl_fd_read_in_progress = false; + pthread_mutex_init(&display->wl_fd_lock, NULL); + if (!wsi_init_pthread_cond_monotonic(&display->wl_fd_reader_finished)) + goto fail; + display->queue = wl_display_create_queue(wl_display); if (!display->queue) { result = VK_ERROR_OUT_OF_HOST_MEMORY; @@ -943,6 +1083,7 @@ fail_registry: wl_registry_destroy(registry); fail: + pthread_mutex_destroy(&display->wl_fd_lock); wsi_wl_display_finish(display); return result; } @@ -1628,19 +1769,15 @@ wsi_wl_swapchain_wait_for_present(struct wsi_swapchain *wsi_chain, 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 end_time; - int wl_fd = wl_display_get_fd(wl_display); - VkResult ret; - int err; + uint64_t end_time, time_left, now; + int ret; + bool expired = false; + bool finished; - uint64_t atimeout; - if (timeout == 0 || timeout == UINT64_MAX) - atimeout = timeout; + if (timeout == UINT64_MAX) + end_time = timeout; else - atimeout = os_time_get_absolute_timeout(timeout); - - timespec_from_nsec(&end_time, atimeout); + end_time = 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. */ @@ -1656,141 +1793,45 @@ wsi_wl_swapchain_wait_for_present(struct wsi_swapchain *wsi_chain, return VK_SUCCESS; } + while (1) { + ret = wl_display_dispatch_queue_pending(chain->wsi_wl_surface->display->wl_display, + chain->queue); + if (ret < 0) + return VK_ERROR_OUT_OF_DATE_KHR; + /* PresentWait can be called concurrently. * If there is contention on this mutex, it means there is currently a dispatcher in flight holding the lock. * The lock is only held while there is forward progress processing events from Wayland, * so there should be no problem locking without timeout. * We would like to be able to support timeout = 0 to query the current max_completed count. * A timedlock with no timeout can be problematic in that scenario. */ - err = pthread_mutex_lock(&chain->present_ids.lock); - if (err != 0) - return VK_ERROR_OUT_OF_DATE_KHR; - - if (chain->present_ids.max_completed >= present_id) { + pthread_mutex_lock(&chain->present_ids.lock); + finished = 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); + if (finished) 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. */ - uint64_t current_time_nsec = os_time_get_nano(); - if (current_time_nsec > atimeout) { - 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 - }; - struct timespec current_time, rel_timeout; - timespec_from_nsec(¤t_time, current_time_nsec); - timespec_sub(&rel_timeout, &end_time, ¤t_time); - ret = ppoll(&pollfd, 1, &rel_timeout, NULL); + if (expired) + return VK_TIMEOUT; - /* 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); + now = os_time_get_nano(); + if (now > end_time) + time_left = 0; + else + time_left = end_time - now; - 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); + ret = wsi_wl_display_dispatch_queue_with_timeout(chain->wsi_wl_surface->display, + chain->queue, + time_left); + if (ret == VK_INCOMPLETE) continue; - } - ret = wl_display_read_events(wl_display); - if (ret < 0) { - ret = VK_ERROR_OUT_OF_DATE_KHR; - goto relinquish_dispatch; - } - } + if (ret != VK_SUCCESS && ret != VK_TIMEOUT) + return ret; -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; + if (time_left == 0) + expired = true; + } } static VkResult @@ -1800,19 +1841,18 @@ wsi_wl_swapchain_acquire_next_image(struct wsi_swapchain *wsi_chain, { struct wsi_wl_swapchain *chain = (struct wsi_wl_swapchain *)wsi_chain; struct wsi_wl_surface *wsi_wl_surface = chain->wsi_wl_surface; - struct timespec start_time, end_time; - struct timespec rel_timeout; - int wl_fd = wl_display_get_fd(wsi_wl_surface->display->wl_display); - - timespec_from_nsec(&rel_timeout, info->timeout); + uint64_t end_time, time_left, now; + bool expired = false; + int ret; - clock_gettime(CLOCK_MONOTONIC, &start_time); - timespec_add(&end_time, &rel_timeout, &start_time); + if (info->timeout == UINT64_MAX) + end_time = info->timeout; + else + end_time = os_time_get_absolute_timeout(info->timeout); while (1) { - /* Try to dispatch potential events. */ - int ret = wl_display_dispatch_queue_pending(wsi_wl_surface->display->wl_display, - wsi_wl_surface->display->queue); + ret = wl_display_dispatch_queue_pending(wsi_wl_surface->display->wl_display, + wsi_wl_surface->display->queue); if (ret < 0) return VK_ERROR_OUT_OF_DATE_KHR; @@ -1826,46 +1866,26 @@ wsi_wl_swapchain_acquire_next_image(struct wsi_swapchain *wsi_chain, } } - /* Check for timeout. */ - struct timespec current_time; - clock_gettime(CLOCK_MONOTONIC, ¤t_time); - if (timespec_after(¤t_time, &end_time)) - return (info->timeout ? VK_TIMEOUT : VK_NOT_READY); + if (expired) + return info->timeout ? VK_TIMEOUT : VK_NOT_READY; - /* Try to read events from the server. */ - ret = wl_display_prepare_read_queue(wsi_wl_surface->display->wl_display, - wsi_wl_surface->display->queue); - if (ret < 0) { - /* Another thread might have read events for our queue already. Go - * back to dispatch them. - */ - if (errno == EAGAIN) - continue; - return VK_ERROR_OUT_OF_DATE_KHR; - } + now = os_time_get_nano(); + if (now > end_time) + time_left = 0; + else + time_left = end_time - now; - struct pollfd pollfd = { - .fd = wl_fd, - .events = POLLIN - }; - timespec_sub(&rel_timeout, &end_time, ¤t_time); - ret = ppoll(&pollfd, 1, &rel_timeout, NULL); - if (ret <= 0) { - int lerrno = errno; - wl_display_cancel_read(wsi_wl_surface->display->wl_display); - if (ret < 0) { - /* If ppoll() was interrupted, try again. */ - if (lerrno == EINTR || lerrno == EAGAIN) - continue; - return VK_ERROR_OUT_OF_DATE_KHR; - } - assert(ret == 0); + ret = wsi_wl_display_dispatch_queue_with_timeout(wsi_wl_surface->display, + wsi_wl_surface->display->queue, + time_left); + if (ret == VK_ERROR_OUT_OF_DATE_KHR) + return ret; + + if (ret == VK_INCOMPLETE) continue; - } - ret = wl_display_read_events(wsi_wl_surface->display->wl_display); - if (ret < 0) - return VK_ERROR_OUT_OF_DATE_KHR; + if (ret == VK_TIMEOUT) + expired = true; } } @@ -1886,9 +1906,10 @@ presentation_handle_presented(void *data, { struct wsi_wl_present_id *id = data; - /* present_ids.lock already held around dispatch */ + pthread_mutex_lock(&id->chain->present_ids.lock); if (id->present_id > id->chain->present_ids.max_completed) id->chain->present_ids.max_completed = id->present_id; + pthread_mutex_unlock(&id->chain->present_ids.lock); wp_presentation_feedback_destroy(feedback); wl_list_remove(&id->link); @@ -1901,9 +1922,10 @@ presentation_handle_discarded(void *data, { struct wsi_wl_present_id *id = data; - /* present_ids.lock already held around dispatch */ + pthread_mutex_lock(&id->chain->present_ids.lock); if (id->present_id > id->chain->present_ids.max_completed) id->chain->present_ids.max_completed = id->present_id; + pthread_mutex_unlock(&id->chain->present_ids.lock); wp_presentation_feedback_destroy(feedback); wl_list_remove(&id->link); @@ -2149,8 +2171,6 @@ wsi_wl_swapchain_chain_free(struct wsi_wl_swapchain *chain, chain->wsi_wl_surface->chain = NULL; if (chain->present_ids.wp_presentation) { - 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. */ @@ -2162,7 +2182,6 @@ wsi_wl_swapchain_chain_free(struct wsi_wl_swapchain *chain, } wl_proxy_wrapper_destroy(chain->present_ids.wp_presentation); - pthread_cond_destroy(&chain->present_ids.list_advanced); pthread_mutex_destroy(&chain->present_ids.lock); } @@ -2306,18 +2325,16 @@ wsi_wl_surface_create_swapchain(VkIcdSurfaceBase *icd_surface, chain->num_drm_modifiers = num_drm_modifiers; chain->drm_modifiers = drm_modifiers; + chain->queue = wl_display_create_queue(chain->wsi_wl_surface->display->wl_display); + if (chain->wsi_wl_surface->display->wp_presentation_notwrapped) { - if (!wsi_init_pthread_cond_monotonic(&chain->present_ids.list_advanced)) - goto fail; 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); 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->queue); } chain->fifo_ready = true; -- 2.42.0 From 42c744d99ddd339d617851ac25fe625aa0f65711 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Fri, 10 Nov 2023 07:25:35 -0600 Subject: [PATCH 2/8] vulkan/wsi/wayland: Use commit_timing/commit_queue protocol for FIFO The commit_timing protocol allows us to set a presentation timestamp, and the commit_queue protocol allows us to request FIFO semantics for committed state (instead of the default mailbox). I these are available, use them to implement Vulkan's FIFO presentation mode. Signed-off-by: Derek Foreman --- src/egl/wayland/wayland-drm/meson.build | 2 + src/vulkan/wsi/meson.build | 2 + src/vulkan/wsi/wsi_common_wayland.c | 148 ++++++++++++++++++++++-- 3 files changed, 140 insertions(+), 12 deletions(-) diff --git a/src/egl/wayland/wayland-drm/meson.build b/src/egl/wayland/wayland-drm/meson.build index 6ab5b9260ff..70940cb8771 100644 --- a/src/egl/wayland/wayland-drm/meson.build +++ b/src/egl/wayland/wayland-drm/meson.build @@ -59,6 +59,8 @@ libwayland_drm = static_library( # here for now as the maybe-least-bad solution. wp_dir = dep_wl_protocols.get_variable(pkgconfig : 'pkgdatadir') wp_protos = { + 'commit-queue-v1': 'staging/commit-queue/commit-queue-v1.xml', + 'commit-timing-v1': 'staging/commit-timing/commit-timing-v1.xml', 'linux-dmabuf-unstable-v1': 'unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml', 'presentation-time': 'stable/presentation-time/presentation-time.xml', } diff --git a/src/vulkan/wsi/meson.build b/src/vulkan/wsi/meson.build index dcc7747fcac..70a73805cd4 100644 --- a/src/vulkan/wsi/meson.build +++ b/src/vulkan/wsi/meson.build @@ -31,6 +31,8 @@ endif if with_platform_wayland files_vulkan_wsi += files('wsi_common_wayland.c') + files_vulkan_wsi += wp_files['commit-queue-v1'] + files_vulkan_wsi += wp_files['commit-timing-v1'] files_vulkan_wsi += wp_files['linux-dmabuf-unstable-v1'] files_vulkan_wsi += wp_files['presentation-time'] endif diff --git a/src/vulkan/wsi/wsi_common_wayland.c b/src/vulkan/wsi/wsi_common_wayland.c index cfa1baf6127..d01ff68781e 100644 --- a/src/vulkan/wsi/wsi_common_wayland.c +++ b/src/vulkan/wsi/wsi_common_wayland.c @@ -41,6 +41,8 @@ #include "vk_util.h" #include "wsi_common_entrypoints.h" #include "wsi_common_private.h" +#include "commit-queue-v1-client-protocol.h" +#include "commit-timing-v1-client-protocol.h" #include "linux-dmabuf-unstable-v1-client-protocol.h" #include "presentation-time-client-protocol.h" @@ -111,6 +113,9 @@ struct wsi_wl_display { /* users want per-chain wsi_wl_swapchain->present_ids.wp_presentation */ struct wp_presentation *wp_presentation_notwrapped; + struct wp_commit_queue_manager_v1 *commit_queue_manager; + struct wp_commit_timing_manager_v1 *commit_timing_manager; + struct wsi_wayland *wsi_wl; /* Formats populated by zwp_linux_dmabuf_v1 or wl_shm interfaces */ @@ -133,6 +138,7 @@ struct wsi_wayland { struct wsi_wl_image { struct wsi_image base; + struct wsi_wl_swapchain *chain; struct wl_buffer *buffer; bool busy; int shm_fd; @@ -178,13 +188,17 @@ struct wsi_wl_swapchain { const uint64_t *drm_modifiers; VkPresentModeKHR present_mode; - bool fifo_ready; + bool legacy_fifo_ready; + + uint64_t last_target_time; struct { pthread_mutex_t lock; /* protects all members */ uint64_t max_completed; struct wl_list outstanding_list; struct wp_presentation *wp_presentation; + uint64_t phase_time; + unsigned int refresh_nsec; } present_ids; struct wsi_wl_image images[0]; @@ -801,6 +944,9 @@ registry_handle_global(void *data, struct wl_registry *registry, } else if (strcmp(interface, wp_tearing_control_manager_v1_interface.name) == 0) { display->tearing_control_manager = wl_registry_bind(registry, name, &wp_tearing_control_manager_v1_interface, 1); + } else if (strcmp(interface, gamescope_commit_queue_manager_v1_interface.name) == 0) { + display->commit_queue_manager = + wl_registry_bind(registry, name, &gamescope_commit_queue_manager_v1_interface, 1); } } @@ -973,6 +973,8 @@ wsi_wl_display_finish(struct wsi_wl_display *display) zwp_linux_dmabuf_v1_destroy(display->wl_dmabuf); if (display->wp_presentation_notwrapped) wp_presentation_destroy(display->wp_presentation_notwrapped); + if (display->commit_queue_manager) + gamescope_commit_queue_manager_v1_destroy(display->commit_queue_manager); if (display->tearing_control_manager) wp_tearing_control_manager_v1_destroy(display->tearing_control_manager); if (display->wl_display_wrapper) @@ -1875,6 +1904,16 @@ wsi_wl_swapchain_acquire_next_image(struct wsi_swapchain *wsi_chain, else time_left = end_time - now; + /* If we can use timestamps, we want to make sure to dispatch the queue + * feedback events are in so we can get a refresh rate and a vsync time to + * phase lock to */ + if (chain->can_timestamp) { + ret = wl_display_dispatch_queue_pending(wsi_wl_surface->display->wl_display, + chain->queue); + if (ret < 0) + return VK_ERROR_OUT_OF_DATE_KHR; + } + ret = wsi_wl_display_dispatch_queue_with_timeout(wsi_wl_surface->display, wsi_wl_surface->display->queue, time_left); @@ -1905,10 +1944,16 @@ presentation_handle_presented(void *data, uint32_t flags) { struct wsi_wl_present_id *id = data; + struct timespec presentation_time; pthread_mutex_lock(&id->chain->present_ids.lock); if (id->present_id > id->chain->present_ids.max_completed) id->chain->present_ids.max_completed = id->present_id; + + presentation_time.tv_sec = ((uint64_t)tv_sec_hi << 32) + tv_sec_lo; + presentation_time.tv_nsec = tv_nsec; + id->chain->present_ids.phase_time = timespec_to_nsec(&presentation_time); + id->chain->present_ids.refresh_nsec = refresh; pthread_mutex_unlock(&id->chain->present_ids.lock); wp_presentation_feedback_destroy(feedback); @@ -1944,8 +1989,10 @@ frame_handle_done(void *data, struct wl_callback *callback, uint32_t serial) { struct wsi_wl_swapchain *chain = data; + assert(!chain->can_timestamp); + chain->frame = NULL; - chain->fifo_ready = true; + chain->legacy_fifo_ready = true; wl_callback_destroy(callback); } @@ -1954,6 +2001,46 @@ static const struct wl_callback_listener frame_listener = { frame_handle_done, }; +static void +set_timestamp(struct wsi_wl_swapchain *chain) +{ + uint64_t now, target; + struct timespec target_ts; + uint64_t refresh; + uint64_t phase_time; + + now = os_time_get_nano(); + + pthread_mutex_lock(&chain->present_ids.lock); + phase_time = chain->present_ids.phase_time; + refresh = chain->present_ids.refresh_nsec; + pthread_mutex_unlock(&chain->present_ids.lock); + + if (refresh == 0) + refresh = 16666666; + + target = chain->last_target_time + refresh; + + if (now > target) { + uint64_t offset; + + if (phase_time > now) + now = phase_time; + + offset = (now - phase_time) % refresh; + target = now - offset + refresh; + } + + timespec_from_nsec(&target_ts, target); + wp_commit_timer_v1_set_timestamp(chain->commit_timer, + target_ts.tv_sec >> 32, target_ts.tv_sec, + target_ts.tv_nsec); + + wp_commit_queue_v1_set_queue_mode(chain->commit_queue, + WP_COMMIT_QUEUE_V1_QUEUE_MODE_FIFO); + chain->last_target_time = target; +} + static VkResult wsi_wl_swapchain_queue_present(struct wsi_swapchain *wsi_chain, uint32_t image_index, @@ -1962,6 +2049,7 @@ wsi_wl_swapchain_queue_present(struct wsi_swapchain *wsi_chain, { struct wsi_wl_swapchain *chain = (struct wsi_wl_swapchain *)wsi_chain; struct wsi_wl_surface *wsi_wl_surface = chain->wsi_wl_surface; + bool mode_fifo = chain->base.present_mode == VK_PRESENT_MODE_FIFO_KHR; if (chain->buffer_type == WSI_WL_BUFFER_SHM_MEMCPY) { struct wsi_wl_image *image = &chain->images[image_index]; @@ -1971,7 +2059,7 @@ wsi_wl_swapchain_queue_present(struct wsi_swapchain *wsi_chain, /* For EXT_swapchain_maintenance1. We might have transitioned from FIFO to MAILBOX. * In this case we need to let the FIFO request complete, before presenting MAILBOX. */ - while (!chain->fifo_ready) { + while (!chain->can_timestamp && !chain->legacy_fifo_ready) { int ret = wl_display_dispatch_queue(wsi_wl_surface->display->wl_display, wsi_wl_surface->display->queue); if (ret < 0) @@ -1994,16 +2082,19 @@ wsi_wl_swapchain_queue_present(struct wsi_swapchain *wsi_chain, wl_surface_damage(wsi_wl_surface->surface, 0, 0, INT32_MAX, INT32_MAX); } - if (chain->base.present_mode == VK_PRESENT_MODE_FIFO_KHR) { - chain->frame = wl_surface_frame(wsi_wl_surface->surface); - wl_callback_add_listener(chain->frame, &frame_listener, chain); - chain->fifo_ready = false; - } else { - /* If we present MAILBOX, any subsequent presentation in FIFO can replace this image. */ - chain->fifo_ready = true; + if (!chain->can_timestamp) { + if (mode_fifo) { + chain->frame = wl_surface_frame(wsi_wl_surface->surface); + wl_callback_add_listener(chain->frame, &frame_listener, chain); + chain->legacy_fifo_ready = false; + } else { + /* If we present MAILBOX, any subsequent presentation in FIFO can replace this image. */ + chain->legacy_fifo_ready = true; + } } - if (present_id > 0 && chain->present_ids.wp_presentation) { + if (chain->present_ids.wp_presentation && + (present_id > 0 || (chain->can_timestamp && mode_fifo))) { 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); @@ -2022,6 +2113,10 @@ wsi_wl_swapchain_queue_present(struct wsi_swapchain *wsi_chain, } chain->images[image_index].busy = true; + + if (chain->can_timestamp && mode_fifo) + set_timestamp(chain); + wl_surface_commit(wsi_wl_surface->surface); wl_display_flush(wsi_wl_surface->display->wl_display); @@ -2137,6 +2232,7 @@ wsi_wl_image_init(struct wsi_wl_swapchain *chain, goto fail_image; wl_buffer_add_listener(image->buffer, &buffer_listener, image); + image->chain = chain; return VK_SUCCESS; @@ -2185,6 +2281,12 @@ wsi_wl_swapchain_chain_free(struct wsi_wl_swapchain *chain, pthread_mutex_destroy(&chain->present_ids.lock); } + if (chain->commit_queue) + wp_commit_queue_v1_destroy(chain->commit_queue); + + if (chain->commit_timer) + wp_commit_timer_v1_destroy(chain->commit_timer); + wsi_swapchain_finish(&chain->base); } @@ -2263,6 +2365,11 @@ wsi_wl_surface_create_swapchain(VkIcdSurfaceBase *icd_surface, wp_tearing_control_v1_destroy(old_chain->tearing_control); old_chain->tearing_control = NULL; } + if (old_chain->commit_queue) { + gamescope_commit_queue_v1_destroy(old_chain->commit_queue); + old_chain->commit_queue = NULL; + old_chain->can_timestamp = false; + } } /* Take ownership of the wsi_wl_surface */ @@ -2337,7 +2452,16 @@ wsi_wl_surface_create_swapchain(VkIcdSurfaceBase *icd_surface, chain->queue); } - chain->fifo_ready = true; + chain->legacy_fifo_ready = true; + struct wsi_wl_display *dpy = chain->wsi_wl_surface->display; + if (dpy->commit_queue_manager && + dpy->commit_timing_manager) { + chain->commit_queue = wp_commit_queue_manager_v1_get_queue_controller(dpy->commit_queue_manager, + chain->wsi_wl_surface->surface); + chain->commit_timer = wp_commit_timing_manager_v1_get_timer(dpy->commit_timing_manager, + chain->wsi_wl_surface->surface); + chain->can_timestamp = true; + } for (uint32_t i = 0; i < chain->base.image_count; i++) { result = wsi_wl_image_init(chain, &chain->images[i], -- 2.42.0 From f40957ad3b4d0ee71a3c1f0728d9e44efde83e4a Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sat, 25 Nov 2023 16:25:58 +0100 Subject: [PATCH 3/8] hack: rip out commit-timing-v1 --- src/egl/wayland/wayland-drm/meson.build | 6 +++--- src/vulkan/wsi/meson.build | 2 +- src/vulkan/wsi/wsi_common_wayland.c | 24 ++---------------------- 3 files changed, 6 insertions(+), 26 deletions(-) diff --git a/src/egl/wayland/wayland-drm/meson.build b/src/egl/wayland/wayland-drm/meson.build index 70940cb8771..fc6ed461c92 100644 --- a/src/egl/wayland/wayland-drm/meson.build +++ b/src/egl/wayland/wayland-drm/meson.build @@ -60,7 +60,7 @@ libwayland_drm = static_library( wp_dir = dep_wl_protocols.get_variable(pkgconfig : 'pkgdatadir') wp_protos = { 'commit-queue-v1': 'staging/commit-queue/commit-queue-v1.xml', - 'commit-timing-v1': 'staging/commit-timing/commit-timing-v1.xml', + #'commit-timing-v1': 'staging/commit-timing/commit-timing-v1.xml', 'linux-dmabuf-unstable-v1': 'unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml', 'presentation-time': 'stable/presentation-time/presentation-time.xml', } @@ -68,13 +68,13 @@ wp_files = {} foreach name, xml : wp_protos code = custom_target( name + '-protocol.c', - input : join_paths(wp_dir, xml), + input : wp_dir / xml, output : name + '-protocol.c', command : [prog_wl_scanner, wl_scanner_arg, '@INPUT@', '@OUTPUT@'], ) header = custom_target( name + '-client-protocol.h', - input : join_paths(wp_dir, xml), + input : wp_dir / xml, output : name + '-client-protocol.h', command : [prog_wl_scanner, 'client-header', '@INPUT@', '@OUTPUT@'], ) diff --git a/src/vulkan/wsi/meson.build b/src/vulkan/wsi/meson.build index 70a73805cd4..e82fb3de4ec 100644 --- a/src/vulkan/wsi/meson.build +++ b/src/vulkan/wsi/meson.build @@ -32,7 +32,7 @@ endif if with_platform_wayland files_vulkan_wsi += files('wsi_common_wayland.c') files_vulkan_wsi += wp_files['commit-queue-v1'] - files_vulkan_wsi += wp_files['commit-timing-v1'] + #files_vulkan_wsi += wp_files['commit-timing-v1'] files_vulkan_wsi += wp_files['linux-dmabuf-unstable-v1'] files_vulkan_wsi += wp_files['presentation-time'] endif diff --git a/src/vulkan/wsi/wsi_common_wayland.c b/src/vulkan/wsi/wsi_common_wayland.c index d01ff68781e..5e05f0488dc 100644 --- a/src/vulkan/wsi/wsi_common_wayland.c +++ b/src/vulkan/wsi/wsi_common_wayland.c @@ -42,7 +42,6 @@ #include "wsi_common_entrypoints.h" #include "wsi_common_private.h" #include "commit-queue-v1-client-protocol.h" -#include "commit-timing-v1-client-protocol.h" #include "linux-dmabuf-unstable-v1-client-protocol.h" #include "presentation-time-client-protocol.h" @@ -114,7 +113,6 @@ struct wsi_wl_display { struct wp_presentation *wp_presentation_notwrapped; struct wp_commit_queue_manager_v1 *commit_queue_manager; - struct wp_commit_timing_manager_v1 *commit_timing_manager; struct wsi_wayland *wsi_wl; @@ -2032,9 +2024,6 @@ set_timestamp(struct wsi_wl_swapchain *chain) } timespec_from_nsec(&target_ts, target); - wp_commit_timer_v1_set_timestamp(chain->commit_timer, - target_ts.tv_sec >> 32, target_ts.tv_sec, - target_ts.tv_nsec); wp_commit_queue_v1_set_queue_mode(chain->commit_queue, WP_COMMIT_QUEUE_V1_QUEUE_MODE_FIFO); @@ -2050,6 +2039,7 @@ wsi_wl_swapchain_queue_present(struct wsi_swapchain *wsi_chain, struct wsi_wl_swapchain *chain = (struct wsi_wl_swapchain *)wsi_chain; struct wsi_wl_surface *wsi_wl_surface = chain->wsi_wl_surface; bool mode_fifo = chain->base.present_mode == VK_PRESENT_MODE_FIFO_KHR; + //fprintf(stderr, "FIFO = %d\n", mode_fifo); if (chain->buffer_type == WSI_WL_BUFFER_SHM_MEMCPY) { struct wsi_wl_image *image = &chain->images[image_index]; @@ -2284,9 +2274,6 @@ wsi_wl_swapchain_chain_free(struct wsi_wl_swapchain *chain, if (chain->commit_queue) wp_commit_queue_v1_destroy(chain->commit_queue); - if (chain->commit_timer) - wp_commit_timer_v1_destroy(chain->commit_timer); - wsi_swapchain_finish(&chain->base); } @@ -2454,12 +2437,9 @@ wsi_wl_surface_create_swapchain(VkIcdSurfaceBase *icd_surface, chain->legacy_fifo_ready = true; struct wsi_wl_display *dpy = chain->wsi_wl_surface->display; - if (dpy->commit_queue_manager && - dpy->commit_timing_manager) { + if (dpy->commit_queue_manager) { chain->commit_queue = wp_commit_queue_manager_v1_get_queue_controller(dpy->commit_queue_manager, chain->wsi_wl_surface->surface); - chain->commit_timer = wp_commit_timing_manager_v1_get_timer(dpy->commit_timing_manager, - chain->wsi_wl_surface->surface); chain->can_timestamp = true; } -- 2.42.0 From 5bac67e0cc8c36ff1fc7836062790d6d96de00d4 Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Wed, 29 Nov 2023 11:06:51 +0000 Subject: [PATCH 4/8] wsi: Use vendored gamescope-commit-queue-v1 protocol --- .../wayland-drm/gamescope-commit-queue-v1.xml | 181 ++++++++++++++++++ src/egl/wayland/wayland-drm/meson.build | 22 ++- src/vulkan/wsi/meson.build | 2 +- src/vulkan/wsi/wsi_common_wayland.c | 22 +-- 4 files changed, 214 insertions(+), 13 deletions(-) create mode 100644 src/egl/wayland/wayland-drm/gamescope-commit-queue-v1.xml diff --git a/src/egl/wayland/wayland-drm/gamescope-commit-queue-v1.xml b/src/egl/wayland/wayland-drm/gamescope-commit-queue-v1.xml new file mode 100644 index 00000000000..d460e0bc10f --- /dev/null +++ b/src/egl/wayland/wayland-drm/gamescope-commit-queue-v1.xml @@ -0,0 +1,181 @@ + + + + Copyright © 2023 Valve Corporation + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + By design Wayland uses a "mailbox" style presentation model. Under + the mailbox model, when wl_surface.commit is called, the currently + pending state is intended to replace the current state immediately. + + If state is committed many times before the compositor repaints a + scene, each commit takes place immediately, updating the existing + state. When the compositor repaints the display only the most + recent accumulation of state is visible. This may lead to client + buffers being released without presentation if they were replaced + before being displayed. + + There are other presentation models such as FIFO (First In First + Out) in which state commits are explicitly queued for future + repaint intervals, and client buffers should not be released + without being displayed. + + Graphics APIs such as Vulkan aim to support these presentation + models, but they are not implementable on top of our mailbox model + without the ability to change the default surface state handling + behaviour. + + This interface provides a way to control the compositor's surface + state handling to enable presentation models other than mailbox. + + It does so by exposing control of a compositor surface state queue, + and specifying for each call of wl_surface.commit whether the + pending state should be handled in a mailbox or a FIFO fashion. + + Warning! The protocol described in this file is currently in the testing + phase. Backward compatible changes may be added together with the + corresponding interface version bump. Backward incompatible changes can + only be done by creating a new major version of the extension. + + + + These fatal protocol errors may be emitted in response to + illegal requests. + + + + + + + Informs the server that the client will no longer be using + this protocol object. Existing objects created by this object + are not affected. + + + + + + Establish a queue controller for a surface. + + Graphics APIs (EGL, Vulkan) will likely use this protocol + internally, so clients using them shouldn't directly use this + protocol on surfaces managed by those APIs, or a + queue_controller_already_exists protocol error will occur. + + + + + + + + + A queue controller for a surface. + + A wayland compositor may implicitly queue surface state to + allow it to pick the most recently ready state at repaint time, + or to allow surface state to contain timing information. + + The commit queue controller object allows explicit control over + the queue of upcoming surface state by allowing a client to attach + a queue drain mode to pending surface state before it calls + wl_surface.commit. + + + + + These fatal protocol errors may be emitted in response to + illegal requests. + + + + + + + This enum is used to choose how the compositor processes a queue + entry at output repaint time. + + + + State from this queue slot may be updated immediately (without + completing a repaint) if newer state is ready to display at + repaint time. + + + + + This queue slot will be the last state update for this surface + that the compositor will process during the repaint in which + it is ready for display. + + If the compositor is presenting with tearing, the surface state + must be made current for an iteration of the compositor's repaint + loop. This may result in the state being visible for a very short + duration, with visible artifacts, or even not visible at all for + surfaces that aren't full screen. + + The compositor must not cause state processing to stall indefinitely + for a surface that is occluded or otherwise not visible. Instead, + if the compositor is choosing not to present a surface for reasons + unrelated to state readiness, the FIFO condition must be considered + satisfied at the moment new state becomes ready to replace the + undisplayed state. + + + + + + + This request adds a queue drain mode to the pending surface + state, which will be commit by the next wl_surface.commit. + + This request tells the compositor how to process the state + from that commit when handling its internal state queue. + + If the drain mode is "mailbox", the compositor may continue + processing the next state in the queue before it repaints + the display. + + If the drain mode is "fifo", the compositor should ensure the + queue is not advanced until after this state has been current + for a repaint. The queue may be advance without repaint in the + case of off-screen or occluded surfaces. + + The default drain mode when none is specified is "mailbox". + + + + + + + Informs the server that the client will no longer be using + this protocol object. + + Surface state changes previously made by this protocol are + unaffected by this object's destruction. + + + + diff --git a/src/egl/wayland/wayland-drm/meson.build b/src/egl/wayland/wayland-drm/meson.build index fc6ed461c92..a6f9bf2e10b 100644 --- a/src/egl/wayland/wayland-drm/meson.build +++ b/src/egl/wayland/wayland-drm/meson.build @@ -59,7 +59,7 @@ libwayland_drm = static_library( # here for now as the maybe-least-bad solution. wp_dir = dep_wl_protocols.get_variable(pkgconfig : 'pkgdatadir') wp_protos = { - 'commit-queue-v1': 'staging/commit-queue/commit-queue-v1.xml', + #'commit-queue-v1': 'staging/commit-queue/commit-queue-v1.xml', #'commit-timing-v1': 'staging/commit-timing/commit-timing-v1.xml', 'linux-dmabuf-unstable-v1': 'unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml', 'presentation-time': 'stable/presentation-time/presentation-time.xml', @@ -80,3 +80,23 @@ foreach name, xml : wp_protos ) wp_files += { name: [code, header] } endforeach + +gamescope_protos = { + 'gamescope-commit-queue-v1': 'gamescope-commit-queue-v1.xml', +} +foreach name, xml : gamescope_protos + code = custom_target( + name + '-protocol.c', + input : xml, + output : name + '-protocol.c', + command : [prog_wl_scanner, wl_scanner_arg, '@INPUT@', '@OUTPUT@'], + ) + header = custom_target( + name + '-client-protocol.h', + input : xml, + output : name + '-client-protocol.h', + command : [prog_wl_scanner, 'client-header', '@INPUT@', '@OUTPUT@'], + ) + wp_files += { name: [code, header] } +endforeach + diff --git a/src/vulkan/wsi/meson.build b/src/vulkan/wsi/meson.build index e82fb3de4ec..43e23ca716d 100644 --- a/src/vulkan/wsi/meson.build +++ b/src/vulkan/wsi/meson.build @@ -31,7 +31,7 @@ endif if with_platform_wayland files_vulkan_wsi += files('wsi_common_wayland.c') - files_vulkan_wsi += wp_files['commit-queue-v1'] + files_vulkan_wsi += wp_files['gamescope-commit-queue-v1'] #files_vulkan_wsi += wp_files['commit-timing-v1'] files_vulkan_wsi += wp_files['linux-dmabuf-unstable-v1'] files_vulkan_wsi += wp_files['presentation-time'] diff --git a/src/vulkan/wsi/wsi_common_wayland.c b/src/vulkan/wsi/wsi_common_wayland.c index 5e05f0488dc..5f76491e7a1 100644 --- a/src/vulkan/wsi/wsi_common_wayland.c +++ b/src/vulkan/wsi/wsi_common_wayland.c @@ -41,7 +41,7 @@ #include "vk_util.h" #include "wsi_common_entrypoints.h" #include "wsi_common_private.h" -#include "commit-queue-v1-client-protocol.h" +#include "gamescope-commit-queue-v1-client-protocol.h" #include "linux-dmabuf-unstable-v1-client-protocol.h" #include "presentation-time-client-protocol.h" @@ -112,7 +112,7 @@ struct wsi_wl_display { /* users want per-chain wsi_wl_swapchain->present_ids.wp_presentation */ struct wp_presentation *wp_presentation_notwrapped; - struct wp_commit_queue_manager_v1 *commit_queue_manager; + struct gamescope_commit_queue_manager_v1 *commit_queue_manager; struct wsi_wayland *wsi_wl; @@ -2025,8 +2025,8 @@ set_timestamp(struct wsi_wl_swapchain *chain) timespec_from_nsec(&target_ts, target); - wp_commit_queue_v1_set_queue_mode(chain->commit_queue, - WP_COMMIT_QUEUE_V1_QUEUE_MODE_FIFO); + gamescope_commit_queue_v1_set_queue_mode(chain->commit_queue, + GAMESCOPE_COMMIT_QUEUE_V1_QUEUE_MODE_FIFO); chain->last_target_time = target; } @@ -2272,7 +2272,7 @@ wsi_wl_swapchain_chain_free(struct wsi_wl_swapchain *chain, } if (chain->commit_queue) - wp_commit_queue_v1_destroy(chain->commit_queue); + gamescope_commit_queue_v1_destroy(chain->commit_queue); wsi_swapchain_finish(&chain->base); } @@ -2438,7 +2438,7 @@ wsi_wl_surface_create_swapchain(VkIcdSurfaceBase *icd_surface, chain->legacy_fifo_ready = true; struct wsi_wl_display *dpy = chain->wsi_wl_surface->display; if (dpy->commit_queue_manager) { - chain->commit_queue = wp_commit_queue_manager_v1_get_queue_controller(dpy->commit_queue_manager, + chain->commit_queue = gamescope_commit_queue_manager_v1_get_queue_controller(dpy->commit_queue_manager, chain->wsi_wl_surface->surface); chain->can_timestamp = true; } -- 2.42.0 From 4aa431b174c37b3bc0748ddc9bd2ca9c808881e2 Mon Sep 17 00:00:00 2001 From: Bas Nieuwenhuizen Date: Fri, 14 Jan 2022 15:58:45 +0100 Subject: [PATCH 5/8] STEAMOS: radv: min image count override for FH5 Otherwise in combination with the vblank time reservation in gamescope the game could get stuck in low power states. --- src/util/00-radv-defaults.conf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/util/00-radv-defaults.conf b/src/util/00-radv-defaults.conf index 9434dba1e3f..3c3ae562eb9 100644 --- a/src/util/00-radv-defaults.conf +++ b/src/util/00-radv-defaults.conf @@ -186,5 +186,9 @@ Application bugs worked around in this file: + + + -- 2.42.0 From 88327ee8507a323f48e43eeb87cf1f102d04f174 Mon Sep 17 00:00:00 2001 From: Bas Nieuwenhuizen Date: Mon, 21 Feb 2022 18:43:54 +0100 Subject: [PATCH 6/8] STEAMOS: Dynamic swapchain override for gamescope limiter --- src/loader/loader_dri3_helper.c | 42 +++++++++++++++++++++++++++++++-- src/loader/loader_dri3_helper.h | 1 + src/loader/meson.build | 2 +- src/vulkan/wsi/wsi_common_x11.c | 38 +++++++++++++++++++++++++++++ 4 files changed, 80 insertions(+), 3 deletions(-) diff --git a/src/loader/loader_dri3_helper.c b/src/loader/loader_dri3_helper.c index 32135770e9d..abc79dda97e 100644 --- a/src/loader/loader_dri3_helper.c +++ b/src/loader/loader_dri3_helper.c @@ -289,6 +289,30 @@ dri3_update_max_num_back(struct loader_dri3_drawable *draw) } } +static unsigned +gamescope_swapchain_override() +{ + const char *path = getenv("GAMESCOPE_LIMITER_FILE"); + if (!path) + return 0; + + static simple_mtx_t mtx = SIMPLE_MTX_INITIALIZER; + static int fd = -1; + + simple_mtx_lock(&mtx); + if (fd < 0) { + fd = open(path, O_RDONLY); + } + simple_mtx_unlock(&mtx); + + if (fd < 0) + return 0; + + uint32_t override_value = 0; + pread(fd, &override_value, sizeof(override_value), 0); + return override_value; +} + void loader_dri3_set_swap_interval(struct loader_dri3_drawable *draw, int interval) { @@ -303,10 +327,12 @@ loader_dri3_set_swap_interval(struct loader_dri3_drawable *draw, int interval) * PS. changing from value A to B and A < B won't cause swap out of order but * may still gets wrong target_msc value at the beginning. */ - if (draw->swap_interval != interval) + if (draw->orig_swap_interval != interval) loader_dri3_swapbuffer_barrier(draw); - draw->swap_interval = interval; + draw->orig_swap_interval = interval; + if (gamescope_swapchain_override() != 1) + draw->swap_interval = interval; } static void @@ -438,6 +464,12 @@ loader_dri3_drawable_init(xcb_connection_t *conn, draw->swap_interval = dri_get_initial_swap_interval(draw->dri_screen_render_gpu, draw->ext->config); + draw->orig_swap_interval = draw->swap_interval; + + unsigned gamescope_override = gamescope_swapchain_override(); + if (gamescope_override == 1) + draw->swap_interval = 1; + dri3_update_max_num_back(draw); /* Create a new drawable */ @@ -1092,6 +1124,12 @@ loader_dri3_swap_buffers_msc(struct loader_dri3_drawable *draw, if (draw->type == LOADER_DRI3_DRAWABLE_WINDOW) { dri3_fence_reset(draw->conn, back); + unsigned gamescope_override = gamescope_swapchain_override(); + if (gamescope_override == 1) + draw->swap_interval = 1; + else + draw->swap_interval = draw->orig_swap_interval; + /* Compute when we want the frame shown by taking the last known * successful MSC and adding in a swap interval for each outstanding swap * request. target_msc=divisor=remainder=0 means "Use glXSwapBuffers() diff --git a/src/loader/loader_dri3_helper.h b/src/loader/loader_dri3_helper.h index 1fd340bd145..b8f5eaaf190 100644 --- a/src/loader/loader_dri3_helper.h +++ b/src/loader/loader_dri3_helper.h @@ -178,6 +178,7 @@ struct loader_dri3_drawable { bool block_on_depleted_buffers; bool queries_buffer_age; int swap_interval; + int orig_swap_interval; struct loader_dri3_extensions *ext; const struct loader_dri3_vtable *vtable; diff --git a/src/loader/meson.build b/src/loader/meson.build index 35f9991ba2f..154cf809a69 100644 --- a/src/loader/meson.build +++ b/src/loader/meson.build @@ -29,7 +29,7 @@ if with_platform_x11 and with_dri3 dependencies : [ idep_mesautil, dep_libdrm, dep_xcb_dri3, dep_xcb_present, dep_xcb_sync, dep_xshmfence, - dep_xcb_xfixes, + dep_xcb_xfixes, dep_xcb_xrandr, idep_mesautil ], build_by_default : false, ) diff --git a/src/vulkan/wsi/wsi_common_x11.c b/src/vulkan/wsi/wsi_common_x11.c index 72ff193d30a..996fc230673 100644 --- a/src/vulkan/wsi/wsi_common_x11.c +++ b/src/vulkan/wsi/wsi_common_x11.c @@ -48,6 +48,7 @@ #include "util/hash_table.h" #include "util/os_file.h" #include "util/os_time.h" +#include "util/simple_mtx.h" #include "util/u_debug.h" #include "util/u_thread.h" #include "util/xmlconfig.h" @@ -219,6 +220,30 @@ wsi_x11_detect_xwayland(xcb_connection_t *conn, return is_xwayland; } +static unsigned +gamescope_swapchain_override() +{ + const char *path = getenv("GAMESCOPE_LIMITER_FILE"); + if (!path) + return 0; + + static simple_mtx_t mtx = SIMPLE_MTX_INITIALIZER; + static int fd = -1; + + simple_mtx_lock(&mtx); + if (fd < 0) { + fd = open(path, O_RDONLY); + } + simple_mtx_unlock(&mtx); + + if (fd < 0) + return 0; + + uint32_t override_value = 0; + pread(fd, &override_value, sizeof(override_value), 0); + return override_value; +} + static struct wsi_x11_connection * wsi_x11_connection_create(struct wsi_device *wsi_dev, xcb_connection_t *conn) @@ -1103,6 +1128,8 @@ struct x11_swapchain { /* Total number of images returned to application in AcquireNextImage. */ uint64_t present_poll_acquire_count; + VkPresentModeKHR orig_present_mode; + struct x11_image images[0]; }; VK_DEFINE_NONDISP_HANDLE_CASTS(x11_swapchain, base.base, VkSwapchainKHR, @@ -1852,6 +1879,12 @@ x11_queue_present(struct wsi_swapchain *anv_chain, if (chain->status < 0) return chain->status; + unsigned gamescope_override = gamescope_swapchain_override(); + if ((gamescope_override == 1 && chain->base.present_mode != VK_PRESENT_MODE_FIFO_KHR) || + (gamescope_override != 1 && chain->base.present_mode != chain->orig_present_mode)) { + return x11_swapchain_result(chain, VK_ERROR_OUT_OF_DATE_KHR); + } + if (damage && damage->pRectangles && damage->rectangleCount > 0 && damage->rectangleCount <= MAX_DAMAGE_RECTS) { xcb_rectangle_t rects[MAX_DAMAGE_RECTS]; @@ -2610,6 +2643,10 @@ x11_surface_create_swapchain(VkIcdSurfaceBase *icd_surface, xcb_void_cookie_t cookie; VkResult result; VkPresentModeKHR present_mode = wsi_swapchain_get_present_mode(wsi_device, pCreateInfo); + VkPresentModeKHR orig_present_mode = present_mode; + + if (gamescope_swapchain_override() == 1) + present_mode = VK_PRESENT_MODE_FIFO_KHR; assert(pCreateInfo->sType == VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR); @@ -2722,6 +2759,7 @@ x11_surface_create_swapchain(VkIcdSurfaceBase *icd_surface, chain->base.wait_for_present = x11_wait_for_present; chain->base.release_images = x11_release_images; chain->base.present_mode = present_mode; + chain->orig_present_mode = orig_present_mode; chain->base.image_count = num_images; chain->conn = conn; chain->window = window; -- 2.42.0