Skip to content

Prediction horizon under varying weave cadence: EMA-smoothed setLatency makes cadence variance read as stutter (per-weave horizon needed) #206

Description

@dfattal

Observation (David, hardware, 2026-08-28)

Across a week of weave-cadence experiments on the win box (runtime #1257 gate + slot-partition builds, native 3DLuma avatar, Leia panel), perceived 3D quality tracks cadence steadiness, not update rate:

weave cadence panel updates/s David's verdict
steady (stock) 60 fine
steady (partition D=3: app 20 + repaints 40) 60 "tracking is great, no stutter, very good experience"
varying (gate hz30 adaptive: oscillating) 28–50 "pretty stuttery"
varying (partition v2 on VK tier) 45–50 unsteady "stuttery"

A 45–50/s varying cadence looks worse than a steady 60 — and by more than temporal sampling explains. David's hypothesis: the head-position prediction is wrong per frame when the cadence varies. The SR runtime predicts from tracking history + a prediction horizon we provide via setLatency; if the horizon is a smoothed average while the actual weave-to-photon time varies frame-to-frame, every weave is derived for a head position at the wrong time → parallax error oscillating at the cadence-variation frequency → spatial judder that reads as stutter.

The code supports the hypothesis

src/drv_leia/leia_sr.cpp (ConfigureAdaptiveLatency, v2.6.4): the adaptive path smooths the weave interval with an EMA, default alpha 0.15 (LEIA_VK_LATENCY_EMA_ALPHA), plus a constant display term. An EMA with alpha 0.15 has a time constant of ~6 weaves — under a cadence oscillating between 28 and 50/s the estimate is always lagging and always averaged across two regimes, so the per-weave horizon error is on the order of half the interval spread (~±8 ms), which at a fast head motion (~0.3 m/s) is ~±2.4 mm of predicted-position error alternating frame to frame.

The comment also says paced paths get their horizon from the runtime's measured set_frame_timing feed — same question applies there: is that feed per-frame-accurate under a varying cadence, or also smoothed?

Repro recipe (all on one box, same app, same rate ladder)

Runtime branch builds from #1257 (or any means of producing the cadence regimes):

  1. Steady control: stock run — steady 60, smooth.
  2. Steady at reduced app rate: DXR_APP_FRAME_DIVISOR=3 on the default hybrid path (slot partition, v2.14.10-20+) — steady 60 (20 app + 40 repaint), verdict "very good".
  3. Varying cadence: gate build hz30 adaptive (v2.14.10-16, DXR_AVATAR_PRESENT_HZ=30) — panel oscillates 28–50/s, verdict "stuttery"; or partition v2 on the VK-pinned tier (45–50/s unsteady), same verdict.
  4. Move your head laterally at a constant moderate speed while comparing 2 vs 3. Witness lines (DXR_FRAME_WITNESS=5) document the cadence of each run.

The discriminating experiment for the hypothesis: run regime 3 with LEIA_VK_LATENCY_FIXED_US set to the true mean horizon — if stutter persists identically, the error is the per-frame variance (not the estimator's lag), and no scalar setLatency value can be correct; per-weave horizons are needed.

Asks

  1. Verify what horizon actually reaches the SR predictor per weave under a varying cadence (adaptive EMA path and the set_frame_timing measured path).
  2. If it is smoothed/lagged: provide the actual per-weave horizon — call setLatency (or the timestamped equivalent) before each weave with that weave's real present-to-photon time, which the compositor knows (it owns the schedule; a repaint knows it presents at the next vblank).
  3. Vendor question for Leia SR (if per-call setLatency can't keep up or is internally smoothed): does the SR predictor support a per-call prediction timestamp rather than a scalar latency, and is srWeaverSetLatencyInFrames(weaver, 1) (the non-adaptive default) interacting with the adaptive path?

Cross-refs: cadence data and verdicts in DisplayXR/displayxr-runtime#1257 (gate + partition investigation); the steadiness-vs-rate finding is what motivated the slot partition, but why variance is so visible was unexplained until this hypothesis — fixing it would also make variable-cadence modes viable.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions