mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-07 18:26:17 +00:00
fix(engine): stop SwiftShader ghosting in software screenshot captures (#3096)
Apply the --disable-gpu-compositing workaround to every software capture, not just BeginFrame ones. SwiftShader's compositor re-presents stale raster for a partially invalidated layer, so successive screenshot captures accumulate copies of earlier seeks; alpha renders are forced onto the screenshot path and were the only ones left unprotected. Refreshes the byte-strict png-sequence alpha baseline for the resulting antialiasing delta (content unchanged, min PSNR 41.3 dB). Fixes #3049.
This commit is contained in:
@@ -7,6 +7,8 @@ import { join } from "node:path";
|
||||
|
||||
import type { Browser, PuppeteerNode } from "puppeteer-core";
|
||||
|
||||
import type { CaptureMode } from "./browserLeasePool.js";
|
||||
|
||||
import {
|
||||
_resetAutoBrowserGpuModeCacheForTests,
|
||||
_resetBrowserPoolForTests,
|
||||
@@ -132,23 +134,22 @@ describe("buildChromeArgs browser GPU mode", () => {
|
||||
expect(args).not.toContain("--enable-gpu-rasterization");
|
||||
});
|
||||
|
||||
it("disables GPU compositing only for software BeginFrame capture", () => {
|
||||
const softwareBeginFrame = buildChromeArgs(
|
||||
{ ...base, captureMode: "beginframe" },
|
||||
{ browserGpuMode: "software" },
|
||||
);
|
||||
const softwareScreenshot = buildChromeArgs(
|
||||
{ ...base, captureMode: "screenshot" },
|
||||
{ browserGpuMode: "software" },
|
||||
);
|
||||
const hardwareBeginFrame = buildChromeArgs(
|
||||
{ ...base, captureMode: "beginframe", platform: "linux" },
|
||||
{ browserGpuMode: "hardware" },
|
||||
);
|
||||
// HF#3049: the stale-raster accumulation lives in SwiftShader's compositor,
|
||||
// which every capture mode reads from — so the gate is the GPU mode alone.
|
||||
// Swept over the whole CaptureMode union so a future mode can't quietly opt
|
||||
// out of the workaround the way `screenshot` did.
|
||||
const captureModes: CaptureMode[] = ["beginframe", "screenshot", "drawelement"];
|
||||
|
||||
expect(softwareBeginFrame).toContain("--disable-gpu-compositing");
|
||||
expect(softwareScreenshot).not.toContain("--disable-gpu-compositing");
|
||||
expect(hardwareBeginFrame).not.toContain("--disable-gpu-compositing");
|
||||
it.each(captureModes)("disables GPU compositing for software %s capture", (captureMode) => {
|
||||
expect(
|
||||
buildChromeArgs({ ...base, captureMode, platform: "linux" }, { browserGpuMode: "software" }),
|
||||
).toContain("--disable-gpu-compositing");
|
||||
});
|
||||
|
||||
it.each(captureModes)("leaves hardware %s capture on the GPU compositor", (captureMode) => {
|
||||
expect(
|
||||
buildChromeArgs({ ...base, captureMode, platform: "linux" }, { browserGpuMode: "hardware" }),
|
||||
).not.toContain("--disable-gpu-compositing");
|
||||
});
|
||||
|
||||
it("uses Metal-backed ANGLE for hardware browser GPU mode on macOS", () => {
|
||||
|
||||
@@ -910,20 +910,33 @@ export function buildChromeArgs(
|
||||
chromeArgs.push(WEBGPU_FLAG);
|
||||
}
|
||||
|
||||
// SwiftShader's GPU compositor can retain a transformed layer for several
|
||||
// sequential frames after a GSAP yoyo/reversal, and it re-presents stale
|
||||
// raster for a partially invalidated layer: content already drawn in an
|
||||
// earlier seek is never cleared, so successive captures accumulate copies of
|
||||
// it (HF#3049 — a moving SVG group smears wider on every frame, and static
|
||||
// siblings appear duplicated one band lower). The DOM and timeline are
|
||||
// already at the requested time; the defect is in the compositor surface both
|
||||
// BeginFrame and Page.captureScreenshot read, so it is not specific to a
|
||||
// capture mode — it is specific to compositing on SwiftShader.
|
||||
//
|
||||
// Routing compositing through Chrome's software path is the only mitigation
|
||||
// that holds: capture-side changes (fromSurface, captureBeyondViewport, a
|
||||
// second capture, extra rAF ticks, a 250ms settle) and every raster/tiling
|
||||
// flag (--disable-partial-raster, --disable-checker-imaging, --disable-zero-copy,
|
||||
// forced tile sizes) leave the accumulation untouched. The cost is that
|
||||
// SwiftShader rasterizes thin strokes and glyph edges slightly differently
|
||||
// (antialiased edges only — measured on HF#3049's frame 0: 96 opaque pixels
|
||||
// differ, by 1/255). Duplicated content in 68% of frames is the worse defect.
|
||||
//
|
||||
// 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",
|
||||
|
||||
Reference in New Issue
Block a user