Files
clawmates/skills/threejs/threejs-perf-and-teardown.md
T
Omar SobhandClaude Opus 4.7 7b23f61632
ci / gates (push) Successful in 4s
ci / frontend (push) Successful in 25s
ci / rust (push) Failing after 3m41s
ci / e2e (push) Skipped
ci / publish (push) Skipped
slice 3.5c: seed 15 built-in skills across the 6 stacks
Hand-authored skill catalog anchored to real 2026-07 versions:
  - Rust 1.97.1 (stable), edition 2024
  - React 19.2.7, Server Components + Actions
  - TailwindCSS 4.3.3 (CSS-first config, Oxide engine)
  - three.js r185 (WebGPURenderer stable, BatchedMesh matured)
  - React Native 0.86 / Expo SDK 54+ (New Architecture default)
  - cargo-nextest 0.9.140, gitleaks 8.20+, cargo-audit 0.21+
  - Postgres 17 (18 in beta, don't rely on)
  - CUDA Blackwell, Metal Apple7+, ROCm CDNA3

Ships 15 skills across the categories:
  foundation/  workspace-repo-commit-protocol
               small-focused-commits
               tdd-red-green-refactor
               code-review-checklist
               int-xx-marker-protocol
               decompose-int-items
  rust/        write-rust-current-edition
               rust-error-handling
               cargo-test-driven-development
               rust-async-tokio-idioms
  backend/     postgres-migrations-forward-only
               postgres-index-selection
               api-pagination-day-1
  frontend/    react-19-server-components
               tailwind-v4-idioms
               component-4-state-model
  mobile/      expo-managed-vs-bare
               rn-flashlist-perf
  gpu/         gpu-coalescing-and-occupancy
               roofline-model
  threejs/     threejs-perf-and-teardown
  security/    cargo-audit-workflow
               secret-scanning-gitleaks

skills_loader.rs walks skills/**/*.md, parses YAML frontmatter
(name, description, when_to_use, tags), upserts via
skills_catalog::upsert_builtin. Idempotent per boot — bumps version
+ appends skill_versions row ONLY when body changes. Deterministic
sha256-derived ids so builtins are stable across boots.

Dockerfile copies skills/ to /etc/clawmates/skills. Server boot
task spawns loader alongside team_template_loader.

Follow-ups (Slice 3.5c continuation, future PRs):
  - 20-30 more skills (duckdb, shadcn composition, a11y, WebGPU
    migration, metal frame capture, rocprof, deep gitea forge
    integration, semgrep rulepacks)
  - Bind skills to team template roles (add [role.skills] refs to
    templates/teams/*.toml + wire template_role_skills population
    in team_template_loader)

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-07-19 13:55:44 -07:00

83 lines
3.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: threejs-perf-and-teardown
description: three.js r185 essentials — 60fps budget, InstancedMesh over per-node Meshes, dispose everything on scene teardown.
when_to_use: You're coding or profiling a three.js/WebGL/WebGPU scene. threeJS team pins.
tags: [threejs, webgl, webgpu, performance, versioned]
---
# three.js perf + teardown (r185)
Anchored to **three.js r185** (2026-07). WebGPURenderer is stable enough for production on Chromium/Safari; WebGL2 renderer still the default fallback.
## Frame budget
Target **16.6 ms/frame** (60 fps) on the median target device. Break it down:
- JS + scene graph updates: ≤ 4 ms
- GPU submission: ≤ 4 ms
- GPU rendering (browser waits): remainder
If you can't hit 60 fps at 1440p on a mid-range GPU, DON'T ship as-is. Downgrade features (fewer shadow casters, lower shadow map res, LOD swaps) before shipping.
## Draw call budget
- Target **≤ 200 draw calls per frame**. Every distinct material × geometry combo = ≥ 1 draw call.
- **InstancedMesh** for any repeated geometry. 10,000 trees = 1 draw call, not 10,000.
- **BatchedMesh** (added in r168+, matured through r185) for heterogeneous geometry with shared material — batch different meshes with one call.
## Memory teardown
**The #1 cause of "why does the tab crash after 20 minutes"**. GPU resources are NOT garbage collected — you must dispose.
```ts
function teardown(scene: THREE.Scene) {
scene.traverse((obj) => {
if ((obj as THREE.Mesh).isMesh) {
const m = obj as THREE.Mesh;
m.geometry.dispose();
if (Array.isArray(m.material)) m.material.forEach((x) => x.dispose());
else m.material.dispose();
}
});
renderer.dispose(); // WebGL context + programs
renderer.forceContextLoss(); // guarantee GPU release, not deferred
}
```
Textures must ALSO be disposed — walk materials and dispose any `map`, `normalMap`, `roughnessMap`, etc.
## Render-loop discipline
- **Reuse math objects.** Never `new THREE.Vector3()` inside the render loop.
```ts
const _tmp = new THREE.Vector3(); // module-level
function tick() {
_tmp.copy(a).sub(b).normalize(); // no allocation
}
```
- Same for `Matrix4`, `Quaternion`, `Color`.
- If you MUST allocate, batch outside the loop.
## Shadows
- **One directional shadow-caster.** Bake everything else via lightmaps or ambient occlusion.
- Shadow map resolution: 1024 for medium range, 2048 max. 4096 is a mobile-crash-in-a-can.
- `light.shadow.autoUpdate = false; light.shadow.needsUpdate = true;` when the shadow is static — one render + freeze.
## Custom shaders
- Try `ShaderMaterial` when built-ins get close but not exact.
- `onBeforeCompile` hook for tweaking a built-in when it's 95% right — patch the shader source instead of rewriting.
- WebGPU: WGSL, not GLSL. Migrate shader-by-shader as you adopt WebGPURenderer.
## Profiling
- **Chrome DevTools Performance panel** — JS time.
- **SpectorJS** browser extension — per-frame draw call breakdown, texture inspector, shader debugger.
- **WebGL / WebGPU inspector** in Firefox — similar to Spector.
## Anti-patterns
- **`new THREE.Mesh(geometry, material)` inside `requestAnimationFrame`.** Creates 60 mesh objects per second; profile-visible before you notice.
- **Adding then removing lights on interaction.** Recompiles shaders (cache-miss). Toggle intensity to 0 instead.
- **`renderer.setPixelRatio(window.devicePixelRatio)` on a 3× retina display without a quality slider.** Renders 9× the pixels; kills fps. Cap at 2 by default.