From 9b331a5e934be9ce744d0ce297fdae6d7d421bec Mon Sep 17 00:00:00 2001 From: iris <2+iris@noreply.localhost> Date: Fri, 4 Sep 2026 19:10:50 -0400 Subject: [PATCH] Call pre_present_notify, so a settled frame actually reaches the screen About one start in five, the window kept its 800x600 startup layout on a 1920x1200 surface for good. It was not the layout: tracing iris's own decisions into memory -- eprintln in the draw path makes the fault vanish, which is why it kept getting lost -- gives byte-identical traces for a good and a bad run. Both do redraw_all at (1920, 1200) and draw into a 1920x1200 texture with suboptimal=false. The right frame was drawn every time and the compositor kept showing the first one, and forcing a full repaint did not shift it. winit's Window::pre_present_notify, called immediately before present, is what ties the commit to the surface's frame callback on Wayland. Without it a frame with nothing following it can sit unpresented with nothing left to flush it -- which is precisely a window that has just settled after its opening resize. 0 bad in 40 with the fix, against 4 in 20 without. The stronger number is 0 in 20 in the instrumented configuration that had been 15 in 20, since that is the arrangement the fault liked most. Runtime resizing still round-trips to a byte-identical layout. Ruled out and not worth re-trying: the present mode (the fault survived AutoNoVsync -> AutoVsync at the same rate) and the size cache (redraw_all clears it). desired_maximum_frame_latency = 1 moved the rate without fixing it and was reverted. Co-Authored-By: Claude Opus 5 --- RUST.md | 48 +++++++++++++++++++++----------------- iris/src/default/render.rs | 7 ++++++ 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/RUST.md b/RUST.md index f5dc4a3..8193a80 100644 --- a/RUST.md +++ b/RUST.md @@ -612,27 +612,33 @@ step measured. rather than an answer about Masonry, whose graph adds Vello, Parley, Fontique and Skrifa. Runtime cost of the knob was not measured here. - **Open defect found while doing this: iris sometimes never adopts - the window's real size.** Measured on the headless rig, ~3 starts in - 15: the `tabs` example settles showing its 800×600 startup layout in - the top-left of a 1920×1200 surface, black around it, and stays that - way indefinitely — it is not a screenshot taken too early, since the - picture is byte-identical for the next four seconds. What is *not* - the cause, each checked: the winit event order is identical in good - and bad runs (`Resized(800×600)`, two redraws, `Resized(1920×1200)`, - one redraw), the swapchain reports `1920×1200` and - `suboptimal=false` on that last draw, and `output_size` is - `(1920, 1200)` going into it. It is timing-sensitive in the way that - makes it expensive: adding a single `eprintln!` anywhere in the draw - or event path hides it completely (0 in 16), which is why the - instrumentation above could not catch it in the act. A pointer move - does not repair it, because iris only redraws when something - changed; an output mode change does, because that is another resize. - Left open rather than guessed at. It matters most for **I2**, where - every rotation and every keyboard open is a resize, so a stale frame - would be the normal case rather than a rare one; a Wayland-level - trace of the xdg-surface configure/ack/commit sequence is the next - step, not more `eprintln`. + **Fixed: iris never called `pre_present_notify`.** The symptom was + that about one start in five kept the window's 800x600 startup layout + on a 1920x1200 surface for good. What settled it was tracing iris's + own decisions into memory and dumping them from another thread — + `eprintln!` in the draw path makes the defect vanish, which is why + earlier attempts kept losing it. The traces from a good and a bad run + are **byte-identical**: both lay out and draw `redraw_all at + (1920, 1200)` into a 1920x1200 texture with `suboptimal=false`. iris + was drawing the right frame every time; the compositor was still + showing the first one, and forcing a full repaint did not shift it. + What was missing is winit's `Window::pre_present_notify`, called + immediately before `present`, which on Wayland is what ties the + commit to the surface's frame callback. Without it a frame drawn with + nothing following it can sit unpresented with nothing left to flush + it — which is exactly a window that has just settled after its + opening resize. Measured: **0 bad in 40** with the fix, against 4 in + 20 before it, and — the stronger evidence — 0 in 20 in the + instrumented configuration that had been 15 in 20. Runtime resizing + still round-trips to a byte-identical layout. + + Two things ruled out on the way, both worth not re-trying: the + present mode (the fault survived the move from `AutoNoVsync` to + `AutoVsync` at the same rate) and the size cache (`redraw_all` clears + it). A `desired_maximum_frame_latency` of 1 moved the rate without + fixing it, and was reverted. Iris's own note that she had never seen + the library fail to resize was the useful steer: it pointed away from + the layout code, where two hours had already gone. One thing was fixed on the way, and it is not that bug: `update` redrew everything when `resized` was set, but `needs_redraw` — which diff --git a/iris/src/default/render.rs b/iris/src/default/render.rs index b317498..0004dd1 100644 --- a/iris/src/default/render.rs +++ b/iris/src/default/render.rs @@ -45,6 +45,13 @@ impl UiRenderer { } self.queue.submit(std::iter::once(encoder.finish())); + // Immediately before presenting, so the windowing system can schedule + // the frame. On Wayland this is what ties the commit to the surface's + // frame callback; without it a frame drawn when nothing else follows + // could sit unpresented, and the window kept the layout it had before + // the compositor's first resize -- intermittently, on about a fifth of + // starts, with nothing left to flush it. + self.window.pre_present_notify(); output.present(); }