Start · Sprachen · JavaScript · Referenz · EXT_disjoint_timer_query.endQueryEXT()

EXT_disjoint_timer_query.endQueryEXT()

Methode

Beendet die aktuell laufende GPU-Timer-Abfrage für das angegebene Ziel, die zuvor mit <code>beginQueryEXT()</code> gestartet wurde.

seit JavaScript WebGL 1 Extension (nur WebGL1-Kontexte; in WebGL2 Kategorie: webapi

Signatur

EXT_disjoint_timer_query.prototype.endQueryEXT(target)

Beschreibung

endQueryEXT() gehört zur WebGL-Erweiterung EXT_disjoint_timer_query. Damit lässt sich messen, wie lange die GPU für eine Folge von Zeichenbefehlen braucht. Eine Messung startet man mit beginQueryEXT(ext.TIME_ELAPSED_EXT, query), danach folgen die zu messenden Aufrufe wie drawArrays() oder drawElements(), und endQueryEXT(ext.TIME_ELAPSED_EXT) schließt die Messung ab.

Die Methode bekommt nur das Ziel (target) übergeben, nicht das Query-Objekt selbst. Pro Ziel kann immer nur eine Abfrage aktiv sein, deshalb weiß WebGL, welche Abfrage beendet werden soll.

Das Ergebnis liegt asynchron vor. Nach endQueryEXT() fragt man in späteren Frames mit getQueryObjectEXT(query, ext.QUERY_RESULT_AVAILABLE_EXT) ab, ob der Wert schon bereitsteht. Erst dann liest man mit ext.QUERY_RESULT_EXT die verstrichene Zeit in Nanosekunden aus. Vorher prüft man gl.getParameter(ext.GPU_DISJOINT_EXT): Ist der Wert true, ist die Messung ungültig, etwa weil der Takt gewechselt hat oder die GPU zurückgesetzt wurde.

Parameter

Name Typ Default Beschreibung
$target Pflicht GLenum Ziel der Abfrage, die beendet werden soll. Möglich ist nur ext.TIME_ELAPSED_EXT. Zeitstempel (ext.TIMESTAMP_EXT) setzt man dagegen mit queryCounterEXT(), nicht mit begin/end.

Rückgabewert

Typ
void
Beschreibung
Kein Rückgabewert (undefined). Das Messergebnis holt man später mit getQueryObjectEXT().

Beispiele

Zeichenaufruf messen und Ergebnis auslesen

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 query = ext.createQueryEXT();

  ext.beginQueryEXT(ext.TIME_ELAPSED_EXT, query);
  gl.clearColor(0.2, 0.3, 0.4, 1);
  gl.clear(gl.COLOR_BUFFER_BIT); // hier stünden normalerweise drawArrays()-Aufrufe
  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)');
      ext.deleteQueryEXT(query);
    } else {
      requestAnimationFrame(poll);
    }
  };
  requestAnimationFrame(poll);
}
GPU-Zeit: 0.042 ms

Wiederverwendbare Messfunktion für die Render-Schleife

const gl = document.querySelector('canvas').getContext('webgl');
const ext = gl.getExtension('EXT_disjoint_timer_query');
const pending = [];

const measure = (label, drawFn) => {
  if (!ext) return drawFn();
  const query = ext.createQueryEXT();
  ext.beginQueryEXT(ext.TIME_ELAPSED_EXT, query);
  drawFn();
  ext.endQueryEXT(ext.TIME_ELAPSED_EXT); // nur das Ziel, nicht die Query übergeben
  pending.push({ label, query });
};

const collectResults = () => {
  if (!ext) return;
  const disjoint = gl.getParameter(ext.GPU_DISJOINT_EXT);
  for (let i = pending.length - 1; i >= 0; i--) {
    const { label, query } = pending[i];
    if (disjoint || ext.getQueryObjectEXT(query, ext.QUERY_RESULT_AVAILABLE_EXT)) {
      if (!disjoint) {
        const ns = ext.getQueryObjectEXT(query, ext.QUERY_RESULT_EXT);
        console.log(`${label}: ${(ns / 1e6).toFixed(2)} ms`);
      }
      ext.deleteQueryEXT(query);
      pending.splice(i, 1);
    }
  }
};

const frame = () => {
  measure('Szene', () => {
    gl.clearColor(0, 0, 0, 1);
    gl.clear(gl.COLOR_BUFFER_BIT);
  });
  collectResults();
  requestAnimationFrame(frame);
};
requestAnimationFrame(frame);
Szene: 0.05 ms

Fehler: endQueryEXT ohne aktive Abfrage

const gl = document.createElement('canvas').getContext('webgl');
const ext = gl.getExtension('EXT_disjoint_timer_query');

if (ext) {
  ext.endQueryEXT(ext.TIME_ELAPSED_EXT); // keine Abfrage aktiv
  console.log(gl.getError() === gl.INVALID_OPERATION);
}
true

// Wichtig · Fallstricke

  • Verfügbarkeit: Die Erweiterung ist wegen Timing-Angriffen (z. B. GLitch/Spectre) in vielen Browsern abgeschaltet oder nur mit gröberer Zeitauflösung verfügbar. Prüfe gl.getExtension() immer auf null. Sie funktioniert nur in WebGL1-Kontexten. In WebGL2 nimmst du EXT_disjoint_timer_query_webgl2 mit gl.endQuery().
  • Wenn für das Ziel keine Abfrage aktiv ist, erzeugt der Aufruf INVALID_OPERATION. Ein falsches Ziel erzeugt INVALID_ENUM.
  • Das Ergebnis ist nie im selben Frame verfügbar. Blockierendes Warten in einer Schleife bringt nichts, weil der Browser Ergebnisse erst nach Rückkehr in die Ereignisschleife aktualisiert. Frage die Ergebnisse deshalb in späteren Frames ab.
  • Verwirf die Messwerte, sobald GPU_DISJOINT_EXT den Wert true liefert.
  • Timer-Abfragen können nicht verschachtelt werden: Solange eine TIME_ELAPSED_EXT-Abfrage läuft, kann keine zweite starten.
  • Das Ergebnis ist in Nanosekunden angegeben und kann Werte über 253 erreichen. In der Praxis ist das bei Laufzeiten aber unkritisch.