mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-01 19:42:03 +00:00
Merge pull request #2359 from heygen-com/via/issue-3-software-gpu-screenshot
fix(engine): software-GPU browsers imply screenshot capture
This commit is contained in:
@@ -1,7 +1,14 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||
import { join } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
import { resolveConfig, DEFAULT_CONFIG, scaleProtocolTimeoutForComposition } from "./config.js";
|
||||
import {
|
||||
resolveConfig,
|
||||
DEFAULT_CONFIG,
|
||||
scaleProtocolTimeoutForComposition,
|
||||
shouldClampToScreenshotForConcreteGpu,
|
||||
applyConcreteGpuScreenshotClamp,
|
||||
} from "./config.js";
|
||||
import type { EngineConfig } from "./config.js";
|
||||
import { isLowMemorySystem } from "./services/systemMemory.js";
|
||||
|
||||
describe("resolveConfig", () => {
|
||||
@@ -296,6 +303,221 @@ describe("resolveConfig", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("forceScreenshot (software-GPU clamp)", () => {
|
||||
it("forces screenshot capture when browserGpuMode resolves to software", () => {
|
||||
setEnv("PRODUCER_BROWSER_GPU_MODE", "software");
|
||||
unsetEnv("PRODUCER_FORCE_SCREENSHOT");
|
||||
const config = resolveConfig();
|
||||
expect(config.forceScreenshot).toBe(true);
|
||||
});
|
||||
|
||||
it("leaves forceScreenshot alone on hardware GPU (default off)", () => {
|
||||
setEnv("PRODUCER_BROWSER_GPU_MODE", "hardware");
|
||||
unsetEnv("PRODUCER_FORCE_SCREENSHOT");
|
||||
const config = resolveConfig();
|
||||
expect(config.forceScreenshot).toBe(false);
|
||||
});
|
||||
|
||||
it("does not force screenshot on auto (auto probes to hardware on real GPUs)", () => {
|
||||
setEnv("PRODUCER_BROWSER_GPU_MODE", "auto");
|
||||
unsetEnv("PRODUCER_FORCE_SCREENSHOT");
|
||||
const config = resolveConfig();
|
||||
expect(config.forceScreenshot).toBe(false);
|
||||
});
|
||||
|
||||
it("explicit env opt-out (PRODUCER_FORCE_SCREENSHOT=false) is honored on software", () => {
|
||||
setEnv("PRODUCER_BROWSER_GPU_MODE", "software");
|
||||
setEnv("PRODUCER_FORCE_SCREENSHOT", "false");
|
||||
const config = resolveConfig();
|
||||
expect(config.forceScreenshot).toBe(false);
|
||||
});
|
||||
|
||||
it("explicit programmatic opt-out is honored on software", () => {
|
||||
setEnv("PRODUCER_BROWSER_GPU_MODE", "software");
|
||||
unsetEnv("PRODUCER_FORCE_SCREENSHOT");
|
||||
const config = resolveConfig({ forceScreenshot: false });
|
||||
expect(config.forceScreenshot).toBe(false);
|
||||
});
|
||||
|
||||
it("caller override forceScreenshot=true stays true regardless of GPU mode", () => {
|
||||
setEnv("PRODUCER_BROWSER_GPU_MODE", "hardware");
|
||||
const config = resolveConfig({ forceScreenshot: true });
|
||||
expect(config.forceScreenshot).toBe(true);
|
||||
});
|
||||
|
||||
it("documents the auto-branch gap: resolveConfig leaves auto→software as forceScreenshot=false", () => {
|
||||
// resolveConfig's clamp keys on the string `browserGpuMode`; `"auto"`
|
||||
// that runtime-probes to software is invisible to this layer. The
|
||||
// runtime companion `shouldClampToScreenshotForConcreteGpu` (below)
|
||||
// closes the gap at the frameCapture + renderOrchestrator sites.
|
||||
setEnv("PRODUCER_BROWSER_GPU_MODE", "auto");
|
||||
unsetEnv("PRODUCER_FORCE_SCREENSHOT");
|
||||
const config = resolveConfig();
|
||||
expect(config.browserGpuMode).toBe("auto");
|
||||
expect(config.forceScreenshot).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("shouldClampToScreenshotForConcreteGpu (runtime companion for auto→software)", () => {
|
||||
it("returns true when resolved GPU is software AND forceScreenshot is currently false", () => {
|
||||
// Env explicitly cleared so PRODUCER_FORCE_SCREENSHOT="false" opt-out
|
||||
// doesn't fire.
|
||||
expect(
|
||||
shouldClampToScreenshotForConcreteGpu("software", false, {} as NodeJS.ProcessEnv),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false when resolved GPU is hardware (no clamp needed)", () => {
|
||||
expect(
|
||||
shouldClampToScreenshotForConcreteGpu("hardware", false, {} as NodeJS.ProcessEnv),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false when forceScreenshot is already true (invariant already satisfied)", () => {
|
||||
expect(shouldClampToScreenshotForConcreteGpu("software", true, {} as NodeJS.ProcessEnv)).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it("honors PRODUCER_FORCE_SCREENSHOT=false env opt-out on software", () => {
|
||||
// BeginFrame-on-software debugging escape hatch.
|
||||
expect(
|
||||
shouldClampToScreenshotForConcreteGpu("software", false, {
|
||||
PRODUCER_FORCE_SCREENSHOT: "false",
|
||||
} as NodeJS.ProcessEnv),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("does NOT treat other PRODUCER_FORCE_SCREENSHOT values as opt-out", () => {
|
||||
// Only literal "false" opts out; "true", "0", missing, anything else clamps.
|
||||
for (const value of [undefined, "true", "1", "0", "no", ""]) {
|
||||
const env = (
|
||||
value === undefined ? {} : { PRODUCER_FORCE_SCREENSHOT: value }
|
||||
) as NodeJS.ProcessEnv;
|
||||
expect(shouldClampToScreenshotForConcreteGpu("software", false, env)).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it("honors the programmatic opt-out via opts.programmaticOptOut on software", () => {
|
||||
// The auto→software probe path is what this really guards: `resolveConfig`
|
||||
// sets `forceScreenshotExplicitlyOptedOut = true` when the caller passed
|
||||
// `overrides.forceScreenshot === false`, and the helper reads it here so
|
||||
// the concrete-resolution route matches the config-time behavior.
|
||||
expect(
|
||||
shouldClampToScreenshotForConcreteGpu("software", false, {} as NodeJS.ProcessEnv, {
|
||||
programmaticOptOut: true,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("programmatic opt-out beats a missing env opt-out (both escape hatches independent)", () => {
|
||||
// Even with no env opt-out set, a programmatic opt-out preserves BeginFrame-
|
||||
// on-software debugging on the auto→software probe path.
|
||||
expect(
|
||||
shouldClampToScreenshotForConcreteGpu(
|
||||
"software",
|
||||
false,
|
||||
{ PRODUCER_FORCE_SCREENSHOT: "true" } as NodeJS.ProcessEnv,
|
||||
{ programmaticOptOut: true },
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("applyConcreteGpuScreenshotClamp (caller-level contract)", () => {
|
||||
// This is the helper both frameCapture.ts and renderOrchestrator.ts call
|
||||
// to compute the value the AUTHORITATIVE `forceScreenshot` local should
|
||||
// hold after the concrete GPU is resolved. Routing AND telemetry read
|
||||
// from that one value, so this contract must hold across default and
|
||||
// opt-out combinations.
|
||||
type OptOutCfg = Pick<EngineConfig, "forceScreenshotExplicitlyOptedOut">;
|
||||
const cleanEnv = {} as NodeJS.ProcessEnv;
|
||||
|
||||
it("resolved software + default false → promotes to true (screenshot route)", () => {
|
||||
// The core auto→software fix: routing AND downstream telemetry read
|
||||
// the promoted value, so `updateCaptureObservability({ forceScreenshot:
|
||||
// captureForceScreenshot })` at the capture_strategy site reports
|
||||
// screenshot instead of overwriting back to beginframe.
|
||||
expect(applyConcreteGpuScreenshotClamp(false, "software", {} as OptOutCfg, cleanEnv)).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it("resolved software + programmatic opt-out → stays false (BeginFrame preserved)", () => {
|
||||
// The programmatic escape hatch caller-level contract: setting
|
||||
// overrides.forceScreenshot=false must keep BeginFrame across BOTH
|
||||
// routing (frameCapture) and telemetry (renderOrchestrator) — since
|
||||
// resolveConfig lifts the flag onto the config, both callers converge.
|
||||
expect(
|
||||
applyConcreteGpuScreenshotClamp(
|
||||
false,
|
||||
"software",
|
||||
{ forceScreenshotExplicitlyOptedOut: true } as OptOutCfg,
|
||||
cleanEnv,
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("resolved hardware + default false → stays false (no clamp needed)", () => {
|
||||
expect(applyConcreteGpuScreenshotClamp(false, "hardware", {} as OptOutCfg, cleanEnv)).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it("resolved software + already-true forceScreenshot → stays true (idempotent)", () => {
|
||||
// Config-time clamp already fired (literal browserGpuMode:"software"),
|
||||
// so re-applying at the concrete-resolved site is a no-op.
|
||||
expect(applyConcreteGpuScreenshotClamp(true, "software", {} as OptOutCfg, cleanEnv)).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it("resolved software + env PRODUCER_FORCE_SCREENSHOT=false → stays false", () => {
|
||||
// Env opt-out preserved even when programmatic flag is not set (some
|
||||
// callers, like debugging BeginFrame-on-software from CI, opt-out via
|
||||
// env only).
|
||||
expect(
|
||||
applyConcreteGpuScreenshotClamp(
|
||||
false,
|
||||
"software",
|
||||
{} as OptOutCfg,
|
||||
{
|
||||
PRODUCER_FORCE_SCREENSHOT: "false",
|
||||
} as NodeJS.ProcessEnv,
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("resolved software + undefined cfg → default (no programmatic opt-out) → clamps to true", () => {
|
||||
// Sanity: frameCapture.ts calls with `config` possibly undefined.
|
||||
// Default case must still promote.
|
||||
expect(applyConcreteGpuScreenshotClamp(false, "software", undefined, cleanEnv)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("forceScreenshotExplicitlyOptedOut provenance", () => {
|
||||
it("is set to true when programmatic override forceScreenshot=false is passed", () => {
|
||||
setEnv("PRODUCER_BROWSER_GPU_MODE", "hardware");
|
||||
unsetEnv("PRODUCER_FORCE_SCREENSHOT");
|
||||
const config = resolveConfig({ forceScreenshot: false });
|
||||
expect(config.forceScreenshotExplicitlyOptedOut).toBe(true);
|
||||
});
|
||||
|
||||
it("is set to true when env PRODUCER_FORCE_SCREENSHOT=false is set", () => {
|
||||
setEnv("PRODUCER_BROWSER_GPU_MODE", "hardware");
|
||||
setEnv("PRODUCER_FORCE_SCREENSHOT", "false");
|
||||
const config = resolveConfig();
|
||||
expect(config.forceScreenshotExplicitlyOptedOut).toBe(true);
|
||||
});
|
||||
|
||||
it("stays unset when neither opt-out is present (default)", () => {
|
||||
setEnv("PRODUCER_BROWSER_GPU_MODE", "hardware");
|
||||
unsetEnv("PRODUCER_FORCE_SCREENSHOT");
|
||||
const config = resolveConfig();
|
||||
expect(config.forceScreenshotExplicitlyOptedOut).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("lowMemoryMode", () => {
|
||||
it("forces on for truthy PRODUCER_LOW_MEMORY_MODE values", () => {
|
||||
setEnv("PRODUCER_LOW_MEMORY_MODE", "true");
|
||||
|
||||
@@ -88,6 +88,19 @@ export interface EngineConfig {
|
||||
* opt-out. Not intended to be set by callers.
|
||||
*/
|
||||
pageSideCompositingAutoDisabled?: boolean;
|
||||
/**
|
||||
* INTERNAL. Set to `true` by `resolveConfig` when the caller explicitly
|
||||
* opted out of the software-GPU→screenshot clamp — either via env
|
||||
* `PRODUCER_FORCE_SCREENSHOT=false` or programmatic
|
||||
* `overrides.forceScreenshot === false`. The concrete-resolved-GPU helper
|
||||
* (`shouldClampToScreenshotForConcreteGpu`) reads this so the
|
||||
* `browserGpuMode:"auto"` → software probe path preserves the same
|
||||
* escape hatch as literal `browserGpuMode:"software"` (the boolean
|
||||
* `forceScreenshot === false` at that point is otherwise ambiguous —
|
||||
* default vs explicit opt-out — because the config resolves before
|
||||
* the runtime probe fires). Not intended to be set by callers.
|
||||
*/
|
||||
forceScreenshotExplicitlyOptedOut?: boolean;
|
||||
/**
|
||||
* Low-memory render profile. When `true`, the orchestrator collapses the
|
||||
* pipeline to its cheapest shape on memory-constrained hosts: it skips the
|
||||
@@ -554,6 +567,47 @@ export function resolveConfig(overrides?: Partial<EngineConfig>): EngineConfig {
|
||||
merged.useDrawElement = false;
|
||||
}
|
||||
|
||||
// Software GPU implies screenshot capture.
|
||||
//
|
||||
// Two existing platform gates already do most of the work: `browserManager`
|
||||
// only launches BeginFrame on Linux + chrome-headless-shell + !forceScreenshot,
|
||||
// and the DE clamp above turns off `useDrawElement` on non-(darwin +
|
||||
// non-software) hosts. Setting `forceScreenshot` here layers defense-in-depth
|
||||
// on top:
|
||||
//
|
||||
// 1. Linux + software (SwiftShader host): kicks the browser off BeginFrame,
|
||||
// which stalls the compositor on shader-heavy frames under CPU raster
|
||||
// (same motivation as the closed PR #822).
|
||||
// 2. Observability truth: `renderOrchestrator`'s reported `captureMode`
|
||||
// field is derived from `cfg.forceScreenshot ? "screenshot" : "beginframe"`
|
||||
// — without this clamp it misreports `"beginframe"` for the actual
|
||||
// screenshot capture on darwin + software.
|
||||
// 3. Future-proofing: any new BeginFrame or drawElement entry point that
|
||||
// forgets to gate on GPU mode still routes to screenshot here.
|
||||
//
|
||||
// Note this does NOT eliminate SwiftShader-on-darwin text-rasterization
|
||||
// artifacts (an ANGLE-SwiftShader issue on macOS text — the fix there is to
|
||||
// use `--browser-gpu`, which routes to `--use-angle=metal`). It only makes
|
||||
// routing consistent + observability accurate.
|
||||
//
|
||||
// Explicit opt-out (env or programmatic override) is honored so BeginFrame-
|
||||
// on-software debugging remains possible.
|
||||
const explicitForceScreenshotOptOut =
|
||||
env("PRODUCER_FORCE_SCREENSHOT") === "false" || overrides?.forceScreenshot === false;
|
||||
// Persist provenance so the concrete-resolved-GPU helper can honor the
|
||||
// programmatic opt-out too — at that point `forceScreenshot === false` is
|
||||
// otherwise ambiguous between default and explicit opt-out.
|
||||
if (explicitForceScreenshotOptOut) {
|
||||
merged.forceScreenshotExplicitlyOptedOut = true;
|
||||
}
|
||||
if (
|
||||
merged.browserGpuMode === "software" &&
|
||||
!merged.forceScreenshot &&
|
||||
!explicitForceScreenshotOptOut
|
||||
) {
|
||||
merged.forceScreenshot = true;
|
||||
}
|
||||
|
||||
// drawElement capture and page-side shader compositing are mutually
|
||||
// incompatible capture strategies (drawElement reads paint records directly
|
||||
// and bypasses the page-side prepare→composite→resolve protocol). When
|
||||
@@ -575,3 +629,63 @@ export function resolveConfig(overrides?: Partial<EngineConfig>): EngineConfig {
|
||||
vp9CpuUsed: normalizeVp9CpuUsed(merged.vp9CpuUsed),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtime-resolved companion to the software-GPU screenshot clamp in
|
||||
* `resolveConfig`. Returns `true` iff callers should treat this render as
|
||||
* `forceScreenshot=true` even though the config's stored `forceScreenshot`
|
||||
* is `false`. Fires when the concrete resolved GPU is software AND neither
|
||||
* the env opt-out (`PRODUCER_FORCE_SCREENSHOT=false`) nor the programmatic
|
||||
* opt-out (`overrides.forceScreenshot === false`, carried via
|
||||
* `cfg.forceScreenshotExplicitlyOptedOut`) is set.
|
||||
*
|
||||
* `resolveConfig`'s clamp only sees `browserGpuMode` as a string, so
|
||||
* `"auto"` that runtime-probes to software slips through. This helper
|
||||
* closes that gap at the concrete-resolution points (`frameCapture` and
|
||||
* `renderOrchestrator`). Same invariant, same escape hatches, one predicate.
|
||||
*
|
||||
* Callers should skip when the invariant is already satisfied
|
||||
* (`currentForceScreenshot === true`) to avoid redundant work. Pass
|
||||
* `cfg.forceScreenshotExplicitlyOptedOut` via `opts.programmaticOptOut` so
|
||||
* the `browserGpuMode:"auto"` → software probe path honors the same
|
||||
* programmatic escape hatch as literal `browserGpuMode:"software"`.
|
||||
*/
|
||||
export function shouldClampToScreenshotForConcreteGpu(
|
||||
resolvedGpuMode: "software" | "hardware",
|
||||
currentForceScreenshot: boolean,
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
opts: { programmaticOptOut?: boolean } = {},
|
||||
): boolean {
|
||||
if (currentForceScreenshot) return false;
|
||||
if (resolvedGpuMode !== "software") return false;
|
||||
if (opts.programmaticOptOut) return false;
|
||||
return env["PRODUCER_FORCE_SCREENSHOT"] !== "false";
|
||||
}
|
||||
|
||||
/**
|
||||
* Caller-facing pair to `shouldClampToScreenshotForConcreteGpu`: computes the
|
||||
* value the *authoritative* `forceScreenshot` local should hold after the
|
||||
* concrete-resolved-GPU decision fires. Returns the (possibly-promoted) new
|
||||
* boolean, so the caller can assign it back to its local — driving both
|
||||
* routing AND telemetry from one source of truth.
|
||||
*
|
||||
* Reads the programmatic opt-out from `cfg.forceScreenshotExplicitlyOptedOut`
|
||||
* (set by `resolveConfig` when EITHER env `PRODUCER_FORCE_SCREENSHOT=false`
|
||||
* OR programmatic `overrides.forceScreenshot === false` was present).
|
||||
*
|
||||
* Idempotent: `applyConcreteGpuScreenshotClamp(true, ...)` returns `true`
|
||||
* without consulting anything else.
|
||||
*/
|
||||
export function applyConcreteGpuScreenshotClamp(
|
||||
currentForceScreenshot: boolean,
|
||||
resolvedGpuMode: "software" | "hardware",
|
||||
cfg: Pick<EngineConfig, "forceScreenshotExplicitlyOptedOut"> | undefined,
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
): boolean {
|
||||
return (
|
||||
currentForceScreenshot ||
|
||||
shouldClampToScreenshotForConcreteGpu(resolvedGpuMode, currentForceScreenshot, env, {
|
||||
programmaticOptOut: cfg?.forceScreenshotExplicitlyOptedOut ?? false,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -50,6 +50,8 @@ export {
|
||||
resolveConfig,
|
||||
DEFAULT_CONFIG,
|
||||
scaleProtocolTimeoutForComposition,
|
||||
shouldClampToScreenshotForConcreteGpu,
|
||||
applyConcreteGpuScreenshotClamp,
|
||||
type EngineConfig,
|
||||
} from "./config.js";
|
||||
export {
|
||||
|
||||
@@ -43,7 +43,7 @@ import {
|
||||
produceDrawElementFrameBatch,
|
||||
} from "./drawElementService.js";
|
||||
import { initThreeDProjection, detectCssEffectRisk } from "./threeDProjection.js";
|
||||
import { DEFAULT_CONFIG, type EngineConfig } from "../config.js";
|
||||
import { DEFAULT_CONFIG, applyConcreteGpuScreenshotClamp, type EngineConfig } from "../config.js";
|
||||
import type {
|
||||
CaptureOptions,
|
||||
CaptureVideoMetadataHint,
|
||||
@@ -815,15 +815,33 @@ export async function createCaptureSession(
|
||||
// need explicit clip+scale on `Page.captureScreenshot`, so fall back to
|
||||
// the screenshot path for any DPR > 1.
|
||||
const supersampling = (options.deviceScaleFactor ?? 1) > 1;
|
||||
const preMode: CaptureMode =
|
||||
headlessShell && isLinux && !forceScreenshot && !supersampling && !drawElementTransparent
|
||||
? "beginframe"
|
||||
: "screenshot";
|
||||
const requestedGpuMode = config?.browserGpuMode ?? DEFAULT_CONFIG.browserGpuMode;
|
||||
const resolvedGpuMode = await resolveBrowserGpuMode(requestedGpuMode, {
|
||||
chromePath: headlessShell ?? undefined,
|
||||
browserTimeout: config?.browserTimeout,
|
||||
});
|
||||
// Apply the software-GPU→screenshot invariant at the concrete-resolved
|
||||
// point too — `resolveConfig` can only see the pre-resolve `browserGpuMode`
|
||||
// string, so `"auto"` that probes to software would otherwise slip through
|
||||
// and launch BeginFrame + SwiftShader (the exact combination the invariant
|
||||
// is meant to prevent). Both env and programmatic opt-outs preserved via
|
||||
// `applyConcreteGpuScreenshotClamp` (the programmatic one carried on the
|
||||
// config as `forceScreenshotExplicitlyOptedOut`, since at this point the
|
||||
// boolean `forceScreenshot === false` is otherwise ambiguous between
|
||||
// default and explicit opt-out).
|
||||
const effectiveForceScreenshot = applyConcreteGpuScreenshotClamp(
|
||||
forceScreenshot,
|
||||
resolvedGpuMode,
|
||||
config,
|
||||
);
|
||||
const preMode: CaptureMode =
|
||||
headlessShell &&
|
||||
isLinux &&
|
||||
!effectiveForceScreenshot &&
|
||||
!supersampling &&
|
||||
!drawElementTransparent
|
||||
? "beginframe"
|
||||
: "screenshot";
|
||||
const chromeArgs = buildChromeArgs(
|
||||
{ width: options.width, height: options.height, captureMode: preMode },
|
||||
{ ...config, browserGpuMode: resolvedGpuMode },
|
||||
|
||||
@@ -71,6 +71,7 @@ import {
|
||||
type SubTimelineWaitOutcome,
|
||||
resolveBrowserGpuMode,
|
||||
resolveHeadlessShellPath,
|
||||
applyConcreteGpuScreenshotClamp,
|
||||
scaleProtocolTimeoutForComposition,
|
||||
isMemoryExhaustionError,
|
||||
isTransientBrowserError,
|
||||
@@ -1982,7 +1983,28 @@ export async function executeRenderJob(
|
||||
chromePath: resolveHeadlessShellPath(cfg),
|
||||
browserTimeout: cfg.browserTimeout,
|
||||
});
|
||||
updateCaptureObservability({ browserGpuMode: resolvedBrowserGpuMode });
|
||||
// Apply the software-GPU→screenshot clamp to the AUTHORITATIVE local
|
||||
// `captureForceScreenshot` (not just the observability copy) so all
|
||||
// downstream strategy + telemetry code reads the corrected value.
|
||||
// Otherwise: `frameCapture.ts` clamps its own local and routes
|
||||
// screenshot, but the still-`false` orchestrator local (a) mislabels the
|
||||
// parallel-stream logging as "beginframe" below and (b) overwrites the
|
||||
// earlier observability correction back to BeginFrame at the
|
||||
// capture_strategy telemetry site. `resolveConfig` couldn't see
|
||||
// `browserGpuMode:"auto"` resolving to software at config time, so
|
||||
// `captureForceScreenshot` was still `compileResult.forceScreenshot === false`
|
||||
// on that path. Both env and programmatic opt-outs preserved via
|
||||
// `applyConcreteGpuScreenshotClamp` (the programmatic one carried on the
|
||||
// config as `forceScreenshotExplicitlyOptedOut`).
|
||||
captureForceScreenshot = applyConcreteGpuScreenshotClamp(
|
||||
captureForceScreenshot,
|
||||
resolvedBrowserGpuMode,
|
||||
cfg,
|
||||
);
|
||||
updateCaptureObservability({
|
||||
browserGpuMode: resolvedBrowserGpuMode,
|
||||
forceScreenshot: captureForceScreenshot,
|
||||
});
|
||||
const videoCaptureBeyondViewport = resolveVideoCaptureBeyondViewport(composition.videos.length);
|
||||
|
||||
const captureOptions: CaptureOptions = {
|
||||
|
||||
Reference in New Issue
Block a user