Start · Sprachen · JavaScript · Referenz · WebGL2RenderingContext.getQueryParameter()

WebGL2RenderingContext.getQueryParameter()

Methode

Liest Informationen aus einem <code>WebGLQuery</code>-Objekt, also ob ein Ergebnis bereitsteht (<code>QUERY_RESULT_AVAILABLE</code>) oder den Ergebniswert selbst (<code>QUERY_RESULT</code>).

seit JavaScript WebGL 2.0 – Baseline: alle modernen Browser Kategorie: webapi

Signatur

WebGL2RenderingContext.prototype.getQueryParameter(query, pname)

Beschreibung

getQueryParameter() ist das Gegenstück zu beginQuery() und endQuery(). Damit fragt man das Ergebnis einer asynchronen GPU-Abfrage ab. Bei einer Occlusion Query (ANY_SAMPLES_PASSED bzw. ANY_SAMPLES_PASSED_CONSERVATIVE) erfährt man, ob beim Zeichnen überhaupt Pixel sichtbar waren. Bei TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN erhält man die Zahl der geschriebenen Primitive. Mit der Erweiterung EXT_disjoint_timer_query_webgl2 misst man zusätzlich GPU-Laufzeiten in Nanosekunden.

GPU-Abfragen laufen asynchron. Deshalb prüft man zuerst mit gl.QUERY_RESULT_AVAILABLE, ob das Ergebnis schon vorliegt. Erst danach liest man den Wert mit gl.QUERY_RESULT. Laut WebGL-2-Spezifikation wird ein Ergebnis nie im selben Frame verfügbar, in dem die Abfrage beendet wurde. Frühestens nach Rückgabe an die Ereignisschleife (z. B. im nächsten requestAnimationFrame) kann das Ergebnis bereitstehen.

Typische Einsatzgebiete sind Occlusion Culling (verdeckte Objekte beim Zeichnen überspringen), Profiling einzelner Render-Durchläufe und die Kontrolle von Transform-Feedback-Ausgaben.

Parameter

Name Typ Default Beschreibung
$query Pflicht WebGLQuery Das abzufragende Query-Objekt, erzeugt mit gl.createQuery() und zuvor mit beginQuery()/endQuery() verwendet.
$pname Pflicht GLenum Welche Information gelesen wird: gl.QUERY_RESULT_AVAILABLE (liegt ein Ergebnis vor?) oder gl.QUERY_RESULT (der Ergebniswert).

Rückgabewert

Typ
boolean | number | null
Beschreibung
Bei QUERY_RESULT_AVAILABLE ein boolean. Bei QUERY_RESULT eine number (GLuint, bei Timer-Queries GLuint64 in Nanosekunden). Occlusion Queries liefern 0 oder 1. Bei einem Fehler wird null zurückgegeben, z. B. bei ungültigem pname oder einer noch aktiven Abfrage.

Beispiele

Occlusion Query: Ist ein Objekt sichtbar?

const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl2');

const query = gl.createQuery();

// Bounding-Box des Objekts testweise zeichnen
gl.beginQuery(gl.ANY_SAMPLES_PASSED, query);
// drawBoundingBox(); // eigener Zeichenaufruf
gl.endQuery(gl.ANY_SAMPLES_PASSED);

// Ergebnis frühestens im nächsten Frame abfragen
const poll = () => {
  const available = gl.getQueryParameter(query, gl.QUERY_RESULT_AVAILABLE);
  if (!available) {
    requestAnimationFrame(poll);
    return;
  }
  const visible = gl.getQueryParameter(query, gl.QUERY_RESULT);
  console.log(visible ? 'Objekt sichtbar' : 'Objekt verdeckt');
};
requestAnimationFrame(poll);
Objekt sichtbar

GPU-Zeitmessung mit EXT_disjoint_timer_query_webgl2

const gl = document.querySelector('canvas').getContext('webgl2');
const ext = gl.getExtension('EXT_disjoint_timer_query_webgl2');

if (ext) {
  const query = gl.createQuery();
  gl.beginQuery(ext.TIME_ELAPSED_EXT, query);
  // renderScene(); // eigener Render-Durchlauf
  gl.endQuery(ext.TIME_ELAPSED_EXT);

  const check = () => {
    const available = gl.getQueryParameter(query, gl.QUERY_RESULT_AVAILABLE);
    const disjoint = gl.getParameter(ext.GPU_DISJOINT_EXT);
    if (available && !disjoint) {
      const ns = gl.getQueryParameter(query, gl.QUERY_RESULT);
      console.log(`GPU-Zeit: ${(ns / 1e6).toFixed(2)} ms`);
      gl.deleteQuery(query);
    } else if (!available) {
      requestAnimationFrame(check);
    }
  };
  requestAnimationFrame(check);
} else {
  console.log('Timer-Queries nicht verfügbar');
}
GPU-Zeit: 1.37 ms

Geschriebene Primitive bei Transform Feedback zählen

const gl = document.querySelector('canvas').getContext('webgl2');
const query = gl.createQuery();

gl.beginQuery(gl.TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, query);
// gl.beginTransformFeedback(gl.POINTS);
// gl.drawArrays(gl.POINTS, 0, 1000);
// gl.endTransformFeedback();
gl.endQuery(gl.TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);

const waitForResult = () => new Promise((resolve) => {
  const tick = () => {
    if (gl.getQueryParameter(query, gl.QUERY_RESULT_AVAILABLE)) {
      resolve(gl.getQueryParameter(query, gl.QUERY_RESULT));
    } else {
      requestAnimationFrame(tick);
    }
  };
  requestAnimationFrame(tick);
});

const count = await waitForResult();
console.log(`Primitive geschrieben: ${count}`);
Primitive geschrieben: 1000

// Wichtig · Fallstricke

  • Nicht im selben Frame abfragen: QUERY_RESULT_AVAILABLE liefert vor Rückkehr zur Ereignisschleife immer false. Eine Warteschleife mit while blockiert endlos. Stattdessen mit requestAnimationFrame oder setTimeout erneut prüfen.
  • Ein direkter Aufruf von QUERY_RESULT ohne vorherige Verfügbarkeitsprüfung kann die Pipeline anhalten und kostet viel Leistung.
  • Eine Abfrage, die gerade aktiv ist (zwischen beginQuery und endQuery), erzeugt INVALID_OPERATION, und der Rückgabewert ist null.
  • Bei Timer-Queries muss GPU_DISJOINT_EXT geprüft werden. Ist der Wert true, sind die Messwerte ungültig. Die Erweiterung ist in vielen Browsern aus Datenschutzgründen (Timing-Angriffe) deaktiviert oder in der Genauigkeit reduziert.
  • Die Methode gibt es nur in WebGL 2. In WebGL 1 steht nur die Erweiterung EXT_disjoint_timer_query mit getQueryObjectEXT() zur Verfügung.
  • Query-Objekte kann man wiederverwenden. Nicht mehr benötigte Objekte gibt man mit gl.deleteQuery() frei.