Start · Sprachen · JavaScript · Referenz · WebGL2RenderingContext.clientWaitSync()

WebGL2RenderingContext.clientWaitSync()

Methode

Blockiert und wartet, bis ein <code>WebGLSync</code>-Objekt signalisiert wird oder der angegebene Timeout (in Nanosekunden) abläuft. Gibt den Wartestatus zurück.

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

Signatur

WebGL2RenderingContext.prototype.clientWaitSync(sync, flags, timeout)

Beschreibung

clientWaitSync() prüft den Zustand eines Fence-Sync-Objekts, das zuvor mit gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0) in den GPU-Befehlsstrom eingefügt wurde. Ist die GPU mit allen Befehlen vor dem Fence fertig, gilt das Sync-Objekt als signalisiert. So erfahren Sie, ob die GPU z. B. einen Rendervorgang oder das Befüllen eines Pixel-Buffers (PBO) abgeschlossen hat.

Der Haupteinsatz in WebGL ist das nicht blockierende Auslesen von GPU-Daten: Statt readPixels() synchron auf den CPU-Speicher warten zu lassen, liest man in einen PIXEL_PACK_BUFFER, setzt einen Fence und fragt pro Frame mit clientWaitSync(sync, 0, 0) ab, ob die Daten bereit sind. Erst dann holt man sie per getBufferSubData() ab – ohne die Render-Pipeline anzuhalten.

Im Unterschied zu waitSync(), das nur die GPU-Befehlswarteschlange warten lässt, blockiert clientWaitSync() den aufrufenden JavaScript-Thread. Da der Hauptthread nicht blockiert werden soll, erlauben Browser nur sehr kurze Timeouts: Der Wert darf gl.MAX_CLIENT_WAIT_TIMEOUT_WEBGL nicht überschreiten, der in vielen Implementierungen 0 ist. In der Praxis ruft man die Methode also fast immer mit Timeout 0 auf und fragt wiederholt ab (Polling).

Parameter

Name Typ Default Beschreibung
$sync Pflicht WebGLSync — Das zu prüfende Sync-Objekt, erzeugt mit gl.fenceSync().
$flags Pflicht GLbitfield (number) 0 0 oder gl.SYNC_FLUSH_COMMANDS_BIT. Mit dem Flag werden ausstehende Befehle vor dem Warten an die GPU übergeben (Flush), damit der Fence überhaupt erreicht werden kann.
$timeout Pflicht GLint64 (number) 0 Maximale Wartezeit in Nanosekunden. Darf gl.getParameter(gl.MAX_CLIENT_WAIT_TIMEOUT_WEBGL) nicht überschreiten; 0 fragt den Zustand nur ab, ohne zu warten.

Rückgabewert

Typ
GLenum (number)
Beschreibung
Einer der Werte:
  • gl.ALREADY_SIGNALED – das Sync-Objekt war beim Aufruf bereits signalisiert.
  • gl.CONDITION_SATISFIED – es wurde während der Wartezeit signalisiert.
  • gl.TIMEOUT_EXPIRED – der Timeout lief ab, die GPU ist noch nicht fertig.
  • gl.WAIT_FAILED – ein Fehler trat auf (z. B. zu großer Timeout); gl.getError() liefert Details.

Beispiele

Einfache Statusabfrage eines Fence

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

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

// Fence nach den Render-Befehlen einfügen
const sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0);

const status = gl.clientWaitSync(sync, gl.SYNC_FLUSH_COMMANDS_BIT, 0);
const names = {
  [gl.ALREADY_SIGNALED]: 'ALREADY_SIGNALED',
  [gl.CONDITION_SATISFIED]: 'CONDITION_SATISFIED',
  [gl.TIMEOUT_EXPIRED]: 'TIMEOUT_EXPIRED',
  [gl.WAIT_FAILED]: 'WAIT_FAILED'
};
console.log(names[status]);
gl.deleteSync(sync);
TIMEOUT_EXPIRED // im selben Task ist der Fence in WebGL noch nicht signalisiert

Fence als Promise verpacken (Polling pro Frame)

const waitForSync = (gl, sync) => new Promise((resolve, reject) => {
  // Erster Aufruf mit Flush, danach reines Polling
  let flags = gl.SYNC_FLUSH_COMMANDS_BIT;
  const check = () => {
    const res = gl.clientWaitSync(sync, flags, 0);
    flags = 0;
    if (res === gl.WAIT_FAILED) {
      reject(new Error('clientWaitSync fehlgeschlagen'));
    } else if (res === gl.TIMEOUT_EXPIRED) {
      requestAnimationFrame(check);
    } else {
      resolve();
    }
  };
  check();
});

const gl = document.createElement('canvas').getContext('webgl2');
const sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0);
await waitForSync(gl, sync);
gl.deleteSync(sync);
console.log('GPU fertig');
GPU fertig

Asynchrones readPixels mit Pixel-Buffer-Objekt

const readPixelsAsync = async (gl, x, y, w, h) => {
  const pbo = gl.createBuffer();
  gl.bindBuffer(gl.PIXEL_PACK_BUFFER, pbo);
  gl.bufferData(gl.PIXEL_PACK_BUFFER, w * h * 4, gl.STREAM_READ);
  // Mit gebundenem PBO schreibt readPixels in den Buffer statt in ein Array
  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.clientWaitSync(sync, 0, 0) === gl.TIMEOUT_EXPIRED) {
    await new Promise(requestAnimationFrame);
  }
  gl.deleteSync(sync);

  const data = new Uint8Array(w * h * 4);
  gl.bindBuffer(gl.PIXEL_PACK_BUFFER, pbo);
  gl.getBufferSubData(gl.PIXEL_PACK_BUFFER, 0, data);
  gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
  gl.deleteBuffer(pbo);
  return data;
};

const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl2');
gl.clearColor(1, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
const pixel = await readPixelsAsync(gl, 0, 0, 1, 1);
console.log(Array.from(pixel));
[255, 0, 0, 255]