Understanding preserveDrawingBuffer in WebGL

This article explains the purpose, technical mechanics, and practical implications of the preserveDrawingBuffer attribute in WebGL. It covers how this configuration affects frame rendering, why it defaults to disabled, the performance trade-offs of enabling it, and the specific scenarios—such as taking canvas screenshots or creating drawing applications—where it becomes essential.

What Is preserveDrawingBuffer?

When initializing a WebGL context using canvas.getContext('webgl', options) or canvas.getContext('webgl2', options), you can pass a configuration object containing the boolean attribute preserveDrawingBuffer.

By default, preserveDrawingBuffer is set to false.

// Default behavior
const gl = canvas.getContext('webgl', { preserveDrawingBuffer: false });

// Preserving the buffer
const gl = canvas.getContext('webgl', { preserveDrawingBuffer: true });

Default Behavior (preserveDrawingBuffer: false)

In default WebGL operation, the browser clears the drawing buffer automatically after compositing the canvas content with the rest of the web page.

Once the current execution frame finishes rendering and the compositor displays the frame, the underlying color buffer is discarded or swapped. If code outside the immediate rendering loop attempts to read the canvas content—such as via canvas.toDataURL() or canvas.toBlob()—the output is usually a completely blank, black, or transparent image.

The primary benefit of this default behavior is performance. It allows the GPU and browser compositor to utilize efficient swap-chain and double-buffering mechanisms without copying pixel data from frame to frame. Mobile devices and integrated GPUs particularly benefit from this optimization, as it conserves memory bandwidth and battery life.

Setting preserveDrawingBuffer: true

Setting preserveDrawingBuffer to true instructs the browser not to automatically clear the color buffer. Instead, the drawing buffer retains its pixel values until you explicitly clear them using gl.clear() or overwrite them with subsequent draw calls.

This setting is significant for two primary use cases:

  1. Canvas Captures and Screenshots: If you need to generate images from the canvas at arbitrary times (for example, triggered by a user clicking a "Save Image" button), setting preserveDrawingBuffer: true ensures that the rendered content remains intact and accessible to methods like toDataURL().
  2. Cumulative Rendering: Applications such as drawing tools, particle trails, or iterative simulations often rely on drawing new content directly on top of the previous frame without clearing the background.

Performance Trade-Offs

While convenient, setting preserveDrawingBuffer: true introduces potential performance penalties:

  • Memory Copy Overhead: The browser may be forced to copy the drawing buffer to an internal texture for compositing rather than simply flipping buffer pointers.
  • Tile-Based Deferred Rendering (TBDR) Inefficiencies: Mobile GPUs frequently use tile-based rendering architectures. Preserving buffers prevents the GPU from discarding tile memory, requiring extra memory read and write cycles.
  • Higher Memory Footprint: The browser must maintain dedicated memory structures to preserve the frame data indefinitely.

For optimal performance, leave preserveDrawingBuffer set to false whenever possible:

  • For Screenshots: Keep the attribute false, but call canvas.toDataURL() synchronously inside the same requestAnimationFrame callback immediately after your draw calls, before execution returns to the browser compositor.
  • For Persistent Drawing or Accumulation: Consider rendering to an offscreen Framebuffer Object (FBO) backed by a texture. This gives you full control over buffer persistence without forcing the presentation buffer to incur compositing penalties.