Skip to content

fix(display): rebuild the GL window surface on resize (CI GUI wipeout) - #400

Merged
eval-exec merged 4 commits into
mainfrom
render/present-contract
Sep 19, 2026
Merged

eval-exec merged 4 commits into
mainfrom
render/present-contract

Conversation

@eval-exec

Copy link
Copy Markdown
Owner

Root cause

CI runners have no Vulkan, so every GUI scenario presents through wgpu's GL backend — whose emulated swapchain lands a stale-geometry buffer after a window resize. A new minimal present-path contract test (bare winit+wgpu, no editor) proves it: 0.67 blue after a 480×360 resize from 320×240 — exactly the stale-height fraction — while the identical test passes on Vulkan (incl. lavapipe). This was the root of the long-running GUI wipeouts that scheduler/ingest logs kept exonerating.

The fix

  • GuiFrameNativeWindowState records the surface's backend (wgpu surfaces don't expose it)
  • handle_resize routes through one policy: GL rebuilds the window surface from the instance at the new geometry; every other backend reconfigures in place (unchanged)
  • The contract test encodes the same policy, guarding both paths on whatever backend the host selects

Verification

Test Vulkan GL (CI's path)
present contract ✅ (was 0.67 blue)
neomacs idle resize ✅ (was the wipeout)
ghost-presentation resize

Also on this branch: sway session extraction into neomacs-infra::display, LC_ALL=C.UTF-8 pinned per display session, frame-outcome/occlusion logging, and the GSettings failure probe.

The CI GUI wipeout distilled to its smallest witness: a bare
winit+wgpu window that presents red, resizes, and presents blue must
show blue at the new geometry.  It passes on Vulkan (incl. lavapipe)
and fails on the GL backend -- 0.67 blue after resize, exactly the
stale-height fraction -- with no editor, redisplay, or scheduler
involved, so the fault is in the GL present path itself and the fix
belongs behind a backend quirk, not in the scheduler.

The backend follows WGPU_BACKEND exactly as CI selects it, the window
runs on the infra harness's isolated Xvfb (whose session env becomes
the process env, because an in-process event loop must not see the
operator's Wayland), and captures go through import like the rest of
the suite.  The session outlives wgpu teardown: dropping it earlier
trips Xlib's fatal IO handler and takes the process with it.
wgpu's GL backend emulates the swapchain, and after a window resize
its presents land a stale-geometry buffer: the new present-path
contract test measures exactly the stale-height fraction (0.67 blue
after a 480x360 resize from 320x240) with no editor involved, while
the same test passes on Vulkan including lavapipe.  CI runners have
no Vulkan, so every GUI scenario there presents through GL -- the
root of the multi-month GUI wipeouts that the scheduler and ingest
logs kept exonerating.

GuiFrameNativeWindowState now records the surface's backend (wgpu
surfaces do not expose it) and handle_resize routes through one
policy: GL rebuilds the window surface from the instance at the new
geometry, every other backend reconfigures in place as before.  The
contract test encodes the same policy so it guards both paths on
whatever backend the host selects.
Copilot AI balanced review requested due to automatic review settings September 19, 2026 05:11

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

It changes the GUI frame present/resize path and its correctness depends on runtime GPU backend behavior that cannot be verified without executing the GUI tests, so it warrants human GUI verification.

Review effort: Balanced
Findings: 1 Medium severity · 1 Low severity

Open (2)
What changed in this PR

This PR fixes a long-standing CI GUI "wipeout" where, on runners without Vulkan, wgpu's GL backend presents a stale-geometry buffer after a window resize because its emulated swapchain does not survive a reconfigure-in-place. The fix records each surface's backend and, on GL only, rebuilds the window surface at the new geometry during resize; all other backends keep reconfiguring in place. A new bare winit+wgpu contract test encodes and guards the same policy on whichever backend the host selects.

Changes:

  • Add surface_backend to GuiFrameNativeWindowState and route resize through surface_configure_or_rebuild, which rebuilds the GL surface but reconfigures every other backend in place.
  • Thread wgpu::Instance into handle_resize (needed to recreate the surface) and update the caller and both construction sites.
  • Add a self-contained present_contract integration test (Xvfb + winit + wgpu) that renders, resizes, and asserts the second frame shows at the new geometry.
File Description
crates/​neomacs-display-runtime/​src/​render_thread/​frame_windows.rs Adds surface_backend field + surface_configure_or_rebuild; GL rebuilds surface on resize, others reconfigure in place.
crates/​neomacs-display-runtime/​src/​render_thread/​surface_resize.rs Passes gpu.instance alongside gpu.device into the updated handle_resize.
crates/​neomacs-display-runtime/​src/​render_thread/​bootstrap.rs Populates surface_backend from adapter_info.backend at the primary-window construction site.
crates/​neomacs-gui-tests/​tests/​present_contract.rs New present-path contract test that reproduces/guards the GL resize quirk.
crates/​neomacs-gui-tests/​Cargo.toml Adds winit/wgpu/raw-window-handle/pollster deps for the new test.
Cargo.lock Records the new dependency edges for neomacs-gui-tests.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread crates/neomacs-gui-tests/tests/present_contract.rs Outdated
Comment thread crates/neomacs-gui-tests/Cargo.toml Outdated
Review follow-ups: the app no longer owns (and never drops) the
DisplaySession -- that was a leak of one Xvfb per run.  It carries
only the session's env pairs; the session stays in test scope, where
it outlives the event loop's teardown (dropping it inside run_app
trips Xlib's fatal IO handler, which exits the process instead of
unwinding) and still cleans up deterministically at test end.  The
winit/wgpu/pollster deps move to dev-dependencies per crate
convention, and the unused direct raw-window-handle dependency goes:
the contract reaches the handle types through winit.
@eval-exec

eval-exec commented Sep 19, 2026

Copy link
Copy Markdown
Owner Author

Both review findings are addressed in 427e988:

  1. Xvfb leak (medium) — the contract app no longer owns the DisplaySession; it carries only the session's env pairs. The session stays in test scope, where it outlives the event loop's teardown (dropping it inside run_app trips Xlib's fatal IO handler, which exits the process rather than unwinding) and still cleans up deterministically at test end. Verified: a fresh run leaves zero Xvfb processes behind.
  2. dev-dependencies (low) — winit/wgpu/pollster moved to [dev-dependencies]; the unused direct raw-window-handle dependency is dropped.

The contract is re-verified green on both GL (CI's path) and Vulkan. @copilot please re-review.

The previous commit moved winit/wgpu/pollster into neomacs-gui-tests'
dev-dependencies but staged only the manifest; the lockfile encoding
that move stayed local, so every --locked CI job (check-dependency-
coherence first among them) refused to run.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

The change is backend-specific GPU surface-lifecycle code validated mainly through GL/Vulkan-dependent GUI tests, and the branch description references further unshown changes, so it warrants human verification alongside fixing the stale Cargo.lock entry.

Review effort: Balanced
Findings: None

Resolved since last review (2)

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

The change alters the GPU surface present/resize hot path with backend-specific behavior whose correctness depends on real GL/Vulkan runtime results that cannot be fully verified statically.

Review effort: Balanced
Findings: None

@eval-exec
eval-exec merged commit 8d08ab1 into main Sep 19, 2026
157 of 255 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants