Skip to content

app: a window shows once it has drawn - #378

Merged
foxnne merged 1 commit into
mainfrom
app/show-once-drawn
Oct 13, 2026
Merged

foxnne merged 1 commit into
mainfrom
app/show-once-drawn

Conversation

@foxnne

@foxnne foxnne commented Oct 13, 2026

Copy link
Copy Markdown
Collaborator

Part of plans/RESTART_AND_CRASHES_PLAN.md (open items); the gap was found with the startup phases from #365.

What changes

The window never appears empty. fizzy showed its window at the end of AppInit, before the first frame. On a launch it appeared empty for the ~80 ms that frame took. In a restart's handover (#356), the new window covered the old one before it had drawn anything.

Now:

  • Shown unseen (platform.window.showUndrawn). A hidden window gets nothing to present into, so it can't simply draw first and show afterwards.
    • macOS: shown fully transparent (SDL_SetWindowOpacity 0, alphaValue).
    • Windows: shown cloaked (DWMWA_CLOAK): shown and composited, but not on screen. That avoids SDL's opacity, which would make the window layered, which win32_titlebar already rules out (it breaks native dialogs' input).
    • Elsewhere: shown as before.
  • The first frame is drawn into it and presented. It then asks for the next frame, so an idle launch is still revealed.
  • Revealed as the next frame begins (platform.window.reveal). The shown phase now comes after first frame, and the log says startup: shown N ms after main.
  • A handover's new instance signals "ready" only once its window is on screen. The old instance quits only after that, so the overlap is guaranteed by the order.
    • Moving "ready" after the first frame exposed a bug: restart.pollReleased ran in the new instance's first frame, with its wait deadline still zero. It deleted the handover folder and took the lock while the old instance still held it, so the new instance handed its own launch over and exited. Polling now starts only once "ready" has been signalled.

SDK impact

  • None

Verified

  • macOS:
    • Presented at opacity 0: the backend's Health counters show the first frame was presented while the window was transparent (presents 0 → 1 between the first frame's start and the reveal, skips unchanged). So what is revealed has been drawn.
    • A handover's new instance, its phases read through the agent: first frame at 525.5 ms, shown 541.7, handover ready 541.7, handover lock 566.0. Two handovers in a row each came up as a new instance that answered.
    • The fix above was found by a handover that stalled for 30 s: the new instance exited about 300 ms in, and the old one gave up.
    • Gates: zig build, zig build test (541/542, 1 skipped), test-integration (383/383), check-web, test-sdk-version.
  • Windows, Windows 11 ARM64 VM, an aarch64-windows-gnu build launched in the user's session, sampled every 50 ms: shown and cloaked at 3852 ms, uncloaked 136 ms later. It is never left cloaked.
  • Linux: CI. The window shows as before there.

🤖 Generated with Claude Code

fizzy's window used to be shown at the end of AppInit, before its first frame, so it appeared
empty for the ~80 ms that frame took (#365's phases measured it), and in a restart's handover the
new window covered the old one before it had drawn. Now it is shown unseen (macOS: transparent;
Windows: DWM-cloaked, not layered), draws its first frame, and is revealed as the next frame
begins. A handover's new instance signals ready only once it is on screen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@foxnne
foxnne enabled auto-merge (squash) October 13, 2026 14:49
@foxnne
foxnne disabled auto-merge October 13, 2026 14:58
@foxnne
foxnne merged commit 38222e3 into main Oct 13, 2026
9 of 10 checks passed
@foxnne
foxnne deleted the app/show-once-drawn branch October 13, 2026 14:58
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.

1 participant