1
0
Fork 0

clutter/frame-clock: Move "last" frame fields into structs

While in double buffering we only care about one previous presentation,
triple buffering will sometimes need to refer to the presentation from
two dispatches prior. So it might help to separate those frame stats
more clearly. This way each frame's dispatch and presentation times are
stored more cohesively such as to not be overwritten during overlapping
frame lifetimes.

Having two types of frame reference (dispatch and presentation) moving
at difference speeds meant that they could not be stored in a ring. Not
all dispatches become presentations and so storing them in a ring would
necessitate very complex conflict avoidance. Instead, a simple reference
counting model was used.

(cherry picked from commit 817a951b246b64fd1c4148e486d24017f71fc4c4)
Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3961>
Signed-off-by: Mingi Sung <sungmg@saltyming.net>
This commit is contained in:
Daniel van Vugt 2024-08-20 17:54:35 +08:00 committed by Mingi Sung
parent 6f955a0668
commit 2ebb183629
Signed by: sungmg
GPG key ID: 41BAFD6FFD8036C5

View file

@ -74,6 +74,20 @@ typedef enum _ClutterFrameClockState
CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED, CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED,
} ClutterFrameClockState; } ClutterFrameClockState;
typedef struct _Frame
{
int use_count;
int64_t dispatch_time_us;
int64_t dispatch_lateness_us;
int64_t presentation_time_us;
int64_t next_presentation_time_us;
int64_t flip_time_us;
int64_t dispatch_interval_us;
ClutterFrameInfoFlag presentation_flags;
gboolean has_next_presentation_time;
gboolean got_measurements;
} Frame;
struct _ClutterFrameClock struct _ClutterFrameClock
{ {
GObject parent; GObject parent;
@ -91,12 +105,12 @@ struct _ClutterFrameClock
ClutterFrameClockState state; ClutterFrameClockState state;
ClutterFrameClockMode mode; ClutterFrameClockMode mode;
int64_t last_dispatch_time_us;
int64_t last_dispatch_lateness_us;
int64_t last_presentation_time_us;
int64_t next_update_time_us; int64_t next_update_time_us;
ClutterFrameInfoFlag last_presentation_flags; Frame frame_pool[2];
Frame *prev_dispatch;
Frame *next_presentation;
Frame *prev_presentation;
gboolean is_next_presentation_time_valid; gboolean is_next_presentation_time_valid;
int64_t next_presentation_time_us; int64_t next_presentation_time_us;
@ -104,15 +118,10 @@ struct _ClutterFrameClock
gboolean has_next_frame_deadline; gboolean has_next_frame_deadline;
int64_t next_frame_deadline_us; int64_t next_frame_deadline_us;
gboolean has_last_next_presentation_time;
int64_t last_next_presentation_time_us;
/* Buffer must be submitted to KMS and GPU rendering must be finished /* Buffer must be submitted to KMS and GPU rendering must be finished
* this amount of time before the next presentation time. * this amount of time before the next presentation time.
*/ */
int64_t vblank_duration_us; int64_t vblank_duration_us;
/* Last KMS buffer submission time. */
int64_t last_flip_time_us;
/* Last time we promoted short-term maximum to long-term one */ /* Last time we promoted short-term maximum to long-term one */
int64_t longterm_promotion_us; int64_t longterm_promotion_us;
@ -121,8 +130,6 @@ struct _ClutterFrameClock
/* Short-term maximum update duration */ /* Short-term maximum update duration */
int64_t shortterm_max_update_duration_us; int64_t shortterm_max_update_duration_us;
/* If we got new measurements last frame. */
gboolean got_measurements_last_frame;
gboolean ever_got_measurements; gboolean ever_got_measurements;
gboolean pending_reschedule; gboolean pending_reschedule;
@ -135,8 +142,6 @@ struct _ClutterFrameClock
int n_missed_frames; int n_missed_frames;
int64_t missed_frame_report_time_us; int64_t missed_frame_report_time_us;
int64_t last_dispatch_interval_us;
int64_t deadline_evasion_us; int64_t deadline_evasion_us;
char *output_name; char *output_name;
@ -160,6 +165,49 @@ clutter_frame_clock_set_refresh_rate (ClutterFrameClock *frame_clock,
(int64_t) (0.5 + G_USEC_PER_SEC / refresh_rate); (int64_t) (0.5 + G_USEC_PER_SEC / refresh_rate);
} }
static Frame *
clutter_frame_clock_new_frame (ClutterFrameClock *frame_clock)
{
for (int i = 0; i < G_N_ELEMENTS (frame_clock->frame_pool); i++)
{
Frame *frame = &frame_clock->frame_pool[i];
if (frame->use_count == 0)
{
memset (frame, 0, sizeof (*frame));
frame->use_count = 1;
return frame;
}
}
g_assert_not_reached ();
return NULL;
}
static Frame *
ref_frame (Frame *frame)
{
frame->use_count++;
return frame;
}
static void
unref_frame (Frame *frame)
{
g_return_if_fail (frame->use_count > 0);
frame->use_count--;
}
static void
clear_frame (Frame **frame)
{
if (frame && *frame)
{
unref_frame (*frame);
*frame = NULL;
}
}
void void
clutter_frame_clock_add_timeline (ClutterFrameClock *frame_clock, clutter_frame_clock_add_timeline (ClutterFrameClock *frame_clock,
ClutterTimeline *timeline) ClutterTimeline *timeline)
@ -279,28 +327,35 @@ void
clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock, clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock,
ClutterFrameInfo *frame_info) ClutterFrameInfo *frame_info)
{ {
Frame *presented_frame;
COGL_TRACE_BEGIN_SCOPED (ClutterFrameClockNotifyPresented, COGL_TRACE_BEGIN_SCOPED (ClutterFrameClockNotifyPresented,
"Clutter::FrameClock::presented()"); "Clutter::FrameClock::presented()");
COGL_TRACE_DESCRIBE (ClutterFrameClockNotifyPresented, COGL_TRACE_DESCRIBE (ClutterFrameClockNotifyPresented,
frame_clock->output_name); frame_clock->output_name);
frame_clock->last_next_presentation_time_us = g_return_if_fail (frame_clock->next_presentation);
clear_frame (&frame_clock->prev_presentation);
presented_frame = frame_clock->prev_presentation =
g_steal_pointer (&frame_clock->next_presentation);
presented_frame->next_presentation_time_us =
frame_clock->next_presentation_time_us; frame_clock->next_presentation_time_us;
frame_clock->has_last_next_presentation_time = presented_frame->has_next_presentation_time =
frame_clock->is_next_presentation_time_valid; frame_clock->is_next_presentation_time_valid;
if (G_UNLIKELY (CLUTTER_HAS_DEBUG (FRAME_CLOCK))) if (G_UNLIKELY (CLUTTER_HAS_DEBUG (FRAME_CLOCK)))
{ {
int64_t now_us; int64_t now_us;
if (frame_clock->has_last_next_presentation_time && if (presented_frame->has_next_presentation_time &&
frame_info->presentation_time != 0) frame_info->presentation_time != 0)
{ {
int64_t diff_us; int64_t diff_us;
int n_missed_frames; int n_missed_frames;
diff_us = llabs (frame_info->presentation_time - diff_us = llabs (frame_info->presentation_time -
frame_clock->last_next_presentation_time_us); presented_frame->next_presentation_time_us);
n_missed_frames = n_missed_frames =
(int) roundf ((float) diff_us / (int) roundf ((float) diff_us /
(float) frame_clock->refresh_interval_us); (float) frame_clock->refresh_interval_us);
@ -362,11 +417,11 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock,
if (frame_info->presentation_time > 0) if (frame_info->presentation_time > 0)
{ {
frame_clock->last_presentation_time_us = frame_info->presentation_time; presented_frame->presentation_time_us = frame_info->presentation_time;
frame_clock->last_presentation_flags = frame_info->flags; presented_frame->presentation_flags = frame_info->flags;
} }
frame_clock->got_measurements_last_frame = FALSE; presented_frame->got_measurements = FALSE;
if (frame_info->cpu_time_before_buffer_swap_us != 0 && if (frame_info->cpu_time_before_buffer_swap_us != 0 &&
frame_info->has_valid_gpu_rendering_duration) frame_info->has_valid_gpu_rendering_duration)
@ -375,22 +430,22 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock,
dispatch_to_swap_us = dispatch_to_swap_us =
frame_info->cpu_time_before_buffer_swap_us - frame_info->cpu_time_before_buffer_swap_us -
frame_clock->last_dispatch_time_us; presented_frame->dispatch_time_us;
swap_to_rendering_done_us = swap_to_rendering_done_us =
frame_info->gpu_rendering_duration_ns / 1000; frame_info->gpu_rendering_duration_ns / 1000;
swap_to_flip_us = swap_to_flip_us =
frame_clock->last_flip_time_us - presented_frame->flip_time_us -
frame_info->cpu_time_before_buffer_swap_us; frame_info->cpu_time_before_buffer_swap_us;
CLUTTER_NOTE (FRAME_TIMINGS, CLUTTER_NOTE (FRAME_TIMINGS,
"update2dispatch %ld µs, dispatch2swap %ld µs, swap2render %ld µs, swap2flip %ld µs", "update2dispatch %ld µs, dispatch2swap %ld µs, swap2render %ld µs, swap2flip %ld µs",
frame_clock->last_dispatch_lateness_us, presented_frame->dispatch_lateness_us,
dispatch_to_swap_us, dispatch_to_swap_us,
swap_to_rendering_done_us, swap_to_rendering_done_us,
swap_to_flip_us); swap_to_flip_us);
frame_clock->shortterm_max_update_duration_us = frame_clock->shortterm_max_update_duration_us =
CLAMP (frame_clock->last_dispatch_lateness_us + dispatch_to_swap_us + CLAMP (presented_frame->dispatch_lateness_us + dispatch_to_swap_us +
MAX (swap_to_rendering_done_us, swap_to_flip_us) + MAX (swap_to_rendering_done_us, swap_to_flip_us) +
frame_clock->deadline_evasion_us, frame_clock->deadline_evasion_us,
frame_clock->shortterm_max_update_duration_us, frame_clock->shortterm_max_update_duration_us,
@ -398,13 +453,13 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock,
maybe_update_longterm_max_duration_us (frame_clock, frame_info); maybe_update_longterm_max_duration_us (frame_clock, frame_info);
frame_clock->got_measurements_last_frame = TRUE; presented_frame->got_measurements = TRUE;
frame_clock->ever_got_measurements = TRUE; frame_clock->ever_got_measurements = TRUE;
} }
else else
{ {
CLUTTER_NOTE (FRAME_TIMINGS, "update2dispatch %ld µs", CLUTTER_NOTE (FRAME_TIMINGS, "update2dispatch %ld µs",
frame_clock->last_dispatch_lateness_us); presented_frame->dispatch_lateness_us);
} }
if (frame_info->refresh_rate > 1.0) if (frame_info->refresh_rate > 1.0)
@ -435,6 +490,8 @@ clutter_frame_clock_notify_ready (ClutterFrameClock *frame_clock)
COGL_TRACE_BEGIN_SCOPED (ClutterFrameClockNotifyReady, "Clutter::FrameClock::ready()"); COGL_TRACE_BEGIN_SCOPED (ClutterFrameClockNotifyReady, "Clutter::FrameClock::ready()");
COGL_TRACE_DESCRIBE (ClutterFrameClockNotifyReady, frame_clock->output_name); COGL_TRACE_DESCRIBE (ClutterFrameClockNotifyReady, frame_clock->output_name);
clear_frame (&frame_clock->next_presentation);
switch (frame_clock->state) switch (frame_clock->state)
{ {
case CLUTTER_FRAME_CLOCK_STATE_INIT: case CLUTTER_FRAME_CLOCK_STATE_INIT:
@ -493,6 +550,7 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock,
int64_t *out_next_presentation_time_us, int64_t *out_next_presentation_time_us,
int64_t *out_next_frame_deadline_us) int64_t *out_next_frame_deadline_us)
{ {
const Frame *last_presentation = frame_clock->prev_presentation;
int64_t last_presentation_time_us; int64_t last_presentation_time_us;
int64_t now_us; int64_t now_us;
int64_t refresh_interval_us; int64_t refresh_interval_us;
@ -505,12 +563,14 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock,
refresh_interval_us = frame_clock->refresh_interval_us; refresh_interval_us = frame_clock->refresh_interval_us;
if (frame_clock->last_presentation_time_us == 0) if (!last_presentation || last_presentation->presentation_time_us == 0)
{ {
const Frame *last_dispatch = frame_clock->prev_dispatch;
*out_next_update_time_us = *out_next_update_time_us =
frame_clock->last_dispatch_time_us ? last_dispatch && last_dispatch->dispatch_time_us ?
((frame_clock->last_dispatch_time_us - ((last_dispatch->dispatch_time_us -
frame_clock->last_dispatch_lateness_us) + refresh_interval_us) : last_dispatch->dispatch_lateness_us) + refresh_interval_us) :
now_us; now_us;
*out_next_presentation_time_us = 0; *out_next_presentation_time_us = 0;
@ -542,7 +602,7 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock,
* 0 * 0
* *
*/ */
last_presentation_time_us = frame_clock->last_presentation_time_us; last_presentation_time_us = last_presentation->presentation_time_us;
next_presentation_time_us = last_presentation_time_us + refresh_interval_us; next_presentation_time_us = last_presentation_time_us + refresh_interval_us;
/* /*
@ -581,7 +641,7 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock,
next_presentation_time_us = now_us - current_phase_us + refresh_interval_us; next_presentation_time_us = now_us - current_phase_us + refresh_interval_us;
} }
if (frame_clock->has_last_next_presentation_time) if (last_presentation->has_next_presentation_time)
{ {
int64_t time_since_last_next_presentation_time_us; int64_t time_since_last_next_presentation_time_us;
@ -600,7 +660,7 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock,
* *
*/ */
time_since_last_next_presentation_time_us = time_since_last_next_presentation_time_us =
next_presentation_time_us - frame_clock->last_next_presentation_time_us; next_presentation_time_us - last_presentation->next_presentation_time_us;
if (time_since_last_next_presentation_time_us > 0 && if (time_since_last_next_presentation_time_us > 0 &&
time_since_last_next_presentation_time_us < (refresh_interval_us / 2)) time_since_last_next_presentation_time_us < (refresh_interval_us / 2))
{ {
@ -609,7 +669,7 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock,
} }
} }
if (frame_clock->last_presentation_flags & CLUTTER_FRAME_INFO_FLAG_VSYNC && if (last_presentation->presentation_flags & CLUTTER_FRAME_INFO_FLAG_VSYNC &&
next_presentation_time_us != last_presentation_time_us + refresh_interval_us) next_presentation_time_us != last_presentation_time_us + refresh_interval_us)
{ {
/* There was an idle period since the last presentation, so there seems /* There was an idle period since the last presentation, so there seems
@ -641,6 +701,7 @@ calculate_next_variable_update_time_us (ClutterFrameClock *frame_clock,
int64_t *out_next_presentation_time_us, int64_t *out_next_presentation_time_us,
int64_t *out_next_frame_deadline_us) int64_t *out_next_frame_deadline_us)
{ {
const Frame *last_presentation = frame_clock->prev_presentation;
int64_t last_presentation_time_us; int64_t last_presentation_time_us;
int64_t now_us; int64_t now_us;
int64_t refresh_interval_us; int64_t refresh_interval_us;
@ -653,12 +714,14 @@ calculate_next_variable_update_time_us (ClutterFrameClock *frame_clock,
refresh_interval_us = frame_clock->refresh_interval_us; refresh_interval_us = frame_clock->refresh_interval_us;
if (frame_clock->last_presentation_time_us == 0) if (!last_presentation || last_presentation->presentation_time_us == 0)
{ {
const Frame *last_dispatch = frame_clock->prev_dispatch;
*out_next_update_time_us = *out_next_update_time_us =
frame_clock->last_dispatch_time_us ? last_dispatch && last_dispatch->dispatch_time_us ?
((frame_clock->last_dispatch_time_us - ((last_dispatch->dispatch_time_us -
frame_clock->last_dispatch_lateness_us) + refresh_interval_us) : last_dispatch->dispatch_lateness_us) + refresh_interval_us) :
now_us; now_us;
*out_next_presentation_time_us = 0; *out_next_presentation_time_us = 0;
@ -669,7 +732,7 @@ calculate_next_variable_update_time_us (ClutterFrameClock *frame_clock,
max_render_time_allowed_us = max_render_time_allowed_us =
clutter_frame_clock_compute_max_render_time_us (frame_clock); clutter_frame_clock_compute_max_render_time_us (frame_clock);
last_presentation_time_us = frame_clock->last_presentation_time_us; last_presentation_time_us = last_presentation->presentation_time_us;
next_presentation_time_us = last_presentation_time_us + refresh_interval_us; next_presentation_time_us = last_presentation_time_us + refresh_interval_us;
next_update_time_us = next_presentation_time_us - max_render_time_allowed_us; next_update_time_us = next_presentation_time_us - max_render_time_allowed_us;
@ -692,28 +755,29 @@ static void
calculate_next_variable_update_timeout_us (ClutterFrameClock *frame_clock, calculate_next_variable_update_timeout_us (ClutterFrameClock *frame_clock,
int64_t *out_next_update_time_us) int64_t *out_next_update_time_us)
{ {
const Frame *last_presentation = frame_clock->prev_presentation;
int64_t now_us; int64_t now_us;
int64_t last_presentation_time_us;
int64_t next_presentation_time_us; int64_t next_presentation_time_us;
int64_t timeout_interval_us; int64_t timeout_interval_us;
now_us = g_get_monotonic_time (); now_us = g_get_monotonic_time ();
last_presentation_time_us = frame_clock->last_presentation_time_us;
timeout_interval_us = frame_clock->minimum_refresh_interval_us; timeout_interval_us = frame_clock->minimum_refresh_interval_us;
if (last_presentation_time_us == 0) if (!last_presentation || last_presentation->presentation_time_us == 0)
{ {
const Frame *last_dispatch = frame_clock->prev_dispatch;
*out_next_update_time_us = *out_next_update_time_us =
frame_clock->last_dispatch_time_us ? last_dispatch && last_dispatch->dispatch_time_us ?
((frame_clock->last_dispatch_time_us - ((last_dispatch->dispatch_time_us -
frame_clock->last_dispatch_lateness_us) + timeout_interval_us) : last_dispatch->dispatch_lateness_us) + timeout_interval_us) :
now_us; now_us;
return; return;
} }
next_presentation_time_us = last_presentation_time_us + timeout_interval_us; next_presentation_time_us = last_presentation->presentation_time_us +
timeout_interval_us;
while (next_presentation_time_us < now_us) while (next_presentation_time_us < now_us)
next_presentation_time_us += timeout_interval_us; next_presentation_time_us += timeout_interval_us;
@ -911,6 +975,10 @@ clutter_frame_clock_dispatch (ClutterFrameClock *frame_clock,
int64_t frame_count; int64_t frame_count;
ClutterFrameResult result; ClutterFrameResult result;
int64_t ideal_dispatch_time_us, lateness_us; int64_t ideal_dispatch_time_us, lateness_us;
Frame *this_dispatch;
int64_t prev_dispatch_time_us = 0;
int64_t prev_dispatch_interval_us = 0;
int64_t prev_dispatch_lateness_us = 0;
#ifdef HAVE_PROFILER #ifdef HAVE_PROFILER
int64_t this_dispatch_ready_time_us; int64_t this_dispatch_ready_time_us;
@ -923,36 +991,54 @@ clutter_frame_clock_dispatch (ClutterFrameClock *frame_clock,
this_dispatch_time_us = time_us; this_dispatch_time_us = time_us;
#endif #endif
/* Discarding the old prev_dispatch early here allows us to keep the
* frame_pool size equal to nbuffers instead of nbuffers+1.
*/
if (frame_clock->prev_dispatch)
{
prev_dispatch_time_us = frame_clock->prev_dispatch->dispatch_time_us;
prev_dispatch_interval_us = frame_clock->prev_dispatch->dispatch_interval_us;
prev_dispatch_lateness_us = frame_clock->prev_dispatch->dispatch_lateness_us;
}
clear_frame (&frame_clock->prev_dispatch);
this_dispatch = frame_clock->prev_dispatch =
clutter_frame_clock_new_frame (frame_clock);
/* This will need changing for triple buffering */
g_warn_if_fail (frame_clock->next_presentation == NULL);
frame_clock->next_presentation = ref_frame (this_dispatch);
ideal_dispatch_time_us = frame_clock->next_update_time_us; ideal_dispatch_time_us = frame_clock->next_update_time_us;
if (ideal_dispatch_time_us <= 0) if (ideal_dispatch_time_us <= 0)
ideal_dispatch_time_us = (frame_clock->last_dispatch_time_us - ideal_dispatch_time_us = (prev_dispatch_time_us -
frame_clock->last_dispatch_lateness_us) + prev_dispatch_lateness_us) +
frame_clock->refresh_interval_us; frame_clock->refresh_interval_us;
lateness_us = time_us - ideal_dispatch_time_us; lateness_us = time_us - ideal_dispatch_time_us;
if (lateness_us < 0 || lateness_us >= frame_clock->refresh_interval_us) if (lateness_us < 0 || lateness_us >= frame_clock->refresh_interval_us)
frame_clock->last_dispatch_lateness_us = 0; this_dispatch->dispatch_lateness_us = 0;
else else
frame_clock->last_dispatch_lateness_us = lateness_us; this_dispatch->dispatch_lateness_us = lateness_us;
#ifdef CLUTTER_ENABLE_DEBUG #ifdef CLUTTER_ENABLE_DEBUG
if (G_UNLIKELY (CLUTTER_HAS_DEBUG (FRAME_TIMINGS))) if (G_UNLIKELY (CLUTTER_HAS_DEBUG (FRAME_TIMINGS)))
{ {
int64_t dispatch_interval_us, jitter_us; int64_t dispatch_interval_us, jitter_us;
dispatch_interval_us = time_us - frame_clock->last_dispatch_time_us; dispatch_interval_us = time_us - prev_dispatch_time_us;
jitter_us = llabs (dispatch_interval_us - jitter_us = llabs (dispatch_interval_us -
frame_clock->last_dispatch_interval_us) % prev_dispatch_interval_us) %
frame_clock->refresh_interval_us; frame_clock->refresh_interval_us;
frame_clock->last_dispatch_interval_us = dispatch_interval_us; this_dispatch->dispatch_interval_us = dispatch_interval_us;
CLUTTER_NOTE (FRAME_TIMINGS, "dispatch jitter %5ldµs (%3ld%%)", CLUTTER_NOTE (FRAME_TIMINGS, "dispatch jitter %5ldµs (%3ld%%)",
jitter_us, jitter_us,
jitter_us * 100 / frame_clock->refresh_interval_us); jitter_us * 100 / frame_clock->refresh_interval_us);
} }
#endif #endif
frame_clock->last_dispatch_time_us = time_us; this_dispatch->dispatch_time_us = time_us;
g_source_set_ready_time (frame_clock->source, -1); g_source_set_ready_time (frame_clock->source, -1);
frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHING; frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHING;
@ -1040,12 +1126,15 @@ void
clutter_frame_clock_record_flip_time (ClutterFrameClock *frame_clock, clutter_frame_clock_record_flip_time (ClutterFrameClock *frame_clock,
int64_t flip_time_us) int64_t flip_time_us)
{ {
frame_clock->last_flip_time_us = flip_time_us; Frame *new_frame = frame_clock->prev_dispatch;
new_frame->flip_time_us = flip_time_us;
} }
GString * GString *
clutter_frame_clock_get_max_render_time_debug_info (ClutterFrameClock *frame_clock) clutter_frame_clock_get_max_render_time_debug_info (ClutterFrameClock *frame_clock)
{ {
const Frame *last_presentation = frame_clock->prev_presentation;
int64_t max_update_duration_us; int64_t max_update_duration_us;
GString *string; GString *string;
@ -1053,7 +1142,7 @@ clutter_frame_clock_get_max_render_time_debug_info (ClutterFrameClock *frame_clo
g_string_append_printf (string, "Max render time: %ld µs", g_string_append_printf (string, "Max render time: %ld µs",
clutter_frame_clock_compute_max_render_time_us (frame_clock)); clutter_frame_clock_compute_max_render_time_us (frame_clock));
if (frame_clock->got_measurements_last_frame) if (last_presentation && last_presentation->got_measurements)
g_string_append_printf (string, " ="); g_string_append_printf (string, " =");
else else
g_string_append_printf (string, " (no measurements last frame)"); g_string_append_printf (string, " (no measurements last frame)");