JavaScript WebGL Table

PieceWhat it doesField note
getContext('webgl2')The entryReturns null when unsupported - branch to fallback; WebGL2 = GLSL ES 3.0, VAOs, instancing built in
shadersThe programVertex + fragment in GLSL, compiled and linked once; the pipeline is fixed, your shaders are its custom stages
buffers + attributesThe geometryFloat32Array into ARRAY_BUFFER; vertexAttribPointer maps buffer to attribute - interleaved layouts save binds
VAOsThe state bundleOne bind restores the whole attribute layout - WebGL2 makes them native instead of an extension
uniformsThe per-draw constantsCache getUniformLocation once; matrices flow here, per-vertex data through attributes
drawArrays/ElementsThe callThe only call that rasterizes; ELEMENT_ARRAY_BUFFER indexes past the 65k Uint16 ceiling
context lossThe silent killGPU reset voids every resource - webglcontextlost/restored events, full re-init; init belongs in a function
preserveDrawingBufferThe screenshot trapDefault false = toDataURL reads blank after compositing - read synchronously or pay the flag's cost
Reference: the MDN WebGL API guide. WebGL is a fixed GPU pipeline with two customizable stages - your vertex and fragment shaders - and the whole craft is state bookkeeping around them: buffers feed attributes, uniforms carry per-draw constants, one draw call rasterizes. WebGL2 makes the extension era optional: VAOs, instancing and integer indices are baseline.
Bottom line: production WebGL is an architecture that can re-initialize from scratch, because the context can vanish underneath you - GPU resets and mobile webview memory pressure void every compiled resource, and webglcontextrestored expects your init function to rebuild the world. Then there is the trap everyone meets once: the drawing buffer clears after compositing, so a screenshot needs a synchronous read in the same task as the draw, or preserveDrawingBuffer: true and its frame budget.
Related tools: the canvas table (the 2D sibling and its coordinate system), the requestAnimationFrame table (the loop that drives every frame), the Web Workers table (the OffscreenCanvas path off the main thread), and the WebAssembly table (where the heavy per-vertex math lives).

WebGL is the GPU escape hatch of the web platform: a canvas whose drawing commands become GPU work, speaking a C-like shader language (GLSL) instead of a 2D API. WebGL2 - the version every modern browser ships - brings GLSL ES 3.0, vertex array objects, instanced drawing, and 3D textures as baseline. The mental model that makes it clickable: the pipeline is FIXED (vertices in, triangles rasterized, pixels out), and your vertex and fragment shaders are the two customizable stages bolted into that fixed pipeline.

Bottom line: WebGL programming is state management with a rendering habit. Buffers hold numbers, attributes map buffers to shader inputs, uniforms carry per-draw constants, and a single drawArrays or drawElements call rasterizes. The wins come from bookkeeping: cache uniform locations once (getUniformLocation is a lookup, not a getter), record your attribute layout in a VAO so restoring geometry is one bind, and prefer interleaved buffers so one binding serves position, normal and UV.

The honest part: the context can vanish underneath you. GPU resets, driver upgrades, and mobile webview memory pressure all trigger webglcontextlost - every buffer, texture and program you compiled is gone, and nothing re-runs until webglcontextrestored fires and you rebuild the entire scene. Production WebGL is an architecture that can re-initialize from scratch, not a script that runs once. And the screenshot trap bites everyone once: by default the drawing buffer is cleared after compositing, so toDataURL returns blank unless you render synchronously right before reading or set preserveDrawingBuffer: true and pay for it.

How to use

  1. Get the context defensively: const gl = canvas.getContext('webgl2') || canvas.getContext('webgl'); - null means no WebGL (blocked, out of GPU memory, or software-rendering disabled), so branch to a static fallback instead of throwing. WebGL2 is a superset: write GLSL ES 3.00 shaders (#version 300 es first line) and VAOs come for free.
  2. Build the pipeline once: compile vertex and fragment shaders, check COMPILE_STATUS, link into a program, check LINK_STATUS, then gl.useProgram. Shader compile errors log to the console via getShaderInfoLog - read them, they carry line numbers.
  3. Feed geometry through buffers: bindBuffer + bufferData with a Float32Array, then vertexAttribPointer maps it to an attribute location (enableVertexAttribArray first). Record the whole layout in a VAO (createVertexArray/bindVertexArray) so switching models later is one bind call instead of re-walking every attribute.
  4. Move per-frame data through uniforms: cache locations with getUniformLocation at init, then uniformMatrix4fv your transform each frame. The split that keeps code sane: attributes are per-vertex data, uniforms are per-draw state, textures are per-draw images.
  5. Plan for context loss from day one: listen for webglcontextlost (call preventDefault to allow restoration) and webglcontextrestored (re-create programs, buffers, textures, VAOs - which is why init belongs in a function, not in top-level script). Test it in the browser's GPU-reset tooling or by opening dozens of WebGL tabs; the first real device reset should not be the first time your code meets the code path.

Frequently asked questions

Why is my canvas screenshot blank (toDataURL after render)?

The drawing buffer - the canvas's backing store - is cleared after each frame is composited to the page (preserveDrawingBuffer defaults to false), so reading it later reads an empty buffer. Fixes in cost order: call toDataURL (or drawImage into a 2D canvas) synchronously in the same task as your draw call, before yielding; or create the context with preserveDrawingBuffer: true, which keeps the buffer readable but costs performance on every frame because the browser cannot reuse or discard it. The synchronous-read pattern is free and works for occasional captures; preserveDrawingBuffer is for apps that screenshot constantly and accept the frame budget.

Do I need a framework like three.js?

For anything product-shaped, yes - three.js (or Babylon) buys you matrices, loaders, lights, and a scene graph that hides exactly the bookkeeping this page describes. Raw WebGL earns its keep for shader-heavy 2D effects (particle systems, image processing, full-screen post effects) where a scene graph is dead weight, for tiny bundles, and for understanding what the framework is actually doing when it breaks. The honest learning path: raw WebGL once, deliberately - then never debug a model matrix by hand again. Framework-free does not mean abstraction-free: you will rebuild a matrix library either way, and gl-matrix exists for a reason.

What actually happens on context loss?

The GPU (or the browser's watchdog for it) resets: the WEBGL_lose_context extension aside, every resource - programs, buffers, textures, framebuffers, VAOs - becomes invalid, the canvas blanks, and webglcontextlost fires. If you preventDefault() that event, the browser later fires webglcontextrestored on the SAME context object; you rebuild everything (init function, remember) and re-render. If you do not preventDefault, the context is gone for good and a new one needs a new canvas. Mobile browsers are aggressive here - backgrounded tabs lose contexts routinely - so the loss-restore path is not an edge case on phones, it is Tuesday.

WebGL2 or WebGL 1 - what do I actually get upgrading?

Baseline features that WebGL1 bolted on through extensions: vertex array objects (native, not OES_vertex_array_object), instanced drawing (angle_instanced_arrays built in), 3D textures and texture arrays, unsigned integer indices (no more 65k-vertex ceilings from Uint16), uniform blocks, transform feedback (GPU-side output of vertex processing), and GLSL ES 3.00 with real loops and integer math. Coverage is effectively universal in 2026 - the fallback branch matters mainly for ancient devices and locked-down webviews. Write GLSL 300 es, keep the WebGL1 context fallback only if your analytics say someone still needs it, and skip the extension dance entirely.

Related tools