Repository navigation
perf(universe): batch 585 player sprites into one InstancedMesh - #15
Merged
Merged
Conversation
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>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.