1
0
Fork 0

Compare commits

...

10 commits

Author SHA1 Message Date
Daniel van Vugt
78fe80a3fa
clutter/frame-clock: Optimize latency for platforms missing TIMESTAMP_QUERY
Previously if we had no measurements then `compute_max_render_time_us`
would pessimise its answer to ensure triple buffering could be reached:
```
if (frame_clock->state == CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE)
  ret += refresh_interval_us;
```
But that also meant entering triple buffering even when not required.

Now we make `compute_max_render_time_us` more honest and return failure
if the answer isn't known (or is disabled). This in turn allows us to
optimize `calculate_next_update_time_us` for this special case, ensuring
triple buffering can be used, but isn't blindly always used.

This makes a visible difference to the latency when dragging windows in
Xorg, but will also help Wayland sessions on platforms lacking
TIMESTAMP_QUERY such as Raspberry Pi.

Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-15 14:28:27 +09:00
Daniel van Vugt
e730d1d1f5
clutter/frame-clock: Record measurements of zero for cursor-only updates
But only if we've ever got actual swap measurements
(COGL_FEATURE_ID_TIMESTAMP_QUERY). If it's supported then we now drop to
double buffering and get optimal latency on a burst of cursor-only
updates.

Closes: https://launchpad.net/bugs/2023363
Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-15 14:28:27 +09:00
Daniel van Vugt
18b3361374
onscreen/native: Avoid callbacks on "detached" onscreens
Detached onscreens have no valid view so avoid servicing callbacks on
them during/after sleep mode. As previously mentioned in 45bda2d969.

Closes: https://launchpad.net/bugs/2020049
Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-15 14:28:27 +09:00
Daniel van Vugt
ef45ec7e9f
tests/native-kms-render: Fix failing client-scanout test
It was assuming an immediate transition from compositing (triple
buffering) to direct scanout (double buffering), whereas there is
a one frame delay in that transition as the buffer queue shrinks.
We don't lose any frames in the transition.

Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-15 14:28:27 +09:00
Daniel van Vugt
da6429f2aa
clutter/frame-clock: Conditionally disable triple buffering
1. When direct scanout is attempted

There's no compositing during direct scanout so the "render" time is zero.
Thus there is no need to implement triple buffering for direct scanouts.
Stick to double buffering and enjoy the lower latency.

2. If disabled by environment variable MUTTER_DEBUG_TRIPLE_BUFFERING

With possible values {never, auto, always} where auto is the default.

3. When VRR is in use

VRR calls `clutter_frame_clock_schedule_update_now` which would keep
the buffer queue full, which in turn prevented direct scanout mode.
Because OnscreenNative currently only supports direct scanout with
double buffering.

We now break that feedback loop by preventing triple buffering from
being scheduled when the frame clock mode becomes variable. Long term
this could also be solved by supporting triple buffering in direct
scanout mode. But whether or not that would be desirable given the
latency penalty remains to be seen.

Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-15 14:28:27 +09:00
Daniel van Vugt
41277449b4
clutter: Pass ClutterFrameHint(s) to the frame clock
Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-15 14:28:27 +09:00
Daniel van Vugt
3e296e84d0
backends: Flag that the frame attempted direct scanout
We need this hint whether direct scanout succeeds or fails because it's
the mechanism by which we will tell the clock to enforce double buffering,
thus making direct scanout possible on future frames. Triple buffering
will be disabled until such time that direct scanout is not being attempted.

Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-15 14:28:26 +09:00
Daniel van Vugt
0b3ca725dd
clutter/frame: Add ClutterFrameHint to ClutterFrame
This will allow the backend to provide performance hints to the frame
clock in future.

Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-15 14:28:26 +09:00
Daniel van Vugt
5734ac639c
clutter/frame-clock: Log N-buffers in CLUTTTER_DEBUG=frame-timings
Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-15 14:28:26 +09:00
Daniel van Vugt
21927dd744
clutter/frame-clock: Add triple buffering support
Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-15 14:28:26 +09:00
9 changed files with 431 additions and 82 deletions

View file

@ -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.875f
#define MINIMUM_REFRESH_RATE 30.f
@ -70,7 +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_DISPATCHED,
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
@ -91,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;
@ -112,6 +125,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;
@ -248,10 +264,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;
@ -278,6 +290,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,
@ -367,22 +385,54 @@ 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)
{
/* User thread cursor-only updates with no "swap": we do know
* the combined time from dispatch to flip at least.
*/
dispatch_to_swap_us = 0;
swap_to_flip_us = flip_time_us - dispatch_time_us;
}
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,
@ -393,7 +443,7 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock,
MAX (swap_to_rendering_done_us, swap_to_flip_us) +
frame_clock->deadline_evasion_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);
@ -402,7 +452,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);
}
@ -420,10 +471,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_DISPATCHED:
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;
}
}
@ -441,25 +504,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_DISPATCHED:
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 (int64_t) (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
@ -473,15 +548,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
@ -496,7 +571,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 ();
@ -516,10 +593,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;
/*
@ -540,7 +620,29 @@ 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:
g_warn_if_reached (); /* quad buffering would be a bug */
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.
@ -607,7 +709,7 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock,
}
if (frame_clock->last_presentation_flags & CLUTTER_FRAME_INFO_FLAG_VSYNC &&
next_presentation_time_us != last_presentation_time_us + refresh_interval_us)
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
@ -619,6 +721,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 =
(int64_t) (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;
@ -650,7 +770,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 ?
@ -663,9 +785,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;
@ -739,7 +858,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_DISPATCHED:
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;
}
@ -758,6 +887,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)
{
@ -775,10 +923,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_DISPATCHED:
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;
@ -807,13 +969,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)
{
@ -829,11 +995,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_DISPATCHED:
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;
}
@ -858,11 +1046,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
@ -878,6 +1066,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;
@ -888,7 +1078,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_DISPATCHED:
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;
}
@ -945,10 +1142,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_DISPATCHED;
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++;
@ -979,26 +1193,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:
g_warn_if_reached ();
case CLUTTER_FRAME_RESULT_PENDING_PRESENTED:
break;
case CLUTTER_FRAME_CLOCK_STATE_IDLE:
/* Presentation completed synchronously in the above listener */
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED:
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW:
break;
case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED:
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:
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:
/* The frame was aborted; nothing to paint/present */
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;
}
@ -1031,21 +1255,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, " =");
@ -1235,6 +1469,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;

View file

@ -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,8 +108,9 @@ 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);

View file

@ -36,6 +36,7 @@ struct _ClutterFrame
gboolean has_result;
ClutterFrameResult result;
ClutterFrameHint hints;
};
CLUTTER_EXPORT

View file

@ -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;
}

View file

@ -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)

View file

@ -1075,14 +1075,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 (context))
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);

View file

@ -798,6 +798,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,

View file

@ -1514,6 +1514,7 @@ try_post_latest_swap (CoglOnscreen *onscreen)
COGL_TRACE_SCOPED_ANCHOR (MetaRendererNativePostKmsUpdate);
if (onscreen_native->next_frame == NULL ||
onscreen_native->view == NULL ||
meta_kms_is_shutting_down (kms))
return;

View file

@ -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),
};