Skip to content

fix(engine,shader): handle matrix3d transforms and hide non-first scenes#374

Merged
vanceingalls merged 2 commits intomainfrom
vance/transform-clipping
Apr 23, 2026
Merged

fix(engine,shader): handle matrix3d transforms and hide non-first scenes#374
vanceingalls merged 2 commits intomainfrom
vance/transform-clipping

Conversation

@vanceingalls
Copy link
Copy Markdown
Collaborator

@vanceingalls vanceingalls commented Apr 21, 2026

Summary

Two correctness fixes in the HDR transform & clipping pipeline: parseTransformMatrix now handles matrix3d(...) (GSAP's default force3D: true), and shader-transitions sets every non-first scene to opacity: 0 at t=0 so the engine doesn't over-composite at the start.

Why

Chunk 4 of plans/hdr-followups.md. Transform extraction and border-radius computation existed but were dead — an HDR video with rotation: 45 rendered un-rotated, and 3-scene compositions ghosted at t=0 because every scene defaulted to CSS opacity: 1 and contributed to the first frame.

What changed

Matrix3d support in parseTransformMatrix. DOMMatrix.toString() emits matrix3d whenever any ancestor in the chain has used a 3D transform — most importantly GSAP's default force3D: true, which converts translate(...) into translate3d(..., 0). Without this, every GSAP-driven transform was silently dropped during HDR compositing because videoFrameInjector.getViewportMatrix() would return matrix3d(...) and the blit path would parse it as null and fall back to identity. The 16-value column-major form is converted to its 2D affine projection (indices 0, 1, 4, 5, 12, 13 → m11, m12, m21, m22, m41, m42); Z, perspective, and out-of-plane rotation components are dropped.

Initial-state opacity in initEngineMode. The browser preview branch uses a GL canvas overlay during transitions, so scene opacity at t=0 doesn't matter visually. The engine branch reads scene opacity directly via queryElementStacking() to decide which layers to composite. Without an explicit initial-state tween, every scene defaulted to CSS opacity: 1 and contributed to the very first frame, causing ghosting/overlap until the first transition fired. tl.set() at position 0 anchors the initial state in the timeline graph so reverse seeks from inside a later transition restore it correctly.

These two fixes together make el.transform and el.borderRadius (already wired in Chunk 7A's compositeHdrFrame) actually flow through the GSAP-animated case, and keep the engine's per-frame compositing aligned with what the user sees in browser preview.

Test plan

  • 6 new alphaBlit.test.ts cases (identity matrix3d, translate3d, scale + translate3d, rotateZ, malformed arg count, non-finite values).
  • Existing hdr-regression Window H already CSS-sets #scene-b { opacity: 0 } as a fallback; the new tl.set is redundant for that case but harmless and removes the need for compositions to remember the CSS workaround.
  • Manual: rotated HDR video (rotation: 45) appears rotated; border-radius: 50% clips to circle; 3-scene composition has no overlap at t=0.

Stack

Chunk 4 of plans/hdr-followups.md. Window F of the regression suite documents the bug; the next PR in the stack tightens the maxFrameFailures budget to 0.

Copy link
Copy Markdown
Collaborator Author

vanceingalls commented Apr 21, 2026

This stack of pull requests is managed by Graphite. Learn more about stacking.

@vanceingalls vanceingalls force-pushed the vance/transform-clipping branch 2 times, most recently from f328731 to 79b029f Compare April 21, 2026 20:54
@vanceingalls vanceingalls force-pushed the vance/refactor-helpers branch from 4f4682b to 78a6af9 Compare April 21, 2026 20:54
@vanceingalls vanceingalls marked this pull request as ready for review April 21, 2026 20:57
@vanceingalls vanceingalls force-pushed the vance/transform-clipping branch from 79b029f to e78f981 Compare April 21, 2026 22:37
@vanceingalls vanceingalls force-pushed the vance/refactor-helpers branch from 3838031 to 95df8c6 Compare April 22, 2026 01:16
@vanceingalls vanceingalls force-pushed the vance/transform-clipping branch 2 times, most recently from 45009c4 to cb840e7 Compare April 22, 2026 02:03
Copy link
Copy Markdown
Collaborator

@jrusso1020 jrusso1020 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two real fixes, both structural:

matrix3d in parseTransformMatrix — previously returned null for any 3D matrix, which meant every GSAP composition with force3D: true (GSAP's default for transform tweens) fell off the deterministic engine path and rendered with identity transforms on the HDR compositor. The spec-correct approach is: parse the 16-element column-major matrix, extract the 2D affine components (a=m[0], b=m[1], c=m[4], d=m[5], tx=m[12], ty=m[13]), drop Z-translation, and validate all six extracted components are finite. Tests pin the three common GSAP emission shapes (identity, translate3d, scale + translate3d, rotateZ) plus malformed rejection (wrong arg count, NaN values). Exactly the right coverage.

Worth documenting in code (or the plans doc) that this is a 2D projection of the 3D transform — any composition that actually uses Z-depth (perspective, 3D rotation around X/Y axes) will silently lose those components in the engine path. For force3D: true with a flat Z this is invisible; for a genuine 3D scene the engine just doesn't support it, which was already the case before this PR but is now hidden behind a "parses successfully" result instead of a null. If a Z-significant transform ever shows up in a regression test, the engine will render it flat without flagging that it threw away the Z info. Non-blocking, but worth a log warning when m[8..11] are non-identity.

Non-first scenes start at opacity: 0 in hyper-shader.ts — the fix for the Window F corruption in #365. Without this, non-first scenes were visible during scene-A capture and bled into the HDR compositor's first frame. Cleaner than the alternative (hiding scenes via DOM manipulation) because it stays inside the shader-transitions package's opacity contract.

CI failures here are the stack-wide Format and Render on windows-latest — not specific to this change. The Windows failure across the whole upper stack is worth running down separately (could be LFS-check issue after #376, or could be unrelated).

Approved.

Rames Jusso

@vanceingalls vanceingalls force-pushed the vance/transform-clipping branch from 7f0411c to d233789 Compare April 23, 2026 00:01
@vanceingalls vanceingalls force-pushed the vance/refactor-helpers branch from 44ff16f to aa8b9be Compare April 23, 2026 00:05
@vanceingalls vanceingalls force-pushed the vance/transform-clipping branch from d233789 to 404ec3f Compare April 23, 2026 00:06
@vanceingalls vanceingalls force-pushed the vance/refactor-helpers branch from aa8b9be to c623430 Compare April 23, 2026 00:45
@vanceingalls vanceingalls force-pushed the vance/transform-clipping branch from 404ec3f to cf5c309 Compare April 23, 2026 00:45
@vanceingalls vanceingalls force-pushed the vance/refactor-helpers branch from c623430 to ba25661 Compare April 23, 2026 01:58
@vanceingalls vanceingalls force-pushed the vance/transform-clipping branch from cf5c309 to 13d8aa7 Compare April 23, 2026 01:58
@vanceingalls vanceingalls force-pushed the vance/refactor-helpers branch from ba25661 to ccc725f Compare April 23, 2026 02:53
@vanceingalls vanceingalls force-pushed the vance/transform-clipping branch from 13d8aa7 to ff96886 Compare April 23, 2026 02:54
@vanceingalls vanceingalls force-pushed the vance/refactor-helpers branch from ccc725f to 5589417 Compare April 23, 2026 03:16
@vanceingalls vanceingalls force-pushed the vance/transform-clipping branch from ff96886 to b841366 Compare April 23, 2026 03:17
@vanceingalls vanceingalls force-pushed the vance/refactor-helpers branch from 5589417 to 6d73142 Compare April 23, 2026 03:38
@vanceingalls vanceingalls force-pushed the vance/transform-clipping branch from b841366 to a56ed28 Compare April 23, 2026 03:39
@vanceingalls vanceingalls force-pushed the vance/refactor-helpers branch from 6d73142 to 52c6034 Compare April 23, 2026 04:46
@vanceingalls vanceingalls force-pushed the vance/transform-clipping branch from a56ed28 to 7f5f8ae Compare April 23, 2026 04:47
@vanceingalls vanceingalls force-pushed the vance/refactor-helpers branch 2 times, most recently from 263d844 to 13b88d2 Compare April 23, 2026 05:07
@vanceingalls vanceingalls force-pushed the vance/transform-clipping branch from 7f5f8ae to 41c8c36 Compare April 23, 2026 05:07
@vanceingalls vanceingalls force-pushed the vance/refactor-helpers branch from 13b88d2 to 3d8b1dd Compare April 23, 2026 05:11
@graphite-app graphite-app Bot changed the base branch from vance/refactor-helpers to graphite-base/374 April 23, 2026 05:14
@vanceingalls vanceingalls force-pushed the vance/transform-clipping branch from 41c8c36 to ea64bc3 Compare April 23, 2026 05:42
@graphite-app graphite-app Bot changed the base branch from graphite-base/374 to main April 23, 2026 05:42
@vanceingalls vanceingalls force-pushed the vance/transform-clipping branch from ea64bc3 to f09f4f9 Compare April 23, 2026 05:43
return values;
}

const match3d = css.match(/^matrix3d\(\s*([^)]+)\)$/);
Address jrusso1020's nit on PR #365 (non-blocking review): both READMEs now
explain where the tolerance values come from.

- hdr-regression/README.md: add a budget-breakdown table that derives the 30
  frames from the deltas in PRs #369 (window C fix → 5) and #375 (window F
  fix → 0). The table doubles as a contract: if a future change forces the
  budget back up, exactly one bucket has regressed and the table tells you
  which one to investigate first.
- hdr-hlg-regression/README.md: add a 'Tolerance' section explaining why 0
  is the right floor (HLG is a pure pass-through path, HEVC over rgb48le is
  byte-deterministic on the same fixture, so any drift is a real regression).

The regeneration command for generate-hdr-photo-pq.py was already documented
at README lines 67-71, so no changes needed there.
@vanceingalls vanceingalls force-pushed the vance/transform-clipping branch from f09f4f9 to b3c7f8f Compare April 23, 2026 06:07
@vanceingalls vanceingalls merged commit 2e1a1d9 into main Apr 23, 2026
31 of 32 checks passed
Copy link
Copy Markdown
Collaborator Author

Merge activity

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.

3 participants