Skip to content

Commit edaccc6

Browse files
committed
better performance
1 parent 277e715 commit edaccc6

File tree

2 files changed

+57
-16
lines changed

2 files changed

+57
-16
lines changed

astro/src/components/patterns/Hero/Hero.astro

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,14 @@ app.listen(port, () => {
6666
<script>
6767
import { initHeroWebGL } from './hero-background';
6868

69-
function initHero() {
69+
async function initHero() {
7070
const canvas = document.querySelector<HTMLCanvasElement>('.hero__canvas');
7171
if (!canvas) return;
7272

7373
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
7474

7575
// Initialize WebGL; if unsupported the canvas stays empty (transparent)
76-
const bg = initHeroWebGL(canvas, prefersReducedMotion);
76+
const bg = await initHeroWebGL(canvas, prefersReducedMotion);
7777
if (!bg) return;
7878

7979
// Only animate when the hero is visible in the viewport

astro/src/components/patterns/Hero/hero-background.ts

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,87 @@
11
import vertexShader from './shaders/vertex.glsl?raw';
22
import fragmentShader from './shaders/fragment.glsl?raw';
33

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+
422
/**
523
* Initializes the WebGL hero background on the given canvas.
624
*
725
* @param canvas - The canvas element to render into.
826
* @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.
1028
*/
11-
export function initHeroWebGL(canvas: HTMLCanvasElement, reducedMotion = false) {
29+
export async function initHeroWebGL(canvas: HTMLCanvasElement, reducedMotion = false) {
1230
// Attempt to get a WebGL context; return null if unsupported
1331
const gl = canvas.getContext('webgl', { alpha: false, powerPreference: 'low-power' });
1432
if (!gl) return null;
1533

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) --
1739

1840
function compile(type: number, src: string) {
1941
const s = gl!.createShader(type)!;
2042
gl!.shaderSource(s, src);
2143
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-
}
2744
return s;
2845
}
2946

47+
// Kick off both compilations in parallel, then yield
3048
const vs = compile(gl.VERTEX_SHADER, vertexShader);
3149
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);
3569
return null;
3670
}
3771

38-
// -- Program linking --
72+
// -- Program linking (non-blocking) --
3973

4074
const prog = gl.createProgram()!;
41-
gl.attachShader(prog, vs);
42-
gl.attachShader(prog, fs);
75+
gl.attachShader(prog, vs!);
76+
gl.attachShader(prog, fs!);
4377
gl.linkProgram(prog);
78+
79+
if (kshr !== null) {
80+
await waitForCompilation(() => gl.getProgramParameter(prog, kshr));
81+
} else {
82+
await yieldToMain();
83+
}
84+
4485
if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {
4586
console.error(gl.getProgramInfoLog(prog));
4687
gl.deleteProgram(prog);

0 commit comments

Comments
 (0)