Signatur
Beschreibung
Die Erweiterung KHR_parallel_shader_compile gehört zur WebGL-API und steht sowohl in WebGLRenderingContext als auch in WebGL2RenderingContext zur Verfügung. Man erhält sie über gl.getExtension('KHR_parallel_shader_compile'). Sie bringt keine eigenen Methoden mit, sondern nur die Konstante COMPLETION_STATUS_KHR (Wert 0x91B1). Diese Konstante übergibt man an gl.getShaderParameter() oder gl.getProgramParameter().
Normalerweise blockiert der erste Aufruf von getShaderParameter(shader, gl.COMPILE_STATUS) oder getProgramParameter(program, gl.LINK_STATUS) den Haupt-Thread, bis der Treiber fertig kompiliert bzw. verlinkt hat. Bei großen Shadern oder vielen Programmen kann das hunderte Millisekunden dauern und sichtbares Ruckeln verursachen. Mit COMPLETION_STATUS_KHR fragt man dagegen nur nach, ob der Vorgang schon beendet ist. Diese Abfrage blockiert nie. Erst wenn sie true liefert, liest man LINK_STATUS aus, und das geht dann ohne Wartezeit.
Typischer Einsatz: Beim Laden einer Szene werden alle Shader kompiliert und alle Programme verlinkt, ohne direkt den Status abzufragen. Danach prüft man einmal pro Frame per requestAnimationFrame, welche Programme schon fertig sind. So bleiben Ladebildschirme und Animationen flüssig, und der Treiber kann die Programme parallel in eigenen Threads übersetzen.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $COMPLETION_STATUS_KHR | GLenum (0x91B1) | Konstante des Erweiterungsobjekts. Wird als pname an gl.getShaderParameter() oder gl.getProgramParameter() übergeben, um nicht-blockierend abzufragen, ob Kompilierung bzw. Verlinkung abgeschlossen ist. |
Beispiele
Erweiterung aktivieren und Unterstützung prüfen
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl2') ?? canvas.getContext('webgl');
const ext = gl.getExtension('KHR_parallel_shader_compile');
if (ext) {
console.log('Paralleles Kompilieren verfügbar:', ext.COMPLETION_STATUS_KHR.toString(16));
} else {
console.log('Erweiterung nicht unterstützt – Fallback auf blockierende Abfrage');
}
Programm asynchron verlinken mit Promise
const compileShader = (gl, type, source) => {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
return shader; // bewusst noch KEIN COMPILE_STATUS abfragen
};
const linkProgramAsync = (gl, vsSource, fsSource) => {
const ext = gl.getExtension('KHR_parallel_shader_compile');
const vs = compileShader(gl, gl.VERTEX_SHADER, vsSource);
const fs = compileShader(gl, gl.FRAGMENT_SHADER, fsSource);
const program = gl.createProgram();
gl.attachShader(program, vs);
gl.attachShader(program, fs);
gl.linkProgram(program);
return new Promise((resolve, reject) => {
const check = () => {
// Ohne Erweiterung: sofort (blockierend) auswerten
if (ext && !gl.getProgramParameter(program, ext.COMPLETION_STATUS_KHR)) {
requestAnimationFrame(check);
return;
}
if (gl.getProgramParameter(program, gl.LINK_STATUS)) {
resolve(program);
} else {
const log = gl.getProgramInfoLog(program)
|| gl.getShaderInfoLog(vs)
|| gl.getShaderInfoLog(fs);
reject(new Error(log));
}
};
check();
});
};
const gl = document.querySelector('canvas').getContext('webgl2');
const vsSrc = `#version 300 es
in vec4 aPos;
void main() { gl_Position = aPos; }`;
const fsSrc = `#version 300 es
precision highp float;
out vec4 color;
void main() { color = vec4(1.0, 0.5, 0.0, 1.0); }`;
const program = await linkProgramAsync(gl, vsSrc, fsSrc);
gl.useProgram(program);
console.log('Programm bereit');
Viele Programme parallel laden mit Fortschrittsanzeige
const gl = document.querySelector('canvas').getContext('webgl2');
const ext = gl.getExtension('KHR_parallel_shader_compile');
// programs: bereits per gl.linkProgram() angestoßene WebGLProgram-Objekte
const waitForAll = (programs, onProgress) => new Promise((resolve) => {
const pending = new Set(programs);
const tick = () => {
for (const p of pending) {
const done = !ext || gl.getProgramParameter(p, ext.COMPLETION_STATUS_KHR);
if (done) pending.delete(p);
}
onProgress(programs.length - pending.size, programs.length);
pending.size ? requestAnimationFrame(tick) : resolve(programs);
};
tick();
});
// Anwendung:
// await waitForAll(meinePrograms, (fertig, gesamt) =>
// console.log(`Shader: ${fertig}/${gesamt}`));
// Wichtig · Fallstricke
- Nicht zu früh den Status abfragen: Wer direkt nach
compileShader()COMPILE_STATUSabfragt odergetShaderInfoLog()aufruft, erzwingt ein synchrones Warten. Damit ist der Vorteil der Erweiterung weg. Fehlerprotokolle erst lesen, wennLINK_STATUSfalseist. COMPLETION_STATUS_KHRmeldet nur, dass der Vorgang fertig ist, nicht dass er geklappt hat. Danach immer nochLINK_STATUSbzw.COMPILE_STATUSprüfen.- Ist die Erweiterung vorhanden, lohnt es sich meist, nur den Programm-Status abzufragen. Den Status der einzelnen Shader braucht man nicht.
- Die ursprüngliche Spezifikation enthielt
maxShaderCompilerThreadsKHR(). WebGL übernimmt diese Methode bewusst nicht. Die Anzahl der Threads legt der Browser fest. - Geht der Kontext verloren (
webglcontextlost), liefert die Abfragetrue, damit Warteschleifen nicht endlos laufen. - Kompatibilität: Chromium-Browser seit Version 76, Firefox seit 80, Safari seit 16.4. Mit
getExtension()gibt es immer einen Fallback, denn ohne Erweiterung funktioniert die blockierende Abfrage weiterhin.