From 79f4e0710698942962ab22ca871ee08650d4c368 Mon Sep 17 00:00:00 2001 From: puckey Date: Tue, 14 Apr 2026 17:01:52 +0200 Subject: [PATCH] fix: detect SwiftShader as BLOCKLISTED, not FALLBACK MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SwiftShader is Chrome's CPU-based WebGL fallback, used when no GPU driver is available (CI, Docker, headless browsers, broken drivers). Consumers need to treat it as tier 0 / BLOCKLISTED so they can fall back to a non-WebGL experience — cpu-rasterized WebGL is functional but unusable for anything graphically interesting. Two bugs combined to make this not work (see #120): 1. The blocklist entry was "google swiftshader", but cleanRenderer produces strings like "google, swiftshader ..." (comma between the vendor and product). Substring match failed. 2. Renderer strings can legitimately contain punctuation that doesn't appear in curated blocklist entries. Relying on exact substring match against the raw cleaned string is fragile. Fixes: - Shorten the blocklist entry from "google swiftshader" to just "swiftshader". SwiftShader is Google-only and always software — no false positives to worry about, and it catches future renderer string variants that might omit or reorder the vendor prefix. - Normalize the renderer (strip commas, collapse whitespace) before the blocklist substring check so punctuation-only mismatches no longer slip through. - Document `fps` field semantics in the README result-types section: it's populated for BENCHMARK and the Apple Silicon FALLBACK only, `undefined` for all other types. --- README.md | 2 ++ src/index.ts | 5 ++++- src/internal/blocklistedGPUS.ts | 2 +- test/index.test.ts | 15 ++++++++++++++- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d129f7ab..8b652952 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,8 @@ Based on the reported `fps` the GPU is then classified into either `tier: 1 (>= | `WEBGL_UNSUPPORTED` | No WebGL context could be created. `tier` is always 0. | | `SSR` | Running server-side — no `window`, detection skipped. | +The `fps` field is populated only for `BENCHMARK` results. All other `type` values leave `fps` as `undefined`. + ## API ```ts diff --git a/src/index.ts b/src/index.ts index 6dc89c07..45bb22ad 100644 --- a/src/index.ts +++ b/src/index.ts @@ -335,8 +335,11 @@ export const getGPUTier = async ({ aDis === bDis ? aFps - bFps : aDis - bDis ); if (!results.length) { + // Commas in cleaned renderers (e.g. "google, swiftshader ...") break + // substring matches against blocklist entries — strip them first. + const renderForBlocklist = renderer!.replace(/,/g, ''); const blocklistedModel: string | undefined = BLOCKLISTED_GPUS.find( - (blocklistedModel) => renderer!.includes(blocklistedModel) + (blocklistedModel) => renderForBlocklist.includes(blocklistedModel) ); if (blocklistedModel) return toResult(0, 'BLOCKLISTED', blocklistedModel); diff --git a/src/internal/blocklistedGPUS.ts b/src/internal/blocklistedGPUS.ts index da4d7176..9570283a 100644 --- a/src/internal/blocklistedGPUS.ts +++ b/src/internal/blocklistedGPUS.ts @@ -17,7 +17,7 @@ export const BLOCKLISTED_GPUS = [ 'geforce gt 130', 'geforce gt 330m', 'geforce gtx 285', - 'google swiftshader', + 'swiftshader', 'intel g41', 'intel g45', 'intel gma 4500mhd', diff --git a/test/index.test.ts b/test/index.test.ts index 78118c80..bb229339 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -325,6 +325,19 @@ for (const { input, expected } of [ }); }); +test('SwiftShader is detected as BLOCKLISTED tier 0', async () => { + // SwiftShader is Chrome's CPU-based WebGL fallback — no hardware + // acceleration, so consumers should treat it as unusable. + const result = await getTier({ + isMobile: false, + renderer: + 'ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)', + }); + expect(result.type).toBe('BLOCKLISTED'); + expect(result.tier).toBe(0); + expect(result.gpu).toBe('swiftshader'); +}); + test('Apple Silicon desktop Safari — tier-3 BENCHMARK with m-series label', async () => { // Safari returns 'Apple GPU' uniformly for M1–M5 with no chip-level // discrimination available from WebGL. Base M1 already hits the tier-3 @@ -403,7 +416,7 @@ test('Apple GPU on mobile does NOT take the desktop tier-3 path', async () => { }, { expected: { - gpu: 'google swiftshader', + gpu: 'swiftshader', }, input: { isMobile: false,