Signatur
Beschreibung
Mit beginQuery() beginnt man eine asynchrone Abfrage auf der GPU. Alle Zeichenbefehle zwischen beginQuery() und dem passenden endQuery() werden für die Abfrage erfasst. Das Ergebnis liegt nicht sofort vor, sondern wird später mit getQueryParameter() ausgelesen, sobald gl.QUERY_RESULT_AVAILABLE den Wert true liefert.
Typische Einsatzfälle:
- Occlusion Culling: Mit
gl.ANY_SAMPLES_PASSEDprüft man, ob ein Objekt (oder dessen Bounding-Box) überhaupt sichtbar ist, und spart sich in späteren Frames aufwendiges Zeichnen verdeckter Objekte. - Transform Feedback: Mit
gl.TRANSFORM_FEEDBACK_PRIMITIVES_WRITTENzählt man, wie viele Primitive in Transform-Feedback-Puffer geschrieben wurden. - GPU-Zeitmessung: Mit der Erweiterung
EXT_disjoint_timer_query_webgl2und dem Zielext.TIME_ELAPSED_EXTmisst man die Ausführungszeit auf der GPU.
Pro Ziel darf immer nur eine Abfrage gleichzeitig aktiv sein. Das Ergebnis sollte man frühestens im nächsten Frame (z. B. im nächsten requestAnimationFrame-Callback) abfragen – WebGL gibt Ergebnisse ohnehin erst nach Rückkehr zur Ereignisschleife frei, um Pipeline-Stalls zu vermeiden.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $target Pflicht | GLenum | Art der Abfrage: gl.ANY_SAMPLES_PASSED (mind. ein Sample hat den Tiefentest bestanden), gl.ANY_SAMPLES_PASSED_CONSERVATIVE (schnellere, ungenauere Variante, darf falsch-positiv sein) oder gl.TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN. Mit Erweiterung zusätzlich ext.TIME_ELAPSED_EXT. |
|
| $query Pflicht | WebGLQuery | Ein mit gl.createQuery() erzeugtes Abfrageobjekt, das die Ergebnisse aufnimmt. |
Beispiele
Occlusion-Abfrage: Ist ein Objekt sichtbar?
const canvas = document.querySelector('canvas');
const gl = canvas.getContext('webgl2');
const query = gl.createQuery();
// Bounding-Box des Objekts testweise zeichnen
gl.colorMask(false, false, false, false);
gl.depthMask(false);
gl.beginQuery(gl.ANY_SAMPLES_PASSED, query);
gl.drawArrays(gl.TRIANGLES, 0, 36); // Würfel-Box
gl.endQuery(gl.ANY_SAMPLES_PASSED);
gl.colorMask(true, true, true, true);
gl.depthMask(true);
// Ergebnis frühestens im nächsten Frame abholen
const pollResult = () => {
const available = gl.getQueryParameter(query, gl.QUERY_RESULT_AVAILABLE);
if (!available) {
requestAnimationFrame(pollResult);
return;
}
const visible = gl.getQueryParameter(query, gl.QUERY_RESULT);
console.log('Objekt sichtbar:', visible);
};
requestAnimationFrame(pollResult);
Geschriebene Primitive bei Transform Feedback zählen
const gl = document.createElement('canvas').getContext('webgl2');
// Annahme: program mit Transform-Feedback-Varyings ist gelinkt,
// transformFeedback und Zielpuffer sind gebunden.
const query = gl.createQuery();
gl.enable(gl.RASTERIZER_DISCARD);
gl.beginQuery(gl.TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, query);
gl.beginTransformFeedback(gl.POINTS);
gl.drawArrays(gl.POINTS, 0, 1000);
gl.endTransformFeedback();
gl.endQuery(gl.TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
gl.disable(gl.RASTERIZER_DISCARD);
setTimeout(() => {
if (gl.getQueryParameter(query, gl.QUERY_RESULT_AVAILABLE)) {
const count = gl.getQueryParameter(query, gl.QUERY_RESULT);
console.log(`Geschriebene Primitive: ${count}`);
}
gl.deleteQuery(query);
}, 100);
GPU-Zeitmessung mit EXT_disjoint_timer_query_webgl2
const gl = document.querySelector('canvas').getContext('webgl2');
const ext = gl.getExtension('EXT_disjoint_timer_query_webgl2');
if (ext) {
const query = gl.createQuery();
gl.beginQuery(ext.TIME_ELAPSED_EXT, query);
gl.drawArrays(gl.TRIANGLES, 0, 3);
gl.endQuery(ext.TIME_ELAPSED_EXT);
const check = () => {
const available = gl.getQueryParameter(query, gl.QUERY_RESULT_AVAILABLE);
const disjoint = gl.getParameter(ext.GPU_DISJOINT_EXT);
if (available && !disjoint) {
const ns = gl.getQueryParameter(query, gl.QUERY_RESULT);
console.log(`GPU-Zeit: ${(ns / 1e6).toFixed(3)} ms`);
gl.deleteQuery(query);
} else if (!available) {
requestAnimationFrame(check);
}
};
requestAnimationFrame(check);
} else {
console.log('Timer-Abfragen nicht verfügbar');
}
// Wichtig · Fallstricke
- Nur eine aktive Abfrage pro Ziel: Ein zweites
beginQuery()für dasselbe Ziel ohne vorherigesendQuery()erzeugtgl.INVALID_OPERATION. Dasselbe gilt, wenn das Query-Objekt bereits für ein anderes Ziel verwendet wurde. ANY_SAMPLES_PASSEDundANY_SAMPLES_PASSED_CONSERVATIVEteilen sich einen Slot – sie dürfen nicht gleichzeitig aktiv sein.- Ergebnis nicht im selben Frame abfragen: WebGL meldet
QUERY_RESULT_AVAILABLEfrühestens nach Rückkehr zur Ereignisschleife alstrue. Aktives Warten in einer Schleife blockiert den Tab dauerhaft. - Eine ungültige
target-Konstante führt zugl.INVALID_ENUM; ein gelöschtes Query-Objekt zugl.INVALID_OPERATION. - Timer-Abfragen (
EXT_disjoint_timer_query_webgl2) sind aus Sicherheitsgründen (Timing-Angriffe) in vielen Browsern deaktiviert oder in der Auflösung reduziert – immer aufnullprüfen. - Kompatibilität: Nur in WebGL 2 (
getContext('webgl2')) verfügbar; in WebGL 1 gibt es Occlusion-Queries nicht, Timer nur überEXT_disjoint_timer_query.