Skip to content

spryte vs Phaser ​

On the same machine and scenes in 2dbench, spryte draws moving sprites 2.2–3.1× faster than Phaser 4's Image objects and 5–11× faster than its bulk paths, at about 1/24 of the JavaScript (11.3 kB against 276.6 kB at startup). Every frame was checked pixel for pixel against the others before anything was timed. spryte is only a renderer and Phaser is a whole framework, so this compares the drawing, not the framework; the end of this post covers where Phaser is the better choice.

Results ​

RTX 5060 laptop (Ryzen 7 260), Chrome 155, AC power, 5 interleaved rounds per scene; median frame time from simulation to GPU completion. spryte 0.2.1, Phaser 4.2.1, PixiJS 8.22.0 for reference. Raw data: results/community/*-rtx-5060-laptop-phaser-*.json.

Workload, 50k spritessprytePhaser 4PixiJS 8spryte vs Phaser
Bunnymark, position only: draw.sprite / Image / Sprite4.8 ms15.0 ms13.5 ms3.1×
Mixed, 4 textures, rotation, scale, tint, alpha7.4 ms20.0 ms23.0 ms2.7×
Particles, every property changing6.9 ms19.9 ms21.4 ms2.9×
Particles, bulk: draw.sprites / SpriteGPULayer / ParticleContainer1.9 ms9.5 ms7.0 ms5.0×

At 200k bulk sprites the gap widens: 2.7 ms against SpriteGPULayer's 23.2 ms and Blitter's 29.9 ms for position-only bunnies (8.6–11×), and 6.2 ms against 35.2 ms for full particles (5.7×).

Most sprites at 60 fps ​

ScenesprytePhaser 4
bunny, one object or call per sprite100,00050,000
mixed100,00020,000
particles100,00020,000
bunny, bulk (Blitter or SpriteGPULayer)800,000100,000
particles, bulk (SpriteGPULayer)400,00050,000
raycaster (enemies)10,0005,000

Counts come from a ladder whose steps are up to 2× apart, so they're lower bounds. In the raycaster (a first-person maze drawn as thousands of texture slices), spryte's frame at 10k enemies was 10.1 ms against Phaser's 22.3 ms (2.2×) and PixiJS's 37.3 ms.

Credit where it's due: Phaser's Image beat PixiJS's Sprite in the mixed and particles scenes (20.0 ms against 23.0 ms, and 19.9 against 21.4, at 50k), was close behind in bunnymark, and was well ahead in the raycaster. The usual assumption that PixiJS is the fast one and Phaser the heavy one doesn't hold for per-sprite drawing.

Download size ​

One rotated sprite, Vite 8 production build, brotli quality 11 (bench/size):

JS at startup
spryte 0.3.011.3 kB
PixiJS 8.2287.8 kB
Phaser 4.2.1276.6 kB

Phaser's npm entry is one prebuilt bundle with every system in it, so a bundler can't tree-shake it. A custom Phaser build is smaller, but it isn't the default path. Most of that size is also things spryte doesn't do: physics, input, audio, tweens, a loader and scenes.

Why the gap ​

A Phaser Image is a game object: each frame Phaser walks the display list, updates its transform, culls it and batches it. spryte keeps no objects. A draw.sprite call writes a few numbers into a typed array, and draw.sprites uploads a Float32Array of positions as it is, with no per-sprite JavaScript at all.

Phaser's bulk options each give something up. Blitter draws position-only Bobs with no rotation or scale. SpriteGPULayer supports rotation, scale and tint, but is built for members that rarely change: its docs say frequent edits are expensive, and rewriting every member every frame re-uploads the buffer. That's what a moving-particle scene needs, so its numbers here are a fair reading of what Phaser offers, not of what SpriteGPULayer was designed for. Writing raw words with patchMember may be faster; we haven't measured it, and a PR to 2dbench that does is welcome.

How it was measured ​

  • Same work: one shared, seeded simulation and the same textures; 960×540, nearest filtering, no antialiasing, whole-pixel rounding (Phaser pixelArt: true, roundPixels: true).
  • Pixel parity first: at 2k and 50k sprites, every adapter's frame is compared with the reference. Phaser drew 0% different pixels inside sprites in every scene, and its edge differences matched spryte's.
  • Phaser runs as a game would: its own loop is stopped and the harness calls game.step() each frame, which runs the scene's update list and camera as well as the render. That's a small cost PixiJS doesn't pay, and every Phaser game pays it.
  • WebGL1: Phaser 4's renderer creates a WebGL1 context and has no WebGL2 option. spryte uses WebGL2.

The Phaser adapter is open. Phaser users who know a faster idiomatic path are invited to send it.

What this doesn't show ​

  • One machine. These numbers are one RTX 5060 laptop, the same machine as spryte's reference results against PixiJS but a different day, so compare ratios within a run, not milliseconds across them. On the same laptop's integrated Radeon 780M, the gaps were smaller (2.0–2.3× and 3.0–4.6×; raw data). Run 2dbench on your device.
  • Static scenes. The gap is per-sprite work each frame. When little moves, it shrinks.
  • Everything except drawing. Physics, input, audio and scene management aren't measured, and spryte doesn't have them.
  • Not yet covered for Phaser: 2dbench's tilemap, churn, clipping, upload and text scenes.

When to pick Phaser ​

Pick Phaser if you want one framework that does everything: Arcade and Matter physics, tweens, a loader, scenes, input, audio, a visual editor and years of examples. Most games aren't limited by sprite count, and Phaser's per-sprite drawing is quick.

Pick spryte if drawing is your bottleneck (bullet hells, particles, crowds, big tilemaps), if download size matters, or if you want a renderer under your own code or under debuff, which adds the loop, input, audio and testing.

Already on Phaser? Migrating from Phaser has a mapping table and a way to measure spryte on your heaviest effect in an afternoon, without rewriting anything.