Typed Arrays

Float32Array, Uint16Array and Other Typed Arrays

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:

Typed arrays and the WebGL types they feed
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):

Views, copies and precision in typed arraysJavaScript
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.

Three covers fed by different typed arrays: Float32Array, normalized Int16Array and Uint8Array coloursHTMLLive
<!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 &rarr; FLOAT<br>32 bytes</div>
  <div>Int16Array &rarr; SHORT, normalized<br>16 bytes</div>
  <div>Uint8Array colours &rarr; 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>