Skip to content

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

Open
jrusso1020 wants to merge 1 commit into
mainfrom
feat/engine-assert-swiftshader
Open

feat(engine): assertSwiftShader chrome://gpu validator#767
jrusso1020 wants to merge 1 commit into
mainfrom
feat/engine-assert-swiftshader

Conversation

@jrusso1020
Copy link
Copy Markdown
Collaborator

@jrusso1020 jrusso1020 commented May 13, 2026

What

New utility packages/engine/src/utils/assertSwiftShader.ts that navigates a Puppeteer Page to chrome://gpu, parses the GL_VENDOR / GL_RENDERER rows, and throws SwiftShaderAssertionError (with code: "BROWSER_GPU_NOT_SOFTWARE") if the active WebGL backend is anything other than SwiftShader. Re-exported from packages/engine/src/index.ts.

Why

Part of Phase 2 of the distributed rendering plan (determinism hardening), §5.2 (browserGpuMode row) and §9.3 (BROWSER_GPU_NOT_SOFTWARE typed failure).

Distributed renders launch Chrome with --use-gl=swiftshader --use-angle=swiftshader so every worker has the same pure-software GL implementation. Those flags are advisory: a misconfigured base image, a missing SwiftShader library, or a chrome://gpu blocklist override can silently downgrade to system GL — and distributed retries would no longer be byte-identical. The validator catches the downgrade post-launch, before any frame is rendered.

How

  • Exposed a default readWebGlVendorInfo(page) implementation that pulls structured browserBridge.gpuInfo_.graphics_info.basic_info rows from chrome://gpu (more stable across Chrome versions than the DOM layout of the GPU page).
  • Required both an exact vendor match (Google Inc. (Google)) and a case-insensitive swiftshader substring in the renderer string. Either alone is insufficient.
  • assertSwiftShader(page, readInfo?) accepts an injectable readInfo so unit tests can drive the assertion without spinning up real Chrome.
  • SwiftShaderAssertionError carries the offending vendor + renderer strings on the error for diagnostic surfacing.

Test plan

  • Unit tests added: assertSwiftShader.test.ts covers the canonical accept case, case/whitespace tolerance, the NVIDIA / Intel fallthrough rejects, empty-string failure, propagation of reader errors, and a third-party vendor + "SwiftShader compatible" renderer is still rejected.
  • No caller invokes the new utility yet — Phase 3's renderChunk() will run it after browser launch.
  • bun run --cwd packages/engine test — 9 new tests + all existing pass.
  • bun run --cwd packages/engine typecheck clean.
  • bunx oxlint + bunx oxfmt --check clean.

This is part of a stack of 10 PRs; this is PR 2 of 10. Stacked on top of #766.

🤖 Generated with Claude Code

miguel-heygen
miguel-heygen previously approved these changes May 13, 2026
Copy link
Copy Markdown
Collaborator

@miguel-heygen miguel-heygen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean utility with solid test coverage.

The dual-gate (exact vendor match + case-insensitive renderer substring) is the right call — vendor alone would false-positive on ANGLE backends that share the Google umbrella string. Injectable readInfo makes this fully testable without Chrome, and the SwiftShaderAssertionError.code field gives Temporal retry policies a clean classification target.

One small observation: readWebGlVendorInfo navigates to chrome://gpu with a 30s timeout, which is generous but fine for a post-launch one-shot. If this ever runs in a hot path, that timeout could be tightened — but for Phase 3's boot-time assertion it's appropriate.

Test matrix covers the important edge cases: empty strings, wrong vendor + right renderer, right vendor + wrong renderer, reader errors propagating unwrapped.

LGTM.

— Magi

vanceingalls
vanceingalls previously approved these changes May 13, 2026
Copy link
Copy Markdown
Collaborator

@vanceingalls vanceingalls left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strengths:

  • Pulling structured browserBridge.gpuInfo_.graphics_info.basic_info from chrome://gpu (assertSwiftShader.ts:78) instead of scraping the DOM is the right call — the DOM layout has drifted across Chrome versions and the structured payload has been stable.
  • Both-checks-required logic (assertSwiftShader.ts:101-102): exact vendor match "Google Inc. (Google)" AND case-insensitive swiftshader substring in renderer. The comment at assertSwiftShader.ts:46-50 correctly justifies why neither check alone is sufficient — a third-party renderer string with "SwiftShader compatible" would otherwise pass.
  • readInfo injectable on assertSwiftShader(page, readInfo?) (assertSwiftShader.ts:111) cleanly separates the "what we check" from the "how we read it," so the 9 unit tests can drive failure cases without a real Chrome.
  • SwiftShaderAssertionError.code is a typed literal (assertSwiftShader.ts:32) — Temporal/Step Functions retry policies key off it, matching the Phase-2 plan §9.3 contract.

Findings:

nit: assertSwiftShader.ts:78 page.goto("chrome://gpu", { waitUntil: "domcontentloaded", timeout: 30_000 })chrome://gpu populates browserBridge.gpuInfo_ asynchronously after DOMContentLoaded (Chrome posts the basic_info rows from a separate IPC). On a slow worker, the await page.evaluate(...) may run before basic_info is populated and return empty vendor/renderer strings — which the assertion already correctly treats as failure (good), but the error message would then claim "Chrome reported a non-SwiftShader WebGL backend" when in fact the info hadn't arrived. Consider polling browserBridge?.gpuInfo_?.graphics_info?.basic_info for non-empty rows up to a short timeout, or distinguishing the empty-string case in the error message. Caller will read the error in production logs and "vendor="" renderer=""" is harder to diagnose than "chrome://gpu info not populated within 5s." Not blocking — current behavior fails closed and tests pin the empty-string case.

nit: assertSwiftShader.ts:55 SWIFTSHADER_VENDOR_SIGNATURE = "Google Inc. (Google)" — Chrome occasionally emits this as "Google Inc." without the trailing (Google) on some platforms (older Linux builds). The literal pin will reject those. If hf supports old base images, worth a comment noting the pin is tight to current Chrome. If not, ignore.

Verdict: APPROVE
Reasoning: Validator is well-encapsulated, both-check-required logic is sound, tests cover the rejection matrix; the timing-on-page-load nit is a soft-edges concern that fails closed today.

— Vai

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>
@jrusso1020 jrusso1020 force-pushed the feat/engine-assert-swiftshader branch from e9e6359 to d8486a7 Compare May 13, 2026 04:36
@jrusso1020 jrusso1020 force-pushed the feat/engine-lockgop-for-chunk-concat branch from ee866de to 2d6372a Compare May 13, 2026 04:36
Base automatically changed from feat/engine-lockgop-for-chunk-concat to main May 13, 2026 06:54
@jrusso1020 jrusso1020 dismissed stale reviews from vanceingalls and miguel-heygen May 13, 2026 06:54

The base branch was changed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants