From aeb4905a39ed1125dd07760f7402f75934230058 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Sat, 13 Jun 2026 14:48:12 -0400 Subject: [PATCH 1/8] refactor(producer): extract HDR compositor from renderOrchestrator Move ~700 LOC of HDR compositing primitives (countNonZeroAlpha, countNonZeroRgb48, cropRgb48le, HdrVideoFrameSource, closeHdrVideoFrameSource, blitHdrVideoLayer, HdrImageBuffer, blitHdrImageLayer, CompositeTransfer, shouldUseLayeredComposite, resolveCompositeTransfer, HdrCompositeContext, compositeHdrFrame, HdrTransitionMeta, TransitionRange) into a dedicated hdrCompositor.ts module. Remove backward-compat re-exports from renderOrchestrator (hdrPerf, captureCost, shared) and rewire all import sites to the authoritative source modules. --- .../producer/src/services/hdrCompositor.ts | 703 +++++++++++++++++ .../src/services/render/perfSummary.ts | 3 +- .../render/stages/captureHdrFrameShared.ts | 5 +- .../render/stages/captureHdrHybridLoop.ts | 8 +- .../render/stages/captureHdrResources.ts | 8 +- .../render/stages/captureHdrSequentialLoop.ts | 10 +- .../services/render/stages/captureHdrStage.ts | 9 +- .../render/stages/extractVideosStage.ts | 3 +- .../src/services/renderOrchestrator.test.ts | 3 +- .../src/services/renderOrchestrator.ts | 721 +----------------- 10 files changed, 723 insertions(+), 750 deletions(-) create mode 100644 packages/producer/src/services/hdrCompositor.ts diff --git a/packages/producer/src/services/hdrCompositor.ts b/packages/producer/src/services/hdrCompositor.ts new file mode 100644 index 0000000000..35c2bc9320 --- /dev/null +++ b/packages/producer/src/services/hdrCompositor.ts @@ -0,0 +1,703 @@ +// fallow-ignore-file complexity code-duplication +/** + * HDR Compositor — pixel-level compositing primitives for the HDR + * layered render path. + * + * Extracted from `renderOrchestrator.ts` so the ~600 LOC of HDR-specific + * buffer manipulation, video/image blit logic, and per-frame compositor + * live in a focused module that can be tested and evolved independently. + * + * Consumers: `captureHdrStage.ts`, `captureHdrSequentialLoop.ts`, + * `captureHdrHybridLoop.ts`, `captureHdrFrameShared.ts`, + * `captureHdrResources.ts`. + */ + +import { readSync, closeSync } from "fs"; +import { join } from "path"; +import { + type CaptureSession, + type BeforeCaptureHook, + type HdrTransfer, + type ElementStackingInfo, + type HfTransitionMeta, + captureAlphaPng, + applyDomLayerMask, + removeDomLayerMask, + decodePng, + blitRgba8OverRgb48le, + blitRgb48leRegion, + groupIntoLayers, + blitRgb48leAffine, + parseTransformMatrix, + convertTransfer, +} from "@hyperframes/engine"; +import type { ProducerLogger } from "../logger.js"; +import { type HdrImageTransferCache } from "./hdrImageTransferCache.js"; +import { writeFileExclusiveSync } from "./render/shared.js"; +import { type HdrPerfCollector, addHdrTiming } from "./render/hdrPerf.js"; + +// ─── Diagnostic helpers ──────────────────────────────────────────────────── + +// Diagnostic helpers used by the HDR layered compositor when KEEP_TEMP=1 +// is set. They are pure (capture no state), so we keep them at module scope +// to avoid re-creating closures per frame and to make them callable from +// any future composite path that needs to log non-zero pixel counts. +function countNonZeroAlpha(rgba: Uint8Array): number { + let n = 0; + for (let p = 3; p < rgba.length; p += 4) { + if (rgba[p] !== 0) n++; + } + return n; +} + +function countNonZeroRgb48(buf: Uint8Array): number { + let n = 0; + for (let p = 0; p < buf.length; p += 6) { + if ( + buf[p] !== 0 || + buf[p + 1] !== 0 || + buf[p + 2] !== 0 || + buf[p + 3] !== 0 || + buf[p + 4] !== 0 || + buf[p + 5] !== 0 + ) + n++; + } + return n; +} + +// ─── Types ───────────────────────────────────────────────────────────────── + +/** + * Metadata for a shader transition between two scenes, extracted from + * `window.__hf.transitions`. Re-exported from the engine so the producer + * shares the contract with composition runtime code. + */ +export type HdrTransitionMeta = HfTransitionMeta; + +/** Pre-computed frame range for an active transition. */ +export interface TransitionRange extends HdrTransitionMeta { + startFrame: number; + endFrame: number; +} + +// ─── Video frame source ──────────────────────────────────────────────────── + +/** + * Crop an rgb48le buffer to a sub-region. Returns a new Buffer containing + * only the cropped pixels. + */ +function cropRgb48le( + src: Buffer, + srcW: number, + srcH: number, + cropX: number, + cropY: number, + cropW: number, + cropH: number, +): Buffer { + const BPP = 6; + const dst = Buffer.alloc(cropW * cropH * BPP); + for (let row = 0; row < cropH; row++) { + const srcRow = cropY + row; + if (srcRow < 0 || srcRow >= srcH) continue; + const srcOff = (srcRow * srcW + cropX) * BPP; + const dstOff = row * cropW * BPP; + const copyLen = Math.min(cropW, srcW - cropX) * BPP; + if (copyLen > 0) src.copy(dst, dstOff, srcOff, srcOff + copyLen); + } + return dst; +} + +/** + * Blit a single HDR video layer onto an rgb48le canvas. + * + * Shared between the normal-frame compositing path (compositeToBuffer) + * and the transition dual-scene compositing loop to avoid duplicating + * the frame lookup, raw read, transfer, transform, and blit logic. + */ +export interface HdrVideoFrameSource { + dir: string; + rawPath: string; + fd: number; + width: number; + height: number; + frameSize: number; + frameCount: number; + scratch: Buffer; +} + +export function closeHdrVideoFrameSource(source: HdrVideoFrameSource, log?: ProducerLogger): void { + try { + closeSync(source.fd); + } catch (err) { + log?.warn("Failed to close HDR raw frame file", { + rawPath: source.rawPath, + error: err instanceof Error ? err.message : String(err), + }); + } +} + +export function blitHdrVideoLayer( + canvas: Buffer, + el: ElementStackingInfo, + time: number, + fps: number, + hdrVideoFrameSources: Map, + hdrStartTimes: Map, + width: number, + height: number, + log?: ProducerLogger, + sourceTransfer?: HdrTransfer, + targetTransfer?: HdrTransfer, + hdrPerf?: HdrPerfCollector, +): void { + const frameSource = hdrVideoFrameSources.get(el.id); + const startTime = hdrStartTimes.get(el.id); + if (!frameSource || startTime === undefined || el.opacity <= 0) { + return; + } + + // Frame index within the video. Clamp to the extracted raw frame count so + // a composition that outlives the source clip freezes on the last frame, + // matching Chrome's