Signatur
Beschreibung
Mit copyBufferSubData() kopierst du Daten zwischen zwei WebGL-Puffern, ohne dass sie über den Hauptspeicher (JavaScript) laufen. Die Kopie findet vollständig auf der GPU statt. Das ist deutlich schneller als der Umweg über getBufferSubData() und bufferSubData().
Typische Einsatzfälle: einen Puffer vergrößern (neuen, größeren Puffer anlegen und den alten Inhalt hineinkopieren), Ergebnisse aus Transform Feedback in einen Vertex-Puffer übernehmen, Sicherungskopien von Partikel-Zuständen anlegen oder Teilbereiche verschiedener Puffer zu einem Gesamtpuffer zusammenführen.
Die Quell- und Zielpuffer werden nicht direkt übergeben, sondern über ihre Bindungspunkte angesprochen. Damit du dabei keine anderen Bindungen (z. B. ARRAY_BUFFER) durcheinanderbringst, gibt es in WebGL 2 die speziellen Ziele gl.COPY_READ_BUFFER und gl.COPY_WRITE_BUFFER. Sie sind genau für solche Kopiervorgänge gedacht und haben sonst keine Wirkung auf das Rendering.
Alle Offsets und die Größe sind in Bytes angegeben, nicht in Elementen. Bei einem Float32Array musst du also mit Float32Array.BYTES_PER_ELEMENT (4) multiplizieren.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $readTarget Pflicht | GLenum | — | Bindungspunkt des Quellpuffers, z. B. gl.COPY_READ_BUFFER, gl.ARRAY_BUFFER, gl.ELEMENT_ARRAY_BUFFER, gl.UNIFORM_BUFFER, gl.TRANSFORM_FEEDBACK_BUFFER, gl.PIXEL_PACK_BUFFER oder gl.PIXEL_UNPACK_BUFFER. |
| $writeTarget Pflicht | GLenum | — | Bindungspunkt des Zielpuffers, meist gl.COPY_WRITE_BUFFER. Gleiche erlaubte Werte wie bei readTarget. |
| $readOffset Pflicht | GLintptr (number) | — | Byte-Offset im Quellpuffer, ab dem gelesen wird. Darf nicht negativ sein. |
| $writeOffset Pflicht | GLintptr (number) | — | Byte-Offset im Zielpuffer, ab dem geschrieben wird. Darf nicht negativ sein. |
| $size Pflicht | GLsizeiptr (number) | — | Anzahl der zu kopierenden Bytes. Darf nicht negativ sein. |
Rückgabewert
gl.getError() gemeldet.Beispiele
Einfache Kopie zwischen zwei Puffern
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl2');
const data = new Float32Array([1, 2, 3, 4, 5, 6]);
// Quellpuffer mit Daten füllen
const src = gl.createBuffer();
gl.bindBuffer(gl.COPY_READ_BUFFER, src);
gl.bufferData(gl.COPY_READ_BUFFER, data, gl.STATIC_DRAW);
// Leeren Zielpuffer gleicher Größe anlegen
const dst = gl.createBuffer();
gl.bindBuffer(gl.COPY_WRITE_BUFFER, dst);
gl.bufferData(gl.COPY_WRITE_BUFFER, data.byteLength, gl.STATIC_DRAW);
// Gesamten Inhalt auf der GPU kopieren
gl.copyBufferSubData(gl.COPY_READ_BUFFER, gl.COPY_WRITE_BUFFER, 0, 0, data.byteLength);
// Zur Kontrolle zurücklesen
const result = new Float32Array(6);
gl.getBufferSubData(gl.COPY_WRITE_BUFFER, 0, result);
console.log(result);
Teilbereich kopieren (Offsets in Bytes)
const gl = document.createElement('canvas').getContext('webgl2');
const BPE = Float32Array.BYTES_PER_ELEMENT; // 4
const src = gl.createBuffer();
gl.bindBuffer(gl.COPY_READ_BUFFER, src);
gl.bufferData(gl.COPY_READ_BUFFER, new Float32Array([10, 20, 30, 40]), gl.STATIC_DRAW);
const dst = gl.createBuffer();
gl.bindBuffer(gl.COPY_WRITE_BUFFER, dst);
gl.bufferData(gl.COPY_WRITE_BUFFER, new Float32Array([0, 0, 0, 0]), gl.STATIC_DRAW);
// Elemente 1 und 2 der Quelle an Position 2 im Ziel kopieren
gl.copyBufferSubData(gl.COPY_READ_BUFFER, gl.COPY_WRITE_BUFFER, 1 * BPE, 2 * BPE, 2 * BPE);
const out = new Float32Array(4);
gl.getBufferSubData(gl.COPY_WRITE_BUFFER, 0, out);
console.log(out);
Vertex-Puffer dynamisch vergrößern
const gl = document.createElement('canvas').getContext('webgl2');
// Puffer vergrößern und alten Inhalt übernehmen
const growBuffer = (gl, oldBuffer, oldSize, newSize) => {
const newBuffer = gl.createBuffer();
gl.bindBuffer(gl.COPY_READ_BUFFER, oldBuffer);
gl.bindBuffer(gl.COPY_WRITE_BUFFER, newBuffer);
gl.bufferData(gl.COPY_WRITE_BUFFER, newSize, gl.DYNAMIC_DRAW);
gl.copyBufferSubData(gl.COPY_READ_BUFFER, gl.COPY_WRITE_BUFFER, 0, 0, oldSize);
gl.bindBuffer(gl.COPY_READ_BUFFER, null);
gl.bindBuffer(gl.COPY_WRITE_BUFFER, null);
gl.deleteBuffer(oldBuffer);
return newBuffer;
};
const initial = new Float32Array(1024);
let vbo = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
gl.bufferData(gl.ARRAY_BUFFER, initial, gl.DYNAMIC_DRAW);
vbo = growBuffer(gl, vbo, initial.byteLength, initial.byteLength * 2);
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
console.log(gl.getBufferParameter(gl.ARRAY_BUFFER, gl.BUFFER_SIZE));
// Wichtig · Fallstricke
- Nur WebGL 2: Die Methode gibt es nicht auf
WebGLRenderingContext(WebGL 1). Prüfe, obcanvas.getContext('webgl2')nichtnullliefert. Alle aktuellen Browser unterstützen WebGL 2, Safari seit Version 15. - Bytes statt Elemente: Offsets und
sizesind Byte-Angaben. Ein häufiger Fehler ist, Elementanzahlen zu übergeben. INVALID_VALUEtritt auf, wenn ein Wert negativ ist, wennreadOffset + sizegrößer ist als der Quellpuffer oderwriteOffset + sizegrößer als der Zielpuffer, oder wenn sich bei gleichem Puffer Quell- und Zielbereich überschneiden.INVALID_OPERATIONtritt auf, wenn an einem der Ziele kein Puffer gebunden ist. In WebGL gilt zusätzlich: Ein Puffer, der einmal anELEMENT_ARRAY_BUFFERgebunden war, darf nicht mit Puffern anderer Ziele gemischt werden. Kopien zwischen Index- und Vertex-Puffern schlagen deshalb fehl.- Nimm bevorzugt
COPY_READ_BUFFER/COPY_WRITE_BUFFER, damit bestehende Bindungen (z. B. am VAO) unverändert bleiben. - Die Kopie läuft asynchron auf der GPU und blockiert nicht. Ein direkt folgendes
getBufferSubData()erzwingt dagegen eine Synchronisierung und kann das Programm ausbremsen.