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

WebGL2RenderingContext.bufferSubData()

Methode

Überschreibt einen Teilbereich des Datenspeichers des aktuell an <code>target</code> gebundenen WebGL-Puffers, ohne den Speicher neu anzulegen.

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

Signatur

WebGL2RenderingContext.bufferSubData(target, dstByteOffset, srcData, srcOffset, length)

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

Typ
void
Beschreibung
Kein 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);
true

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);
Hochgeladene Bytes: 1200

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 dstByteOffset plus die Datenmenge größer ist als der mit bufferData() angelegte Puffer, entsteht gl.INVALID_VALUE und es wird nichts geschrieben. Soll der Puffer wachsen, muss man ihn mit bufferData() neu anlegen.
  • Einheiten verwechselt: dstByteOffset zählt Bytes, srcOffset und length zählen Elemente des TypedArray. Das ist ein häufiger Fehler.
  • Ist an target kein Puffer gebunden (null), entsteht gl.INVALID_OPERATION. Bei einem ungültigen target entsteht gl.INVALID_ENUM. Wenn srcOffset (bzw. srcOffset + length) über das Ende von srcData hinausgeht, entsteht gl.INVALID_VALUE.
  • Ein Puffer, der einmal an ELEMENT_ARRAY_BUFFER gebunden 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/length gibt es nur in WebGL 2. In WebGL 1 (WebGLRenderingContext.bufferSubData(target, offset, srcData)) nimmt man stattdessen typedArray.subarray().