pixijs-performance
Use this skill when profiling or optimizing a PixiJS v8 app for FPS, draw calls, or GPU memory. Covers destroy patterns (cacheAsTexture(false), releaseGlobalResources), GCSystem and TextureGCSystem, PrepareSystem, object pooling, batching rules, BitmapText for dynamic text, culling (Culler, CullerPlugin, cullable, cullArea), resolution/antialias tradeoffs. Triggers on: FPS, jank, draw calls, batching, object pool, GCSystem, PrepareSystem, Culler, cacheAsTexture, memory leak, destroy patterns.
What this skill does
Profile before optimizing. PixiJS handles a lot of content well out of the box; browser DevTools Performance + GPU profiling should be your first move. Once you've found the bottleneck, apply the targeted pattern below (destroy, pool, batch, cache, or cull).
## Quick Start
```ts
container.cacheAsTexture(true);
container.updateCacheTexture();
container.cacheAsTexture(false);
container.destroy({ children: true });
import { CullerPlugin, extensions } from "pixi.js";
extensions.add(CullerPlugin);
offscreenContainer.cullable = true;
offscreenContainer.cullArea = new Rectangle(0, 0, 256, 256);
// Tune GC via init options (ms). The `textureGC.*` properties are
// deprecated since 8.15.0 — use these on the Application init instead.
await app.init({ gcMaxUnusedTime: 60_000, gcFrequency: 30_000 });
```
**Related skills:** `pixijs-scene-container` (destroy options), `pixijs-scene-core-concepts` (render groups, layers, culling), `pixijs-scene-text` (BitmapText for dynamic content), `pixijs-assets` (atlasing), `pixijs-custom-rendering` (custom batchers).
## Core Patterns
### Proper destroy with cleanup
```ts
import { Sprite, Assets } from "pixi.js";
const texture = await Assets.load("character.png");
const sprite = new Sprite(texture);
// Destroy sprite only (preserve texture for reuse)
sprite.destroy();
// Destroy sprite AND its texture
sprite.destroy({ children: true, texture: true, textureSource: true });
```
When done with a loaded asset entirely:
```ts
Assets.unload("character.png");
```
This removes it from the cache and unloads the GPU resource.
### Application destroy/recreate cycle
```ts
import { Application } from "pixi.js";
// Correct destroy that cleans global pools
app.destroy({ releaseGlobalResources: true });
const newApp = new Application();
await newApp.init({ width: 800, height: 600 });
```
Without `releaseGlobalResources: true`, pooled objects (batches, textures) from the old app leak into the new one, causing flickering and corruption.
### Texture garbage collection
PixiJS auto-collects unused textures and GPU resources via `GCSystem`. Defaults: checks every 30 seconds, removes resources idle for 60 seconds. These are time-based (milliseconds).
```ts
import { Application } from "pixi.js";
const app = new Application();
await app.init({
gcActive: true,
gcMaxUnusedTime: 120000, // idle time before cleanup in ms (default: 60000)
gcFrequency: 60000, // check interval in ms (default: 30000)
});
```
For manual control:
```ts
texture.source.unload(); // immediate GPU memory release
```
### PrepareSystem for GPU upload
Upload textures and graphics to GPU before rendering to avoid first-frame hitches:
```ts
import "pixi.js/prepare";
import { Application, Assets } from "pixi.js";
const app = new Application();
await app.init();
// Don't render until assets are uploaded
app.stop();
const texture = await Assets.load("large-scene.png");
// Upload to GPU ahead of time
await app.renderer.prepare.upload(app.stage);
// Now rendering won't hitch on first frame
app.start();
```
`prepare.upload()` accepts a Container (uploads all textures, text, and graphics in the subtree) or individual resources.
### cacheAsTexture for performance
`cacheAsTexture()` renders a container's subtree to a single texture, reducing draw calls for complex static content. Internally it creates a render group and caches the result.
**When to use:**
- Many static children (UI panels, decorative backgrounds, complex Graphics)
- Containers with expensive filters (cache the filter result)
- Large subtrees that rarely change
**Tradeoffs:**
- Uses GPU memory for the cached texture (larger containers = more memory)
- Max texture size is GPU-dependent (typically 4096x4096; check `renderer.texture.maxTextureSize`)
- Must call `updateCacheTexture()` after modifying children
- Combining with masks is fragile (see the masking skill)
```ts
import { Container, Sprite } from "pixi.js";
const panel = new Container();
// ... add many static children ...
panel.cacheAsTexture(true);
// With options
panel.cacheAsTexture({ resolution: 2, antialias: true });
// Refresh after changes
panel.updateCacheTexture();
// MUST disable before destroying (see Common Mistakes below)
panel.cacheAsTexture(false);
panel.destroy();
```
**Avoid:** toggling on/off repeatedly (constant re-caching negates benefits), caching sparse containers (negligible gain), caching containers larger than 4096x4096.
### Object recycling
Reuse objects by changing their properties instead of destroy/recreate:
```ts
import { Sprite, Container, Texture } from "pixi.js";
class BulletPool {
private _pool: Sprite[] = [];
private _container: Container;
constructor(container: Container) {
this._container = container;
}
public get(texture: Texture): Sprite {
let bullet = this._pool.pop();
if (!bullet) {
bullet = new Sprite(texture);
this._container.addChild(bullet);
}
bullet.texture = texture;
bullet.position.set(0, 0);
bullet.rotation = 0;
bullet.scale.set(1);
bullet.alpha = 1;
bullet.tint = 0xffffff;
bullet.blendMode = "normal";
bullet.visible = true;
return bullet;
}
public release(bullet: Sprite): void {
bullet.visible = false;
this._pool.push(bullet);
}
}
```
Destroying and recreating is significantly more expensive than toggling `visible` and updating properties. GPU resources stay allocated; only scene graph visibility changes.
### Batching optimization
PixiJS batches similar consecutive objects into single draw calls. Batch breaks occur on:
- Object type change (Sprite vs Graphics)
- Texture source change (beyond the per-batch texture limit, typically 16)
- Blend mode change
- Topology change
Optimize draw order:
```ts
import { Sprite, Graphics, Container } from "pixi.js";
// 4 draw calls: type alternates
const bad = new Container();
bad.addChild(new Sprite(t1));
bad.addChild(new Graphics().rect(0, 0, 10, 10).fill(0xff0000));
bad.addChild(new Sprite(t2));
bad.addChild(new Graphics().rect(0, 0, 10, 10).fill(0x00ff00));
// 2 draw calls: types grouped
const good = new Container();
good.addChild(new Sprite(t1));
good.addChild(new Sprite(t2));
good.addChild(new Graphics().rect(0, 0, 10, 10).fill(0xff0000));
good.addChild(new Graphics().rect(0, 0, 10, 10).fill(0x00ff00));
```
Same principle applies to blend modes: `screen/normal/screen/normal` = 4 draws; `screen/screen/normal/normal` = 2 draws.
### Spritesheets over individual textures
```ts
import { Assets, Sprite } from "pixi.js";
// Load a spritesheet (single texture atlas)
const sheet = await Assets.load("game-atlas.json");
// All frames share one GPU texture; enables batching
const hero = new Sprite(sheet.textures["hero.png"]);
const enemy = new Sprite(sheet.textures["enemy.png"]);
const coin = new Sprite(sheet.textures["coin.png"]);
```
Individual textures each require their own GPU upload and break batches when the texture limit is exceeded. Spritesheets consolidate many frames into one atlas texture.
Use `@0.5x` filename suffix on half-resolution sheets so PixiJS auto-scales them.
### Text performance
Text and HTMLText re-render to a canvas and re-upload to the GPU on every change. Never update them per frame unconditionally:
```ts
import { BitmapText, Text } from "pixi.js";
// Wrong: re-renders canvas + GPU upload every frame
app.ticker.add(() => {
scoreText.text = `Score: ${score}`;
});
// Correct: use BitmapText for frequently changing content
const scoreText = new BitmapText({
text: "Score: 0",
style: { fontFamily: "Arial", fontSize: 24, fill: 0xffffff },
});
app.ticker.add(() => {
scoreText.text = `Score: ${score}`;
});
```
BitmapText renders from a pre-generated glyph atlas. Updates only reposition quads; no canvas re-render or GPU upload. Use it for scores, timers, counters, and anything that changes frequently.
If you must use canvas Text, guard updates so they only happen when the value changes:
```tsRelated in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.