Boost p5.js Performance with createGraphics: Off‑Screen Buffers for Static Backgrounds
Learn how p5.js’s createGraphics lets you render static backgrounds once and reuse them each frame, cutting draw calls and boosting FPS. Includes a step‑by‑step example, trade‑offs, and a practical checklist.
20 Jul 2025, 19:16 UTC

Problem: Re‑drawing a static background every frame
In many interactive sketches the background is a complex, multi‑layer image or a procedurally generated scene that never changes. When drawn inside draw(), the browser must execute the same pixel‑by‑pixel operations on every frame, even though the visual output is identical. This wastes CPU/GPU cycles and can drop FPS, especially on mobile devices.
Thesis: Render once, reuse often with createGraphics
The createGraphics() function creates an off‑screen p5.Graphics buffer that behaves like the main canvas. By drawing the static content once into this buffer and then simply displaying it each frame with image(), you eliminate repeated draw calls and keep the main draw() loop light.
How createGraphics Works
- Same API: All drawing functions (rect, ellipse, text, etc.) work inside the buffer as they do on the main canvas.
- Independent state: Transformations, fill styles, and other settings are local to the buffer; the main canvas’s coordinate system remains untouched.
- WebGL support: From p5.js 1.6+, you can create a WebGL buffer to harness GPU‑accelerated shaders while still layering 2D content on the main canvas.
- Memory cost: A buffer of size
w × hconsumes4 × w × hbytes (RGBA). A 1920×1080 buffer uses roughly 8 MB.
Concrete Example: A Static Cityscape
Below is a minimal sketch that demonstrates the performance benefit. The city skyline is drawn once into bg, then reused each frame while animated cars move across the screen.
let bg; // off‑screen buffer
let cars = [];
function setup() {
createCanvas(800, 400);
// Create a 2D graphics buffer the same size as the main canvas
bg = createGraphics(width, height);
drawStaticBackground(bg);
// Initialise a few moving cars
for (let i = 0; i < 5; i++) {
cars.push({x: random(width), y: 200 + i * 20, speed: random(1, 3)});
}
}
function draw() {
// Display the pre‑rendered background
image(bg, 0, 0);
// Draw moving cars on top
noStroke();
fill(255, 0, 0);
cars.forEach(car => {
ellipse(car.x, car.y, 20, 10);
car.x += car.speed;
if (car.x > width) car.x = -20;
});
}
function drawStaticBackground(g) {
// All drawing goes into the graphics buffer 'g'
g.background(100, 150, 200); // sky
// Draw buildings
g.fill(50, 50, 50);
for (let i = 0; i < 10; i++) {
let w = random(30, 80);
let h = random(100, 250);
g.rect(20 + i * 80, height - h, w, h);
}
// Add some static details
g.stroke(255);
g.line(0, height - 10, width, height - 10); // horizon line
}
Key points:
- All static drawing is done in
drawStaticBackground()and stored inbg. - Inside
draw()we only callimage(bg, 0, 0)and animate the cars. - Because the background never changes, the browser skips the heavy pixel operations each frame.
Trade‑offs & Limitations
- Memory pressure: Large buffers can exhaust heap space on older phones. Keep buffer dimensions proportional to the visible area and consider down‑scaling if high resolution isn’t required.
- Manual refresh: The buffer does not auto‑update the main canvas. Forgetting
image(bg, 0, 0)will leave a static frame. Always verify the call is present. - Context switching: If you mix 2D and WebGL drawing in the same buffer, you must reset the context with
g.resetMatrix()or create separate buffers. - Dynamic content: Anything that changes (e.g., user input affecting the background) must be redrawn into the buffer, negating the performance benefit.
Actionable Checklist for Production Sketches
- Identify static or semi‑static layers that are drawn every frame.
- Instantiate a
p5.Graphicsbuffer withcreateGraphics()and the same dimensions as the main canvas. - Render the static content once into the buffer during
setup()or when the layer changes. - In
draw(), callimage(buffer, 0, 0)before any dynamic drawing. - Measure FPS and memory usage in dev tools. Compare against a sketch that draws the same background in
draw()to confirm the gain. - For mobile targets, keep buffer size ≤ 1920×1080 and consider using
createGraphics(width, height, WEBGL)only when GPU shaders are needed.
By following this pattern, you can keep your p5.js sketches snappy, even with rich, complex scenes. The off‑screen buffer becomes a powerful tool in your performance optimization toolbox.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.