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

EXT_disjoint_timer_query.createQueryEXT()

Methode

Erzeugt ein neues <code>WebGLQuery</code>-Objekt, mit dem sich die GPU-Ausführungszeit einer Folge von WebGL-Befehlen messen lässt.

seit JavaScript WebGL 1 Extension (nur verfügbar, wenn der Browser Kategorie: webapi

Signatur

EXT_disjoint_timer_query.createQueryEXT()

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

Typ
WebGLQuery
Beschreibung
Ein neues, noch nicht aktives 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);
}
GPU-Zeit: 0.042 ms

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);
}
Dauer in ns: 38400

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);
Frame-GPU-Zeit (ms): 1.27

// 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() auf null prü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_EXT abfragt, blockiert im schlimmsten Fall die Pipeline oder erhält 0.
  • GPU_DISJOINT_EXT: Vor jeder Auswertung prüfen – bei true sind alle laufenden Messungen zu verwerfen.
  • Pro Ziel darf nur eine Abfrage gleichzeitig aktiv sein; verschachtelte beginQueryEXT()-Aufrufe erzeugen INVALID_OPERATION.
  • In WebGL-2-Kontexten ist diese Erweiterung nicht verfügbar; dort gl.createQuery() zusammen mit EXT_disjoint_timer_query_webgl2 verwenden.
  • Abfragen nach Gebrauch mit deleteQueryEXT() freigeben, um GPU-Ressourcen nicht zu verschwenden.