Signatur
Beschreibung
Die Methode createQueryEXT() gehört zur WebGL-1-Erweiterung EXT_disjoint_timer_query. Sie liefert ein leeres WebGLQuery-Objekt, das anschließend mit beginQueryEXT()/endQueryEXT() (Zeitspanne, Ziel TIME_ELAPSED_EXT) oder mit queryCounterEXT() (Zeitstempel, Ziel TIMESTAMP_EXT) verwendet wird.
Anders als performance.now(), das nur die CPU-Zeit bis zum Absetzen der Befehle misst, erfasst eine Timer-Abfrage, wie lange die GPU tatsächlich für die Ausführung benötigt hat. Das ist ideal für Profiling, adaptive Qualitätsstufen (z. B. Auflösung oder Schattenqualität dynamisch anpassen) und das Aufspüren teurer Render-Passes.
Ergebnisse stehen asynchron zur Verfügung – meist erst einige Frames später. Man fragt mit getQueryObjectEXT(query, ext.QUERY_RESULT_AVAILABLE_EXT) nach, ob das Ergebnis bereitsteht, und prüft zusätzlich gl.getParameter(ext.GPU_DISJOINT_EXT): Ist dieser Wert true, ist die Messung ungültig (z. B. durch Taktänderungen oder Kontextwechsel der GPU). Nicht mehr benötigte Abfragen gibt man mit deleteQueryEXT() frei.
In WebGL 2 existiert createQuery() nativ auf dem Kontext; dort nutzt man stattdessen die Erweiterung EXT_disjoint_timer_query_webgl2 für das Ziel TIME_ELAPSED_EXT.
Rückgabewert
WebGLQuery-Objekt. Es wird erst durch beginQueryEXT() bzw. queryCounterEXT() an ein Ziel gebunden.Beispiele
Abfrage erzeugen und Zeichenaufruf messen
const canvas = document.querySelector('canvas');
const gl = canvas.getContext('webgl');
const ext = gl.getExtension('EXT_disjoint_timer_query');
if (!ext) {
console.warn('Timer-Abfragen werden nicht unterstützt.');
} else {
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 Zeichenbefehle, z. B. gl.drawArrays(...)
ext.endQueryEXT(ext.TIME_ELAPSED_EXT);
// Ergebnis in späteren Frames abholen
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.warn('Messung ungültig (GPU disjoint).');
ext.deleteQueryEXT(query);
} else {
requestAnimationFrame(poll);
}
};
requestAnimationFrame(poll);
}
Zeitstempel mit queryCounterEXT()
const gl = document.querySelector('canvas').getContext('webgl');
const ext = gl.getExtension('EXT_disjoint_timer_query');
// Nur sinnvoll, wenn der Zeitstempel-Zähler Bits besitzt
if (ext && ext.getQueryEXT(ext.TIMESTAMP_EXT, ext.QUERY_COUNTER_BITS_EXT) > 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);
setTimeout(() => {
const ready = ext.getQueryObjectEXT(end, ext.QUERY_RESULT_AVAILABLE_EXT);
if (ready && !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 in ns:', t1 - t0);
}
ext.deleteQueryEXT(start);
ext.deleteQueryEXT(end);
}, 100);
}
Wiederverwendbarer Abfrage-Pool pro Frame
const gl = document.querySelector('canvas').getContext('webgl');
const ext = gl.getExtension('EXT_disjoint_timer_query');
const pending = [];
const renderFrame = () => {
const query = ext ? ext.createQueryEXT() : null;
if (query) ext.beginQueryEXT(ext.TIME_ELAPSED_EXT, query);
gl.clear(gl.COLOR_BUFFER_BIT);
// ... Szene zeichnen
if (query) {
ext.endQueryEXT(ext.TIME_ELAPSED_EXT);
pending.push(query);
}
// Ältere Abfragen auswerten
const disjoint = ext && gl.getParameter(ext.GPU_DISJOINT_EXT);
while (pending.length) {
const q = pending[0];
if (!ext.getQueryObjectEXT(q, ext.QUERY_RESULT_AVAILABLE_EXT) && !disjoint) break;
if (!disjoint) {
console.log('Frame-GPU-Zeit (ms):', ext.getQueryObjectEXT(q, ext.QUERY_RESULT_EXT) / 1e6);
}
ext.deleteQueryEXT(q);
pending.shift();
}
requestAnimationFrame(renderFrame);
};
requestAnimationFrame(renderFrame);
// Wichtig · Fallstricke
- Verfügbarkeit: Die Erweiterung wurde wegen des Timing-Angriffs „GLitch“ (2018) in vielen Browsern zeitweise deaktiviert und ist heute oft nur eingeschränkt oder gar nicht verfügbar (z. B. Firefox/Safari, viele Mobilgeräte). Immer das Ergebnis von
gl.getExtension()aufnullprüfen. - Geringere Genauigkeit: Aus Sicherheitsgründen (Fingerprinting, Seitenkanalangriffe) runden Browser die Ergebnisse teils ab oder fügen Rauschen hinzu.
- Asynchronität: Das Ergebnis ist fast nie im selben Frame verfügbar. Wer sofort
QUERY_RESULT_EXTabfragt, blockiert im schlimmsten Fall die Pipeline oder erhält0. - GPU_DISJOINT_EXT: Vor jeder Auswertung prüfen – bei
truesind alle laufenden Messungen zu verwerfen. - Pro Ziel darf nur eine Abfrage gleichzeitig aktiv sein; verschachtelte
beginQueryEXT()-Aufrufe erzeugenINVALID_OPERATION. - In WebGL-2-Kontexten ist diese Erweiterung nicht verfügbar; dort
gl.createQuery()zusammen mitEXT_disjoint_timer_query_webgl2verwenden. - Abfragen nach Gebrauch mit
deleteQueryEXT()freigeben, um GPU-Ressourcen nicht zu verschwenden.