fix(engine,producer): apply software-GPU screenshot invariant at concrete-resolved point

Addresses Miguel's R1 blockers:

1. `browserGpuMode: "auto"` that runtime-probes to software slipped past the
   `resolveConfig` clamp — that clamp only sees the pre-resolve string. Add
   `shouldClampToScreenshotForConcreteGpu(resolvedGpuMode, currentForceScreenshot, env)`
   in `packages/engine/src/config.ts` and apply it at BOTH concrete-resolution
   sites:
   - `packages/engine/src/services/frameCapture.ts`: downgrades `preMode`
     from "beginframe" to "screenshot" when resolved GPU is software (respects
     `PRODUCER_FORCE_SCREENSHOT=false` env opt-out), fixing the routing.
   - `packages/producer/src/services/renderOrchestrator.ts`: updates
     `captureObservability.forceScreenshot` (and thus `captureMode`) at the
     same call site, fixing the observability truth on the auto → software
     case.

2. New unit tests in `config.test.ts`:
   - Documents the auto-branch gap (resolveConfig leaves auto as
     forceScreenshot=false — the runtime companion closes it).
   - 5 branch tests on `shouldClampToScreenshotForConcreteGpu` covering
     software / hardware / already-forced / env-opt-out / non-"false" env
     values.
   Full suite: 56/56 pass.

Scope narrowing on Blocker 2: the distributed rendering path at
`packages/producer/src/services/distributed/plan.ts:753-754` and
`renderChunk.ts:462-466` explicitly hardcodes `browserGpuMode:"software",
forceScreenshot:false` post-resolveConfig and stays outside this PR's
invariant boundary. `compileStage` may still flip it to true for alpha
formats, but generic MP4 distributed renders on SwiftShader hosts remain
BeginFrame. That's a separate architectural cleanup (needs its own
behavior-change trace); the PR body now scopes the invariant to the
in-process CLI/orchestrator path.
This commit is contained in:
Vance Ingalls
2026-07-13 23:13:30 +00:00
parent ba5168293f
commit 2e44602f99
5 changed files with 123 additions and 7 deletions
+20 -5
View File
@@ -43,7 +43,11 @@ import {
produceDrawElementFrameBatch,
} from "./drawElementService.js";
import { initThreeDProjection, detectCssEffectRisk } from "./threeDProjection.js";
import { DEFAULT_CONFIG, type EngineConfig } from "../config.js";
import {
DEFAULT_CONFIG,
shouldClampToScreenshotForConcreteGpu,
type EngineConfig,
} from "../config.js";
import type {
CaptureOptions,
CaptureVideoMetadataHint,
@@ -812,15 +816,26 @@ 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). Env-level opt-out preserved via the shared helper.
const effectiveForceScreenshot =
forceScreenshot || shouldClampToScreenshotForConcreteGpu(resolvedGpuMode, forceScreenshot);
const preMode: CaptureMode =
headlessShell &&
isLinux &&
!effectiveForceScreenshot &&
!supersampling &&
!drawElementTransparent
? "beginframe"
: "screenshot";
const chromeArgs = buildChromeArgs(
{ width: options.width, height: options.height, captureMode: preMode },
{ ...config, browserGpuMode: resolvedGpuMode },