Skip to content

fix(windows): cache compiled WebGL programs; WebGPU canvases no longer freeze on resize - #184

Merged
triniwiz merged 9 commits into
masterfrom
fix/windows-angle-program-cache
Oct 20, 2026
Merged

triniwiz merged 9 commits into
masterfrom
fix/windows-angle-program-cache

Conversation

@triniwiz

@triniwiz triniwiz commented Oct 20, 2026 •

Copy link
Copy Markdown
Member

WebGL: compiled programs are cached

ANGLE only reuses linked programs when the app installs EGL_ANDROID_blob_cache callbacks (its built-in cache size is 0), so every new context recompiled every shader through the HLSL compiler. A page with a large physically based scene blocked the UI thread for about a second on every visit.

Programs are now kept in memory and on disk under %LOCALAPPDATA%\<exe>\canvas\program-cache (packaged apps get it redirected into their package data). CANVAS_PROGRAM_CACHE_DIR overrides the location; an empty value keeps the cache in memory only. Memory is capped at 32 MB and disk at 64 MB.

First visit after launch Before After (second launch)
Large scene: time in shader compile 933 ms 47 ms
Large scene: longest UI stall 672 ms 173 ms
Mixed page: time in shader compile 943 ms 33 ms

Revisits within a session are fast too. The very first visit on a fresh install still compiles once.

WebGL: links run in the background

  • linkProgram queried ACTIVE_UNIFORM_BLOCKS straight after glLinkProgram, which waits for the link to finish, so ANGLE could never link off-thread. The count is now queried the first time uniformBlockBinding needs it.
  • KHR_parallel_shader_compile is exposed on Windows. Once a page enables it, ANGLE compiles and links on worker threads, and COMPLETION_STATUS_KHR can be polled without waiting (three.js's compileAsync uses it).

The same three.js scene (PMREM room environment, physical and standard materials), first open with an empty shader cache, measured as time the JS thread is blocked:

Blocked
Before this PR ~1,330 ms
This PR, compileAsync ~740 ms (most of it is three's PMREM generation, which is synchronous)
Edge 155, same machine and GPU (ANGLE/D3D11), cold profile ~1,710 ms (render()), ~1,000 ms (compileAsync)
This PR, cached ~140 ms (Edge: ~350 ms)

WebGPU: resizing a canvas no longer freezes the app

  • Reconfiguring the surface from the layout pass waited on the GPU with no limit, while the last presented frame waited on the compositor, which waits on the UI thread. The resize is now applied from the next getCurrentTexture, once that frame has finished, and frames draw offscreen until then. Nothing blocks.
  • A resize gets a fresh swapchain. ResizeBuffers failed while the page still held a texture of the old one (window is in use), and the canvas stayed at its old size.
  • getCurrentTexture falls back to the offscreen texture instead of returning null (context may not be configured).

Tested on Windows x64 (Intel Iris Xe) by resizing the window 6 times on a compute + render page: 9 runs in a row stayed responsive and kept animating, with no ResizeBuffers errors. Before the change, nearly every run froze.

Binaries

canvasnative.node rebuilt for x64 and arm64. The arm64 build has not been run on a device.

Note for local builds: Cargo.lock is ignored, and a fresh resolve picks napi 3.14 / napi-sys 3.4. That silently bypasses the [patch.crates-io] napi-sys, and the module aborts on load in a NativeScript Windows host. These binaries were built against napi 3.13.0.

ANGLE only reuses linked programs when the app installs EGL_ANDROID_blob_cache
callbacks; its own cache size defaults to 0. Without them every new context
recompiled every shader through the HLSL compiler, which blocked large scenes
for about a second on each visit.

Programs are now kept in memory and under %LOCALAPPDATA%\<exe>\canvas\program-cache
(CANVAS_PROGRAM_CACHE_DIR overrides it; empty keeps it in memory only).
Configuring the surface from the layout pass waited for the GPU without a
limit, while the last presented frame waited on the compositor, which waits
on the UI thread. The resize is now applied from the next getCurrentTexture,
once that frame is done, drawing offscreen until then.

Resizing gets a fresh swapchain, since ResizeBuffers fails while the page
still holds a texture of the old one ("window is in use"). getCurrentTexture
falls back to the offscreen texture instead of returning null.
@coderabbitai

coderabbitai Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 57ab8366-bd93-4688-9042-7c8c214c8c87

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

linkProgram queried ACTIVE_UNIFORM_BLOCKS straight after glLinkProgram, which
waits for the link to finish, so ANGLE could never link in the background. The
count is now queried (and cached) the first time uniformBlockBinding needs it.
ANGLE compiles and links on worker threads once a context enables it, and
COMPLETION_STATUS_KHR lets the page poll without waiting (three.js's
compileAsync, for example).
Exposed when the driver reports GL_KHR_parallel_shader_compile. The GL default
for MAX_SHADER_COMPILER_THREADS_KHR already lets the driver use its own maximum,
so nothing else has to be set. COMPLETION_STATUS_KHR reads back as a boolean.
@triniwiz
triniwiz merged commit 57d7331 into master Oct 20, 2026
9 of 16 checks passed
@triniwiz
triniwiz deleted the fix/windows-angle-program-cache branch October 20, 2026 03:50
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