8000 gl: start compiling a shader without waiting for it by benface · Pull Request #692 · not-fl3/miniquad · GitHub
[go: up one dir, main page]

Skip to content

gl: start compiling a shader without waiting for it - #692

Open
benface wants to merge 2 commits into
not-fl3:masterfrom
benface:gl-parallel-shader-compile
Open

benface wants to merge 2 commits into
not-fl3:masterfrom
benface:gl-parallel-shader-compile

Conversation

@benface
@benface benface commented Oct 17, 2026 •
Copy link
Copy Markdown
Contributor

Why

new_shader compiles a program's shaders, links it, then asks how each step went, and asking waits for the driver to finish. An app that makes many shaders while it loads waits for each in turn, even where the driver could compile them all at once.

WebKit is the slowest at it. A macroquad game (CHOMP) makes 38 programs while it loads, and in Safari's engine on a Mac (M2 Max) with nothing cached, they take 4.8 s one after the other. Started all at once and waited for with KHR_parallel_shader_compile, they take about 1 s, and no frame waits more than 2 ms. Safari, Chrome and Edge all have the extension.

Change

  • RenderingBackend::start_shader(source, meta) compiles and links a shader without asking how it went, and returns a PendingShader.
  • RenderingBackend::finish_shader(pending) waits for it, if it's still compiling, and returns what new_shader would have.
  • RenderingBackend::is_shader_compiling(&pending) says whether finish_shader would still wait. On OpenGL, it asks for COMPLETION_STATUS_KHR when the context has GL_KHR_parallel_shader_compile or GL_ARB_parallel_shader_compile. Without either, and on Metal, it's false.
  • new_shader is now start_shader followed by finish_shader, so its behaviour is unchanged. On Metal, the defaults compile in start_shader, as new_shader does.
  • WebGL only enables an extension once it's asked for, so gl.js gains webgl_enable_extension, and has_extension gets a wasm version that calls it.
  • gl.js's glLinkProgram read the program's uniforms right after linking, which waits for the link, so every program still linked one after the other on WebGL: about 150 ms each in WebKit. The uniform table now fills on the first glGetUniformLocation after a link, and glGetProgramiv checks the program rather than its table, so asking for COMPLETION_STATUS_KHR doesn't wait.

An app that wants its shaders ready without stopping on one can start every shader, keep drawing frames while is_shader_compiling says any still is, then finish each.

Measurements

CHOMP starts its 38 programs on its loading screen and finishes each once is_shader_compiling says it's done. Its loading screen times that phase. Each "first visit" run adds a different no-op statement to every shader, so the browser's shader cache has none of them.

First visit, before First visit, after Cached, before Cached, after
WebKit (Safari 27's engine), Mac 5.11–5.17 s 0.78–1.10 s 85–89 ms 37–39 ms
Chrome 155, Mac 2.35–2.63 s 0.42–0.54 s
Firefox, Mac (no extension) 0.10–0.20 s 0.08–0.15 s

One more Firefox run after the change took 0.70 s, and everything else in that run was slow too.

Verification

  • cargo check passes for aarch64-apple-darwin, aarch64-apple-ios, aarch64-linux-android, x86_64-unknown-linux-gnu and wasm32-unknown-unknown.
  • CHOMP's own tests, which render through OpenGL on macOS, pass with this change.

benface and others added 2 commits October 17, 2026 14:35
`new_shader` compiles a program's shaders, links it and asks how each
step went, and the asking waits for the driver to finish. An app that
makes many shaders as it loads waits for each in turn, even where the
driver could compile them all at once. WebKit is the slowest at it:
38 programs from a macroquad game (CHOMP) take 4.8 s to compile one
after the other on a Mac with nothing cached, and 1.0 s when every one
starts before any is checked.

`RenderingBackend::start_shader` compiles and links a shader without
asking how it went, and `finish_shader` waits for it and returns what
`new_shader` would have. In between, `is_shader_compiling` says
whether `finish_shader` would still wait, with
`KHR_parallel_shader_compile` (or `ARB_parallel_shader_compile`), so
an app can wait for its shaders across frames instead of stopping on
one. Without the extension it says `false`. Metal compiles in
`start_shader`, as in `new_shader`.

WebGL only enables an extension once it's asked for, so gl.js gains
`webgl_enable_extension`, which also gives `has_extension` a wasm
version. `new_shader` is now `start_shader` and `finish_shader`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`glLinkProgram` read every active uniform right after linking, which
waits for the link to finish, so on WebGL every program still compiled
and linked one after the other: 150 ms each in WebKit with nothing
cached. The table now fills on the first `glGetUniformLocation` after
a link, and `glGetProgramiv` checks the program itself rather than its
table, so it can ask for `COMPLETION_STATUS_KHR` without waiting.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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

0