Signatur
Beschreibung
bufferSubData() schreibt neue Daten in einen bereits angelegten Puffer, und zwar ab dem Byte-Offset dstByteOffset. Anders als bufferData() wird dabei kein neuer Speicher auf der GPU angelegt. Die Größe des Puffers bleibt gleich, nur der angegebene Bereich wird ersetzt. Deshalb eignet sich die Methode gut für Daten, die sich häufig ändern, z. B. Partikelpositionen, Instanzdaten in jedem Frame, Uniform-Buffer-Blöcke oder einzelne geänderte Vertices.
WebGL 2 bietet gegenüber WebGL 1 eine erweiterte Überladung mit den Parametern srcOffset und length. Damit lässt sich ein Ausschnitt eines großen TypedArray hochladen, ohne vorher mit subarray() eine neue Ansicht anzulegen. Wichtig: srcOffset und length zählen Elemente des TypedArray (bei DataView Bytes). dstByteOffset zählt dagegen immer Bytes.
Typischer Ablauf: Puffer einmal mit bufferData(target, byteSize, gl.DYNAMIC_DRAW) in der gewünschten Größe anlegen, dann bei jeder Änderung mit bindBuffer() binden und bufferSubData() aufrufen.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $target Pflicht | GLenum | Bindungspunkt des Puffers: gl.ARRAY_BUFFER, gl.ELEMENT_ARRAY_BUFFER, gl.COPY_READ_BUFFER, gl.COPY_WRITE_BUFFER, gl.TRANSFORM_FEEDBACK_BUFFER, gl.UNIFORM_BUFFER, gl.PIXEL_PACK_BUFFER oder gl.PIXEL_UNPACK_BUFFER. |
|
| $dstByteOffset Pflicht | GLintptr (number) | Offset im Zielpuffer in Bytes, ab dem die Daten geschrieben werden. | |
| $srcData Pflicht | ArrayBuffer | SharedArrayBuffer | TypedArray | DataView | Quelldaten, die in den Puffer kopiert werden. | |
| $srcOffset | GLuint (number) | 0 | Start in srcData, gezählt in Elementen des TypedArray (bei DataView in Bytes). Nur bei TypedArray/DataView erlaubt. |
| $length | GLuint (number) | 0 | Anzahl der zu kopierenden Elemente ab srcOffset. Bei 0 wird alles bis zum Ende von srcData kopiert. |
Rückgabewert
undefined). Fehler werden über gl.getError() gemeldet.Beispiele
Einzelnen Vertex in einem dynamischen Puffer aktualisieren
const canvas = document.querySelector('canvas');
const gl = canvas.getContext('webgl2');
// Dreieck mit 3 Vertices à 2 Floats (x, y)
const positions = new Float32Array([0, 0.5, -0.5, -0.5, 0.5, -0.5]);
const vbo = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.DYNAMIC_DRAW);
// Nur den zweiten Vertex verschieben: Offset = 1 Vertex * 2 Floats * 4 Bytes
const newVertex = new Float32Array([-0.8, -0.8]);
gl.bufferSubData(gl.ARRAY_BUFFER, 1 * 2 * Float32Array.BYTES_PER_ELEMENT, newVertex);
console.log(gl.getError() === gl.NO_ERROR);
Ausschnitt eines großen Arrays per srcOffset/length hochladen
const gl = document.querySelector('canvas').getContext('webgl2');
const PARTICLES = 10000;
const data = new Float32Array(PARTICLES * 3); // x, y, z
const vbo = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
gl.bufferData(gl.ARRAY_BUFFER, data.byteLength, gl.DYNAMIC_DRAW);
// Nur Partikel 100 bis 199 haben sich geändert
const first = 100;
const count = 100;
for (let i = first * 3; i < (first + count) * 3; i++) data[i] = Math.random();
gl.bufferSubData(
gl.ARRAY_BUFFER,
first * 3 * Float32Array.BYTES_PER_ELEMENT, // Ziel in Bytes
data,
first * 3, // srcOffset in Elementen
count * 3 // length in Elementen
);
console.log('Hochgeladene Bytes:', count * 3 * 4);
Uniform Buffer Object in jedem Frame aktualisieren
const gl = document.querySelector('canvas').getContext('webgl2');
// UBO für einen Block mit vec4 color + float time (std140: 32 Bytes)
const ubo = gl.createBuffer();
gl.bindBuffer(gl.UNIFORM_BUFFER, ubo);
gl.bufferData(gl.UNIFORM_BUFFER, 32, gl.DYNAMIC_DRAW);
gl.bindBufferBase(gl.UNIFORM_BUFFER, 0, ubo);
const uniforms = new Float32Array(8);
const render = (time) => {
uniforms.set([1, 0.5, 0.2, 1], 0); // color
uniforms[4] = time / 1000; // time
gl.bindBuffer(gl.UNIFORM_BUFFER, ubo);
gl.bufferSubData(gl.UNIFORM_BUFFER, 0, uniforms);
// ... gl.drawArrays(...)
requestAnimationFrame(render);
};
requestAnimationFrame(render);
// Wichtig · Fallstricke
- Kein Vergrößern: Wenn
dstByteOffsetplus die Datenmenge größer ist als der mitbufferData()angelegte Puffer, entstehtgl.INVALID_VALUEund es wird nichts geschrieben. Soll der Puffer wachsen, muss man ihn mitbufferData()neu anlegen. - Einheiten verwechselt:
dstByteOffsetzählt Bytes,srcOffsetundlengthzählen Elemente des TypedArray. Das ist ein häufiger Fehler. - Ist an
targetkein Puffer gebunden (null), entstehtgl.INVALID_OPERATION. Bei einem ungültigentargetentstehtgl.INVALID_ENUM. WennsrcOffset(bzw.srcOffset + length) über das Ende vonsrcDatahinausgeht, entstehtgl.INVALID_VALUE. - Ein Puffer, der einmal an
ELEMENT_ARRAY_BUFFERgebunden war, darf in WebGL nicht an andere Ziele gebunden werden (außer die Kopier-Ziele). Das gilt auch umgekehrt. - Performance: Viele kleine Aufrufe pro Frame sind teuer. Besser ist es, Änderungen zu einem zusammenhängenden Bereich zusammenzufassen. Wird ein Puffer gerade noch von der GPU gelesen, kann der Treiber blockieren. Abhilfe schafft das Wechseln zwischen mehreren Puffern (Double Buffering).
- Die Überladung mit
srcOffset/lengthgibt es nur in WebGL 2. In WebGL 1 (WebGLRenderingContext.bufferSubData(target, offset, srcData)) nimmt man stattdessentypedArray.subarray().