This is internal, proof-level guidance from the Milestone 3 dense shape benchmark. It is not a public performance claim, not a final renderer API, and not a universal target. The numbers below are local evidence from one browser and machine run, useful for choosing the next implementation path.
Build the first real BoundingBox or rectangle feature on the Pixi Graphics
path. Use dirty redraws keyed by decoded media annotation frame changes: when
the selected annotation frame for the current decoded media sample changes,
redraw the rectangle layer; when it does not change, keep the existing graphics.
Do not route the hot path through React state or DOM overlays. Keep media and annotations in one Pixi-owned scene, with Mediabunny decoded sample timestamps as the source of truth for synchronized annotation state.
The current recommendation is based on stable per-sample present/redraw costs
and earlier foreground-like frame runs. Automated Codex in-app browser runs may
throttle Pixi ticker/frame statistics to around 1000ms; when the demo labels
frame timing as throttled or insufficient, ignore frame avg/p95/p99 for
strategy comparison and use present/redraw costs instead.
Recent automated Codex browser sweeps still produced stable per-sample costs:
graphicsredraw at6000boxes reported present/redraw near5.9ms/5.6ms.graphicsredraw at12000boxes reported present/redraw near10.0ms/9.8ms.graphicsredraw at24000boxes reported present/redraw near20.6ms/20.4ms.particle-edgesredraw at12000boxes reported present/redraw near19.0ms/18.8ms.particle-edgesredraw at24000boxes reported present/redraw near40.6ms/40.4ms.- Static
graphicsat24000boxes reported present near0.1msafter a one-time redraw around22.4ms. static-cachedgraphicsat24000boxes reported present near0.2msafter one-time setup around19.5ms.
The earlier foreground-like built-in sweep showed dynamic Graphics redraws
staying reasonable through about 6000 boxes on this run. Around 12000
boxes, the dynamic path started showing risk in the tail frame timings. Around
24000 boxes and above, dynamic redraws were clearly too expensive for this
local setup.
The stress run reinforced that split:
graphicsredraw at12000boxes averaged about9.6ms, with p95 around16.7msand p99 around25.0ms.graphicsredraw at24000boxes averaged about13.5ms, with p95 around41.7msand p99 around49.7ms.graphicsredraw at48000boxes averaged about31.9ms, with p95 around100.2msand p99 around118.1ms.- Static
graphicsat48000boxes stayed near the media-only frame profile after a one-time draw cost of about39.9ms. static-cachedgraphicsat48000boxes also stayed near the media-only frame profile after a one-time draw and cache setup cost of about41.9ms.
Media-only and static dense layers stayed near the same frame profile in these runs, which points to per-sample dynamic redraw pressure as the main concern for dense changing rectangles.
Keep a prepared render window between the hot detection window and Pixi scene objects. Cold detections should stay semantic, while the prepared window builds renderer-friendly artifacts for a small set of nearby frames. The first concrete artifact is a composited mask canvas per detection frame, which Pixi can upload once and present as a single sprite. Future artifacts may include grouped box draw instructions, text layout/cache entries, track paths, heatmaps, or custom geometry.
Mask artifact preparation should prefer Web Workers when available. The main
thread may still resolve style objects into serializable draw instructions, but
expensive RLE decode and RGBA composition belong behind the prepared-window
preparer boundary. Keep a main-thread fallback and a custom workerFactory
escape hatch so strict CSP or unusual bundlers can still use the library.
Shared browser worker plumbing belongs in packages/web/src/workers/;
feature-specific preparers should translate their own request and artifact
shapes rather than each rebuilding request IDs, pending promises, worker
errors, and orphan cleanup. Core may define renderer-neutral preparation
contracts, but DOM Worker, Blob, and worker bundle details stay in the web
package.
Keep prepared artifact identity separate from presentation-only controls. For
masks, the style artifactKey should include values that change prepared
pixels, such as color grouping or confidence filtering, while renderer-level
opacity should remain outside the prepared artifact. This lets opacity
sliders update the Pixi sprite immediately without recompositing every cached
mask frame.
Do not assume every prepared artifact should be an image. Masks and heatmaps
fit texture artifacts well; boxes often fit grouped Graphics instructions
better until measured pressure says otherwise. The common rule is to move
expensive decode, style resolution, sorting, and grouping out of the per-sample
presentation path.
Static dense layers can use Graphics. Use static-cached when the layer is
complex and rarely changes, but avoid repeated recaching. Treat recache as a
costly redraw plus texture update, not as a cheap per-frame optimization.
The naive particle-edges benchmark is not the default box renderer. It is a
useful proof of a batched draw direction, but it currently loses for dynamic
boxes because each box becomes four elements and the benchmark repopulates the
particles on redraw. In the observed stress run, particle-edges reached
192000 edge elements for 48000 boxes and paid a much larger per-redraw cost
than Graphics.
If future real workloads need dynamic counts beyond the local Graphics
comfort zone, the next candidate should be pooled and reused particles or a
custom mesh or instanced renderer. Do not respond to dense dynamic pressure by
moving boxes into React or DOM overlays.
- Mediabunny owns media reading, decode, and decoded sample timestamps.
- Pixi owns one composed scene for media, synchronized overlays, and future
rectangle annotations. Benchmark layers live in
/benchmark/initial, not the package renderer. - The renderer presents one canvas and does not create visible DOM media or DOM annotation layers.
- Annotation state follows decoded media sample timing, not an independent app clock.
48000boxes remains a manual stress case, not part of the normal benchmark sweep.