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

WebGL2RenderingContext.beginTransformFeedback()

Methode

Startet eine Transform-Feedback-Operation. Dabei werden die Ausgaben des Vertex-Shaders in gebundene Puffer geschrieben, statt nur gerastert zu werden.

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

Signatur

WebGL2RenderingContext.prototype.beginTransformFeedback(primitiveMode)

Beschreibung

beginTransformFeedback() startet den Transform-Feedback-Modus des aktuell gebundenen WebGLTransformFeedback-Objekts. Alle folgenden Zeichenaufrufe (drawArrays(), drawArraysInstanced()) schreiben die als „Varyings“ markierten Ausgaben des Vertex-Shaders in die Puffer, die an gl.TRANSFORM_FEEDBACK_BUFFER gebunden sind. Beendet wird der Modus mit endTransformFeedback().

Das ist nützlich für GPU-seitige Berechnungen, etwa Partikelsysteme, Physik-Simulationen oder vorab transformierte Geometrie. Die Daten bleiben dabei auf der GPU und müssen nicht in jedem Frame über JavaScript neu berechnet und hochgeladen werden. Ein typisches Muster ist das „Ping-Pong“: Zwei Puffer wechseln sich in jedem Frame als Eingabe und Ausgabe ab.

Vorher müssen die Ausgabe-Varyings mit transformFeedbackVaryings() festgelegt werden, und zwar vor linkProgram(). Außerdem muss das Programm mit useProgram() aktiv sein und für jede Varying ein Puffer per bindBufferBase() gebunden sein. Wenn nur berechnet und nichts gezeichnet werden soll, schaltet man mit gl.enable(gl.RASTERIZER_DISCARD) die Rasterisierung ab.

Parameter

Name Typ Default Beschreibung
$primitiveMode Pflicht GLenum Primitivtyp, der während der Operation aufgezeichnet wird: gl.POINTS, gl.LINES oder gl.TRIANGLES. Er muss zum Modus der folgenden Draw-Aufrufe passen. Bei POINTS sind nur POINTS erlaubt, bei LINES auch LINE_STRIP und LINE_LOOP, bei TRIANGLES auch TRIANGLE_STRIP und TRIANGLE_FAN.

Rückgabewert

Typ
void
Beschreibung
Kein Rückgabewert (undefined). Fehler erkennt man über gl.getError().

Beispiele

Grundablauf: Vertex-Daten auf der GPU verdoppeln

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

const vsSource = `#version 300 es
in float inValue;
out float outValue;
void main() {
  outValue = inValue * 2.0;
}`;
const fsSource = `#version 300 es
precision mediump float;
out vec4 color;
void main() { color = vec4(0.0); }`;

const compile = (type, src) => {
  const s = gl.createShader(type);
  gl.shaderSource(s, src);
  gl.compileShader(s);
  return s;
};

const program = gl.createProgram();
gl.attachShader(program, compile(gl.VERTEX_SHADER, vsSource));
gl.attachShader(program, compile(gl.FRAGMENT_SHADER, fsSource));
// Varyings MÜSSEN vor linkProgram() festgelegt werden
gl.transformFeedbackVaryings(program, ['outValue'], gl.SEPARATE_ATTRIBS);
gl.linkProgram(program);
gl.useProgram(program);

const input = new Float32Array([1, 2, 3, 4]);
const inBuf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, inBuf);
gl.bufferData(gl.ARRAY_BUFFER, input, gl.STATIC_DRAW);
const loc = gl.getAttribLocation(program, 'inValue');
gl.enableVertexAttribArray(loc);
gl.vertexAttribPointer(loc, 1, gl.FLOAT, false, 0, 0);

const outBuf = gl.createBuffer();
gl.bindBuffer(gl.TRANSFORM_FEEDBACK_BUFFER, outBuf);
gl.bufferData(gl.TRANSFORM_FEEDBACK_BUFFER, input.byteLength, gl.STATIC_READ);
gl.bindBuffer(gl.TRANSFORM_FEEDBACK_BUFFER, null);

const tf = gl.createTransformFeedback();
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, tf);
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, outBuf);

gl.enable(gl.RASTERIZER_DISCARD); // nur berechnen, nicht zeichnen
gl.beginTransformFeedback(gl.POINTS);
gl.drawArrays(gl.POINTS, 0, input.length);
gl.endTransformFeedback();
gl.disable(gl.RASTERIZER_DISCARD);

gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null);
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, null);

const result = new Float32Array(input.length);
gl.bindBuffer(gl.TRANSFORM_FEEDBACK_BUFFER, outBuf);
gl.getBufferSubData(gl.TRANSFORM_FEEDBACK_BUFFER, 0, result);
console.log(Array.from(result));
[2, 4, 6, 8]

Partikel-Update per Ping-Pong-Puffer (Ausschnitt einer Render-Schleife)

// Annahme: updateProgram, vaos[2], buffers[2] und tf wurden vorher angelegt.
// Die Varyings wurden mit transformFeedbackVaryings(['vPosition', 'vVelocity'], gl.INTERLEAVED_ATTRIBS) festgelegt.
let read = 0;

const step = (deltaTime) => {
  const write = 1 - read;
  gl.useProgram(updateProgram);
  gl.uniform1f(gl.getUniformLocation(updateProgram, 'uDelta'), deltaTime);

  gl.bindVertexArray(vaos[read]);             // Eingabe: alter Zustand
  gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, tf);
  gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, buffers[write]); // Ausgabe

  gl.enable(gl.RASTERIZER_DISCARD);
  gl.beginTransformFeedback(gl.POINTS);
  gl.drawArrays(gl.POINTS, 0, PARTICLE_COUNT);
  gl.endTransformFeedback();
  gl.disable(gl.RASTERIZER_DISCARD);

  // Puffer lösen, bevor er als Vertex-Eingabe dient
  gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, null);
  gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null);

  read = write; // Rollen tauschen
};

Anzahl geschriebener Primitive per Query ermitteln

const query = gl.createQuery();
gl.beginQuery(gl.TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, query);

gl.beginTransformFeedback(gl.TRIANGLES);
gl.drawArrays(gl.TRIANGLES, 0, 30);
gl.endTransformFeedback();

gl.endQuery(gl.TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);

// Ergebnisse sind asynchron verfügbar
const poll = () => {
  if (gl.getQueryParameter(query, gl.QUERY_RESULT_AVAILABLE)) {
    console.log('Primitive:', gl.getQueryParameter(query, gl.QUERY_RESULT));
  } else {
    requestAnimationFrame(poll);
  }
};
poll();
Primitive: 10