Skip to content

GPU: build effect kernels on first use, each family from a module of its own (fixes #1590) - #1705

Open
00xJS wants to merge 2 commits into
storytold:mainfrom
00xJS:gpu/lazy-effect-kernels
Open

00xJS wants to merge 2 commits into
storytold:mainfrom
00xJS:gpu/lazy-effect-kernels

Conversation

@00xJS

@00xJS 00xJS commented Oct 17, 2026

Copy link
Copy Markdown

Problem

GpuContext::new compiled one ~460K-character WGSL module and built every compute pipeline from it up front: ~20 compositor kernels and ~208 effect kernels. On the web, the page and its frame workers each create a device and do this. Dawn compiles the module again for each pipeline, so Chrome's GPU process stays busy past its watchdog and the device is lost about 60 s after every load (#1590). On desktop DX12 with FXC, the same up-front build keeps the window from appearing for about a minute (#613).

Change (crates/gpu/src/context.rs)

  1. Effect kernels are built on first use. The compositor's own kernels (ENTRIES, sky, EXT_ENTRIES) are still built with the context and still required. Effect kernels sit in effect_pipelines (OnceLock<Result<ComputePipeline, String>>) and are built the first time Enc::dispatch_ext asks for one, inside the same init_resource error scopes, so a kernel a compiler rejects still renders its effects on the CPU (Enc::take_missing). unavailable_kernels() lists the failures so far, and build_all_kernels() builds the rest.
  2. Each kernel family is a module of its own. Each FAMILIES entry is common.wgsl + kernels.wgsl + its own file, plus the files it borrows functions from (fx_color→fx_lut, fx_key→fx_distort, fx_extra→fx_generate, fx_pixel2→fx_stylize, fx_particles→fx_stylize+fx_sim). That's at most ~120K characters, built with the family's first kernel. The compositor's kernels, sky and classic3d each get a small module (52–67K).

New test effect_kernels_build_on_first_use: no effect kernel is built with the context, and once asked for, every one builds on the device, so a kernel no test dispatches still can't break without anyone noticing. effects_whose_kernels_failed_render_on_the_cpu now expects a sorted list.

Results (Windows 11, AMD Radeon RX 6900 XT, Adrenalin 32.0.21045.5002)

Chrome 155.0.8059.80, release web build (cargo xtask web), served on 127.0.0.1 with COOP/COEP, fresh profile every run, 100 s each, page pinged every 2 s over the DevTools protocol:

build device frame workers
main 8f02f52 lost at 64.7 s ("A valid external Instance reference no longer exists") CPU only after the loss
main, ?nogpuworkers lost at 60.7 s —
first-use kernels only (one big module) lost at 64.9 s CPU only after the loss
first-use kernels only, ?nogpuworkers kept —
this PR (2 runs) kept for 100 s both on the GPU (frame worker 0/1: GPU (BrowserWebGpu) at ~30 s)

So first-use builds alone fix the page's own device, but the frame workers' devices still overran the watchdog until the module was split.

Native (cargo test -p effectcraft-gpu --lib --release, EFFECTCRAFT_REQUIRE_GPU=1):

context ready all 208 effect kernels suite result
Vulkan, main — — 77.5 s 209 passed, 3 failed
Vulkan, this PR 0.27 s 1.0–1.4 s (warm driver cache; 22.9 s cold) 44–70 s 212 passed, the same 3 failed
DX12/FXC, #1571 ~66 s — 1305.8 s 208 passed, 5 failed
DX12/FXC, #1571 + first-use kernels 1.96 s 64.2 s 251.9 s 209 passed, the same 5 failed

The 3 Vulkan failures (posterize, ccgridwipe, cchextile) fail identically on main (#1605). On current main, DX12 can't start without #1571 (FXC rejects classic3d), so the DX12 rows are measured on top of #1571. The split module isn't in those rows; it should only make FXC faster, since each pipeline compiles less HLSL.

Trade-off

The first frame that uses an effect family pays for compiling its module and kernel: a few ms per kernel on Vulkan with a warm cache, and about 300 ms per kernel on FXC. A host that would rather pay up front can call build_all_kernels() off the first frame.

Notes

🤖 Generated with Claude Code

00xJS and others added 2 commits October 16, 2026 17:20
… GPU context

GpuContext::new built every compute pipeline up front: the compositor's own
kernels and ~208 effect kernels. In Chrome on Windows that kept the GPU
process busy past its watchdog, so the device was lost ~60 s after every load
(storytold#1590); on DirectX 12 with FXC the window took over a minute to appear (storytold#613).

The compositor's kernels are still built up front and required. Effect kernels
are built the first time a dispatch asks for one, inside the same error scopes,
so one a compiler rejects still renders its effects on the CPU. unavailable_kernels
lists the failures so far; build_all_kernels builds the rest.

RX 6900 XT, Windows: Vulkan context 0.27 s, all effect kernels 0.9 s, GPU tests
77 s -> 26 s; on storytold#1571's DX12/FXC, context ~66 s -> 1.96 s, tests 1306 s -> 252 s,
same results.

Co-Authored-By: Claude <noreply@anthropic.com>
Building effect kernels on first use was not enough in Chrome: the page and
its frame workers each create a device, every one compiled its required
kernels against the single ~460K-character module, and Dawn compiles the
module again for each pipeline, so the GPU process still passed its watchdog
and the device was lost ~64 s after load (RX 6900 XT, Chrome 155).

Each kernel family (FAMILIES) is now common.wgsl + kernels.wgsl + its own file
and the files it borrows functions from (at most ~120K characters), built with
the family's first kernel; the compositor's kernels, sky and classic3d use
small modules of their own.

Chrome 155 on Windows / RX 6900 XT, release web build, default flags: main
loses the device at ~64 s (60.7 s with ?nogpuworkers); with this, two 100 s
runs keep it, and both frame workers get the GPU. Native Vulkan tests
unchanged (212 passed, the 3 failures that main has, storytold#1605).

Co-Authored-By: Claude <noreply@anthropic.com>
@00xJS

00xJS commented Oct 19, 2026

Copy link
Copy Markdown
Author

Follow-up check in Chrome with real work after load, not just an idle page.

Windows 11, RX 6900 XT, Chrome 155.0.8059.80, release cargo xtask web builds served on 127.0.0.1 with
COOP/COEP, a fresh Chrome profile per run, and a ping every 2 s over DevTools for 110 s. At 45 s the
probe runs file.openDemoProject, then effect.apply Gaussian Blur on layers #1–#3, then time.set
to 0.5, 1.0 … 3.0 s (2 s apart), and reads effectcraft.info().

build device frame workers worker frames after demo + blur + 6 scrubs
main (8f02f52a) lost at 60.6 s (during the scrub) CPU only after the loss ("webgpu found no adapters") 0 rendered, compositor null
this PR (2dd3a707) kept for 110 s, page never unresponsive GPU (BrowserWebGpu) from 17.4 s 20 rendered, 17 on the GPU, 0 failed, last 15 ms

Earlier runs (2026-10-16): main lost at 64.7 s (60.7 s with ?nogpuworkers); first-use kernels without
the module split lost at 64.9 s; the split kept the device in two 100 s runs.

So with this PR the page device and both frame workers stay on the GPU while effects render.


Generated with Claude Code

@ded-achtung

Copy link
Copy Markdown

Native results on NVIDIA, as a second vendor next to your RX 6900 XT: RTX 4070 Laptop (Ada, driver 596.21), Windows 11, head 2dd3a707. cargo test --release -p effectcraft-gpu, EFFECTCRAFT_REQUIRE_GPU=1, WGPU_BACKEND=vulkan:

  • effect_kernels_build_on_first_use: passes. built 208 effect kernels in 9.99s, the first build of these kernels on this machine.
  • Full suite: 215 passed, 0 failed, 29.1 s.

Not tested: DX12 (it needs #1571, which touches context.rs too), the web build in Chrome, and the desktop app's start-up time.

🤖 Generated with Claude Code

@00xJS

00xJS commented Oct 21, 2026

Copy link
Copy Markdown
Author

@ded-achtung thanks for the RTX 4070 run. That gives the first-use build a second vendor (215/215 on Vulkan).

A note for whoever merges #1571 and this PR: they conflict in GpuContext::new, because #1571 makes classic3d optional and this PR changes how kernels are built. To save the second merge from redoing that, here's the resolved version on top of #1571's current head ce551848:

https://github.com/00xJS/effectcraft/tree/gpu/lazy-effect-kernels-on-1571 (one merge commit, d3f8da17)

  • classic3d is built with the context, as in GPU: effect kernels build and match the CPU on DirectX 12 (#613) #1571. If it fails, it's recorded as an unavailable kernel and its 3D runs render on the CPU; the compositor doesn't stop. Effect kernels stay first-use, each family from its own module.
  • has_kernel asks the pipeline lookup, so it also sees first-use kernels.
  • dx12_kernel_report calls build_all_kernels() before listing failures. Otherwise it would report 0 without having built anything.

On the RX 6900 XT: the Vulkan suite passes apart from the three #1605 / #1776 failures that main has, and the first-use test and all 8 tests_3d pass. DX12 + FXC tests_init passes 5/5, including the ignored ones, and dx12_kernel_report shows 0 unavailable.

If #1571 lands first, I'm happy to rebase this PR onto it so it's a normal fast-forward-able branch.


Generated with Claude Code

This branch has not been deployed

No deployments
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