mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-10 22:20:14 +00:00
feat(engine): browserGpuMode "auto" — probe WebGL once, fall back to software
When the host doesn't have a usable GPU (CI containers, eval rigs without
GPU passthrough, dev VMs), Chrome's hardware-mode WebGL flags
(`--use-gl=egl/metal/d3d11`) silently leave WebGL unavailable —
`getContext("webgl")` returns null, three.js' WebGLRenderer dies, the
canvas stays black. Surfaced today by Abhay's c2v-eval failing on a
docker render of an hf bundle that uses three.js + a custom fragment
shader.
The fix that's been there: `--use-gl=angle --use-angle=swiftshader` (CPU
software WebGL, ~5-50× slower but pixel-identical). The engine already
exposed `browserGpuMode: "software"` for this. The gap was discovery —
users had to know to pass `--no-browser-gpu` on no-GPU hosts.
This change adds `browserGpuMode: "auto"` (now the CLI default for local
renders): on first launch in the process, probe Chrome with hardware
args, check `canvas.getContext("webgl") !== null`, cache the result.
~1-2 s on first render, free on every subsequent render in the same
worker. Hardware GPUs keep their fast path; no-GPU hosts get SwiftShader
without ceremony.
Behaviour matrix:
- No flag, no env, local → "auto" (NEW default)
- `--browser-gpu` → "hardware" (force; errors if no GPU)
- `--no-browser-gpu` → "software" (force SwiftShader)
- `PRODUCER_BROWSER_GPU_MODE` → "hardware" / "software" / "auto" / unset
- Docker mode → forced "software" (unchanged)
Engine-config default stays "software" (conservative for embedders); the
"auto" default lives in the CLI's `resolveBrowserGpuForCli` so producer
embedders aren't surprised by a probe-on-launch.
Also adds `--enable-unsafe-swiftshader` to the software flag set —
Chrome 120+ deprecated implicit SwiftShader fallback and emits a
deprecation warning unless the flag is set explicitly. Despite the
"unsafe" name this is exactly the pre-deprecation behaviour; the rename
is about Chrome's threat model on the open web, not about the rendering
itself.
Verification:
- Engine 535/535 + CLI 256/256 (incl. new probe tests + tri-state CLI test)
- Empirical: probe on this no-GPU devbox returns "software" in 240 ms,
cached 0 ms on subsequent calls
- Format / lint / typecheck clean across all packages
Refs the Abhay/Slack thread on c2v-eval rendering without a GPU node.
This commit is contained in:
@@ -1,6 +1,11 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { buildChromeArgs, forceReleaseBrowser } from "./browserManager.js";
|
||||
import {
|
||||
_resetAutoBrowserGpuModeCacheForTests,
|
||||
buildChromeArgs,
|
||||
forceReleaseBrowser,
|
||||
resolveBrowserGpuMode,
|
||||
} from "./browserManager.js";
|
||||
|
||||
describe("buildChromeArgs browser GPU mode", () => {
|
||||
const base = { width: 1920, height: 1080 };
|
||||
@@ -10,6 +15,7 @@ describe("buildChromeArgs browser GPU mode", () => {
|
||||
expect(args).toContain("--enable-features=CanvasDrawElement");
|
||||
expect(args).toContain("--use-gl=angle");
|
||||
expect(args).toContain("--use-angle=swiftshader");
|
||||
expect(args).toContain("--enable-unsafe-swiftshader");
|
||||
expect(args).not.toContain("--enable-gpu-rasterization");
|
||||
});
|
||||
|
||||
@@ -48,6 +54,57 @@ describe("buildChromeArgs browser GPU mode", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveBrowserGpuMode", () => {
|
||||
beforeEach(() => {
|
||||
_resetAutoBrowserGpuModeCacheForTests();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
_resetAutoBrowserGpuModeCacheForTests();
|
||||
});
|
||||
|
||||
it("passes 'software' through unchanged without probing", async () => {
|
||||
const mode = await resolveBrowserGpuMode("software");
|
||||
expect(mode).toBe("software");
|
||||
});
|
||||
|
||||
it("passes 'hardware' through unchanged without probing", async () => {
|
||||
const mode = await resolveBrowserGpuMode("hardware");
|
||||
expect(mode).toBe("hardware");
|
||||
});
|
||||
|
||||
it("falls back to 'software' when the probe browser cannot launch", async () => {
|
||||
// No chromePath, env unset, and (in the test env) no system Chrome to find
|
||||
// → puppeteer.launch will throw → caller catches → software fallback.
|
||||
// Force a definitely-missing chrome binary so the launch path errors fast.
|
||||
const mode = await resolveBrowserGpuMode("auto", {
|
||||
chromePath: "/definitely/not/a/real/chrome/binary",
|
||||
browserTimeout: 2000,
|
||||
});
|
||||
expect(mode).toBe("software");
|
||||
});
|
||||
|
||||
it("caches the probe result across calls", async () => {
|
||||
const first = await resolveBrowserGpuMode("auto", {
|
||||
chromePath: "/definitely/not/a/real/chrome/binary",
|
||||
browserTimeout: 2000,
|
||||
});
|
||||
// Second call uses cache — no new launch. Assert the same answer comes back
|
||||
// even with a different chromePath that would have a different probe outcome.
|
||||
const second = await resolveBrowserGpuMode("auto", {
|
||||
chromePath: "/another/definitely/missing/path",
|
||||
browserTimeout: 2000,
|
||||
});
|
||||
expect(first).toBe("software");
|
||||
expect(second).toBe("software");
|
||||
// Reset and re-probe to confirm the test-only reset works.
|
||||
_resetAutoBrowserGpuModeCacheForTests();
|
||||
const third = await resolveBrowserGpuMode("hardware");
|
||||
expect(third).toBe("hardware");
|
||||
});
|
||||
});
|
||||
|
||||
describe("forceReleaseBrowser", () => {
|
||||
it("kills the browser process and disconnects", () => {
|
||||
const killFn = vi.fn(() => true);
|
||||
|
||||
Reference in New Issue
Block a user