1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
//! Vsync pacing for the `android_main` frame loop.
//!
//! The loop used to be paced by its own output: a `Fifo` present blocks until
//! the display has taken the previous image, so "render every iteration" and
//! "render once per vsync" were the same thing and nothing else was needed. As
//! soon as the loop learned to *skip* presenting a frame identical to the one
//! already on screen, that pacing disappeared with it - an app holding a frame
//! await open (any game loop, any polling effect) asked for a frame, got no
//! present, asked again, and the loop span at whatever rate the CPU could
//! manage. Measured on a Pixel 9 Pro: a big core pinned at 2.97 GHz and
//! 3062 mW on `CPU(BIG)/S3M_VDD_CPUCL2`, against 4.4 mW for the same screen
//! under HWUI.
//!
//! So the loop needs a clock of its own, and the display's is the right one -
//! it is the rate at which a new frame can possibly become visible, and it is
//! what HWUI paces on. `AChoreographer_postFrameCallback64` delivers it.
//!
//! The callback fires on the looper of the thread that posted it, so
//! [`request_wake_at_next_vsync`] must only ever be called from `android_main`.
//! Waking the looper is not enough on its own: `android-activity` owns the
//! `ALooper_pollOnce` call and decides when to hand control back, so the
//! callback goes through the same [`android_activity::AndroidAppWaker`] the
//! rest of the shell uses rather than relying on the poll returning by itself.
use c_void;
use ;
use OnceLock;
/// Set while a frame callback is posted and has not fired yet, so a loop that
/// iterates several times before the next vsync posts one callback, not one per
/// iteration. The choreographer coalesces duplicates anyway; this keeps the
/// bookkeeping honest and the FFI call off the hot path.
static CALLBACK_POSTED: AtomicBool = new;
/// Set once `AChoreographer_getInstance` has failed, so a device that cannot
/// give us a display clock is asked exactly once and the caller falls back to
/// its previous behaviour instead of paying for a null check every iteration.
static UNAVAILABLE: AtomicBool = new;
/// Wakes the `android_main` looper. There is one frame loop per process, which
/// is why this is a global rather than callback user data: the choreographer
/// takes a raw pointer, and a `OnceLock` gives the same reach without a leaked
/// allocation to justify.
static WAKER: = new;
/// Installs the waker the vsync callback fires. Called once, from
/// `android_main`, before the loop starts; later calls are ignored.
pub
/// Asks the display to wake the frame loop at the next vsync.
///
/// Returns `false` when no choreographer is available, which tells the caller
/// to keep polling the way it did before rather than sleep on a clock that will
/// never tick.
///
/// Must be called from `android_main`: the callback is delivered on the looper
/// of the posting thread.
pub
unsafe extern "C"
use AtomicI64;
/// Timestamp of the most recent choreographer callback, for period
/// measurement in [`observed_vsync_period_ns`].
static LAST_VSYNC_NS: AtomicI64 = new;
/// Most recent single-period delta between consecutive choreographer
/// callbacks; 0 until two callbacks have been observed.
static VSYNC_PERIOD_NS: AtomicI64 = new;
/// The display's refresh period as measured between consecutive
/// choreographer callbacks, if two have been seen. Reflects
/// SurfaceFlinger's `frameRateOverride` pinning automatically, which a
/// panel-mode query would not.
pub