9 min read

GPU Rendering and Choosing a Renderer

The CPU draws points one after another. The GPU draws them all at once. Knowing when to make that switch is the real skill.


The story

A flight-tracking dashboard plots live aircraft positions on a map, plus the last 30 minutes of each track: around 250,000 points, updated every second. The first version draws them on a canvas 2D layer. It manages 8 frames per second while panning, and the fan on every laptop in the operations room spins up.

The team profiles it. Nothing is wasted: the code already batches by colour, culls off-screen points and uses one path per layer. It's simply a quarter of a million rect() calls described one by one on the CPU every frame.

They move the data layer to WebGL using deck.gl. Panning is now smooth at 60 fps, and CPU time per frame drops from over 100 ms to a few milliseconds, because the per-point work moved to the GPU.

The new problems are different in kind:

  • Labels: drawing crisp text in WebGL is surprisingly hard. The callsigns move to an HTML overlay that only labels the 50 aircraft nearest the cursor.
  • "WebGL context lost" on some laptops after they wake from sleep. The map goes blank until a reload.
  • One team member's old office PC has its GPU blocklisted by the browser and falls back to software rendering, where WebGL is slower than the canvas version was.

The GPU is the most powerful renderer in the browser. It's also the one with the most moving parts. This chapter is about when the trade is worth it.


Part 1: What the GPU is good at

A CPU core runs one sequence of instructions very fast. A GPU runs the same small program on thousands of inputs at once. Drawing a million points is the perfect GPU job: the same calculation (where is this point on screen? what colour is this pixel?) repeated a million times, independently.

The rules for fast GPU rendering follow from that picture:

  1. Few draw calls, lots of data per call. A draw call has fixed overhead. One call with a million points is fast. A million calls with one point each is slower than canvas.
  2. Upload once, update little. Moving data from CPU memory to GPU memory costs time. Keep buffers on the GPU, and update only what changed (bufferSubData on a range).
  3. Do per-point math in the shader. If points move along known paths, upload start position, velocity and start time once, and compute the current position in the vertex shader from a single time value. The CPU's per-frame work drops to setting one number.
  4. Draw many copies of one shape with instancing. A thousand identical icons with different positions and colours is one shape plus a per-instance array: one draw call (drawArraysInstanced in WebGL2).

Part 2: WebGL, WebGL2 and WebGPU

APIWhat it isStatus
WebGLOpenGL ES 2.0 in the browserEverywhere, including old devices
WebGL2OpenGL ES 3.0: instancing built in, more texture formats, better buffersSupported in all current major browsers
WebGPUModern design (like Vulkan/Metal/Direct3D 12), with compute shaders for general GPU computationShipping in recent versions of the major browsers, with gaps on some platforms. Check support for your audience and keep a WebGL fallback.

For data visualisation, WebGPU's big addition is compute: aggregation, binning, layout and filtering of millions of points can run on the GPU instead of in a worker. For most apps today, WebGL2 through a library is the safe default.


Part 3: What the GPU makes harder

ProblemWhat to do
TextUse signed-distance-field (SDF) font atlases (libraries handle this), or put labels in an HTML/canvas overlay and only label what matters
Context loss: the browser can take the GPU context away (driver reset, sleep, too many contexts)Listen for webglcontextlost / webglcontextrestored, and be able to re-upload everything from your own data
Too many contexts: browsers cap active WebGL contexts per page (often around 16)One shared context for a whole dashboard, not one per chart
Device variety: integrated GPUs, mobile GPUs, blocklisted drivers with software fallbackTest on low-end hardware. Detect slow frames and fall back to canvas or fewer points.
Startup cost: shader compilation, buffer uploadsCompile once, warm up during load, avoid recompiling on state changes
Debugging: no DevTools elements to inspectSpector.js frame capture, chrome://gpu to check hardware acceleration
Accessibility and eventsSame as canvas: your own hit testing (often GPU colour picking) and a DOM layer for a11y (Chapter 6)

Part 4: The library landscape

You rarely write raw WebGL for product work. Pick by the job:

LibraryGood for
deck.glLarge-scale geospatial and data layers (millions of points, arcs, hexbins), often on top of a base map
PixiJSFast 2D sprite rendering: games, interactive canvases, big diagrams
reglA thin, functional layer over WebGL when you want control without the boilerplate
three.js3D scenes
uPlotVery fast time-series charts on canvas 2D (no GPU needed for many use cases)
Plotly scattergl / ECharts with large-data modesFamiliar chart APIs with GPU or optimised canvas paths for big series
Commercial (LightningChart, SciChart)Extreme point counts, scientific and financial charting

Figma is the well-known example of going all the way: its editor renders the design canvas with WebGL, driven by a C++ engine compiled to WebAssembly, which is how it keeps huge documents interactive in a browser.


Part 5: Race them on your device

Below, the same moving points are drawn four ways: DOM elements, SVG circles, Canvas 2D (batched into one path) and raw WebGL points. The frame rate and per-frame script time are measured live in this tab. Try all three sizes. Then try it on your phone.

Loading simulation…

Two things to notice:

  • At small sizes, all four are fine. Choosing WebGL for a 500-point chart buys nothing and costs a lot.
  • For DOM and SVG, the script time is low even when the frame rate collapses. The cost is in style, layout and paint after your script runs, which is invisible to a JavaScript timer and obvious in the Performance panel.

Part 6: Choosing a renderer

The thresholds below are ranges, not rules. They depend on the device, how often things move, and how complex each shape is. Measure on your weakest target device.

RendererComfortable rangeYou getYou pay
DOMHundreds of moving elements, more if staticLayout, text, forms, events, a11y, CSSPer-element style/layout/paint cost
SVGUp to a few thousand shapesVector crispness, CSS, per-shape events, a11yEvery shape is a DOM node
Canvas 2DThousands to roughly a hundred thousand per frameOne bitmap, full control, cheap redrawsYour own hit testing, a11y, text layout
WebGL / WebGPUHundreds of thousands to millionsGPU parallelism, near-zero CPU per frameText, context loss, device variety, complexity

Most real apps are hybrids

Use the GPU for the dense data, and the DOM for everything a person reads or clicks. Each layer gets the renderer that fits it, which is the story's final architecture.


Numbers to remember

Ranges vary by device. Verify with the simulation above and a Performance recording.

NumberMeaning
~1,000sWhere SVG with per-frame updates typically starts to struggle
~100kWhere batched canvas 2D typically becomes CPU-bound at 60 fps
MillionsWhat WebGL handles when data stays on the GPU
~16A common per-page limit on active WebGL contexts
1The ideal number of draw calls per layer

The DevTools workflow

  1. chrome://gpu: check that WebGL is hardware-accelerated, not a software fallback.
  2. Performance panel: the GPU track shows GPU work. A long GPU block with little script means fragment work: too many large or overlapping shapes, or expensive shaders.
  3. Spector.js (browser extension) captures one WebGL frame and lists every draw call and state change. Count the draw calls.
  4. Test context loss: canvas.getContext('webgl').getExtension('WEBGL_lose_context').loseContext() in the console, then restore, and check the app recovers.
  5. Throttle with a low-end device or CPU slowdown. The GPU path is fast on your laptop. Check the users' hardware.

Cheat sheet

SituationChoose
A normal chart, hundreds of pointsSVG (or any chart library)
Interactive diagram, a few thousand shapesSVG, or canvas if it animates heavily
Tens of thousands of points, live updatesCanvas 2D, batched and culled
Canvas profile is CPU-bound at 100k+WebGL via deck.gl / PixiJS / regl
Points move on predictable pathsAnimate in the vertex shader from a time uniform
Many identical iconsInstancing
Many charts on one pageOne shared GL context (or canvas), not one per chart
Labels on a GPU layerSDF text, or an HTML overlay for the few labels that matter
Unreliable GPUs in your audienceDetect slow frames, fall back to canvas or fewer points

Say it in the interview

"I'd pick the renderer by element count and update rate: SVG up to a few thousand shapes because it's cheap to build and accessible, canvas 2D for tens of thousands, and WebGL beyond roughly a hundred thousand or when profiling shows canvas is CPU-bound. Here, 250k moving points means a WebGL data layer through deck.gl, with data in GPU buffers updated incrementally and labels in an HTML overlay for the nearest few aircraft. I'd handle context loss by re-uploading from our own store, and fall back to fewer points on devices with software rendering."


Go deeper