From aee81b76a6c15cd8a72831fb16f635d3f7410ef0 Mon Sep 17 00:00:00 2001 From: miga-heygen Date: Fri, 11 Sep 2026 17:58:07 +0000 Subject: [PATCH] docs(engine): ship the fast-capture-limitations.md every gate points at drawElementService.ts, frameCapture.ts, compileStage.ts, fallbackCaptureProfile.ts, validate-fast-video.ts, and the fast-video CI workflow all cite `docs/fast-capture-limitations.md` (several by specific `Lim N` number) as the reference for why a composition falls back to screenshot capture -- but the file never existed anywhere in the repo, so every one of those pointers was dangling and nothing was ever shipped in the published @hyperframes/engine package either. Add packages/engine/docs/fast-capture-limitations.md covering every fallback gate (SwiftShader, video capture, unsupported CSS effects, 3D contexts, supersampling, clip-cut boundaries, the at-risk-timeline predictor) plus the operational requirements and per-frame recoverable errors, and add "docs" to the package's `files` so it actually publishes. Add a regression test asserting the doc exists and still defines every limitation number cited by name elsewhere in the codebase. Co-Authored-By: Claude Sonnet 5 --- .../engine/docs/fast-capture-limitations.md | 227 ++++++++++++++++++ packages/engine/package.json | 3 +- .../fastCaptureLimitationsDoc.test.ts | 32 +++ 3 files changed, 261 insertions(+), 1 deletion(-) create mode 100644 packages/engine/docs/fast-capture-limitations.md create mode 100644 packages/engine/src/services/fastCaptureLimitationsDoc.test.ts diff --git a/packages/engine/docs/fast-capture-limitations.md b/packages/engine/docs/fast-capture-limitations.md new file mode 100644 index 0000000000..47a9c8e3e3 --- /dev/null +++ b/packages/engine/docs/fast-capture-limitations.md @@ -0,0 +1,227 @@ +# Fast-capture (`drawElementImage`) gates and limitations + +`canvas.drawElementImage(element, x, y)` reads DOM paint records directly into +a canvas, skipping the full compositor + `Page.captureScreenshot` GPU→CPU +readback IPC that the baseline capture path pays on every frame. On a +hardware GPU this is measurably faster (~46% in the original eval); on a +software rasterizer (SwiftShader — see Limitation 1) it is not. + +The technique reads paint records rather than compositing pixels, so it +cannot faithfully reproduce every CSS/compositor feature. Rather than ship +silently-damaged frames, the engine gates ahead of time on the conditions +below and falls back to the platform's baseline capture (screenshot or +BeginFrame, whichever the render already launched with) whenever one applies. +Every fallback is logged with a `[engine] fast capture: falling back to ...` +message naming the trigger; the low-cardinality trigger is also recorded on +the render session (`deGateReason` / `deFallbackTrigger`) for telemetry. + +This document is the canonical reference those log lines and source comments +point at. Limitation numbers below are load-bearing — several call sites +across `packages/engine`, `packages/producer`, and CI cite a specific `Lim N` +by number, so an existing number must never be reassigned to a different +limitation. + +## Limitation 1 — SwiftShader (software rasterizer) + +drawElementImage's entire advantage is skipping the GPU→CPU screenshot +readback. On a software rasterizer (Docker/CI with no GPU — SwiftShader) both +the drawElement and screenshot paths block on identical software +rasterization, so drawElement is parity-or-slower (measured: font-variant +baseline 7822ms vs. fast 7979ms). On a transparent destination it is also +strictly worse: SwiftShader drops compositor-promoted sub-layers on a +transparent canvas (Chromium bug 521434899). The speedup is only real on a +hardware GPU (macOS, ~1.6×), so SwiftShader always routes to the platform +baseline unconditionally — this is the one gate `HF_FORCE_DRAWELEMENT=1` +still overrides, since it exists purely for upstream-Chromium repro work. + +Source: `resolveDrawElementCaptureMode` in +`packages/engine/src/services/drawElementService.ts`. + +## Limitation 2 — Video composition capture + +Two gates that used to force every `