Signatur
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 immer0, 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()unddeleteSync(): 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
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');
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');
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);
// 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_WEBGList in Browsern meist0. Ein größeres Zeitlimit beiclientWaitSync()löstINVALID_OPERATIONaus. Zum Warten fragt man deshalb immer wieder ab, etwa mitrequestAnimationFrameodersetTimeout. - 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 mangl.SYNC_FLUSH_COMMANDS_BITanclientWaitSync().- Speicher freigeben: Nicht mehr benötigte Fences mit
gl.deleteSync()löschen. Viele offene Sync-Objekte belasten den Treiber. - Kontextverlust: Nach einem
webglcontextlostwerden alleWebGLSync-Objekte ungültig und müssen neu erzeugt werden.