2024-05-13 15:34:05 -07:00

1869 lines
71 KiB
Diff

diff --git a/clutter/clutter/clutter-frame-clock.c b/clutter/clutter/clutter-frame-clock.c
index 93e4c9329..6bd83101e 100644
--- a/clutter/clutter/clutter-frame-clock.c
+++ b/clutter/clutter/clutter-frame-clock.c
@@ -42,6 +42,15 @@ enum
static guint signals[N_SIGNALS];
+typedef enum
+{
+ TRIPLE_BUFFERING_MODE_NEVER,
+ TRIPLE_BUFFERING_MODE_AUTO,
+ TRIPLE_BUFFERING_MODE_ALWAYS,
+} TripleBufferingMode;
+
+static TripleBufferingMode triple_buffering_mode = TRIPLE_BUFFERING_MODE_AUTO;
+
#define SYNC_DELAY_FALLBACK_FRACTION 0.875
#define MINIMUM_REFRESH_RATE 30.f
@@ -70,8 +79,10 @@ typedef enum _ClutterFrameClockState
CLUTTER_FRAME_CLOCK_STATE_IDLE,
CLUTTER_FRAME_CLOCK_STATE_SCHEDULED,
CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW,
- CLUTTER_FRAME_CLOCK_STATE_DISPATCHING,
- CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED,
+ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE,
+ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED,
+ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW,
+ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO,
} ClutterFrameClockState;
struct _ClutterFrameClock
@@ -92,6 +103,7 @@ struct _ClutterFrameClock
ClutterFrameClockMode mode;
int64_t last_dispatch_time_us;
+ int64_t prev_last_dispatch_time_us;
int64_t last_dispatch_lateness_us;
int64_t last_presentation_time_us;
int64_t next_update_time_us;
@@ -111,6 +123,9 @@ struct _ClutterFrameClock
int64_t vblank_duration_us;
/* Last KMS buffer submission time. */
int64_t last_flip_time_us;
+ int64_t prev_last_flip_time_us;
+
+ ClutterFrameHint last_flip_hints;
/* Last time we promoted short-term maximum to long-term one */
int64_t longterm_promotion_us;
@@ -245,10 +260,6 @@ static void
maybe_update_longterm_max_duration_us (ClutterFrameClock *frame_clock,
ClutterFrameInfo *frame_info)
{
- /* Do not update long-term max if there has been no measurement */
- if (!frame_clock->shortterm_max_update_duration_us)
- return;
-
if ((frame_info->presentation_time - frame_clock->longterm_promotion_us) <
G_USEC_PER_SEC)
return;
@@ -275,6 +286,12 @@ void
clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock,
ClutterFrameInfo *frame_info)
{
+#ifdef CLUTTER_ENABLE_DEBUG
+ const char *debug_state =
+ frame_clock->state == CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO ?
+ "Triple buffering" : "Double buffering";
+#endif
+
COGL_TRACE_BEGIN_SCOPED (ClutterFrameClockNotifyPresented,
"Clutter::FrameClock::presented()");
COGL_TRACE_DESCRIBE (ClutterFrameClockNotifyPresented,
@@ -361,22 +378,52 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock,
frame_clock->got_measurements_last_frame = FALSE;
- if (frame_info->cpu_time_before_buffer_swap_us != 0 &&
- frame_info->has_valid_gpu_rendering_duration)
+ if ((frame_info->cpu_time_before_buffer_swap_us != 0 &&
+ frame_info->has_valid_gpu_rendering_duration) ||
+ frame_clock->ever_got_measurements)
{
int64_t dispatch_to_swap_us, swap_to_rendering_done_us, swap_to_flip_us;
+ int64_t dispatch_time_us = 0, flip_time_us = 0;
- dispatch_to_swap_us =
- frame_info->cpu_time_before_buffer_swap_us -
- frame_clock->last_dispatch_time_us;
+ switch (frame_clock->state)
+ {
+ case CLUTTER_FRAME_CLOCK_STATE_INIT:
+ case CLUTTER_FRAME_CLOCK_STATE_IDLE:
+ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED:
+ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW:
+ g_warn_if_reached ();
+ G_GNUC_FALLTHROUGH;
+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE:
+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED:
+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW:
+ dispatch_time_us = frame_clock->last_dispatch_time_us;
+ flip_time_us = frame_clock->last_flip_time_us;
+ break;
+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO:
+ dispatch_time_us = frame_clock->prev_last_dispatch_time_us;
+ flip_time_us = frame_clock->prev_last_flip_time_us;
+ break;
+ }
+
+ if (frame_info->cpu_time_before_buffer_swap_us == 0)
+ {
+ /* Cursor-only updates with no "swap" or "flip" */
+ dispatch_to_swap_us = 0;
+ swap_to_flip_us = 0;
+ }
+ else
+ {
+ dispatch_to_swap_us = frame_info->cpu_time_before_buffer_swap_us -
+ dispatch_time_us;
+ swap_to_flip_us = flip_time_us -
+ frame_info->cpu_time_before_buffer_swap_us;
+ }
swap_to_rendering_done_us =
frame_info->gpu_rendering_duration_ns / 1000;
- swap_to_flip_us =
- frame_clock->last_flip_time_us -
- frame_info->cpu_time_before_buffer_swap_us;
CLUTTER_NOTE (FRAME_TIMINGS,
- "update2dispatch %ld µs, dispatch2swap %ld µs, swap2render %ld µs, swap2flip %ld µs",
+ "%s: update2dispatch %ld µs, dispatch2swap %ld µs, swap2render %ld µs, swap2flip %ld µs",
+ debug_state,
frame_clock->last_dispatch_lateness_us,
dispatch_to_swap_us,
swap_to_rendering_done_us,
@@ -386,7 +433,7 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock,
CLAMP (frame_clock->last_dispatch_lateness_us + dispatch_to_swap_us +
MAX (swap_to_rendering_done_us, swap_to_flip_us),
frame_clock->shortterm_max_update_duration_us,
- frame_clock->refresh_interval_us);
+ 2 * frame_clock->refresh_interval_us);
maybe_update_longterm_max_duration_us (frame_clock, frame_info);
@@ -395,7 +442,8 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock,
}
else
{
- CLUTTER_NOTE (FRAME_TIMINGS, "update2dispatch %ld µs",
+ CLUTTER_NOTE (FRAME_TIMINGS, "%s: update2dispatch %ld µs",
+ debug_state,
frame_clock->last_dispatch_lateness_us);
}
@@ -413,11 +461,22 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock,
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW:
g_warn_if_reached ();
break;
- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING:
- case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED:
+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE:
frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE;
maybe_reschedule_update (frame_clock);
break;
+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED:
+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED;
+ maybe_reschedule_update (frame_clock);
+ break;
+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW:
+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW;
+ maybe_reschedule_update (frame_clock);
+ break;
+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO:
+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE;
+ maybe_reschedule_update (frame_clock);
+ break;
}
}
@@ -435,26 +494,37 @@ clutter_frame_clock_notify_ready (ClutterFrameClock *frame_clock)
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW:
g_warn_if_reached ();
break;
- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING:
- case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED:
+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE:
frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE;
maybe_reschedule_update (frame_clock);
break;
+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED:
+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED;
+ maybe_reschedule_update (frame_clock);
+ break;
+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW:
+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW;
+ maybe_reschedule_update (frame_clock);
+ break;
+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO:
+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE;
+ maybe_reschedule_update (frame_clock);
+ break;
}
}
-static int64_t
-clutter_frame_clock_compute_max_render_time_us (ClutterFrameClock *frame_clock)
+static gboolean
+clutter_frame_clock_compute_max_render_time_us (ClutterFrameClock *frame_clock,
+ int64_t *max_render_time_us)
{
int64_t refresh_interval_us;
- int64_t max_render_time_us;
refresh_interval_us = frame_clock->refresh_interval_us;
if (!frame_clock->ever_got_measurements ||
G_UNLIKELY (clutter_paint_debug_flags &
CLUTTER_DEBUG_DISABLE_DYNAMIC_MAX_RENDER_TIME))
- return refresh_interval_us * SYNC_DELAY_FALLBACK_FRACTION;
+ return FALSE;
/* Max render time shows how early the frame clock needs to be dispatched
* to make it to the predicted next presentation time. It is an estimate of
@@ -468,15 +538,15 @@ clutter_frame_clock_compute_max_render_time_us (ClutterFrameClock *frame_clock)
* - The duration of vertical blank.
* - A constant to account for variations in the above estimates.
*/
- max_render_time_us =
+ *max_render_time_us =
MAX (frame_clock->longterm_max_update_duration_us,
frame_clock->shortterm_max_update_duration_us) +
frame_clock->vblank_duration_us +
clutter_max_render_time_constant_us;
- max_render_time_us = CLAMP (max_render_time_us, 0, refresh_interval_us);
+ *max_render_time_us = CLAMP (*max_render_time_us, 0, 2 * refresh_interval_us);
- return max_render_time_us;
+ return TRUE;
}
static void
@@ -491,7 +561,9 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock,
int64_t min_render_time_allowed_us;
int64_t max_render_time_allowed_us;
int64_t next_presentation_time_us;
+ int64_t next_smooth_presentation_time_us = 0;
int64_t next_update_time_us;
+ gboolean max_render_time_is_known;
now_us = g_get_monotonic_time ();
@@ -511,10 +583,13 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock,
}
min_render_time_allowed_us = refresh_interval_us / 2;
- max_render_time_allowed_us =
- clutter_frame_clock_compute_max_render_time_us (frame_clock);
- if (min_render_time_allowed_us > max_render_time_allowed_us)
+ max_render_time_is_known =
+ clutter_frame_clock_compute_max_render_time_us (frame_clock,
+ &max_render_time_allowed_us);
+
+ if (max_render_time_is_known &&
+ min_render_time_allowed_us > max_render_time_allowed_us)
min_render_time_allowed_us = max_render_time_allowed_us;
/*
@@ -535,7 +610,28 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock,
*
*/
last_presentation_time_us = frame_clock->last_presentation_time_us;
- next_presentation_time_us = last_presentation_time_us + refresh_interval_us;
+ switch (frame_clock->state)
+ {
+ case CLUTTER_FRAME_CLOCK_STATE_INIT:
+ case CLUTTER_FRAME_CLOCK_STATE_IDLE:
+ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED:
+ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW:
+ next_smooth_presentation_time_us = last_presentation_time_us +
+ refresh_interval_us;
+ break;
+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE:
+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED:
+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW:
+ next_smooth_presentation_time_us = last_presentation_time_us +
+ 2 * refresh_interval_us;
+ break;
+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO:
+ next_smooth_presentation_time_us = last_presentation_time_us +
+ 3 * refresh_interval_us;
+ break;
+ }
+
+ next_presentation_time_us = next_smooth_presentation_time_us;
/*
* However, the last presentation could have happened more than a frame ago.
@@ -601,7 +697,7 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock,
}
}
- if (next_presentation_time_us != last_presentation_time_us + refresh_interval_us)
+ if (next_presentation_time_us != next_smooth_presentation_time_us)
{
/* There was an idle period since the last presentation, so there seems
* be no constantly updating actor. In this case it's best to start
@@ -613,6 +709,24 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock,
}
else
{
+ /* If the max render time isn't known then using the current value of
+ * next_presentation_time_us is suboptimal. Targeting always one frame
+ * prior to that we'd lose the ability to scale up to triple buffering
+ * on late presentation. But targeting two frames prior we would be
+ * always triple buffering even when not required.
+ * So the algorithm for deciding when to scale up to triple buffering
+ * in the absence of render time measurements is to simply target full
+ * frame rate. If we're keeping up then we'll stay double buffering. If
+ * we're not keeping up then this will switch us to triple buffering.
+ */
+ if (!max_render_time_is_known)
+ {
+ max_render_time_allowed_us =
+ refresh_interval_us * SYNC_DELAY_FALLBACK_FRACTION;
+ next_presentation_time_us =
+ last_presentation_time_us + refresh_interval_us;
+ }
+
while (next_presentation_time_us - min_render_time_allowed_us < now_us)
next_presentation_time_us += refresh_interval_us;
@@ -644,7 +758,9 @@ calculate_next_variable_update_time_us (ClutterFrameClock *frame_clock,
refresh_interval_us = frame_clock->refresh_interval_us;
- if (frame_clock->last_presentation_time_us == 0)
+ if (frame_clock->last_presentation_time_us == 0 ||
+ !clutter_frame_clock_compute_max_render_time_us (frame_clock,
+ &max_render_time_allowed_us))
{
*out_next_update_time_us =
frame_clock->last_dispatch_time_us ?
@@ -657,9 +773,6 @@ calculate_next_variable_update_time_us (ClutterFrameClock *frame_clock,
return;
}
- max_render_time_allowed_us =
- clutter_frame_clock_compute_max_render_time_us (frame_clock);
-
last_presentation_time_us = frame_clock->last_presentation_time_us;
next_presentation_time_us = last_presentation_time_us + refresh_interval_us;
@@ -733,8 +846,17 @@ clutter_frame_clock_inhibit (ClutterFrameClock *frame_clock)
frame_clock->pending_reschedule_now = TRUE;
frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE;
break;
- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING:
- case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED:
+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED:
+ frame_clock->pending_reschedule = TRUE;
+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE;
+ break;
+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW:
+ frame_clock->pending_reschedule = TRUE;
+ frame_clock->pending_reschedule_now = TRUE;
+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE;
+ break;
+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE:
+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO:
break;
}
@@ -753,6 +875,25 @@ clutter_frame_clock_uninhibit (ClutterFrameClock *frame_clock)
maybe_reschedule_update (frame_clock);
}
+static gboolean
+want_triple_buffering (ClutterFrameClock *frame_clock)
+{
+ switch (triple_buffering_mode)
+ {
+ case TRIPLE_BUFFERING_MODE_NEVER:
+ return FALSE;
+ case TRIPLE_BUFFERING_MODE_AUTO:
+ return frame_clock->mode == CLUTTER_FRAME_CLOCK_MODE_FIXED &&
+ !(frame_clock->last_flip_hints &
+ CLUTTER_FRAME_HINT_DIRECT_SCANOUT_ATTEMPTED);
+ case TRIPLE_BUFFERING_MODE_ALWAYS:
+ return TRUE;
+ }
+
+ g_assert_not_reached ();
+ return FALSE;
+}
+
void
clutter_frame_clock_schedule_update_now (ClutterFrameClock *frame_clock)
{
@@ -770,11 +911,24 @@ clutter_frame_clock_schedule_update_now (ClutterFrameClock *frame_clock)
case CLUTTER_FRAME_CLOCK_STATE_INIT:
case CLUTTER_FRAME_CLOCK_STATE_IDLE:
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED:
+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW;
break;
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW:
+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW:
return;
- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING:
- case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED:
+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED:
+ frame_clock->state =
+ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW;
+ break;
+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE:
+ if (want_triple_buffering (frame_clock))
+ {
+ frame_clock->state =
+ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW;
+ break;
+ }
+ G_GNUC_FALLTHROUGH;
+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO:
frame_clock->pending_reschedule = TRUE;
frame_clock->pending_reschedule_now = TRUE;
return;
@@ -803,13 +957,17 @@ clutter_frame_clock_schedule_update_now (ClutterFrameClock *frame_clock)
frame_clock->next_update_time_us = next_update_time_us;
g_source_set_ready_time (frame_clock->source, next_update_time_us);
- frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW;
}
void
clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock)
{
int64_t next_update_time_us = -1;
+ TripleBufferingMode current_mode = triple_buffering_mode;
+
+ if (current_mode == TRIPLE_BUFFERING_MODE_AUTO &&
+ !want_triple_buffering (frame_clock))
+ current_mode = TRIPLE_BUFFERING_MODE_NEVER;
if (frame_clock->inhibit_count > 0)
{
@@ -825,12 +983,33 @@ clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock)
frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED;
return;
case CLUTTER_FRAME_CLOCK_STATE_IDLE:
+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED;
break;
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED:
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW:
+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED:
+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW:
return;
- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING:
- case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED:
+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE:
+ switch (current_mode)
+ {
+ case TRIPLE_BUFFERING_MODE_NEVER:
+ frame_clock->pending_reschedule = TRUE;
+ return;
+ case TRIPLE_BUFFERING_MODE_AUTO:
+ frame_clock->state =
+ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED;
+ break;
+ case TRIPLE_BUFFERING_MODE_ALWAYS:
+ next_update_time_us = g_get_monotonic_time ();
+ frame_clock->next_presentation_time_us = 0;
+ frame_clock->is_next_presentation_time_valid = FALSE;
+ frame_clock->state =
+ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED;
+ goto got_update_time;
+ }
+ break;
+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO:
frame_clock->pending_reschedule = TRUE;
return;
}
@@ -855,11 +1034,11 @@ clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock)
break;
}
+got_update_time:
g_warn_if_fail (next_update_time_us != -1);
frame_clock->next_update_time_us = next_update_time_us;
g_source_set_ready_time (frame_clock->source, next_update_time_us);
- frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED;
}
void
@@ -875,6 +1054,8 @@ clutter_frame_clock_set_mode (ClutterFrameClock *frame_clock,
{
case CLUTTER_FRAME_CLOCK_STATE_INIT:
case CLUTTER_FRAME_CLOCK_STATE_IDLE:
+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE:
+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO:
break;
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED:
frame_clock->pending_reschedule = TRUE;
@@ -885,8 +1066,14 @@ clutter_frame_clock_set_mode (ClutterFrameClock *frame_clock,
frame_clock->pending_reschedule_now = TRUE;
frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE;
break;
- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING:
- case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED:
+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED:
+ frame_clock->pending_reschedule = TRUE;
+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE;
+ break;
+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW:
+ frame_clock->pending_reschedule = TRUE;
+ frame_clock->pending_reschedule_now = TRUE;
+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE;
break;
}
@@ -922,7 +1109,7 @@ clutter_frame_clock_dispatch (ClutterFrameClock *frame_clock,
frame_clock->refresh_interval_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 / 4)
frame_clock->last_dispatch_lateness_us = 0;
else
frame_clock->last_dispatch_lateness_us = lateness_us;
@@ -943,10 +1130,27 @@ clutter_frame_clock_dispatch (ClutterFrameClock *frame_clock,
}
#endif
+ frame_clock->prev_last_dispatch_time_us = frame_clock->last_dispatch_time_us;
frame_clock->last_dispatch_time_us = time_us;
g_source_set_ready_time (frame_clock->source, -1);
- frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHING;
+ switch (frame_clock->state)
+ {
+ case CLUTTER_FRAME_CLOCK_STATE_INIT:
+ case CLUTTER_FRAME_CLOCK_STATE_IDLE:
+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE:
+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO:
+ g_warn_if_reached ();
+ return;
+ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED:
+ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW:
+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE;
+ break;
+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED:
+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW:
+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO;
+ break;
+ }
frame_count = frame_clock->frame_count++;
@@ -977,26 +1181,36 @@ clutter_frame_clock_dispatch (ClutterFrameClock *frame_clock,
result = iface->frame (frame_clock, frame, frame_clock->listener.user_data);
COGL_TRACE_END (ClutterFrameClockFrame);
- switch (frame_clock->state)
+ switch (result)
{
- case CLUTTER_FRAME_CLOCK_STATE_INIT:
- case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED:
- g_warn_if_reached ();
+ case CLUTTER_FRAME_RESULT_PENDING_PRESENTED:
break;
- case CLUTTER_FRAME_CLOCK_STATE_IDLE:
- case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED:
- case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW:
- break;
- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING:
- switch (result)
+ case CLUTTER_FRAME_RESULT_IDLE:
+ /* The frame was aborted; nothing to paint/present */
+ switch (frame_clock->state)
{
- case CLUTTER_FRAME_RESULT_PENDING_PRESENTED:
- frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED;
+ case CLUTTER_FRAME_CLOCK_STATE_INIT:
+ case CLUTTER_FRAME_CLOCK_STATE_IDLE:
+ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED:
+ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW:
+ g_warn_if_reached ();
break;
- case CLUTTER_FRAME_RESULT_IDLE:
+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE:
frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE;
maybe_reschedule_update (frame_clock);
break;
+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED:
+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED;
+ maybe_reschedule_update (frame_clock);
+ break;
+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW:
+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW;
+ maybe_reschedule_update (frame_clock);
+ break;
+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO:
+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE;
+ maybe_reschedule_update (frame_clock);
+ break;
}
break;
}
@@ -1029,21 +1243,31 @@ frame_clock_source_dispatch (GSource *source,
}
void
-clutter_frame_clock_record_flip_time (ClutterFrameClock *frame_clock,
- int64_t flip_time_us)
+clutter_frame_clock_record_flip (ClutterFrameClock *frame_clock,
+ int64_t flip_time_us,
+ ClutterFrameHint hints)
{
+ frame_clock->prev_last_flip_time_us = frame_clock->last_flip_time_us;
frame_clock->last_flip_time_us = flip_time_us;
+ frame_clock->last_flip_hints = hints;
}
GString *
clutter_frame_clock_get_max_render_time_debug_info (ClutterFrameClock *frame_clock)
{
+ int64_t max_render_time_us;
int64_t max_update_duration_us;
GString *string;
- string = g_string_new (NULL);
- g_string_append_printf (string, "Max render time: %ld µs",
- clutter_frame_clock_compute_max_render_time_us (frame_clock));
+ string = g_string_new ("Max render time: ");
+ if (!clutter_frame_clock_compute_max_render_time_us (frame_clock,
+ &max_render_time_us))
+ {
+ g_string_append (string, "unknown");
+ return string;
+ }
+
+ g_string_append_printf (string, "%ld µs", max_render_time_us);
if (frame_clock->got_measurements_last_frame)
g_string_append_printf (string, " =");
@@ -1210,8 +1434,6 @@ clutter_frame_clock_dispose (GObject *object)
{
ClutterFrameClock *frame_clock = CLUTTER_FRAME_CLOCK (object);
- g_warn_if_fail (frame_clock->state != CLUTTER_FRAME_CLOCK_STATE_DISPATCHING);
-
if (frame_clock->source)
{
g_signal_emit (frame_clock, signals[DESTROY], 0);
@@ -1235,6 +1457,15 @@ static void
clutter_frame_clock_class_init (ClutterFrameClockClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ const char *mode_str;
+
+ mode_str = g_getenv ("MUTTER_DEBUG_TRIPLE_BUFFERING");
+ if (!g_strcmp0 (mode_str, "never"))
+ triple_buffering_mode = TRIPLE_BUFFERING_MODE_NEVER;
+ else if (!g_strcmp0 (mode_str, "auto"))
+ triple_buffering_mode = TRIPLE_BUFFERING_MODE_AUTO;
+ else if (!g_strcmp0 (mode_str, "always"))
+ triple_buffering_mode = TRIPLE_BUFFERING_MODE_ALWAYS;
object_class->dispose = clutter_frame_clock_dispose;
diff --git a/clutter/clutter/clutter-frame-clock.h b/clutter/clutter/clutter-frame-clock.h
index a7be5ef31..bfc89bde0 100644
--- a/clutter/clutter/clutter-frame-clock.h
+++ b/clutter/clutter/clutter-frame-clock.h
@@ -33,6 +33,12 @@ typedef enum _ClutterFrameResult
CLUTTER_FRAME_RESULT_IDLE,
} ClutterFrameResult;
+typedef enum _ClutterFrameHint
+{
+ CLUTTER_FRAME_HINT_NONE = 0,
+ CLUTTER_FRAME_HINT_DIRECT_SCANOUT_ATTEMPTED = 1 << 0,
+} ClutterFrameHint;
+
#define CLUTTER_TYPE_FRAME_CLOCK (clutter_frame_clock_get_type ())
CLUTTER_EXPORT
G_DECLARE_FINAL_TYPE (ClutterFrameClock, clutter_frame_clock,
@@ -102,7 +108,8 @@ void clutter_frame_clock_remove_timeline (ClutterFrameClock *frame_clock,
CLUTTER_EXPORT
float clutter_frame_clock_get_refresh_rate (ClutterFrameClock *frame_clock);
-void clutter_frame_clock_record_flip_time (ClutterFrameClock *frame_clock,
- int64_t flip_time_us);
+void clutter_frame_clock_record_flip (ClutterFrameClock *frame_clock,
+ int64_t flip_time_us,
+ ClutterFrameHint hints);
GString * clutter_frame_clock_get_max_render_time_debug_info (ClutterFrameClock *frame_clock);
diff --git a/clutter/clutter/clutter-frame-private.h b/clutter/clutter/clutter-frame-private.h
index ef66b874e..ce140560a 100644
--- a/clutter/clutter/clutter-frame-private.h
+++ b/clutter/clutter/clutter-frame-private.h
@@ -36,6 +36,7 @@ struct _ClutterFrame
gboolean has_result;
ClutterFrameResult result;
+ ClutterFrameHint hints;
};
CLUTTER_EXPORT
diff --git a/clutter/clutter/clutter-frame.c b/clutter/clutter/clutter-frame.c
index 7436f9f18..53c289b2c 100644
--- a/clutter/clutter/clutter-frame.c
+++ b/clutter/clutter/clutter-frame.c
@@ -115,3 +115,16 @@ clutter_frame_set_result (ClutterFrame *frame,
frame->result = result;
frame->has_result = TRUE;
}
+
+void
+clutter_frame_set_hint (ClutterFrame *frame,
+ ClutterFrameHint hint)
+{
+ frame->hints |= hint;
+}
+
+ClutterFrameHint
+clutter_frame_get_hints (ClutterFrame *frame)
+{
+ return frame->hints;
+}
diff --git a/clutter/clutter/clutter-frame.h b/clutter/clutter/clutter-frame.h
index 34f0770bd..c7b3d02ac 100644
--- a/clutter/clutter/clutter-frame.h
+++ b/clutter/clutter/clutter-frame.h
@@ -54,4 +54,11 @@ void clutter_frame_set_result (ClutterFrame *frame,
CLUTTER_EXPORT
gboolean clutter_frame_has_result (ClutterFrame *frame);
+CLUTTER_EXPORT
+void clutter_frame_set_hint (ClutterFrame *frame,
+ ClutterFrameHint hint);
+
+CLUTTER_EXPORT
+ClutterFrameHint clutter_frame_get_hints (ClutterFrame *frame);
+
G_DEFINE_AUTOPTR_CLEANUP_FUNC (ClutterFrame, clutter_frame_unref)
diff --git a/clutter/clutter/clutter-stage-view.c b/clutter/clutter/clutter-stage-view.c
index f5188e2ac..d53e37785 100644
--- a/clutter/clutter/clutter-stage-view.c
+++ b/clutter/clutter/clutter-stage-view.c
@@ -898,14 +898,21 @@ handle_frame_clock_frame (ClutterFrameClock *frame_clock,
_clutter_stage_window_redraw_view (stage_window, view, frame);
- clutter_frame_clock_record_flip_time (frame_clock,
- g_get_monotonic_time ());
+ clutter_frame_clock_record_flip (frame_clock,
+ g_get_monotonic_time (),
+ clutter_frame_get_hints (frame));
clutter_stage_emit_after_paint (stage, view, frame);
if (_clutter_context_get_show_fps ())
end_frame_timing_measurement (view);
}
+ else
+ {
+ clutter_frame_clock_record_flip (frame_clock,
+ g_get_monotonic_time (),
+ clutter_frame_get_hints (frame));
+ }
_clutter_stage_window_finish_frame (stage_window, view, frame);
diff --git a/cogl/cogl/cogl-onscreen-private.h b/cogl/cogl/cogl-onscreen-private.h
index 959a60533..86d8ea2d5 100644
--- a/cogl/cogl/cogl-onscreen-private.h
+++ b/cogl/cogl/cogl-onscreen-private.h
@@ -78,4 +78,7 @@ COGL_EXPORT CoglFrameInfo *
cogl_onscreen_peek_tail_frame_info (CoglOnscreen *onscreen);
COGL_EXPORT CoglFrameInfo *
-cogl_onscreen_pop_head_frame_info (CoglOnscreen *onscreen);
+cogl_onscreen_pop_head_frame_info (CoglOnscreen *onscreen);
+
+COGL_EXPORT unsigned int
+cogl_onscreen_count_pending_frames (CoglOnscreen *onscreen);
diff --git a/cogl/cogl/cogl-onscreen.c b/cogl/cogl/cogl-onscreen.c
index afb648bcd..086be7ed7 100644
--- a/cogl/cogl/cogl-onscreen.c
+++ b/cogl/cogl/cogl-onscreen.c
@@ -515,6 +515,14 @@ cogl_onscreen_pop_head_frame_info (CoglOnscreen *onscreen)
return g_queue_pop_head (&priv->pending_frame_infos);
}
+unsigned int
+cogl_onscreen_count_pending_frames (CoglOnscreen *onscreen)
+{
+ CoglOnscreenPrivate *priv = cogl_onscreen_get_instance_private (onscreen);
+
+ return g_queue_get_length (&priv->pending_frame_infos);
+}
+
CoglFrameClosure *
cogl_onscreen_add_frame_callback (CoglOnscreen *onscreen,
CoglFrameCallback callback,
diff --git a/src/backends/meta-stage-impl.c b/src/backends/meta-stage-impl.c
index 7aa24439d..727e1a5f3 100644
--- a/src/backends/meta-stage-impl.c
+++ b/src/backends/meta-stage-impl.c
@@ -774,6 +774,8 @@ meta_stage_impl_redraw_view (ClutterStageWindow *stage_window,
{
g_autoptr (GError) error = NULL;
+ clutter_frame_set_hint (frame, CLUTTER_FRAME_HINT_DIRECT_SCANOUT_ATTEMPTED);
+
if (meta_stage_impl_scanout_view (stage_impl,
stage_view,
scanout,
diff --git a/src/backends/native/meta-kms-impl-device.c b/src/backends/native/meta-kms-impl-device.c
index b15eee14d..05bc89e83 100644
--- a/src/backends/native/meta-kms-impl-device.c
+++ b/src/backends/native/meta-kms-impl-device.c
@@ -1559,9 +1559,11 @@ meta_kms_impl_device_handle_update (MetaKmsImplDevice *impl_device,
meta_kms_update_merge_from (crtc_frame->pending_update, update);
meta_kms_update_free (update);
update = g_steal_pointer (&crtc_frame->pending_update);
- disarm_crtc_frame_deadline_timer (crtc_frame);
}
+ if (crtc_frame->deadline.armed)
+ disarm_crtc_frame_deadline_timer (crtc_frame);
+
meta_kms_device_handle_flush (priv->device, latch_crtc);
feedback = do_process (impl_device, latch_crtc, update, flags);
diff --git a/src/backends/native/meta-kms.c b/src/backends/native/meta-kms.c
index 795008b21..70d1e792c 100644
--- a/src/backends/native/meta-kms.c
+++ b/src/backends/native/meta-kms.c
@@ -63,6 +63,8 @@ struct _MetaKms
int kernel_thread_inhibit_count;
MetaKmsCursorManager *cursor_manager;
+
+ gboolean shutting_down;
};
G_DEFINE_TYPE (MetaKms, meta_kms, META_TYPE_THREAD)
@@ -354,6 +356,7 @@ static void
on_prepare_shutdown (MetaBackend *backend,
MetaKms *kms)
{
+ kms->shutting_down = TRUE;
meta_kms_run_impl_task_sync (kms, prepare_shutdown_in_impl, NULL, NULL);
meta_thread_flush_callbacks (META_THREAD (kms));
@@ -408,6 +411,12 @@ meta_kms_new (MetaBackend *backend,
return kms;
}
+gboolean
+meta_kms_is_shutting_down (MetaKms *kms)
+{
+ return kms->shutting_down;
+}
+
static void
meta_kms_finalize (GObject *object)
{
diff --git a/src/backends/native/meta-kms.h b/src/backends/native/meta-kms.h
index 743401406..f6b19520b 100644
--- a/src/backends/native/meta-kms.h
+++ b/src/backends/native/meta-kms.h
@@ -60,6 +60,8 @@ MetaKmsDevice * meta_kms_create_device (MetaKms *kms,
MetaKmsDeviceFlag flags,
GError **error);
+gboolean meta_kms_is_shutting_down (MetaKms *kms);
+
MetaKms * meta_kms_new (MetaBackend *backend,
MetaKmsFlags flags,
GError **error);
diff --git a/src/backends/native/meta-onscreen-native.c b/src/backends/native/meta-onscreen-native.c
index 1a31f04a1..9836663d0 100644
--- a/src/backends/native/meta-onscreen-native.c
+++ b/src/backends/native/meta-onscreen-native.c
@@ -76,7 +76,7 @@ typedef struct _MetaOnscreenNativeSecondaryGpuState
struct {
MetaDrmBufferDumb *current_dumb_fb;
- MetaDrmBufferDumb *dumb_fbs[2];
+ MetaDrmBufferDumb *dumb_fbs[3];
} cpu;
gboolean noted_primary_gpu_copy_ok;
@@ -98,9 +98,13 @@ struct _MetaOnscreenNative
struct {
struct gbm_surface *surface;
MetaDrmBuffer *current_fb;
+ MetaDrmBuffer *posted_fb;
MetaDrmBuffer *next_fb;
+ MetaDrmBuffer *stalled_fb;
CoglScanout *current_scanout;
+ CoglScanout *posted_scanout;
CoglScanout *next_scanout;
+ CoglScanout *stalled_scanout;
} gbm;
#ifdef HAVE_EGL_DEVICE
@@ -125,6 +129,16 @@ struct _MetaOnscreenNative
gulong privacy_screen_changed_handler_id;
gulong color_space_changed_handler_id;
gulong hdr_metadata_changed_handler_id;
+
+ gboolean needs_flush;
+
+ unsigned int swaps_pending;
+
+ struct {
+ int *rectangles; /* 4 x n_rectangles */
+ int n_rectangles;
+ ClutterFrame *frame;
+ } next_post;
};
G_DEFINE_TYPE (MetaOnscreenNative, meta_onscreen_native,
@@ -132,44 +146,42 @@ G_DEFINE_TYPE (MetaOnscreenNative, meta_onscreen_native,
static GQuark blit_source_quark = 0;
+static void
+try_post_latest_swap (CoglOnscreen *onscreen);
+
+static void
+post_finish_frame (MetaOnscreenNative *onscreen_native,
+ MetaKmsUpdate *kms_update);
+
static gboolean
init_secondary_gpu_state (MetaRendererNative *renderer_native,
CoglOnscreen *onscreen,
GError **error);
-static void
-free_current_bo (CoglOnscreen *onscreen)
-{
- MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
-
- g_clear_object (&onscreen_native->gbm.current_fb);
- g_clear_object (&onscreen_native->gbm.current_scanout);
-}
-
static void
meta_onscreen_native_swap_drm_fb (CoglOnscreen *onscreen)
{
MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
- if (!onscreen_native->gbm.next_fb)
+ if (!onscreen_native->gbm.posted_fb)
return;
- free_current_bo (onscreen);
+ g_set_object (&onscreen_native->gbm.current_fb,
+ onscreen_native->gbm.posted_fb);
+ g_clear_object (&onscreen_native->gbm.posted_fb);
- g_set_object (&onscreen_native->gbm.current_fb, onscreen_native->gbm.next_fb);
- g_clear_object (&onscreen_native->gbm.next_fb);
g_set_object (&onscreen_native->gbm.current_scanout,
- onscreen_native->gbm.next_scanout);
- g_clear_object (&onscreen_native->gbm.next_scanout);
+ onscreen_native->gbm.posted_scanout);
+ g_clear_object (&onscreen_native->gbm.posted_scanout);
}
static void
-meta_onscreen_native_clear_next_fb (CoglOnscreen *onscreen)
+meta_onscreen_native_clear_posted_fb (CoglOnscreen *onscreen)
{
MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
- g_clear_object (&onscreen_native->gbm.next_fb);
- g_clear_object (&onscreen_native->gbm.next_scanout);
+ g_clear_object (&onscreen_native->gbm.posted_fb);
+ g_clear_object (&onscreen_native->gbm.posted_scanout);
}
static void
@@ -207,7 +219,7 @@ meta_onscreen_native_notify_frame_complete (CoglOnscreen *onscreen)
info = cogl_onscreen_pop_head_frame_info (onscreen);
- g_assert (!cogl_onscreen_peek_head_frame_info (onscreen));
+ g_return_if_fail (info);
_cogl_onscreen_notify_frame_sync (onscreen, info);
_cogl_onscreen_notify_complete (onscreen, info);
@@ -243,6 +255,7 @@ notify_view_crtc_presented (MetaRendererView *view,
meta_onscreen_native_notify_frame_complete (onscreen);
meta_onscreen_native_swap_drm_fb (onscreen);
+ try_post_latest_swap (onscreen);
}
static void
@@ -292,15 +305,13 @@ page_flip_feedback_ready (MetaKmsCrtc *kms_crtc,
CoglFramebuffer *framebuffer =
clutter_stage_view_get_onscreen (CLUTTER_STAGE_VIEW (view));
CoglOnscreen *onscreen = COGL_ONSCREEN (framebuffer);
- MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
CoglFrameInfo *frame_info;
frame_info = cogl_onscreen_peek_head_frame_info (onscreen);
frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC;
- g_warn_if_fail (!onscreen_native->gbm.next_fb);
-
meta_onscreen_native_notify_frame_complete (onscreen);
+ try_post_latest_swap (onscreen);
}
static void
@@ -350,7 +361,8 @@ page_flip_feedback_discarded (MetaKmsCrtc *kms_crtc,
frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC;
meta_onscreen_native_notify_frame_complete (onscreen);
- meta_onscreen_native_clear_next_fb (onscreen);
+ meta_onscreen_native_clear_posted_fb (onscreen);
+ try_post_latest_swap (onscreen);
}
static const MetaKmsPageFlipListenerVtable page_flip_listener_vtable = {
@@ -411,18 +423,41 @@ custom_egl_stream_page_flip (gpointer custom_page_flip_data,
}
#endif /* HAVE_EGL_DEVICE */
-void
-meta_onscreen_native_dummy_power_save_page_flip (CoglOnscreen *onscreen)
+static void
+drop_stalled_swap (CoglOnscreen *onscreen)
{
CoglFrameInfo *frame_info;
+ MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
- meta_onscreen_native_swap_drm_fb (onscreen);
+ /* Remember we can't compare stalled_fb because it's not used by
+ * META_RENDERER_NATIVE_MODE_EGL_DEVICE. So we judge stalled to be whenever
+ * swaps_pending > 1.
+ */
+ if (onscreen_native->swaps_pending <= 1)
+ return;
+
+ onscreen_native->swaps_pending--;
+
+ g_clear_object (&onscreen_native->gbm.stalled_fb);
+ g_clear_object (&onscreen_native->gbm.stalled_scanout);
frame_info = cogl_onscreen_peek_tail_frame_info (onscreen);
frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC;
meta_onscreen_native_notify_frame_complete (onscreen);
}
+void
+meta_onscreen_native_dummy_power_save_page_flip (CoglOnscreen *onscreen)
+{
+ drop_stalled_swap (onscreen);
+
+ /* If the monitor just woke up and the shell is fully idle (has nothing
+ * more to swap) then we just woke to an indefinitely black screen. Let's
+ * fix that using the last swap (which is never classified as "stalled").
+ */
+ try_post_latest_swap (onscreen);
+}
+
static void
apply_transform (MetaCrtcKms *crtc_kms,
MetaKmsPlaneAssignment *kms_plane_assignment,
@@ -521,13 +556,21 @@ meta_onscreen_native_flip_crtc (CoglOnscreen *onscreen,
switch (renderer_gpu_data->mode)
{
case META_RENDERER_NATIVE_MODE_GBM:
- buffer = onscreen_native->gbm.next_fb;
+ g_set_object (&onscreen_native->gbm.posted_fb,
+ onscreen_native->gbm.next_fb);
+ g_clear_object (&onscreen_native->gbm.next_fb);
+
+ buffer = onscreen_native->gbm.posted_fb;
- if (onscreen_native->gbm.next_scanout)
+ g_set_object (&onscreen_native->gbm.posted_scanout,
+ onscreen_native->gbm.next_scanout);
+ g_clear_object (&onscreen_native->gbm.next_scanout);
+
+ if (onscreen_native->gbm.posted_scanout)
{
- cogl_scanout_get_src_rect (onscreen_native->gbm.next_scanout,
+ cogl_scanout_get_src_rect (onscreen_native->gbm.posted_scanout,
&src_rect);
- cogl_scanout_get_dst_rect (onscreen_native->gbm.next_scanout,
+ cogl_scanout_get_dst_rect (onscreen_native->gbm.posted_scanout,
&dst_rect);
}
else
@@ -918,12 +961,17 @@ static MetaDrmBufferDumb *
secondary_gpu_get_next_dumb_buffer (MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state)
{
MetaDrmBufferDumb *current_dumb_fb;
+ const int n_dumb_fbs = G_N_ELEMENTS (secondary_gpu_state->cpu.dumb_fbs);
+ int i;
current_dumb_fb = secondary_gpu_state->cpu.current_dumb_fb;
- if (current_dumb_fb == secondary_gpu_state->cpu.dumb_fbs[0])
- return secondary_gpu_state->cpu.dumb_fbs[1];
- else
- return secondary_gpu_state->cpu.dumb_fbs[0];
+ for (i = 0; i < n_dumb_fbs; i++)
+ {
+ if (current_dumb_fb == secondary_gpu_state->cpu.dumb_fbs[i])
+ return secondary_gpu_state->cpu.dumb_fbs[(i + 1) % n_dumb_fbs];
+ }
+
+ return secondary_gpu_state->cpu.dumb_fbs[0];
}
static MetaDrmBuffer *
@@ -1255,10 +1303,17 @@ swap_buffer_result_feedback (const MetaKmsFeedback *kms_feedback,
g_warning ("Page flip failed: %s", error->message);
frame_info = cogl_onscreen_peek_head_frame_info (onscreen);
- frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC;
- meta_onscreen_native_notify_frame_complete (onscreen);
- meta_onscreen_native_clear_next_fb (onscreen);
+ /* After resuming from suspend, drop_stalled_swap might have done this
+ * already and emptied the frame_info queue.
+ */
+ if (frame_info)
+ {
+ frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC;
+ meta_onscreen_native_notify_frame_complete (onscreen);
+ }
+
+ meta_onscreen_native_clear_posted_fb (onscreen);
}
static const MetaKmsResultListenerVtable swap_buffer_result_listener_vtable = {
@@ -1279,32 +1334,37 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen,
CoglRendererEGL *cogl_renderer_egl = cogl_renderer->winsys;
MetaRendererNativeGpuData *renderer_gpu_data = cogl_renderer_egl->platform;
MetaRendererNative *renderer_native = renderer_gpu_data->renderer_native;
- MetaRenderer *renderer = META_RENDERER (renderer_native);
- MetaBackend *backend = meta_renderer_get_backend (renderer);
- MetaMonitorManager *monitor_manager =
- meta_backend_get_monitor_manager (backend);
MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state;
MetaGpuKms *render_gpu = onscreen_native->render_gpu;
MetaDeviceFile *render_device_file;
ClutterFrame *frame = user_data;
- MetaFrameNative *frame_native = meta_frame_native_from_frame (frame);
- MetaKmsUpdate *kms_update;
CoglOnscreenClass *parent_class;
gboolean create_timestamp_query = TRUE;
gboolean egl_context_changed = FALSE;
- MetaPowerSave power_save_mode;
g_autoptr (GError) error = NULL;
MetaDrmBufferFlags buffer_flags;
MetaDrmBufferGbm *buffer_gbm;
g_autoptr (MetaDrmBuffer) primary_gpu_fb = NULL;
g_autoptr (MetaDrmBuffer) secondary_gpu_fb = NULL;
- MetaKmsCrtc *kms_crtc;
- MetaKmsDevice *kms_device;
+ size_t rectangles_size;
COGL_TRACE_BEGIN_SCOPED (MetaRendererNativeSwapBuffers,
"Meta::OnscreenNative::swap_buffers_with_damage()");
+ if (meta_is_topic_enabled (META_DEBUG_KMS))
+ {
+ unsigned int frames_pending =
+ cogl_onscreen_count_pending_frames (onscreen);
+
+ meta_topic (META_DEBUG_KMS,
+ "Swap buffers: %u frames pending (%s-buffering)",
+ frames_pending,
+ frames_pending == 1 ? "double" :
+ frames_pending == 2 ? "triple" :
+ "?");
+ }
+
secondary_gpu_fb =
update_secondary_gpu_state_pre_swap_buffers (onscreen,
rectangles,
@@ -1379,7 +1439,17 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen,
switch (renderer_gpu_data->mode)
{
case META_RENDERER_NATIVE_MODE_GBM:
- g_warn_if_fail (onscreen_native->gbm.next_fb == NULL);
+ if (onscreen_native->gbm.next_fb != NULL)
+ {
+ g_warn_if_fail (onscreen_native->gbm.stalled_fb == NULL);
+ drop_stalled_swap (onscreen);
+ g_assert (onscreen_native->gbm.stalled_fb == NULL);
+ onscreen_native->gbm.stalled_fb =
+ g_steal_pointer (&onscreen_native->gbm.next_fb);
+ onscreen_native->gbm.stalled_scanout =
+ g_steal_pointer (&onscreen_native->gbm.next_scanout);
+ }
+
if (onscreen_native->secondary_gpu_state)
g_set_object (&onscreen_native->gbm.next_fb, secondary_gpu_fb);
else
@@ -1404,6 +1474,9 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen,
#endif
}
+ clutter_frame_set_result (frame,
+ CLUTTER_FRAME_RESULT_PENDING_PRESENTED);
+
/*
* If we changed EGL context, cogl will have the wrong idea about what is
* current, making it fail to set it when it needs to. Avoid that by making
@@ -1413,12 +1486,78 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen,
if (egl_context_changed)
_cogl_winsys_egl_ensure_current (cogl_display);
- kms_crtc = meta_crtc_kms_get_kms_crtc (META_CRTC_KMS (onscreen_native->crtc));
- kms_device = meta_kms_crtc_get_device (kms_crtc);
+ rectangles_size = n_rectangles * 4 * sizeof (int);
+ onscreen_native->next_post.rectangles =
+ g_realloc (onscreen_native->next_post.rectangles, rectangles_size);
+ memcpy (onscreen_native->next_post.rectangles, rectangles, rectangles_size);
+ onscreen_native->next_post.n_rectangles = n_rectangles;
+
+ g_clear_pointer (&onscreen_native->next_post.frame, clutter_frame_unref);
+ onscreen_native->next_post.frame = clutter_frame_ref (frame);
+
+ onscreen_native->swaps_pending++;
+ try_post_latest_swap (onscreen);
+}
+
+static void
+try_post_latest_swap (CoglOnscreen *onscreen)
+{
+ CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen);
+ CoglContext *cogl_context = cogl_framebuffer_get_context (framebuffer);
+ CoglRenderer *cogl_renderer = cogl_context->display->renderer;
+ CoglRendererEGL *cogl_renderer_egl = cogl_renderer->winsys;
+ MetaRendererNativeGpuData *renderer_gpu_data = cogl_renderer_egl->platform;
+ MetaRendererNative *renderer_native = renderer_gpu_data->renderer_native;
+ MetaRenderer *renderer = META_RENDERER (renderer_native);
+ MetaBackend *backend = meta_renderer_get_backend (renderer);
+ MetaBackendNative *backend_native = META_BACKEND_NATIVE (backend);
+ MetaKms *kms = meta_backend_native_get_kms (backend_native);
+ MetaMonitorManager *monitor_manager =
+ meta_backend_get_monitor_manager (backend);
+ MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
+ MetaPowerSave power_save_mode;
+ MetaCrtcKms *crtc_kms = META_CRTC_KMS (onscreen_native->crtc);
+ MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (crtc_kms);
+ MetaKmsDevice *kms_device = meta_kms_crtc_get_device (kms_crtc);
+ MetaKmsUpdate *kms_update;
+ g_autoptr (MetaKmsFeedback) kms_feedback = NULL;
+ g_autoptr (ClutterFrame) frame = NULL;
+ MetaFrameNative *frame_native;
+
+ if (onscreen_native->next_post.frame == NULL ||
+ onscreen_native->view == NULL ||
+ meta_kms_is_shutting_down (kms))
+ return;
power_save_mode = meta_monitor_manager_get_power_save_mode (monitor_manager);
if (power_save_mode == META_POWER_SAVE_ON)
{
+ unsigned int frames_pending =
+ cogl_onscreen_count_pending_frames (onscreen);
+ unsigned int posts_pending;
+
+ g_assert (frames_pending >= onscreen_native->swaps_pending);
+ posts_pending = frames_pending - onscreen_native->swaps_pending;
+ if (posts_pending > 0)
+ return; /* wait for the next frame notification and then try again */
+
+ frame = g_steal_pointer (&onscreen_native->next_post.frame);
+ frame_native = meta_frame_native_from_frame (frame);
+
+ if (onscreen_native->swaps_pending == 0)
+ {
+ if (frame_native)
+ {
+ kms_update = meta_frame_native_steal_kms_update (frame_native);
+ if (kms_update)
+ post_finish_frame (onscreen_native, kms_update);
+ }
+ return;
+ }
+
+ drop_stalled_swap (onscreen);
+ onscreen_native->swaps_pending--;
+
kms_update = meta_frame_native_ensure_kms_update (frame_native,
kms_device);
meta_kms_update_add_result_listener (kms_update,
@@ -1433,15 +1572,13 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen,
onscreen_native->crtc,
kms_update,
META_KMS_ASSIGN_PLANE_FLAG_NONE,
- rectangles,
- n_rectangles);
+ onscreen_native->next_post.rectangles,
+ onscreen_native->next_post.n_rectangles);
}
else
{
meta_renderer_native_queue_power_save_page_flip (renderer_native,
onscreen);
- clutter_frame_set_result (frame,
- CLUTTER_FRAME_RESULT_PENDING_PRESENTED);
return;
}
@@ -1461,8 +1598,6 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen,
kms_update = meta_frame_native_steal_kms_update (frame_native);
meta_renderer_native_queue_mode_set_update (renderer_native,
kms_update);
- clutter_frame_set_result (frame,
- CLUTTER_FRAME_RESULT_PENDING_PRESENTED);
return;
}
else if (meta_renderer_native_has_pending_mode_set (renderer_native))
@@ -1476,8 +1611,6 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen,
meta_frame_native_steal_kms_update (frame_native);
meta_renderer_native_post_mode_set_updates (renderer_native);
- clutter_frame_set_result (frame,
- CLUTTER_FRAME_RESULT_PENDING_PRESENTED);
return;
}
break;
@@ -1493,8 +1626,6 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen,
kms_update);
meta_renderer_native_post_mode_set_updates (renderer_native);
- clutter_frame_set_result (frame,
- CLUTTER_FRAME_RESULT_PENDING_PRESENTED);
return;
}
break;
@@ -1509,7 +1640,6 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen,
kms_update = meta_frame_native_steal_kms_update (frame_native);
meta_kms_device_post_update (kms_device, kms_update,
META_KMS_UPDATE_FLAG_NONE);
- clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_PENDING_PRESENTED);
}
gboolean
@@ -1580,7 +1710,7 @@ scanout_result_feedback (const MetaKmsFeedback *kms_feedback,
g_warning ("Direct scanout page flip failed: %s", error->message);
- cogl_scanout_notify_failed (onscreen_native->gbm.next_scanout,
+ cogl_scanout_notify_failed (onscreen_native->gbm.posted_scanout,
onscreen);
clutter_stage_view_add_redraw_clip (view, NULL);
clutter_stage_view_schedule_update_now (view);
@@ -1590,7 +1720,7 @@ scanout_result_feedback (const MetaKmsFeedback *kms_feedback,
frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC;
meta_onscreen_native_notify_frame_complete (onscreen);
- meta_onscreen_native_clear_next_fb (onscreen);
+ meta_onscreen_native_clear_posted_fb (onscreen);
}
static const MetaKmsResultListenerVtable scanout_result_listener_vtable = {
@@ -1642,6 +1772,18 @@ meta_onscreen_native_direct_scanout (CoglOnscreen *onscreen,
return FALSE;
}
+ /* Our direct scanout frame counts as 1, so more than that means we would
+ * be jumping the queue (and post would fail).
+ */
+ if (cogl_onscreen_count_pending_frames (onscreen) > 1)
+ {
+ g_set_error_literal (error,
+ COGL_SCANOUT_ERROR,
+ COGL_SCANOUT_ERROR_INHIBITED,
+ "Direct scanout is inhibited during triple buffering");
+ return FALSE;
+ }
+
renderer_gpu_data = meta_renderer_native_get_gpu_data (renderer_native,
render_gpu);
@@ -1757,11 +1899,7 @@ meta_onscreen_native_before_redraw (CoglOnscreen *onscreen,
ClutterFrame *frame)
{
MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
- MetaCrtcKms *crtc_kms = META_CRTC_KMS (onscreen_native->crtc);
- MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (crtc_kms);
- meta_kms_device_await_flush (meta_kms_crtc_get_device (kms_crtc),
- kms_crtc);
maybe_update_frame_sync (onscreen_native, frame);
}
@@ -1877,22 +2015,79 @@ meta_onscreen_native_finish_frame (CoglOnscreen *onscreen,
MetaKmsDevice *kms_device = meta_kms_crtc_get_device (kms_crtc);
MetaFrameNative *frame_native = meta_frame_native_from_frame (frame);
MetaKmsUpdate *kms_update;
+ unsigned int frames_pending = cogl_onscreen_count_pending_frames (onscreen);
+ unsigned int swaps_pending = onscreen_native->swaps_pending;
+ unsigned int posts_pending = frames_pending - swaps_pending;
- kms_update = meta_frame_native_steal_kms_update (frame_native);
- if (!kms_update)
+ onscreen_native->needs_flush |= meta_kms_device_handle_flush (kms_device,
+ kms_crtc);
+
+ if (!meta_frame_native_has_kms_update (frame_native))
{
- if (meta_kms_device_handle_flush (kms_device, kms_crtc))
- {
- kms_update = meta_kms_update_new (kms_device);
- meta_kms_update_set_flushing (kms_update, kms_crtc);
- }
- else
+ if (!onscreen_native->needs_flush || posts_pending)
{
clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_IDLE);
return;
}
}
+ if (posts_pending && !swaps_pending)
+ {
+ g_return_if_fail (meta_frame_native_has_kms_update (frame_native));
+ g_warn_if_fail (onscreen_native->next_post.frame == NULL);
+
+ g_clear_pointer (&onscreen_native->next_post.frame, clutter_frame_unref);
+ onscreen_native->next_post.frame = clutter_frame_ref (frame);
+ clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_PENDING_PRESENTED);
+ return;
+ }
+
+ kms_update = meta_frame_native_steal_kms_update (frame_native);
+
+ if (posts_pending && swaps_pending)
+ {
+ MetaFrameNative *older_frame_native;
+ MetaKmsUpdate *older_kms_update;
+
+ g_return_if_fail (kms_update);
+ g_return_if_fail (onscreen_native->next_post.frame != NULL);
+
+ older_frame_native =
+ meta_frame_native_from_frame (onscreen_native->next_post.frame);
+ older_kms_update =
+ meta_frame_native_ensure_kms_update (older_frame_native, kms_device);
+ meta_kms_update_merge_from (older_kms_update, kms_update);
+ meta_kms_update_free (kms_update);
+ clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_IDLE);
+ return;
+ }
+
+ if (!kms_update)
+ {
+ kms_update = meta_kms_update_new (kms_device);
+ g_warn_if_fail (onscreen_native->needs_flush);
+ }
+
+ if (onscreen_native->needs_flush)
+ {
+ meta_kms_update_set_flushing (kms_update, kms_crtc);
+ onscreen_native->needs_flush = FALSE;
+ }
+
+ post_finish_frame (onscreen_native, kms_update);
+
+ clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_PENDING_PRESENTED);
+}
+
+static void
+post_finish_frame (MetaOnscreenNative *onscreen_native,
+ MetaKmsUpdate *kms_update)
+{
+ MetaCrtc *crtc = onscreen_native->crtc;
+ MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (META_CRTC_KMS (crtc));
+ MetaKmsDevice *kms_device = meta_kms_crtc_get_device (kms_crtc);
+ g_autoptr (MetaKmsFeedback) kms_feedback = NULL;
+
meta_kms_update_add_result_listener (kms_update,
&finish_frame_result_listener_vtable,
NULL,
@@ -1915,7 +2110,19 @@ meta_onscreen_native_finish_frame (CoglOnscreen *onscreen,
meta_kms_update_set_flushing (kms_update, kms_crtc);
meta_kms_device_post_update (kms_device, kms_update,
META_KMS_UPDATE_FLAG_NONE);
- clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_PENDING_PRESENTED);
+}
+
+void
+meta_onscreen_native_discard_pending_swaps (CoglOnscreen *onscreen)
+{
+ MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
+
+ onscreen_native->swaps_pending = 0;
+
+ g_clear_object (&onscreen_native->gbm.stalled_fb);
+ g_clear_object (&onscreen_native->gbm.stalled_scanout);
+ g_clear_object (&onscreen_native->gbm.next_fb);
+ g_clear_object (&onscreen_native->gbm.next_scanout);
}
static gboolean
@@ -2830,8 +3037,11 @@ meta_onscreen_native_dispose (GObject *object)
{
case META_RENDERER_NATIVE_MODE_GBM:
g_clear_object (&onscreen_native->gbm.next_fb);
+ g_clear_object (&onscreen_native->gbm.posted_fb);
+ g_clear_object (&onscreen_native->gbm.current_fb);
g_clear_object (&onscreen_native->gbm.next_scanout);
- free_current_bo (onscreen);
+ g_clear_object (&onscreen_native->gbm.posted_scanout);
+ g_clear_object (&onscreen_native->gbm.current_scanout);
break;
case META_RENDERER_NATIVE_MODE_SURFACELESS:
g_assert_not_reached ();
@@ -2865,6 +3075,10 @@ meta_onscreen_native_dispose (GObject *object)
g_clear_object (&onscreen_native->output);
g_clear_object (&onscreen_native->crtc);
+
+ g_clear_pointer (&onscreen_native->next_post.rectangles, g_free);
+ g_clear_pointer (&onscreen_native->next_post.frame, clutter_frame_unref);
+ onscreen_native->next_post.n_rectangles = 0;
}
static void
diff --git a/src/backends/native/meta-onscreen-native.h b/src/backends/native/meta-onscreen-native.h
index 0e1193325..e30357d19 100644
--- a/src/backends/native/meta-onscreen-native.h
+++ b/src/backends/native/meta-onscreen-native.h
@@ -48,6 +48,8 @@ void meta_onscreen_native_dummy_power_save_page_flip (CoglOnscreen *onscreen);
gboolean meta_onscreen_native_is_buffer_scanout_compatible (CoglOnscreen *onscreen,
CoglScanout *scanout);
+void meta_onscreen_native_discard_pending_swaps (CoglOnscreen *onscreen);
+
void meta_onscreen_native_set_view (CoglOnscreen *onscreen,
MetaRendererView *view);
diff --git a/src/backends/native/meta-renderer-native.c b/src/backends/native/meta-renderer-native.c
index aa76d018c..3c22b4e86 100644
--- a/src/backends/native/meta-renderer-native.c
+++ b/src/backends/native/meta-renderer-native.c
@@ -731,12 +731,18 @@ static gboolean
dummy_power_save_page_flip_cb (gpointer user_data)
{
MetaRendererNative *renderer_native = user_data;
+ GList *old_list =
+ g_steal_pointer (&renderer_native->power_save_page_flip_onscreens);
- g_list_foreach (renderer_native->power_save_page_flip_onscreens,
+ g_list_foreach (old_list,
(GFunc) meta_onscreen_native_dummy_power_save_page_flip,
NULL);
- g_clear_list (&renderer_native->power_save_page_flip_onscreens,
+ g_clear_list (&old_list,
g_object_unref);
+
+ if (renderer_native->power_save_page_flip_onscreens != NULL)
+ return G_SOURCE_CONTINUE;
+
renderer_native->power_save_page_flip_source_id = 0;
return G_SOURCE_REMOVE;
@@ -748,6 +754,9 @@ meta_renderer_native_queue_power_save_page_flip (MetaRendererNative *renderer_na
{
const unsigned int timeout_ms = 100;
+ if (g_list_find (renderer_native->power_save_page_flip_onscreens, onscreen))
+ return;
+
if (!renderer_native->power_save_page_flip_source_id)
{
renderer_native->power_save_page_flip_source_id =
@@ -1529,6 +1538,26 @@ detach_onscreens (MetaRenderer *renderer)
}
}
+static void
+discard_pending_swaps (MetaRenderer *renderer)
+{
+ GList *views = meta_renderer_get_views (renderer);;
+ GList *l;
+
+ for (l = views; l; l = l->next)
+ {
+ ClutterStageView *stage_view = l->data;
+ CoglFramebuffer *fb = clutter_stage_view_get_onscreen (stage_view);
+ CoglOnscreen *onscreen;
+
+ if (!COGL_IS_ONSCREEN (fb))
+ continue;
+
+ onscreen = COGL_ONSCREEN (fb);
+ meta_onscreen_native_discard_pending_swaps (onscreen);
+ }
+}
+
static void
meta_renderer_native_rebuild_views (MetaRenderer *renderer)
{
@@ -1539,6 +1568,7 @@ meta_renderer_native_rebuild_views (MetaRenderer *renderer)
MetaRendererClass *parent_renderer_class =
META_RENDERER_CLASS (meta_renderer_native_parent_class);
+ discard_pending_swaps (renderer);
meta_kms_discard_pending_page_flips (kms);
g_hash_table_remove_all (renderer_native->mode_set_updates);
diff --git a/src/tests/native-kms-render.c b/src/tests/native-kms-render.c
index f5ebc23fe..2f870fdc3 100644
--- a/src/tests/native-kms-render.c
+++ b/src/tests/native-kms-render.c
@@ -39,6 +39,8 @@
#include "tests/meta-wayland-test-driver.h"
#include "tests/meta-wayland-test-utils.h"
+#define N_FRAMES_PER_TEST 30
+
typedef struct
{
int number_of_frames_left;
@@ -46,12 +48,15 @@ typedef struct
struct {
int n_paints;
- uint32_t fb_id;
+ int n_presentations;
+ int n_direct_scanouts;
+ GList *fb_ids;
} scanout;
gboolean wait_for_scanout;
struct {
+ int scanouts_attempted;
gboolean scanout_sabotaged;
gboolean fallback_painted;
guint repaint_guard_id;
@@ -101,7 +106,7 @@ meta_test_kms_render_basic (void)
gulong handler_id;
test = (KmsRenderingTest) {
- .number_of_frames_left = 10,
+ .number_of_frames_left = N_FRAMES_PER_TEST,
.loop = g_main_loop_new (NULL, FALSE),
};
handler_id = g_signal_connect (stage, "after-update",
@@ -123,7 +128,6 @@ on_scanout_before_update (ClutterStage *stage,
KmsRenderingTest *test)
{
test->scanout.n_paints = 0;
- test->scanout.fb_id = 0;
}
static void
@@ -135,6 +139,7 @@ on_scanout_before_paint (ClutterStage *stage,
CoglScanout *scanout;
CoglScanoutBuffer *scanout_buffer;
MetaDrmBuffer *buffer;
+ uint32_t fb_id;
scanout = clutter_stage_view_peek_scanout (stage_view);
if (!scanout)
@@ -143,8 +148,13 @@ on_scanout_before_paint (ClutterStage *stage,
scanout_buffer = cogl_scanout_get_buffer (scanout);
g_assert_true (META_IS_DRM_BUFFER (scanout_buffer));
buffer = META_DRM_BUFFER (scanout_buffer);
- test->scanout.fb_id = meta_drm_buffer_get_fb_id (buffer);
- g_assert_cmpuint (test->scanout.fb_id, >, 0);
+ fb_id = meta_drm_buffer_get_fb_id (buffer);
+ g_assert_cmpuint (fb_id, >, 0);
+ test->scanout.fb_ids = g_list_append (test->scanout.fb_ids,
+ GUINT_TO_POINTER (fb_id));
+
+ /* Triple buffering, but no higher */
+ g_assert_cmpuint (g_list_length (test->scanout.fb_ids), <=, 2);
}
static void
@@ -173,12 +183,12 @@ on_scanout_presented (ClutterStage *stage,
MetaDeviceFile *device_file;
GError *error = NULL;
drmModeCrtc *drm_crtc;
+ uint32_t first_fb_id_expected;
- if (test->wait_for_scanout && test->scanout.n_paints > 0)
+ if (test->wait_for_scanout && test->scanout.fb_ids == NULL)
return;
- if (test->wait_for_scanout && test->scanout.fb_id == 0)
- return;
+ test->scanout.n_presentations++;
device_pool = meta_backend_native_get_device_pool (backend_native);
@@ -197,15 +207,41 @@ on_scanout_presented (ClutterStage *stage,
drm_crtc = drmModeGetCrtc (meta_device_file_get_fd (device_file),
meta_kms_crtc_get_id (kms_crtc));
g_assert_nonnull (drm_crtc);
- if (test->scanout.fb_id == 0)
- g_assert_cmpuint (drm_crtc->buffer_id, !=, test->scanout.fb_id);
+
+ if (test->scanout.fb_ids)
+ {
+ test->scanout.n_direct_scanouts++;
+ first_fb_id_expected = GPOINTER_TO_UINT (test->scanout.fb_ids->data);
+ test->scanout.fb_ids = g_list_delete_link (test->scanout.fb_ids,
+ test->scanout.fb_ids);
+ }
else
- g_assert_cmpuint (drm_crtc->buffer_id, ==, test->scanout.fb_id);
+ {
+ first_fb_id_expected = 0;
+ }
+
+ /* The buffer ID won't match on the first frame because switching from
+ * triple buffered compositing to double buffered direct scanout takes
+ * an extra frame to drain the queue. Thereafter we are in direct scanout
+ * mode and expect the buffer IDs to match.
+ */
+ if (test->scanout.n_presentations > 1)
+ {
+ if (first_fb_id_expected == 0)
+ g_assert_cmpuint (drm_crtc->buffer_id, !=, first_fb_id_expected);
+ else
+ g_assert_cmpuint (drm_crtc->buffer_id, ==, first_fb_id_expected);
+ }
+
drmModeFreeCrtc (drm_crtc);
meta_device_file_release (device_file);
- g_main_loop_quit (test->loop);
+ test->number_of_frames_left--;
+ if (test->number_of_frames_left <= 0)
+ g_main_loop_quit (test->loop);
+ else
+ clutter_actor_queue_redraw (CLUTTER_ACTOR (stage));
}
typedef enum
@@ -244,7 +280,9 @@ meta_test_kms_render_client_scanout (void)
g_assert_nonnull (wayland_test_client);
test = (KmsRenderingTest) {
+ .number_of_frames_left = N_FRAMES_PER_TEST,
.loop = g_main_loop_new (NULL, FALSE),
+ .scanout = {0},
.wait_for_scanout = TRUE,
};
@@ -270,7 +308,8 @@ meta_test_kms_render_client_scanout (void)
clutter_actor_queue_redraw (CLUTTER_ACTOR (stage));
g_main_loop_run (test.loop);
- g_assert_cmpuint (test.scanout.fb_id, >, 0);
+ g_assert_cmpint (test.scanout.n_presentations, ==, N_FRAMES_PER_TEST);
+ g_assert_cmpint (test.scanout.n_direct_scanouts, ==, N_FRAMES_PER_TEST);
g_debug ("Unmake fullscreen");
window = meta_find_window_from_title (test_context, "dma-buf-scanout-test");
@@ -292,10 +331,15 @@ meta_test_kms_render_client_scanout (void)
g_assert_cmpint (buffer_rect.y, ==, 10);
test.wait_for_scanout = FALSE;
+ test.number_of_frames_left = N_FRAMES_PER_TEST;
+ test.scanout.n_presentations = 0;
+ test.scanout.n_direct_scanouts = 0;
+
clutter_actor_queue_redraw (CLUTTER_ACTOR (stage));
g_main_loop_run (test.loop);
- g_assert_cmpuint (test.scanout.fb_id, ==, 0);
+ g_assert_cmpint (test.scanout.n_presentations, ==, N_FRAMES_PER_TEST);
+ g_assert_cmpint (test.scanout.n_direct_scanouts, ==, 0);
g_debug ("Moving back to 0, 0");
meta_window_move_frame (window, TRUE, 0, 0);
@@ -307,10 +351,15 @@ meta_test_kms_render_client_scanout (void)
g_assert_cmpint (buffer_rect.y, ==, 0);
test.wait_for_scanout = TRUE;
+ test.number_of_frames_left = N_FRAMES_PER_TEST;
+ test.scanout.n_presentations = 0;
+ test.scanout.n_direct_scanouts = 0;
+
clutter_actor_queue_redraw (CLUTTER_ACTOR (stage));
g_main_loop_run (test.loop);
- g_assert_cmpuint (test.scanout.fb_id, >, 0);
+ g_assert_cmpint (test.scanout.n_presentations, ==, N_FRAMES_PER_TEST);
+ g_assert_cmpint (test.scanout.n_direct_scanouts, ==, N_FRAMES_PER_TEST);
g_signal_handler_disconnect (stage, before_update_handler_id);
g_signal_handler_disconnect (stage, before_paint_handler_id);
@@ -364,6 +413,15 @@ on_scanout_fallback_before_paint (ClutterStage *stage,
if (!scanout)
return;
+ test->scanout_fallback.scanouts_attempted++;
+
+ /* The first scanout candidate frame will get composited due to triple
+ * buffering draining the queue to drop to double buffering. So don't
+ * sabotage that first frame.
+ */
+ if (test->scanout_fallback.scanouts_attempted < 2)
+ return;
+
g_assert_false (test->scanout_fallback.scanout_sabotaged);
if (is_atomic_mode_setting (kms_device))
@@ -401,6 +459,15 @@ on_scanout_fallback_paint_view (ClutterStage *stage,
g_clear_handle_id (&test->scanout_fallback.repaint_guard_id,
g_source_remove);
test->scanout_fallback.fallback_painted = TRUE;
+ test->scanout_fallback.scanout_sabotaged = FALSE;
+ }
+ else if (test->scanout_fallback.scanouts_attempted == 1)
+ {
+ /* Now that we've seen the first scanout attempt that was inhibited by
+ * triple buffering, try a second frame. The second one should scanout
+ * and will be sabotaged.
+ */
+ clutter_actor_queue_redraw (CLUTTER_ACTOR (stage));
}
}
@@ -410,11 +477,11 @@ on_scanout_fallback_presented (ClutterStage *stage,
ClutterFrameInfo *frame_info,
KmsRenderingTest *test)
{
- if (!test->scanout_fallback.scanout_sabotaged)
- return;
+ if (test->scanout_fallback.fallback_painted)
+ g_main_loop_quit (test->loop);
- g_assert_true (test->scanout_fallback.fallback_painted);
- g_main_loop_quit (test->loop);
+ test->number_of_frames_left--;
+ g_assert_cmpint (test->number_of_frames_left, >, 0);
}
static void
@@ -443,6 +510,7 @@ meta_test_kms_render_client_scanout_fallback (void)
g_assert_nonnull (wayland_test_client);
test = (KmsRenderingTest) {
+ .number_of_frames_left = N_FRAMES_PER_TEST,
.loop = g_main_loop_new (NULL, FALSE),
};