|
1 | 1 | import vertexShader from './shaders/vertex.glsl?raw'; |
2 | 2 | import fragmentShader from './shaders/fragment.glsl?raw'; |
3 | 3 |
|
| 4 | +/** Yields to the main thread so long tasks don't block rendering/input. */ |
| 5 | +function yieldToMain(): Promise<void> { |
| 6 | + const s = globalThis as unknown as { scheduler?: { yield?: () => Promise<void> } }; |
| 7 | + if (s.scheduler?.yield) return s.scheduler.yield(); |
| 8 | + return new Promise((r) => setTimeout(r, 0)); |
| 9 | +} |
| 10 | + |
| 11 | +/** |
| 12 | + * Polls until a shader/program is ready when KHR_parallel_shader_compile is |
| 13 | + * available, yielding to the main thread between checks. Falls back to a |
| 14 | + * single yield when the extension is absent. |
| 15 | + */ |
| 16 | +async function waitForCompilation(isReady: () => boolean) { |
| 17 | + while (!isReady()) { |
| 18 | + await yieldToMain(); |
| 19 | + } |
| 20 | +} |
| 21 | + |
4 | 22 | /** |
5 | 23 | * Initializes the WebGL hero background on the given canvas. |
6 | 24 | * |
7 | 25 | * @param canvas - The canvas element to render into. |
8 | 26 | * @param reducedMotion - If true, freezes time at 0 (static frame). |
9 | | - * @returns Controller with play/pause/destroy, or null if WebGL is unavailable. |
| 27 | + * @returns Controller with play/pause, or null if WebGL is unavailable. |
10 | 28 | */ |
11 | | -export function initHeroWebGL(canvas: HTMLCanvasElement, reducedMotion = false) { |
| 29 | +export async function initHeroWebGL(canvas: HTMLCanvasElement, reducedMotion = false) { |
12 | 30 | // Attempt to get a WebGL context; return null if unsupported |
13 | 31 | const gl = canvas.getContext('webgl', { alpha: false, powerPreference: 'low-power' }); |
14 | 32 | if (!gl) return null; |
15 | 33 |
|
16 | | - // -- Shader compilation -- |
| 34 | + const parallelExt = gl.getExtension('KHR_parallel_shader_compile') as { |
| 35 | + COMPLETION_STATUS_KHR: number; |
| 36 | + } | null; |
| 37 | + |
| 38 | + // -- Shader compilation (non-blocking) -- |
17 | 39 |
|
18 | 40 | function compile(type: number, src: string) { |
19 | 41 | const s = gl!.createShader(type)!; |
20 | 42 | gl!.shaderSource(s, src); |
21 | 43 | gl!.compileShader(s); |
22 | | - if (!gl!.getShaderParameter(s, gl!.COMPILE_STATUS)) { |
23 | | - console.error(gl!.getShaderInfoLog(s)); |
24 | | - gl!.deleteShader(s); |
25 | | - return null; |
26 | | - } |
27 | 44 | return s; |
28 | 45 | } |
29 | 46 |
|
| 47 | + // Kick off both compilations in parallel, then yield |
30 | 48 | const vs = compile(gl.VERTEX_SHADER, vertexShader); |
31 | 49 | const fs = compile(gl.FRAGMENT_SHADER, fragmentShader); |
32 | | - if (!vs || !fs) { |
33 | | - if (vs) gl.deleteShader(vs); |
34 | | - if (fs) gl.deleteShader(fs); |
| 50 | + |
| 51 | + const kshr = parallelExt?.COMPLETION_STATUS_KHR ?? null; |
| 52 | + if (kshr !== null) { |
| 53 | + await waitForCompilation(() => gl.getShaderParameter(vs!, kshr)); |
| 54 | + await waitForCompilation(() => gl.getShaderParameter(fs!, kshr)); |
| 55 | + } else { |
| 56 | + await yieldToMain(); |
| 57 | + } |
| 58 | + |
| 59 | + if (!gl.getShaderParameter(vs!, gl.COMPILE_STATUS)) { |
| 60 | + console.error(gl.getShaderInfoLog(vs!)); |
| 61 | + gl.deleteShader(vs); |
| 62 | + gl.deleteShader(fs); |
| 63 | + return null; |
| 64 | + } |
| 65 | + if (!gl.getShaderParameter(fs!, gl.COMPILE_STATUS)) { |
| 66 | + console.error(gl.getShaderInfoLog(fs!)); |
| 67 | + gl.deleteShader(vs); |
| 68 | + gl.deleteShader(fs); |
35 | 69 | return null; |
36 | 70 | } |
37 | 71 |
|
38 | | - // -- Program linking -- |
| 72 | + // -- Program linking (non-blocking) -- |
39 | 73 |
|
40 | 74 | const prog = gl.createProgram()!; |
41 | | - gl.attachShader(prog, vs); |
42 | | - gl.attachShader(prog, fs); |
| 75 | + gl.attachShader(prog, vs!); |
| 76 | + gl.attachShader(prog, fs!); |
43 | 77 | gl.linkProgram(prog); |
| 78 | + |
| 79 | + if (kshr !== null) { |
| 80 | + await waitForCompilation(() => gl.getProgramParameter(prog, kshr)); |
| 81 | + } else { |
| 82 | + await yieldToMain(); |
| 83 | + } |
| 84 | + |
44 | 85 | if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) { |
45 | 86 | console.error(gl.getProgramInfoLog(prog)); |
46 | 87 | gl.deleteProgram(prog); |
|
0 commit comments