Choreographer, i.e. the panel vsync clock) and the render frame (paced by xrWaitFrame, the compositor clock) are two independent ~72-90Hz clocks with no phase relationship. They beat against each other, so commit() routinely lands after the render thread has already started reading the committed state (read-during-write on the single-buffered renderable proxy) and the consumed input is ~a full frame stale.
xrWaitFrame return timing so commit completion targets the midpoint between consecutive returns.class FramePhaseController(callbacks: FramePhaseController.Callbacks)
FramePhaseController
(
callbacks
)
|
Signature
constructor(callbacks: FramePhaseController.Callbacks) Parameters callbacks: FramePhaseController.Callbacks
Driver hooks for timing inputs, lifecycle guards, and the tick itself.
Returns FramePhaseController |
damping
: Float
[Get][Set] |
Proportional gain / damping: fraction of the phase error corrected per frame.
Signature
var damping: Float |
enabled
: Boolean
[Get][Set] |
Whether phase-locking is enabled for this controller (the app's intent). Defaults to FramePhaseController.Companion.enabledByDefault. The loop additionally only phase-locks when the backend supports it (GraphicsHSR) and when predicted-display timing is available; otherwise it auto-falls-back to the historical free-running Choreographer schedule. So phase-sync is off on non-HSR backends even when this is true.
Signature
var enabled: Boolean |
lockedScheduleMode
: FramePhasePll.LockedScheduleMode
[Get][Set] |
Locked-mode scheduling policy used after valid OpenXR timing is available.
Signature
var lockedScheduleMode: FramePhasePll.LockedScheduleMode |
maxSlewUs
: Long
[Get][Set] |
Maximum schedule correction applied in a single frame, in microseconds (slew-rate limit).
Signature
var maxSlewUs: Long |
targetLeadUs
: Long
[Get][Set] |
Target lead (us) from commit end to the next predicted-display grid line. Tune via captures.
Signature
var targetLeadUs: Long |
onFrame
(
frameTimeNanos
)
|
Per-frame entry point. Called by the driver's doFrame (fallback mode, Choreographer) and by the internal Handler runnable (locked mode). The FramePhaseController.onFrame is the Choreographer vsync timestamp in fallback mode and System.nanoTime in locked mode; both are CLOCK_MONOTONIC.
Signature
fun onFrame(frameTimeNanos: Long) Parameters frameTimeNanos: Long |
start
()
|
Starts (or restarts) the frame loop. No-op if already running.
Signature
fun start() |
stop
()
|
Stops the frame loop and clears any pending scheduled frame.
Signature
fun stop() |
enabledByDefault
: Boolean
[Get][Set] |
Process-wide default for FramePhaseController.enabled, read when a FramePhaseController is constructed. Set this before activity/service creation to globally opt the phase scheduler in or out (e.g. a kill-switch). Defaults to true (on where supported).
Signature
var enabledByDefault: Boolean |
lockedScheduleModeByDefault
: FramePhasePll.LockedScheduleMode
[Get][Set] |
Process-wide default for locked scheduling. WAITFRAME_MIDPOINT targets commit completion midway between consecutive xrWaitFrame return times.
Signature
var lockedScheduleModeByDefault: FramePhasePll.LockedScheduleMode |
interface Callbacks
commit
()
|
The commit itself ( spatial.tick(isResumed)). The controller measures wall-clock duration around this call to derive the realized lead and self-tune.
Signature
abstract fun commit() |
getConfirmedFrameRate
()
|
Confirmed display refresh rate in Hz (e.g. scene.getConfirmedFrameRate()).
Signature
abstract fun getConfirmedFrameRate(): Float Returns Float |
getFrameTiming
()
|
Latest OpenXR predicted-display timing snapshot (e.g. spatial.getFrameTiming()).
Signature
abstract fun getFrameTiming(): FrameTiming Returns FrameTiming |
isPhaseSyncSupported
()
|
Whether the active graphics backend supports phase-locking (GraphicsHSR). When false, the controller never locks and runs the historical Choreographer schedule regardless of FramePhaseController.enabled — i.e. phase-sync defaults off on non-HSR backends. Queried at FramePhaseController.start.
Signature
abstract fun isPhaseSyncSupported(): Boolean Returns Boolean |
onSceneTick
()
|
Per-frame application work, run only on frames that tick. The driver is responsible for any isResumed gating (matching the historical if (isResumed) onSceneTick()).
Signature
abstract fun onSceneTick() |
postChoreographerFrame
()
|
Posts a Choreographer frame callback (the driver itself) so the next fallback-mode frame arrives via the driver's doFrame, which must delegate to FramePhaseController.onFrame. Used only in fallback mode; locked mode is driven by an internal Handler.
Signature
abstract fun postChoreographerFrame() |
removeChoreographerFrame
()
|
Removes any pending Choreographer frame callback posted via FramePhaseController.Callbacks.postChoreographerFrame.
Signature
abstract fun removeChoreographerFrame() |
shouldContinueFrameLoop
()
|
Top-of-frame lifecycle guard. When false, the controller neither ticks nor re-arms and the loop stops (until FramePhaseController.start is called again). Mirrors the drivers' isFinishing || isDestroyed / cancelScheduledFrames checks.
Signature
abstract fun shouldContinueFrameLoop(): Boolean Returns Boolean |
shouldRearmFrameLoop
()
|
Re-arm guard evaluated after the tick. When false the loop stops (until FramePhaseController.start is called again). Mirrors VrActivity's post-pause OpenXR settle window; drivers without a settle window return the same value as FramePhaseController.Callbacks.shouldContinueFrameLoop.
Signature
abstract fun shouldRearmFrameLoop(): Boolean Returns Boolean |