c/main: Hookup up new frame timing code

This commit is contained in:
Jakob Bornecrantz 2021-01-05 18:09:32 +00:00
parent a40c2e7d50
commit fac1ce4a5a
6 changed files with 391 additions and 103 deletions

View file

@ -111,116 +111,52 @@ compositor_end_session(struct xrt_compositor *xc)
return XRT_SUCCESS;
}
/*!
* @brief Utility for waiting (for rendering purposes) until the next vsync or a
* specified time point, whichever comes first.
*
* Only for rendering - this will busy-wait if needed.
*
* @return true if we waited until the time indicated
*
* @todo In the future, this may differ between platforms since some have ways
* to directly wait on a vsync.
*/
static bool
compositor_wait_vsync_or_time(struct comp_compositor *c, int64_t wake_up_time)
{
int64_t now_ns = os_monotonic_get_ns();
/*!
* @todo this is not accurate, but it serves the purpose of not letting
* us sleep longer than the next vsync usually
*/
int64_t next_vsync = now_ns + c->settings.nominal_frame_interval_ns / 2;
bool ret = true;
// Sleep until the sooner of vsync or our deadline.
if (next_vsync < wake_up_time) {
ret = false;
wake_up_time = next_vsync;
}
int64_t wait_duration = wake_up_time - now_ns;
if (wait_duration <= 0) {
// Don't wait at all
return ret;
}
if (wait_duration > 1000000) {
os_nanosleep(wait_duration - (wait_duration % 1000000));
}
// Busy-wait for fine-grained delays.
while (now_ns < wake_up_time) {
now_ns = os_monotonic_get_ns();
}
return ret;
}
static xrt_result_t
compositor_wait_frame(struct xrt_compositor *xc,
int64_t *out_frame_id,
uint64_t *predicted_display_time,
uint64_t *predicted_display_period)
uint64_t *out_predicted_display_time_ns,
uint64_t *out_predicted_display_period_ns)
{
struct comp_compositor *c = comp_compositor(xc);
// A little bit easier to read.
int64_t interval_ns = (int64_t)c->settings.nominal_frame_interval_ns;
uint64_t interval_ns = (int64_t)c->settings.nominal_frame_interval_ns;
int64_t now_ns = os_monotonic_get_ns();
comp_target_update_timings(c->target);
COMP_SPEW(c, "WAIT_FRAME at %8.3fms", ns_to_ms(now_ns));
assert(c->frame.waited.id == -1);
if (c->last_next_display_time == 0) {
// First frame, we'll just assume we will display immediately
int64_t frame_id = -1;
uint64_t wake_up_time_ns = 0;
uint64_t present_slop_ns = 0;
uint64_t desired_present_time_ns = 0;
uint64_t predicted_display_time_ns = 0;
comp_target_calc_frame_timings(c->target, //
&frame_id, //
&wake_up_time_ns, //
&desired_present_time_ns, //
&present_slop_ns, //
&predicted_display_time_ns); //
*predicted_display_period = interval_ns;
c->last_next_display_time = now_ns + interval_ns;
*predicted_display_time = c->last_next_display_time;
*out_frame_id = c->last_next_display_time;
c->frame.waited.id = frame_id;
c->frame.waited.desired_present_time_ns = desired_present_time_ns;
c->frame.waited.present_slop_ns = present_slop_ns;
COMP_SPEW(c,
"WAIT_FRAME Finished at %8.3fms, predicted display "
"time %8.3fms, period %8.3fms",
ns_to_ms(now_ns), ns_to_ms(*predicted_display_time), ns_to_ms(*predicted_display_period));
return XRT_SUCCESS;
uint64_t now_ns = os_monotonic_get_ns();
if (now_ns < wake_up_time_ns) {
os_nanosleep(wake_up_time_ns - now_ns);
}
// First estimate of next display time.
while (1) {
now_ns = os_monotonic_get_ns();
comp_target_mark_wake_up(c->target, frame_id, now_ns);
int64_t render_time_ns = c->expected_app_duration_ns + c->frame_overhead_ns;
int64_t swap_interval = ceilf((float)render_time_ns / interval_ns);
int64_t render_interval_ns = swap_interval * interval_ns;
int64_t next_display_time = c->last_next_display_time + render_interval_ns;
/*!
* @todo adjust next_display_time to be a multiple of
* interval_ns from c->last_frame_time_ns
*/
comp_target_update_timings(c->target);
while ((next_display_time - render_time_ns) < now_ns) {
// we can't unblock in the past
next_display_time += render_interval_ns;
}
if (compositor_wait_vsync_or_time(c, (next_display_time - render_time_ns))) {
// True return val means we actually waited for the
// deadline.
*predicted_display_period = next_display_time - c->last_next_display_time;
*predicted_display_time = next_display_time;
*out_frame_id = c->last_next_display_time;
*out_frame_id = frame_id;
*out_predicted_display_time_ns = predicted_display_time_ns;
*out_predicted_display_period_ns = interval_ns;
c->last_next_display_time = next_display_time;
COMP_SPEW(c,
"WAIT_FRAME Finished at %8.3fms, predicted "
"display time %8.3fms, period %8.3fms",
ns_to_ms(now_ns), ns_to_ms(*predicted_display_time),
ns_to_ms(*predicted_display_period));
return XRT_SUCCESS;
}
}
return XRT_SUCCESS;
}
static xrt_result_t
@ -1356,6 +1292,8 @@ xrt_gfx_provider_create_system(struct xrt_device *xdev, struct xrt_system_compos
c->base.base.layer_commit = compositor_layer_commit;
c->base.base.poll_events = compositor_poll_events;
c->base.base.destroy = compositor_destroy;
c->frame.waited.id = -1;
c->frame.rendering.id = -1;
c->system.create_native_compositor = system_compositor_create_native_compositor;
c->system.destroy = system_compositor_destroy;
c->xdev = xdev;

View file

@ -152,6 +152,16 @@ struct comp_shaders
VkShaderModule layer_frag;
};
/*!
* Tracking frame state.
*/
struct comp_frame
{
int64_t id;
uint64_t desired_present_time_ns;
uint64_t present_slop_ns;
};
/*!
* Main compositor struct tying everything in the compositor together.
*
@ -221,6 +231,12 @@ struct comp_compositor
struct u_var_timing *debug_var;
} compositor_frame_times;
struct
{
struct comp_frame waited;
struct comp_frame rendering;
} frame;
/*!
* @brief Estimated rendering time per frame of the application.
*

View file

@ -10,6 +10,8 @@
#include "xrt/xrt_compositor.h"
#include "os/os_time.h"
#include "math/m_space.h"
#include "util/u_misc.h"
@ -93,7 +95,7 @@ static void
renderer_acquire_swapchain_image(struct comp_renderer *r);
static void
renderer_present_swapchain_image(struct comp_renderer *r);
renderer_present_swapchain_image(struct comp_renderer *r, uint64_t desired_present_time_ns, uint64_t present_slop_ns);
static void
renderer_destroy(struct comp_renderer *r);
@ -622,14 +624,37 @@ comp_renderer_set_equirect2_layer(struct comp_renderer *r,
void
comp_renderer_draw(struct comp_renderer *r)
{
struct comp_target *ct = r->c->target;
struct comp_compositor *c = r->c;
assert(c->frame.rendering.id == -1);
c->frame.rendering = c->frame.waited;
c->frame.waited.id = -1;
comp_target_mark_begin(ct, c->frame.rendering.id, os_monotonic_get_ns());
comp_target_flush(ct);
comp_target_update_timings(ct);
renderer_acquire_swapchain_image(r);
comp_target_update_timings(ct);
comp_target_mark_submit(ct, c->frame.rendering.id, os_monotonic_get_ns());
renderer_get_view_projection(r);
comp_layer_renderer_draw(r->lr);
comp_target_flush(r->c->target);
comp_target_update_timings(ct);
renderer_acquire_swapchain_image(r);
renderer_submit_queue(r);
renderer_present_swapchain_image(r);
renderer_present_swapchain_image(r, c->frame.rendering.desired_present_time_ns,
c->frame.rendering.present_slop_ns);
// Clear the frame.
c->frame.rendering.id = -1;
/*
* This fixes a lot of validation issues as it makes sure that the
@ -639,6 +664,8 @@ comp_renderer_draw(struct comp_renderer *r)
* This is done after a swap so isn't time critical.
*/
renderer_wait_gpu_idle(r);
comp_target_update_timings(ct);
}
static void
@ -743,11 +770,12 @@ renderer_acquire_swapchain_image(struct comp_renderer *r)
}
static void
renderer_present_swapchain_image(struct comp_renderer *r)
renderer_present_swapchain_image(struct comp_renderer *r, uint64_t desired_present_time_ns, uint64_t present_slop_ns)
{
VkResult ret;
ret = comp_target_present(r->c->target, r->queue, r->current_buffer, r->semaphores.render_complete);
ret = comp_target_present(r->c->target, r->queue, r->current_buffer, r->semaphores.render_complete,
desired_present_time_ns, present_slop_ns);
if (ret == VK_ERROR_OUT_OF_DATE_KHR) {
renderer_resize(r);
return;

View file

@ -20,6 +20,16 @@ extern "C" {
#endif
/*!
* For marking timepoints on a frame's lifetime, not a async event.
*/
enum comp_target_timing_point
{
COMP_TARGET_TIMING_POINT_WAKE_UP, //<! Woke up after sleeping in wait frame.
COMP_TARGET_TIMING_POINT_BEGIN, //<! Began CPU side work for GPU.
COMP_TARGET_TIMING_POINT_SUBMIT, //<! Submitted work to the GPU.
};
/*!
* Image and view pair for @ref comp_target.
*
@ -60,6 +70,13 @@ struct comp_target
//! Transformation of the current surface, required for pre-rotation
VkSurfaceTransformFlagBitsKHR surface_transform;
/*
*
* Vulkan functions.
*
*/
/*!
* Do any initialization that is required to happen before Vulkan has
* been loaded.
@ -91,13 +108,59 @@ struct comp_target
/*!
* Present the image at index to the screen.
*/
VkResult (*present)(struct comp_target *ct, VkQueue queue, uint32_t index, VkSemaphore semaphore);
VkResult (*present)(struct comp_target *ct,
VkQueue queue,
uint32_t index,
VkSemaphore semaphore,
uint64_t desired_present_time_ns,
uint64_t present_slop_ns);
/*!
* Flush any WSI state before rendering.
*/
void (*flush)(struct comp_target *ct);
/*
*
* Timing functions.
*
*/
/*!
* Predict when the next frame should be started and when it will be
* turned into photons by the hardware.
*/
void (*calc_frame_timings)(struct comp_target *ct,
int64_t *out_frame_id,
uint64_t *out_wake_up_time_ns,
uint64_t *out_desired_present_time_ns,
uint64_t *out_present_slop_ns,
uint64_t *out_predicted_display_time_ns);
/*!
* The compositor tells the target a timing information about a single
* timing point on the frames lifecycle.
*/
void (*mark_timing_point)(struct comp_target *ct,
enum comp_target_timing_point point,
int64_t frame_id,
uint64_t when_ns);
/*!
* Update timing information for this target, this function should be
* lightweight and is called multiple times during a frame to make sure
* that we get the timing data as soon as possible.
*/
VkResult (*update_timings)(struct comp_target *ct);
/*
*
* Misc functions.
*
*/
/*!
* If the target can show a title (like a window) set the title.
*/
@ -170,10 +233,21 @@ comp_target_acquire(struct comp_target *ct, VkSemaphore semaphore, uint32_t *out
* @ingroup comp_main
*/
static inline VkResult
comp_target_present(struct comp_target *ct, VkQueue queue, uint32_t index, VkSemaphore semaphore)
comp_target_present(struct comp_target *ct,
VkQueue queue,
uint32_t index,
VkSemaphore semaphore,
uint64_t desired_present_time_ns,
uint64_t present_slop_ns)
{
return ct->present(ct, queue, index, semaphore);
return ct->present( //
ct, //
queue, //
index, //
semaphore, //
desired_present_time_ns, //
present_slop_ns); //
}
/*!
@ -188,6 +262,80 @@ comp_target_flush(struct comp_target *ct)
ct->flush(ct);
}
/*!
* @copydoc comp_target::calc_frame_timings
*
* @public @memberof comp_target
* @ingroup comp_main
*/
static inline void
comp_target_calc_frame_timings(struct comp_target *ct,
int64_t *out_frame_id,
uint64_t *out_wake_up_time_ns,
uint64_t *out_desired_present_time_ns,
uint64_t *out_present_slop_ns,
uint64_t *out_predicted_display_time_ns)
{
ct->calc_frame_timings( //
ct, //
out_frame_id, //
out_wake_up_time_ns, //
out_desired_present_time_ns, //
out_present_slop_ns, //
out_predicted_display_time_ns); //
}
/*!
* Quick helper for marking wake up.
* @copydoc comp_target::mark_timing_point
*
* @public @memberof comp_target
* @ingroup comp_main
*/
static inline void
comp_target_mark_wake_up(struct comp_target *ct, int64_t frame_id, uint64_t when_woke_ns)
{
ct->mark_timing_point(ct, COMP_TARGET_TIMING_POINT_WAKE_UP, frame_id, when_woke_ns);
}
/*!
* Quick helper for marking begin.
* @copydoc comp_target::mark_timing_point
*
* @public @memberof comp_target
* @ingroup comp_main
*/
static inline void
comp_target_mark_begin(struct comp_target *ct, int64_t frame_id, uint64_t when_began_ns)
{
ct->mark_timing_point(ct, COMP_TARGET_TIMING_POINT_BEGIN, frame_id, when_began_ns);
}
/*!
* Quick helper for marking submit.
* @copydoc comp_target::mark_timing_point
*
* @public @memberof comp_target
* @ingroup comp_main
*/
static inline void
comp_target_mark_submit(struct comp_target *ct, int64_t frame_id, uint64_t when_submitted_ns)
{
ct->mark_timing_point(ct, COMP_TARGET_TIMING_POINT_SUBMIT, frame_id, when_submitted_ns);
}
/*!
* @copydoc comp_target::update_timings
*
* @public @memberof comp_target
* @ingroup comp_main
*/
static inline VkResult
comp_target_update_timings(struct comp_target *ct)
{
return ct->update_timings(ct);
}
/*!
* @copydoc comp_target::set_title
*

View file

@ -8,7 +8,10 @@
* @ingroup comp_main
*/
#include "xrt/xrt_config_os.h"
#include "util/u_misc.h"
#include "util/u_timing.h"
#include "main/comp_compositor.h"
#include "main/comp_target_swapchain.h"
@ -17,6 +20,7 @@
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
/*
@ -91,6 +95,17 @@ comp_target_swapchain_create_images(struct comp_target *ct,
VkBool32 supported;
VkResult ret;
#ifndef XRT_OS_ANDROID
if (cts->uft == NULL && vk->has_GOOGLE_display_timing) {
u_frame_timing_display_timing_create(ct->c->settings.nominal_frame_interval_ns, &cts->uft);
} else if (cts->uft == NULL) {
u_frame_timing_fake_create(ct->c->settings.nominal_frame_interval_ns, &cts->uft);
}
#else
COMP_INFO(ct->c, "Always using the fake timing code on Android.");
u_frame_timing_fake_create(ct->c->settings.nominal_frame_interval_ns, &cts->uft);
#endif
// Free old image views.
comp_target_swapchain_destroy_image_views(cts);
@ -250,13 +265,33 @@ comp_target_swapchain_acquire_next_image(struct comp_target *ct, VkSemaphore sem
}
static VkResult
comp_target_swapchain_present(struct comp_target *ct, VkQueue queue, uint32_t index, VkSemaphore semaphore)
comp_target_swapchain_present(struct comp_target *ct,
VkQueue queue,
uint32_t index,
VkSemaphore semaphore,
uint64_t desired_present_time_ns,
uint64_t present_slop_ns)
{
struct comp_target_swapchain *cts = (struct comp_target_swapchain *)ct;
struct vk_bundle *vk = get_vk(cts);
assert(cts->current_frame_id >= 0);
assert(cts->current_frame_id <= UINT32_MAX);
VkPresentTimeGOOGLE times = {
.presentID = (uint32_t)cts->current_frame_id,
.desiredPresentTime = desired_present_time_ns - present_slop_ns,
};
VkPresentTimesInfoGOOGLE timings = {
.sType = VK_STRUCTURE_TYPE_PRESENT_TIMES_INFO_GOOGLE,
.swapchainCount = 1,
.pTimes = &times,
};
VkPresentInfoKHR presentInfo = {
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
.pNext = vk->has_GOOGLE_display_timing ? &timings : NULL,
.waitSemaphoreCount = 1,
.pWaitSemaphores = &semaphore,
.swapchainCount = 1,
@ -467,6 +502,116 @@ comp_target_swapchain_create_image_views(struct comp_target_swapchain *cts)
}
/*
*
* Timing functions.
*
*/
static void
comp_target_swapchain_calc_frame_timings(struct comp_target *ct,
int64_t *out_frame_id,
uint64_t *out_wake_up_time_ns,
uint64_t *out_desired_present_time_ns,
uint64_t *out_present_slop_ns,
uint64_t *out_predicted_display_time_ns)
{
struct comp_target_swapchain *cts = (struct comp_target_swapchain *)ct;
int64_t frame_id = -1;
uint64_t wake_up_time_ns = 0;
uint64_t desired_present_time_ns = 0;
uint64_t present_slop_ns = 0;
uint64_t predicted_display_time_ns = 0;
uint64_t predicted_display_period_ns = 0;
uint64_t min_display_period_ns = 0;
u_frame_timing_predict(cts->uft, //
&frame_id, //
&wake_up_time_ns, //
&desired_present_time_ns, //
&present_slop_ns, //
&predicted_display_time_ns, //
&predicted_display_period_ns, //
&min_display_period_ns); //
cts->current_frame_id = frame_id;
*out_frame_id = frame_id;
*out_wake_up_time_ns = wake_up_time_ns;
*out_desired_present_time_ns = desired_present_time_ns;
*out_predicted_display_time_ns = predicted_display_time_ns;
*out_present_slop_ns = present_slop_ns;
}
static void
comp_target_swapchain_mark_timing_point(struct comp_target *ct,
enum comp_target_timing_point point,
int64_t frame_id,
uint64_t when_ns)
{
struct comp_target_swapchain *cts = (struct comp_target_swapchain *)ct;
assert(frame_id == cts->current_frame_id);
switch (point) {
case COMP_TARGET_TIMING_POINT_WAKE_UP:
u_frame_timing_mark_point(cts->uft, U_TIMING_POINT_WAKE_UP, cts->current_frame_id, when_ns);
break;
case COMP_TARGET_TIMING_POINT_BEGIN:
u_frame_timing_mark_point(cts->uft, U_TIMING_POINT_BEGIN, cts->current_frame_id, when_ns);
break;
case COMP_TARGET_TIMING_POINT_SUBMIT:
u_frame_timing_mark_point(cts->uft, U_TIMING_POINT_SUBMIT, cts->current_frame_id, when_ns);
break;
default: assert(false);
}
}
static VkResult
comp_target_swapchain_update_timings(struct comp_target *ct)
{
struct comp_target_swapchain *cts = (struct comp_target_swapchain *)ct;
struct comp_compositor *c = ct->c;
struct vk_bundle *vk = &c->vk;
if (!vk->has_GOOGLE_display_timing) {
return VK_SUCCESS;
}
if (cts->swapchain.handle == VK_NULL_HANDLE) {
return VK_SUCCESS;
}
uint32_t count = 0;
c->vk.vkGetPastPresentationTimingGOOGLE( //
vk->device, //
cts->swapchain.handle, //
&count, //
NULL); //
if (count <= 0) {
return VK_SUCCESS;
}
VkPastPresentationTimingGOOGLE timings[count];
c->vk.vkGetPastPresentationTimingGOOGLE( //
vk->device, //
cts->swapchain.handle, //
&count, //
timings); //
for (uint32_t i = 0; i < count; i++) {
u_frame_timing_info(cts->uft, //
timings[i].presentID, //
timings[i].desiredPresentTime, //
timings[i].actualPresentTime, //
timings[i].earliestPresentTime, //
timings[i].presentMargin); //
}
return VK_SUCCESS;
}
/*
*
* 'Exported' functions.
@ -495,6 +640,8 @@ comp_target_swapchain_cleanup(struct comp_target_swapchain *cts)
NULL); //
cts->swapchain.handle = VK_NULL_HANDLE;
}
u_frame_timing_destroy(&cts->uft);
}
void
@ -503,4 +650,7 @@ comp_target_swapchain_init_set_fnptrs(struct comp_target_swapchain *cts)
cts->base.create_images = comp_target_swapchain_create_images;
cts->base.acquire = comp_target_swapchain_acquire_next_image;
cts->base.present = comp_target_swapchain_present;
cts->base.calc_frame_timings = comp_target_swapchain_calc_frame_timings;
cts->base.mark_timing_point = comp_target_swapchain_mark_timing_point;
cts->base.update_timings = comp_target_swapchain_update_timings;
}

View file

@ -26,6 +26,8 @@ extern "C" {
*
*/
struct u_frame_timing;
/*!
* Wraps and manage VkSwapchainKHR and VkSurfaceKHR, used by @ref comp code.
*
@ -36,6 +38,12 @@ struct comp_target_swapchain
//! Base target.
struct comp_target base;
//! Frame timing tracker.
struct u_frame_timing *uft;
//! Also works as a frame index.
int64_t current_frame_id;
struct
{
VkSwapchainKHR handle;