Skip to content

GPU compositor can't start on DirectX 12 (FXC fails on the pointwise kernel) #613

Open

Description

@KattKnight

What happens

On Windows, when egui-wgpu's device is DirectX 12, GpuContext::new fails while building the compute pipelines. The app then quietly composites on the CPU. session.accel_note reads:

GPU compositor unavailable: GPU initialization (pointwise): Validation Error
  In Device::create_compute_pipeline, label = 'pointwise'
    Internal error: FXC D3DCompile error (Unspecified error (0x80004005)):
    …effectcraft kernels(69,12-24): warning X3571: pow(f, e) will not work for negative f, …
    internal error: compilation aborted unexpectedly

Where seen

  • Windows 11, EffectCraft main at 1446e27 (10 October 2026), on a laptop with an RTX 4060 Laptop GPU.
  • First seen on WARP (Microsoft Basic Render Driver) on DirectX 12. That is what egui_kittest picks in ui_gpu.
  • Reproduced in the desktop app by starting it with WGPU_BACKEND=dx12. render.backend then reports "active": "cpu" and the error above as why.
  • Started normally, the same build picks Vulkan (NVIDIA GeForce RTX 4060 Laptop GPU (Vulkan)) and the GPU compositor works.
  • So any setup where the window's device lands on DirectX 12 should hit it, for example a machine without a working Vulkan driver.

Why

FXC (the legacy D3D shader compiler wgpu uses for DirectX 12 by default) aborts on the HLSL that naga generates for pointwise. DXC usually compiles such kernels.

Possible directions (maintainers' call)

  • Select DXC for DirectX 12 in the window's InstanceDescriptor (backend_options.dx12.shader_compiler). This needs DXC shipped or statically linked.
  • Prefer Vulkan over DirectX 12 for the window when both are available.
  • Or find which construct in pointwise trips FXC and work around it there.

🤖 Generated with Claude Code

Activity

  1. 7n4xt commented on Oct 10, 2026

    @7n4xt

    I am experiencing the same issue, with the same RTX 4060 laptop GPU on the 0,7 version of Effectcraft

  2. bflatastic commented on Oct 10, 2026

    @bflatastic
    Member

    Thanks for the precise report, @KattKnight! #667 (on main) makes this non-fatal. An effect kernel that fails to build on a backend (here pointwise under FXC on DX12) no longer switches off the whole GPU compositor. The compositor starts, and only the effects that need that kernel render on the CPU, matching the CPU output. The FXC incompatibility itself still needs bisecting on a Windows/DX12 machine, so we're keeping this open. If you can share the FXC error text from the log, that would help a lot.


    Generated by Claude Code

  3. KattKnight commented on Oct 10, 2026

    @KattKnight
    ContributorAuthor

    Thanks! Here's the FXC text, plus a bit more from this Windows/DX12 machine, because on main the compositor still doesn't start on DX12.

    1. On main (d024a16, with #667), DX12 still falls back to the CPU

    A small probe that calls GpuContext::request() with WGPU_BACKEND=dx12 (RTX 4060 Laptop, Windows 11):

    compositor: FAILED
    GPU initialization (classic3d): Validation Error
      In Device::create_compute_pipeline, label = 'classic3d'
        Internal error: FXC D3DCompile error (Unspecified error (0x80004005)):
        effectcraft kernels(1043,13-37): warning X3550: array reference cannot be used as an l-value; not natively addressable, forcing loop to unroll
        effectcraft kernels(1060,5-15): error X3511: unable to unroll loop, loop does not appear to terminate in a timely manner (24 iterations) or unrolled loop is too large, use the [unroll(n)] attribute to force an exact higher number
        effectcraft kernels(1147,5-15): error X3511: forced to unroll loop, but unrolling failed.
        effectcraft kernels(1346,13-23): error X3511: forced to unroll loop, but unrolling failed.
        effectcraft kernels(1298,5-15): error X3511: forced to unroll loop, but unrolling failed.
    

    pointwise is optional now, but classic3d is in EXT_ENTRIES, which are still required (the ? on the ENTRIES/EXT_ENTRIES collect). So the build now stops there instead. With WGPU_BACKEND=vulkan the same build starts.

    2. The FXC text for pointwise

    (91,18-29): warning X3092: unary negate of unsigned value is still unsigned
    (332,12-26): warning X3571: pow(f, e) will not work for negative f, use abs(f) or conditionally handle negative values if you expect them
    internal error: compilation aborted unexpectedly
    compilation failed; no code produced
    

    That's all FXC prints: the abort comes with no error code.

    3. All 224 kernels through FXC: 12 fail

    I compiled every compute entry point in the module the way wgpu does on DX12:

    • naga 30.0.1 (the version in Cargo.lock) writes the HLSL for one entry point with wgpu's default options;
    • fxc /T cs_5_1 /Ges (ENABLE_STRICTNESS, as in wgpu-hal's compile_fxc), Windows SDK 10.0.26100.
    Kernel File FXC
    classic3d classic3d.wgsl X3511 can't unroll loop (required kernel)
    pointwise kernels.wgsl internal error: compilation aborted
    fxc_point fx_color.wgsl internal error: compilation aborted
    fxc_kl_final fx_color.wgsl X3500 array reference can't be an l-value
    fxd_point fx_depth.wgsl internal error: compilation aborted
    fg2_segs fx_gen2.wgsl X3511 can't unroll loop
    fxk_point fx_key.wgsl internal error: compilation aborted
    fxl_point fx_lut.wgsl X3511 can't unroll loop
    fxn_median_huang fx_noise.wgsl X3511 can't unroll loop
    fxt_point fx_tone.wgsl internal error: compilation aborted
    fxv_gen fx_vr.wgsl internal error: compilation aborted
    fxw_warp fx_warp.wgsl X3507 fxw_style: not all control paths return a value

    Turning off naga's force_loop_bounding and restrict_indexing doesn't fix any of them.

    All 12 compile with DXC (dxc -T cs_6_0 -Ges, naga at shader model 6.0).

    4. Shipping DXC fixes it with no code change

    wgpu 30's default Dx12Compiler::Auto uses DXC whenever it can load dxcompiler.dll. I checked this on the same probe build, on DX12:

    • without dxcompiler.dll, FXC is used and the compositor fails at classic3d (above);
    • with the SDK's dxcompiler.dll (1.8.2502) next to the exe, it starts, and every kernel builds (unavailable_kernels() is empty).

    So either option fixes this:

    • ship dxcompiler.dll with the Windows build. wgpu needs 1.8.2502 or newer; the redistributable is on DirectXShaderCompiler's releases;
    • or enable wgpu's static-dxc feature. wgpu doesn't offer it on aarch64-pc-windows, so the ARM64 build would need the DLL or FXC.

    5. The pointwise abort, bisected

    It's an FXC bug. A vector component written with a runtime index, inside a loop, inside a switch case, aborts the compiler. This is enough:

    switch P.u[0].x {
        case 1u: {
            var o = vec3<f32>(0.0);
            for (var i = 0; i < 3; i++) { o[i] = 2.0; }
            px = vec4<f32>(o, 1.0);
        }
        default: {}
    }

    The same loop compiles inside an if, and so does a read (c[i]) or a write to an array<f32, 3>. Moving the loop into a helper function doesn't help, because FXC inlines it. In pointwise, cases 2, 4, 5 and 8 each hit it. With o as array<f32, 3> in those four cases (8 lines changed, same math), pointwise compiles under FXC. The other five aborts (fxc_point, fxd_point, fxk_point, fxt_point, fxv_gen) each have the same ingredients (a switch, loops and indexed writes), but I haven't bisected them one by one.

    So the options as I see them, your call:

    1. DXC (the DLL or static-dxc): fixes all 12 at once.
    2. WGSL workarounds for FXC: one per pattern. This is easy for the switch + loop ones like pointwise; classic3d and the unroll failures need their loops reworked.
    3. Make classic3d optional like the effect kernels, so the compositor at least starts on FXC.

    Happy to open a PR for whichever you prefer.

    🤖 Generated with Claude Code

  4. didpublishing commented on Oct 10, 2026

    @didpublishing
    Contributor

    @KattKnight thanks for the thorough write-up: the per-kernel FXC table, the DXC check and the switch + loop bisect make this very tractable.

    I'd like to help without duplicating your work, so before writing any code: are you already working on a PR for this, and if so for which option? I'm on Windows 11 with an RTX 3060, so I can run DirectX 12 with WGPU_BACKEND=dx12 and check results on a second machine.

    If you're not planning to, this is how I'd split the options (the first needs a maintainer decision):

    1. DXC fixes all 12 kernels with no shader change. It's a packaging call for @bflatastic: ship dxcompiler.dll with the Windows build (worth checking whether dxil.dll is needed too, for validation on other machines), or static-dxc, which builds C++ and isn't available on aarch64-pc-windows, so it sits uneasily with the Pure Rust rule and the ARM64 build.
    2. WGSL workarounds for FXC keep the build pure Rust and work with no extra files, and they would also help anyone without the DLL. I could take the switch + indexed-write pattern first: pointwise (cases 2, 4, 5, 8 with array<f32, 3>), then fxc_point, fxd_point, fxk_point, fxt_point and fxv_gen, bisecting each as you did, with the CPU output as the oracle for each kernel.
    3. classic3d optional, so the compositor starts on FXC even before the loop-unrolling kernels (classic3d, fg2_segs, fxl_point, fxn_median_huang) are reworked. That one probably belongs with whoever owns the required-kernel list from Fix community-reported issues: Light theme contrast, macOS Sync with System, Timeline eyedropper, DX12 kernel fallback #667.

    My suggestion would be 3 plus 2 for the switch kernels as the quick win, with 1 decided separately. I won't start until I've heard from you, so we don't both open a PR. If you'd rather do it all yourself, I'm happy to review and test on DX12 here instead.

  5. added 4 commits that reference this issue on Oct 10, 2026
  6. didpublishing commented on Oct 11, 2026

    @didpublishing
    Contributor

    Confirming on a second machine, plus one thing for the DXC option. Windows 11, RTX 3060 (driver 32.0.16.1060), main at 21681e9 (includes #667), release build of effectcraft-cli, WGPU_BACKEND as shown, info --gpu (it calls Gpu::try_headless()):

    Run Result
    vulkan the compositor starts
    dx12, no DXC fails at classic3d: FXC X3550 / X3511 "forced to unroll loop, but unrolling failed", the same errors as in the comment above. pointwise is optional since #667, so the build now stops at the first required kernel
    dx12, dxcompiler.dll 1.9.2607 next to the exe starts, and bench --gpu renders on NVIDIA GeForce RTX 3060 (Dx12) across the demo comps, including the 3D ones that use classic3d; GPU vs CPU differs by 0.000 to 0.008% of pixels
    dx12, the Windows SDK's old dxcompiler.dll 1.5.0 next to the exe a different failure: no device: Parent device is lost

    So your DXC result reproduces here. The last row matters for the DXC option: wgpu picks up any dxcompiler.dll it can load, and a stale one is worse than none. Without a DLL the app falls back to the CPU compositor cleanly; with an old one device creation fails outright. If DXC is shipped, the version needs to be pinned (your 1.8.2502 minimum is right, and 1.9 works), and it's worth checking what happens when an older copy is also on the DLL search path.

    Details and caveats: the 1.9.2607 DLL came from a Chromium-based browser installed on this machine, used only in a scratch folder to test (I'm not suggesting redistributing it). That browser ships no dxil.dll, so that run paired it with the SDK's old dxil.dll (101.5.x), which suggests dxil.dll doesn't have to match, but I only tried that one combination. I tested only the CLI path, not the desktop window, and haven't touched the WGSL workarounds. Still holding off on code until I hear whether you're already on a PR.

  7. KattKnight commented on Oct 11, 2026

    @KattKnight
    ContributorAuthor

    @didpublishing thanks for checking it on a second machine, and for the old-dxcompiler.dll result: I hadn't tried a stale copy, and "worse than none" is worth knowing before anyone ships the DLL.

    I'm not working on a PR for this, so please go ahead with your plan: 3 (classic3d optional) plus 2 for the switch kernels. That sounds right to me. Option 1 is @bflatastic's packaging call.

    I'm happy to test your PR here on DX12 (RTX 4060 Laptop, Windows 11), in both the CLI and the desktop app with WGPU_BACKEND=dx12, against the CPU output. Tag me when it's up.

  8. davidlilja commented on Oct 11, 2026

    @davidlilja

    One more data point from the desktop app (not the CLI), because the FXC failure on classic3d also makes the app look like it never starts.

    Setup: EffectCraft 0.8.0, effectcraft-0.8.0-windows-x64-portable.zip (sha256 02fb4491…b0d2), Windows 11 Pro 26H2 (build 26300), NVIDIA GeForce RTX 5060 Ti, driver 616.92. RUST_LOG=info, fresh EFFECTCRAFT_CONFIG_DIR for every run, window visibility polled once a second with EnumWindows + IsWindowVisible on the Window Class window.

    Backend Shader compiler Window visible after GPU compositor
    Vulkan (what Automatic picks here) n/a 3 s starts
    DX12 DXC (dxcompiler.dll + dxil.dll found on the user PATH, see below) 11 s starts
    DX12 FXC (d3dcompiler_47.dll, no DXC reachable) 69 s unavailable, classic3d X3550 / X3511 as in the comment above
    OpenGL n/a 1 s unavailable (expected)

    Log excerpt for the FXC run (timestamps in seconds):

    1791726063 INFO  effectcraft: effectcraft 0.8.0: opening the window
    1791726063 INFO  effectcraft: graphics: AdapterInfo { … backend: Dx12 … }
    1791726063 INFO  effectcraft: app created; the window shows after its first frame
    1791726131 ERROR wgpu::backend::wgpu_core: Shader translation error for stage ShaderStages(COMPUTE): FXC D3DCompile error (Unspecified error (0x80004005)): …effectcraft kernels(1060,5-15): error X3511: unable to unroll loop …
    1791726131 INFO  effectcraft_ui_egui: GPU compositor unavailable: GPU initialization (classic3d): Validation Error …
    1791726131 INFO  effectcraft: first frame drawn
    

    So on a DX12-only machine the CPU fallback is clean, but it costs 68 s of FXC unrolling attempts during which there is no window, no splash and nothing in the log at the default level. A user who gives up and closes the process in that time trips the launch guard (#247 / #589): the next launch is on OpenGL, without the compositor, until they find Settings ▸ Startup & Repair ▸ Window Graphics.

    Two things that may be worth considering alongside the kernel work:

    • Show the window (or at least a status) before the compositor pipelines are built, or build them off the first frame. The 11 s with DXC is also long enough to make people wonder.
    • The DXC that worked here was a dxcompiler.dll + dxil.dll pair that a third-party audio plug-in had put in the user PATH. wgpu loaded it silently from there, in line with the "wgpu picks up any dxcompiler.dll it can load" finding above. It worked this time, but photocraft Color picker (Character panel Fill/Stroke) has no eyedropper — add viewer color sampling #712 shows the same mechanism crashing, so a pinned, shipped copy (or static-dxc) seems safer than relying on whatever is on PATH.

    Happy to re-run any of this on DX12 against a branch.

    Measurements and draft prepared with Claude Code; I read and checked them before posting.

  9. beziergrin commented on Oct 11, 2026

    @beziergrin

    Possibly the browser counterpart of this issue: on Windows/Chrome the web build loses its GPU device on load and hangs Chrome's GPU process (also with ?nogpuworkers).

    Chrome's WebGPU on Windows runs on Dawn's D3D12 backend. If a compositor kernel trips the D3D shader compiler there too, the browser outcome is much worse than the desktop's quiet CPU fallback.

    What happens

    The web build starts normally. The console logs restored the last session (untitled), event handlers installed. and GPU compositor: (BrowserWebGpu), and the title changes to EffectCraft - Untitled Project.ecproj. Within about a second the page stops painting, and DevTools-protocol screenshots of the tab time out after 30 s.

    The hang takes Chrome's GPU process with it: the whole browser stops responding, and a browser extension using the tab lost its connection. Once Chrome recovered, the page showed the device-lost notice from #237:

    The graphics device was lost
    GPU device lost (Unknown). A valid external Instance reference no longer exists.

    Afterwards, navigator.gpu.requestAdapter() returns null in every tab until Chrome restarts. Chrome has disabled WebGPU for the session.

    What I tried

    URL Result
    / Hangs as above
    /?nogpuworkers Still hangs. effectcraft.info() just before the hang: gpu: {compositor: " (BrowserWebGpu)", viewerOnGpu: false}, frameWorkers: {alive: 2, gpu: [null, null], …}, workers: {gpu: null}. Only the page's device was in use.
    /?webgl Not an EffectCraft flag (it's PhotoCraft's), so the app still started on WebGPU and hung.

    I couldn't find a URL flag that keeps EffectCraft's page off WebGPU. A ?webgl / ?cpu flag for the page would give users a workaround.

    On the same machine and browser, the other Craft web builds work, all of them on BrowserWebGpu: PhotoCraft, VectorCraft, FilmCraft, PdfCraft, WordCraft, DeckCraft, GridCraft, CADCraft, DesignCraft, LightCraft and SoundCraft. EffectCraft is the only one that uses WebGPU compute (the GPU compositor), which points at the compositor's pipelines or the first compute dispatch.

    Environment

    • EffectCraft main at 67434e90388b6dca4417555346fa6850b6f96033 (2026-10-11), workspace version 0.8.0.
    • Web build: cargo xtask web with Rust 1.99 and wasm-bindgen-cli 0.2.129. Reproduced with and without wasm-opt (binaryen version_133, -O2 as in xtask), with wasm sizes of 60.2 MB and 46.9 MB.
    • Served as static files by nginx 1.28 over HTTPS (secure context). No COOP/COEP headers; .wasm served as application/wasm, gzipped.
    • Client: Windows 11 (platformVersion 19.0.0), Google Chrome 155.0.8059.27, x64.
    • GPU: details to follow in a later comment. Chrome had disabled WebGPU after the crash, so the adapter info wasn't readable.

    Related: #225 / #237. The device-lost notice appears as designed; the problem is that the loss hangs the browser first.

    I can run any diagnostic build or URL flag you'd like.

    🤖 Generated with Claude Code

  10. beziergrin commented on Oct 11, 2026

    @beziergrin

    GPU details for the browser report above (from chrome://gpu, after restarting Chrome, which is now 155.0.8059.40):

    • GPU: Intel UHD Graphics 620, VENDOR=0x8086 DEVICE=0x5917, Gen 9, integrated. No discrete GPU (Has Discrete GPU: no). Driver 31.0.101.2134.
    • WebGPU adapter: Dawn <Integrated GPU> D3D12 backend - Intel(R) UHD Graphics 620, status Available. Vulkan isn't supported by this driver, so D3D12 is the only WebGPU path. The D3D11 and Microsoft Basic Render Driver adapters are blocklisted.
    • Toggles: Dawn's enabled toggles include use_dxc and d3d12_use_hlsl_2021, so the browser path compiles with DXC rather than FXC. That makes this probably not the same FXC abort as the desktop case above. It's more likely the compositor's first compute work hitting a TDR or out-of-memory condition on a low-end iGPU.
    • Memory: 7 GB of system RAM, shared with the iGPU.
    • Workaround in effect: Chrome applies exit_on_context_lost on this machine ("Some drivers are unable to reset the D3D device in the GPU process sandbox"). That fits with the GPU process going down rather than recovering in place.

    Every other Craft web app runs on this same adapter, so a smaller first dispatch or a ?cpu escape hatch for the page would probably be enough here.

    🤖 Generated with Claude Code

  11. beziergrin commented on Oct 11, 2026

    @beziergrin

    Also reproduces on macOS, so it's not specific to D3D12 or DXC.

    I opened the same build (67434e9, served the same way) in Chrome 155.0.8059.39 on macOS 15.7.8. The machine is a 2018 Intel Mac mini (4 cores, 8 GB). WebGPU adapter: vendor: intel, architecture: gen-9 on Dawn's Metal backend.

    It behaved the same as on Windows, only a bit faster: the app started, then the whole browser froze.

    Both failing machines use Intel Gen 9 integrated GPUs:

    • Windows: UHD 620 on D3D12.
    • macOS: UHD 630 on Metal.

    Every other Craft web app runs on both machines. That suggests the GPU compositor's first work is something Gen 9 iGPUs can't handle (dispatch size, memory, or a feature they handle badly), not a problem with the backend or shader compiler. A ?cpu flag for the page would still be a useful escape hatch for this class of hardware.

    🤖 Generated with Claude Code

  12. 2 remaining items

  13. MatejKozlovsky commented on Oct 15, 2026

    @MatejKozlovsky
    Contributor

    @didpublishing are you still working on this? I traced the classic3d failure to a single line: in occlusion, o[k] = … writes a vector component through a loop index. FXC can't do that natively (X3550), so it tries to unroll every loop around the call, which fails with the X3511 errors. Computing the three channels as one vec3 makes classic3d compile with FXC, and the output is unchanged. I also have classic3d falling back to the CPU like the effect kernels if a compiler still rejects it.

    If you already have a branch, I'm happy to hand this over or fold it into yours. Otherwise I can open a small PR. It doesn't touch the switch kernels you mentioned.

  14. MatejKozlovsky commented on Oct 16, 2026

    @MatejKozlovsky
    Contributor

    Reproduced this on current main (8f02f52), on an RTX 3090 / Ryzen 7 5800X, Windows 10 22H2 (19045), NVIDIA 595.97, with no dxcompiler.dll anywhere. effectcraft-cli info --gpu with WGPU_BACKEND=dx12 fails after about 38 s, and adding WGPU_DX12_COMPILER=fxc gives the identical error (Vulkan is fine):

    no usable GPU adapter (GPU initialization (classic3d): Validation Error
      In Device::create_compute_pipeline, label = 'classic3d'
        effectcraft kernels(1043,13-37): warning X3550: array reference cannot be used as an l-value; not natively addressable, forcing loop to unroll
        effectcraft kernels(1060,5-15): error X3511: unable to unroll loop, … (25 iterations) …
        effectcraft kernels(1147,5-15): error X3511: forced to unroll loop, but unrolling failed.
        effectcraft kernels(1346,13-23): error X3511: forced to unroll loop, but unrolling failed.
        effectcraft kernels(1298,5-15): error X3511: forced to unroll loop, but unrolling failed.
    

    Mapped to the source with naga 30.0.1: the X3550 is classic3d.wgsl:265 (o[k] = … in occlusion()'s for k < 3 loop), which naga emits as a dynamically indexed write that FXC can't address. FXC then has to unroll the runtime-bounded loops around it and fails: lines 274 (shadow_of), 317 (shade_texel), 386 and 400 (classic3d).

    On packaging: the v0.10.0 windows-x64-portable.zip and windows-x64.msi contain only effectcraft.exe and effectcraft-cli.exe, with no dxcompiler.dll or dxil.dll. So release users who land on DX12 always get FXC unless they have DXC installed themselves. In an earlier run on the same machine (2959ec7a), DXC 1.9.2609 (dxcompiler.dll + dxil.dll next to the exe) worked on DX12.

    #1571 fixes the occlusion write (one vec3 instead of o[k]), makes classic3d optional, and reworks the other kernels FXC rejects, so it should cover this without DXC. It doesn't ship DXC, though. I haven't re-run this 3090 against the #1571 branch yet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions