Skip to content

perf(universe): batch 585 player sprites into one InstancedMesh - #15

Merged
bryanshao07 merged 1 commit into
mainfrom
perf/instanced-player-points
Aug 4, 2026
Merged

bryanshao07 merged 1 commit into
mainfrom
perf/instanced-player-points

Conversation

@bryanshao07

Copy link
Copy Markdown
Owner

PlayerPoints mounted one per player, so the cloud cost 585 draw calls and 585 useFrame subscriptions every frame -- even though at most two points (hovered, selected) are ever animating.

Replace them with a single InstancedMesh. Quads are billboarded in the vertex shader via onBeforeCompile rather than on the CPU, which is what THREE.Sprite does internally, so the look and perspective falloff are unchanged and instanceMatrix never has to be rewritten just to face the camera. Position and scale live in instanceMatrix, colour in instanceColor, both written only when the data or colour mode changes. A single useFrame rewrites just the hovered and selected instances, making per-frame work O(1) instead of O(585).

three's stock InstancedMesh.raycast intersects the quad as stored in instanceMatrix -- edge-on to the camera, since billboarding is in the shader -- so it is replaced with a per-instance ray/sphere test returning the nearest hit.

Measured on /universe at 1440x900 with 585 points, counting GL draw calls by wrapping the WebGL draw entrypoints (driver-independent, cross-checked against an independent in-app HUD):

draw calls, idle 593 -> 9
draw calls, selected 599 -> 15
useFrame subscriptions 595 -> 11

PlayerPoints' own share: 585 draw calls -> 1, 585 subscriptions -> 1. The remainder belongs to the gizmo and overlays, untouched here.

Also adds a dev-only perf HUD at /universe?perf=1 for reading FPS and draw calls on real hardware; headless rendering is software rasterized and cannot produce a meaningful FPS figure. It is compiled out of production builds.

PlayerPoints mounted one <sprite> per player, so the cloud cost 585 draw
calls and 585 useFrame subscriptions every frame -- even though at most two
points (hovered, selected) are ever animating.

Replace them with a single InstancedMesh. Quads are billboarded in the vertex
shader via onBeforeCompile rather than on the CPU, which is what THREE.Sprite
does internally, so the look and perspective falloff are unchanged and
instanceMatrix never has to be rewritten just to face the camera. Position and
scale live in instanceMatrix, colour in instanceColor, both written only when
the data or colour mode changes. A single useFrame rewrites just the hovered
and selected instances, making per-frame work O(1) instead of O(585).

three's stock InstancedMesh.raycast intersects the quad as stored in
instanceMatrix -- edge-on to the camera, since billboarding is in the shader --
so it is replaced with a per-instance ray/sphere test returning the nearest hit.

Measured on /universe at 1440x900 with 585 points, counting GL draw calls by
wrapping the WebGL draw entrypoints (driver-independent, cross-checked against
an independent in-app HUD):

  draw calls, idle          593 -> 9
  draw calls, selected      599 -> 15
  useFrame subscriptions    595 -> 11

PlayerPoints' own share: 585 draw calls -> 1, 585 subscriptions -> 1. The
remainder belongs to the gizmo and overlays, untouched here.

Also adds a dev-only perf HUD at /universe?perf=1 for reading FPS and draw
calls on real hardware; headless rendering is software rasterized and cannot
produce a meaningful FPS figure. It is compiled out of production builds.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@bryanshao07
bryanshao07 merged commit cf0ef73 into main Aug 4, 2026
2 checks passed
@bryanshao07
bryanshao07 deleted the perf/instanced-player-points branch August 4, 2026 12:41
@vercel

vercel Bot commented Aug 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
crossover Ready Ready Preview Aug 4, 2026 12:42pm

This branch was successfully deployed

1 active deployment
Preview — 290b6a44 Deployed Aug 4, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant