ParticleContainer vs Container: handling per-particle texture swaps without breaking batch rendering
0 reputation · 15 Jun 2020, 11:53 UTC
0 reputation · 15 Jun 2020, 11:53 UTC
Render 20,000–50,000 similar entities (particles, tiles, or sprites) at 60 fps while allowing a small subset (≈1–5 %) to change their visual appearance at runtime by swapping to a different texture from the same atlas.
Container preserves full per-sprite transform flexibility and lets any child change its texture freely, but incurs one draw call per child, which exceeds the frame budget at this scale. ParticleContainer collapses the entire batch into a single draw call when all children share the same baseTexture, yet the current implementation silently drops any child whose texture points to a different baseTexture until the container is rebuilt—no event, warning, or automatic re-batch occurs. Rebuilding the container (or toggling autoResize) reallocates the geometry buffers and stalls the frame. The migration guide suggests flattening the hierarchy and using a texture atlas, but does not address the case where a minority of particles must temporarily show a different atlas frame that lives on a separate baseTexture.
ParticleContainer layer, a custom shader, or a texture-atlas layout strategy—that lets a few particles switch baseTexture without rebuilding the primary batch?ParticleContainer buffer reallocation for 50 k sprites on typical desktop GPUs, and does autoResize: true amortize this cost acceptably?ParticleContainer while a secondary container handles the texture-swapping particles?35275 reputation · 15 Jun 2020, 15:57 UTC
ParticleContainer that references the primary atlas. The container batches all children into one draw call as long as every child points to the same BaseTexture.
BaseTexture into a second ParticleContainer (or a plain Container if interactivity is required). Layer it above or below the main container using zIndex or addChildAt.
ParticleContainer but may increase texture size.
autoResize triggers a power‑of‑two resize.
autoResize: true the buffer grows geometrically (≈2×). The cost is confined to the resize boundary; subsequent frames are cheap.
maxSize (if the particle count is bounded) removes the resize overhead entirely.
ParticleContainer do not support interactive or hitArea by default, so InteractionManager will not register pointer events on them.
Container (or a second ParticleContainer if you still want batching) and set interactive: true on the individual sprites.
1. Primary batch (all particles that stay on the main atlas):
const mainAtlas = PIXI.BaseTexture.from('atlasA.png');
const mainContainer = new PIXI.ParticleContainer(50000, {
scale: true,
rotation: true,
uvs: true,
alpha: true
});
mainContainer.baseTexture = mainAtlas;
// add 49 500 sprites here
2. Secondary batch for swapping sprites (small minority):
const swapAtlas = PIXI.BaseTexture.from('atlasB.png');
const swapContainer = new PIXI.ParticleContainer(2500, {
scale: true,
rotation: true,
uvs: true,
alpha: true
});
swapContainer.baseTexture = swapAtlas;
// add 1 250 sprites that need the alternate texture
3. Layering (add the containers to the stage in the desired order):
app.stage.addChild(mainContainer);
app.stage.addChild(swapContainer); // or addChildAt(swapContainer, 0) to render below
4. Pre‑allocate buffer size to avoid resize spikes:
mainContainer.maxSize = 50000;
swapContainer.maxSize = 2500;
5. Interaction (if needed):
const interactiveLayer = new PIXI.Container();
interactiveLayer.interactive = true;
interactiveLayer.addChild(swapContainer); // only if swapping sprites must be clickable
app.stage.addChild(interactiveLayer);
Do the particles that change texture also need to respond to pointer events (e.g., clicks or hover)? If so, a plain Container for the swapping subset (instead of a ParticleContainer) will be required to preserve interactivity, which adds a second draw call for those sprites but keeps the primary batch untouched.
Use comments to ask for clarification. Post a solution as an answer.
6,200 reputation · 15 Jun 2020, 23:21 UTC
PixiJS v5–v7 handle cross-baseTexture particles differently: v5 silently drops them, v6 may flush the batch per-frame, and v7 introduces ParticleContainer#batchable flags that can auto-split. Verify your exact version with a minimal 50 k sprite test—switch 1 % to a second atlas frame and log renderer.batch.drawCalls each frame.
The most reliable zero-rebuild pattern remains packing every frame the minority particles might need into a single atlas (same baseTexture). If that’s impossible, a second ParticleContainer layered above the primary works, but set hitArea = new PIXI.Rectangle(0,0,w,h) on every child in both containers; ParticleContainer bounds are not auto-computed for hit-testing.
autoResize only avoids reallocation when the count grows; it does not amortize texture-induced rebuilds. Profile with PIXI.settings.GC_MODE = PIXI.GC_MODES.MANUAL and explicit container.updateBuffers() to measure the actual stall on your target GPUs.