mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
Merge pull request #642 from heygen-com/feat/auto-detect-browser-gpu
feat(engine): browserGpuMode "auto" — probe-once WebGL detection with software fallback
This commit is contained in:
@@ -60,7 +60,7 @@ describe("renderLocal browser GPU config", () => {
|
||||
quality: "standard",
|
||||
format: "mp4",
|
||||
gpu: false,
|
||||
browserGpu: false,
|
||||
browserGpuMode: "software",
|
||||
hdrMode: "auto",
|
||||
quiet: true,
|
||||
});
|
||||
@@ -72,6 +72,25 @@ describe("renderLocal browser GPU config", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("forwards browserGpuMode='auto' into producer config (probe-then-choose)", async () => {
|
||||
const { renderLocal } = await import("./render.js");
|
||||
await renderLocal("/tmp/project", "/tmp/out.mp4", {
|
||||
fps: 30,
|
||||
quality: "standard",
|
||||
format: "mp4",
|
||||
gpu: false,
|
||||
browserGpuMode: "auto",
|
||||
hdrMode: "auto",
|
||||
quiet: true,
|
||||
});
|
||||
|
||||
expect(producerState.resolveConfigCalls).toContainEqual({ browserGpuMode: "auto" });
|
||||
expect(producerState.createdJobs[0]?.producerConfig).toMatchObject({
|
||||
browserGpuMode: "auto",
|
||||
resolved: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("passes an explicit hardware override for default local browser GPU", async () => {
|
||||
const { renderLocal } = await import("./render.js");
|
||||
await renderLocal("/tmp/project", "/tmp/out.mp4", {
|
||||
@@ -79,7 +98,7 @@ describe("renderLocal browser GPU config", () => {
|
||||
quality: "standard",
|
||||
format: "mp4",
|
||||
gpu: false,
|
||||
browserGpu: true,
|
||||
browserGpuMode: "hardware",
|
||||
hdrMode: "auto",
|
||||
quiet: true,
|
||||
});
|
||||
@@ -94,12 +113,18 @@ describe("renderLocal browser GPU config", () => {
|
||||
it("resolves browser GPU from CLI flags, Docker mode, and env fallback", async () => {
|
||||
const { resolveBrowserGpuForCli } = await import("./render.js");
|
||||
|
||||
expect(resolveBrowserGpuForCli(false, undefined, undefined)).toBe(true);
|
||||
expect(resolveBrowserGpuForCli(false, undefined, "hardware")).toBe(true);
|
||||
expect(resolveBrowserGpuForCli(false, undefined, "software")).toBe(false);
|
||||
expect(resolveBrowserGpuForCli(false, true, "software")).toBe(true);
|
||||
expect(resolveBrowserGpuForCli(false, false, "hardware")).toBe(false);
|
||||
expect(resolveBrowserGpuForCli(true, undefined, "hardware")).toBe(false);
|
||||
// Default (no flag, no env): auto — engine probes and chooses.
|
||||
expect(resolveBrowserGpuForCli(false, undefined, undefined)).toBe("auto");
|
||||
// Env override
|
||||
expect(resolveBrowserGpuForCli(false, undefined, "hardware")).toBe("hardware");
|
||||
expect(resolveBrowserGpuForCli(false, undefined, "software")).toBe("software");
|
||||
expect(resolveBrowserGpuForCli(false, undefined, "auto")).toBe("auto");
|
||||
// Explicit CLI flag wins over env
|
||||
expect(resolveBrowserGpuForCli(false, true, "software")).toBe("hardware");
|
||||
expect(resolveBrowserGpuForCli(false, false, "hardware")).toBe("software");
|
||||
// Docker forces software regardless of flags/env
|
||||
expect(resolveBrowserGpuForCli(true, undefined, "hardware")).toBe("software");
|
||||
expect(resolveBrowserGpuForCli(true, undefined, "auto")).toBe("software");
|
||||
});
|
||||
|
||||
it("forwards parsed --variables payload to createRenderJob", async () => {
|
||||
@@ -109,7 +134,7 @@ describe("renderLocal browser GPU config", () => {
|
||||
quality: "standard",
|
||||
format: "mp4",
|
||||
gpu: false,
|
||||
browserGpu: false,
|
||||
browserGpuMode: "software",
|
||||
hdrMode: "auto",
|
||||
quiet: true,
|
||||
variables: { title: "Hello", count: 3 },
|
||||
@@ -125,7 +150,7 @@ describe("renderLocal browser GPU config", () => {
|
||||
quality: "standard",
|
||||
format: "mp4",
|
||||
gpu: false,
|
||||
browserGpu: false,
|
||||
browserGpuMode: "software",
|
||||
hdrMode: "auto",
|
||||
quiet: true,
|
||||
});
|
||||
@@ -147,7 +172,7 @@ describe("renderLocal browser GPU config", () => {
|
||||
quality: "standard",
|
||||
format: "mp4",
|
||||
gpu: false,
|
||||
browserGpu: true,
|
||||
browserGpuMode: "hardware",
|
||||
hdrMode: "auto",
|
||||
quiet: true,
|
||||
exitAfterComplete: true,
|
||||
|
||||
@@ -118,7 +118,7 @@ export default defineCommand({
|
||||
"browser-gpu": {
|
||||
type: "boolean",
|
||||
description:
|
||||
"Use host GPU acceleration for Chrome/WebGL capture. Enabled by default for local renders; use --no-browser-gpu to opt out.",
|
||||
"Force host GPU acceleration for Chrome/WebGL capture. Default: auto (probe on first launch; fall back to software if no GPU). Use --no-browser-gpu to force software (SwiftShader).",
|
||||
},
|
||||
quiet: {
|
||||
type: "boolean",
|
||||
@@ -224,7 +224,7 @@ export default defineCommand({
|
||||
const useDocker = args.docker ?? false;
|
||||
const useGpu = args.gpu ?? false;
|
||||
const browserGpuArg = args["browser-gpu"];
|
||||
const useBrowserGpu = resolveBrowserGpuForCli(useDocker, browserGpuArg);
|
||||
const browserGpuMode = resolveBrowserGpuForCli(useDocker, browserGpuArg);
|
||||
const quiet = args.quiet ?? false;
|
||||
const strictAll = args["strict-all"] ?? false;
|
||||
const strictErrors = (args.strict ?? false) || strictAll;
|
||||
@@ -275,10 +275,14 @@ export default defineCommand({
|
||||
c.dim(" \u2192 " + outputPath),
|
||||
);
|
||||
console.log(c.dim(" " + fps + "fps \u00B7 " + quality + " \u00B7 " + workerLabel));
|
||||
if (useGpu || useBrowserGpu) {
|
||||
if (useGpu || browserGpuMode !== "software") {
|
||||
const gpuModes = [
|
||||
useGpu ? "encoder GPU" : null,
|
||||
useBrowserGpu ? "browser GPU (auto)" : null,
|
||||
browserGpuMode === "hardware"
|
||||
? "browser GPU (forced)"
|
||||
: browserGpuMode === "auto"
|
||||
? "browser GPU (auto-detect)"
|
||||
: null,
|
||||
].filter(Boolean);
|
||||
console.log(c.dim(" GPU: " + gpuModes.join(" + ")));
|
||||
}
|
||||
@@ -397,7 +401,7 @@ export default defineCommand({
|
||||
format,
|
||||
workers,
|
||||
gpu: useGpu,
|
||||
browserGpu: useBrowserGpu,
|
||||
browserGpuMode,
|
||||
hdrMode: args.sdr ? "force-sdr" : args.hdr ? "force-hdr" : "auto",
|
||||
crf,
|
||||
videoBitrate,
|
||||
@@ -412,7 +416,7 @@ export default defineCommand({
|
||||
format,
|
||||
workers,
|
||||
gpu: useGpu,
|
||||
browserGpu: useBrowserGpu,
|
||||
browserGpuMode,
|
||||
hdrMode: args.sdr ? "force-sdr" : args.hdr ? "force-hdr" : "auto",
|
||||
crf,
|
||||
videoBitrate,
|
||||
@@ -431,7 +435,12 @@ interface RenderOptions {
|
||||
format: "mp4" | "webm" | "mov";
|
||||
workers?: number;
|
||||
gpu: boolean;
|
||||
browserGpu: boolean;
|
||||
/**
|
||||
* Chrome WebGL backend mode. "auto" probes on first launch and falls back
|
||||
* to "software" if no usable GPU. Defaults to "software" when omitted to
|
||||
* stay backwards-compatible with callers that pre-date the tri-state.
|
||||
*/
|
||||
browserGpuMode?: "auto" | "hardware" | "software";
|
||||
hdrMode: "auto" | "force-hdr" | "force-sdr";
|
||||
crf?: number;
|
||||
videoBitrate?: string;
|
||||
@@ -579,15 +588,33 @@ export function validateVariablesAgainstProject(
|
||||
return validateVariables(values, meta.variables);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the browser-GPU mode for a CLI render invocation.
|
||||
*
|
||||
* Priority (highest first):
|
||||
* 1. Docker mode → always "software" (docker has no portable GPU
|
||||
* passthrough; the engine's render path uses SwiftShader).
|
||||
* 2. Explicit CLI flag — `--browser-gpu` → "hardware",
|
||||
* `--no-browser-gpu` → "software".
|
||||
* 3. Env var `PRODUCER_BROWSER_GPU_MODE` accepts "hardware" / "software" /
|
||||
* "auto".
|
||||
* 4. Default = "auto" — engine probes WebGL availability on first launch
|
||||
* and falls back to software if the host lacks a usable GPU.
|
||||
*
|
||||
* Returning "auto" by default lets local renders Just Work whether or not the
|
||||
* host has a GPU, while preserving the explicit overrides for CI / power
|
||||
* users who want failure-on-misconfig.
|
||||
*/
|
||||
export function resolveBrowserGpuForCli(
|
||||
useDocker: boolean,
|
||||
browserGpuArg: boolean | undefined,
|
||||
envMode = process.env.PRODUCER_BROWSER_GPU_MODE,
|
||||
): boolean {
|
||||
if (useDocker) return false;
|
||||
if (browserGpuArg !== undefined) return browserGpuArg;
|
||||
if (envMode === "software") return false;
|
||||
return true;
|
||||
): "auto" | "hardware" | "software" {
|
||||
if (useDocker) return "software";
|
||||
if (browserGpuArg === true) return "hardware";
|
||||
if (browserGpuArg === false) return "software";
|
||||
if (envMode === "hardware" || envMode === "software" || envMode === "auto") return envMode;
|
||||
return "auto";
|
||||
}
|
||||
|
||||
const DOCKER_IMAGE_PREFIX = "hyperframes-renderer";
|
||||
@@ -707,7 +734,7 @@ async function renderDocker(
|
||||
format: options.format,
|
||||
workers: options.workers,
|
||||
gpu: options.gpu,
|
||||
browserGpu: options.browserGpu,
|
||||
browserGpu: options.browserGpuMode === "hardware",
|
||||
hdrMode: options.hdrMode,
|
||||
crf: options.crf,
|
||||
videoBitrate: options.videoBitrate,
|
||||
@@ -777,7 +804,7 @@ export async function renderLocal(
|
||||
workers: options.workers,
|
||||
useGpu: options.gpu,
|
||||
producerConfig: producer.resolveConfig({
|
||||
browserGpuMode: options.browserGpu ? "hardware" : "software",
|
||||
browserGpuMode: options.browserGpuMode ?? "software",
|
||||
}),
|
||||
hdrMode: options.hdrMode,
|
||||
crf: options.crf,
|
||||
|
||||
@@ -20,13 +20,13 @@ Requires: Docker installed and running.
|
||||
- `--crf` — Override encoder CRF (mutually exclusive with `--video-bitrate`)
|
||||
- `--video-bitrate` — Target video bitrate such as `10M` (mutually exclusive with `--crf`)
|
||||
- `--gpu` — Use GPU encoding (NVENC, VideoToolbox, VAAPI, QSV)
|
||||
- `--browser-gpu` / `--no-browser-gpu` — Use or opt out of host GPU acceleration for local Chrome/WebGL capture (enabled by default for local renders, disabled in Docker)
|
||||
- `--browser-gpu` / `--no-browser-gpu` — Force host GPU or software (SwiftShader) for Chrome/WebGL capture. Default for local renders is `auto` — probe WebGL availability on first launch and fall back to software if no GPU is reachable. Docker mode always uses software.
|
||||
- `-o, --output` — Custom output path
|
||||
|
||||
## Tips
|
||||
|
||||
- Use `draft` quality for fast previews during development
|
||||
- Local renders use browser GPU capture automatically; use `--no-browser-gpu` to compare against the software-browser path
|
||||
- Local renders auto-detect GPU on first launch; use `--browser-gpu` to force hardware (errors if no GPU) or `--no-browser-gpu` to force SwiftShader
|
||||
- Use `--gpu` when a local render also benefits from hardware FFmpeg encoding
|
||||
- Use `npx hyperframes benchmark` to find optimal settings
|
||||
- 4 workers is usually the sweet spot for most compositions
|
||||
|
||||
Reference in New Issue
Block a user