Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions packages/engine/src/services/browserManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ describe("buildChromeArgs browser GPU mode", () => {
expect(args).not.toContain("--enable-gpu-rasterization");
});

it("disables GPU compositing only for software BeginFrame capture", () => {
it("disables GPU compositing for all software capture modes (#2550, #3049)", () => {
const softwareBeginFrame = buildChromeArgs(
{ ...base, captureMode: "beginframe" },
{ browserGpuMode: "software" },
Expand All @@ -145,10 +145,27 @@ describe("buildChromeArgs browser GPU mode", () => {
{ ...base, captureMode: "beginframe", platform: "linux" },
{ browserGpuMode: "hardware" },
);
const hardwareScreenshot = buildChromeArgs(
{ ...base, captureMode: "screenshot", platform: "linux" },
{ browserGpuMode: "hardware" },
);

expect(softwareBeginFrame).toContain("--disable-gpu-compositing");
expect(softwareScreenshot).not.toContain("--disable-gpu-compositing");
expect(softwareScreenshot).toContain("--disable-gpu-compositing");
expect(hardwareBeginFrame).not.toContain("--disable-gpu-compositing");
expect(hardwareScreenshot).not.toContain("--disable-gpu-compositing");
});

it("appends PRODUCER_EXTRA_CHROME_ARGS when set", () => {
process.env.PRODUCER_EXTRA_CHROME_ARGS =
"--disable-partial-raster --force-gpu-mem-available-mb=4096";
try {
const args = buildChromeArgs(base);
expect(args).toContain("--disable-partial-raster");
expect(args).toContain("--force-gpu-mem-available-mb=4096");
} finally {
delete process.env.PRODUCER_EXTRA_CHROME_ARGS;
}
});

it("uses Metal-backed ANGLE for hardware browser GPU mode on macOS", () => {
Expand Down
31 changes: 19 additions & 12 deletions packages/engine/src/services/browserManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -910,20 +910,21 @@ export function buildChromeArgs(
chromeArgs.push(WEBGPU_FLAG);
}

// SwiftShader's GPU compositor can retain a transformed layer across frames,
// producing phantom duplicate content at regular vertical offsets (HF#2550,
// HF#3049). The artefact appears in BOTH BeginFrame and Page.captureScreenshot
// capture modes: a band of page content is duplicated at exactly
// renderHeight/4 in the alpha channel, and static DOM elements (SVG lines,
// rects) are affected equally. Routing compositing through Chrome's software
// path eliminates the stale-layer retention. Hardware-GPU captures keep their
// existing compositor path. Remove this workaround once the pinned
// chrome-headless-shell includes https://issues.chromium.org/issues/535256667.
if (browserGpuMode === "software") {
chromeArgs.push("--disable-gpu-compositing");
}

// BeginFrame flags — only when using chrome-headless-shell on Linux
if (options.captureMode !== "screenshot") {
// SwiftShader's GPU compositor can retain a transformed layer for several
// sequential frames after a GSAP yoyo/reversal. The DOM and timeline are
// already at the requested time, but both BeginFrame and
// Page.captureScreenshot read the stale surface (the duplicate is present
// in the raw JPEG before encoding). Keep deterministic BeginFrame capture,
// but route compositing through Chrome's software path when the browser is
// already in software-GPU mode. Hardware-GPU and screenshot captures keep
// their existing compositor paths. Remove this workaround once the pinned
// chrome-headless-shell includes https://issues.chromium.org/issues/535256667.
if (browserGpuMode === "software") {
chromeArgs.push("--disable-gpu-compositing");
}
chromeArgs.push(
"--deterministic-mode",
"--enable-begin-frame-control",
Expand All @@ -940,6 +941,12 @@ export function buildChromeArgs(
if (gpuDisabled) {
chromeArgs.push("--disable-gpu");
}

const extraArgs = process.env.PRODUCER_EXTRA_CHROME_ARGS;
if (extraArgs) {
chromeArgs.push(...extraArgs.split(/\s+/).filter(Boolean));
}

return chromeArgs;
}

Expand Down
Loading