Repository navigation
Remove the synchronous DRM atomic page-flip wait from Flutter’s raster presentation path - #529
Conversation
|
This is related to #528 |
|
mh, I'm not sure about this, this is basically triple buffering and there is already a 1 frame queue, it is in the kernel, so with this it's now a 2-frame queue, plus rendering that can basically be 48ms of latency in total... also since there's no vsync callback and you're not blocking on presents anymore, the frame timings can become totally out of sync with the actual vsync times how much queueing you want is a tradeoff of throughput vs latency. Some graphics intensive applications might want a deeper queue, some more more interactive apps might want lower latency. I started working on proper frame scheduling here: https://github.com/ardera/flutter-pi/blob/feat/frame-scheduling/src/frame_scheduler.c but never finished it. We can add your queueing, since it helps in your case, but I'd hide it behind a feature flag / commandline option for now and make it not the default for now |
|
Thanks for this patch — wanted to share independent real-hardware test results, since this directly addresses the symptom described in #528 ("constant raster jank") on our hardware too. Setup: Embedded ARM SoM, weak Vivante/Etnaviv GPU via Mesa+DRM/KMS, 480x272 panel (~69Hz), flutter-pi with Method: App's own frame-timing instrumentation ( Structural fix, confirmed at the kernel level:
Pre-patch, the raster thread reliably sat in Raster latency, 5 independent clean idle runs (fresh restart each time, no touch input, backend data stream still active; numbers are the final cumulative
Steady-state raster Startup transient: every run shows elevated/variable latency for the first few minutes after a fresh restart (briefly up to ~25-27ms). This is not a deterministic "warm-up" — three independent short runs showed three different shapes (declining / flat / rising), so we treat it as run-to-run startup noise rather than a real phenomenon tied to the patch. 1-hour soak test (addressing the "tradeoff of throughput vs latency... can compound over time" concern): after the initial transient settled (~15-20 min in), raster Caveat: per the note that We haven't yet done a physical-touch responsiveness check (remote-only testing so far), but based on the above this looks like a solid win for our use case. |
|
I definitely agree this is a good change. But sorry @frog-fest that analysis is not the helpful 😅 Raster time and "d-time" don't mean anything in this case. Latency can't be measured using raster time. |
|
Field-test results: touch/render responsiveness with & without this patch I ran a black-box A/B comparison on real hardware (STM32MP1 DHCOM SOM, 480×272 LVDS panel, ~69Hz) between two otherwise-identical builds of our Flutter HMI app — one running flutter-pi with this PR applied, one without — to see if it measurably affects what #528 describes as "constant raster jank." Method: A small custom tool logs evdev touch events and DRM/KMS scanout-buffer changes on a shared Test 1 — animation jank: Repeatedly tapped an isolated checkbox with a build/collapse checkmark animation, measured the max frame-to-frame gap within each tap's reaction burst (a proxy for a stalled/dropped frame mid-animation). Reproduced over 2 independent rounds:
Patched is consistently lower on both p50 and p95 in both rounds. Test 2 — on-screen keyboard popup latency: This device has no OS-level keyboard/input method — the "keyboard" is an in-app Flutter widget we render ourselves. Typed through a full on-screen keyboard layout (all letters + punctuation), measuring touch-release → key-popup-appear latency:
Patched is ~3× faster at the median and >2× faster at p95. Caveats: Manual taps by a single tester, moderate sample sizes (n≈20–59/condition), not a rigorous lab setup. A simpler "touch → first visible frame change" onset-latency metric was inconclusive/noisy early on (background redraw contamination, drag-gesture mismatches) and was dropped in favor of the two metrics above, which were more robust and reproducible. |
|
Independent A/B/C on different hardware, measuring FPS as you suggested. Setup: Orange Pi 6 Plus (CIX CD8180 / Sky1), Mali-G720 via Panfrost GLES (Mesa 26.0.8), Linlon-D60 display controller + PS185 DP→HDMI bridge, 1920×1080 @ 60 Hz,
On this display controller the blocking commit returns two vblanks after it is issued (raster pinned at exactly 33.3 ms on master), so the raster thread misses every other vsync. GPU time itself is well under 1 ms per frame (glmark2-es2-drm), so this is purely the present path. On the latency concern: I tried a smaller variant that keeps at most one page flip in flight. It reaches the same FPS here. I have not measured input-to-photon latency for either variant, so I can't claim one is better on latency; only that the single-in-flight version adds no queue by construction. Not tested: Raspberry Pi / vc4, and the Vulkan path (on this board, with master, every Vulkan present fails with |
|
@ardera the pipeline expired, but I am not getting any feedback what went wrong other than it was expired because it wasn't started I guess? I would be 100% fine with having this as a commandline option. |
Raspberry Pi 5: higher presentation throughput with PR #529I compared this change on our actual Raspberry Pi 5 photoframe setup, using the Setup
The full sweep covers all 51 effects, plus a no-effect control. Mosaic variants Additional runs cover 48 MP photos, Mosaic/MosaicFade grids of 100/150/200 with ResultsIn addition to Flutter
Flutter-reported FPS follows the same improvement in the demanding cases: The display limit matters: in the lightweight control, Flutter-reported FPS rises Tail behaviour is less uniform. For Water Refraction, the p95 interval between DRM Validation and limits
|
|
thanks all, I could also reproduce the improvement locally. I think it's not perfect but definitely better for most people right now, so I'll just merge directly. Pipeline is failing because it hasn't been updated in ages, I'll have to fix that separately. |
Integrate upstream PR ardera#529 without the unrelated video changes from remove-page-flip, preserving the published support_dpms history. Before a blocking commit, discard the queued frame and drain the pending page flip so it cannot re-enable the CRTC after a power change. Keep power requests out of the page-flip path and leave mutex ownership to the common commit wrapper. Port the DPMS regression cases, use an error-checking test mutex, and adapt the upstream presentation fixture to provide valid DRM property IDs. Validation: all four CTest targets pass in Debug and Release (OpenGL/Vulkan). DPMS passes ASan/UBSan with leak detection. The full sanitizer suite reports 316 bytes of existing command-line test leaks in flutterpi_test.
|
@ardera If you can give me some pointers on what to improve, I am happy to learn <3 |
Previously, FlutterCompositorPresentLayers called the blocking atomic commit path, kms_req_commit_blocking (ironically surrounded by a tracer log calling it unblocking).
The raster thread could therefore spend up to a refresh interval waiting for KMS page flip completion, inflating raster duration and causing phase-dependent frame latency.
This change moves that wait out of the raster path while keeping request and framebuffer lifetimes valid until scanout completion.
KMS presentation is now asynchronous: the raster thread submits a nonblocking commit and returns immediately, while framebuffer ownership is retained until the DRM page-flip event confirms scanout.