核心概念

确定性渲染

相同的输入,完全相同的输出。每次如此。

Hyperframes 围绕一个核心保证构建:相同的合成总是产生相同的视频。这正是自动化流程、CI 测试和 AI 驱动工作流可靠的基础。

工作原理

渲染流水线是逐帧、定位驱动的。不涉及实时播放——每一帧都独立地被定位和捕获。

帧时钟

引擎使用整数运算计算每帧的时间:time = floor(frame) / fps。没有挂钟依赖——渲染完全与实时解耦。

定位

帧适配器接收 seekFrame(frame) 调用,并确定性地将所有动画、DOM 状态和 Canvas 内容定位到精确的帧。适配器的 renderSeek 暂停所有 GSAP 时间线并将它们定位到计算出的时间。

捕获

Chrome 的 HeadlessExperimental.beginFrame API 捕获当前帧的像素缓冲区。这是一个原子操作——没有部分绘制或竞态条件。

编码

FFmpeg 将捕获的帧编码为最终的 MP4 视频。来自 <audio><video> 元素的音轨在此阶段被混合。

graph LR
A["帧时钟<br/>t = frame / fps"] --> B["定位<br/>adapter.seekFrame(frame)"]
B --> C["捕获<br/>beginFrame API"]
C --> D["编码<br/>FFmpeg"]
D --> E["MP4"]
style A fill:#00C4FF,color:#fff
style B fill:#00C4FF,color:#fff
style C fill:#00C4FF,color:#fff
style D fill:#00C4FF,color:#fff
style E fill:#00A8E1,color:#fff

什么使它具有确定性

  • 无挂钟依赖 -- 渲染不使用 Date.now()requestAnimationFrame 或系统定时器
  • 无未设种子的随机性 -- 没有种子的 Math.random() 会破坏确定性
  • 无渲染时网络请求 -- 所有资源必须在渲染开始前加载完毕
  • 固定输出参数 -- fpswidthheight 在第一帧之前就已锁定
  • 有限时长 -- 每个合成都有一个已知的、有限的长度

同样的规则适用于每个帧适配器。如果你正在构建自定义适配器,必须遵循确定性契约

Docker 模式

为了获得最大可重现性,在 Docker 中渲染:

npx hyperframes render --docker --output output.mp4

Docker 模式使用精确的 Chrome 版本和字体集,确保:

  • 所有平台上使用相同的 Chromium 渲染引擎
  • 相同的系统字体(无平台特定的字体替换)
  • 相同的 FFmpeg 编码器版本

所有渲染选项请参阅渲染指南

预览与渲染的一致性

浏览器预览和渲染的 MP4 应该保持一致。Hyperframes 通过以下方式实现这一点:

  • 统一运行时 -- 相同的 hyperframe.runtime 同时驱动预览和渲染
  • 生产器规范行为 -- 生产器的定位语义是权威来源
  • 就绪门控 -- __playerReady__renderReady 确保合成在捕获任何帧之前完全加载

这里的一致性指的是视觉保真度——每一帧看起来相同。它意味着性能一致性。预览在浏览器中实时播放,因此帧率受硬件限制。渲染是定位驱动的、逐帧进行的,因此无论单帧成本如何都不会丢帧。一个合成在预览中可能卡顿,但渲染时完美呈现。详情请参阅性能

ℹ️ Note

本地渲染(不使用 Docker)可能由于平台特定的字体渲染和 Chrome 版本而显示轻微差异。当需要精确可重现性时,请使用 Docker 模式。

面向适配器作者

如果你正在构建帧适配器,你的适配器必须遵循确定性契约:

  • seekFrame(frame) 必须是幂等的——相同帧,相同结果
  • 无依赖调用顺序的副作用(必须支持随机访问)
  • 无在帧"提交"之后才完成的异步操作
  • 干净的生命周期:init -> seekFrame(N 次)-> destroy

下一步