Skip to content

docs(three): add preserveDrawingBuffer and GSAP onUpdate pattern#583

Closed
gumibera wants to merge 1 commit intoheygen-com:mainfrom
gumibera:docs/fix-three-webgl-rendering
Closed

docs(three): add preserveDrawingBuffer and GSAP onUpdate pattern#583
gumibera wants to merge 1 commit intoheygen-com:mainfrom
gumibera:docs/fix-three-webgl-rendering

Conversation

@gumibera
Copy link
Copy Markdown

@gumibera gumibera commented May 1, 2026

Summary

  • Adds preserveDrawingBuffer: true to the WebGLRenderer constructor in the /three skill basic pattern — without it, Puppeteer screenshots capture blank WebGL canvases
  • Documents the GSAP onUpdate callback as a reliable alternative to hf-seek events for compositions that use GSAP timelines
  • Adds preserveDrawingBuffer to the Contract and Avoid sections

Context

Discovered by 0xHoneyJar while building a Three.js + GLB composition using the Loa framework. The existing basic pattern in skills/three/SKILL.md silently produces blank canvases during npx hyperframes render because:

  1. WebGLRenderer without preserveDrawingBuffer: true clears its buffer after compositing — Puppeteer screenshots capture nothing
  2. hf-seek events from the Three.js adapter may not reach user listeners reliably during Puppeteer frame capture (filed separately as an issue)

The preserveDrawingBuffer pattern is already used internally by @hyperframes/shader-transitions (packages/shader-transitions/src/webgl.ts). The GSAP onUpdate pattern works because HyperFrames seeks GSAP timelines directly via timeline.totalTime(), which fires the callback on every frame.

Test plan

  • Verify skills/three/SKILL.md passes skill lint
  • Create a minimal Three.js composition using the updated pattern
  • Render with npx hyperframes render and confirm WebGL canvas is visible in output

🤖 Generated with Claude Code

…kill

The basic pattern in SKILL.md omits preserveDrawingBuffer: true on the
WebGLRenderer constructor. Since HyperFrames captures frames via Puppeteer
screenshots, WebGL canvases clear their drawing buffer after compositing
and screenshots capture blank output. This is the same pattern already
used internally by @hyperframes/shader-transitions.

Additionally, documents the GSAP onUpdate callback as a reliable
alternative to hf-seek events for compositions that use GSAP timelines.

Discovered by 0xHoneyJar while building a Three.js + GLB composition
with the Loa framework.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@miguel-heygen
Copy link
Copy Markdown
Collaborator

Thanks for writing this up. I tried to reproduce the two claimed render failures on current main (dde26cf6) using a minimal Three.js composition driven only by the hf-seek listener, with no requestAnimationFrame / animation loop.

What I tested:

  • built the local CLI/runtime from current main
  • linted the repro composition
  • rendered a 2s / 24fps PNG sequence through the producer with format: "png-sequence" and workers: 1
  • rendered the same fixture through the normal CLI MP4 path: hyperframes render ... --fps 24 --quality draft --workers 1
  • ran the fixture twice: one WebGLRenderer with alpha: true, preserveDrawingBuffer: true, and one with alpha: true only

What I observed:

  • hf-seek did fire during producer/CLI render. Sampled center pixels changed across the render: eb3835 -> 35eeec -> ed385a.
  • I also could not reproduce the blank-canvas behavior without preserveDrawingBuffer; the preserve and no-preserve renders produced identical sampled frames in this minimal case.
  • On the PR head, bun run lint:skills passes, but bunx oxfmt --check skills/three/SKILL.md currently fails.

So I do not think this PR proves or fixes the root issue yet. Could you add a small reproducible failing fixture, ideally a checked-in or gist-sized index.html plus the exact hyperframes render command, Chrome/OS mode, and expected-vs-actual frames? If the failure only happens with a GLB/Loa setup or a specific Chrome/Node combo, that detail matters. With that fixture we can tell whether the right fix belongs in the Three skill docs or in the runtime adapter/capture path.

@gumibera
Copy link
Copy Markdown
Author

gumibera commented May 1, 2026

Hey Miguel, thanks for the thorough repro attempt — you were right.

We went back and isolated the variables properly. Three test cases, identical compositions, only one variable changed per case:

Case Render method preserveDrawingBuffer Result
1 hf-seek only true Static (no animation)
2 GSAP onUpdate false Animated
3 GSAP onUpdate true Animated

Cases 2 and 3 are byte-for-byte identical across all frames. preserveDrawingBuffer has no effect on HyperFrames' capture path.

We originally debugged both issues simultaneously — when we went from "blank canvas" to "static model," we'd added preserveDrawingBuffer and an initial render call at the same time and attributed the fix to preserveDrawingBuffer. Confounded variable fr, completely my bad.

The real and only issue is hf-seek not firing during render, which is already tracked in #584. Closing this PR since the GSAP onUpdate workaround doesn't belong in the skill docs if #584 gets fixed at the adapter level.

@gumibera gumibera closed this May 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants