fix(engine): drawElement self-verify waits for colour-grading LUTs before its truth screenshot

This commit is contained in:
Miguel Angel Simon Sierra
2026-10-29 19:00:32 -04:00
parent 3e462f4277
commit 6929fdaaea
2 changed files with 51 additions and 9 deletions
@@ -5,6 +5,7 @@ import type { CaptureSession } from "./frameCapture.js";
import type { SubTimelineWaitMemo } from "../types.js";
import {
buildZeroDurationDiagnostic,
captureDeVerificationFrames,
pollHfReady,
captureFramesBatchPipelined,
captureFrameToBufferPipelined,
@@ -726,3 +727,38 @@ describe("HF_READY_DIAGNOSTIC_EXPR (evaluated as real JS, not via a fake fixture
expect(result.rejectedBuildReadyKeys).toEqual([]);
});
});
describe("captureDeVerificationFrames", () => {
it("waits for the colour-grading LUTs after injecting video frames, before every truth screenshot", async () => {
const log: string[] = [];
const cdp = {
send: vi.fn(async () => {
log.push("screenshot");
return { data: "" };
}),
};
const page = {
createCDPSession: async () => cdp,
evaluate: vi.fn(async (fn: unknown) => {
const src = String(fn);
if (src.includes("waitForActiveLuts")) log.push("luts");
else if (src.includes("data-start")) return [];
return undefined;
}),
};
const session = {
options: { fps: 30, width: 8, height: 8, format: "jpeg", compositionDurationSeconds: 10 },
onBeforeCapture: async () => {
log.push("inject");
},
} as unknown as CaptureSession;
await captureDeVerificationFrames(session, page as never, () => {});
expect(session.deVerifyFrames?.size).toBeGreaterThan(0);
log.forEach((entry, i) => {
if (entry === "inject") expect(log.slice(i + 1, i + 3)).toEqual(["luts", "screenshot"]);
});
expect(log).toContain("inject");
});
});
+15 -9
View File
@@ -3269,14 +3269,7 @@ async function prepareFrameForCapture(
session.captureStep = "waiting for page seek handlers";
await waitForPendingSeekCompletion(page);
session.captureStep = "loading color-grading LUTs";
await page.evaluate(async () => {
const runtime = (
window as Window & {
__hf?: { colorGrading?: { waitForActiveLuts?: () => Promise<number> } };
}
).__hf?.colorGrading;
await runtime?.waitForActiveLuts?.();
});
await waitForColorGradingLuts(page);
const beforeCaptureMs = Date.now() - beforeCaptureStart;
// Page-side compositing three-phase protocol:
@@ -3346,6 +3339,18 @@ export function computeAuthoredClipBoundaryFrames(
return frames;
}
/** A graded frame paints ungraded until the LUT has loaded, so wait before every screenshot. */
async function waitForColorGradingLuts(page: Page): Promise<void> {
await page.evaluate(async () => {
const runtime = (
window as Window & {
__hf?: { colorGrading?: { waitForActiveLuts?: () => Promise<number> } };
}
).__hf?.colorGrading;
await runtime?.waitForActiveLuts?.();
});
}
async function computeClipBoundaryFrames(page: Page, fps: number): Promise<Set<number>> {
const schedule = await page.evaluate(() =>
Array.from(document.querySelectorAll("[data-start]")).map((el) => ({
@@ -5408,7 +5413,7 @@ export async function seekDeVerifyTruth(
);
}
async function captureDeVerificationFrames(
export async function captureDeVerificationFrames(
session: CaptureSession,
page: Page,
logInitPhase: (phase: string) => void,
@@ -5493,6 +5498,7 @@ async function captureDeVerificationFrames(
// it, <video> elements screenshot black and every video comp would
// false-positive into the screenshot fallback.
await injectFrames?.(truthPage, t);
await waitForColorGradingLuts(truthPage);
// The first screenshot forces a frame, which runs rAF-driven callbacks (a count-up lands
// a tick after seek()); waiting on rAF via evaluate deadlocks, headless fires it only
// when a frame is produced.