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

WebGL2RenderingContext.createQuery()

Methode

Erstellt ein neues <code>WebGLQuery</code>-Objekt, mit dem sich GPU-Informationen wie Verdeckung, geschriebene Primitive oder die Renderzeit asynchron abfragen lassen.

seit JavaScript WebGL 2 – Baseline: alle modernen Browser (seit 20 Kategorie: webapi

Signatur

WebGL2RenderingContext.prototype.createQuery()

Beschreibung

createQuery() legt ein leeres WebGLQuery-Objekt an. Dieses Objekt ist ein Behälter für eine asynchrone GPU-Abfrage: Zwischen gl.beginQuery(target, query) und gl.endQuery(target) sammelt die GPU Informationen, die du später mit gl.getQueryParameter() ausliest, ohne die Render-Pipeline anzuhalten.

Welche Informationen gesammelt werden, legt erst das Ziel (target) beim Aufruf von beginQuery() fest:

  • gl.ANY_SAMPLES_PASSED – Occlusion Query: Wurde mindestens ein Fragment gezeichnet (Tiefentest bestanden)?
  • gl.ANY_SAMPLES_PASSED_CONSERVATIVE – wie oben, aber schneller und mit möglichen falsch-positiven Ergebnissen.
  • gl.TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN – Anzahl der per Transform Feedback geschriebenen Primitive.
  • ext.TIME_ELAPSED_EXT – GPU-Laufzeit in Nanosekunden (nur mit der Erweiterung EXT_disjoint_timer_query_webgl2).

Typische Einsatzgebiete sind Occlusion Culling (verdeckte Objekte im nächsten Frame nicht zeichnen), GPU-Profiling und das Zählen von Ergebnissen aus Transform-Feedback-Durchläufen. Das Ergebnis steht frühestens nach Rückkehr zur Event-Loop bereit – also in der Regel erst in einem der folgenden Frames. Prüfe deshalb immer zuerst gl.QUERY_RESULT_AVAILABLE, bevor du gl.QUERY_RESULT liest.

Nicht mehr benötigte Abfrageobjekte solltest du mit gl.deleteQuery() freigeben. Query-Objekte lassen sich jedoch problemlos für viele Durchläufe wiederverwenden – das ist effizienter, als pro Frame neue anzulegen.

Rückgabewert

Typ
WebGLQuery | null
Beschreibung
Ein neues WebGLQuery-Objekt. Bei verlorenem Kontext (webglcontextlost) wird null zurückgegeben.

Beispiele

Occlusion Query: Ist ein Objekt sichtbar?

const canvas = document.querySelector('canvas');
const gl = canvas.getContext('webgl2');
gl.enable(gl.DEPTH_TEST);

const query = gl.createQuery();
let queryPending = false;

const render = () => {
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  // ... hier Verdecker (z. B. Wände) zeichnen ...

  if (!queryPending) {
    gl.beginQuery(gl.ANY_SAMPLES_PASSED_CONSERVATIVE, query);
    // ... hier z. B. die Bounding-Box des Objekts zeichnen ...
    gl.endQuery(gl.ANY_SAMPLES_PASSED_CONSERVATIVE);
    queryPending = true;
  } else if (gl.getQueryParameter(query, gl.QUERY_RESULT_AVAILABLE)) {
    const visible = gl.getQueryParameter(query, gl.QUERY_RESULT);
    console.log('Objekt sichtbar:', visible);
    queryPending = false;
  }

  requestAnimationFrame(render);
};
requestAnimationFrame(render);
Objekt sichtbar: true

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) {
  console.warn('Timer-Queries werden nicht unterstützt.');
} else {
  const query = gl.createQuery();
  gl.beginQuery(ext.TIME_ELAPSED_EXT, query);
  gl.clearColor(0.1, 0.2, 0.3, 1);
  gl.clear(gl.COLOR_BUFFER_BIT);
  // ... aufwendige Draw-Calls ...
  gl.endQuery(ext.TIME_ELAPSED_EXT);

  const poll = () => {
    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(3)} ms`);
      gl.deleteQuery(query);
    } else if (!disjoint) {
      requestAnimationFrame(poll);
    } else {
      gl.deleteQuery(query); // Messung ungültig
    }
  };
  requestAnimationFrame(poll);
}
GPU-Zeit: 0.042 ms

Transform Feedback: geschriebene Primitive zählen

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

// Annahme: Programm mit transformFeedbackVaryings und gebundenem
// Transform-Feedback-Puffer ist bereits eingerichtet.
gl.enable(gl.RASTERIZER_DISCARD);
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);
gl.disable(gl.RASTERIZER_DISCARD);

const waitForResult = () => new Promise((resolve) => {
  const check = () => gl.getQueryParameter(query, gl.QUERY_RESULT_AVAILABLE)
    ? resolve(gl.getQueryParameter(query, gl.QUERY_RESULT))
    : setTimeout(check, 0);
  check();
});

const count = await waitForResult();
console.log('Geschriebene Primitive:', count);
Geschriebene Primitive: 1000

// Wichtig · Fallstricke

  • Nicht im selben Frame abfragen: Laut Spezifikation ist QUERY_RESULT_AVAILABLE erst nach Rückkehr zur Event-Loop true. Busy-Waiting in einer Schleife blockiert den Tab und liefert trotzdem kein Ergebnis.
  • Nur eine aktive Abfrage pro Ziel: Ein zweites beginQuery() für dasselbe Ziel vor endQuery() erzeugt INVALID_OPERATION. Ebenso darf ein Query-Objekt nach der ersten Nutzung nicht mit einem anderen Ziel verwendet werden.
  • Nur WebGL 2: In WebGL 1 gibt es createQuery() nicht; dort existiert lediglich EXT_disjoint_timer_query mit createQueryEXT().
  • Timer-Queries: EXT_disjoint_timer_query_webgl2 ist aus Sicherheitsgründen (Timing-Angriffe wie Spectre) nicht überall verfügbar bzw. hat reduzierte Genauigkeit. Immer auf null prüfen und GPU_DISJOINT_EXT auswerten – ist er true, sind die Messwerte ungültig.
  • Kontextverlust: Nach webglcontextlost sind alle Query-Objekte ungültig und müssen nach webglcontextrestored neu erzeugt werden.
  • Query-Objekte wiederverwenden statt pro Frame neu anzulegen; nicht mehr benötigte mit deleteQuery() freigeben.