Using PGraphics in Processing to Cache Layers and Reduce Flicker
When a Processing sketch stutters because static elements are redrawn every frame, PGraphics offers an off-screen buffer you can composite with a single image() call. Here is how the pattern works, a concrete example, and the memory and renderer trade-offs to check.
16 Sept 2026, 20:40 UTC

You add a dense background—thousands of points, a detailed grid, a particle field—and the sketch that ran smoothly suddenly drops in frame rate and flickers. The problem is not usually the number of shapes; it is that Processing redraws everything on the main canvas every frame, including elements that have not changed. A useful fix is to move those layers into a PGraphics off-screen buffer and composite them with one image() call.
The main canvas is not a compositing engine
In a standard Processing sketch, draw() runs in a loop. Each iteration clears the canvas and re-executes every drawing command. If 90% of your scene is static, you still pay the cost of issuing those commands 60 times per second. PGraphics is an off-screen bitmap that uses the same drawing API as the main window. You can render into it independently, then copy the finished bitmap onto the canvas in a single operation. That shifts the work from many draw calls per frame to one copy per frame for the cached layer.
The buffer pattern: create, draw, blit
The core pattern has three steps:
- In
setup(), create the buffer withcreateGraphics(width, height). Choose a renderer such asP2DorP3Dif you want the buffer to use OpenGL acceleration on supported systems. - Draw the heavy or static content into the buffer between
beginDraw()andendDraw(). For truly static art, do this once insetup(). For slowly changing content, redraw the buffer only when the source data changes. - In
draw(), callimage(buffer, 0, 0)to copy the buffer onto the main canvas, then draw your dynamic elements on top.
Because the buffer is allocated once and reused, you avoid the memory churn of creating a new PImage every frame.
Worked example: a static field with a moving cursor
The following sketch renders 5,000 random points into a PGraphics buffer once, then draws a cursor circle every frame. Run it in the Processing IDE (Java mode); no external libraries or special permissions are required.
PGraphics field;
void setup() {
size(800, 600);
field = createGraphics(width, height);
field.beginDraw();
field.background(240);
field.strokeWeight(2);
for (int i = 0; i < 5000; i++) {
field.stroke(random(100), random(100), random(160));
field.point(random(width), random(height));
}
field.endDraw();
}
void draw() {
image(field, 0, 0);
noStroke();
fill(200, 30, 30);
ellipse(mouseX, mouseY, 40, 40);
}
If the field must change—say, every 30 frames—wrap the buffer drawing in a conditional and call field.beginDraw() only when needed. To debug what the buffer contains, temporarily replace the image() call with a scaled or offset version so you can inspect the intermediate render without the overlay.
Memory, threads, resizing, and verification
PGraphics is not free. It consumes RAM proportional to width × height × bytes-per-pixel. A full-window 800×600 P2D buffer is roughly 1.9 MB (800 × 600 × 4 bytes), but multiple buffers or 4K surfaces can exhaust memory on constrained devices. You can compare Runtime.getRuntime().totalMemory() and freeMemory() before and after createGraphics() to measure the cost on your target hardware.
Two other limitations matter in practice:
- Thread safety:
PGraphicsis not thread-safe. Do not draw to it from a background thread unless you synchronize access, or you risk race conditions and corrupted buffers. - Renderer fallback: On some older GPUs or drivers,
P3Dmay not be available forPGraphicsand the renderer can fall back to CPU rasterization. If frame rate does not improve after switching toP2D/P3D, check whether hardware acceleration is actually active on your system.
Resizing introduces a state change: if the sketch window changes size, the old buffer no longer matches the canvas. Recreate it with the new dimensions to avoid stretching or clipping.
To check whether the pattern helped, add println(frameRate) inside draw() and compare the average over a 10-second window with and without the buffer. If the frame rate is unchanged, the bottleneck is likely the dynamic layer itself, not the cached background. Visually, the buffer version is expected to show less stutter when the static layer contains thousands of primitives. If you see tearing or stale pixels, confirm that the buffer dimensions match the canvas and that you are not accidentally redrawing the buffer every frame when it does not need to change.
If you are deciding where to apply this, start with the layer that contains the most primitives and changes the least. Cache it once, measure the frame rate, and only add buffers where the numbers justify the extra memory.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.