Files
hyperframes/packages/producer/src/services/render/perfSummary.ts
T
Miguel Ángel a0d7295367 refactor(producer): simplify — extract HDR compositor, delete dead code, consolidate patterns (#1414)
* 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.

* refactor(producer): delete 4 re-export shim files

screenshotService.ts, videoFrameExtractor.ts, videoFrameInjector.ts,
and streamingEncoder.ts existed solely to re-export symbols from
@hyperframes/engine. No internal consumer imported from them except
index.ts → videoFrameInjector, which now imports directly from engine.

* refactor(producer): delete unused PNG decode/blit worker pool

The pool (455 LOC) and worker (127 LOC) were built speculatively for
pipelining Chrome screenshots with PNG decode/blit but were never
wired into any capture path. Zero non-test source files imported them.

Also removed the esbuild entry point from producer/build.mjs, the
tsup entry point + alpha-blit alias from cli/tsup.config.ts, and
the PNG worker bootstrap from cli/src/cli.ts.

* refactor(producer): centralize frame filename construction

Replace 4 inline padStart(6) template literals with shared helpers:
- formatCaptureFrameName(index, ext): zero-based, for internal capture
- formatExportFrameName(index, ext): zero-based input, one-based output
  for user-facing png-sequence export

* perf(producer): hoist allElementIds out of compositing loop

Move fullStacking.map() from inside the per-layer iteration to before
the loop, computing the element ID list once per frame instead of once
per DOM layer per frame.

* refactor(producer): consolidate HDR timing instrumentation

* refactor(producer): remove typecasts and deduplicate HDR capture patterns

- Extract seekInjectAndQueryStacking() and seekAndInject() helpers to
  deduplicate the seek+inject+query pattern across sequential loop,
  hybrid loop, and per-scene transition capture (3 call sites → 1 helper)
- Fix sceneBuf as Buffer casts by properly typing the scene-capture
  arrays as [Buffer, Set<string>][] instead of using as const + cast
- Replace as NonNullable<> cast on outputFormat with as const fallback
- Add explanatory comments on inherent linkedom DOM casts

* refactor(producer): name constants, type matrix, extract opacity helper

- Replace magic 0.001/0.999 with TRANSFORM_IDENTITY_EPSILON and
  OPAQUE_ALPHA_THRESHOLD; replace BPP=6 with RGB48_BYTES_PER_PIXEL
- Add AffineMatrix tuple type + isAffineMatrix guard, eliminating
  all 4 non-null assertions on matrix indices
- Extract resolveBlitOpacity() to replace 5 identical ternaries
- Narrow fallow-ignore-file to line-level complexity suppressions
2026-06-13 18:49:19 -04:00

87 lines
3.2 KiB
TypeScript

/**
* Build the `RenderPerfSummary` that lands on `job.perfSummary` and
* the `perf-summary.json` debug artifact.
*/
import { fpsToNumber } from "@hyperframes/core";
import type { CaptureCalibrationSample, CaptureCostEstimate } from "./captureCost.js";
import type {
CaptureAttemptSummary,
HdrDiagnostics,
RenderJob,
RenderPerfSummary,
} from "../renderOrchestrator.js";
import { type HdrPerfCollector, finalizeHdrPerf } from "./hdrPerf.js";
import type { RenderObservabilitySummary } from "./observability.js";
export function buildRenderPerfSummary(input: {
job: RenderJob;
workerCount: number;
enableChunkedEncode: boolean;
chunkedEncodeSize: number;
compositionDurationSeconds: number;
totalFrames: number;
outputWidth: number;
outputHeight: number;
videoCount: number;
audioCount: number;
totalElapsedMs: number;
perfStages: Record<string, number>;
videoExtractBreakdown: RenderPerfSummary["videoExtractBreakdown"];
tmpPeakBytes: number;
captureCalibration?: {
estimate: CaptureCostEstimate;
samples: CaptureCalibrationSample[];
};
captureAttempts: CaptureAttemptSummary[];
hdrDiagnostics: HdrDiagnostics;
hdrPerf?: HdrPerfCollector;
observability?: RenderObservabilitySummary;
peakRssBytes: number;
peakHeapUsedBytes: number;
}): RenderPerfSummary {
return {
renderId: input.job.id,
totalElapsedMs: input.totalElapsedMs,
// RenderPerfSummary surfaces fps as a decimal because it lands in JSON
// payloads (CLI telemetry, regression-harness reports) where a single
// number is friendlier than `{num,den}`. Callers needing the rational
// back can read `job.config.fps`.
fps: fpsToNumber(input.job.config.fps),
quality: input.job.config.quality,
workers: input.workerCount,
chunkedEncode: input.enableChunkedEncode,
chunkSizeFrames: input.enableChunkedEncode ? input.chunkedEncodeSize : null,
compositionDurationSeconds: input.compositionDurationSeconds,
totalFrames: input.totalFrames,
resolution: { width: input.outputWidth, height: input.outputHeight },
videoCount: input.videoCount,
audioCount: input.audioCount,
stages: input.perfStages,
videoExtractBreakdown: input.videoExtractBreakdown,
tmpPeakBytes: input.tmpPeakBytes,
captureCalibration: input.captureCalibration
? {
sampledFrames: input.captureCalibration.samples.map((sample) => sample.frameIndex),
p95Ms: input.captureCalibration.estimate.p95Ms,
multiplier: input.captureCalibration.estimate.multiplier,
reasons: input.captureCalibration.estimate.reasons,
}
: undefined,
captureAttempts: input.captureAttempts.length > 0 ? input.captureAttempts : undefined,
hdrDiagnostics:
input.hdrDiagnostics.videoExtractionFailures > 0 ||
input.hdrDiagnostics.imageDecodeFailures > 0
? { ...input.hdrDiagnostics }
: undefined,
hdrPerf: input.hdrPerf ? finalizeHdrPerf(input.hdrPerf) : undefined,
observability: input.observability,
captureAvgMs:
input.totalFrames > 0
? Math.round((input.perfStages.captureMs ?? 0) / input.totalFrames)
: undefined,
peakRssMb: Math.round(input.peakRssBytes / (1024 * 1024)),
peakHeapUsedMb: Math.round(input.peakHeapUsedBytes / (1024 * 1024)),
};
}