Skip to content

Commit 54a3ef2

Browse files
committed
fix(renderer): prevent stale SwiftShader layers
1 parent 33a4f82 commit 54a3ef2

7 files changed

Lines changed: 484 additions & 0 deletions

File tree

packages/engine/src/services/browserManager.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,25 @@ describe("buildChromeArgs browser GPU mode", () => {
2828
expect(args).not.toContain("--enable-gpu-rasterization");
2929
});
3030

31+
it("disables GPU compositing only for software BeginFrame capture", () => {
32+
const softwareBeginFrame = buildChromeArgs(
33+
{ ...base, captureMode: "beginframe" },
34+
{ browserGpuMode: "software" },
35+
);
36+
const softwareScreenshot = buildChromeArgs(
37+
{ ...base, captureMode: "screenshot" },
38+
{ browserGpuMode: "software" },
39+
);
40+
const hardwareBeginFrame = buildChromeArgs(
41+
{ ...base, captureMode: "beginframe", platform: "linux" },
42+
{ browserGpuMode: "hardware" },
43+
);
44+
45+
expect(softwareBeginFrame).toContain("--disable-gpu-compositing");
46+
expect(softwareScreenshot).not.toContain("--disable-gpu-compositing");
47+
expect(hardwareBeginFrame).not.toContain("--disable-gpu-compositing");
48+
});
49+
3150
it("uses Metal-backed ANGLE for hardware browser GPU mode on macOS", () => {
3251
const args = buildChromeArgs({ ...base, platform: "darwin" }, { browserGpuMode: "hardware" });
3352
expect(args).toContain("--enable-unsafe-webgpu");

packages/engine/src/services/browserManager.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,17 @@ export function buildChromeArgs(
686686

687687
// BeginFrame flags — only when using chrome-headless-shell on Linux
688688
if (options.captureMode !== "screenshot") {
689+
// SwiftShader's GPU compositor can retain a transformed layer for several
690+
// sequential frames after a GSAP yoyo/reversal. The DOM and timeline are
691+
// already at the requested time, but both BeginFrame and
692+
// Page.captureScreenshot read the stale surface (the duplicate is present
693+
// in the raw JPEG before encoding). Keep deterministic BeginFrame capture,
694+
// but route compositing through Chrome's software path when the browser is
695+
// already in software-GPU mode. Hardware-GPU and screenshot captures keep
696+
// their existing compositor paths.
697+
if (browserGpuMode === "software") {
698+
chromeArgs.push("--disable-gpu-compositing");
699+
}
689700
chromeArgs.push(
690701
"--deterministic-mode",
691702
"--enable-begin-frame-control",

packages/producer/src/regression-harness.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,12 @@ type TestMetadata = {
121121
* guard; omit for the default screenshot/BeginFrame capture.
122122
*/
123123
experimentalFastCapture?: boolean;
124+
/**
125+
* Pin the browser capture path for a regression fixture. The producer's
126+
* software-GPU default normally prefers screenshots, so BeginFrame-only
127+
* compositor regressions must opt out explicitly to exercise that path.
128+
*/
129+
captureMode?: "screenshot" | "beginframe";
124130
/**
125131
* Render-time variable overrides, equivalent to `hyperframes render
126132
* --variables '<json>'`. Injected as `window.__hfVariables` before any
@@ -390,6 +396,15 @@ function validateMetadata(meta: unknown): TestMetadata {
390396
"meta.json: 'renderConfig.experimentalFastCapture' must be a boolean (or omit for false)",
391397
);
392398
}
399+
if (
400+
rc.captureMode !== undefined &&
401+
rc.captureMode !== "screenshot" &&
402+
rc.captureMode !== "beginframe"
403+
) {
404+
throw new Error(
405+
"meta.json: 'renderConfig.captureMode' must be 'screenshot' or 'beginframe' (or omitted)",
406+
);
407+
}
393408
if (
394409
rc.variables !== undefined &&
395410
(rc.variables === null || typeof rc.variables !== "object" || Array.isArray(rc.variables))
@@ -1037,7 +1052,12 @@ async function runTestSuite(
10371052
// var, scoped to this suite's render so it never leaks to other suites.
10381053
const useFast = suite.meta.renderConfig.experimentalFastCapture === true;
10391054
const prevFast = process.env.PRODUCER_EXPERIMENTAL_FAST_CAPTURE;
1055+
const captureMode = suite.meta.renderConfig.captureMode;
1056+
const prevForceScreenshot = process.env.PRODUCER_FORCE_SCREENSHOT;
10401057
if (useFast) process.env.PRODUCER_EXPERIMENTAL_FAST_CAPTURE = "true";
1058+
if (captureMode) {
1059+
process.env.PRODUCER_FORCE_SCREENSHOT = captureMode === "screenshot" ? "true" : "false";
1060+
}
10411061
try {
10421062
const job = createRenderJob({
10431063
fps: suite.meta.renderConfig.fps,
@@ -1056,6 +1076,10 @@ async function runTestSuite(
10561076
if (prevFast === undefined) delete process.env.PRODUCER_EXPERIMENTAL_FAST_CAPTURE;
10571077
else process.env.PRODUCER_EXPERIMENTAL_FAST_CAPTURE = prevFast;
10581078
}
1079+
if (captureMode) {
1080+
if (prevForceScreenshot === undefined) delete process.env.PRODUCER_FORCE_SCREENSHOT;
1081+
else process.env.PRODUCER_FORCE_SCREENSHOT = prevForceScreenshot;
1082+
}
10591083
}
10601084
}
10611085

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "software-beginframe-yoyo-compositor",
3+
"description": "Regression guard for stale SwiftShader compositor layers after a GSAP yoyo reversal. At 25 fps the unfixed BeginFrame path briefly retained the first output badge at its previous transform, painting a third ghost badge around 10.12 seconds.",
4+
"tags": ["regression", "beginframe", "gsap"],
5+
"minPsnr": 30,
6+
"maxFrameFailures": 0,
7+
"minAudioCorrelation": 0,
8+
"maxAudioLagWindows": 1,
9+
"renderConfig": {
10+
"fps": 25,
11+
"workers": 1,
12+
"captureMode": "beginframe"
13+
}
14+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:9763ae3659454f2baa08f403562e334f4b17b1610a54df20203d21b58543fc26
3+
size 437140
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:326ced7888acace3f38c020eb7c56a6164940b94d335603153d615f4b66dc408
3+
size 1843718

0 commit comments

Comments
 (0)