Signatur
Beschreibung
createQuery() legt ein leeres WebGLQuery-Objekt an. Dieses Objekt ist ein Behälter für eine asynchrone GPU-Abfrage: Zwischen gl.beginQuery(target, query) und gl.endQuery(target) sammelt die GPU Informationen, die du später mit gl.getQueryParameter() ausliest, ohne die Render-Pipeline anzuhalten.
Welche Informationen gesammelt werden, legt erst das Ziel (target) beim Aufruf von beginQuery() fest:
gl.ANY_SAMPLES_PASSED– Occlusion Query: Wurde mindestens ein Fragment gezeichnet (Tiefentest bestanden)?gl.ANY_SAMPLES_PASSED_CONSERVATIVE– wie oben, aber schneller und mit möglichen falsch-positiven Ergebnissen.gl.TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN– Anzahl der per Transform Feedback geschriebenen Primitive.ext.TIME_ELAPSED_EXT– GPU-Laufzeit in Nanosekunden (nur mit der ErweiterungEXT_disjoint_timer_query_webgl2).
Typische Einsatzgebiete sind Occlusion Culling (verdeckte Objekte im nächsten Frame nicht zeichnen), GPU-Profiling und das Zählen von Ergebnissen aus Transform-Feedback-Durchläufen. Das Ergebnis steht frühestens nach Rückkehr zur Event-Loop bereit – also in der Regel erst in einem der folgenden Frames. Prüfe deshalb immer zuerst gl.QUERY_RESULT_AVAILABLE, bevor du gl.QUERY_RESULT liest.
Nicht mehr benötigte Abfrageobjekte solltest du mit gl.deleteQuery() freigeben. Query-Objekte lassen sich jedoch problemlos für viele Durchläufe wiederverwenden – das ist effizienter, als pro Frame neue anzulegen.
Rückgabewert
WebGLQuery-Objekt. Bei verlorenem Kontext (webglcontextlost) wird null zurückgegeben.Beispiele
Occlusion Query: Ist ein Objekt sichtbar?
const canvas = document.querySelector('canvas');
const gl = canvas.getContext('webgl2');
gl.enable(gl.DEPTH_TEST);
const query = gl.createQuery();
let queryPending = false;
const render = () => {
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// ... hier Verdecker (z. B. Wände) zeichnen ...
if (!queryPending) {
gl.beginQuery(gl.ANY_SAMPLES_PASSED_CONSERVATIVE, query);
// ... hier z. B. die Bounding-Box des Objekts zeichnen ...
gl.endQuery(gl.ANY_SAMPLES_PASSED_CONSERVATIVE);
queryPending = true;
} else if (gl.getQueryParameter(query, gl.QUERY_RESULT_AVAILABLE)) {
const visible = gl.getQueryParameter(query, gl.QUERY_RESULT);
console.log('Objekt sichtbar:', visible);
queryPending = false;
}
requestAnimationFrame(render);
};
requestAnimationFrame(render);
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) {
console.warn('Timer-Queries werden nicht unterstützt.');
} else {
const query = gl.createQuery();
gl.beginQuery(ext.TIME_ELAPSED_EXT, query);
gl.clearColor(0.1, 0.2, 0.3, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
// ... aufwendige Draw-Calls ...
gl.endQuery(ext.TIME_ELAPSED_EXT);
const poll = () => {
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 (!disjoint) {
requestAnimationFrame(poll);
} else {
gl.deleteQuery(query); // Messung ungültig
}
};
requestAnimationFrame(poll);
}
Transform Feedback: geschriebene Primitive zählen
const gl = document.querySelector('canvas').getContext('webgl2');
const query = gl.createQuery();
// Annahme: Programm mit transformFeedbackVaryings und gebundenem
// Transform-Feedback-Puffer ist bereits eingerichtet.
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);
const waitForResult = () => new Promise((resolve) => {
const check = () => gl.getQueryParameter(query, gl.QUERY_RESULT_AVAILABLE)
? resolve(gl.getQueryParameter(query, gl.QUERY_RESULT))
: setTimeout(check, 0);
check();
});
const count = await waitForResult();
console.log('Geschriebene Primitive:', count);
// Wichtig · Fallstricke
- Nicht im selben Frame abfragen: Laut Spezifikation ist
QUERY_RESULT_AVAILABLEerst nach Rückkehr zur Event-Looptrue. Busy-Waiting in einer Schleife blockiert den Tab und liefert trotzdem kein Ergebnis. - Nur eine aktive Abfrage pro Ziel: Ein zweites
beginQuery()für dasselbe Ziel vorendQuery()erzeugtINVALID_OPERATION. Ebenso darf ein Query-Objekt nach der ersten Nutzung nicht mit einem anderen Ziel verwendet werden. - Nur WebGL 2: In WebGL 1 gibt es
createQuery()nicht; dort existiert lediglichEXT_disjoint_timer_querymitcreateQueryEXT(). - Timer-Queries:
EXT_disjoint_timer_query_webgl2ist aus Sicherheitsgründen (Timing-Angriffe wie Spectre) nicht überall verfügbar bzw. hat reduzierte Genauigkeit. Immer aufnullprüfen undGPU_DISJOINT_EXTauswerten – ist ertrue, sind die Messwerte ungültig. - Kontextverlust: Nach
webglcontextlostsind alle Query-Objekte ungültig und müssen nachwebglcontextrestoredneu erzeugt werden. - Query-Objekte wiederverwenden statt pro Frame neu anzulegen; nicht mehr benötigte mit
deleteQuery()freigeben.