Commit 52c21bbc authored by Niklas Haas's avatar Niklas Haas

swapchain: improve latency consistency and documentation

This mirrors a similar change in mpv, which helped reduce vsync jitter
measurements by including the time of frame acquisition in the
`swap_buffers` call. In general, doing things this way around is nicer
for the user. In typical swapchain implementations, it also "does the
right thing" w.r.t actually blocking until the buffer swap.

As a side effect of the necessary internal cache metadata, we also make
the vulkan swapchain more robust against API misuse (especially
out-of-order / non-lockstepped swapchain calls).
parent a588a3e7
......@@ -74,12 +74,11 @@ struct pl_swapchain_frame {
// Retrieve a new frame from the swapchain. Returns whether successful. It's
// worth noting that this function can fail sporadically for benign reasons,
// for example the window being invisible or inaccessible. This function may
// block until an image is available, which may be the case if the GPU is
// rendering frames significantly faster than the display can output them. It
// may also be non-blocking, so users shouldn't rely on this call alone in
// order to meter rendering speed. (Specifics depend on the underlying graphics
// API)
// for example the window being invisible or inaccessible. This function *may*
// block until an image is available, which may be the case if the user not
// calling `pl_swapchain_swap_buffers` often enough to meter rendering. It may
// also be non-blocking, so users shouldn't rely on this call alone in order to
// meter rendering speed. (Specifics depend on the underlying graphics API)
bool pl_swapchain_start_frame(const struct pl_swapchain *sw,
struct pl_swapchain_frame *out_frame);
......@@ -96,8 +95,9 @@ bool pl_swapchain_submit_frame(const struct pl_swapchain *sw);
// Performs a "buffer swap", or some generalization of the concept. In layman's
// terms, this blocks until the execution of the Nth previously submitted frame
// has been "made complete" in some sense. (The N derives from the swapchain's
// built-in latency. See `pl_swapchain_latency` for more information).
// has been "made complete/visible" in some sense. (The N derives from the
// swapchain's built-in latency. See `pl_swapchain_latency` for more
// information).
//
// Users should include this call in their rendering loops in order to make
// sure they aren't submitting rendering commands faster than the GPU can
......@@ -110,11 +110,11 @@ bool pl_swapchain_submit_frame(const struct pl_swapchain *sw);
// struct pl_swapchain_frame frame;
// bool ok = pl_swapchain_start_frame(swapchain, &frame);
// if (!ok) {
// /* wait some time, or decide to stop rendering */
// // wait some time, or decide to stop rendering
// continue;
// }
//
// /* do some rendering with frame.fbo */
// // do some rendering with frame.fbo
//
// ok = pl_swapchain_submit_frame(swapchain);
// if (!ok)
......@@ -125,8 +125,7 @@ bool pl_swapchain_submit_frame(const struct pl_swapchain *sw);
//
// The duration this function blocks for, if at all, may be very inconsistent
// and should not be used as an authoritative source of vsync timing
// information without sufficient smoothing/filtering (and if so, the time that
// `start_frame` blocked for should also be included).
// information without sufficient smoothing/filtering.
void pl_swapchain_swap_buffers(const struct pl_swapchain *sw);
#endif // LIBPLACEBO_SWAPCHAIN_H_
......@@ -34,6 +34,7 @@ struct priv {
int frames_in_flight; // number of frames currently queued
struct pl_color_repr color_repr;
struct pl_color_space color_space;
struct pl_swapchain_frame cached_frame; // cached next frame
// state of the images:
const struct pl_tex **images; // pl_tex wrappers for the VkImages
......@@ -440,6 +441,8 @@ static bool vk_sw_recreate(const struct pl_swapchain *sw)
VkImage *vkimages = NULL;
int num_images = 0;
p->cached_frame.fbo = NULL;
p->last_imgidx = -1;
// It's invalid to trigger another swapchain recreation while there's more
// than one swapchain already active, so we need to flush any pending
......@@ -534,6 +537,18 @@ static bool vk_sw_start_frame(const struct pl_swapchain *sw,
if (!p->swapchain && !vk_sw_recreate(sw))
return false;
// If we already had a cached frame, re-use that directly
if (p->cached_frame.fbo) {
*out_frame = p->cached_frame;
p->cached_frame.fbo = NULL;
return true;
}
if (p->last_imgidx >= 0) {
PL_ERR(sw, "pl_swapchain_start_frame called twice in a row?");
return false;
}
VkSemaphore sem_in = p->sems_in[p->idx_sems];
PL_TRACE(vk, "vkAcquireNextImageKHR signals %p", (void *) sem_in);
......@@ -570,6 +585,7 @@ static bool vk_sw_start_frame(const struct pl_swapchain *sw,
// If we've exhausted the number of attempts to recreate the swapchain,
// just give up silently and let the user retry some time later.
p->last_imgidx = -1;
return false;
}
......@@ -586,10 +602,18 @@ static bool vk_sw_submit_frame(const struct pl_swapchain *sw)
if (!p->swapchain)
return false;
if (p->last_imgidx < 0) {
PL_ERR(sw, "pl_swapchain_submit_frame called without start_frame?");
return false;
}
uint32_t imgidx = p->last_imgidx;
p->last_imgidx = -1;
VkSemaphore sem_out = p->sems_out[p->idx_sems++];
p->idx_sems %= p->num_sems;
pl_vulkan_hold(gpu, p->images[p->last_imgidx],
pl_vulkan_hold(gpu, p->images[imgidx],
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
VK_ACCESS_MEMORY_READ_BIT, sem_out);
......@@ -617,7 +641,7 @@ static bool vk_sw_submit_frame(const struct pl_swapchain *sw)
.pWaitSemaphores = &sem_out,
.swapchainCount = 1,
.pSwapchains = &p->swapchain,
.pImageIndices = &p->last_imgidx,
.pImageIndices = &imgidx,
};
PL_TRACE(vk, "vkQueuePresentKHR waits on %p", (void *) sem_out);
......@@ -645,6 +669,14 @@ static void vk_sw_swap_buffers(const struct pl_swapchain *sw)
while (p->frames_in_flight >= p->swapchain_depth)
vk_poll_commands(p->vk, 1000000); // 1 ms
// Pre-fetch the next swapchain image (if possible), since this will also
// typically block. If not possible, just ignore the error (the user
// will most likely run into it on the next `start_frame` call)
if (!p->cached_frame.fbo && p->last_imgidx < 0) {
if (!vk_sw_start_frame(sw, &p->cached_frame))
p->cached_frame.fbo = NULL;
}
}
static struct pl_sw_fns vulkan_swapchain = {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment