// Copyright 2020-2021, Collabora, Ltd. // SPDX-License-Identifier: BSL-1.0 /*! * @file * @brief Shared frame timing code. * @author Jakob Bornecrantz * @ingroup aux_util */ #include "util/u_time.h" #include "util/u_misc.h" #include "util/u_debug.h" #include "util/u_logging.h" #include "util/u_timing_render.h" #include #include #include /* * * Helpers * */ DEBUG_GET_ONCE_LOG_OPTION(ll, "U_TIMING_RENDER_LOG", U_LOGGING_WARN) #define RT_LOG_T(...) U_LOG_IFL_T(debug_get_log_option_ll(), __VA_ARGS__) #define RT_LOG_D(...) U_LOG_IFL_D(debug_get_log_option_ll(), __VA_ARGS__) #define RT_LOG_I(...) U_LOG_IFL_I(debug_get_log_option_ll(), __VA_ARGS__) #define RT_LOG_W(...) U_LOG_IFL_W(debug_get_log_option_ll(), __VA_ARGS__) #define RT_LOG_E(...) U_LOG_IFL_E(debug_get_log_option_ll(), __VA_ARGS__) #define DEBUG_PRINT_FRAME_ID() RT_LOG_T("%" PRIi64, frame_id) #define GET_INDEX_FROM_ID(URTH, ID) ((uint64_t)(ID) % ARRAY_SIZE((URTH)->frames)) static uint64_t min_period(const struct u_rt_helper *urth) { return urth->last_input.predicted_display_period_ns; } static uint64_t last_sample_displayed(const struct u_rt_helper *urth) { return urth->last_input.predicted_display_time_ns; } static uint64_t last_return_predicted_display(const struct u_rt_helper *urth) { return urth->last_returned_ns; } static uint64_t total_app_time_ns(const struct u_rt_helper *urth) { return urth->app.cpu_time_ns + urth->app.draw_time_ns; } static uint64_t total_app_and_compositor_time_ns(const struct u_rt_helper *urth) { return total_app_time_ns(urth) + urth->app.margin_ns + urth->last_input.extra_ns; } static uint64_t predict_display_time(struct u_rt_helper *urth) { // Now uint64_t now_ns = os_monotonic_get_ns(); // Error checking. uint64_t period_ns = min_period(urth); if (period_ns == 0) { assert(false && "Have not yet received and samples from timing driver."); return now_ns; } // Total app and compositor time to produce a frame uint64_t app_and_compositor_time_ns = total_app_and_compositor_time_ns(urth); // Start from the last time that the driver displayed something. uint64_t val = last_sample_displayed(urth); // Return a time after the last returned display time. while (val < last_return_predicted_display(urth)) { val += period_ns; } // Have to have enough time to perform app work. while ((val - app_and_compositor_time_ns) <= now_ns) { val += period_ns; } return val; } /* * * 'Exported' functions. * */ void u_rt_helper_client_clear(struct u_rt_helper *urth) { for (size_t i = 0; i < ARRAY_SIZE(urth->frames); i++) { urth->frames[i].state = U_RT_READY; urth->frames[i].frame_id = -1; } urth->app.cpu_time_ns = U_TIME_1MS_IN_NS * 2; urth->app.draw_time_ns = U_TIME_1MS_IN_NS * 2; urth->app.margin_ns = U_TIME_1MS_IN_NS / 2; } void u_rt_helper_init(struct u_rt_helper *urth) { U_ZERO(urth); u_rt_helper_client_clear(urth); } void u_rt_helper_predict(struct u_rt_helper *urth, int64_t *out_frame_id, uint64_t *out_wake_up_time, uint64_t *out_predicted_display_time, uint64_t *out_predicted_display_period) { int64_t frame_id = ++urth->frame_counter; *out_frame_id = frame_id; DEBUG_PRINT_FRAME_ID(); uint64_t predict_ns = predict_display_time(urth); urth->last_returned_ns = predict_ns; *out_wake_up_time = predict_ns - total_app_and_compositor_time_ns(urth); *out_predicted_display_time = predict_ns; *out_predicted_display_period = min_period(urth); size_t index = GET_INDEX_FROM_ID(urth, frame_id); assert(urth->frames[index].frame_id == -1); assert(urth->frames[index].state == U_RT_READY); /* * When the client should deliver the frame to us, take into account the * extra time needed by the main loop, plus a bit of extra time. */ uint64_t delivery_time_ns = predict_ns - urth->last_input.extra_ns - U_TIME_HALF_MS_IN_NS; urth->frames[index].when.predicted_ns = os_monotonic_get_ns(); urth->frames[index].state = U_RT_PREDICTED; urth->frames[index].frame_id = frame_id; urth->frames[index].predicted_delivery_time_ns = delivery_time_ns; } void u_rt_helper_mark(struct u_rt_helper *urth, int64_t frame_id, enum u_timing_point point, uint64_t when_ns) { DEBUG_PRINT_FRAME_ID(); size_t index = GET_INDEX_FROM_ID(urth, frame_id); assert(urth->frames[index].frame_id == frame_id); switch (point) { case U_TIMING_POINT_WAKE_UP: assert(urth->frames[index].state == U_RT_PREDICTED); urth->frames[index].when.wait_woke_ns = when_ns; urth->frames[index].state = U_RT_WAIT_LEFT; break; case U_TIMING_POINT_BEGIN: assert(urth->frames[index].state == U_RT_WAIT_LEFT); urth->frames[index].when.begin_ns = os_monotonic_get_ns(); urth->frames[index].state = U_RT_BEGUN; break; case U_TIMING_POINT_SUBMIT: default: assert(false); } } void u_rt_helper_mark_discarded(struct u_rt_helper *urth, int64_t frame_id) { DEBUG_PRINT_FRAME_ID(); size_t index = GET_INDEX_FROM_ID(urth, frame_id); assert(urth->frames[index].frame_id == frame_id); assert(urth->frames[index].state == U_RT_WAIT_LEFT || urth->frames[index].state == U_RT_BEGUN); urth->frames[index].when.delivered_ns = os_monotonic_get_ns(); urth->frames[index].state = U_RT_READY; urth->frames[index].frame_id = -1; } void u_rt_helper_mark_delivered(struct u_rt_helper *urth, int64_t frame_id) { DEBUG_PRINT_FRAME_ID(); size_t index = GET_INDEX_FROM_ID(urth, frame_id); assert(urth->frames[index].frame_id == frame_id); assert(urth->frames[index].state == U_RT_BEGUN); uint64_t now_ns = os_monotonic_get_ns(); urth->frames[index].when.delivered_ns = now_ns; urth->frames[index].state = U_RT_READY; urth->frames[index].frame_id = -1; int64_t diff_ns = urth->frames[index].predicted_delivery_time_ns - now_ns; bool late = false; if (diff_ns < 0) { diff_ns = -diff_ns; late = true; } int64_t ms100 = diff_ns / (1000 * 10); RT_LOG_D("Delivered frame %i.%02ims %s.", (int)ms100 / 100, (int)ms100 % 100, late ? "late" : "early"); } void u_rt_helper_new_sample(struct u_rt_helper *urth, uint64_t predicted_display_time_ns, uint64_t predicted_display_period_ns, uint64_t extra_ns) { urth->last_input.predicted_display_time_ns = predicted_display_time_ns; urth->last_input.predicted_display_period_ns = predicted_display_period_ns; urth->last_input.extra_ns = extra_ns; }