Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 25 additions & 6 deletions bench/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ contributors to verify perf claims and catch render regressions.
pnpm bench:serve # static server on :4400 with an index page
pnpm bench:perf # build bundles + run all 4 renderers × 5 scenarios
pnpm bench:animated-human # build bundles + run the animated human run bench
pnpm bench:nonvoxel-drag-trace # build bundles + trace a Playwright drag orbit on teapot
pnpm bench:lossy # compare lossless / previous lossy / auto lossy counts
pnpm bench:visual # screenshot diff against bench/baselines/*.png
pnpm bench:visual --record # capture new baselines (after intentional renderer changes)
pnpm bench:build # just rebuild the bench bundles (rarely needed alone)
node bench/nonvoxel-rotation-bench.mjs # non-voxel vanilla rotation probe
node bench/nonvoxel-drag-trace.mjs --label teapot-drag # pointer-drag trace, no auto-rotate
node bench/nonvoxel-frame-buckets.mjs --no-trace # non-voxel rAF cadence buckets
node bench/nonvoxel-visual-compare.mjs # non-voxel variant visual parity
```
Expand All @@ -38,6 +40,7 @@ node bench/lossy-optimizer-bench.mjs --json bench/results/lossy-optimizer.json
node bench/lossy-optimizer-bench.mjs --models ducky,shark,bicycle
node bench/perf-visual.mjs --mesh chicken --tolerance 0.005
node bench/nonvoxel-rotation-bench.mjs --models teapot,bicycle --variants baseline,order-tile4 --run-order round-robin
node bench/nonvoxel-drag-trace.mjs --mesh teapot --degrees 360 --drag-ms 1500 --label teapot-drag --frame-details --no-print-json
node bench/nonvoxel-frame-buckets.mjs --mesh glb:Elephant.glb --variant baseline --no-trace
node bench/nonvoxel-visual-compare.mjs --models bicycle,elephant,policecar --variants scene-split-target,scene-transform-perspective
```
Expand Down Expand Up @@ -141,6 +144,19 @@ tests above what the gallery's OBJs cover.
Use `domOrder` for pure post-render DOM-order probes; `polygonOrder` changes
the polygon array before render planning and is only for diagnostics.

`nonvoxel-drag-trace.mjs` is the focused user-input lane for the same page.
It loads a non-voxel mesh (`teapot` by default), leaves OrbitControls
auto-rotate off, performs real Playwright mouse drags until the requested
camera yaw delta is reached, and writes `bench/results/<label>.trace.json`
plus `bench/results/<label>.json`. A 360 degree run uses clutched drags inside
the viewport because OrbitControls maps 4 pointer pixels to 1 degree of yaw.
Pass `--variant <id>` to reuse the non-voxel variant params from the rotation
bench, or `--trace-out <path>` / `--json <path>` for explicit outputs.
`--frame-details` aligns page-side frame work with Chrome trace events, adding
per-frame `rotationFrames` / `slowestFrames` breakdowns; use
`--frame-details-limit <n>` to keep every rotation frame and `--no-print-json`
when the full result is too large for terminal output.

`animated-human.html` is the focused animated-model page. It loads
`/gallery/glb/poly-pizza/animated-human.glb` by default, chooses the run-like
clip when available, and drives `createPolyAnimationMixer.update(dt)` into
Expand All @@ -155,12 +171,11 @@ Playwright runner accepts `--mode baked,dynamic`, `--clip <name|index|run>`,
`--stable-triangle-color-max-step <channel-delta>`,
`--animation-driver js|progressive-style-cache|js-style-cache|typed-om-style-cache|css-keyframes`,
`--compare-stable-triangle-debug`, `--require-solid-triangles`, `--trace`, and
the same GPU lane flags as the other browser benches. The color freeze option
keeps exact baked colors but staggers leaf color writes across frames; adaptive
color policy spends a capped write budget on leaves with the largest accumulated
color error first. The max-step option caps the per-write RGB channel delta so
cadence updates drift toward the next baked color instead of jumping directly to
it. `css-keyframes` is a bench-only prototype that samples the clip into
the same GPU lane flags as the other browser benches. The default baked color
path uses 8-channel quantized colors, staggers leaf color writes over a
12-frame cadence, and caps the per-write RGB channel delta so updates drift
toward the next baked color instead of jumping directly to it. `css-keyframes`
is a bench-only prototype that samples the clip into
per-leaf CSS animations, removing per-frame JS playback from the measurement
window. The solid-triangle guard fails the run if the page leaves the baked
`<u>` path. Use `--stable-triangle-color-freeze-frames 0` to keep the initial
Expand Down Expand Up @@ -199,6 +214,10 @@ bench/
GPU-default Playwright runner for the animated
human run sequence. Reports FPS, mixer/update cost,
setPolygons cost, render stats, and optional trace.
nonvoxel-drag-trace.mjs
Vanilla-only non-voxel pointer-input trace bench.
Uses Playwright mouse drags through OrbitControls
instead of scene auto-rotation.
lossy-optimizer-bench.mjs
Polygon-count strategy bench for lossless,
previous pair-only lossy, forced grouped lossy,
Expand Down
8 changes: 6 additions & 2 deletions bench/animated-human-bench.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ const ANIMATION_FRAME_CACHE_FRAMES = optNum(
optNum("animationFrameCacheFrames", 60),
);
const KEYFRAME_SAMPLES = optNum("keyframe-samples", optNum("keyframeSamples", 24));
const DEFAULT_STABLE_TRIANGLE_COLOR_STEPS = 0;
const DEFAULT_STABLE_TRIANGLE_COLOR_STEPS = 8;
const DEFAULT_STABLE_TRIANGLE_COLOR_POLICY = "cadence";
const DEFAULT_STABLE_TRIANGLE_COLOR_FREEZE_FRAMES = 12;
const DEFAULT_STABLE_TRIANGLE_COLOR_BUDGET = 0.16;
Expand All @@ -96,7 +96,7 @@ const HAS_STABLE_TRIANGLE_COLOR_STEPS =
hasOpt("stable-triangle-color-steps") || hasOpt("stableTriangleColorSteps");
const STABLE_TRIANGLE_COLOR_STEPS = optNum(
"stable-triangle-color-steps",
optNum("stableTriangleColorSteps", 0),
optNum("stableTriangleColorSteps", DEFAULT_STABLE_TRIANGLE_COLOR_STEPS),
);
const HAS_STABLE_TRIANGLE_COLOR_POLICY =
hasOpt("stable-triangle-color-policy") || hasOpt("stableTriangleColorPolicy");
Expand Down Expand Up @@ -610,6 +610,9 @@ async function runScenario(port, scenario) {
animation_update: summarizeDurations(pageResult.animationSamples, "updateMs"),
set_polygons: summarizeDurations(pageResult.animationSamples, "setPolygonsMs"),
sample_and_mixer: summarizeDurations(pageResult.animationSamples, "nonSetPolygonsMs"),
triangleFrameApplied: pageResult.animationSamples.filter((sample) =>
sample?.triangleFrameApplied === true
).length,
animation_sample_count: pageResult.animationSamples.length,
polyCount: pageResult.polyCount,
renderStats: pageResult.renderStats,
Expand Down Expand Up @@ -697,6 +700,7 @@ try {
`p95=${result.fps_p95.toFixed(1).padStart(5)}fps ` +
`update p50=${result.animation_update.p50_ms.toFixed(2)}ms ` +
`setPolys p50=${result.set_polygons.p50_ms.toFixed(2)}ms ` +
`typed=${result.triangleFrameApplied}/${result.animation_sample_count} ` +
`clip=${result.animation?.clip?.name ?? "?"}${tagNote}${traceNote}${profileNote}\n`,
);
}
Expand Down
22 changes: 21 additions & 1 deletion bench/animated-human.html
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
: "";
const stableTriangleColorSteps = params.has("stableTriangleColorSteps")
? numberParam("stableTriangleColorSteps", 0)
: 0;
: 8;
const stableTriangleColorPolicyRaw = params.get("stableTriangleColorPolicy");
const stableTriangleColorPolicy =
stableTriangleColorPolicyRaw === "adaptive" ? "adaptive" : "cadence";
Expand Down Expand Up @@ -400,6 +400,7 @@

let lastSetPolygonsMs = 0;
let lastAnimatedPolygons = initialPolygons.length;
let lastTriangleFrameApplied = false;
const keyframeInfo = animationDriver === "css-keyframes"
? await installCssKeyframeAnimation(mesh, renderParsed.animation, clip)
: null;
Expand Down Expand Up @@ -583,13 +584,31 @@
return;
}

const triangleFrameTargetSymbol = Symbol.for("polycss.animation.triangleFrameTarget");
const meshTriangleFrameTarget = mesh[triangleFrameTargetSymbol];
const animationTarget = {
setPolygons(polygons) {
lastTriangleFrameApplied = false;
const start = performance.now();
mesh.setPolygons(polygons, animationSetPolygonsOptions());
lastSetPolygonsMs = performance.now() - start;
lastAnimatedPolygons = polygons.length;
},
[triangleFrameTargetSymbol](frame) {
if (typeof meshTriangleFrameTarget !== "function") return false;
const start = performance.now();
const applied = meshTriangleFrameTarget.call(
mesh,
frame,
animationSetPolygonsOptions(),
);
if (applied) {
lastTriangleFrameApplied = true;
lastSetPolygonsMs = performance.now() - start;
lastAnimatedPolygons = frame.polygonCount;
}
return applied;
},
};
const mixer = createPolyAnimationMixer(animationTarget, renderParsed.animation);
const action = mixer.clipAction(clip.name);
Expand Down Expand Up @@ -627,6 +646,7 @@
setPolygonsMs: lastSetPolygonsMs,
nonSetPolygonsMs: Math.max(0, updateMs - lastSetPolygonsMs),
polygons: lastAnimatedPolygons,
triangleFrameApplied: lastTriangleFrameApplied,
});
if (window.__perf__.animationSamples.length > 1800) {
window.__perf__.animationSamples.splice(
Expand Down
Loading
Loading