Signatur
Beschreibung
WebGLTransformFeedback gehört zur WebGL-2-API und steht für ein Transform-Feedback-Objekt. Transform Feedback bedeutet: Die out-Variablen eines Vertex-Shaders werden direkt in einen oder mehrere WebGLBuffer geschrieben, statt nur an den Rasterizer weitergereicht zu werden. So lassen sich Berechnungen auf der GPU ausführen und ihre Ergebnisse wiederverwenden, ohne dass Daten über die CPU laufen.
Typische Einsatzfälle sind GPU-Partikelsysteme (Position und Geschwindigkeit werden jedes Bild im Shader fortgeschrieben und per „Ping-Pong“ zwischen zwei Puffern gewechselt), Physik- und Simulationsschritte sowie allgemeine GPGPU-Aufgaben, für die man sonst Compute-Shader bräuchte, die es in WebGL nicht gibt.
Das Objekt hat selbst keine Eigenschaften oder Methoden. Man erzeugt es mit gl.createTransformFeedback(), bindet es mit gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, tf) und hängt Ausgabepuffer mit gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, index, buffer) an. Welche Shader-Ausgaben erfasst werden, legt gl.transformFeedbackVaryings() fest. Das muss vor gl.linkProgram() aufgerufen werden. Die Aufnahme läuft zwischen gl.beginTransformFeedback() und gl.endTransformFeedback(). Mit gl.pauseTransformFeedback() und gl.resumeTransformFeedback() lässt sie sich unterbrechen und fortsetzen.
Freigegeben wird das Objekt mit gl.deleteTransformFeedback(tf). Mit gl.isTransformFeedback(tf) prüft man, ob es sich um ein gültiges Objekt handelt.
Rückgabewert
WebGL2RenderingContext.createTransformFeedback() zurückgegeben. Das Objekt ist undurchsichtig und hat keine eigenen Eigenschaften oder Methoden.Beispiele
Transform-Feedback-Objekt erzeugen und binden
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl2');
if (!gl) throw new Error('WebGL 2 wird nicht unterstützt');
const tf = gl.createTransformFeedback();
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, tf);
console.log(tf instanceof WebGLTransformFeedback);
console.log(gl.isTransformFeedback(tf));
Werte auf der GPU verdoppeln und zurücklesen
const gl = document.createElement('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 highp 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);
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(s));
return s;
};
const program = gl.createProgram();
gl.attachShader(program, compile(gl.VERTEX_SHADER, vsSource));
gl.attachShader(program, compile(gl.FRAGMENT_SHADER, fsSource));
// Muss VOR linkProgram aufgerufen 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.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 Rendern nötig
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.ARRAY_BUFFER, outBuf);
gl.getBufferSubData(gl.ARRAY_BUFFER, 0, result);
console.log(Array.from(result));
Aufnahme pausieren und Objekt freigeben
const gl = document.createElement('canvas').getContext('webgl2');
const tf = gl.createTransformFeedback();
// ... Programm, Puffer und bindBufferBase wie oben einrichten ...
// gl.beginTransformFeedback(gl.POINTS);
// gl.drawArrays(gl.POINTS, 0, 100);
// gl.pauseTransformFeedback(); // z. B. für einen Zwischen-Draw ohne Aufnahme
// gl.resumeTransformFeedback();
// gl.endTransformFeedback();
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null);
gl.deleteTransformFeedback(tf);
console.log(gl.isTransformFeedback(tf));
// Wichtig · Fallstricke
- Nur WebGL 2: Transform Feedback gibt es nur mit einem
webgl2-Kontext. WebGL 1 bietet dafür keine Erweiterung. Alle modernen Browser unterstützen es. Safari erst ab Version 15. gl.transformFeedbackVaryings()muss vorgl.linkProgram()aufgerufen werden, sonst werden die Ausgaben nicht erfasst.- Ein Puffer darf nicht gleichzeitig als Ausgabe (
TRANSFORM_FEEDBACK_BUFFER) und als Eingabe (z. B.ARRAY_BUFFERdes Vertex-Attributs) gebunden sein. Sonst meldet der BrowserINVALID_OPERATION. Für Simulationen deshalb zwei Puffer im Wechsel („Ping-Pong“) verwenden. - Der Primitivtyp von
drawArraysmuss zu dem Modus passen, der beibeginTransformFeedbackangegeben wurde (POINTS,LINESoderTRIANGLES). - Solange Transform Feedback aktiv ist, darf man weder Programm noch Puffer-Bindungen ändern. Ohne Fragment-Ausgabe spart
gl.RASTERIZER_DISCARDRechenzeit. - Ein synchrones
getBufferSubDatadirekt nach dem Zeichnen blockiert die Pipeline. Besser ist es, mitgl.fenceSync()undclientWaitSynczu warten, bis die GPU fertig ist. - Geht der Kontext verloren (
webglcontextlost), werden alle Transform-Feedback-Objekte ungültig und müssen neu erzeugt werden.