API Differences: WebGL vs WebGPU
Fresh 🌱Canvas Handling
javascript
// WebGL: canvas creates the context, state attached to canvas
const gl = canvas.getContext('webgl2');
// WebGPU: device is independent; configure canvas separately
const device = await (await navigator.gpu.requestAdapter()).requestDevice();
const context = canvas.getContext('webgpu');
context.configure({ device, format: navigator.gpu.getPreferredCanvasFormat() });
// WebGPU advantage: one device, MANY canvases
context1.configure({ device, format });
context2.configure({ device, format });
// No canvas limit in WebGPU (vs 16 in Chrome's WebGL)Antialiasing
javascript
// WebGL: pass antialias as context attribute
const gl = canvas.getContext('webgl2', { antialias: true });
// WebGPU: manual MSAA texture + resolve
const msaa = device.createTexture({
size: [canvas.width, canvas.height],
sampleCount: 4,
format,
usage: GPUTextureUsage.RENDER_ATTACHMENT
});
// Render to msaa.createView(), resolve to canvas textureBuffers and Textures: Immutability
javascript
// WebGL: mutable — can resize at any time
gl.bufferData(gl.ARRAY_BUFFER, newData, gl.DYNAMIC_DRAW);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, newWidth, newHeight, ...);
// WebGPU: IMMUTABLE after creation — cannot change size/format/usage
// To "resize": destroy old, create new
const newBuffer = device.createBuffer({ size: newSize, usage: ... });
device.queue.writeBuffer(newBuffer, 0, newData);Error Handling
javascript
// WebGL: poll synchronously (pipeline stall)
gl.drawArrays(...);
if (gl.getError() !== gl.NO_ERROR) { /* handle */ }
// WebGPU: async scoped errors (no stalls)
device.pushErrorScope('validation'); // or 'out-of-memory', 'internal'
device.createBuffer({ /* bad params */ });
const error = await device.popErrorScope();
if (error) console.error(error.message); // Includes call stack!
// WebGPU also surfaces errors via device.onuncapturederror
device.addEventListener('uncapturederror', (event) => {
console.error('Uncaptured:', event.error.message);
});Object Labels
javascript
// WebGPU: every object can have a label for debugging
const buffer = device.createBuffer({
label: 'MVP uniform buffer', // Shows in DevTools and error messages!
size: 64,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
});
const encoder = device.createCommandEncoder({ label: 'Main frame encoder' });Buffer Upload Patterns
javascript
// WebGL
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferSubData(gl.ARRAY_BUFFER, offset, data);
// WebGPU option A: writeBuffer (most common)
device.queue.writeBuffer(buffer, byteOffset, data);
// WebGPU option B: mapped at creation (for initialization)
const buffer = device.createBuffer({
size: data.byteLength,
usage: GPUBufferUsage.VERTEX,
mappedAtCreation: true
});
new Float32Array(buffer.getMappedRange()).set(data);
buffer.unmap(); // must unmap before GPU can use itReading GPU Data Back to CPU
javascript
// WebGL: synchronous readPixels (stalls GPU)
const pixels = new Uint8Array(width * height * 4);
gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
// WebGPU: always async (copyBufferToBuffer + mapAsync)
const staging = device.createBuffer({
size: bufferToRead.size,
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
});
const enc = device.createCommandEncoder();
enc.copyBufferToBuffer(bufferToRead, 0, staging, 0, staging.size);
device.queue.submit([enc.finish()]);
await staging.mapAsync(GPUMapMode.READ);
const data = new Float32Array(staging.getMappedRange().slice(0));
staging.unmap();