Culling the Full Scene Graph vs. Windowing Visible Sprites for Large PixiJS Datasets
0 reputation · 20 Nov 2022, 06:19 UTC
0 reputation · 20 Nov 2022, 06:19 UTC
The goal is to maintain a stable frame rate when rendering thousands of sprites where most are outside the viewport. PixiJS offers two documented strategies: enabling the cullable flag (optionally with a cullArea rectangle) so the renderer skips off‑screen bounds during the draw pass, or applying a windowing/pagination technique that keeps only the visible slice of the dataset in the scene graph, adding and removing sprites as the camera moves. A key constraint is whether off‑screen items must still receive updates, tweens, or pointer events, and the exact behavior of automatic per‑frame culling varies between PixiJS major versions.
Does setting cullable trigger automatic culling each frame in the current PixiJS version, or must I invoke Culler.shared.cull manually? If off‑screen sprites need to stay interactive or animated, does windowing avoid the extra transform cost of keeping them in the scene graph? Which method yields lower overall overhead when the camera moves slowly versus rapidly?
33275 reputation · 20 Nov 2022, 07:48 UTC
Marking a display object cullable = true only skips its draw call when its bounds fall outside the viewport — the object stays in the scene graph and still costs you transform updates, event-manager traversal, and bounds computation. Windowing (keeping only the visible slice in the graph) removes those costs entirely but shifts the burden to your own add/remove bookkeeping. For truly large datasets where most sprites are off-screen, windowing almost always wins on CPU; culling wins on simplicity and correctness when off-screen sprites must keep animating or receiving events.
In current PixiJS (v7/v8 behavior), culling is applied during the render pass for objects flagged cullable — you do not call Culler.shared.cull() yourself for normal rendering. Culler.shared exists so you can run culling manually against an arbitrary rectangle, which is useful for off-screen render targets or custom viewports, not for the default canvas render. The exact integration point has shifted between major versions (v7's @pixi/cull-* packages vs. v8's reorganized culling), so verify against your installed version:
sprite.cullable = true;
// optionally constrain the tested bounds:
sprite.cullArea = new PIXI.Rectangle(0, 0, 800, 600);Important caveat: culling skips the draw, but if the sprite sits inside a moving container, PixiJS still recalculates its world transform every frame. Culling reduces GPU/draw overhead, not scene-graph traversal overhead.
Windowing removes off-screen sprites from the graph, so they cost nothing in transforms, bounds, or event traversal. The trade-offs:
| Scenario | Lower-overhead choice |
|---|---|
| Slow camera, huge dataset, off-screen sprites inert | Windowing with a sprite pool |
| Rapid camera movement | Culling (constant overhead, no churn) |
| Off-screen sprites need tweens/pointer events | Culling — windowing removes them from updates and hit-testing |
Don't trust either strategy blindly — measure on your target hardware:
root.children.length per frame to confirm windowing actually shrinks the graph.cullable toggled.One diagnostic detail that changes the recommendation: roughly how many sprites enter/leave the viewport per second at your worst-case camera speed? If that number is high and you can't pool effectively, culling is the safer default.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.