feat(engine): assertSwiftShader chrome://gpu validator

Part of Phase 2 of the distributed rendering plan (determinism hardening).
See DISTRIBUTED-RENDERING-PLAN.md §5.2 (browserGpuMode row) and §9.3
(BROWSER_GPU_NOT_SOFTWARE typed failure).

Adds packages/engine/src/utils/assertSwiftShader.ts:

  - assertSwiftShader(page, readInfo?) — navigates to chrome://gpu, reads
    the GL_VENDOR / GL_RENDERER rows from browserBridge.gpuInfo_, throws
    SwiftShaderAssertionError ({ code: "BROWSER_GPU_NOT_SOFTWARE" }) if
    the active backend isn't SwiftShader.
  - readWebGlVendorInfo(page) — extracted helper so tests can stub the
    info read without spinning up real Chrome.
  - SwiftShaderAssertionError + BROWSER_GPU_NOT_SOFTWARE constant exposed
    so the Phase 3 distributed adapter can match typed non-retryable
    failures.

Re-exported from packages/engine/src/index.ts. No caller invokes it yet;
Phase 3 renderChunk() will run it post-launch.

In-process behavior is unchanged — assertSwiftShader is a new pure utility.
Producer regression baselines remain byte-identical.

This is part of a stack of 10 PRs; this is PR 2 of 10.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
James
2026-05-13 04:36:11 +00:00
co-authored by Claude Opus 4.7
parent 2d6372ac2a
commit d8486a7c4d
3 changed files with 263 additions and 0 deletions
+7
View File
@@ -155,6 +155,13 @@ export {
// ── Utilities ──────────────────────────────────────────────────────────────────
export { quantizeTimeToFrame, MEDIA_VISUAL_STYLE_PROPERTIES } from "@hyperframes/core";
export {
assertSwiftShader,
readWebGlVendorInfo,
SwiftShaderAssertionError,
BROWSER_GPU_NOT_SOFTWARE,
} from "./utils/assertSwiftShader.js";
export {
extractMediaMetadata,
extractVideoMetadata,
@@ -0,0 +1,130 @@
/**
* Tests for assertSwiftShader and its companion readWebGlVendorInfo helper.
*
* We don't spin up a real Chrome here — the assertion's contract is "given a
* WebGL info pair, accept SwiftShader and reject anything else." Tests inject
* the info pair through the optional `readInfo` override that the
* production code path leaves as a default.
*/
import { describe, expect, it } from "vitest";
import type { Page } from "puppeteer-core";
import {
BROWSER_GPU_NOT_SOFTWARE,
SwiftShaderAssertionError,
assertSwiftShader,
} from "./assertSwiftShader.js";
// Minimal Page stub. Only assertSwiftShader's default `readInfo` ever touches
// `page.goto` / `page.evaluate`; when we inject a custom `readInfo` the page
// object is never used, so an empty cast is safe.
const stubPage = {} as unknown as Page;
describe("assertSwiftShader", () => {
it("accepts the canonical SwiftShader vendor + renderer pair", async () => {
await assertSwiftShader(stubPage, async () => ({
vendor: "Google Inc. (Google)",
renderer:
"ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)",
}));
});
it("accepts SwiftShader regardless of trailing whitespace on vendor", async () => {
await assertSwiftShader(stubPage, async () => ({
vendor: " Google Inc. (Google) ",
renderer: "SwiftShader",
}));
});
it("accepts case-insensitive renderer token", async () => {
await assertSwiftShader(stubPage, async () => ({
vendor: "Google Inc. (Google)",
renderer: "ANGLE (Google, swiftshader Device, swiftshader driver)",
}));
});
it("throws SwiftShaderAssertionError when vendor is hardware-accelerated", async () => {
let caught: unknown;
try {
await assertSwiftShader(stubPage, async () => ({
vendor: "Google Inc. (NVIDIA Corporation)",
renderer: "ANGLE (NVIDIA, NVIDIA GeForce RTX 4090, OpenGL 4.6)",
}));
} catch (err) {
caught = err;
}
expect(caught).toBeInstanceOf(SwiftShaderAssertionError);
expect((caught as SwiftShaderAssertionError).code).toBe(BROWSER_GPU_NOT_SOFTWARE);
expect((caught as Error).message).toContain("non-SwiftShader");
expect((caught as Error).message).toContain("--use-gl=swiftshader");
expect((caught as SwiftShaderAssertionError).vendor).toBe("Google Inc. (NVIDIA Corporation)");
});
it("throws when the renderer string lacks SwiftShader even if vendor matches", async () => {
// Google Inc. is the umbrella vendor for many ANGLE backends — vendor
// alone is not enough; the renderer must actually mention SwiftShader.
let caught: unknown;
try {
await assertSwiftShader(stubPage, async () => ({
vendor: "Google Inc. (Google)",
renderer: "ANGLE (Google, Vulkan 1.3.0 (Intel(R) UHD Graphics 630), OpenGL ES 3.0)",
}));
} catch (err) {
caught = err;
}
expect(caught).toBeInstanceOf(SwiftShaderAssertionError);
expect((caught as SwiftShaderAssertionError).code).toBe(BROWSER_GPU_NOT_SOFTWARE);
});
it("throws when both vendor and renderer are empty", async () => {
// Some chrome:// pages return empty strings before the GPU info table
// populates. We treat that as failure rather than silently passing.
let caught: unknown;
try {
await assertSwiftShader(stubPage, async () => ({ vendor: "", renderer: "" }));
} catch (err) {
caught = err;
}
expect(caught).toBeInstanceOf(SwiftShaderAssertionError);
expect((caught as SwiftShaderAssertionError).code).toBe(BROWSER_GPU_NOT_SOFTWARE);
});
it("propagates errors from the info reader without wrapping", async () => {
const upstream = new Error("simulated CDP failure");
let caught: unknown;
try {
await assertSwiftShader(stubPage, async () => {
throw upstream;
});
} catch (err) {
caught = err;
}
// Reader errors should not be masked by SwiftShaderAssertionError —
// they are a separate failure class (probably retryable).
expect(caught).toBe(upstream);
});
it("rejects an unrelated vendor that happens to contain the SwiftShader token in the renderer", async () => {
// Defensive: if some future ANGLE build uses a non-Google vendor string
// but still mentions SwiftShader in the renderer for some reason, we
// still want to require the exact Google vendor signature.
let caught: unknown;
try {
await assertSwiftShader(stubPage, async () => ({
vendor: "Mesa/X.org",
renderer: "llvmpipe (SwiftShader compatible)",
}));
} catch (err) {
caught = err;
}
expect(caught).toBeInstanceOf(SwiftShaderAssertionError);
});
});
describe("SwiftShaderAssertionError", () => {
it("exposes the BROWSER_GPU_NOT_SOFTWARE typed-failure code", () => {
const err = new SwiftShaderAssertionError("test", "v", "r");
expect(err.code).toBe(BROWSER_GPU_NOT_SOFTWARE);
expect(err.code).toBe("BROWSER_GPU_NOT_SOFTWARE");
});
});
@@ -0,0 +1,126 @@
/**
* assertSwiftShader — verify Chrome's WebGL is rendered by SwiftShader.
*
* Distributed renders pixel-lock on the GPU backend: hardware GL is bitwise
* unstable across worker machines (different drivers, driver versions, GL
* extension sets, even differing fp32 rounding on the same vendor). Chunk
* workers launch Chrome with `--use-gl=swiftshader --use-angle=swiftshader`
* so every worker uses the same pure-software GL implementation.
*
* Those Chrome flags are advisory: a misconfigured base image, a missing
* SwiftShader library, or a `chrome://gpu` blocklist override can silently
* downgrade to system GL. The distributed pipeline cannot detect the
* downgrade by sampling pixels (one machine = one render), so we read
* `chrome://gpu` directly after launch and refuse to render if the active
* GL renderer is anything other than SwiftShader.
*/
import type { Page } from "puppeteer-core";
/**
* Error code classifying this failure as non-retryable for distributed
* workflow adapters — a downgraded GPU on a worker will not heal on retry.
*/
export const BROWSER_GPU_NOT_SOFTWARE = "BROWSER_GPU_NOT_SOFTWARE";
/**
* Error thrown when chrome://gpu reports a non-SwiftShader WebGL backend.
*
* Carries a `code` property so the adapter can match on it without parsing
* the message string — Temporal/Step Functions retry policies key off the
* code, not the message.
*/
export class SwiftShaderAssertionError extends Error {
readonly code: typeof BROWSER_GPU_NOT_SOFTWARE = BROWSER_GPU_NOT_SOFTWARE;
readonly vendor: string;
readonly renderer: string;
constructor(message: string, vendor: string, renderer: string) {
super(message);
this.name = "SwiftShaderAssertionError";
this.vendor = vendor;
this.renderer = renderer;
}
}
/**
* SwiftShader identifies itself on `chrome://gpu` and in
* `WEBGL_debug_renderer_info` with this exact vendor string. Locking to
* Google's own GL string (rather than a substring match on "swiftshader")
* avoids false-positives from third-party ANGLE backends that incidentally
* mention SwiftShader in unrelated diagnostic text.
*/
const SWIFTSHADER_VENDOR_SIGNATURE = "Google Inc. (Google)";
/**
* Renderer string contains the literal "SwiftShader" token. We match
* case-insensitively and only require the substring; Chrome occasionally
* appends a build suffix (e.g. " Vulkan 1.3").
*/
const SWIFTSHADER_RENDERER_TOKEN = "swiftshader";
interface WebGlInfo {
vendor: string;
renderer: string;
}
/**
* Read the WebGL vendor/renderer strings from a live `chrome://gpu` page.
*
* Extracted from `assertSwiftShader` so tests can stub the navigation +
* extraction step. Returns the raw values; callers decide how to interpret
* them. Both fields are best-effort — Chrome returns empty strings if the
* GPU info table hasn't populated yet, which the caller treats as failure.
*/
export async function readWebGlVendorInfo(page: Page): Promise<WebGlInfo> {
await page.goto("chrome://gpu", { waitUntil: "domcontentloaded", timeout: 30_000 });
// The "GL_VENDOR" / "GL_RENDERER" rows live inside <info-view> shadow DOM
// in modern Chrome. We pull the structured `info_log_` payload off the
// page-level globals instead of querying the DOM, since the DOM layout has
// drifted across versions.
const info = await page.evaluate((): WebGlInfo => {
type Row = { description?: string; value?: string };
type InfoLog = { graphics_info?: { basic_info?: Row[] } };
const w = window as unknown as { browserBridge?: { gpuInfo_?: InfoLog } };
const rows: Row[] = w.browserBridge?.gpuInfo_?.graphics_info?.basic_info ?? [];
let vendor = "";
let renderer = "";
for (const row of rows) {
if (typeof row.description !== "string" || typeof row.value !== "string") continue;
if (row.description === "GL_VENDOR") vendor = row.value;
else if (row.description === "GL_RENDERER") renderer = row.value;
}
return { vendor, renderer };
});
return info;
}
/**
* Validate that the active WebGL renderer is SwiftShader. Throws
* `SwiftShaderAssertionError` otherwise.
*
* Pass an optional `readInfo` override for tests that don't have a real
* Puppeteer `Page`. The default implementation navigates to `chrome://gpu`
* and parses the GL_VENDOR / GL_RENDERER rows.
*/
export async function assertSwiftShader(
page: Page,
readInfo: (page: Page) => Promise<WebGlInfo> = readWebGlVendorInfo,
): Promise<void> {
const { vendor, renderer } = await readInfo(page);
const vendorMatches = vendor.trim() === SWIFTSHADER_VENDOR_SIGNATURE;
const rendererMatches = renderer.toLowerCase().includes(SWIFTSHADER_RENDERER_TOKEN);
if (vendorMatches && rendererMatches) return;
throw new SwiftShaderAssertionError(
`[assertSwiftShader] Chrome reported a non-SwiftShader WebGL backend. ` +
`Distributed renders require pure-software GL for pixel-identical retries. ` +
`Got vendor=${JSON.stringify(vendor)} renderer=${JSON.stringify(renderer)}; ` +
`expected vendor=${JSON.stringify(SWIFTSHADER_VENDOR_SIGNATURE)} renderer to contain "SwiftShader". ` +
`Ensure Chrome was launched with --use-gl=swiftshader --use-angle=swiftshader and that the ` +
`SwiftShader libraries are present in the runtime image.`,
vendor,
renderer,
);
}