wsi/wayland: Support VK_KHR_present_wait
authorDaniel Stone <daniels@collabora.com>
Wed, 11 May 2022 18:47:52 +0000 (19:47 +0100)
committerMarge Bot <emma+marge@anholt.net>
Wed, 3 May 2023 16:09:10 +0000 (16:09 +0000)
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>

src/egl/wayland/wayland-drm/meson.build
src/vulkan/wsi/meson.build
src/vulkan/wsi/wsi_common_wayland.c

index 7bb9237..6ab5b92 100644 (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
index 08414a3..163fb22 100644 (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
index 154d127..1ca62da 100644 (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)
@@ -1561,6 +1590,173 @@ wsi_wl_swapchain_set_present_mode(struct wsi_swapchain *wsi_chain,
 }
 
 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,
                                     uint32_t *image_index)
@@ -1637,6 +1833,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)
 {
    struct wsi_wl_swapchain *chain = data;
@@ -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++) {