Start · Sprachen · JavaScript · Referenz · KHR_parallel_shader_compile extension

KHR_parallel_shader_compile extension

Schnittstelle

WebGL-Erweiterung, mit der sich über <code>COMPLETION_STATUS_KHR</code> ohne Blockieren abfragen lässt, ob das Kompilieren eines Shaders oder das Verlinken eines Programms abgeschlossen ist.

seit JavaScript WebGL 1 und WebGL 2 (Erweiterung); Chrome 76+, Edg Kategorie: webapi

Signatur

interface KHR_parallel_shader_compile

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');
}
Paralleles Kompilieren verfügbar: 91b1

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');
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}`));
Shader: 3/12 Shader: 9/12 Shader: 12/12

// Wichtig · Fallstricke

  • Nicht zu früh den Status abfragen: Wer direkt nach compileShader() COMPILE_STATUS abfragt oder getShaderInfoLog() aufruft, erzwingt ein synchrones Warten. Damit ist der Vorteil der Erweiterung weg. Fehlerprotokolle erst lesen, wenn LINK_STATUS false ist.
  • COMPLETION_STATUS_KHR meldet nur, dass der Vorgang fertig ist, nicht dass er geklappt hat. Danach immer noch LINK_STATUS bzw. COMPILE_STATUS prü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 Abfrage true, 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.