Skip to content

Commit 03b681a

Browse files
authored
Merge pull request #159 from pmndrs/feat/apple-silicon-desktop-tier
feat: conservative tier-3 fallback for Apple Silicon desktop Safari
2 parents 4234e83 + b405534 commit 03b681a

5 files changed

Lines changed: 77 additions & 3 deletions

File tree

index.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
<html>
33
<head>
44
<meta charset="utf-8" />
5+
<script>
6+
// During `pnpm dev` the static server runs on :8000; redirect to the
7+
// local-dist page so refreshes pick up uncommitted source changes.
8+
if (location.port === '8000') location.replace('./local.html');
9+
</script>
510
<title>detect-gpu</title>
611
<style>
712
body {

local.html

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
<title>detect-gpu (local)</title>
6+
<style>
7+
body {
8+
font-family: system-ui, sans-serif;
9+
font-size: 1.25em;
10+
padding: 2rem;
11+
}
12+
</style>
13+
</head>
14+
<body>
15+
<pre id="root">Detecting GPU…</pre>
16+
<script type="module">
17+
import { getGPUTier } from './dist/index.mjs';
18+
const result = await getGPUTier({ benchmarksURL: './dist/benchmarks' });
19+
document.getElementById('root').textContent = JSON.stringify(
20+
result,
21+
null,
22+
2
23+
);
24+
</script>
25+
</body>
26+
</html>

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@
5858
"test:watch": "vitest",
5959
"test:coverage": "vitest run --coverage",
6060
"build": "tsdown",
61+
"dev": "pnpm run --parallel /^dev:/",
62+
"dev:build": "tsdown --watch",
63+
"dev:serve": "echo 'open http://localhost:8000/local.html' && python3 -m http.server 8000",
6164
"update-benchmarks": "node ./scripts/update_benchmarks.ts"
6265
},
6366
"devDependencies": {

src/index.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -316,9 +316,22 @@ export const getGPUTier = async ({
316316
const blocklistedModel: string | undefined = BLOCKLISTED_GPUS.find(
317317
(blocklistedModel) => renderer!.includes(blocklistedModel)
318318
);
319-
return blocklistedModel
320-
? toResult(0, 'BLOCKLISTED', blocklistedModel)
321-
: toResult(1, 'FALLBACK', `${renderer} (${rawRenderer})`);
319+
if (blocklistedModel) return toResult(0, 'BLOCKLISTED', blocklistedModel);
320+
321+
// Apple Silicon on desktop Safari: the renderer string is the generic
322+
// "Apple GPU" and Safari reports identical WebGL capabilities across
323+
// M1–M5 (verified empirically on M1 Max / M2 / M4), so no web-facing
324+
// signal can identify the specific chip. The floor of the M-series
325+
// (base M1) sustains 60fps in our benchmark scene, which maps to the
326+
// top bin under the default `desktopTiers: [0, 15, 30, 60]` — so
327+
// tier 3 is a true lower bound for every Apple Silicon Mac, not a
328+
// guess. iPhone/iPad take an earlier path (deobfuscateAppleGPU fans
329+
// out to chip candidates), so this only fires on desktop.
330+
if (!isMobile && renderer === 'apple gpu') {
331+
return toResult(3, 'FALLBACK', 'apple gpu', 60);
332+
}
333+
334+
return toResult(1, 'FALLBACK', `${renderer} (${rawRenderer})`);
322335
}
323336

324337
const [, fps, model, device] = results[0];

test/index.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,33 @@ for (const { input, expected } of [
324324
});
325325
});
326326

327+
test('Apple Silicon desktop Safari — conservative tier-3 FALLBACK', async () => {
328+
// Safari returns 'Apple GPU' uniformly for M1–M5 with no chip-level
329+
// discrimination available from WebGL. Since base M1 already hits the
330+
// tier-3 fps floor in our benchmarks, the conservative guess is tier 3.
331+
const result = await getTier({
332+
isMobile: false,
333+
renderer: 'Apple GPU',
334+
});
335+
expectGPUResults(
336+
{ type: 'FALLBACK', tier: 3, gpu: 'apple gpu', isMobile: false },
337+
result
338+
);
339+
expect(result.fps).toBe(60);
340+
});
341+
342+
test('Apple GPU on mobile does NOT take the desktop tier-3 path', async () => {
343+
// iPhone/iPad route through deobfuscateAppleGPU and resolve to specific
344+
// chip benchmarks. The desktop tier-3 fallback must not fire on mobile,
345+
// even with the same masked renderer string.
346+
const result = await getTier({
347+
isMobile: true,
348+
renderer: 'Apple GPU',
349+
});
350+
expect(result.tier).not.toBe(3);
351+
expect(result.gpu).not.toBe('apple gpu');
352+
});
353+
327354
// expect BLOCKLISTED results:
328355
[
329356
{

0 commit comments

Comments
 (0)