1
0
Fork 0

Compare commits

...

49 commits

Author SHA1 Message Date
Daniel van Vugt
eb6cac66e0
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-14 11:49:27 +09:00
Daniel van Vugt
079b584062
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-14 11:49:27 +09:00
Daniel van Vugt
058927c90d
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-14 11:49:26 +09:00
Daniel van Vugt
36ba811442
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-14 11:49:26 +09:00
Daniel van Vugt
6f1996c843
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-14 11:49:24 +09:00
Daniel van Vugt
f0c5c13176
clutter: Pass ClutterFrameHint(s) to the frame clock
Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-14 11:48:11 +09:00
Daniel van Vugt
abe490bb9b
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-14 11:48:11 +09:00
Daniel van Vugt
ce3d4617dc
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-14 11:48:10 +09:00
Daniel van Vugt
11c6ac1463
clutter/frame-clock: Log N-buffers in CLUTTTER_DEBUG=frame-timings
Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-14 11:48:10 +09:00
Daniel van Vugt
3e6342452d
clutter/frame-clock: Add triple buffering support
Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-14 11:48:09 +09:00
Daniel van Vugt
e1c4e914d0
clutter/frame-clock: Merge states DISPATCHING and PENDING_PRESENTED
Chronologically they already overlap in time as presentation may
complete in the middle of the dispatch function, otherwise they are
contiguous in time. And most switch statements treated the two states
the same already so they're easy to merge into a single `DISPATCHED`
state.

Having fewer states now will make life easier when we add more states
later.

Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-14 11:46:37 +09:00
Daniel van Vugt
f5a501b57e
clutter/frame-clock: Lower the threshold for disabling error diffusion
Error diffusion was introduced in 0555a5bbc1 for Nvidia where last
presentation time is always unknown (zero). Dispatch times would drift
apart always being a fraction of a frame late, and accumulated to cause
periodic frame skips. So error diffusion corrected that precisely and
avoided the skips.

That works great with double buffering but less great with triple
buffering. It's certainly still needed with triple buffering but
correcting for a lateness of many milliseconds isn't a good idea. That's
because a dispatch being that late is not due to main loop jitter but due
to Nvidia's swap buffers blocking when the queue is full. So scheduling
the next frame even earlier using last_dispatch_lateness_us would just
perpetuate the problem of swap buffers blocking for too long.

So now we lower the threshold of when error diffusion gets disabled. It's
still high enough to fix the original smoothness problem it was for, but
now low enough to detect Nvidia's occasionally blocking swaps and backs
off in that case.

Since the average duration of a blocking swap is half a frame interval
and we want to distinguish between that and sub-millisecond jitter, the
logical threshold is halfway again: refresh_interval_us/4.

Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-14 11:46:37 +09:00
Daniel van Vugt
ccc205e7d4
renderer/native: Discard pending swaps when rebuilding views
It's analogous to discard_pending_page_flips but represents swaps that
might become flips after the next frame notification callbacks, thanks
to triple buffering. Since the views are being rebuilt and their onscreens
are about to be destroyed, turning those swaps into more flips/posts would
just lead to unexpected behaviour (like trying to flip on a half-destroyed
inactive CRTC).

Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-14 11:46:37 +09:00
Daniel van Vugt
4995fa2a48
onscreen/native: Skip try_post_latest_swap if shutting down
Otherwise we could get:

  meta_kms_prepare_shutdown ->
  flush_callbacks ->
  ... ->
  try_post_latest_swap ->
  post and queue more callbacks

So later in shutdown those callbacks would trigger an assertion failure
in meta_kms_impl_device_atomic_finalize:

  g_hash_table_size (impl_device_atomic->page_flip_datas) == 0

Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-14 11:45:54 +09:00
Daniel van Vugt
d9a6cc334f
onscreen/native: Add function meta_onscreen_native_discard_pending_swaps
Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-14 11:45:47 +09:00
Daniel van Vugt
7951c84e5e
onscreen/native: Increase secondary GPU dumb_fbs from 2 to 3
So that they don't get overwritten prematurely during triple buffering
causing tearing.

https://launchpad.net/bugs/1999216
Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-14 11:45:42 +09:00
Daniel van Vugt
224d92ee97
onscreen/native: Defer posting if there's already a post in progress
And when the number of pending posts decreases we know it's safe to submit
a new one. Since KMS generally only supports one outstanding post right now,
"decreases" means equal to zero.

Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-14 11:45:34 +09:00
Daniel van Vugt
4a0bc60b3b
onscreen/native: Insert a 'posted' frame between 'next' and 'presented'
This will allow us to keep track of up to two buffers that have been
swapped but not yet scanning out, for triple buffering.

This commit replaces mutter!1968

Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-14 11:45:27 +09:00
Daniel van Vugt
dc1ff38cc8
onscreen/native: Split swap_buffers_with_damage into two functions
1. The EGL part: meta_onscreen_native_swap_buffers_with_damage
2. The KMS part: post_latest_swap

Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-14 11:45:19 +09:00
Daniel van Vugt
ea9e21875f
onscreen/native: Deduplicate calls to clutter_frame_set_result
All paths out of `meta_onscreen_native_swap_buffers_with_damage` from
here onward would set the same `CLUTTER_FRAME_RESULT_PENDING_PRESENTED`
(or terminate with `g_assert_not_reached`).

Even failed posts set this result because they will do a
`meta_onscreen_native_notify_frame_complete` in
`page_flip_feedback_discarded`.

Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-14 11:45:13 +09:00
Daniel van Vugt
ff71a88123
onscreen/native: Replace an assertion that double buffering is the maximum
Because it soon won't be the maximum. But we do want to verify that the
frame info queue is not empty, to avoid NULL dereferencing and catch logic
errors.

Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-14 11:43:58 +09:00
Daniel van Vugt
c235fd0eba
onscreen/native: Log swapbuffers and N-buffering when MUTTER_DEBUG=kms
Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-14 11:43:58 +09:00
Daniel van Vugt
ee31306ad8
backends/native: Add set/get_damage functions to MetaFrameNative
Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-14 11:43:58 +09:00
Daniel van Vugt
f138afc0bd
renderer/native: Steal the power save flip list before iterating over it
Because a single iteration might also grow the list again.

Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-14 11:43:58 +09:00
Daniel van Vugt
f05d08ac02
renderer/native: Avoid requeuing the same onscreen for a power save flip
This is a case that triple buffering will encounter. We don't want it
to queue the same onscreen multiple times because that would represent
multiple flips occurring simultaneously.

It's a linear search but the list length is typically only 1 or 2 so
no need for anything fancier yet.

Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-14 11:43:58 +09:00
Daniel van Vugt
c1bf67c500
kms: Keep a shutting_down flag
Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-14 11:43:57 +09:00
Daniel van Vugt
1d192d4814
cogl/onscreen: Indent declaration parameters to align with above
This fixes warnings from check-code-style.

Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-14 11:43:57 +09:00
Daniel van Vugt
f6c3846358
cogl/onscreen: Add function cogl_onscreen_get_pending_frame_count
Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-14 11:43:57 +09:00
Michel Dänzer
fd3373f9cf
onscreen/native: Set latest cogl sync_fd on KMS update
See previous commit log on the effects of this.

This means the deadline evasion needs to be added in both cases in
clutter_frame_clock_notify_presented.

v2:
* Use meta_kms_update_set_sync_fd. (Jonas Ådahl)

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3958>
Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-14 11:42:13 +09:00
Michel Dänzer
279baeaea4
kms/impl-device: Handle sync_fd in meta_kms_impl_device_handle_update
If the KMS thread is using the deadline timer, and a valid sync_file
descriptor is passed in:

1. The update is deferred, and the deadline timer is left armed, until
   the sync_fd signals (becomes readable).
2. Implicit synchronization is disabled for the KMS update.

This means cursor updates should no longer miss a display refresh
cycle due to mutter's compositing GPU work finishing too late.

v2:
* Use g_autoptr for GSource in meta_kms_impl_device_handle_update.
  (Sebastian Wick)
v3:
* Use meta_kms_update_get_sync_fd, don't track sync_fd in
  CrtcFrame::submitted_update. (Jonas Ådahl)
v4:
* Clean up CrtcFrame::submitted_update members in crtc_frame_free.
v5:
* Coding style cleanup in meta_kms_impl_device_handle_update.
  (Jonas Ådahl)

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3958>
Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-14 11:40:56 +09:00
Michel Dänzer
f5b3b673ed
kms/update: Add meta_kms_update_get/set_sync_fd
v2:
* Use g_steal_fd in meta_kms_update_merge_from. (Jonas Ådahl)

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3958>
Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-14 11:40:56 +09:00
Michel Dänzer
302f71df9d
kms/plane: Rename META_KMS_ASSIGN_PLANE_FLAG_DIRECT_SCANOUT
To META_KMS_ASSIGN_PLANE_FLAG_DISABLE_IMPLICIT_SYNC. This describes the
effect of the flag, instead of the circumstances it's currently used
for.

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3958>
Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-14 11:40:56 +09:00
Michel Dänzer
71c1bf4cd4
kms/crtc: Conditionally return 0 in meta_kms_crtc_get_deadline_evasion
If both crtc->shortterm_max_dispatch_duration_us and
crtc->deadline_evasion_us are 0, i.e. we're not using the deadline
timer.

v2:
* Fix coding style. (Jonas Ådahl)

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3958>
Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-14 11:40:55 +09:00
Michel Dänzer
0129d10266
kms/impl-device: Track dispatch duration in crtc_frame_deadline_dispatch
And take it into account in meta_kms_crtc_get_deadline_evasion.

This uses the same fundamental approach as clutter frame clock scheduling:

Measure the deadline timer dispatch duration, keep track of the longest
duration, and set the timer to fire such that the longest measured
dispatch duration would result in it completing shortly before start of
vblank.

Closes: https://gitlab.gnome.org/GNOME/mutter/-/issues/3612

v2:
* Move DEADLINE_EVASION_CONSTANT_US addition from
  meta_kms_crtc_determine_deadline to meta_kms_crtc_get_deadline_evasion.
* Calculate how long before start of vblank dispatch completed for
  debug output in crtc_frame_deadline_dispatch.
* Shorten over-long lines in crtc_frame_deadline_dispatch.

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3934>
Signed-off-by: Mingi Sung <sungmg@saltyming.net>
(cherry picked from commit 88e7f353)
2024-09-14 11:40:00 +09:00
Michel Dänzer
64df19d2ba
kms/impl-device: Use KMS_DEADLINE in crtc_page_flip_feedback_flipped
It's useful for this to match the debug topic in
crtc_frame_deadline_dispatch.

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3934>
Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-14 11:40:00 +09:00
Daniel van Vugt
60a340218c
kms/impl-device: Add debug logging for deadline dispatch lateness
And also "completion" time to measure when the commit returned.

This is structured so as to measure all timestamps first before logging
anything. That way our results shouldn't be (don't seem to be) affected
by the logging itself.

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3265>
Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-14 11:40:00 +09:00
Daniel van Vugt
3fe6990db9
kms/impl-device: Remember the expected deadline dispatch time
Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3265>
Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-14 11:40:00 +09:00
Daniel van Vugt
2d9bffe681
Add debug topic "kms-deadline"
Which will allow us to report on deadline timings without influencing
the CPU clock like the busy "kms" topic does.

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3265>
Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-14 11:39:59 +09:00
Jonas Ådahl
b85dce10b1
kms: Don't disarm deadline timer when compositing
If we finish compositing in time, the composited result will be
submitted prior to the deadline timer is triggered, and we'll be fine,
and if not, at least the cursor updates will be smooth, which makes it
appear smoother than not.

There is a risk that this can negatively impact composited updates when
moving the cursor, so make it possible to toggle a paint-debug flag for
now until this has been more tested.

This also mean we need to disarm the deadline timer after handling
update, as there might be a scheduled cursor update pending, but we
already handled it, so disarm the timer.

Here is an illustration of the difference.

In the following scenario, with disarming, the composited frame E, and
the cursor movement C gets presented. With this branch, only the cursor
movement C gets presented.
```
 * A: beginning of composited frame
 * B: begin notification reaches KMS thread
 * C: cursor moved
 * D: calculated deadline dispatch time (disabled with the branch)
 * E: KMS update posted
 * F: KMS update reaches KMS thread
 * G: actual deadline (and with branch and gets committed)
Compositor thread: --------A---------------E---------
                            \               \
                             \               \
KMS thread:        -----------B------C----D---F-G----
```

In the following scenario, by not disarming, the cursor update C will be
presented, and the would-be-delayed composited frame E would be delayed
anyway, i.e. fixing cursor stutter.
```
 * A: beginning of composited frame
 * B: begin notification reaches KMS thread
 * C: cursor moved
 * D: calculated deadline dispatch time (and with branch will be dispatched)
 * E: KMS update posted
 * F: actual deadline
 * G: KMS update reaches KMS thread (and with branch gets postponed)
Compositor thread: --------A---------------E---------
                            \               \
                             \               \
KMS thread:        -----------B------C----D-F-G------
```

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3184>
Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-14 11:39:59 +09:00
Jonas Ådahl
1b407cb533
renderer-view/native: Update deadline evasion each frame
The deadline evasion depends on debug flags, but they are not trackable,
so update the deadline evasion each time we schedule an update.

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3184>
Signed-off-by: Mingi Sung <sungmg@saltyming.net>
(cherry picked from commit 6ec1312384)
2024-09-14 11:39:59 +09:00
Jonas Ådahl
157ddfed97
clutter/frame-clock: Take deadline evasion into account
This is meant to be the amount of time before a CRTC deadline we're
usually dispatching at. It's not yet set by anything however.

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3184>
Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-14 11:39:55 +09:00
Daniel van Vugt
d18752b3b2
onscreen/native: Return GErrors from secondary GPU updates
And return early from `swap_buffers_with_damage` if the error would have
led to flipping a NULL buffer.

This is also the perfect time to remove the `egl_context_changed` parameter
and move `_cogl_winsys_egl_ensure_current` closer to the code that actually
needs it.

Related: https://bugs.launchpad.net/bugs/2069565
Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3817>
Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-14 11:26:06 +09:00
Daniel van Vugt
25416e3f5b
onscreen/native: Set frame result to IDLE on swap failure
So that swap failure messages are not also followed by:

meta_stage_native_redraw_view: runtime check failed: (!META_IS_CRTC_KMS (crtc))

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3817>
Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-14 11:26:06 +09:00
Daniel van Vugt
c1dc75704c
onscreen/native: Unify the failure paths of swap_buffers_with_damage
They're both the same and a third one will be added soon.

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3817>
Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-14 11:26:06 +09:00
Daniel van Vugt
40203436a6
onscreen/native: Squash adjacent switch statements
Because we can. And it's now clearer that `buffer` is only used in
`META_RENDERER_NATIVE_MODE_GBM`.

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3891>
Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-14 11:25:27 +09:00
Daniel van Vugt
1c938c80c4
onscreen/native: Move next_frame storage to later in the function
It won't be used until later when we flip, and in fact assigning
it early could have led to its own assertion failing on the next frame
in the unlikely event that we return with "Failed to ensure KMS FB ID...

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3891>
Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-14 11:24:51 +09:00
Daniel van Vugt
7c22a88c97
onscreen/native: Return the framebuffer by result, not parameters
`update_secondary_gpu_state_post_swap_buffers` decides what our front
buffer object will be. There is only one answer. So return it as the
function result instead of making the caller figure it out.

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3830>
Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-14 11:24:20 +09:00
Daniel van Vugt
247e70d9e2
onscreen/native: Remove frame parameter from flip_crtc
It's always equal to `onscreen_native->next_frame` and we can't eliminate
that copy so easily. Removing the parameter removes all ambiguity about
where the next frame will come from.

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3829>
Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-14 11:24:02 +09:00
Jonas Ådahl
ea64b55916
onscreen/native: Track next and presenting buffers via ClutterFrame
Let the ClutterFrame (or rather MetaFrameNative) own both the scanout
object and the framebuffer object, and let the frame itself live for as
long as it's needed. This allows to place fields that is related to a
single frame together, aiming to help reasoning about the lifetime of
the fields that were previously directly stored in MetaOnscreenNative.

Also take the opportunity to rename "current" to "presenting", to make
it clearer that frame's buffer is what is currently presenting to the
user.

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3799>
Signed-off-by: Mingi Sung <sungmg@saltyming.net>
2024-09-14 11:12:11 +09:00
33 changed files with 1335 additions and 331 deletions

View file

@ -42,7 +42,16 @@ enum
static guint signals[N_SIGNALS]; static guint signals[N_SIGNALS];
#define SYNC_DELAY_FALLBACK_FRACTION 0.875 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 #define MINIMUM_REFRESH_RATE 30.f
@ -70,8 +79,10 @@ typedef enum _ClutterFrameClockState
CLUTTER_FRAME_CLOCK_STATE_IDLE, CLUTTER_FRAME_CLOCK_STATE_IDLE,
CLUTTER_FRAME_CLOCK_STATE_SCHEDULED, CLUTTER_FRAME_CLOCK_STATE_SCHEDULED,
CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW, CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW,
CLUTTER_FRAME_CLOCK_STATE_DISPATCHING, CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE,
CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED, CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED,
CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW,
CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO,
} ClutterFrameClockState; } ClutterFrameClockState;
struct _ClutterFrameClock struct _ClutterFrameClock
@ -92,6 +103,7 @@ struct _ClutterFrameClock
ClutterFrameClockMode mode; ClutterFrameClockMode mode;
int64_t last_dispatch_time_us; int64_t last_dispatch_time_us;
int64_t prev_last_dispatch_time_us;
int64_t last_dispatch_lateness_us; int64_t last_dispatch_lateness_us;
int64_t last_presentation_time_us; int64_t last_presentation_time_us;
int64_t next_update_time_us; int64_t next_update_time_us;
@ -113,6 +125,9 @@ struct _ClutterFrameClock
int64_t vblank_duration_us; int64_t vblank_duration_us;
/* Last KMS buffer submission time. */ /* Last KMS buffer submission time. */
int64_t last_flip_time_us; 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 */ /* Last time we promoted short-term maximum to long-term one */
int64_t longterm_promotion_us; int64_t longterm_promotion_us;
@ -137,6 +152,8 @@ struct _ClutterFrameClock
int64_t last_dispatch_interval_us; int64_t last_dispatch_interval_us;
int64_t deadline_evasion_us;
char *output_name; char *output_name;
}; };
@ -247,10 +264,6 @@ static void
maybe_update_longterm_max_duration_us (ClutterFrameClock *frame_clock, maybe_update_longterm_max_duration_us (ClutterFrameClock *frame_clock,
ClutterFrameInfo *frame_info) 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) < if ((frame_info->presentation_time - frame_clock->longterm_promotion_us) <
G_USEC_PER_SEC) G_USEC_PER_SEC)
return; return;
@ -277,6 +290,12 @@ void
clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock, clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock,
ClutterFrameInfo *frame_info) 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, COGL_TRACE_BEGIN_SCOPED (ClutterFrameClockNotifyPresented,
"Clutter::FrameClock::presented()"); "Clutter::FrameClock::presented()");
COGL_TRACE_DESCRIBE (ClutterFrameClockNotifyPresented, COGL_TRACE_DESCRIBE (ClutterFrameClockNotifyPresented,
@ -366,22 +385,54 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock,
frame_clock->got_measurements_last_frame = FALSE; frame_clock->got_measurements_last_frame = FALSE;
if (frame_info->cpu_time_before_buffer_swap_us != 0 && if ((frame_info->cpu_time_before_buffer_swap_us != 0 &&
frame_info->has_valid_gpu_rendering_duration) frame_info->has_valid_gpu_rendering_duration) ||
frame_clock->ever_got_measurements)
{ {
int64_t dispatch_to_swap_us, swap_to_rendering_done_us, swap_to_flip_us; 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 = switch (frame_clock->state)
frame_info->cpu_time_before_buffer_swap_us - {
frame_clock->last_dispatch_time_us; 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 = swap_to_rendering_done_us =
frame_info->gpu_rendering_duration_ns / 1000; 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, 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, frame_clock->last_dispatch_lateness_us,
dispatch_to_swap_us, dispatch_to_swap_us,
swap_to_rendering_done_us, swap_to_rendering_done_us,
@ -389,9 +440,10 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock,
frame_clock->shortterm_max_update_duration_us = frame_clock->shortterm_max_update_duration_us =
CLAMP (frame_clock->last_dispatch_lateness_us + dispatch_to_swap_us + CLAMP (frame_clock->last_dispatch_lateness_us + dispatch_to_swap_us +
MAX (swap_to_rendering_done_us, swap_to_flip_us), MAX (swap_to_rendering_done_us, swap_to_flip_us) +
frame_clock->deadline_evasion_us,
frame_clock->shortterm_max_update_duration_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); maybe_update_longterm_max_duration_us (frame_clock, frame_info);
@ -400,7 +452,8 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock,
} }
else else
{ {
CLUTTER_NOTE (FRAME_TIMINGS, "update2dispatch %ld µs", CLUTTER_NOTE (FRAME_TIMINGS, "%s: update2dispatch %ld µs",
debug_state,
frame_clock->last_dispatch_lateness_us); frame_clock->last_dispatch_lateness_us);
} }
@ -418,11 +471,22 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock,
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW:
g_warn_if_reached (); g_warn_if_reached ();
break; break;
case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE:
case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED:
frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE; frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE;
maybe_reschedule_update (frame_clock); maybe_reschedule_update (frame_clock);
break; 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;
} }
} }
@ -440,26 +504,37 @@ clutter_frame_clock_notify_ready (ClutterFrameClock *frame_clock)
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW:
g_warn_if_reached (); g_warn_if_reached ();
break; break;
case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE:
case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED:
frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE; frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE;
maybe_reschedule_update (frame_clock); maybe_reschedule_update (frame_clock);
break; 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 static gboolean
clutter_frame_clock_compute_max_render_time_us (ClutterFrameClock *frame_clock) clutter_frame_clock_compute_max_render_time_us (ClutterFrameClock *frame_clock,
int64_t *max_render_time_us)
{ {
int64_t refresh_interval_us; int64_t refresh_interval_us;
int64_t max_render_time_us;
refresh_interval_us = frame_clock->refresh_interval_us; refresh_interval_us = frame_clock->refresh_interval_us;
if (!frame_clock->ever_got_measurements || if (!frame_clock->ever_got_measurements ||
G_UNLIKELY (clutter_paint_debug_flags & G_UNLIKELY (clutter_paint_debug_flags &
CLUTTER_DEBUG_DISABLE_DYNAMIC_MAX_RENDER_TIME)) 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 /* 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 * 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. * - The duration of vertical blank.
* - A constant to account for variations in the above estimates. * - 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, MAX (frame_clock->longterm_max_update_duration_us,
frame_clock->shortterm_max_update_duration_us) + frame_clock->shortterm_max_update_duration_us) +
frame_clock->vblank_duration_us + frame_clock->vblank_duration_us +
clutter_max_render_time_constant_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 static void
@ -496,7 +571,9 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock,
int64_t min_render_time_allowed_us; int64_t min_render_time_allowed_us;
int64_t max_render_time_allowed_us; int64_t max_render_time_allowed_us;
int64_t next_presentation_time_us; int64_t next_presentation_time_us;
int64_t next_smooth_presentation_time_us = 0;
int64_t next_update_time_us; int64_t next_update_time_us;
gboolean max_render_time_is_known;
now_us = g_get_monotonic_time (); 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; 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; 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; 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. * 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 && 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 /* 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 * 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 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) while (next_presentation_time_us - min_render_time_allowed_us < now_us)
next_presentation_time_us += refresh_interval_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; 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 = *out_next_update_time_us =
frame_clock->last_dispatch_time_us ? frame_clock->last_dispatch_time_us ?
@ -663,9 +785,6 @@ calculate_next_variable_update_time_us (ClutterFrameClock *frame_clock,
return; 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; last_presentation_time_us = frame_clock->last_presentation_time_us;
next_presentation_time_us = last_presentation_time_us + refresh_interval_us; next_presentation_time_us = last_presentation_time_us + refresh_interval_us;
@ -739,8 +858,17 @@ clutter_frame_clock_inhibit (ClutterFrameClock *frame_clock)
frame_clock->pending_reschedule_now = TRUE; frame_clock->pending_reschedule_now = TRUE;
frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE; frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE;
break; break;
case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED:
case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED: 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; break;
} }
@ -759,6 +887,25 @@ clutter_frame_clock_uninhibit (ClutterFrameClock *frame_clock)
maybe_reschedule_update (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 void
clutter_frame_clock_schedule_update_now (ClutterFrameClock *frame_clock) clutter_frame_clock_schedule_update_now (ClutterFrameClock *frame_clock)
{ {
@ -776,11 +923,24 @@ clutter_frame_clock_schedule_update_now (ClutterFrameClock *frame_clock)
case CLUTTER_FRAME_CLOCK_STATE_INIT: case CLUTTER_FRAME_CLOCK_STATE_INIT:
case CLUTTER_FRAME_CLOCK_STATE_IDLE: case CLUTTER_FRAME_CLOCK_STATE_IDLE:
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED:
frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW;
break; break;
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW:
case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW:
return; return;
case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED:
case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED: 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 = TRUE;
frame_clock->pending_reschedule_now = TRUE; frame_clock->pending_reschedule_now = TRUE;
return; return;
@ -809,13 +969,17 @@ clutter_frame_clock_schedule_update_now (ClutterFrameClock *frame_clock)
frame_clock->next_update_time_us = next_update_time_us; frame_clock->next_update_time_us = next_update_time_us;
g_source_set_ready_time (frame_clock->source, 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 void
clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock) clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock)
{ {
int64_t next_update_time_us = -1; 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) if (frame_clock->inhibit_count > 0)
{ {
@ -831,12 +995,33 @@ clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock)
frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED; frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED;
return; return;
case CLUTTER_FRAME_CLOCK_STATE_IDLE: case CLUTTER_FRAME_CLOCK_STATE_IDLE:
frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED;
break; break;
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED:
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: 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; return;
case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE:
case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED: 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; frame_clock->pending_reschedule = TRUE;
return; return;
} }
@ -861,11 +1046,11 @@ clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock)
break; break;
} }
got_update_time:
g_warn_if_fail (next_update_time_us != -1); g_warn_if_fail (next_update_time_us != -1);
frame_clock->next_update_time_us = next_update_time_us; frame_clock->next_update_time_us = next_update_time_us;
g_source_set_ready_time (frame_clock->source, 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 void
@ -881,6 +1066,8 @@ clutter_frame_clock_set_mode (ClutterFrameClock *frame_clock,
{ {
case CLUTTER_FRAME_CLOCK_STATE_INIT: case CLUTTER_FRAME_CLOCK_STATE_INIT:
case CLUTTER_FRAME_CLOCK_STATE_IDLE: case CLUTTER_FRAME_CLOCK_STATE_IDLE:
case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE:
case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO:
break; break;
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED:
frame_clock->pending_reschedule = TRUE; frame_clock->pending_reschedule = TRUE;
@ -891,8 +1078,14 @@ clutter_frame_clock_set_mode (ClutterFrameClock *frame_clock,
frame_clock->pending_reschedule_now = TRUE; frame_clock->pending_reschedule_now = TRUE;
frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE; frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE;
break; break;
case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED:
case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED: 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; break;
} }
@ -928,7 +1121,7 @@ clutter_frame_clock_dispatch (ClutterFrameClock *frame_clock,
frame_clock->refresh_interval_us; frame_clock->refresh_interval_us;
lateness_us = time_us - ideal_dispatch_time_us; lateness_us = time_us - ideal_dispatch_time_us;
if (lateness_us < 0 || lateness_us >= frame_clock->refresh_interval_us) if (lateness_us < 0 || lateness_us >= frame_clock->refresh_interval_us / 4)
frame_clock->last_dispatch_lateness_us = 0; frame_clock->last_dispatch_lateness_us = 0;
else else
frame_clock->last_dispatch_lateness_us = lateness_us; frame_clock->last_dispatch_lateness_us = lateness_us;
@ -949,10 +1142,27 @@ clutter_frame_clock_dispatch (ClutterFrameClock *frame_clock,
} }
#endif #endif
frame_clock->prev_last_dispatch_time_us = frame_clock->last_dispatch_time_us;
frame_clock->last_dispatch_time_us = time_us; frame_clock->last_dispatch_time_us = time_us;
g_source_set_ready_time (frame_clock->source, -1); g_source_set_ready_time (frame_clock->source, -1);
frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHING; 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++; frame_count = frame_clock->frame_count++;
@ -983,26 +1193,36 @@ clutter_frame_clock_dispatch (ClutterFrameClock *frame_clock,
result = iface->frame (frame_clock, frame, frame_clock->listener.user_data); result = iface->frame (frame_clock, frame, frame_clock->listener.user_data);
COGL_TRACE_END (ClutterFrameClockFrame); COGL_TRACE_END (ClutterFrameClockFrame);
switch (frame_clock->state)
{
case CLUTTER_FRAME_CLOCK_STATE_INIT:
case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED:
g_warn_if_reached ();
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) switch (result)
{ {
case CLUTTER_FRAME_RESULT_PENDING_PRESENTED: case CLUTTER_FRAME_RESULT_PENDING_PRESENTED:
frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED;
break; break;
case CLUTTER_FRAME_RESULT_IDLE: case CLUTTER_FRAME_RESULT_IDLE:
/* The frame was aborted; nothing to paint/present */
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 ();
break;
case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE:
frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE; frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE;
maybe_reschedule_update (frame_clock); maybe_reschedule_update (frame_clock);
break; 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; break;
} }
@ -1035,21 +1255,31 @@ frame_clock_source_dispatch (GSource *source,
} }
void void
clutter_frame_clock_record_flip_time (ClutterFrameClock *frame_clock, clutter_frame_clock_record_flip (ClutterFrameClock *frame_clock,
int64_t flip_time_us) 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_time_us = flip_time_us;
frame_clock->last_flip_hints = hints;
} }
GString * GString *
clutter_frame_clock_get_max_render_time_debug_info (ClutterFrameClock *frame_clock) clutter_frame_clock_get_max_render_time_debug_info (ClutterFrameClock *frame_clock)
{ {
int64_t max_render_time_us;
int64_t max_update_duration_us; int64_t max_update_duration_us;
GString *string; GString *string;
string = g_string_new (NULL); string = g_string_new ("Max render time: ");
g_string_append_printf (string, "Max render time: %ld µs", if (!clutter_frame_clock_compute_max_render_time_us (frame_clock,
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) if (frame_clock->got_measurements_last_frame)
g_string_append_printf (string, " ="); g_string_append_printf (string, " =");
@ -1216,8 +1446,6 @@ clutter_frame_clock_dispose (GObject *object)
{ {
ClutterFrameClock *frame_clock = CLUTTER_FRAME_CLOCK (object); ClutterFrameClock *frame_clock = CLUTTER_FRAME_CLOCK (object);
g_warn_if_fail (frame_clock->state != CLUTTER_FRAME_CLOCK_STATE_DISPATCHING);
if (frame_clock->source) if (frame_clock->source)
{ {
g_signal_emit (frame_clock, signals[DESTROY], 0); g_signal_emit (frame_clock, signals[DESTROY], 0);
@ -1241,6 +1469,15 @@ static void
clutter_frame_clock_class_init (ClutterFrameClockClass *klass) clutter_frame_clock_class_init (ClutterFrameClockClass *klass)
{ {
GObjectClass *object_class = G_OBJECT_CLASS (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; object_class->dispose = clutter_frame_clock_dispose;
@ -1253,3 +1490,10 @@ clutter_frame_clock_class_init (ClutterFrameClockClass *klass)
G_TYPE_NONE, G_TYPE_NONE,
0); 0);
} }
void
clutter_frame_clock_set_deadline_evasion (ClutterFrameClock *frame_clock,
int64_t deadline_evasion_us)
{
frame_clock->deadline_evasion_us = deadline_evasion_us;
}

View file

@ -33,6 +33,12 @@ typedef enum _ClutterFrameResult
CLUTTER_FRAME_RESULT_IDLE, CLUTTER_FRAME_RESULT_IDLE,
} ClutterFrameResult; } 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 ()) #define CLUTTER_TYPE_FRAME_CLOCK (clutter_frame_clock_get_type ())
CLUTTER_EXPORT CLUTTER_EXPORT
G_DECLARE_FINAL_TYPE (ClutterFrameClock, clutter_frame_clock, G_DECLARE_FINAL_TYPE (ClutterFrameClock, clutter_frame_clock,
@ -102,7 +108,12 @@ void clutter_frame_clock_remove_timeline (ClutterFrameClock *frame_clock,
CLUTTER_EXPORT CLUTTER_EXPORT
float clutter_frame_clock_get_refresh_rate (ClutterFrameClock *frame_clock); float clutter_frame_clock_get_refresh_rate (ClutterFrameClock *frame_clock);
void clutter_frame_clock_record_flip_time (ClutterFrameClock *frame_clock, void clutter_frame_clock_record_flip (ClutterFrameClock *frame_clock,
int64_t flip_time_us); int64_t flip_time_us,
ClutterFrameHint hints);
GString * clutter_frame_clock_get_max_render_time_debug_info (ClutterFrameClock *frame_clock); GString * clutter_frame_clock_get_max_render_time_debug_info (ClutterFrameClock *frame_clock);
CLUTTER_EXPORT
void clutter_frame_clock_set_deadline_evasion (ClutterFrameClock *frame_clock,
int64_t deadline_evasion_us);

View file

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

View file

@ -115,3 +115,16 @@ clutter_frame_set_result (ClutterFrame *frame,
frame->result = result; frame->result = result;
frame->has_result = TRUE; 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 CLUTTER_EXPORT
gboolean clutter_frame_has_result (ClutterFrame *frame); 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) G_DEFINE_AUTOPTR_CLEANUP_FUNC (ClutterFrame, clutter_frame_unref)

View file

@ -740,6 +740,10 @@ clutter_stage_view_schedule_update (ClutterStageView *view)
{ {
ClutterStageViewPrivate *priv = ClutterStageViewPrivate *priv =
clutter_stage_view_get_instance_private (view); clutter_stage_view_get_instance_private (view);
ClutterStageViewClass *view_class = CLUTTER_STAGE_VIEW_GET_CLASS (view);
if (view_class->schedule_update)
view_class->schedule_update (view);
clutter_frame_clock_schedule_update (priv->frame_clock); clutter_frame_clock_schedule_update (priv->frame_clock);
} }
@ -898,14 +902,21 @@ handle_frame_clock_frame (ClutterFrameClock *frame_clock,
_clutter_stage_window_redraw_view (stage_window, view, frame); _clutter_stage_window_redraw_view (stage_window, view, frame);
clutter_frame_clock_record_flip_time (frame_clock, clutter_frame_clock_record_flip (frame_clock,
g_get_monotonic_time ()); g_get_monotonic_time (),
clutter_frame_get_hints (frame));
clutter_stage_emit_after_paint (stage, view, frame); clutter_stage_emit_after_paint (stage, view, frame);
if (_clutter_context_get_show_fps ()) if (_clutter_context_get_show_fps ())
end_frame_timing_measurement (view); 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); _clutter_stage_window_finish_frame (stage_window, view, frame);

View file

@ -54,6 +54,8 @@ struct _ClutterStageViewClass
ClutterFrame * (* new_frame) (ClutterStageView *view); ClutterFrame * (* new_frame) (ClutterStageView *view);
ClutterPaintFlag (* get_default_paint_flags) (ClutterStageView *view); ClutterPaintFlag (* get_default_paint_flags) (ClutterStageView *view);
void (* schedule_update) (ClutterStageView *view);
}; };
CLUTTER_EXPORT CLUTTER_EXPORT

View file

@ -79,3 +79,6 @@ cogl_onscreen_peek_tail_frame_info (CoglOnscreen *onscreen);
COGL_EXPORT CoglFrameInfo * 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_get_pending_frame_count (CoglOnscreen *onscreen);

View file

@ -514,6 +514,14 @@ cogl_onscreen_pop_head_frame_info (CoglOnscreen *onscreen)
return g_queue_pop_head (&priv->pending_frame_infos); return g_queue_pop_head (&priv->pending_frame_infos);
} }
unsigned int
cogl_onscreen_get_pending_frame_count (CoglOnscreen *onscreen)
{
CoglOnscreenPrivate *priv = cogl_onscreen_get_instance_private (onscreen);
return g_queue_get_length (&priv->pending_frame_infos);
}
CoglFrameClosure * CoglFrameClosure *
cogl_onscreen_add_frame_callback (CoglOnscreen *onscreen, cogl_onscreen_add_frame_callback (CoglOnscreen *onscreen,
CoglFrameCallback callback, CoglFrameCallback callback,

View file

@ -774,6 +774,8 @@ meta_stage_impl_redraw_view (ClutterStageWindow *stage_window,
{ {
g_autoptr (GError) error = NULL; g_autoptr (GError) error = NULL;
clutter_frame_set_hint (frame, CLUTTER_FRAME_HINT_DIRECT_SCANOUT_ATTEMPTED);
if (meta_stage_impl_scanout_view (stage_impl, if (meta_stage_impl_scanout_view (stage_impl,
stage_view, stage_view,
scanout, scanout,

View file

@ -348,6 +348,15 @@ meta_crtc_kms_is_hw_cursor_supported (MetaCrtcNative *crtc_native)
return meta_kms_device_has_cursor_plane_for (kms_device, kms_crtc); return meta_kms_device_has_cursor_plane_for (kms_device, kms_crtc);
} }
static int64_t
meta_crtc_kms_get_deadline_evasion (MetaCrtcNative *crtc_native)
{
MetaCrtcKms *crtc_kms = META_CRTC_KMS (crtc_native);
MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (crtc_kms);
return meta_kms_crtc_get_deadline_evasion (kms_crtc);
}
MetaKmsPlane * MetaKmsPlane *
meta_crtc_kms_get_assigned_cursor_plane (MetaCrtcKms *crtc_kms) meta_crtc_kms_get_assigned_cursor_plane (MetaCrtcKms *crtc_kms)
{ {
@ -479,6 +488,7 @@ meta_crtc_kms_class_init (MetaCrtcKmsClass *klass)
crtc_native_class->is_transform_handled = meta_crtc_kms_is_transform_handled; crtc_native_class->is_transform_handled = meta_crtc_kms_is_transform_handled;
crtc_native_class->is_hw_cursor_supported = meta_crtc_kms_is_hw_cursor_supported; crtc_native_class->is_hw_cursor_supported = meta_crtc_kms_is_hw_cursor_supported;
crtc_native_class->get_deadline_evasion = meta_crtc_kms_get_deadline_evasion;
signals[GAMMA_LUT_CHANGED] = signals[GAMMA_LUT_CHANGED] =
g_signal_new ("gamma-lut-changed", g_signal_new ("gamma-lut-changed",

View file

@ -39,6 +39,14 @@ meta_crtc_native_is_hw_cursor_supported (MetaCrtcNative *crtc_native)
return klass->is_hw_cursor_supported (crtc_native); return klass->is_hw_cursor_supported (crtc_native);
} }
int64_t
meta_crtc_native_get_deadline_evasion (MetaCrtcNative *crtc_native)
{
MetaCrtcNativeClass *klass = META_CRTC_NATIVE_GET_CLASS (crtc_native);
return klass->get_deadline_evasion (crtc_native);
}
static void static void
meta_crtc_native_init (MetaCrtcNative *crtc_native) meta_crtc_native_init (MetaCrtcNative *crtc_native)
{ {

View file

@ -31,9 +31,12 @@ struct _MetaCrtcNativeClass
gboolean (* is_transform_handled) (MetaCrtcNative *crtc_native, gboolean (* is_transform_handled) (MetaCrtcNative *crtc_native,
MetaMonitorTransform monitor_transform); MetaMonitorTransform monitor_transform);
gboolean (* is_hw_cursor_supported) (MetaCrtcNative *crtc_native); gboolean (* is_hw_cursor_supported) (MetaCrtcNative *crtc_native);
int64_t (* get_deadline_evasion) (MetaCrtcNative *crtc_native);
}; };
gboolean meta_crtc_native_is_transform_handled (MetaCrtcNative *crtc_native, gboolean meta_crtc_native_is_transform_handled (MetaCrtcNative *crtc_native,
MetaMonitorTransform transform); MetaMonitorTransform transform);
gboolean meta_crtc_native_is_hw_cursor_supported (MetaCrtcNative *crtc_native); gboolean meta_crtc_native_is_hw_cursor_supported (MetaCrtcNative *crtc_native);
int64_t meta_crtc_native_get_deadline_evasion (MetaCrtcNative *crtc_native);

View file

@ -70,6 +70,12 @@ meta_crtc_virtual_is_hw_cursor_supported (MetaCrtcNative *crtc_native)
return TRUE; return TRUE;
} }
static int64_t
meta_crtc_virtual_get_deadline_evasion (MetaCrtcNative *crtc_native)
{
return 0;
}
static void static void
meta_crtc_virtual_init (MetaCrtcVirtual *crtc_virtual) meta_crtc_virtual_init (MetaCrtcVirtual *crtc_virtual)
{ {
@ -89,4 +95,6 @@ meta_crtc_virtual_class_init (MetaCrtcVirtualClass *klass)
meta_crtc_virtual_is_transform_handled; meta_crtc_virtual_is_transform_handled;
crtc_native_class->is_hw_cursor_supported = crtc_native_class->is_hw_cursor_supported =
meta_crtc_virtual_is_hw_cursor_supported; meta_crtc_virtual_is_hw_cursor_supported;
crtc_native_class->get_deadline_evasion =
meta_crtc_virtual_get_deadline_evasion;
} }

View file

@ -27,7 +27,15 @@ struct _MetaFrameNative
{ {
ClutterFrame base; ClutterFrame base;
MetaDrmBuffer *buffer;
CoglScanout *scanout;
MetaKmsUpdate *kms_update; MetaKmsUpdate *kms_update;
struct {
int n_rectangles;
int *rectangles; /* 4 x n_rectangles */
} damage;
}; };
static void static void
@ -35,6 +43,10 @@ meta_frame_native_release (ClutterFrame *frame)
{ {
MetaFrameNative *frame_native = meta_frame_native_from_frame (frame); MetaFrameNative *frame_native = meta_frame_native_from_frame (frame);
g_clear_pointer (&frame_native->damage.rectangles, g_free);
g_clear_object (&frame_native->buffer);
g_clear_object (&frame_native->scanout);
g_return_if_fail (!frame_native->kms_update); g_return_if_fail (!frame_native->kms_update);
} }
@ -76,3 +88,54 @@ meta_frame_native_has_kms_update (MetaFrameNative *frame_native)
{ {
return !!frame_native->kms_update; return !!frame_native->kms_update;
} }
void
meta_frame_native_set_buffer (MetaFrameNative *frame_native,
MetaDrmBuffer *buffer)
{
g_set_object (&frame_native->buffer, buffer);
}
MetaDrmBuffer *
meta_frame_native_get_buffer (MetaFrameNative *frame_native)
{
return frame_native->buffer;
}
void
meta_frame_native_set_scanout (MetaFrameNative *frame_native,
CoglScanout *scanout)
{
g_set_object (&frame_native->scanout, scanout);
}
CoglScanout *
meta_frame_native_get_scanout (MetaFrameNative *frame_native)
{
return frame_native->scanout;
}
void
meta_frame_native_set_damage (MetaFrameNative *frame_native,
const int *rectangles,
int n_rectangles)
{
size_t rectangles_size;
rectangles_size = n_rectangles * 4 * sizeof (int);
frame_native->damage.rectangles =
g_realloc (frame_native->damage.rectangles, rectangles_size);
memcpy (frame_native->damage.rectangles, rectangles, rectangles_size);
frame_native->damage.n_rectangles = n_rectangles;
}
int
meta_frame_native_get_damage (MetaFrameNative *frame_native,
int **rectangles)
{
if (rectangles)
*rectangles = frame_native->damage.rectangles;
return frame_native->damage.n_rectangles;
}

View file

@ -17,6 +17,7 @@
#pragma once #pragma once
#include "backends/native/meta-backend-native-types.h"
#include "backends/native/meta-kms-types.h" #include "backends/native/meta-kms-types.h"
#include "clutter/clutter.h" #include "clutter/clutter.h"
#include "core/util-private.h" #include "core/util-private.h"
@ -36,3 +37,22 @@ MetaKmsUpdate * meta_frame_native_steal_kms_update (MetaFrameNative *frame_nativ
META_EXPORT_TEST META_EXPORT_TEST
gboolean meta_frame_native_has_kms_update (MetaFrameNative *frame_native); gboolean meta_frame_native_has_kms_update (MetaFrameNative *frame_native);
void meta_frame_native_set_buffer (MetaFrameNative *frame_native,
MetaDrmBuffer *buffer);
MetaDrmBuffer * meta_frame_native_get_buffer (MetaFrameNative *frame_native);
void meta_frame_native_set_scanout (MetaFrameNative *frame_native,
CoglScanout *scanout);
CoglScanout * meta_frame_native_get_scanout (MetaFrameNative *frame_native);
void
meta_frame_native_set_damage (MetaFrameNative *frame_native,
const int *rectangles,
int n_rectangles);
int
meta_frame_native_get_damage (MetaFrameNative *frame_native,
int **rectangles);

View file

@ -28,8 +28,7 @@
#include "backends/native/meta-kms-update-private.h" #include "backends/native/meta-kms-update-private.h"
#include "backends/native/meta-kms-utils.h" #include "backends/native/meta-kms-utils.h"
#define DEADLINE_EVASION_US 800 #define DEADLINE_EVASION_CONSTANT_US 200
#define DEADLINE_EVASION_WITH_KMS_TOPIC_US 1000
#define MINIMUM_REFRESH_RATE 30.f #define MINIMUM_REFRESH_RATE 30.f
@ -50,6 +49,10 @@ struct _MetaKmsCrtc
MetaKmsCrtcState current_state; MetaKmsCrtcState current_state;
MetaKmsCrtcPropTable prop_table; MetaKmsCrtcPropTable prop_table;
int64_t shortterm_max_dispatch_duration_us;
int64_t deadline_evasion_us;
int64_t deadline_evasion_update_time_us;
}; };
G_DEFINE_TYPE (MetaKmsCrtc, meta_kms_crtc, G_TYPE_OBJECT) G_DEFINE_TYPE (MetaKmsCrtc, meta_kms_crtc, G_TYPE_OBJECT)
@ -543,6 +546,33 @@ get_crtc_type_bitmask (MetaKmsCrtc *crtc)
} }
} }
static void
maybe_update_deadline_evasion (MetaKmsCrtc *crtc,
int64_t next_presentation_time_us)
{
/* Do not update long-term max if there has been no measurement */
if (!crtc->shortterm_max_dispatch_duration_us)
return;
if (next_presentation_time_us - crtc->deadline_evasion_update_time_us <
G_USEC_PER_SEC)
return;
if (crtc->deadline_evasion_us > crtc->shortterm_max_dispatch_duration_us)
{
/* Exponential drop-off toward the clamped short-term max */
crtc->deadline_evasion_us -=
(crtc->deadline_evasion_us - crtc->shortterm_max_dispatch_duration_us) / 2;
}
else
{
crtc->deadline_evasion_us = crtc->shortterm_max_dispatch_duration_us;
}
crtc->shortterm_max_dispatch_duration_us = 0;
crtc->deadline_evasion_update_time_us = next_presentation_time_us;
}
gboolean gboolean
meta_kms_crtc_determine_deadline (MetaKmsCrtc *crtc, meta_kms_crtc_determine_deadline (MetaKmsCrtc *crtc,
int64_t *out_next_deadline_us, int64_t *out_next_deadline_us,
@ -610,10 +640,8 @@ meta_kms_crtc_determine_deadline (MetaKmsCrtc *crtc,
* *
*/ */
if (meta_is_topic_enabled (META_DEBUG_KMS)) deadline_evasion_us = meta_kms_crtc_get_deadline_evasion (crtc);
deadline_evasion_us = DEADLINE_EVASION_WITH_KMS_TOPIC_US; maybe_update_deadline_evasion (crtc, next_presentation_us);
else
deadline_evasion_us = DEADLINE_EVASION_US;
vblank_duration_us = meta_calculate_drm_mode_vblank_duration_us (drm_mode); vblank_duration_us = meta_calculate_drm_mode_vblank_duration_us (drm_mode);
next_deadline_us = next_presentation_us - (vblank_duration_us + next_deadline_us = next_presentation_us - (vblank_duration_us +
@ -625,3 +653,33 @@ meta_kms_crtc_determine_deadline (MetaKmsCrtc *crtc,
return TRUE; return TRUE;
} }
void
meta_kms_crtc_update_shortterm_max_dispatch_duration (MetaKmsCrtc *crtc,
int64_t duration_us)
{
int64_t refresh_interval_us;
if (duration_us <= crtc->shortterm_max_dispatch_duration_us)
return;
refresh_interval_us =
(int64_t) (0.5 + G_USEC_PER_SEC /
meta_calculate_drm_mode_refresh_rate (&crtc->current_state.drm_mode));
crtc->shortterm_max_dispatch_duration_us = MIN (duration_us, refresh_interval_us);
}
int64_t
meta_kms_crtc_get_deadline_evasion (MetaKmsCrtc *crtc)
{
int64_t deadline_evasion_us;
deadline_evasion_us =
MAX (crtc->shortterm_max_dispatch_duration_us, crtc->deadline_evasion_us);
if (!deadline_evasion_us)
return 0;
return deadline_evasion_us + DEADLINE_EVASION_CONSTANT_US;
}

View file

@ -65,3 +65,8 @@ int meta_kms_crtc_get_idx (MetaKmsCrtc *crtc);
META_EXPORT_TEST META_EXPORT_TEST
gboolean meta_kms_crtc_is_active (MetaKmsCrtc *crtc); gboolean meta_kms_crtc_is_active (MetaKmsCrtc *crtc);
void meta_kms_crtc_update_shortterm_max_dispatch_duration (MetaKmsCrtc *crtc,
int64_t duration_us);
int64_t meta_kms_crtc_get_deadline_evasion (MetaKmsCrtc *crtc);

View file

@ -643,7 +643,7 @@ process_plane_assignment (MetaKmsImplDevice *impl_device,
return FALSE; return FALSE;
} }
if (plane_assignment->flags & META_KMS_ASSIGN_PLANE_FLAG_DIRECT_SCANOUT) if (plane_assignment->flags & META_KMS_ASSIGN_PLANE_FLAG_DISABLE_IMPLICIT_SYNC)
{ {
int signaled_sync_file; int signaled_sync_file;

View file

@ -39,6 +39,7 @@
#include "backends/native/meta-kms-plane-private.h" #include "backends/native/meta-kms-plane-private.h"
#include "backends/native/meta-kms-plane.h" #include "backends/native/meta-kms-plane.h"
#include "backends/native/meta-kms-private.h" #include "backends/native/meta-kms-private.h"
#include "backends/native/meta-kms-utils.h"
#include "backends/native/meta-thread-private.h" #include "backends/native/meta-thread-private.h"
#include "meta-default-modes.h" #include "meta-default-modes.h"
@ -71,9 +72,17 @@ typedef struct _CrtcDeadline
GSource *source; GSource *source;
gboolean armed; gboolean armed;
gboolean is_deadline_page_flip; gboolean is_deadline_page_flip;
int64_t expected_deadline_time_us;
int64_t expected_presentation_time_us; int64_t expected_presentation_time_us;
gboolean has_expected_presentation_time; gboolean has_expected_presentation_time;
} deadline; } deadline;
struct {
MetaKmsUpdate *kms_update;
MetaKmsUpdateFlag flags;
MetaKmsCrtc *latch_crtc;
GSource *source;
} submitted_update;
} CrtcFrame; } CrtcFrame;
typedef enum _MetaDeadlineTimerState typedef enum _MetaDeadlineTimerState
@ -1165,6 +1174,7 @@ arm_crtc_frame_deadline_timer (CrtcFrame *crtc_frame,
timerfd_settime (crtc_frame->deadline.timer_fd, timerfd_settime (crtc_frame->deadline.timer_fd,
TFD_TIMER_ABSTIME, &its, NULL); TFD_TIMER_ABSTIME, &its, NULL);
crtc_frame->deadline.expected_deadline_time_us = next_deadline_us;
crtc_frame->deadline.expected_presentation_time_us = next_presentation_us; crtc_frame->deadline.expected_presentation_time_us = next_presentation_us;
crtc_frame->deadline.has_expected_presentation_time = next_presentation_us != 0; crtc_frame->deadline.has_expected_presentation_time = next_presentation_us != 0;
crtc_frame->deadline.armed = TRUE; crtc_frame->deadline.armed = TRUE;
@ -1197,7 +1207,7 @@ crtc_page_flip_feedback_flipped (MetaKmsCrtc *crtc,
CrtcFrame *crtc_frame = user_data; CrtcFrame *crtc_frame = user_data;
if (crtc_frame->deadline.is_deadline_page_flip && if (crtc_frame->deadline.is_deadline_page_flip &&
meta_is_topic_enabled (META_DEBUG_KMS)) meta_is_topic_enabled (META_DEBUG_KMS_DEADLINE))
{ {
struct timeval page_flip_timeval; struct timeval page_flip_timeval;
int64_t presentation_time_us; int64_t presentation_time_us;
@ -1210,7 +1220,7 @@ crtc_page_flip_feedback_flipped (MetaKmsCrtc *crtc,
if (crtc_frame->deadline.has_expected_presentation_time) if (crtc_frame->deadline.has_expected_presentation_time)
{ {
meta_topic (META_DEBUG_KMS, meta_topic (META_DEBUG_KMS_DEADLINE,
"Deadline page flip presentation time: %" G_GINT64_FORMAT " us, " "Deadline page flip presentation time: %" G_GINT64_FORMAT " us, "
"expected %" G_GINT64_FORMAT " us " "expected %" G_GINT64_FORMAT " us "
"(diff: %" G_GINT64_FORMAT ")", "(diff: %" G_GINT64_FORMAT ")",
@ -1221,7 +1231,7 @@ crtc_page_flip_feedback_flipped (MetaKmsCrtc *crtc,
} }
else else
{ {
meta_topic (META_DEBUG_KMS, meta_topic (META_DEBUG_KMS_DEADLINE,
"Deadline page flip presentation time: %" G_GINT64_FORMAT " us", "Deadline page flip presentation time: %" G_GINT64_FORMAT " us",
presentation_time_us); presentation_time_us);
} }
@ -1390,11 +1400,16 @@ crtc_frame_deadline_dispatch (MetaThreadImpl *thread_impl,
GError **error) GError **error)
{ {
CrtcFrame *crtc_frame = user_data; CrtcFrame *crtc_frame = user_data;
MetaKmsDevice *device = meta_kms_crtc_get_device (crtc_frame->crtc); MetaKmsCrtc *crtc = crtc_frame->crtc;
MetaKmsDevice *device = meta_kms_crtc_get_device (crtc);
MetaKmsImplDevice *impl_device = meta_kms_device_get_impl_device (device); MetaKmsImplDevice *impl_device = meta_kms_device_get_impl_device (device);
g_autoptr (MetaKmsFeedback) feedback = NULL; g_autoptr (MetaKmsFeedback) feedback = NULL;
uint64_t timer_value; uint64_t timer_value;
ssize_t ret; ssize_t ret;
int64_t dispatch_time_us = 0, update_done_time_us, interval_us;
if (meta_is_topic_enabled (META_DEBUG_KMS_DEADLINE))
dispatch_time_us = g_get_monotonic_time ();
ret = read (crtc_frame->deadline.timer_fd, ret = read (crtc_frame->deadline.timer_fd,
&timer_value, &timer_value,
@ -1416,6 +1431,36 @@ crtc_frame_deadline_dispatch (MetaThreadImpl *thread_impl,
crtc_frame->crtc, crtc_frame->crtc,
g_steal_pointer (&crtc_frame->pending_update), g_steal_pointer (&crtc_frame->pending_update),
META_KMS_UPDATE_FLAG_NONE); META_KMS_UPDATE_FLAG_NONE);
update_done_time_us = g_get_monotonic_time ();
/* Calculate how long after the planned start of deadline dispatch it finished */
interval_us = update_done_time_us - crtc_frame->deadline.expected_deadline_time_us;
if (meta_is_topic_enabled (META_DEBUG_KMS_DEADLINE))
{
int64_t deadline_evasion_us, lateness_us, duration_us, vblank_delta_us;
deadline_evasion_us = meta_kms_crtc_get_deadline_evasion (crtc);
lateness_us = dispatch_time_us -
crtc_frame->deadline.expected_deadline_time_us;
duration_us = update_done_time_us - dispatch_time_us;
vblank_delta_us = deadline_evasion_us - lateness_us - duration_us;
meta_topic (META_DEBUG_KMS_DEADLINE,
"Deadline evasion %3"G_GINT64_FORMAT "µs, "
"dispatch started %3"G_GINT64_FORMAT "µs %s and "
"completed %3"G_GINT64_FORMAT "µs after that, "
"%3"G_GINT64_FORMAT "µs %s start of vblank.",
deadline_evasion_us,
ABS (lateness_us),
lateness_us >= 0 ? "late" : "early",
duration_us,
ABS (vblank_delta_us),
vblank_delta_us >= 0 ? "before" : "after");
}
meta_kms_crtc_update_shortterm_max_dispatch_duration (crtc, interval_us);
if (meta_kms_feedback_did_pass (feedback)) if (meta_kms_feedback_did_pass (feedback))
crtc_frame->deadline.is_deadline_page_flip = TRUE; crtc_frame->deadline.is_deadline_page_flip = TRUE;
disarm_crtc_frame_deadline_timer (crtc_frame); disarm_crtc_frame_deadline_timer (crtc_frame);
@ -1429,6 +1474,8 @@ crtc_frame_free (CrtcFrame *crtc_frame)
g_clear_fd (&crtc_frame->deadline.timer_fd, NULL); g_clear_fd (&crtc_frame->deadline.timer_fd, NULL);
g_clear_pointer (&crtc_frame->deadline.source, g_source_destroy); g_clear_pointer (&crtc_frame->deadline.source, g_source_destroy);
g_clear_pointer (&crtc_frame->pending_update, meta_kms_update_free); g_clear_pointer (&crtc_frame->pending_update, meta_kms_update_free);
g_clear_pointer (&crtc_frame->submitted_update.kms_update, meta_kms_update_free);
g_clear_pointer (&crtc_frame->submitted_update.source, g_source_destroy);
g_free (crtc_frame); g_free (crtc_frame);
} }
@ -1530,6 +1577,79 @@ queue_update (MetaKmsImplDevice *impl_device,
} }
} }
static gpointer
meta_kms_impl_device_update_ready (MetaThreadImpl *impl,
gpointer user_data,
GError **error)
{
CrtcFrame *crtc_frame = user_data;
MetaKmsDevice *device = meta_kms_crtc_get_device (crtc_frame->crtc);
MetaKmsImplDevice *impl_device = meta_kms_device_get_impl_device (device);
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
MetaKmsUpdate *update;
MetaKmsCrtc *latch_crtc;
MetaKmsFeedback *feedback;
meta_assert_in_kms_impl (meta_kms_impl_get_kms (priv->impl));
g_clear_pointer (&crtc_frame->submitted_update.source, g_source_destroy);
update = g_steal_pointer (&crtc_frame->submitted_update.kms_update);
meta_kms_update_realize (update, impl_device);
latch_crtc = g_steal_pointer (&crtc_frame->submitted_update.latch_crtc);
if (crtc_frame->pending_page_flip &&
!meta_kms_update_get_mode_sets (update))
{
g_assert (latch_crtc);
meta_topic (META_DEBUG_KMS,
"Queuing update on CRTC %u (%s): pending page flip",
meta_kms_crtc_get_id (latch_crtc),
priv->path);
queue_update (impl_device, crtc_frame, update);
return GINT_TO_POINTER (TRUE);
}
if (crtc_frame->pending_update)
{
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);
}
meta_kms_device_handle_flush (priv->device, latch_crtc);
feedback = do_process (impl_device, latch_crtc, update, crtc_frame->submitted_update.flags);
if (meta_kms_feedback_did_pass (feedback) &&
crtc_frame->deadline.armed)
disarm_crtc_frame_deadline_timer (crtc_frame);
meta_kms_feedback_unref (feedback);
return GINT_TO_POINTER (TRUE);
}
static gboolean
is_fd_readable (int fd)
{
GPollFD poll_fd;
poll_fd.fd = fd;
poll_fd.events = G_IO_IN;
poll_fd.revents = 0;
if (!g_poll (&poll_fd, 1, 0))
return FALSE;
return (poll_fd.revents & (G_IO_IN | G_IO_NVAL)) != 0;
}
void void
meta_kms_impl_device_handle_update (MetaKmsImplDevice *impl_device, meta_kms_impl_device_handle_update (MetaKmsImplDevice *impl_device,
MetaKmsUpdate *update, MetaKmsUpdate *update,
@ -1537,10 +1657,15 @@ meta_kms_impl_device_handle_update (MetaKmsImplDevice *impl_device,
{ {
MetaKmsImplDevicePrivate *priv = MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device); meta_kms_impl_device_get_instance_private (impl_device);
MetaKmsImpl *kms_impl = meta_kms_impl_device_get_impl (impl_device);
MetaThreadImpl *thread_impl = META_THREAD_IMPL (kms_impl);
g_autoptr (GError) error = NULL; g_autoptr (GError) error = NULL;
MetaKmsCrtc *latch_crtc; MetaKmsCrtc *latch_crtc;
CrtcFrame *crtc_frame; CrtcFrame *crtc_frame;
MetaKmsFeedback *feedback; MetaKmsFeedback *feedback;
g_autoptr (GSource) source = NULL;
g_autofree char *name = NULL;
int sync_fd = -1;
meta_assert_in_kms_impl (meta_kms_impl_get_kms (priv->impl)); meta_assert_in_kms_impl (meta_kms_impl_get_kms (priv->impl));
@ -1561,38 +1686,58 @@ meta_kms_impl_device_handle_update (MetaKmsImplDevice *impl_device,
if (!ensure_device_file (impl_device, &error)) if (!ensure_device_file (impl_device, &error))
goto err; goto err;
meta_kms_update_realize (update, impl_device);
crtc_frame = ensure_crtc_frame (impl_device, latch_crtc); crtc_frame = ensure_crtc_frame (impl_device, latch_crtc);
crtc_frame->await_flush = FALSE; if (crtc_frame->submitted_update.kms_update)
if (crtc_frame->pending_page_flip &&
!meta_kms_update_get_mode_sets (update))
{ {
g_assert (latch_crtc); g_set_error (&error, G_IO_ERROR, G_IO_ERROR_PENDING,
"Previously-submitted update wasn't ready yet");
goto err;
}
meta_topic (META_DEBUG_KMS, crtc_frame->await_flush = FALSE;
"Queuing update on CRTC %u (%s): pending page flip", crtc_frame->submitted_update.kms_update = update;
meta_kms_crtc_get_id (latch_crtc), crtc_frame->submitted_update.flags = flags;
priv->path); crtc_frame->submitted_update.latch_crtc = latch_crtc;
queue_update (impl_device, crtc_frame, update); if (is_using_deadline_timer (impl_device))
sync_fd = meta_kms_update_get_sync_fd (update);
if (sync_fd >= 0)
{
GList *l;
for (l = meta_kms_update_get_plane_assignments (update); l; l = l->next)
{
MetaKmsPlaneAssignment *assignment = l->data;
assignment->flags |= META_KMS_ASSIGN_PLANE_FLAG_DISABLE_IMPLICIT_SYNC;
}
}
if (sync_fd < 0 ||
is_fd_readable (sync_fd))
{
meta_kms_impl_device_update_ready (thread_impl,
crtc_frame,
NULL);
return; return;
} }
if (crtc_frame->pending_update) source = meta_thread_impl_register_fd (thread_impl,
{ sync_fd,
meta_kms_update_merge_from (crtc_frame->pending_update, update); meta_kms_impl_device_update_ready,
meta_kms_update_free (update); crtc_frame);
update = g_steal_pointer (&crtc_frame->pending_update);
disarm_crtc_frame_deadline_timer (crtc_frame);
}
meta_kms_device_handle_flush (priv->device, latch_crtc); name = g_strdup_printf ("[mutter] KMS update sync_fd (crtc: %u, %s)",
meta_kms_crtc_get_id (latch_crtc),
priv->path);
g_source_set_name (source, name);
g_source_set_priority (source, G_PRIORITY_HIGH + 1);
g_source_set_can_recurse (source, FALSE);
g_source_set_ready_time (source, -1);
feedback = do_process (impl_device, latch_crtc, update, flags); crtc_frame->submitted_update.source = source;
meta_kms_feedback_unref (feedback);
return; return;
err: err:

View file

@ -20,6 +20,8 @@
#include "backends/native/meta-kms-update.h" #include "backends/native/meta-kms-update.h"
#include "backends/native/meta-kms-update-private.h" #include "backends/native/meta-kms-update-private.h"
#include <glib/gstdio.h>
#include "backends/meta-display-config-shared.h" #include "backends/meta-display-config-shared.h"
#include "backends/native/meta-kms-connector.h" #include "backends/native/meta-kms-connector.h"
#include "backends/native/meta-kms-crtc.h" #include "backends/native/meta-kms-crtc.h"
@ -51,6 +53,8 @@ struct _MetaKmsUpdate
gboolean needs_modeset; gboolean needs_modeset;
MetaKmsImplDevice *impl_device; MetaKmsImplDevice *impl_device;
int sync_fd;
}; };
void void
@ -1136,6 +1140,8 @@ meta_kms_update_merge_from (MetaKmsUpdate *update,
merge_custom_page_flip_from (update, other_update); merge_custom_page_flip_from (update, other_update);
merge_page_flip_listeners_from (update, other_update); merge_page_flip_listeners_from (update, other_update);
merge_result_listeners_from (update, other_update); merge_result_listeners_from (update, other_update);
meta_kms_update_set_sync_fd (update, g_steal_fd (&other_update->sync_fd));
} }
gboolean gboolean
@ -1152,6 +1158,7 @@ meta_kms_update_new (MetaKmsDevice *device)
update = g_new0 (MetaKmsUpdate, 1); update = g_new0 (MetaKmsUpdate, 1);
update->device = device; update->device = device;
update->is_latchable = TRUE; update->is_latchable = TRUE;
update->sync_fd = -1;
return update; return update;
} }
@ -1175,6 +1182,7 @@ meta_kms_update_free (MetaKmsUpdate *update)
g_list_free_full (update->crtc_color_updates, g_list_free_full (update->crtc_color_updates,
(GDestroyNotify) meta_kms_crtc_color_updates_free); (GDestroyNotify) meta_kms_crtc_color_updates_free);
g_clear_pointer (&update->custom_page_flip, meta_kms_custom_page_flip_free); g_clear_pointer (&update->custom_page_flip, meta_kms_custom_page_flip_free);
g_clear_fd (&update->sync_fd, NULL);
g_free (update); g_free (update);
} }
@ -1200,6 +1208,23 @@ meta_kms_update_get_latch_crtc (MetaKmsUpdate *update)
return update->latch_crtc; return update->latch_crtc;
} }
int
meta_kms_update_get_sync_fd (MetaKmsUpdate *update)
{
return update->sync_fd;
}
void
meta_kms_update_set_sync_fd (MetaKmsUpdate *update,
int sync_fd)
{
if (update->sync_fd == sync_fd)
return;
g_clear_fd (&update->sync_fd, NULL);
update->sync_fd = sync_fd;
}
gboolean gboolean
meta_kms_update_is_empty (MetaKmsUpdate *update) meta_kms_update_is_empty (MetaKmsUpdate *update)
{ {

View file

@ -39,7 +39,7 @@ typedef enum _MetaKmsAssignPlaneFlag
META_KMS_ASSIGN_PLANE_FLAG_NONE = 0, META_KMS_ASSIGN_PLANE_FLAG_NONE = 0,
META_KMS_ASSIGN_PLANE_FLAG_FB_UNCHANGED = 1 << 0, META_KMS_ASSIGN_PLANE_FLAG_FB_UNCHANGED = 1 << 0,
META_KMS_ASSIGN_PLANE_FLAG_ALLOW_FAIL = 1 << 1, META_KMS_ASSIGN_PLANE_FLAG_ALLOW_FAIL = 1 << 1,
META_KMS_ASSIGN_PLANE_FLAG_DIRECT_SCANOUT = 1 << 2, META_KMS_ASSIGN_PLANE_FLAG_DISABLE_IMPLICIT_SYNC = 1 << 2,
} MetaKmsAssignPlaneFlag; } MetaKmsAssignPlaneFlag;
struct _MetaKmsPageFlipListenerVtable struct _MetaKmsPageFlipListenerVtable
@ -157,6 +157,13 @@ void meta_kms_update_set_crtc_gamma (MetaKmsUpdate *update,
MetaKmsCrtc *crtc, MetaKmsCrtc *crtc,
const MetaGammaLut *gamma); const MetaGammaLut *gamma);
int
meta_kms_update_get_sync_fd (MetaKmsUpdate *update);
void
meta_kms_update_set_sync_fd (MetaKmsUpdate *update,
int sync_fd);
void meta_kms_plane_assignment_set_fb_damage (MetaKmsPlaneAssignment *plane_assignment, void meta_kms_plane_assignment_set_fb_damage (MetaKmsPlaneAssignment *plane_assignment,
const int *rectangles, const int *rectangles,
int n_rectangles); int n_rectangles);

View file

@ -63,6 +63,8 @@ struct _MetaKms
int kernel_thread_inhibit_count; int kernel_thread_inhibit_count;
MetaKmsCursorManager *cursor_manager; MetaKmsCursorManager *cursor_manager;
gboolean shutting_down;
}; };
G_DEFINE_TYPE (MetaKms, meta_kms, META_TYPE_THREAD) G_DEFINE_TYPE (MetaKms, meta_kms, META_TYPE_THREAD)
@ -339,6 +341,12 @@ meta_kms_create_device (MetaKms *kms,
return device; return device;
} }
gboolean
meta_kms_is_shutting_down (MetaKms *kms)
{
return kms->shutting_down;
}
static gpointer static gpointer
prepare_shutdown_in_impl (MetaThreadImpl *thread_impl, prepare_shutdown_in_impl (MetaThreadImpl *thread_impl,
gpointer user_data, gpointer user_data,
@ -354,6 +362,7 @@ static void
on_prepare_shutdown (MetaBackend *backend, on_prepare_shutdown (MetaBackend *backend,
MetaKms *kms) MetaKms *kms)
{ {
kms->shutting_down = TRUE;
meta_kms_run_impl_task_sync (kms, prepare_shutdown_in_impl, NULL, NULL); meta_kms_run_impl_task_sync (kms, prepare_shutdown_in_impl, NULL, NULL);
meta_thread_flush_callbacks (META_THREAD (kms)); meta_thread_flush_callbacks (META_THREAD (kms));

View file

@ -60,6 +60,8 @@ MetaKmsDevice * meta_kms_create_device (MetaKms *kms,
MetaKmsDeviceFlag flags, MetaKmsDeviceFlag flags,
GError **error); GError **error);
gboolean meta_kms_is_shutting_down (MetaKms *kms);
MetaKms * meta_kms_new (MetaBackend *backend, MetaKms * meta_kms_new (MetaBackend *backend,
MetaKmsFlags flags, MetaKmsFlags flags,
GError **error); GError **error);

View file

@ -76,7 +76,7 @@ typedef struct _MetaOnscreenNativeSecondaryGpuState
struct { struct {
MetaDrmBufferDumb *current_dumb_fb; MetaDrmBufferDumb *current_dumb_fb;
MetaDrmBufferDumb *dumb_fbs[2]; MetaDrmBufferDumb *dumb_fbs[3];
} cpu; } cpu;
gboolean noted_primary_gpu_copy_ok; gboolean noted_primary_gpu_copy_ok;
@ -102,12 +102,13 @@ struct _MetaOnscreenNative
MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state; MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state;
ClutterFrame *presented_frame;
ClutterFrame *posted_frame;
ClutterFrame *stalled_frame;
ClutterFrame *next_frame;
struct { struct {
struct gbm_surface *surface; struct gbm_surface *surface;
MetaDrmBuffer *current_fb;
MetaDrmBuffer *next_fb;
CoglScanout *current_scanout;
CoglScanout *next_scanout;
} gbm; } gbm;
#ifdef HAVE_EGL_DEVICE #ifdef HAVE_EGL_DEVICE
@ -118,6 +119,9 @@ struct _MetaOnscreenNative
} egl; } egl;
#endif #endif
gboolean needs_flush;
unsigned int swaps_pending;
gboolean frame_sync_requested; gboolean frame_sync_requested;
gboolean frame_sync_enabled; gboolean frame_sync_enabled;
@ -139,44 +143,37 @@ G_DEFINE_TYPE (MetaOnscreenNative, meta_onscreen_native,
static GQuark blit_source_quark = 0; 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 static gboolean
init_secondary_gpu_state (MetaRendererNative *renderer_native, init_secondary_gpu_state (MetaRendererNative *renderer_native,
CoglOnscreen *onscreen, CoglOnscreen *onscreen,
GError **error); 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 static void
meta_onscreen_native_swap_drm_fb (CoglOnscreen *onscreen) meta_onscreen_native_swap_drm_fb (CoglOnscreen *onscreen)
{ {
MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
if (!onscreen_native->gbm.next_fb) if (!onscreen_native->posted_frame)
return; return;
free_current_bo (onscreen); g_clear_pointer (&onscreen_native->presented_frame, clutter_frame_unref);
onscreen_native->presented_frame =
g_set_object (&onscreen_native->gbm.current_fb, onscreen_native->gbm.next_fb); g_steal_pointer (&onscreen_native->posted_frame);
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);
} }
static void 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); MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
g_clear_object (&onscreen_native->gbm.next_fb); g_clear_pointer (&onscreen_native->posted_frame, clutter_frame_unref);
g_clear_object (&onscreen_native->gbm.next_scanout);
} }
static void static void
@ -214,7 +211,7 @@ meta_onscreen_native_notify_frame_complete (CoglOnscreen *onscreen)
info = cogl_onscreen_pop_head_frame_info (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_frame_sync (onscreen, info);
_cogl_onscreen_notify_complete (onscreen, info); _cogl_onscreen_notify_complete (onscreen, info);
@ -256,6 +253,7 @@ notify_view_crtc_presented (MetaRendererView *view,
meta_onscreen_native_notify_frame_complete (onscreen); meta_onscreen_native_notify_frame_complete (onscreen);
meta_onscreen_native_swap_drm_fb (onscreen); meta_onscreen_native_swap_drm_fb (onscreen);
try_post_latest_swap (onscreen);
} }
static void static void
@ -305,15 +303,13 @@ page_flip_feedback_ready (MetaKmsCrtc *kms_crtc,
CoglFramebuffer *framebuffer = CoglFramebuffer *framebuffer =
clutter_stage_view_get_onscreen (CLUTTER_STAGE_VIEW (view)); clutter_stage_view_get_onscreen (CLUTTER_STAGE_VIEW (view));
CoglOnscreen *onscreen = COGL_ONSCREEN (framebuffer); CoglOnscreen *onscreen = COGL_ONSCREEN (framebuffer);
MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
CoglFrameInfo *frame_info; CoglFrameInfo *frame_info;
frame_info = cogl_onscreen_peek_head_frame_info (onscreen); frame_info = cogl_onscreen_peek_head_frame_info (onscreen);
frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC; frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC;
g_warn_if_fail (!onscreen_native->gbm.next_fb);
meta_onscreen_native_notify_frame_complete (onscreen); meta_onscreen_native_notify_frame_complete (onscreen);
try_post_latest_swap (onscreen);
} }
static void static void
@ -379,7 +375,8 @@ page_flip_feedback_discarded (MetaKmsCrtc *kms_crtc,
} }
meta_onscreen_native_notify_frame_complete (onscreen); 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 = { static const MetaKmsPageFlipListenerVtable page_flip_listener_vtable = {
@ -440,18 +437,36 @@ custom_egl_stream_page_flip (gpointer custom_page_flip_data,
} }
#endif /* HAVE_EGL_DEVICE */ #endif /* HAVE_EGL_DEVICE */
void static void
meta_onscreen_native_dummy_power_save_page_flip (CoglOnscreen *onscreen) drop_stalled_swap (CoglOnscreen *onscreen)
{ {
CoglFrameInfo *frame_info; CoglFrameInfo *frame_info;
MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
meta_onscreen_native_swap_drm_fb (onscreen); if (onscreen_native->swaps_pending <= 1)
return;
onscreen_native->swaps_pending--;
g_clear_pointer (&onscreen_native->stalled_frame, clutter_frame_unref);
frame_info = cogl_onscreen_peek_tail_frame_info (onscreen); frame_info = cogl_onscreen_peek_tail_frame_info (onscreen);
frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC; frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC;
meta_onscreen_native_notify_frame_complete (onscreen); 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 static void
apply_transform (MetaCrtcKms *crtc_kms, apply_transform (MetaCrtcKms *crtc_kms,
MetaKmsPlaneAssignment *kms_plane_assignment, MetaKmsPlaneAssignment *kms_plane_assignment,
@ -528,12 +543,15 @@ meta_onscreen_native_flip_crtc (CoglOnscreen *onscreen,
{ {
MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
MetaRendererNative *renderer_native = onscreen_native->renderer_native; MetaRendererNative *renderer_native = onscreen_native->renderer_native;
g_autoptr (ClutterFrame) frame = NULL;
MetaFrameNative *frame_native;
MetaGpuKms *render_gpu = onscreen_native->render_gpu; MetaGpuKms *render_gpu = onscreen_native->render_gpu;
MetaCrtcKms *crtc_kms = META_CRTC_KMS (crtc); MetaCrtcKms *crtc_kms = META_CRTC_KMS (crtc);
MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (crtc_kms); MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (crtc_kms);
MetaRendererNativeGpuData *renderer_gpu_data; MetaRendererNativeGpuData *renderer_gpu_data;
MetaGpuKms *gpu_kms; MetaGpuKms *gpu_kms;
MetaDrmBuffer *buffer; MetaDrmBuffer *buffer;
CoglScanout *scanout;
MetaKmsPlaneAssignment *plane_assignment; MetaKmsPlaneAssignment *plane_assignment;
graphene_rect_t src_rect; graphene_rect_t src_rect;
MtkRectangle dst_rect; MtkRectangle dst_rect;
@ -541,6 +559,9 @@ meta_onscreen_native_flip_crtc (CoglOnscreen *onscreen,
COGL_TRACE_BEGIN_SCOPED (MetaOnscreenNativeFlipCrtcs, COGL_TRACE_BEGIN_SCOPED (MetaOnscreenNativeFlipCrtcs,
"Meta::OnscreenNative::flip_crtc()"); "Meta::OnscreenNative::flip_crtc()");
frame = g_steal_pointer (&onscreen_native->next_frame);
g_return_if_fail (frame);
gpu_kms = META_GPU_KMS (meta_crtc_get_gpu (crtc)); gpu_kms = META_GPU_KMS (meta_crtc_get_gpu (crtc));
g_assert (meta_gpu_kms_is_crtc_active (gpu_kms, crtc)); g_assert (meta_gpu_kms_is_crtc_active (gpu_kms, crtc));
@ -550,14 +571,14 @@ meta_onscreen_native_flip_crtc (CoglOnscreen *onscreen,
switch (renderer_gpu_data->mode) switch (renderer_gpu_data->mode)
{ {
case META_RENDERER_NATIVE_MODE_GBM: case META_RENDERER_NATIVE_MODE_GBM:
buffer = onscreen_native->gbm.next_fb; frame_native = meta_frame_native_from_frame (frame);
buffer = meta_frame_native_get_buffer (frame_native);
scanout = meta_frame_native_get_scanout (frame_native);
if (onscreen_native->gbm.next_scanout) if (scanout)
{ {
cogl_scanout_get_src_rect (onscreen_native->gbm.next_scanout, cogl_scanout_get_src_rect (scanout, &src_rect);
&src_rect); cogl_scanout_get_dst_rect (scanout, &dst_rect);
cogl_scanout_get_dst_rect (onscreen_native->gbm.next_scanout,
&dst_rect);
} }
else else
{ {
@ -600,6 +621,10 @@ meta_onscreen_native_flip_crtc (CoglOnscreen *onscreen,
#endif #endif
} }
g_warn_if_fail (!onscreen_native->posted_frame);
g_clear_pointer (&onscreen_native->posted_frame, clutter_frame_unref);
onscreen_native->posted_frame = g_steal_pointer (&frame);
meta_kms_update_add_page_flip_listener (kms_update, meta_kms_update_add_page_flip_listener (kms_update,
kms_crtc, kms_crtc,
&page_flip_listener_vtable, &page_flip_listener_vtable,
@ -851,19 +876,21 @@ static MetaDrmBuffer *
copy_shared_framebuffer_gpu (CoglOnscreen *onscreen, copy_shared_framebuffer_gpu (CoglOnscreen *onscreen,
MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state, MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state,
MetaRendererNativeGpuData *renderer_gpu_data, MetaRendererNativeGpuData *renderer_gpu_data,
gboolean *egl_context_changed, MetaDrmBuffer *primary_gpu_fb,
MetaDrmBuffer *primary_gpu_fb) GError **error)
{ {
MetaRendererNative *renderer_native = renderer_gpu_data->renderer_native; MetaRendererNative *renderer_native = renderer_gpu_data->renderer_native;
MetaEgl *egl = meta_renderer_native_get_egl (renderer_native); MetaEgl *egl = meta_renderer_native_get_egl (renderer_native);
MetaGles3 *gles3 = meta_renderer_native_get_gles3 (renderer_native); MetaGles3 *gles3 = meta_renderer_native_get_gles3 (renderer_native);
CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen);
CoglContext *cogl_context = cogl_framebuffer_get_context (framebuffer);
CoglDisplay *cogl_display = cogl_context_get_display (cogl_context);
MetaRenderDevice *render_device; MetaRenderDevice *render_device;
EGLDisplay egl_display; EGLDisplay egl_display;
GError *error = NULL;
gboolean use_modifiers; gboolean use_modifiers;
MetaDeviceFile *device_file; MetaDeviceFile *device_file;
MetaDrmBufferFlags flags; MetaDrmBufferFlags flags;
MetaDrmBufferGbm *buffer_gbm; MetaDrmBufferGbm *buffer_gbm = NULL;
struct gbm_bo *bo; struct gbm_bo *bo;
COGL_TRACE_BEGIN_SCOPED (CopySharedFramebufferSecondaryGpu, COGL_TRACE_BEGIN_SCOPED (CopySharedFramebufferSecondaryGpu,
@ -880,16 +907,12 @@ copy_shared_framebuffer_gpu (CoglOnscreen *onscreen,
secondary_gpu_state->egl_surface, secondary_gpu_state->egl_surface,
secondary_gpu_state->egl_surface, secondary_gpu_state->egl_surface,
renderer_gpu_data->secondary.egl_context, renderer_gpu_data->secondary.egl_context,
&error)) error))
{ {
g_warning ("Failed to make current: %s", error->message); g_prefix_error (error, "Failed to make current: ");
g_error_free (error); goto done;
return NULL;
} }
*egl_context_changed = TRUE;
buffer_gbm = META_DRM_BUFFER_GBM (primary_gpu_fb); buffer_gbm = META_DRM_BUFFER_GBM (primary_gpu_fb);
bo = meta_drm_buffer_gbm_get_bo (buffer_gbm); bo = meta_drm_buffer_gbm_get_bo (buffer_gbm);
if (!meta_renderer_native_gles3_blit_shared_bo (egl, if (!meta_renderer_native_gles3_blit_shared_bo (egl,
@ -898,21 +921,19 @@ copy_shared_framebuffer_gpu (CoglOnscreen *onscreen,
renderer_gpu_data->secondary.egl_context, renderer_gpu_data->secondary.egl_context,
secondary_gpu_state->egl_surface, secondary_gpu_state->egl_surface,
bo, bo,
&error)) error))
{ {
g_warning ("Failed to blit shared framebuffer: %s", error->message); g_prefix_error (error, "Failed to blit shared framebuffer: ");
g_error_free (error); goto done;
return NULL;
} }
if (!meta_egl_swap_buffers (egl, if (!meta_egl_swap_buffers (egl,
egl_display, egl_display,
secondary_gpu_state->egl_surface, secondary_gpu_state->egl_surface,
&error)) error))
{ {
g_warning ("Failed to swap buffers: %s", error->message); g_prefix_error (error, "Failed to swap buffers: ");
g_error_free (error); goto done;
return NULL;
} }
use_modifiers = meta_renderer_native_use_modifiers (renderer_native); use_modifiers = meta_renderer_native_use_modifiers (renderer_native);
@ -926,13 +947,11 @@ copy_shared_framebuffer_gpu (CoglOnscreen *onscreen,
meta_drm_buffer_gbm_new_lock_front (device_file, meta_drm_buffer_gbm_new_lock_front (device_file,
secondary_gpu_state->gbm.surface, secondary_gpu_state->gbm.surface,
flags, flags,
&error); error);
if (!buffer_gbm) if (!buffer_gbm)
{ {
g_warning ("meta_drm_buffer_gbm_new_lock_front failed: %s", g_prefix_error (error, "meta_drm_buffer_gbm_new_lock_front failed: ");
error->message); goto done;
g_error_free (error);
return NULL;
} }
g_object_set_qdata_full (G_OBJECT (buffer_gbm), g_object_set_qdata_full (G_OBJECT (buffer_gbm),
@ -940,18 +959,26 @@ copy_shared_framebuffer_gpu (CoglOnscreen *onscreen,
g_object_ref (primary_gpu_fb), g_object_ref (primary_gpu_fb),
g_object_unref); g_object_unref);
return META_DRM_BUFFER (buffer_gbm); done:
_cogl_winsys_egl_ensure_current (cogl_display);
return buffer_gbm ? META_DRM_BUFFER (buffer_gbm) : NULL;
} }
static MetaDrmBufferDumb * static MetaDrmBufferDumb *
secondary_gpu_get_next_dumb_buffer (MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state) secondary_gpu_get_next_dumb_buffer (MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state)
{ {
MetaDrmBufferDumb *current_dumb_fb; 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; current_dumb_fb = secondary_gpu_state->cpu.current_dumb_fb;
if (current_dumb_fb == secondary_gpu_state->cpu.dumb_fbs[0]) for (i = 0; i < n_dumb_fbs; i++)
return secondary_gpu_state->cpu.dumb_fbs[1]; {
else 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]; return secondary_gpu_state->cpu.dumb_fbs[0];
} }
@ -1192,24 +1219,24 @@ update_secondary_gpu_state_pre_swap_buffers (CoglOnscreen *onscreen,
return copy; return copy;
} }
static void static MetaDrmBuffer *
update_secondary_gpu_state_post_swap_buffers (CoglOnscreen *onscreen, acquire_front_buffer (CoglOnscreen *onscreen,
gboolean *egl_context_changed,
MetaDrmBuffer *primary_gpu_fb, MetaDrmBuffer *primary_gpu_fb,
MetaDrmBuffer **secondary_gpu_fb) MetaDrmBuffer *secondary_gpu_fb,
GError **error)
{ {
MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
MetaRendererNative *renderer_native = onscreen_native->renderer_native; MetaRendererNative *renderer_native = onscreen_native->renderer_native;
MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state; MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state;
MetaRendererNativeGpuData *renderer_gpu_data;
MetaDrmBuffer *imported_fb;
COGL_TRACE_BEGIN_SCOPED (MetaRendererNativeGpuStatePostSwapBuffers, COGL_TRACE_BEGIN_SCOPED (MetaRendererNativeGpuStatePostSwapBuffers,
"update_secondary_gpu_state_post_swap_buffers()"); "acquire_front_buffer()");
secondary_gpu_state = onscreen_native->secondary_gpu_state; secondary_gpu_state = onscreen_native->secondary_gpu_state;
if (secondary_gpu_state) if (!secondary_gpu_state)
{ return g_object_ref (primary_gpu_fb);
MetaRendererNativeGpuData *renderer_gpu_data;
g_autoptr (MetaDrmBuffer) next_fb = NULL;
renderer_gpu_data = renderer_gpu_data =
meta_renderer_native_get_gpu_data (renderer_native, meta_renderer_native_get_gpu_data (renderer_native,
@ -1217,11 +1244,11 @@ update_secondary_gpu_state_post_swap_buffers (CoglOnscreen *onscreen,
switch (renderer_gpu_data->secondary.copy_mode) switch (renderer_gpu_data->secondary.copy_mode)
{ {
case META_SHARED_FRAMEBUFFER_COPY_MODE_ZERO: case META_SHARED_FRAMEBUFFER_COPY_MODE_ZERO:
next_fb = import_shared_framebuffer (onscreen, imported_fb = import_shared_framebuffer (onscreen,
secondary_gpu_state, secondary_gpu_state,
primary_gpu_fb); primary_gpu_fb);
if (next_fb) if (imported_fb)
break; return imported_fb;
/* The fallback was prepared in pre_swap_buffers and is currently /* The fallback was prepared in pre_swap_buffers and is currently
* in secondary_gpu_fb. * in secondary_gpu_fb.
*/ */
@ -1229,19 +1256,17 @@ update_secondary_gpu_state_post_swap_buffers (CoglOnscreen *onscreen,
META_SHARED_FRAMEBUFFER_COPY_MODE_PRIMARY; META_SHARED_FRAMEBUFFER_COPY_MODE_PRIMARY;
G_GNUC_FALLTHROUGH; G_GNUC_FALLTHROUGH;
case META_SHARED_FRAMEBUFFER_COPY_MODE_PRIMARY: case META_SHARED_FRAMEBUFFER_COPY_MODE_PRIMARY:
next_fb = g_object_ref (*secondary_gpu_fb); return g_object_ref (secondary_gpu_fb);
break;
case META_SHARED_FRAMEBUFFER_COPY_MODE_SECONDARY_GPU: case META_SHARED_FRAMEBUFFER_COPY_MODE_SECONDARY_GPU:
next_fb = copy_shared_framebuffer_gpu (onscreen, return copy_shared_framebuffer_gpu (onscreen,
secondary_gpu_state, secondary_gpu_state,
renderer_gpu_data, renderer_gpu_data,
egl_context_changed, primary_gpu_fb,
primary_gpu_fb); error);
break;
} }
g_set_object (secondary_gpu_fb, next_fb); g_assert_not_reached ();
} return NULL;
} }
static void static void
@ -1284,10 +1309,36 @@ swap_buffer_result_feedback (const MetaKmsFeedback *kms_feedback,
g_warning ("Page flip failed: %s", error->message); g_warning ("Page flip failed: %s", error->message);
frame_info = cogl_onscreen_peek_head_frame_info (onscreen); frame_info = cogl_onscreen_peek_head_frame_info (onscreen);
frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC;
/* 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_notify_frame_complete (onscreen);
meta_onscreen_native_clear_next_fb (onscreen); }
meta_onscreen_native_clear_posted_fb (onscreen);
}
static void
assign_next_frame (MetaOnscreenNative *onscreen_native,
ClutterFrame *frame)
{
CoglOnscreen *onscreen = COGL_ONSCREEN (onscreen_native);
if (onscreen_native->next_frame != NULL)
{
g_warn_if_fail (onscreen_native->stalled_frame == NULL);
drop_stalled_swap (onscreen);
g_warn_if_fail (onscreen_native->stalled_frame == NULL);
g_clear_pointer (&onscreen_native->stalled_frame, clutter_frame_unref);
onscreen_native->stalled_frame =
g_steal_pointer (&onscreen_native->next_frame);
}
onscreen_native->next_frame = clutter_frame_ref (frame);
} }
static const MetaKmsResultListenerVtable swap_buffer_result_listener_vtable = { static const MetaKmsResultListenerVtable swap_buffer_result_listener_vtable = {
@ -1303,37 +1354,41 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen,
{ {
CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen);
CoglContext *cogl_context = cogl_framebuffer_get_context (framebuffer); CoglContext *cogl_context = cogl_framebuffer_get_context (framebuffer);
CoglDisplay *cogl_display = cogl_context_get_display (cogl_context);
CoglRenderer *cogl_renderer = cogl_context->display->renderer; CoglRenderer *cogl_renderer = cogl_context->display->renderer;
CoglRendererEGL *cogl_renderer_egl = cogl_renderer->winsys; CoglRendererEGL *cogl_renderer_egl = cogl_renderer->winsys;
MetaRendererNativeGpuData *renderer_gpu_data = cogl_renderer_egl->platform; MetaRendererNativeGpuData *renderer_gpu_data = cogl_renderer_egl->platform;
MetaRendererNative *renderer_native = renderer_gpu_data->renderer_native; 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); MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state; MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state;
MetaGpuKms *render_gpu = onscreen_native->render_gpu; MetaGpuKms *render_gpu = onscreen_native->render_gpu;
MetaDeviceFile *render_device_file; MetaDeviceFile *render_device_file;
ClutterFrame *frame = user_data; ClutterFrame *frame = user_data;
MetaFrameNative *frame_native = meta_frame_native_from_frame (frame); MetaFrameNative *frame_native = meta_frame_native_from_frame (frame);
MetaKmsUpdate *kms_update;
CoglOnscreenClass *parent_class; CoglOnscreenClass *parent_class;
gboolean create_timestamp_query = TRUE; gboolean create_timestamp_query = TRUE;
gboolean egl_context_changed = FALSE;
MetaPowerSave power_save_mode;
g_autoptr (GError) error = NULL; g_autoptr (GError) error = NULL;
MetaDrmBufferFlags buffer_flags; MetaDrmBufferFlags buffer_flags;
MetaDrmBufferGbm *buffer_gbm; MetaDrmBufferGbm *buffer_gbm;
g_autoptr (MetaDrmBuffer) primary_gpu_fb = NULL; g_autoptr (MetaDrmBuffer) primary_gpu_fb = NULL;
g_autoptr (MetaDrmBuffer) secondary_gpu_fb = NULL; g_autoptr (MetaDrmBuffer) secondary_gpu_fb = NULL;
MetaKmsCrtc *kms_crtc; g_autoptr (MetaDrmBuffer) buffer = NULL;
MetaKmsDevice *kms_device;
COGL_TRACE_BEGIN_SCOPED (MetaRendererNativeSwapBuffers, COGL_TRACE_BEGIN_SCOPED (MetaRendererNativeSwapBuffers,
"Meta::OnscreenNative::swap_buffers_with_damage()"); "Meta::OnscreenNative::swap_buffers_with_damage()");
if (meta_is_topic_enabled (META_DEBUG_KMS))
{
unsigned int frames_pending =
cogl_onscreen_get_pending_frame_count (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 = secondary_gpu_fb =
update_secondary_gpu_state_pre_swap_buffers (onscreen, update_secondary_gpu_state_pre_swap_buffers (onscreen,
rectangles, rectangles,
@ -1383,46 +1438,28 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen,
g_warning ("Failed to lock front buffer on %s: %s", g_warning ("Failed to lock front buffer on %s: %s",
meta_device_file_get_path (render_device_file), meta_device_file_get_path (render_device_file),
error->message); error->message);
goto swap_failed;
frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC;
meta_onscreen_native_notify_frame_complete (onscreen);
return;
} }
primary_gpu_fb = META_DRM_BUFFER (g_steal_pointer (&buffer_gbm)); primary_gpu_fb = META_DRM_BUFFER (g_steal_pointer (&buffer_gbm));
break; buffer = acquire_front_buffer (onscreen,
case META_RENDERER_NATIVE_MODE_SURFACELESS: primary_gpu_fb,
g_assert_not_reached (); secondary_gpu_fb,
break; &error);
#ifdef HAVE_EGL_DEVICE if (buffer == NULL)
case META_RENDERER_NATIVE_MODE_EGL_DEVICE: {
break; g_warning ("Failed to acquire front buffer: %s", error->message);
#endif goto swap_failed;
} }
update_secondary_gpu_state_post_swap_buffers (onscreen, meta_frame_native_set_buffer (frame_native, buffer);
&egl_context_changed,
primary_gpu_fb,
&secondary_gpu_fb);
switch (renderer_gpu_data->mode) if (!meta_drm_buffer_ensure_fb_id (buffer, &error))
{
case META_RENDERER_NATIVE_MODE_GBM:
g_warn_if_fail (onscreen_native->gbm.next_fb == NULL);
if (onscreen_native->secondary_gpu_state)
g_set_object (&onscreen_native->gbm.next_fb, secondary_gpu_fb);
else
g_set_object (&onscreen_native->gbm.next_fb, primary_gpu_fb);
if (!meta_drm_buffer_ensure_fb_id (onscreen_native->gbm.next_fb, &error))
{ {
g_warning ("Failed to ensure KMS FB ID on %s: %s", g_warning ("Failed to ensure KMS FB ID on %s: %s",
meta_device_file_get_path (render_device_file), meta_device_file_get_path (render_device_file),
error->message); error->message);
goto swap_failed;
frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC;
meta_onscreen_native_notify_frame_complete (onscreen);
return;
} }
break; break;
case META_RENDERER_NATIVE_MODE_SURFACELESS: case META_RENDERER_NATIVE_MODE_SURFACELESS:
@ -1433,21 +1470,86 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen,
#endif #endif
} }
/* assign_next_frame (onscreen_native, frame);
* 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
* EGL_NO_CONTEXT current now, making cogl eventually set the correct
* context.
*/
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)); clutter_frame_set_result (frame,
kms_device = meta_kms_crtc_get_device (kms_crtc); CLUTTER_FRAME_RESULT_PENDING_PRESENTED);
meta_frame_native_set_damage (frame_native, rectangles, n_rectangles);
onscreen_native->swaps_pending++;
try_post_latest_swap (onscreen);
return;
swap_failed:
frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC;
meta_onscreen_native_notify_frame_complete (onscreen);
clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_IDLE);
}
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;
int sync_fd;
COGL_TRACE_SCOPED_ANCHOR (MetaRendererNativePostKmsUpdate);
if (onscreen_native->next_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); power_save_mode = meta_monitor_manager_get_power_save_mode (monitor_manager);
if (power_save_mode == META_POWER_SAVE_ON) if (power_save_mode == META_POWER_SAVE_ON)
{ {
unsigned int frames_pending =
cogl_onscreen_get_pending_frame_count (onscreen);
unsigned int posts_pending;
int n_rectangles;
int *rectangles;
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 = clutter_frame_ref (onscreen_native->next_frame);
frame_native = meta_frame_native_from_frame (frame);
n_rectangles = meta_frame_native_get_damage (frame_native, &rectangles);
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_update = meta_frame_native_ensure_kms_update (frame_native,
kms_device); kms_device);
meta_kms_update_add_result_listener (kms_update, meta_kms_update_add_result_listener (kms_update,
@ -1469,13 +1571,11 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen,
{ {
meta_renderer_native_queue_power_save_page_flip (renderer_native, meta_renderer_native_queue_power_save_page_flip (renderer_native,
onscreen); onscreen);
clutter_frame_set_result (frame,
CLUTTER_FRAME_RESULT_PENDING_PRESENTED);
return; return;
} }
COGL_TRACE_BEGIN_SCOPED (MetaRendererNativePostKmsUpdate, COGL_TRACE_BEGIN_ANCHORED (MetaRendererNativePostKmsUpdate,
"Meta::OnscreenNative::swap_buffers_with_damage#post_pending_update()"); "Meta::OnscreenNative::try_post_latest_swap#post_pending_update()");
switch (renderer_gpu_data->mode) switch (renderer_gpu_data->mode)
{ {
@ -1490,8 +1590,6 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen,
kms_update = meta_frame_native_steal_kms_update (frame_native); kms_update = meta_frame_native_steal_kms_update (frame_native);
meta_renderer_native_queue_mode_set_update (renderer_native, meta_renderer_native_queue_mode_set_update (renderer_native,
kms_update); kms_update);
clutter_frame_set_result (frame,
CLUTTER_FRAME_RESULT_PENDING_PRESENTED);
return; return;
} }
else if (meta_renderer_native_has_pending_mode_set (renderer_native)) else if (meta_renderer_native_has_pending_mode_set (renderer_native))
@ -1505,8 +1603,6 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen,
meta_frame_native_steal_kms_update (frame_native); meta_frame_native_steal_kms_update (frame_native);
meta_renderer_native_post_mode_set_updates (renderer_native); meta_renderer_native_post_mode_set_updates (renderer_native);
clutter_frame_set_result (frame,
CLUTTER_FRAME_RESULT_PENDING_PRESENTED);
return; return;
} }
break; break;
@ -1522,8 +1618,6 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen,
kms_update); kms_update);
meta_renderer_native_post_mode_set_updates (renderer_native); meta_renderer_native_post_mode_set_updates (renderer_native);
clutter_frame_set_result (frame,
CLUTTER_FRAME_RESULT_PENDING_PRESENTED);
return; return;
} }
break; break;
@ -1536,9 +1630,10 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen,
meta_kms_device_get_path (kms_device)); meta_kms_device_get_path (kms_device));
kms_update = meta_frame_native_steal_kms_update (frame_native); kms_update = meta_frame_native_steal_kms_update (frame_native);
sync_fd = cogl_context_get_latest_sync_fd (cogl_context);
meta_kms_update_set_sync_fd (kms_update, sync_fd);
meta_kms_device_post_update (kms_device, kms_update, meta_kms_device_post_update (kms_device, kms_update,
META_KMS_UPDATE_FLAG_NONE); META_KMS_UPDATE_FLAG_NONE);
clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_PENDING_PRESENTED);
} }
gboolean gboolean
@ -1571,7 +1666,7 @@ meta_onscreen_native_is_buffer_scanout_compatible (CoglOnscreen *onscreen,
assign_primary_plane (crtc_kms, assign_primary_plane (crtc_kms,
buffer, buffer,
test_update, test_update,
META_KMS_ASSIGN_PLANE_FLAG_DIRECT_SCANOUT, META_KMS_ASSIGN_PLANE_FLAG_DISABLE_IMPLICIT_SYNC,
&src_rect, &src_rect,
&dst_rect); &dst_rect);
@ -1606,11 +1701,15 @@ scanout_result_feedback (const MetaKmsFeedback *kms_feedback,
G_IO_ERROR_PERMISSION_DENIED)) G_IO_ERROR_PERMISSION_DENIED))
{ {
ClutterStageView *view = CLUTTER_STAGE_VIEW (onscreen_native->view); ClutterStageView *view = CLUTTER_STAGE_VIEW (onscreen_native->view);
ClutterFrame *posted_frame = onscreen_native->posted_frame;
MetaFrameNative *posted_frame_native =
meta_frame_native_from_frame (posted_frame);
CoglScanout *scanout =
meta_frame_native_get_scanout (posted_frame_native);
g_warning ("Direct scanout page flip failed: %s", error->message); g_warning ("Direct scanout page flip failed: %s", error->message);
cogl_scanout_notify_failed (onscreen_native->gbm.next_scanout, cogl_scanout_notify_failed (scanout, onscreen);
onscreen);
clutter_stage_view_add_redraw_clip (view, NULL); clutter_stage_view_add_redraw_clip (view, NULL);
clutter_stage_view_schedule_update_now (view); clutter_stage_view_schedule_update_now (view);
} }
@ -1619,7 +1718,7 @@ scanout_result_feedback (const MetaKmsFeedback *kms_feedback,
frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC; frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC;
meta_onscreen_native_notify_frame_complete (onscreen); 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 = { static const MetaKmsResultListenerVtable scanout_result_listener_vtable = {
@ -1671,15 +1770,27 @@ meta_onscreen_native_direct_scanout (CoglOnscreen *onscreen,
return FALSE; 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_get_pending_frame_count (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, renderer_gpu_data = meta_renderer_native_get_gpu_data (renderer_native,
render_gpu); render_gpu);
g_warn_if_fail (renderer_gpu_data->mode == META_RENDERER_NATIVE_MODE_GBM); g_warn_if_fail (renderer_gpu_data->mode == META_RENDERER_NATIVE_MODE_GBM);
g_warn_if_fail (!onscreen_native->gbm.next_fb);
g_warn_if_fail (!onscreen_native->gbm.next_scanout);
g_set_object (&onscreen_native->gbm.next_scanout, scanout); assign_next_frame (onscreen_native, frame);
g_set_object (&onscreen_native->gbm.next_fb,
meta_frame_native_set_scanout (frame_native, scanout);
meta_frame_native_set_buffer (frame_native,
META_DRM_BUFFER (cogl_scanout_get_buffer (scanout))); META_DRM_BUFFER (cogl_scanout_get_buffer (scanout)));
frame_info->cpu_time_before_buffer_swap_us = g_get_monotonic_time (); frame_info->cpu_time_before_buffer_swap_us = g_get_monotonic_time ();
@ -1701,7 +1812,7 @@ meta_onscreen_native_direct_scanout (CoglOnscreen *onscreen,
onscreen_native->view, onscreen_native->view,
onscreen_native->crtc, onscreen_native->crtc,
kms_update, kms_update,
META_KMS_ASSIGN_PLANE_FLAG_DIRECT_SCANOUT, META_KMS_ASSIGN_PLANE_FLAG_DISABLE_IMPLICIT_SYNC,
NULL, NULL,
0); 0);
@ -1786,11 +1897,15 @@ meta_onscreen_native_before_redraw (CoglOnscreen *onscreen,
ClutterFrame *frame) ClutterFrame *frame)
{ {
MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
if (meta_get_debug_paint_flags () & META_DEBUG_PAINT_SYNC_CURSOR_PRIMARY)
{
MetaCrtcKms *crtc_kms = META_CRTC_KMS (onscreen_native->crtc); MetaCrtcKms *crtc_kms = META_CRTC_KMS (onscreen_native->crtc);
MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (crtc_kms); MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (crtc_kms);
meta_kms_device_await_flush (meta_kms_crtc_get_device (kms_crtc), meta_kms_device_await_flush (meta_kms_crtc_get_device (kms_crtc), kms_crtc);
kms_crtc); }
maybe_update_frame_sync (onscreen_native, frame); maybe_update_frame_sync (onscreen_native, frame);
} }
@ -1919,22 +2034,79 @@ meta_onscreen_native_finish_frame (CoglOnscreen *onscreen,
MetaKmsDevice *kms_device = meta_kms_crtc_get_device (kms_crtc); MetaKmsDevice *kms_device = meta_kms_crtc_get_device (kms_crtc);
MetaFrameNative *frame_native = meta_frame_native_from_frame (frame); MetaFrameNative *frame_native = meta_frame_native_from_frame (frame);
MetaKmsUpdate *kms_update; MetaKmsUpdate *kms_update;
unsigned int frames_pending = cogl_onscreen_get_pending_frame_count (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); onscreen_native->needs_flush |= meta_kms_device_handle_flush (kms_device,
if (!kms_update) kms_crtc);
if (!meta_frame_native_has_kms_update (frame_native))
{ {
if (meta_kms_device_handle_flush (kms_device, kms_crtc)) if (!onscreen_native->needs_flush || posts_pending)
{
kms_update = meta_kms_update_new (kms_device);
meta_kms_update_set_flushing (kms_update, kms_crtc);
}
else
{ {
clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_IDLE); clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_IDLE);
return; 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_frame == NULL);
g_clear_pointer (&onscreen_native->next_frame, clutter_frame_unref);
onscreen_native->next_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_frame != NULL);
older_frame_native =
meta_frame_native_from_frame (onscreen_native->next_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, meta_kms_update_add_result_listener (kms_update,
&finish_frame_result_listener_vtable, &finish_frame_result_listener_vtable,
NULL, NULL,
@ -1957,7 +2129,17 @@ meta_onscreen_native_finish_frame (CoglOnscreen *onscreen,
meta_kms_update_set_flushing (kms_update, kms_crtc); meta_kms_update_set_flushing (kms_update, kms_crtc);
meta_kms_device_post_update (kms_device, kms_update, meta_kms_device_post_update (kms_device, kms_update,
META_KMS_UPDATE_FLAG_NONE); 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_pointer (&onscreen_native->stalled_frame, clutter_frame_unref);
g_clear_pointer (&onscreen_native->next_frame, clutter_frame_unref);
} }
static gboolean static gboolean
@ -2865,15 +3047,17 @@ meta_onscreen_native_dispose (GObject *object)
meta_onscreen_native_detach (onscreen_native); meta_onscreen_native_detach (onscreen_native);
g_clear_pointer (&onscreen_native->next_frame, clutter_frame_unref);
g_clear_pointer (&onscreen_native->stalled_frame, clutter_frame_unref);
g_clear_pointer (&onscreen_native->posted_frame, clutter_frame_unref);
g_clear_pointer (&onscreen_native->presented_frame, clutter_frame_unref);
renderer_gpu_data = renderer_gpu_data =
meta_renderer_native_get_gpu_data (renderer_native, meta_renderer_native_get_gpu_data (renderer_native,
onscreen_native->render_gpu); onscreen_native->render_gpu);
switch (renderer_gpu_data->mode) switch (renderer_gpu_data->mode)
{ {
case META_RENDERER_NATIVE_MODE_GBM: case META_RENDERER_NATIVE_MODE_GBM:
g_clear_object (&onscreen_native->gbm.next_fb);
g_clear_object (&onscreen_native->gbm.next_scanout);
free_current_bo (onscreen);
break; break;
case META_RENDERER_NATIVE_MODE_SURFACELESS: case META_RENDERER_NATIVE_MODE_SURFACELESS:
g_assert_not_reached (); g_assert_not_reached ();

View file

@ -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, gboolean meta_onscreen_native_is_buffer_scanout_compatible (CoglOnscreen *onscreen,
CoglScanout *scanout); CoglScanout *scanout);
void meta_onscreen_native_discard_pending_swaps (CoglOnscreen *onscreen);
void meta_onscreen_native_set_view (CoglOnscreen *onscreen, void meta_onscreen_native_set_view (CoglOnscreen *onscreen,
MetaRendererView *view); MetaRendererView *view);

View file

@ -731,12 +731,18 @@ static gboolean
dummy_power_save_page_flip_cb (gpointer user_data) dummy_power_save_page_flip_cb (gpointer user_data)
{ {
MetaRendererNative *renderer_native = 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, (GFunc) meta_onscreen_native_dummy_power_save_page_flip,
NULL); NULL);
g_clear_list (&renderer_native->power_save_page_flip_onscreens, g_clear_list (&old_list,
g_object_unref); 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; renderer_native->power_save_page_flip_source_id = 0;
return G_SOURCE_REMOVE; 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; 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) if (!renderer_native->power_save_page_flip_source_id)
{ {
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 static void
meta_renderer_native_rebuild_views (MetaRenderer *renderer) meta_renderer_native_rebuild_views (MetaRenderer *renderer)
{ {
@ -1539,6 +1568,7 @@ meta_renderer_native_rebuild_views (MetaRenderer *renderer)
MetaRendererClass *parent_renderer_class = MetaRendererClass *parent_renderer_class =
META_RENDERER_CLASS (meta_renderer_native_parent_class); META_RENDERER_CLASS (meta_renderer_native_parent_class);
discard_pending_swaps (renderer);
meta_kms_discard_pending_page_flips (kms); meta_kms_discard_pending_page_flips (kms);
g_hash_table_remove_all (renderer_native->mode_set_updates); g_hash_table_remove_all (renderer_native->mode_set_updates);

View file

@ -24,6 +24,7 @@
#include "backends/native/meta-renderer-view-native.h" #include "backends/native/meta-renderer-view-native.h"
#include "backends/native/meta-crtc-native.h"
#include "backends/native/meta-frame-native.h" #include "backends/native/meta-frame-native.h"
struct _MetaRendererViewNative struct _MetaRendererViewNative
@ -34,18 +35,58 @@ struct _MetaRendererViewNative
G_DEFINE_TYPE (MetaRendererViewNative, meta_renderer_view_native, G_DEFINE_TYPE (MetaRendererViewNative, meta_renderer_view_native,
META_TYPE_RENDERER_VIEW) META_TYPE_RENDERER_VIEW)
static void
update_frame_clock_deadline_evasion (MetaRendererView *renderer_view)
{
ClutterStageView *stage_view = CLUTTER_STAGE_VIEW (renderer_view);
ClutterFrameClock *frame_clock;
MetaCrtc *crtc;
MetaCrtcNative *crtc_native;
int64_t deadline_evasion_us;
frame_clock = clutter_stage_view_get_frame_clock (stage_view);
crtc = meta_renderer_view_get_crtc (renderer_view);
crtc_native = META_CRTC_NATIVE (crtc);
deadline_evasion_us = meta_crtc_native_get_deadline_evasion (crtc_native);
clutter_frame_clock_set_deadline_evasion (frame_clock,
deadline_evasion_us);
}
static void
meta_renderer_view_native_constructed (GObject *object)
{
MetaRendererView *renderer_view = META_RENDERER_VIEW (object);
G_OBJECT_CLASS (meta_renderer_view_native_parent_class)->constructed (object);
update_frame_clock_deadline_evasion (renderer_view);
}
static ClutterFrame * static ClutterFrame *
meta_renderer_view_native_new_frame (ClutterStageView *stage_view) meta_renderer_view_native_new_frame (ClutterStageView *stage_view)
{ {
return (ClutterFrame *) meta_frame_native_new (); return (ClutterFrame *) meta_frame_native_new ();
} }
static void
meta_renderer_view_native_schedule_update (ClutterStageView *stage_view)
{
MetaRendererView *renderer_view = META_RENDERER_VIEW (stage_view);
update_frame_clock_deadline_evasion (renderer_view);
}
static void static void
meta_renderer_view_native_class_init (MetaRendererViewNativeClass *klass) meta_renderer_view_native_class_init (MetaRendererViewNativeClass *klass)
{ {
GObjectClass *object_class = G_OBJECT_CLASS (klass);
ClutterStageViewClass *stage_view_class = CLUTTER_STAGE_VIEW_CLASS (klass); ClutterStageViewClass *stage_view_class = CLUTTER_STAGE_VIEW_CLASS (klass);
object_class->constructed = meta_renderer_view_native_constructed;
stage_view_class->new_frame = meta_renderer_view_native_new_frame; stage_view_class->new_frame = meta_renderer_view_native_new_frame;
stage_view_class->schedule_update = meta_renderer_view_native_schedule_update;
} }
static void static void

View file

@ -69,6 +69,7 @@ static const GDebugKey meta_debug_keys[] = {
{ "color", META_DEBUG_COLOR }, { "color", META_DEBUG_COLOR },
{ "input-events", META_DEBUG_INPUT_EVENTS }, { "input-events", META_DEBUG_INPUT_EVENTS },
{ "eis", META_DEBUG_EIS }, { "eis", META_DEBUG_EIS },
{ "kms-deadline", META_DEBUG_KMS_DEADLINE },
}; };
static gint verbose_topics = 0; static gint verbose_topics = 0;
@ -326,6 +327,8 @@ meta_topic_to_string (MetaDebugTopic topic)
return "INPUT_EVENTS"; return "INPUT_EVENTS";
case META_DEBUG_EIS: case META_DEBUG_EIS:
return "EIS"; return "EIS";
case META_DEBUG_KMS_DEADLINE:
return "KMS_DEADLINE";
} }
return "WM"; return "WM";

View file

@ -50,6 +50,7 @@
* @META_DEBUG_COLOR: color management * @META_DEBUG_COLOR: color management
* @META_DEBUG_INPUT_EVENTS: input events * @META_DEBUG_INPUT_EVENTS: input events
* @META_DEBUG_EIS: eis state * @META_DEBUG_EIS: eis state
* @META_DEBUG_KMS_DEADLINE: KMS deadline timers
*/ */
typedef enum typedef enum
{ {
@ -83,6 +84,7 @@ typedef enum
META_DEBUG_COLOR = 1 << 26, META_DEBUG_COLOR = 1 << 26,
META_DEBUG_INPUT_EVENTS = 1 << 27, META_DEBUG_INPUT_EVENTS = 1 << 27,
META_DEBUG_EIS = 1 << 28, META_DEBUG_EIS = 1 << 28,
META_DEBUG_KMS_DEADLINE = 1 << 29,
} MetaDebugTopic; } MetaDebugTopic;
META_EXPORT META_EXPORT

View file

@ -51,11 +51,14 @@ void meta_fatal (const char *format,
* MetaDebugPaintFlag: * MetaDebugPaintFlag:
* @META_DEBUG_PAINT_NONE: default * @META_DEBUG_PAINT_NONE: default
* @META_DEBUG_PAINT_OPAQUE_REGION: paint opaque regions * @META_DEBUG_PAINT_OPAQUE_REGION: paint opaque regions
* @META_DEBUG_PAINT_SYNC_CURSOR_PRIMARY: make cursor updates await compositing
* frames
*/ */
typedef enum typedef enum
{ {
META_DEBUG_PAINT_NONE = 0, META_DEBUG_PAINT_NONE = 0,
META_DEBUG_PAINT_OPAQUE_REGION = 1 << 0, META_DEBUG_PAINT_OPAQUE_REGION = 1 << 0,
META_DEBUG_PAINT_SYNC_CURSOR_PRIMARY = 1 << 1,
} MetaDebugPaintFlag; } MetaDebugPaintFlag;
META_EXPORT META_EXPORT

View file

@ -517,6 +517,12 @@ meta_crtc_test_set_gamma_lut (MetaCrtc *crtc,
sizeof (uint16_t) * lut->size); sizeof (uint16_t) * lut->size);
} }
static int64_t
meta_crtc_test_get_deadline_evasion (MetaCrtcNative *crtc_native)
{
return 0;
}
static void static void
meta_crtc_test_finalize (GObject *object) meta_crtc_test_finalize (GObject *object)
{ {

View file

@ -39,6 +39,8 @@
#include "tests/meta-wayland-test-driver.h" #include "tests/meta-wayland-test-driver.h"
#include "tests/meta-wayland-test-utils.h" #include "tests/meta-wayland-test-utils.h"
#define N_FRAMES_PER_TEST 30
typedef struct typedef struct
{ {
int number_of_frames_left; int number_of_frames_left;
@ -46,12 +48,15 @@ typedef struct
struct { struct {
int n_paints; int n_paints;
uint32_t fb_id; int n_presentations;
int n_direct_scanouts;
GList *fb_ids;
} scanout; } scanout;
gboolean wait_for_scanout; gboolean wait_for_scanout;
struct { struct {
int scanouts_attempted;
gboolean scanout_sabotaged; gboolean scanout_sabotaged;
gboolean fallback_painted; gboolean fallback_painted;
guint repaint_guard_id; guint repaint_guard_id;
@ -101,7 +106,7 @@ meta_test_kms_render_basic (void)
gulong handler_id; gulong handler_id;
test = (KmsRenderingTest) { test = (KmsRenderingTest) {
.number_of_frames_left = 10, .number_of_frames_left = N_FRAMES_PER_TEST,
.loop = g_main_loop_new (NULL, FALSE), .loop = g_main_loop_new (NULL, FALSE),
}; };
handler_id = g_signal_connect (stage, "after-update", handler_id = g_signal_connect (stage, "after-update",
@ -123,7 +128,6 @@ on_scanout_before_update (ClutterStage *stage,
KmsRenderingTest *test) KmsRenderingTest *test)
{ {
test->scanout.n_paints = 0; test->scanout.n_paints = 0;
test->scanout.fb_id = 0;
} }
static void static void
@ -135,6 +139,7 @@ on_scanout_before_paint (ClutterStage *stage,
CoglScanout *scanout; CoglScanout *scanout;
CoglScanoutBuffer *scanout_buffer; CoglScanoutBuffer *scanout_buffer;
MetaDrmBuffer *buffer; MetaDrmBuffer *buffer;
uint32_t fb_id;
scanout = clutter_stage_view_peek_scanout (stage_view); scanout = clutter_stage_view_peek_scanout (stage_view);
if (!scanout) if (!scanout)
@ -143,8 +148,13 @@ on_scanout_before_paint (ClutterStage *stage,
scanout_buffer = cogl_scanout_get_buffer (scanout); scanout_buffer = cogl_scanout_get_buffer (scanout);
g_assert_true (META_IS_DRM_BUFFER (scanout_buffer)); g_assert_true (META_IS_DRM_BUFFER (scanout_buffer));
buffer = META_DRM_BUFFER (scanout_buffer); buffer = META_DRM_BUFFER (scanout_buffer);
test->scanout.fb_id = meta_drm_buffer_get_fb_id (buffer); fb_id = meta_drm_buffer_get_fb_id (buffer);
g_assert_cmpuint (test->scanout.fb_id, >, 0); 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 static void
@ -173,12 +183,12 @@ on_scanout_presented (ClutterStage *stage,
MetaDeviceFile *device_file; MetaDeviceFile *device_file;
GError *error = NULL; GError *error = NULL;
drmModeCrtc *drm_crtc; 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; return;
if (test->wait_for_scanout && test->scanout.fb_id == 0) test->scanout.n_presentations++;
return;
device_pool = meta_backend_native_get_device_pool (backend_native); 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), drm_crtc = drmModeGetCrtc (meta_device_file_get_fd (device_file),
meta_kms_crtc_get_id (kms_crtc)); meta_kms_crtc_get_id (kms_crtc));
g_assert_nonnull (drm_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 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); drmModeFreeCrtc (drm_crtc);
meta_device_file_release (device_file); meta_device_file_release (device_file);
test->number_of_frames_left--;
if (test->number_of_frames_left <= 0)
g_main_loop_quit (test->loop); g_main_loop_quit (test->loop);
else
clutter_actor_queue_redraw (CLUTTER_ACTOR (stage));
} }
typedef enum typedef enum
@ -244,7 +280,9 @@ meta_test_kms_render_client_scanout (void)
g_assert_nonnull (wayland_test_client); g_assert_nonnull (wayland_test_client);
test = (KmsRenderingTest) { test = (KmsRenderingTest) {
.number_of_frames_left = N_FRAMES_PER_TEST,
.loop = g_main_loop_new (NULL, FALSE), .loop = g_main_loop_new (NULL, FALSE),
.scanout = {0},
.wait_for_scanout = TRUE, .wait_for_scanout = TRUE,
}; };
@ -270,7 +308,8 @@ meta_test_kms_render_client_scanout (void)
clutter_actor_queue_redraw (CLUTTER_ACTOR (stage)); clutter_actor_queue_redraw (CLUTTER_ACTOR (stage));
g_main_loop_run (test.loop); 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"); g_debug ("Unmake fullscreen");
window = meta_find_window_from_title (test_context, "dma-buf-scanout-test"); 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); g_assert_cmpint (buffer_rect.y, ==, 10);
test.wait_for_scanout = FALSE; 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)); clutter_actor_queue_redraw (CLUTTER_ACTOR (stage));
g_main_loop_run (test.loop); 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"); g_debug ("Moving back to 0, 0");
meta_window_move_frame (window, TRUE, 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); g_assert_cmpint (buffer_rect.y, ==, 0);
test.wait_for_scanout = TRUE; 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)); clutter_actor_queue_redraw (CLUTTER_ACTOR (stage));
g_main_loop_run (test.loop); 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_update_handler_id);
g_signal_handler_disconnect (stage, before_paint_handler_id); g_signal_handler_disconnect (stage, before_paint_handler_id);
@ -364,6 +413,15 @@ on_scanout_fallback_before_paint (ClutterStage *stage,
if (!scanout) if (!scanout)
return; 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); g_assert_false (test->scanout_fallback.scanout_sabotaged);
if (is_atomic_mode_setting (kms_device)) 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_clear_handle_id (&test->scanout_fallback.repaint_guard_id,
g_source_remove); g_source_remove);
test->scanout_fallback.fallback_painted = TRUE; 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, ClutterFrameInfo *frame_info,
KmsRenderingTest *test) KmsRenderingTest *test)
{ {
if (!test->scanout_fallback.scanout_sabotaged) if (test->scanout_fallback.fallback_painted)
return;
g_assert_true (test->scanout_fallback.fallback_painted);
g_main_loop_quit (test->loop); g_main_loop_quit (test->loop);
test->number_of_frames_left--;
g_assert_cmpint (test->number_of_frames_left, >, 0);
} }
static void static void
@ -443,6 +510,7 @@ meta_test_kms_render_client_scanout_fallback (void)
g_assert_nonnull (wayland_test_client); g_assert_nonnull (wayland_test_client);
test = (KmsRenderingTest) { test = (KmsRenderingTest) {
.number_of_frames_left = N_FRAMES_PER_TEST,
.loop = g_main_loop_new (NULL, FALSE), .loop = g_main_loop_new (NULL, FALSE),
}; };