The GPU cannot read a JavaScript array. A typed array is a view of fixed-size numbers over a raw ArrayBuffer, the exact bytes WebGL uploads:
| View (bytes each) | WebGL type | Typical use |
|---|---|---|
| Uint8Array, Int8Array (1) | UNSIGNED_BYTE, BYTE | Normalized colors |
| Uint16Array, Int16Array (2) | UNSIGNED_SHORT, SHORT | Indices |
| Uint32Array, Int32Array (4) | UNSIGNED_INT, INT | Large index sets |
| Float32Array (4) | FLOAT | Positions, matrices |
| Float16Array (2) | HALF_FLOAT (WebGL 2) | Compact data (Baseline 2025) |
Older tutorials list get(), set(index, value) and subset() methods; they do not exist. Use brackets, set(array, offset) to copy an array in, and subarray() for a view sharing memory (slice() copies):
const f = new Float32Array([0.1, 0.2, 0.3, 0.4]);
const view = f.subarray(1, 3), copy = f.slice(1, 3);
f[1] = 9;
console.log(view[0], copy[0], f.BYTES_PER_ELEMENT, f.byteLength);
console.log(f[0], new Float16Array([0.1])[0], new Uint8Array([300])[0],
new Uint8ClampedArray([300])[0]);Output
9 0.20000000298023224 4 16 0.10000000149011612 0.0999755859375 44 255
Floats round to 32 (or 16) bits; integer views wrap around, while Uint8ClampedArray clamps.
<!doctype html>
<style>
body { margin: 0; font: 11px system-ui, sans-serif; background: #f7f4ee; color: #333; }
canvas { display: block; width: 100%; max-width: 600px; }
.names { display: flex; max-width: 600px; text-align: center; font-family: monospace; }
.names div { flex: 1; padding: 4px 2px; }
pre { max-width: 600px; margin: 4px 0 0; padding: 6px 8px; background: #fff; border: 1px solid #ddd; }
</style>
<canvas id="view" width="600" height="180"></canvas>
<div class="names">
<div>Float32Array → FLOAT<br>32 bytes</div>
<div>Int16Array → SHORT, normalized<br>16 bytes</div>
<div>Uint8Array colours → UNSIGNED_BYTE<br>12 bytes</div>
</div>
<pre id="out"></pre>
<script>
const gl = document.getElementById('view').getContext('webgl2');
const program = gl.createProgram();
for (const [type, src] of [[gl.VERTEX_SHADER, `#version 300 es
layout(location = 0) in vec2 aPos; layout(location = 1) in vec3 aColor;
uniform float uX; out vec3 vColor;
void main() { vColor = aColor; gl_Position = vec4(aPos * vec2(0.25, 0.85) + vec2(uX, 0.0), 0.0, 1.0); }`],
[gl.FRAGMENT_SHADER, `#version 300 es
precision mediump float; in vec3 vColor; out vec4 c; void main() { c = vec4(vColor, 1.0); }`]]) {
const s = gl.createShader(type);
gl.shaderSource(s, src); gl.compileShader(s);
gl.attachShader(program, s);
}
gl.linkProgram(program);
gl.useProgram(program);
// A cover quad (triangle strip) described three ways.
const asFloat = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]); // 4 bytes each
const asShort = new Int16Array([-32767, -32767, 32767, -32767, -32767, 32767, 32767, 32767]); // 2 bytes
const colorBytes = new Uint8Array([224, 154, 16, 181, 69, 47, 31, 95, 139, 42, 157, 143]); // 1 byte
function quad(positions, type, normalized, colors) {
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW); // the typed array's raw bytes
gl.vertexAttribPointer(0, 2, type, normalized, 0, 0);
gl.enableVertexAttribArray(0);
if (colors) {
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
gl.vertexAttribPointer(1, 3, gl.UNSIGNED_BYTE, true, 0, 0); // 255 -> 1.0
gl.enableVertexAttribArray(1);
}
return vao;
}
const quads = [
[quad(asFloat, gl.FLOAT, false), [0.12, 0.37, 0.55], -0.66],
[quad(asShort, gl.SHORT, true), [0.36, 0.25, 0.6], 0.0], // 32767 -> 1.0
[quad(asFloat, gl.FLOAT, false, colorBytes), null, 0.66],
];
gl.clearColor(0.97, 0.96, 0.93, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
for (const [vao, color, x] of quads) {
gl.bindVertexArray(vao);
if (color) gl.vertexAttrib3fv(1, color); // constant colour when no array is enabled
gl.uniform1f(gl.getUniformLocation(program, 'uX'), x);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}
// The page's console experiment: views share memory, slices copy, integers wrap or clamp.
const f = new Float32Array([0.1, 0.2, 0.3, 0.4]);
const view = f.subarray(1, 3), copy = f.slice(1, 3);
f[1] = 9;
const half = typeof Float16Array === 'function' ? new Float16Array([0.1])[0] : '(no Float16Array)';
document.getElementById('out').textContent =
`view[0] ${view[0]} copy[0] ${copy[0]} BYTES_PER_ELEMENT ${f.BYTES_PER_ELEMENT} byteLength ${f.byteLength}\n` +
`Float32 0.1 -> ${f[0]} Float16 0.1 -> ${half} Uint8 300 -> ${new Uint8Array([300])[0]}` +
` Uint8Clamped 300 -> ${new Uint8ClampedArray([300])[0]}`;
</script>