WebGL

The browser's original GPU API — OpenGL ES exposed to JavaScript, supported everywhere. WebGL has no compute shaders, so general-purpose GPU work is done either by rendering into a texture with a fragment shader, or (in WebGL2) with transform feedback as shown below. For genuine compute in the browser, reach for WebGPU instead; WebGL remains the compatibility fallback.

KhronosJavaScript · GLSL ESbrowsergraphicsno compute shaders

Official docs ↗ ← All libraries

Install

<!-- no install: WebGL2 ships in every modern browser. -->
<canvas id="c"></canvas>
<script type="module" src="double.js"></script>

Hello, GPU

double.js — double an array with WebGL2 transform feedback (GPGPU)

const gl = document.querySelector("#c").getContext("webgl2");

// Vertex shader does the work; the fragment shader is a no-op.
const vs = `#version 300 es
in float value;
out float result;
void main() { result = value * 2.0; }`;
const fs = `#version 300 es
precision highp float; out vec4 c; void main() { c = vec4(0); }`;

function compile(type, src) {
  const s = gl.createShader(type);
  gl.shaderSource(s, src); gl.compileShader(s); return s;
}
const prog = gl.createProgram();
gl.attachShader(prog, compile(gl.VERTEX_SHADER, vs));
gl.attachShader(prog, compile(gl.FRAGMENT_SHADER, fs));
gl.transformFeedbackVaryings(prog, ["result"], gl.SEPARATE_ATTRIBS); // before link
gl.linkProgram(prog);

const input = new Float32Array([1, 2, 3, 4]);
const inBuf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, inBuf);
gl.bufferData(gl.ARRAY_BUFFER, input, gl.STATIC_DRAW);
const loc = gl.getAttribLocation(prog, "value");
gl.enableVertexAttribArray(loc);
gl.vertexAttribPointer(loc, 1, gl.FLOAT, false, 0, 0);

const outBuf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, outBuf);
gl.bufferData(gl.ARRAY_BUFFER, input.byteLength, gl.STATIC_READ);
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, outBuf);

gl.useProgram(prog);
gl.enable(gl.RASTERIZER_DISCARD);
gl.beginTransformFeedback(gl.POINTS);
gl.drawArrays(gl.POINTS, 0, input.length);
gl.endTransformFeedback();
gl.disable(gl.RASTERIZER_DISCARD);

const out = new Float32Array(input.length);
gl.getBufferSubData(gl.TRANSFORM_FEEDBACK_BUFFER, 0, out); // [2, 4, 6, 8]
console.log(out);

Run it:

# serve over http(s)/localhost and open in any browser with WebGL2 (all current browsers)

Learn more