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 <noreply@anthropic.com>
This commit is contained in:
irisandClaude Opus 5 committed 2026-09-04 19:10:50 -04:00
1 parent 3fc224b584
commit 9b331a5e93
2 files changed
+34 -21

No files matched your search

+27 -21
View File
@@ -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