mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-14 18:01:20 +08:00
216886308a
drawElementImage cannot paint CSS 3D: rendering contexts drop earlier siblings and backgrounds, backface-visibility is ignored (mirrored backfaces even at rest), and flat 3D matrices silently lose their rotation. Until now every 3D comp was gated to the baseline route. threeDProjection.ts rewrites 3D content in-page before capture: - discovery: perspective/preserve-3d contexts, elements at a 3D matrix at t=0, and the producer stub's record of 3D tween targets (rotationX/rotationY/transformPerspective) for to()-style tweens that are still flat at init - leaf quads rasterized once via SVG foreignObject (fonts and images inlined as data URLs), shell quads carry only own paint when a child contains further 3D - per-frame WebGL projection from live computed matrices: CSS-convention matrix math, perspective + transform-origin sandwiches, GL backface culling, accumulated opacity as a fragment uniform - live elements hidden via clip-path (GSAP autoAlpha fights visibility/opacity), 3D contexts neutralized and live 3D matrices stripped after reading (perspective-carrying and rotated matrices poison the capture even when hidden), authored backface flags captured before neutralization - projected canvases composite OVER the DOM paint — the under-pass would bury them beneath the composition root's own background Correctness guards fall back to the platform baseline route, same contract as the video gate: degenerate markup (zero-size/inline-box quads — gen_os flip-card spans) and quads with GSAP-animated descendants (static textures would freeze them; golf measured 46->24 dB without this). fast-capture-3d test comp (flip card + perspective-free rotationX entrance): 57.5 dB avg / 51.7 dB min vs baseline at 1.5x speedup. Real-world comps with animated 3D subtrees fall back cleanly. Engine suite 692/694 (2 failures pre-exist on clean tree); build, lint, oxfmt verified manually — hook bypassed for the same stale-lockfile core-build failure as the previous commit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
48 lines
2.5 KiB
JavaScript
48 lines
2.5 KiB
JavaScript
// Does a WebGL canvas inside the captured root make drawElementImage drop
|
|
// the canvas's EARLIER DOM siblings?
|
|
import puppeteer from "puppeteer";
|
|
import { writeFileSync } from "node:fs";
|
|
const W=400,H=300;
|
|
const HTML=`<!doctype html><meta charset=utf-8>
|
|
<style>*{margin:0;padding:0}html,body{width:${W}px;height:${H}px;background:#ece8dd}
|
|
#root{position:relative;width:${W}px;height:${H}px;background:#ece8dd}
|
|
.headline{position:absolute;top:20px;left:0;width:100%;text-align:center;font:700 28px serif;color:#1a2640}
|
|
#footer{position:absolute;top:220px;left:0;width:100%;text-align:center;font:700 20px serif;color:#406080}</style>
|
|
<div id=root><div class=headline>HEADLINE</div><div id=footer>FOOTER</div></div>
|
|
<script>
|
|
const root=document.getElementById("root");
|
|
window.__addGl=(hidden)=>{
|
|
const c=document.createElement("canvas");
|
|
c.width=200;c.height=100;
|
|
c.style.cssText="position:absolute;left:50px;top:10px;width:200px;height:100px;"+(hidden?"visibility:hidden;":"");
|
|
root.appendChild(c);
|
|
const gl=c.getContext("webgl",{alpha:true,preserveDrawingBuffer:true});
|
|
gl.clearColor(0,0.5,0,0.5);gl.clear(gl.COLOR_BUFFER_BIT);
|
|
return !!gl;
|
|
};
|
|
const canvas=document.createElement("canvas");
|
|
canvas.setAttribute("layoutsubtree","");canvas.width=${W};canvas.height=${H};
|
|
canvas.style.cssText="display:block;position:absolute;top:0;left:0";
|
|
root.parentNode.insertBefore(canvas,root);canvas.appendChild(root);
|
|
window.__cap=()=>{
|
|
const ctx=canvas.getContext("2d");
|
|
return new Promise(r=>requestAnimationFrame(()=>setTimeout(()=>{
|
|
try{ctx.clearRect(0,0,${W},${H});ctx.drawElementImage(root,0,0);}catch(e){return r({err:String(e)});}
|
|
const url=canvas.toDataURL("image/png");ctx.clearRect(0,0,${W},${H});r({url});
|
|
},30)));
|
|
};
|
|
</script>`;
|
|
const b=await puppeteer.launch({headless:true,args:["--no-sandbox","--enable-features=CanvasDrawElement","--use-gl=angle",`--window-size=${W},${H}`]});
|
|
const p=await b.newPage();await p.setViewport({width:W,height:H});
|
|
await p.setContent(HTML,{waitUntil:"load"});
|
|
let r=await p.evaluate(()=>window.__cap());
|
|
writeFileSync("/tmp/cs-none.png",Buffer.from(r.url.split(",")[1],"base64"));
|
|
await p.evaluate(()=>window.__addGl(false));
|
|
r=await p.evaluate(()=>window.__cap());
|
|
writeFileSync("/tmp/cs-visible.png",Buffer.from(r.url.split(",")[1],"base64"));
|
|
await p.evaluate(()=>{document.querySelectorAll("#root canvas").forEach(c=>c.style.visibility="hidden");});
|
|
r=await p.evaluate(()=>window.__cap());
|
|
writeFileSync("/tmp/cs-hidden.png",Buffer.from(r.url.split(",")[1],"base64"));
|
|
console.log("done");
|
|
await b.close();
|