Signatur
Beschreibung
Die Methode endTransformFeedback() schließt eine Transform-Feedback-Operation ab. Beim Transform Feedback schreibt der Vertex-Shader seine out-Varyings direkt in Puffer-Objekte (TRANSFORM_FEEDBACK_BUFFER), statt sie nur an den Rasterizer weiterzugeben. So lassen sich z. B. Partikelsysteme, Physik-Simulationen oder Geometrie-Transformationen vollständig auf der GPU berechnen.
Der typische Ablauf ist: Transform-Feedback-Objekt binden (bindTransformFeedback()), Ausgabepuffer mit bindBufferBase() anhängen, beginTransformFeedback(primitiveMode) aufrufen, zeichnen (drawArrays()), dann endTransformFeedback(). Erst danach dürfen die Puffer wieder als normale Vertex-Puffer gelesen oder mit getBufferSubData() ausgelesen werden.
Nach dem Aufruf ist das Transform Feedback nicht mehr aktiv; das Programm darf wieder gewechselt werden und das gebundene Transform-Feedback-Objekt kann neu gebunden werden. Soll eine Aufzeichnung nur kurz unterbrochen und später fortgesetzt werden, verwendet man stattdessen pauseTransformFeedback() und resumeTransformFeedback().
Beispiele
Vollständiger Transform-Feedback-Durchlauf (Werte 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(1.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 VOR dem Linken festlegen
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.ARRAY_BUFFER, outBuf);
gl.bufferData(gl.ARRAY_BUFFER, input.byteLength, gl.STATIC_READ);
gl.bindBuffer(gl.ARRAY_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); // kein Rendering nötig
gl.beginTransformFeedback(gl.POINTS);
gl.drawArrays(gl.POINTS, 0, input.length);
gl.endTransformFeedback();
gl.disable(gl.RASTERIZER_DISCARD);
// Puffer lösen, bevor er gelesen wird
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, null);
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null);
const result = new Float32Array(input.length);
gl.bindBuffer(gl.ARRAY_BUFFER, outBuf);
gl.getBufferSubData(gl.ARRAY_BUFFER, 0, result);
console.log(Array.from(result));
Partikel-Update mit Ping-Pong-Puffern pro Frame
// Annahme: gl, updateProgram (mit transformFeedbackVaryings),
// sowie je zwei VAOs/Puffer/TF-Objekte sind bereits eingerichtet.
let read = 0;
const PARTICLE_COUNT = 10000;
const step = () => {
const write = 1 - read;
gl.useProgram(updateProgram);
gl.bindVertexArray(vaos[read]);
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, tfs[write]);
gl.enable(gl.RASTERIZER_DISCARD);
gl.beginTransformFeedback(gl.POINTS);
gl.drawArrays(gl.POINTS, 0, PARTICLE_COUNT);
gl.endTransformFeedback(); // Aufzeichnung abschließen
gl.disable(gl.RASTERIZER_DISCARD);
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null);
read = write; // Puffer tauschen
requestAnimationFrame(step);
};
requestAnimationFrame(step);
Fehlerprüfung: end ohne begin
const gl = document.createElement('canvas').getContext('webgl2');
gl.endTransformFeedback(); // kein aktives Transform Feedback
console.log(gl.getError() === gl.INVALID_OPERATION);
// Wichtig · Fallstricke
- INVALID_OPERATION: Wird ausgelöst, wenn beim Aufruf kein Transform Feedback aktiv ist (also kein vorheriges
beginTransformFeedback()). - Ein pausiertes Transform Feedback darf direkt mit
endTransformFeedback()beendet werden; ein erneutesresumeTransformFeedback()ist nicht nötig. - Solange Transform Feedback aktiv ist, darf das Programm nicht gewechselt (
useProgram) oder neu gelinkt werden, und die Ausgabepuffer dürfen nicht gleichzeitig als Vertex-Eingabe gebunden sein – sonst gibt es Fehler. - Vor dem Auslesen mit
getBufferSubData()sollte der Puffer vomTRANSFORM_FEEDBACK_BUFFER-Bindungspunkt gelöst werden; WebGL verbietet, dass derselbe Puffer gleichzeitig an Transform Feedback und einen anderen Bindungspunkt gebunden ist. getBufferSubData()ist synchron und kann die GPU-Pipeline blockieren. Für bessere Performance einenfenceSync()setzen und asynchron abwarten.- Kompatibilität: Nur im
webgl2-Kontext verfügbar (nicht in WebGL 1). Unterstützt in allen aktuellen Browsern, Safari ab Version 15.