Skip to content
GitHub repository
Rendering

Performance

One instanced draw call per SilkGraphics and about 12 ms of CPU to rebuild 50,000 primitives, plus tips for static layers, gradients and fill rate.

Updated pixi-silk 0.1.0

Source src/demos/stress.demo.ts
import { Text } from 'pixi.js';
import { SilkGraphics } from 'pixi-silk';
import { defineDemo } from './runtime';

// Everything is rebuilt every frame: one SilkGraphics, one draw call, however many primitives.
export default defineDemo({
    size: [480, 260],
    controls: {
        count: { type: 'range', label: 'primitives', min: 500, max: 30000, step: 500, value: 5000 },
    },
    setup({ stage, params, tick }) {
        const g = new SilkGraphics();
        const stats = new Text({
            text: '',
            style: { fill: 0xffffff, fontSize: 12, fontFamily: 'ui-monospace, monospace' },
        });

        stats.position.set(12, 10);
        stage.addChild(g, stats);
        let cpu = 0;

        tick((t) => {
            const start = performance.now();

            g.clear();
            for (let i = 0; i < params.count; i++) {
                const a = i * 2.399963 + t * 0.2;
                const r = Math.sqrt(i / params.count) * 120;
                const x = 240 + Math.cos(a) * r * 1.8;
                const y = 130 + Math.sin(a) * r;

                if (i % 3 === 0) g.circle(x, y, 2.2).fill(0xff375f);
                else if (i % 3 === 1) g.roundRect(x - 2, y - 2, 4, 4, 1).fill(0x0a84ff);
                else g.line(x, y, x + 5, y + 2).stroke({ width: 1, color: 0x30d158 });
            }
            cpu = cpu * 0.9 + (performance.now() - start) * 0.1;
            stats.text = `${params.count} primitives, 1 draw call, rebuilt in ${cpu.toFixed(1)} ms`;
        });
    },
});

The cost model#

  • Each SilkGraphics is one draw call, whatever it contains.
  • The CPU writes 40 floats per primitive into a reusable buffer. Rebuilding 50,000 primitives every frame takes about 12 ms on a laptop. Typical UIs (hundreds of primitives) cost well under 0.5 ms.
  • The GPU shades each primitive as one quad sized to its bounds plus a one-pixel margin, once per covered pixel. Cost scales with covered area, like any 2D renderer. Large blurs grow the quad by 3σ.
  • Buffers grow to the largest frame and stay in memory. Gradients share rows in one 256x256 half-float atlas.

Tips#

  • Split static and dynamic parts. Draw backgrounds and label shapes once, in one object. Redraw only the animated parts every frame.
  • Move objects instead of rebuilding them. Animating position, rotation, scale or alpha of a SilkGraphics changes one matrix.
  • Reuse gradients. Create them at module scope. Identical ramps share an atlas row anyway, but reusing the object avoids re-parsing stops.
  • Group primitives that share a layer into one SilkGraphics to save draw calls.
  • Cap the resolution on large canvases: createSilkApp({ maxResolution: 2 }).
  • Stop redrawing when idle. Check Spring.settled, or redraw only on data changes.

API reference: SilkGraphics