Start · Sprachen · JavaScript · Referenz · WebGLSync

WebGLSync

Schnittstelle

Ein <code>WebGLSync</code>-Objekt ist ein Sync-Objekt (Fence) aus WebGL 2. Es zeigt an, wann die GPU alle bis dahin abgesetzten Befehle abgearbeitet hat.

seit JavaScript WebGL 2 – Baseline: alle modernen Browser (seit 20 Kategorie: webapi

Signatur

interface WebGLSync

Beschreibung

Die Schnittstelle WebGLSync gehört zur WebGL 2 API. Sie steht für ein Sync-Objekt, auch Fence genannt. Die GPU arbeitet asynchron: Ein Aufruf wie gl.drawArrays() reiht Befehle nur in eine Warteschlange ein. Ein Fence ist eine Markierung in dieser Warteschlange. Sobald die GPU alle Befehle davor ausgeführt hat, wechselt der Fence in den Zustand gl.SIGNALED.

Das Objekt hat keine eigenen Methoden oder Eigenschaften. Man erzeugt es mit gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0) und arbeitet damit über Methoden von WebGL2RenderingContext:

  • clientWaitSync(): Wartet auf der CPU. In WebGL ist das Zeitlimit praktisch immer 0, der Aufruf fragt also nur den Zustand ab.
  • waitSync(): Lässt die GPU-Warteschlange warten, der JavaScript-Code läuft weiter.
  • getSyncParameter(): Liest Status, Typ, Bedingung und Flags aus.
  • isSync() und deleteSync(): Prüfen bzw. geben das Objekt frei.

Typischer Einsatz ist das asynchrone Zurücklesen von Pixeldaten. Man ruft readPixels() mit einem Pixel-Pack-Buffer (PBO) auf, setzt danach einen Fence und fragt ihn in späteren Frames ab. Erst wenn er signalisiert ist, holt man die Daten mit getBufferSubData(). So blockiert die Anwendung nicht und die Render-Schleife läuft ruckelfrei weiter.

Parameter

Name Typ Default Beschreibung
$condition Pflicht GLenum Parameter von gl.fenceSync(). Erlaubt ist nur gl.SYNC_GPU_COMMANDS_COMPLETE.
$flags Pflicht GLbitfield 0 Parameter von gl.fenceSync(). Muss 0 sein. Bei gl.clientWaitSync() ist optional gl.SYNC_FLUSH_COMMANDS_BIT möglich.
$timeout Pflicht GLuint64 (number) 0 Parameter von gl.clientWaitSync() in Nanosekunden. Er darf gl.MAX_CLIENT_WAIT_TIMEOUT_WEBGL nicht überschreiten, das ist in Browsern meist 0. Bei gl.waitSync() muss er gl.TIMEOUT_IGNORED sein.

Rückgabewert

Typ
WebGLSync | null
Beschreibung
gl.fenceSync() liefert ein neues WebGLSync-Objekt. Bei einem Fehler, etwa einem verlorenen Kontext, kommt null zurück. clientWaitSync() liefert eine dieser Konstanten: ALREADY_SIGNALED, CONDITION_SATISFIED, TIMEOUT_EXPIRED oder WAIT_FAILED.

Beispiele

Fence erzeugen und Status abfragen

const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl2');

gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);

// Fence hinter die bisherigen Befehle setzen
const sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0);
gl.flush(); // Befehle an die GPU übergeben

console.log(sync instanceof WebGLSync);
console.log(gl.isSync(sync));
console.log(gl.getSyncParameter(sync, gl.SYNC_STATUS) === gl.SIGNALED
  ? 'fertig' : 'läuft noch');
true true läuft noch

Nicht blockierend auf die GPU warten (Promise)

const waitForGpu = (gl, intervalMs = 4) => {
  const sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0);
  gl.flush();

  return new Promise((resolve, reject) => {
    const check = () => {
      const res = gl.clientWaitSync(sync, 0, 0); // Zeitlimit 0 = nur abfragen
      if (res === gl.WAIT_FAILED) {
        gl.deleteSync(sync);
        reject(new Error('clientWaitSync fehlgeschlagen'));
      } else if (res === gl.TIMEOUT_EXPIRED) {
        setTimeout(check, intervalMs);
      } else {
        gl.deleteSync(sync); // Ressource freigeben
        resolve();
      }
    };
    check();
  });
};

// Nutzung
const gl = document.createElement('canvas').getContext('webgl2');
gl.clear(gl.COLOR_BUFFER_BIT);
await waitForGpu(gl);
console.log('GPU hat alle Befehle ausgeführt');
GPU hat alle Befehle ausgeführt

Asynchrones readPixels mit Pixel-Pack-Buffer

const readPixelsAsync = async (gl, x, y, w, h) => {
  const size = w * h * 4;
  const pbo = gl.createBuffer();
  gl.bindBuffer(gl.PIXEL_PACK_BUFFER, pbo);
  gl.bufferData(gl.PIXEL_PACK_BUFFER, size, gl.STREAM_READ);
  // Offset 0 statt Array: in den Buffer lesen
  gl.readPixels(x, y, w, h, gl.RGBA, gl.UNSIGNED_BYTE, 0);
  gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);

  const sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0);
  gl.flush();

  // Pro Frame abfragen, ohne den Hauptthread zu blockieren
  while (gl.getSyncParameter(sync, gl.SYNC_STATUS) !== gl.SIGNALED) {
    await new Promise(requestAnimationFrame);
  }
  gl.deleteSync(sync);

  const pixels = new Uint8Array(size);
  gl.bindBuffer(gl.PIXEL_PACK_BUFFER, pbo);
  gl.getBufferSubData(gl.PIXEL_PACK_BUFFER, 0, pixels);
  gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
  gl.deleteBuffer(pbo);
  return pixels;
};

const gl = document.querySelector('canvas').getContext('webgl2');
gl.clearColor(1, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
const px = await readPixelsAsync(gl, 0, 0, 1, 1);
console.log(px);
Uint8Array(4) [255, 0, 0, 255]

// Wichtig · Fallstricke

  • Nur WebGL 2: Sync-Objekte gibt es nur in einem webgl2-Kontext. In WebGL 1 existiert dafür keine Entsprechung. Alle modernen Browser unterstützen WebGL 2, darunter Safari ab Version 15.
  • Kein blockierendes Warten: gl.MAX_CLIENT_WAIT_TIMEOUT_WEBGL ist in Browsern meist 0. Ein größeres Zeitlimit bei clientWaitSync() löst INVALID_OPERATION aus. Zum Warten fragt man deshalb immer wieder ab, etwa mit requestAnimationFrame oder setTimeout.
  • Status ändert sich erst im nächsten Durchlauf: Laut Spezifikation wird der Zustand eines Fence nie innerhalb derselben JavaScript-Aufgabe aktualisiert, in der er erzeugt wurde. Eine enge Warteschleife wie while (…) {} endet also nie. Man muss die Kontrolle an die Ereignisschleife zurückgeben.
  • gl.flush() nicht vergessen: Ohne Flush kann es sein, dass die Befehle nie bei der GPU ankommen und der Fence nie signalisiert wird. Alternativ übergibt man gl.SYNC_FLUSH_COMMANDS_BIT an clientWaitSync().
  • Speicher freigeben: Nicht mehr benötigte Fences mit gl.deleteSync() löschen. Viele offene Sync-Objekte belasten den Treiber.
  • Kontextverlust: Nach einem webglcontextlost werden alle WebGLSync-Objekte ungültig und müssen neu erzeugt werden.