Files
clawmates/skills/threejs/webgl-frame-profiling.md
T
Omar SobhandClaude Opus 5 4358964c05 fix(skills): every team-template skill binding now resolves
55 of 85 role skill bindings pointed at skills that were never authored,
so 10 of 11 team templates bound a smaller context bundle than their role
prompts assumed. Three roles bound nothing at all (gpu.bench_engineer,
threejs.shader_author, threejs.perf_engineer) while their prompts described
procedures they had no way to read.

The loader comment at team_template_loader.rs:167 already diagnosed this —
snake_case slugs in TOML against kebab-case skill files — and it was
half-fixed: the kebab names were corrected, the snake_case ones left.

It was invisible because both existing tests assert authored ⊆ referenced
(30/30, green) and the second explicitly declines to check the other
direction. So the failing half was the half nobody asserted.

Resolved every name by one of three explicit choices:

  - 23 skills authored where the role genuinely needed the procedure
    (gpu, threejs, research, analysis, frontend, mobile, backend, platform)
  - renames onto authored skills where one existed in substance, including
    the four-near-duplicate cases that collapse onto one real skill
  - 22 aspirational references deleted — a binding an agent cannot read is
    a promise, not a capability

Two tests now hold it. The unit test checks referenced ⊆ authored against
the files. The new integration test runs both loaders in boot order and
asserts the bindings survive the trip through the database, which is a
different question: resolution goes through skills_catalog rows, so a skill
file that exists but fails to ingest still leaves the role empty.

Negative controls: the unit test failed naming all 55; the integration test
fails naming the exact role when one name is reverted.

threejs.shader_author and .perf_engineer gained a second and third skill
after the collapse — pin_in_context pins idx < 2, so a role left with one
skill silently pins less than the policy intends.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-19 07:42:48 -07:00

53 lines
2.4 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: webgl-frame-profiling
description: Finding what actually costs a frame — draw calls, overdraw, shader cost — using Chrome DevTools and Spector.js.
when_to_use: You are the performance engineer on a three.js/WebGL team and a scene is dropping frames.
tags: [threejs, profiling]
---
# Find the frame's real cost before changing the scene
A dropped frame has a small number of possible causes and they are
distinguishable in minutes. Guessing usually leads to optimising geometry when
the cost was overdraw, or the reverse.
## The order
1. **CPU or GPU?** Chrome DevTools Performance panel: record 5 seconds of the
stall. Long scripting bars mean the CPU is the problem (scene-graph
traversal, matrix updates, garbage). A near-idle main thread with dropped
frames means the GPU is.
2. **How many draw calls?** `renderer.info.render.calls`. Anything in the
thousands is the answer on its own — instance, merge, or batch by material.
`renderer.info` is free and should be on screen during development.
3. **Overdraw?** Spector.js captures a frame and lists every GL call in order.
Transparent objects drawn back-to-front over the whole viewport are the
usual culprit; the same scene with `transparent: false` running fast
confirms it in one test.
4. **Shader cost?** Only now. Halve the canvas resolution: if the frame time
halves, you are fragment-bound and the shader (or overdraw) is the cost. If
it does not move, you are not.
## The resolution test is the cheapest diagnostic
`renderer.setPixelRatio(1)` versus `2` changes fragment work 4× and geometry
work not at all. That one toggle separates vertex/CPU cost from fragment cost
faster than any profiler, and it needs no tooling.
## What Spector.js is for
It is a *capture*, not a sampler: one frame, every call, with state at each
step. Use it to answer "what is this frame actually doing" — unexpected state
changes, redundant binds, a texture uploaded per frame — not to measure time.
For time, use the DevTools timeline.
## Measure the steady state
The first seconds include shader compilation, texture upload and JIT warmup.
three.js compiles a material's program on first use, so a stutter the first time
an object becomes visible is a compile, not a leak. Pre-warm with
`renderer.compile(scene, camera)` and profile after.
Record the device and the pixel ratio with any number you report. A frame time
without a resolution is not a measurement.