Reducing Draw Calls in PixiJS with cacheAsBitmap
Learn how to use PixiJS cacheAsBitmap to collapse complex container hierarchies into single draw calls, improving GPU performance for static UI and backgrounds.
12 Aug 2026, 07:53 UTC

The Performance Wall of Complex Hierarchies
In PixiJS, every individual sprite or graphic in your scene typically requires a draw call—a command sent from the CPU to the GPU to render an object. While PixiJS uses batching to group similar objects, a complex scene with hundreds of nested containers, diverse textures, and layered UI elements can quickly overwhelm the GPU, leading to dropped frames and stuttering.
The core problem is that the GPU must calculate the position, rotation, and scale for every single child in a hierarchy every frame, even if those objects aren't moving relative to each other. The solution for static or semi-static groups is cacheAsBitmap.
How Bitmap Caching Works
The cacheAsBitmap property is a feature of the Container class. When set to true, PixiJS renders the entire container and all its descendants into a single internal render texture (a static image stored in GPU memory). Instead of iterating through every child object during the render loop, the engine simply draws one single rectangle containing that pre-rendered image.
This effectively collapses a deep tree of objects into a single draw call. It is most useful for:
- Complex HUDs or UI panels that only change occasionally.
- Detailed background layers composed of many small decorative sprites.
- Static maps or game boards.
Implementation Example
Consider a scenario where you have a "City Block" consisting of 200 small window sprites, street lamps, and sidewalk textures. Without caching, the engine processes 200+ objects every frame.
// Assuming PixiJS v7+ environment
const app = new PIXI.Application({ width: 800, height: 600 });
document.body.appendChild(app.view);
const cityBlock = new PIXI.Container();
// Create 200 decorative elements
for (let i = 0; i < 200; i++) {
const sprite = PIXI.Sprite.from('window.png');
sprite.x = Math.random() * 400;
sprite.y = Math.random() * 400;
cityBlock.addChild(sprite);
}
// Initial state: 200+ draw calls
app.stage.addChild(cityBlock);
// Optimization: Collapse to 1 draw call
cityBlock.cacheAsBitmap = true;
Updating a Cached Container
Because the container is now a static image, changing a child's position or color will have no visual effect. To update the content, you must toggle the cache to force a re-render:
function updateCityBlock() {
cityBlock.cacheAsBitmap = false; // Clear the texture
// Perform updates
cityBlock.children[0].x += 10;
cityBlock.cacheAsBitmap = true; // Re-generate the texture
}
Trade-offs and Technical Limitations
Bitmap caching is not a "silver bullet" and introduces three specific engineering costs:
| Constraint | Impact | Mitigation |
|---|---|---|
| GPU Memory | Each cached container consumes VRAM based on its pixel dimensions. | Avoid caching massive containers that cover the entire screen if they aren't complex. |
| Visual Fidelity | Scaling a cached bitmap causes pixelation/blurring, as it is now a raster image. | Apply scaling before setting cacheAsBitmap = true. |
| CPU Spikes | Toggling the cache causes a momentary freeze while the texture is re-generated. | Avoid toggling the cache inside the main ticker loop. |
Verification and Testing
To verify if cacheAsBitmap is working, use the browser's performance profiler or a PixiJS debugger plugin. Look for the "Draw Calls" or "Batches" metric. When the property is false, the number of batches should correlate with the number of children; when true, those children should disappear from the count, replaced by a single batch for the parent container.
If you notice a sudden drop in image quality when zooming into a cached object, it is a sign that you are scaling a rasterized texture rather than the original vector/sprite data.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.