Signatur
Beschreibung
queryCounterEXT() gehört zur WebGL-Erweiterung EXT_disjoint_timer_query. Die Methode schreibt einen GPU-Zeitstempel in ein WebGLTimerQueryEXT-Objekt. Einen Zeitraum wie bei beginQueryEXT()/endQueryEXT() misst sie nicht. Aufgezeichnet wird der Zeitpunkt, an dem die GPU alle vorher abgesetzten Befehle vollständig abgearbeitet hat. Der Wert wird in Nanosekunden angegeben.
Wenn du zwei Zeitstempel vor und nach einer Befehlsfolge setzt und die Differenz bildest, erhältst du die tatsächliche GPU-Dauer dieses Abschnitts. Anders als bei beginQueryEXT() dürfen Zeitstempel-Abfragen verschachtelt oder überlappend verwendet werden. Deshalb eignen sie sich gut für Profiler, die mehrere Render-Durchläufe eines Frames getrennt messen.
Das Ergebnis liegt asynchron vor. Frühestens in einem späteren Frame prüfst du mit getQueryObjectEXT(query, ext.QUERY_RESULT_AVAILABLE_EXT), ob es bereit ist, und liest es dann mit ext.QUERY_RESULT_EXT aus. Prüfe außerdem immer gl.getParameter(ext.GPU_DISJOINT_EXT). Ist der Wert true, ist in der Zwischenzeit etwas passiert, das die Zeitmessung ungültig macht, zum Beispiel ein Taktwechsel der GPU. Die Messwerte musst du dann verwerfen.
In WebGL 2 gibt es dafür die eigene Erweiterung EXT_disjoint_timer_query_webgl2. Sie stellt queryCounterEXT() mit denselben Parametern bereit, verwendet für die Abfrageobjekte aber die normalen Methoden des WebGL2-Kontexts (gl.createQuery(), gl.getQueryParameter()).
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $query Pflicht | WebGLTimerQueryEXT | Das Abfrageobjekt, das den Zeitstempel aufnimmt. In WebGL 1 wird es mit ext.createQueryEXT() erzeugt, in WebGL 2 mit gl.createQuery(). |
|
| $target Pflicht | GLenum | Muss ext.TIMESTAMP_EXT sein. Andere Werte führen zu gl.INVALID_ENUM. |
Rückgabewert
getQueryObjectEXT() bzw. gl.getQueryParameter() gelesen.Beispiele
GPU-Dauer eines Zeichenaufrufs mit zwei Zeitstempeln messen (WebGL 1)
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 {
const start = ext.createQueryEXT();
const end = ext.createQueryEXT();
ext.queryCounterEXT(start, ext.TIMESTAMP_EXT);
gl.clearColor(0.2, 0.4, 0.6, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
// ... weitere Zeichenbefehle ...
ext.queryCounterEXT(end, ext.TIMESTAMP_EXT);
const poll = () => {
const available = ext.getQueryObjectEXT(end, ext.QUERY_RESULT_AVAILABLE_EXT);
const disjoint = gl.getParameter(ext.GPU_DISJOINT_EXT);
if (available && !disjoint) {
const t0 = ext.getQueryObjectEXT(start, ext.QUERY_RESULT_EXT);
const t1 = ext.getQueryObjectEXT(end, ext.QUERY_RESULT_EXT);
console.log(`GPU-Zeit: ${((t1 - t0) / 1e6).toFixed(3)} ms`);
ext.deleteQueryEXT(start);
ext.deleteQueryEXT(end);
} else if (disjoint) {
console.log('Messung ungültig (disjoint)');
} else {
requestAnimationFrame(poll);
}
};
requestAnimationFrame(poll);
}
Zeitstempel in WebGL 2 mit EXT_disjoint_timer_query_webgl2
const gl = document.createElement('canvas').getContext('webgl2');
const ext = gl.getExtension('EXT_disjoint_timer_query_webgl2');
if (ext) {
const q0 = gl.createQuery();
const q1 = gl.createQuery();
ext.queryCounterEXT(q0, ext.TIMESTAMP_EXT);
gl.clear(gl.COLOR_BUFFER_BIT);
ext.queryCounterEXT(q1, ext.TIMESTAMP_EXT);
const check = () => {
const ready = gl.getQueryParameter(q1, gl.QUERY_RESULT_AVAILABLE);
if (!ready) return requestAnimationFrame(check);
if (gl.getParameter(ext.GPU_DISJOINT_EXT)) return console.log('verworfen');
const ns = gl.getQueryParameter(q1, gl.QUERY_RESULT) - gl.getQueryParameter(q0, gl.QUERY_RESULT);
console.log(`Clear dauerte ${ns} ns`);
};
requestAnimationFrame(check);
}
Unterstützung für Zeitstempel prüfen
const gl = document.createElement('canvas').getContext('webgl');
const ext = gl.getExtension('EXT_disjoint_timer_query');
// Manche Implementierungen haben einen 0-Bit-Zähler für Zeitstempel
const bits = ext ? ext.getQueryEXT(ext.TIMESTAMP_EXT, ext.QUERY_COUNTER_BITS_EXT) : 0;
console.log(bits > 0 ? `Zeitstempel mit ${bits} Bit` : 'queryCounterEXT nicht nutzbar');
// Wichtig · Fallstricke
- Verfügbarkeit: Wegen der Timing-Angriffe Spectre und GLitch wurde die Erweiterung in vielen Browsern zeitweise abgeschaltet. Firefox und Safari bieten sie in der Regel nicht an. Verlass dich deshalb nie darauf, dass sie vorhanden ist, und prüfe immer vorher mit
getExtension(). - Auch wenn die Erweiterung vorhanden ist, kann
QUERY_COUNTER_BITS_EXTfürTIMESTAMP_EXTden Wert0haben. Zeitstempel werden dann nicht unterstützt, auch wennbeginQueryEXT()/endQueryEXT()funktionieren. - Wenn du das Ergebnis im selben Frame liest, ist es nie verfügbar. Frag es frühestens im nächsten Frame ab, sonst blockiert oder verzögert sich die Pipeline.
- Wird
GPU_DISJOINT_EXTgemeldet, müssen alle offenen Messwerte verworfen werden. - Ist
querygerade in einer aktivenbeginQueryEXT()-Messung in Gebrauch, löst der AufrufINVALID_OPERATIONaus. Dasselbe gilt, wenn das Objekt zuvor schon mit einem anderen Ziel verwendet wurde. - Die Werte sind 64-Bit-Nanosekunden. Als JavaScript-
numberkann dabei Genauigkeit verloren gehen, für Differenzen im Frame-Bereich reicht sie aber aus.