Files
coffeegrind123 c2817c1cae fix(coherence): let macOS draw Intel Mac GPUs, checked as machines
coherence rejected "Intel(R) HD Graphics 400" and "Radeon R9 200
Series" on macOS as a Braswell Atom IGP and a desktop PC card. They are
Firefox's sanitized buckets, not devices: SanitizeRenderer
(FIREFOX_152_0_4_RELEASE) maps an Intel Mac's UHD 630 to the first and
a Radeon Pro 5300M to the second, per Firefox's own TestCiMac and
TestMacAmd. fpgen's pinned model records both from Firefox on macOS at
1.14% each. The rule kept every generated Mac off them, removed 24
real macOS presets in #779 (restored here, 9 + 15), and dropped either
GPU from a caller's Mac preset. ANGLE and llvmpipe stay rejected:
Firefox 152 has no ANGLE-on-Metal path (Bug 2046027 came later).

Intel Macs are now checked as machines instead (intel-mac-hardware),
for every non-Apple GPU on macOS:

- a core count some Intel Mac with that GPU reports. Firefox reports
  physical cores where kern.tcsm_available is set and logical ones
  otherwise, so either counts: 2-8 physical / 4-16 logical for the
  IGP, up to the 2019 Mac Pro for a discrete GPU;
- a screen that is not a notched MacBook's or the 24" iMac's. 45.5% of
  fpgen's Firefox macOS screens are one, and the draw already put an
  Intel or AMD bucket behind 4.9% of them.

The WebGL draw applies the same check, given the core count. The preset
GPU drop moves next to the WebGL draw, after the host core count and
the display clamp: before, a preset's GPU was judged against cores the
launch then replaced. The TypeScript launcher gets the same changes,
and the goldens cover the new check and the narrowed draws.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit d6e2adcb6c)
2026-09-27 20:09:03 +00:00

395 lines
12 KiB
TypeScript

/**
* WebGL: ports of pythonlib/tests/test_webgl.py and
* test_webgl_screen_consistency.py. The seeded draws themselves are pinned
* against Python in identity-golden.test.ts. (test_no_coherent_gpu_raises is
* not ported: it needs fpgen's trace replaced, which an ES module cannot do.)
*/
import * as fs from "node:fs";
import * as path from "node:path";
import { fileURLToPath } from "node:url";
import { describe, expect, it } from "vitest";
import { gpuFitsOs } from "../src/coherence.js";
import {
generateContextFingerprint,
gpuScreenIsPlausible,
isSoftwareRenderer,
MODERN_SCREEN_FLOOR,
raiseScreenToModernFloor,
rendererBucket,
} from "../src/fingerprints.js";
import { orjsonDumps } from "../src/pycompat.js";
import { sampleWebglForScreen, toConfig, webglForGpu } from "../src/webgl.js";
import { MODEL } from "./fpgen-setup.js";
import { prerequisite } from "./prereq.js";
const OSES = ["win", "mac", "lin"] as const;
const SEEDS = Array.from({ length: 300 }, (_, i) => i);
const GTX_980_LINUX = [
"NVIDIA Corporation",
"NVIDIA GeForce GTX 980, or similar",
] as const;
const BASIC_RENDER_DRIVER = [
"Google Inc. (Microsoft)",
"ANGLE (Microsoft, Microsoft Basic Render Driver Direct3D11 vs_5_0 ps_5_0), or similar",
] as const;
// Limits WebGL1 and WebGL2 read from the same device.
const SHARED_LIMITS = [
"3379",
"3386",
"34024",
"34076",
"34921",
"34930",
"35660",
"35661",
"36347",
"36348",
"36349",
];
const GTX_980_FIXTURE = path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
"../../pythonlib/tests/data/webgl-gtx980-linux.json",
);
describe.skipIf(!MODEL.ok)("webgl (test_webgl.py)", () => {
it.skipIf(
!prerequisite(
"pythonlib-source",
fs.existsSync(GTX_980_FIXTURE),
GTX_980_FIXTURE,
),
)("the converter reproduces the recorded device", () => {
// What the retired webgl_data.db gave for this GPU. Parameters are
// compared where the old row had a value, less the UNMASKED_* strings.
const old = JSON.parse(fs.readFileSync(GTX_980_FIXTURE, "utf-8"));
const fresh = JSON.parse(
orjsonDumps(webglForGpu("lin", ...GTX_980_LINUX, 0), false),
);
expect(Object.keys(fresh).sort()).toEqual(Object.keys(old).sort());
for (const key of Object.keys(old)) {
if (key.endsWith(":parameters")) {
for (const [pname, value] of Object.entries(old[key])) {
if (value === null || pname === "37445" || pname === "37446")
continue;
expect(fresh[key][pname], `${key} ${pname}`).toEqual(value);
}
} else {
expect(fresh[key], key).toEqual(old[key]);
}
}
});
it("the same seed draws the same device", () => {
for (const os of OSES) {
for (const seed of [0, 1, 12345]) {
expect(sampleWebglForScreen(os, 1920, 1080, seed)).toEqual(
sampleWebglForScreen(os, 1920, 1080, seed),
);
}
}
const gpu = ["AMD", "Radeon R9 200 Series, or similar"] as const;
expect(webglForGpu("lin", ...gpu, 7)).toEqual(
webglForGpu("lin", ...gpu, 7),
);
});
it("the seed chooses among a GPU's recorded devices", () => {
const drawn = new Set(
Array.from({ length: 40 }, (_, s) =>
orjsonDumps(
webglForGpu("lin", "AMD", "Radeon R9 200 Series, or similar", s),
false,
),
),
);
expect(drawn.size).toBeGreaterThan(1);
});
it.each(
OSES,
)("a synthetic %s draw is a hardware GPU the OS reports", (os) => {
const renderers = new Set(
SEEDS.map(
(s) => sampleWebglForScreen(os, 1920, 1080, s)["webGl:renderer"],
),
);
for (const renderer of renderers) {
expect(isSoftwareRenderer(renderer), renderer).toBe(false);
expect(renderer).not.toBe("Mozilla");
expect(gpuFitsOs(renderer, os), renderer).toBe(true);
}
// A single GPU per OS is its own tell.
expect(renderers.size).toBeGreaterThanOrEqual(2);
});
it("every Intel Mac bucket is drawn", () => {
// fpgen model-2/2026 records each at 1.1% of Firefox on macOS.
const drawn = new Set(
SEEDS.map(
(s) => sampleWebglForScreen("mac", 2560, 1440, s)["webGl:renderer"],
),
);
expect(drawn).toContain("Intel(R) HD Graphics 400, or similar");
expect(drawn).toContain("Radeon R9 200 Series, or similar");
});
it("no Intel Mac behind a notched panel", () => {
for (const seed of SEEDS) {
const renderer = sampleWebglForScreen("mac", 1470, 956, seed, 8)[
"webGl:renderer"
];
expect(renderer, `seed ${seed}`).toContain("Apple M");
}
});
it("no Intel IGP beside a core count no Intel Mac reports", () => {
for (const seed of SEEDS) {
const renderer = sampleWebglForScreen("mac", 2560, 1440, seed, 20)[
"webGl:renderer"
];
expect(renderer, `seed ${seed}`).not.toContain("Intel");
}
});
it.each(OSES)("a %s netbook screen never draws a discrete GPU", (os) => {
for (const seed of SEEDS) {
const renderer = sampleWebglForScreen(os, 1024, 600, seed)[
"webGl:renderer"
];
expect(gpuScreenIsPlausible(renderer, 1024, 600), renderer).toBe(true);
}
});
it.each(OSES)("%s WebGL2 comes from the same device as WebGL1", (os) => {
for (const seed of SEEDS) {
const config = sampleWebglForScreen(os, 1920, 1080, seed);
for (const pname of SHARED_LIMITS) {
expect(
config["webGl:parameters"][pname],
`seed ${seed} ${pname}`,
).toEqual(config["webGl2:parameters"][pname]);
}
}
});
it("the GPU is pinned by vendor and renderer", () => {
// "Mesa" and "AMD" both report this renderer on Linux.
for (let seed = 0; seed < 40; seed++) {
expect(
webglForGpu("lin", "Mesa", "Radeon HD 3200 Graphics, or similar", seed)[
"webGl:vendor"
],
).toBe("Mesa");
}
});
it("a device without WebGL2 sets no webGl2 keys", () => {
const config = webglForGpu("win", ...BASIC_RENDER_DRIVER, 0);
expect(config.webGl2Enabled).toBe(false);
expect(Object.keys(config).some((key) => key.startsWith("webGl2:"))).toBe(
false,
);
});
it("a GPU fpgen has never seen raises", () => {
expect(() =>
webglForGpu("win", "Apple", "Apple M1, or similar", 0),
).toThrow(/No recorded WebGL data/);
});
const extensions = (os: string, key: string) =>
Array.from(
{ length: 100 },
(_, s) =>
new Set<string>(sampleWebglForScreen(os, 1920, 1080, s)[key] ?? []),
);
it("Windows keeps OVR_multiview2 on WebGL2", () => {
expect(
extensions("win", "webGl2:supportedExtensions").some((e) =>
e.has("OVR_multiview2"),
),
).toBe(true);
});
it("Linux filters OVR_multiview2", () => {
expect(
extensions("lin", "webGl2:supportedExtensions").some((e) =>
e.has("OVR_multiview2"),
),
).toBe(false);
});
});
it("draft extensions are filtered on every OS", () => {
const recorded = {
vendor: "v",
renderer: "r",
contextAttributes: {},
params: {},
shaderPrecisionFormats: [],
supportedExtensions: [
"ANGLE_instanced_arrays",
"WEBGL_multi_draw",
"WEBGL_compressed_texture_etc1",
],
};
const webgl2 = {
...recorded,
supportedExtensions: [
"EXT_texture_norm16",
"WEBGL_clip_cull_distance",
"OVR_multiview2",
],
};
for (const os of OSES) {
const config = toConfig(recorded, webgl2, os);
expect(config["webGl:supportedExtensions"]).toEqual([
"ANGLE_instanced_arrays",
]);
expect(config["webGl2:supportedExtensions"]).toEqual(
os === "win" ? ["OVR_multiview2"] : [],
);
}
});
// The three spellings Gecko emits for one discrete-NVIDIA bucket.
const NV_ANGLE =
"ANGLE (NVIDIA, NVIDIA GeForce GTX 980 Direct3D11 vs_5_0 ps_5_0), or similar";
const NV_PCIE = "NVIDIA GeForce GTX 980/PCIe/SSE2";
const NV_NOUVEAU = "GeForce GTX 980, or similar";
const AMD_IGP =
"ANGLE (AMD, Radeon HD 3200 Graphics Direct3D11 vs_5_0 ps_5_0), or similar";
const INTEL =
"ANGLE (Intel, Intel(R) HD Graphics Direct3D11 vs_5_0 ps_5_0), or similar";
const APPLE = "Apple M1, or similar";
const LLVMPIPE = "llvmpipe, or similar";
describe("WebGL <-> screen coherence (test_webgl_screen_consistency.py)", () => {
it("every spelling of one GPU reduces to one bucket", () => {
for (const r of [NV_ANGLE, NV_PCIE, NV_NOUVEAU]) {
expect(rendererBucket(r)).toBe("GeForce GTX 980");
}
});
it("the ANGLE vendor field does not decide the bucket", () => {
expect(rendererBucket(AMD_IGP)).toBe("Radeon HD 3200 Graphics");
expect(rendererBucket("Radeon HD 3200 Graphics, or similar")).toBe(
"Radeon HD 3200 Graphics",
);
});
it("the ANGLE Vulkan form is unwrapped", () => {
expect(rendererBucket("ANGLE (Samsung Xclipse 920) on Vulkan")).toBe(
"Samsung Xclipse 920",
);
});
it("a discrete GPU is rejected on netbook panels only", () => {
for (const r of [NV_ANGLE, NV_PCIE, NV_NOUVEAU]) {
expect(gpuScreenIsPlausible(r, 1024, 600)).toBe(false);
}
for (const [w, h] of [
[1024, 768],
[1280, 720],
[1280, 800],
[1366, 768],
[1920, 1080],
]) {
expect(gpuScreenIsPlausible(NV_ANGLE, w, h)).toBe(true);
}
for (const [w, h] of [
[1024, 600],
[800, 480],
[1024, 576],
]) {
expect(gpuScreenIsPlausible(NV_ANGLE, w, h)).toBe(false);
}
});
it("integrated parts, Apple silicon, raw model names and missing values are unconstrained", () => {
expect(gpuScreenIsPlausible(INTEL, 1024, 600)).toBe(true);
expect(gpuScreenIsPlausible(AMD_IGP, 1024, 600)).toBe(true);
expect(gpuScreenIsPlausible(APPLE, 1280, 800)).toBe(true);
expect(
gpuScreenIsPlausible(
"ANGLE (NVIDIA, NVIDIA GeForce RTX 3070 Direct3D11 vs_5_0 ps_5_0)",
1024,
600,
),
).toBe(true);
expect(gpuScreenIsPlausible(null, 1920, 1080)).toBe(true);
expect(gpuScreenIsPlausible(NV_ANGLE, null, null)).toBe(true);
});
it("software renderers are recognized and unconstrained", () => {
for (const r of [
LLVMPIPE,
"ANGLE (Microsoft, Microsoft Basic Render Driver Direct3D11 vs_5_0 ps_5_0)",
"ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero)), SwiftShader driver)",
"Generic Renderer",
]) {
expect(isSoftwareRenderer(r)).toBe(true);
expect(gpuScreenIsPlausible(r, 1024, 600)).toBe(true);
}
for (const r of [NV_ANGLE, INTEL, APPLE, AMD_IGP]) {
expect(isSoftwareRenderer(r)).toBe(false);
}
});
it.skipIf(!MODEL.ok)("the sampled GPU is coherent with the screen", () => {
for (const os of ["win", "mac", "lin"]) {
for (let seed = 0; seed < 25; seed++) {
const fp = sampleWebglForScreen(os, 1280, 800, seed);
expect(gpuScreenIsPlausible(fp["webGl:renderer"], 1280, 800)).toBe(
true,
);
}
}
});
it("the screen floor lifts netbook geometry and keeps the taskbar gap", () => {
const config: Record<string, number> = {
"screen.width": 1024,
"screen.height": 600,
"screen.availWidth": 1024,
"screen.availHeight": 560,
};
raiseScreenToModernFloor(config);
expect([config["screen.width"], config["screen.height"]]).toEqual([
...MODERN_SCREEN_FLOOR,
]);
expect(config["screen.height"] - config["screen.availHeight"]).toBe(40);
expect(config["screen.width"] - config["screen.availWidth"]).toBe(0);
});
it("the screen floor leaves an adequate screen, or no screen, alone", () => {
const config = {
"screen.width": 1920,
"screen.height": 1080,
"screen.availWidth": 1920,
"screen.availHeight": 1040,
};
const before = { ...config };
raiseScreenToModernFloor(config);
expect(config).toEqual(before);
const empty = {};
raiseScreenToModernFloor(empty);
expect(empty).toEqual({});
});
it.skipIf(!MODEL.ok)("context fingerprints get the same treatment", () => {
for (const os of ["windows", "macos", "linux"]) {
for (let i = 0; i < 5; i++) {
const { config } = generateContextFingerprint({ os });
const r = config["webGl:renderer"];
const w = config["screen.width"];
const h = config["screen.height"];
expect(r && w && h).toBeTruthy();
expect(gpuScreenIsPlausible(r, w, h)).toBe(true);
expect(w * h).toBeGreaterThan(1024 * 600);
}
}
});
});