Skip to content

Upgrade GPUI to wgpu 30 and speed up startup - #649

Merged
Havunen merged 79 commits into
devfrom
gpui-upgrade-startup-performance
Oct 21, 2026
Merged

Havunen merged 79 commits into
devfrom
gpui-upgrade-startup-performance

Conversation

@Havunen

@Havunen Havunen commented Oct 21, 2026 •

Copy link
Copy Markdown
Member

Combines #633, #645 and #648 into one changeset on top of dev, without #648's Linux renderer choice. Those three PRs are closed in favour of this one.

GPUI. Every gpui-ce crate is pinned to 1b6f3ced, the current head of gpui-ce main. It contains Havunen/gpui-ce#33, #34 and #35, the upstream sync, and the wgpu 30.0.1 upgrade (Havunen/gpui-ce#36).

  • DX12 pipelines are created from build-time shader bytecode, and a window whose DX12 renderer fails switches to DX11 in place. Smoothing shaders are deferred and DX12 startup runs in parallel. Vulkan shaders are precompiled to SPIR-V.
  • wgpu and Naga are now 30.0.1, with the font-kit revision feat: upgrade GPUI and add Linux renderer selection #648 asked for. License attribution is regenerated from the new lock.
  • The *_style style-builder renames are applied. The interaction-kit source guard now matches both the old and new names; with only the old names it had stopped seeing custom styling.

Windows renderer (#645). A runtime DX12 failure now leaves the app running on DX11, so it is recorded like a startup failure: DX11 is saved and no restart is requested.

Startup performance (#633).

  • Window creation overlaps repository bootstrap, and Git discovery is adopted before Home attaches.
  • Large-file tool (LFS/annex) discovery is deferred until a repository opens; obsolete probes are cancelled.
  • Remote tag discovery is separated from local repository loads.
  • Activating a tab refreshes history, selected paths and linked worktrees only when their metadata changed. Clean status snapshots skip line statistics, and empty submodule checkouts skip redundant status checks.
  • Status scans reuse filesystem and stat caches.
  • Installed-font scans are skipped for native system UI aliases.
  • Windows uses opaque surfaces and coalesces startup resizes.
  • Startup stages can be traced. docs/windows-startup-performance.md records the measurements, and scripts/profiling has the capture helpers.

Linux. #648's Auto/Vulkan/OpenGL setting is gone, along with its saved preference, restart and recovery paths, and docs. Linux uses GPUI's default renderer again; the gpui-ce side of that API was removed in Havunen/gpui-ce#36 before it merged. One part of #648 stays: when the main window fails to open, the message now includes the full error cause chain instead of advice to reboot.

Tests fixed here. None of these failures comes from the merge itself; #633's own CI already showed the first one.

  • repo_picker_row_action_says_so_when_the_repository_is_gone failed on every native job of Performance investigation #633's own CI. Performance investigation #633 makes the view report store errors that arrive before its window attaches, and the popover tests' fake repository failed submodule listing, so every view built on it started with an "Unsupported" toast. The fake now lists no submodules.
  • Performance investigation #633's ancestor_replaced_by_symlink_invalidates_cached_pass_and_next_pass_resolves_it moved the replacement link into place, which refreshes its ctime. The test's equal-stamp precondition then held only within one filesystem clock tick on Linux (22 of 200 local runs failed) and never on macOS (APFS keeps nanosecond ctimes). It now swaps the parent directory instead, which leaves the link's ctime alone, and asserts on the ancestor's own stamp entry: 0 of 200 runs failed. Keeping the old stamp (entry().or_insert) still fails it.
  • Four of Performance investigation #633's window_bootstrap tests compared tempdir paths with the store's canonical workdirs, so they failed on Windows (RUNNER~1 vs runneradmin). They now canonicalize their paths. A symlinked TMPDIR reproduced all four on Linux; with that TMPDIR, the full UI harness and nextest suite now pass.

Verification (local, Linux).

  • Workspace suite through scripts/ci/run.py with the syntax corpus present: 5,425 nextest tests passed (37 skipped). libtest: UI 3,796 passed (21 ignored), UI kit 524, media 76, terminal 108. Headless contexts: core 890, diagrams 84, state 1,446, backend 1,191, app 424, all passing.
  • Clippy with -D warnings: CI's three commands; --workspace --all-targets --all-features on Linux; the same for x86_64-pc-windows-gnu.
  • cargo fmt --check, source-size, identity-literal, boundary, grammar-symbol and license --check scripts, CI helper tests (145) and profiling tests (106).
  • Not run locally: macOS, Windows, and native display tests. CI covers those.

🤖 Generated with Claude Code

Run optional remote tag lookups on a lazy shared worker pool so an offline remote cannot delay local tags and reference metadata. Keep remote discovery tracked for coalescing without treating it as foreground readiness work.

Cover blocked network workers and load bookkeeping with regression tests; validated with the state test suite.
Compare bounded Git metadata snapshots on the monitor thread while retaining a working-tree refresh for missed filesystem events. Validate repository lifetime, load epoch and displayed revisions before applying the selective result, falling back to a full refresh when metadata or state cannot be trusted.

Reuse discovered watch inputs, deduplicate control-file reads and avoid recursively rescanning linked-checkout administration directories. Cover stale results, configuration and topology changes, scan budgets and unchanged activation with regression tests.
Show tab progress for opening, explicit reloads, history changes and user operations, while retaining a quiet tab when focus or filesystem checks refresh already-ready primary data.

Add spinner classification and window activation lifecycle regressions; validated with 53 window lifecycle tests.
Honor core.fscache with the platform default instead of disabling the per-walk filesystem cache. Retain refreshed stats for small indexes even below byte thresholds so clean filters are not restarted on every refresh, while preserving the large-index allocation guard.

Pass cancellation through supplemental gitlink status commands. Cover filter reuse, filesystem-cache parity, index guards and cancellation; backend suite passed with 390 tests. Paired local warm scans improved about 29% on GitComet and 73% on a 20,000-file fixture.
Add opt-in JSONL startup spans through launch, fonts, renderer selection and first usable content. Distinguish ready home and repository milestones so launch benchmarks do not stop at an empty window or wait for optional remote discovery.

Provide a PowerShell capture helper with isolated-profile support and graceful closure, a repository-load probe, and documented reporter instructions. Validate startup probes, launch-harness tests, trace output and real capture runs.
Treat the native System UI font alias as known on platforms that resolve it directly. Preserve catalog lookup on Linux and FreeBSD, where the alias needs resolution, and for user-selected installed families.

Cover catalog-free native startup and retain existing font-resolution tests. Local launch measurements show renderer initialization still dominates total startup.
Reconcile the merged dev ratchet with the new readiness instrumentation and background-refresh spinner coverage. Record the 266 added host lines explicitly while keeping the other nine structural limits unchanged.
Avoid the supplemental Git process only for positively empty, unchanged stage-zero gitlink directories. Recheck checkout and ancestor metadata on every scan; preserve Git fallback for staged changes, initialized or missing checkouts, sparse entries, conflicts, nested repositories and reparse points.

Replace native Ignore::All submodule setup with the equivalent no-op provider, since Git owns child status. Add all-lane parity and same-handle initialization regressions. All 393 backend tests passed; two five-pair release runs reduced warm GitComet status from about 101 ms to 56 ms (44%).
RepoActivated no longer prepares selected HEAD gitlinks or filesystem markers before the asynchronous monitor check. The resulting RepoExternallyChanged message still prepares the inputs its reducer uses, avoiding duplicate store-thread I/O on every focus.

Cover empty activation preparation, maintenance clock and throttling, and preserved selected-path inputs for full, index and worktree refreshes.
Integrate dev 89788be, retain its removal of obsolete source-budget ratchets, and migrate performance refresh tests to the shared backend fixture helpers.
Remove the orphaned fontdb scan span left by the deleted scanner and include GPUI font discovery in the remaining catalog span. Update the profiling guide to match the merged implementation.
Keep the blocked-writer assertion while allowing ten seconds for completion after releasing the shared session lock. Other parallel session tests can contend for that process-wide lock; include the writer label in timeout diagnostics.
# Conflicts:
#	crates/gitcomet-git-gix/src/repo/status.rs
Worker classification only needs the effective filter=lfs assignment. Use gix's selected-attribute lookup instead of resolving every attribute for every indexed path, preserving macro overrides and lazy nested/index attribute discovery.
Settle empty line counts and large-file rows inline after both fresh status lanes and their replays finish. Preserve the generation protocol so obsolete replies cannot publish or release newer work, while dirty snapshots continue through the backend.

Cover clean loads, dirty-to-clean transitions, replay and error recovery, and stale replies across subsequent dirty refreshes.
Fix the product-identity lint regression introduced by the remote metadata pool while preserving its default thread name and the existing literal exceptions.

Reproduced the failing PR #633 CI check locally; identity_literals.py now passes, all seven IdentityLiteralTests pass, and Rustfmt and diff checks pass.
Watch discovery retains lexical and resolved spellings of the same file. Share
control contents within each snapshot using the resolved existing-file path,
while preserving both lexical discovery entries and fresh reads next capture.

Proof on Windows: a portable parent/../ alias to one 3 MiB file failed before
because two reads exceeded the 4 MiB budget. It now reads/budgets exactly 3 MiB;
both discovery paths remain and subsequent edits invalidate the snapshot.
The original duplicate-control test and regression both pass. Final state
Clippy and 35 activation tests pass with this implementation unchanged.
Known symlinks and Windows junctions in discovery inputs previously made the
activation snapshot unavailable, disabling unchanged-focus narrowing. Record
link spelling and resolved target stamps so target edits and retargets remain
visible. Stamp ancestor targets nonrecursively; recursive links and unknown
reparse layouts retain the conservative fallback.

Proof: an unprivileged Windows junction regression failed before and now
checks stable snapshots, target edits, equal-byte retargeting, absent children,
and no traversal of unrelated data. Recursive rejection leaves an empty
snapshot. Final 35 activation tests and state Clippy pass; the full state suite
passed 1402 tests (7 ignored) before the final conservative recursive guard.
Two privilege-dependent symlink fixtures await native Unix CI.
Read the sole primary checkout from fresh configuration and refs when trust,
layout, ref storage and environment permit it. Return raw HEAD IDs without
loading commit objects. Preserve Git fallback for linked/unusual layouts and
propagate cancellation. The negative layout check borrows shared repository
paths and releases its lock before filesystem I/O.

Windows release proof: two reversed AB/BA sessions, 10 process pairs per case,
5 warmups and 15 measured calls each (100 processes, 1500 warm calls). Project
listing median fell 40.4844 -> 1.0428 ms; 20000-commit repo 40.4991 -> 0.9755 ms;
detached checkout 40.8603 -> 1.0001 ms, about 97.5% less time. Git commands per
eligible call fell 1 -> 0. Linked controls keep one Git command; paired confidence
intervals cross zero, so no linked speedup is claimed. Full porcelain rows and
index hashes/mtimes matched throughout. These are API timings, not app launch.

Validated exact integrated source: 16 focused tests; full backend suite 422
passed, 13 ignored; release Clippy with benchmarks and warnings denied; locked
production release builds. Raw evidence: target/performance-investigation/
iteration-4/worktrees-v3-analysis.json and candidate-v3-aligned-build.json.
Retain one source/projection/plan position in the details pane. Check source,
selection, view and order identities before resolving the split section or
materializing drawn order. Distinguish an authoritative missing file from an
unavailable owner so fallback behavior remains intact; clear on repo changes.

Windows release helper proof (48 cases, nine samples each): repeated selected-
end lookup at 50000 files fell 2.854713 -> 0.000033 ms combined and
7.279625 -> 0.000033 ms split. This measures exact extracted lookup/order
helpers versus the production cache, not complete UI frames.

Actual GPUI caller at 20000 entries confirms zero warm section/position scans,
projection/plan builds or order materializations. Combined-tree allocation
requests fell 422182 -> 6 bytes/call; split-tree 237774 -> 6 bytes/call. The
existing six-byte preference allocation remains. Four GPUI regression tests,
the caller probe, 54 helper correctness cases and UI Clippy pass.

Raw evidence: target/performance-investigation/iteration-4/footer-probe/
analysis-final.json with tested source/binary hashes and scope limitations.
Integrate dev 38b4973 after the separate scanner, worktree and footer fixes.
The status-position provider remains alongside the new sidebar file-history
commands without further integration edits.

Validation: merged UI Clippy with warnings denied; 77 selected GPUI tests
(4 footer, 50 file-history, 21 file-navigation, 2 status-navigation) passed.
Formatting, product identity and source-size checks pass. Backend, activation
scanner and cache helper remain byte-identical to their validated versions.
Wait on the child's existing Windows process handle for whole-millisecond
polls instead of sleeping after Git has exited. Retain sub-millisecond
sleeps and the error fallback, and preserve cancellation, silence deadlines,
process-tree cleanup, and separate pipe-worker draining.

Measured on this Windows computer with identical optimized build commands:
three same-binary control pairs, then two reversed-order sessions with five
pairs per case, five warmups and twenty samples per process. Median process
medians (baseline -> candidate):
- git --version: 39.46 -> 35.21 ms
- HEAD lookup: 39.61 -> 36.87 ms
- worktree list with activity context: 64.91 -> 62.30 ms
- parsed file list: 39.69 -> 37.23 ms
- stdin hashing: 39.83 -> 36.92 ms

Combined paired median savings were 2.37-4.41 ms; all five combined 95%
bootstrap intervals excluded zero, and observed pooled p95 improved. Both
sessions' medians improved, though the second session's worktree interval
crossed zero. The control differences were under 0.4 ms. No warm drain
sample exceeded 1 ms. All outputs and repository/index/ref stamps matched.
This measures Git-backed commands; the clean refresh profile runs no CLI
commands. CPU measurements were mixed and are not an improvement claim.

Validation: 52 shared-runner tests, six Win32 helper tests, affected-package
Clippy with warnings denied, workspace formatting, identity literals, source
layout, and git diff --check. Raw samples, frozen binaries, source hashes,
and commands are under target/performance-investigation/iteration-5.
Resolve only the parent of a nested link when asserting its discovery
identity. With macOS /var -> /private/var, discovery records that physical
nested-link spelling after resolving the ancestor; the original fixture
incorrectly expected its lexical alias.

Recreate the ancestor-link topology explicitly on all platforms, including
unprivileged Windows junctions. Retain checks for original leaf inputs,
outer/nested link identities, target edits, equal-byte retargeting,
recursive-link rejection, and exclusion of unrelated large files.

Proof: the same frozen original Windows junction test passes with ordinary
TEMP/TMP and fails at the CI assertion with child-only TEMP/TMP routed
through an owned junction. The corrected executable passes in that exact
environment and all 35 activation-related tests pass. State Clippy with
warnings denied and source formatting pass. Two privilege-dependent symlink
fixtures skip locally. All three macOS native CI jobs reproduced the old
assertion; fresh native CI will validate the corrected fixture.

Production code is byte-identical. Evidence and source hashes are preserved
in target/performance-investigation/iteration-5/activation-ci-proof.
Construct the spelling-only relative target using Path::new(".").join("second")
instead of the literal "./second". Windows stores the relative target without
normalizing its separators, leaving the old fixture unreadable when the
kernel follows it. Preserve the intended distinct spelling and equal target.

Assert the stored raw target, canonical target identity and readable chain
bytes before asking activation discovery to snapshot it. Keep the production
fallback strict for genuinely unreadable links.

Evidence: both Windows x64 and ARM64 CI jobs passed the first target change
and failed only after the old spelling-only replacement. Pinned Rust passes
the original target directly to CreateSymbolicLinkW; LLVM's Windows symlink
implementation explicitly normalizes target separators for this API.
All 35 activation-related tests and state Clippy pass locally. Native
file-symlink execution still requires CI confirmation because this computer
lacks symlink privilege; the fixture explicitly skips in that case.

Production code is unchanged. Source snapshots, separate diffs, primary
source links and reports are in
 target/performance-investigation/iteration-5/activation-ci-proof.
Havunen and others added 23 commits October 20, 2026 16:44
Fall back to an NTFS directory junction in the new cached-ancestor replacement
test when Windows denies symlink creation. Keep every identity, ABA, retry,
resolved-path, and fresh-pass assertion, and print an explicit executed marker.
The fixture command uses fixed relative names in its temporary directory.

An explicit nocapture rerun revealed that Windows privilege error 1314 had
previously bypassed this test body. The fallback now executes the complete
race on this host as directory-junction. All six policy tests pass; production
code is unchanged.
Format each startup trace and allocation payload into a complete JSON line
before write_all. Preserve immediate trace visibility, successful stderr
flushing, JSON escaping, timestamps, and every existing readiness predicate.
Also record startup milestones on the UI journal's anchored clock before
synchronous diagnostic output.

A Windows experiment using the real serde_json formatter counted 59 writes
per stderr payload and 44 per trace record. With PowerShell redirection, the
interval between adjacent callback markers reached 29.709 ms without GPUI.
Batching both records reduced the median from 3.406 to 0.079 ms. These are
isolated logging measurements, not a normal application startup speedup.

Regression coverage checks complete escaped JSON, a single full-write sink
call per record, flushing, timestamp preservation, and file visibility before
shutdown. Focused ci-test validation passed all three startup_trace tests and
all eight startup_probe tests; formatting and whitespace checks passed.
Production application timings remain tied to the frozen 8bd0600
binary; its rendering and readiness behavior is unchanged by this fix.
Record the opaque surface, coalesced resize, lazy filter, early Home adoption,
deferred optional-tool, and validated watcher-metadata changes with their
source revisions, frozen executable/PDB hashes, and test evidence.

The later five-pair DX12 confirmation improves every pair: median draw
481.0 -> 440.5 ms, submission 561.2 -> 481.0 ms, and repository readiness
566.7 -> 495.8 ms. Only three candidates are ready below 500 ms; the range is
466.5-512.5 ms. Preserve the earlier 707.2 ms candidate regression separately,
because its slow renderer state was not reproduced or causally explained.

Include valid CPU/graphics evidence, separate watcher and Home diagnostics,
the measured diagnostic-output overhead, and the bounded 3/4/6/8-worker
experiment that does not justify changing the three-participant limit.
Keep app callback, process-spawn, and ETW clock origins distinct, and identify
status, renderer work, and observer polling as separate remaining costs.
Moves gpui-ce to Havunen/gpui-ce#35, which creates DX12 pipelines from
build-time shader bytecode and switches a failing DX12 window to DX11 in
place, and brings in #33 and #34.

- Migrate the style builders deprecated by #34 to their *_style names.
- Teach the interaction-kit source guard the *_style names; it matched only
  .hover(/.active( and so stopped seeing custom styling after the rename.
- A runtime DX12 failure now leaves the app running on DX11, so record it
  like a startup failure: save DX11 without asking for a restart.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cargo audit now fails on RUSTSEC-2026-0382 (rustls-webpki 0.103.15 may
accept distrusted intermediates) and RUSTSEC-2026-0381 (webpki-roots 1.0.9
trusts distrusted emSign intermediates). Take the patch releases that fix
both, and record their versions in the open-source license list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The previous run failed only in tests that also fail intermittently on
unrelated PRs (the conflict-resolver bar tests on Windows) and in Linux
nextest suites that pass on ubuntu22-x64 and locally.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The GPUI pin is replaced by the final gpui-ce pin later on this branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The GPUI pin is replaced by the final gpui-ce pin later on this branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Linux goes back to GPUI's default renderer: no Auto/Vulkan/OpenGL setting,
no saved preference, and no restart or recovery path for it. The Windows
DirectX selector is unchanged. A window that fails to open still reports
its full error cause.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pin every gpui-ce crate to 1b6f3ced: gpui-ce main (precompiled DX12
shaders, deferred smoothing shaders, disabled interactivity and the
upstream sync) merged with the wgpu 30.0.1 upgrade, which no longer has
a Linux renderer choice. Regenerate the license attribution from the
new lock.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since #633 the view reports store errors that arrive before its window
attaches. The popover tests' fake repository inherited the backend
default, which fails submodule listing, so every view built on it
started with an "Unsupported" error toast and
repo_picker_row_action_says_so_when_the_repository_is_gone saw two
toasts instead of one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Moving the replacement link into place refreshes its ctime on Unix, so
the stamp cached before the move equals the replacement only when both
land in the same filesystem clock tick. The test asserted that
precondition and failed in 22 of 200 local runs. Retry the setup until
a pass establishes it, then make the same assertions: 0 of 200 failed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Retrying across clock ticks never helps on macOS: APFS keeps
nanosecond ctimes, so moving the link always changes its stamp and all
50 attempts failed on every macOS CI job. Build the link inside a
staging directory and swap the parent instead. Moving a directory
leaves its children's ctimes alone, so the cached stamp equals the
final link on every filesystem. The swapped parent is invalidated too,
so assert on the ancestor's own entry: it must hold the ordinary
directory the pass saw.

0 of 200 local runs failed, and keeping the old stamp
(`entry().or_insert`) fails the new assertion.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The store canonicalizes workdirs, so on Windows (8.3 RUNNER~1 temp
paths) four #633 bootstrap tests waited for a workdir that never
matched and timed out, or compared unequal spellings. A symlinked
TMPDIR reproduces all four on Linux; with canonical paths they pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Havunen
Havunen merged commit 383f2f9 into dev Oct 21, 2026
22 checks passed
@Havunen
Havunen deleted the gpui-upgrade-startup-performance branch October 21, 2026 16:28
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