Choosing p5.js Architecture: Global vs Instance Mode and P2D vs WEBGL Renderer
A practical decision guide for p5.js architecture: when to use global vs instance mode, P2D vs WEBGL renderer, and how to validate your choice with concrete benchmarks and migration checks.
04 Sept 2025, 04:31 UTC

The Decision You Face
Every p5.js project starts with two architectural choices that cascade through development: global mode vs instance mode for sketch encapsulation, and P2D vs WEBGL for the rendering backend. The wrong combination forces rewrites when you add a second canvas, embed in React, or hit performance walls with particle counts. This guide gives you a decision framework, a comparison table, and a validation checklist you can run in five minutes.
Quick Decision Matrix
| Scenario | Mode | Renderer | Primary Reason |
|---|---|---|---|
| Single standalone sketch, no framework | Global | P2D | Zero boilerplate; full 2D API |
| Multiple canvases on one page | Instance | P2D | No global namespace collisions |
| Embedding in React, Vue, Svelte | Instance | P2D or WEBGL | Component lifecycle control |
| 3D primitives, cameras, lights | Either | WEBGL | GPU-accelerated 3D pipeline |
| 10k+ particles per frame | Instance | WEBGL + shaders | GPU batching, custom shaders |
| 2D sketch with one 3D overlay layer | Either | P2D main + WEBGL buffer | createGraphics(W, H, WEBGL) composited via image() |
Global Mode vs Instance Mode
Global Mode (Default)
Functions setup(), draw(), mouseX, createCanvas() attach to window. Simplest for one-off sketches.
// global-mode.js
function setup() {
createCanvas(400, 400);
}
function draw() {
background(220);
ellipse(mouseX, mouseY, 50, 50);
}
Constraint: Only one sketch per page. Adding a second setup() overwrites the first. Any other library defining draw or mousePressed collides.
Instance Mode
Wrap the sketch in a function that receives a p5 instance. Call new p5(sketch, container).
// instance-mode.js
const sketch = (p) => {
p.setup = () => {
p.createCanvas(400, 400);
};
p.draw = () => {
p.background(220);
p.ellipse(p.mouseX, p.mouseY, 50, 50);
};
};
// Mount into a specific DOM element
new p5(sketch, document.getElementById('canvas-container'));
Trade-off: Every p5 call needs the p. prefix. Porting global-mode code means a find-and-replace pass. Benefit: multiple independent sketches, clean integration with framework component lifecycles (mount in useEffect/onMounted, destroy in cleanup).
P2D vs WEBGL Renderer
P2D (Default 2D Canvas)
- CPU rasterization via
CanvasRenderingContext2D - Full 2D API:
text(),curve(),drawingContextaccess - Origin: top-left (0,0)
- Performance ceiling: ~5,000–10,000 distinct shape calls per frame at 60 fps on modern desktop
WEBGL (GPU-Accelerated)
createCanvas(w, h, WEBGL)orcreateGraphics(w, h, WEBGL)- 3D primitives:
box(),sphere(),cylinder() - Lights:
ambientLight(),directionalLight(),pointLight() - Camera:
camera(),orbitControl() - Custom shaders:
loadShader(),createShader(),shader() - Origin: center (0,0 at canvas center). Top-left is
(-width/2, -height/2) - Weaker 2D conveniences: no
curve(), limitedtext()(usetextFont()with preloaded font), nodrawingContext
Coordinate System Migration Bug
The center origin is the #1 migration failure. A rectangle drawn at (0,0,100,100) in WEBGL appears centered, not top-left.
// WEBGL - draws centered rectangle
function draw() {
rect(0, 0, 100, 100); // center of canvas
}
// To match P2D top-left behavior:
function draw() {
translate(-width/2, -height/2);
rect(0, 0, 100, 100); // now top-left
}
Mixing Renderers: The Hybrid Approach
You can keep a P2D main canvas and composite a WEBGL layer using createGraphics().
// hybrid-sketch.js (instance mode)
const sketch = (p) => {
let glBuffer;
p.setup = () => {
p.createCanvas(640, 480); // P2D default
glBuffer = p.createGraphics(640, 480, p.WEBGL);
};
p.draw = () => {
p.background(20);
// Draw 2D UI layer
p.fill(255);
p.text('2D Overlay', 20, 30);
// Draw 3D scene into offscreen buffer
glBuffer.background(0);
glBuffer.rotateY(p.frameCount * 0.01);
glBuffer.normalMaterial();
glBuffer.box(100);
// Composite
p.image(glBuffer, 0, 0);
};
};
new p5(sketch, 'container');
This pattern lets you keep performant 2D text/UI while offloading heavy 3D or particle work to the GPU.
Performance Validation Checklist
Run these in your target browser with your actual p5.js version (check p5.VERSION).
1. Mode Isolation Test
- Create an HTML page loading two global-mode scripts with different
setup()functions. Open DevTools console → verify only the last sketch runs. - Convert one to instance mode mounted in separate
<div>s. Confirm both run simultaneously, nowindow.drawcollision.
2. Renderer Benchmark
// benchmark.js - run in both P2D and WEBGL
const PARTICLE_COUNT = 8000;
const particles = [];
function setup() {
createCanvas(800, 600, RENDERER); // swap P2D / WEBGL
for (let i = 0; i < PARTICLE_COUNT; i++) {
particles.push({x: random(width), y: random(height), vx: random(-2,2), vy: random(-2,2)});
}
}
function draw() {
background(0);
noStroke();
fill(255);
for (const p of particles) {
p.x += p.vx; p.y += p.vy;
if (p.x < 0 || p.x > width) p.vx *= -1;
if (p.y < 0 || p.y > height) p.vy *= -1;
circle(p.x, p.y, 4);
}
// Log frameRate every 60 frames
if (frameCount % 60 === 0) console.log(frameRate().toFixed(1));
}
Run with RENDERER = P2D then RENDERER = WEBGL. In WEBGL, add translate(-width/2, -height/2) at start of draw() and adjust particle init to center-origin coordinates. Compare sustained frame rates.
3. WEBGL Origin Verification
function draw() {
background(50);
// This rectangle should appear centered if origin is center
noFill(); stroke(255); strokeWeight(2);
rect(0, 0, 200, 100);
// Visual marker for true center
stroke(255,0,0); point(0,0);
}
If the red dot sits in the exact canvas center and the rectangle surrounds it, origin is center. If rectangle sits top-left, you're still in P2D or something else is wrong.
Version-Specific Gotchas
- p5.js ≥ 1.6.0:
createCanvas()returns the renderer instance; earlier versions return the canvas element. - WEBGL
text(): RequirestextFont(font)with aloadFont()result; system fonts are unreliable. - Instance mode
remove(): Callp.remove()in framework cleanup to detach canvas and event listeners. - Shader precision: Mobile GPUs often need
precision highp float;in fragment shaders.
Limitations & When to Re-evaluate
- If your sketch stays under 2,000 shapes/frame and never needs 3D, P2D + global mode is fine — don't over-engineer.
- WEBGL adds ~200 KB gzipped (p5.js + webgl build). For tiny embeds, that may matter.
- Accessibility: P2D
text()is selectable; WEBGL text is pixels. If screen-reader access matters, keep UI in P2D or overlay HTML. - Offscreen
createGraphics(WEBGL)contexts share the main canvas's WebGL context limit (typically 16 per origin). Creating many buffers can hit this.
Final Decision Flow
- Multiple canvases or framework integration? → Instance mode. Otherwise global is fine.
- Need 3D, shaders, or >10k GPU-friendly particles? → WEBGL (full canvas or hybrid buffer).
- Mostly 2D with occasional 3D? → P2D main +
createGraphics(WEBGL)buffer. - Validate with the benchmark above using your actual asset count and target devices.
Commit to the combination that matches your highest-confidence requirement today; the hybrid buffer pattern lets you migrate incrementally rather than rewriting everything.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.