Skip to content

Render hard-fails with drawElement canvas not initialized instead of falling back to screenshot capture (0.7.109 through 0.8.8) #3423

Description

@flexchar

Summary

On macOS, the experimental fast-capture path (drawElementImage) fails at frame 0 with drawElement canvas not initialized and the render aborts. The docs for --experimental-fast-capture say incompatible compositions and self-verification failures fall back to screenshot capture automatically, but this error is rethrown instead of triggering the fallback. Passing --experimental-fast-capture=false renders fine.

Reproduced on 0.7.109 and 0.8.4. I diffed dist/cli.js between 0.8.4 and 0.8.8: the capture functions involved (captureFramesBatchPipelined, captureFrameCore, captureFrameToBufferPipelined) are byte-identical, so 0.8.8 has the same behavior.

Environment

  • hyperframes 0.8.4 (also 0.7.109), installed globally via bun
  • macOS 26.6.1, Apple M4 Max
  • Node v26.7.0
  • Chrome: hyperframes-managed chrome-headless-shell mac_arm-152.0.7928.2
  • ffmpeg 9.0.1 (Homebrew)

Minimal reproduction

index.html:

<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<style>
  html, body { margin: 0; padding: 0; }
  #composition { width: 640px; height: 360px; background: #1b2a3a; position: relative; overflow: hidden; }
  h1 { color: #f5efe2; font-family: sans-serif; position: absolute; top: 150px; left: 0; right: 0; text-align: center; }
</style>
</head>
<body>
<div id="composition" class="clip" data-duration="2" data-width="640" data-height="360" data-fps="30">
  <h1>audio fix test</h1>
  <audio src="assets/tone.m4a" data-start="0" data-duration="2" data-media-start="0" data-track-index="10" data-volume="1"></audio>
</div>
</body>
</html>

assets/tone.m4a is any 2s audio file, e.g.:

ffmpeg -f lavfi -i "sine=frequency=440:duration=2" -c:a aac assets/tone.m4a

Run:

hyperframes render . --fps 30 --gpu --output rendered/test.mp4

Same result without --gpu.

Actual output

[INFO] [Render:trace] {"phase":"capture_streaming","status":"start","stagePhase":"capturing","workerCount":1,"totalFrames":60,"framesCompleted":0,"captureMode":"drawelement","captureOperation":"drawElement"}
[engine] fast capture: batch produce failed at frame 0 (drawElement canvas not initialized); re-capturing 4 frame(s) per-frame
[INFO] [Render:trace] {"phase":"capture_streaming","status":"error","message":"drawElement canvas not initialized","captureMode":"drawelement"}
  ██████░░░░░░░░░░░░░░░░░░░  25%  Failed: drawElement canvas not initialized
[INFO] [Render] Failure summary {"failedStage":"Starting frame capture","error":"drawElement canvas not initialized","browserDiagnostics":{"errors":0,"pageErrors":0,"consoleErrors":0}}

✗  Render failed

   drawElement canvas not initialized
   Try --docker for containerized rendering

Also noted right before capture: [WARN] [Render] Reduced auto worker count for high-cost capture workload ... "reasons":["calibration-failed"], which may be the same underlying failure showing up earlier.

Expected

Either the drawElement capture works, or the error triggers the documented automatic fallback to screenshot capture (as isNoCachedPaintRecordError already does).

Workaround

hyperframes render . --fps 30 --gpu --experimental-fast-capture=false --output rendered/test.mp4

Renders successfully (captureMode: beginframe), output has h264 video and aac audio.

Where it goes wrong (from reading dist/cli.js)

In captureFrameCore, the catch around captureDrawElementFrame(...) only handles isNoCachedPaintRecordError(err) by falling back to pageScreenshotCapture; every other error hits else { throw err; }. The batch path logs "re-capturing N frame(s) per-frame", but the per-frame retry calls captureDrawElementFrame again, so it fails the same way and the render dies. Treating "canvas not initialized" like the no-cached-paint-record case (fall back to screenshot) would fix the hard failure.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions