Skip to content
pixi-silk
GitHub repository
Reference

Performance

What a SilkGraphics costs on the CPU and GPU, how batching joins objects into one draw, and how to keep static layers and large canvases cheap.

Updated pixi-silk 1.0.1View as Markdown

A SilkGraphics is one instanced draw call, whatever it contains. What you pay for is the CPU work to rebuild it and the GPU work per covered pixel.

Drag the slider to add primitives.

The cost model#

CostWhat it is
Draw callsOne per SilkGraphics. With batching on, a run of them shares one
CPU44 floats per primitive, written into a reusable buffer when you redraw. See CPU cost measured for what a rebuild takes
GPUEach primitive is one quad sized to its bounds plus a one-pixel margin, shaded once per covered pixel. A blur grows the quad by three sigma
MemoryBuffers grow to the largest frame and stay. Gradients share one 256x256 half-float atlas, one row per ramp

GPU cost follows covered area, like any 2D renderer. A full-screen blurred shape costs more than a thousand small dots.

CPU cost measured#

The benchmark in packages/pixi-silk/bench/primitives.mjs rebuilds one SilkGraphics per frame like the stress demo above. It calls clear(), then draws circles and rounded rectangles with fill() and lines with stroke(), a third each. It times the CPU work only, not the upload or the draw.

Primitives per frameMedian CPU time
5000.2 ms
5,0001.8 ms
50,00018 to 21 ms

Each row is the range of medians over several runs of 200 or more frames, on an Apple M4 with Node 24.14. A browser on other hardware gives other numbers. Run pnpm build && pnpm --filter pixi-silk bench to measure your machine, and pass a count to try other sizes.

Batch many objects#

Batching is off by default. enableSilkBatching() turns it on for objects created after the call.

Turn batching off and watch the draw count.
import { enableSilkBatching, SilkGraphics } from 'pixi-silk';

enableSilkBatching();
const dot = new SilkGraphics().circle(0, 0, 4).fill(0x0a84ff); // batched
const alone = new SilkGraphics({ batch: false }); // its own draw call

Consecutive SilkGraphics with the same blend mode and the same dither, aaWidth and minStrokePx render in one draw. A filter, a mask, another kind of display object or a different blend mode in between starts a new batch. Batching works on WebGL2 and on WebGPU.

A batch copies the instances and path data of its objects into one buffer. When none of them was redrawn since the last frame, it copies only their matrices and tints.

Keep frames cheap#

Split static and dynamic parts. Draw backgrounds and fixed shapes once, and redraw only the parts that change.

Move objects instead of redrawing them. Changing position, rotation, scale or alpha of a SilkGraphics updates one matrix and costs nothing on the CPU side.

Create gradients once, at module scope. Identical ramps share an atlas row anyway, but reusing the object skips parsing the stops again. The atlas has 256 rows. Past that, pixi-silk warns and reuses row 0.

Put primitives that share a layer into one SilkGraphics, or turn on batching.

Cap the resolution of large canvases with createSilkApp({ maxResolution: 2 }). Fill rate grows with the square of the resolution.

Stop redrawing when nothing moves. Check Spring.settled, or redraw only when the data changes.

API reference: SilkGraphics, SilkGraphicsOptions, enableSilkBatching