Signatur
Beschreibung
Die Erweiterung EXT_disjoint_timer_query ergänzt einen WebGLRenderingContext um Timer-Queries. Damit misst du, wie lange die GPU tatsächlich für bestimmte Zeichenbefehle braucht. Mit performance.now() geht das nicht, denn es misst nur die CPU-Zeit für das Absetzen der Befehle. Du holst die Erweiterung über gl.getExtension("EXT_disjoint_timer_query"). Ist sie nicht verfügbar, bekommst du null.
Das Objekt stellt folgende Methoden bereit:
createQueryEXT(),deleteQueryEXT(query),isQueryEXT(query): Query-Objekte (WebGLTimerQueryEXT) anlegen, löschen und prüfen.beginQueryEXT(ext.TIME_ELAPSED_EXT, query)/endQueryEXT(ext.TIME_ELAPSED_EXT): misst die Dauer aller Befehle zwischen den beiden Aufrufen.queryCounterEXT(query, ext.TIMESTAMP_EXT): speichert einen GPU-Zeitstempel.getQueryEXT(target, pname): Informationen zum Ziel, z. B.QUERY_COUNTER_BITS_EXToderCURRENT_QUERY_EXT.getQueryObjectEXT(query, pname): fragt mitQUERY_RESULT_AVAILABLE_EXTab, ob das Ergebnis schon da ist, und liest es mitQUERY_RESULT_EXTin Nanosekunden aus.
Die Messung läuft asynchron: Das Ergebnis liegt meist erst ein oder mehrere Frames später vor. Frag es deshalb in einer späteren requestAnimationFrame-Runde ab. Prüfe außerdem jedes Mal gl.getParameter(ext.GPU_DISJOINT_EXT). Steht dieser Wert auf true, ist etwas passiert, das die Messung verfälscht (z. B. ein Wechsel der GPU-Taktrate oder ein Kontextwechsel). Das Ergebnis solltest du dann verwerfen.
Für WebGL 2 gibt es die eigene Erweiterung EXT_disjoint_timer_query_webgl2. Sie nutzt die eingebauten Query-Methoden des WebGL2RenderingContext (gl.createQuery(), gl.beginQuery() usw.).
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $name Pflicht | string | Argument für gl.getExtension(): genau "EXT_disjoint_timer_query". |
|
| $target Pflicht | GLenum | Für beginQueryEXT/endQueryEXT: ext.TIME_ELAPSED_EXT. Für queryCounterEXT: ext.TIMESTAMP_EXT. |
|
| $query Pflicht | WebGLTimerQueryEXT | Das mit createQueryEXT() erzeugte Query-Objekt. |
|
| $pname Pflicht | GLenum | Für getQueryObjectEXT: ext.QUERY_RESULT_AVAILABLE_EXT oder ext.QUERY_RESULT_EXT. Für getQueryEXT: ext.QUERY_COUNTER_BITS_EXT oder ext.CURRENT_QUERY_EXT. |
Rückgabewert
gl.getExtension() gibt das Erweiterungsobjekt zurück oder null, wenn die Erweiterung nicht unterstützt wird. getQueryObjectEXT(query, QUERY_RESULT_EXT) liefert die gemessene Zeit in Nanosekunden als number.Beispiele
Erweiterung abfragen und Unterstützung prüfen
const canvas = document.createElement("canvas");
const gl = canvas.getContext("webgl");
const ext = gl?.getExtension("EXT_disjoint_timer_query");
if (!ext) {
console.log("Timer-Queries nicht verfügbar");
} else {
// Genauigkeit des Zählers in Bit
const bits = ext.getQueryEXT(ext.TIME_ELAPSED_EXT, ext.QUERY_COUNTER_BITS_EXT);
console.log(`TIME_ELAPSED-Zähler: ${bits} Bit`);
}
GPU-Zeit eines Draw-Calls asynchron messen
const gl = document.querySelector("canvas").getContext("webgl");
const ext = gl.getExtension("EXT_disjoint_timer_query");
const query = ext.createQueryEXT();
ext.beginQueryEXT(ext.TIME_ELAPSED_EXT, query);
gl.clearColor(0.1, 0.2, 0.3, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
// ... hier weitere Draw-Calls, z. B. gl.drawArrays(...)
ext.endQueryEXT(ext.TIME_ELAPSED_EXT);
const poll = () => {
const available = ext.getQueryObjectEXT(query, ext.QUERY_RESULT_AVAILABLE_EXT);
const disjoint = gl.getParameter(ext.GPU_DISJOINT_EXT);
if (available && !disjoint) {
const ns = ext.getQueryObjectEXT(query, ext.QUERY_RESULT_EXT);
console.log(`GPU-Zeit: ${(ns / 1e6).toFixed(3)} ms`);
ext.deleteQueryEXT(query);
} else if (disjoint) {
console.log("Messung ungültig (disjoint) – verworfen");
ext.deleteQueryEXT(query);
} else {
requestAnimationFrame(poll); // Ergebnis noch nicht da
}
};
requestAnimationFrame(poll);
Zeitstempel-Queries (TIMESTAMP_EXT)
const gl = document.querySelector("canvas").getContext("webgl");
const ext = gl.getExtension("EXT_disjoint_timer_query");
// Zeitstempel werden nicht überall unterstützt: 0 Bit = nicht verfügbar
const tsBits = ext.getQueryEXT(ext.TIMESTAMP_EXT, ext.QUERY_COUNTER_BITS_EXT);
if (tsBits > 0) {
const start = ext.createQueryEXT();
const end = ext.createQueryEXT();
ext.queryCounterEXT(start, ext.TIMESTAMP_EXT);
gl.clear(gl.COLOR_BUFFER_BIT);
ext.queryCounterEXT(end, ext.TIMESTAMP_EXT);
const check = () => {
const ready = ext.getQueryObjectEXT(end, ext.QUERY_RESULT_AVAILABLE_EXT);
if (!ready) return requestAnimationFrame(check);
if (!gl.getParameter(ext.GPU_DISJOINT_EXT)) {
const t0 = ext.getQueryObjectEXT(start, ext.QUERY_RESULT_EXT);
const t1 = ext.getQueryObjectEXT(end, ext.QUERY_RESULT_EXT);
console.log(`Dauer: ${(t1 - t0) / 1e6} ms`);
}
ext.deleteQueryEXT(start);
ext.deleteQueryEXT(end);
};
requestAnimationFrame(check);
} else {
console.log("TIMESTAMP_EXT nicht unterstützt");
}