Ask Chrome on a Windows PC for its unmasked WebGL renderer and you get a line like ANGLE (NVIDIA, NVIDIA GeForce RTX 4060 (0x00002882) Direct3D11 vs_5_0 ps_5_0, D3D11). That is the ANGLE Direct3D11 renderer string, and the hex number in the middle is the part we got wrong. When we checked our device catalogue against strings from real machines, 41 of the 45 Windows rows we had typed by hand left the (0x00002882) out.

What is in an ANGLE Direct3D11 renderer string

On Windows, a page's WebGL calls never reach the driver as OpenGL. ANGLE turns them into Direct3D 11 calls and builds the renderer string from what the driver says about itself. Read left to right, the example above is:

ANGLE (                      the translation layer
  NVIDIA,                    the vendor
  NVIDIA GeForce RTX 4060    the adapter name, as the driver spells it
  (0x00002882)               the PCI device id
  Direct3D11                 the backend ANGLE chose
  vs_5_0 ps_5_0,             vertex and pixel shader models
  D3D11)                     the backend again

A page reads it with one call, getParameter on UNMASKED_RENDERER_WEBGL from the WEBGL_debug_renderer_info extension, and the vendor beside it reads Google Inc. (NVIDIA). (That call can come back empty on a fresh context, which is its own trap; the null UNMASKED_RENDERER_WEBGL post has the cause.)

The device id is the part nobody remembers. It is not printed on the box or in a spec sheet's headline, and it is not something a careful person can work out from the card's name. ANGLE takes it from the driver. If you have not seen the string on a real machine, you do not have it.

Every profile is drawn from one real computer, not from separate listsIn Mosaic: Machines that exist. GPU, screen, CPU and fonts from one real device. Free holds 3 profiles. Pro is $49 a month for any number.

How it works: Machines that exist

How 41 of 45 rows went wrong the same way

Every renderer in our catalogue had been written once, by hand, from what a GPU "should" report. Nobody left the device id out on purpose. It just was not in anybody's head when they typed the row, so it was in none of the rows, apart from four.

No Windows driver reports an RTX 4060 without 0x00002882 after the name. A site that compares our string with the ones it collects from real visitors is not weighing a rare machine; it has never seen one like it. Every Windows profile drawn from those 41 rows went out with that gap, underneath the canvas-seed work and the checks that every frame of a page tells the same story.

Linux was worse, and wrong in three places at once:

  • The vendor read ANGLE (NVIDIA, …) where the real string says ANGLE (NVIDIA Corporation, …).
  • The OpenGL version read 4.6.0 where NVIDIA's driver reports 4.5.0.
  • The Mesa suffix, (radeonsi, navi22, LLVM 17.0.6, DRM 3.54, 6.8.0-45-generic), was copied from an older release. No current Mesa emits that triplet; the real string is (radeonsi renoir ACO).

macOS was the one platform that came out right. Metal's renderer string, ANGLE Metal Renderer: Apple M1 Pro and its relatives, carries no id and very little else, so there was nothing to forget.

Where our renderer strings come from now

scripts/sync-gpu-strings.mjs samples Apify's fingerprint network (a Bayesian model fitted to fingerprints collected from real browsers) and writes what it saw to observed-gpus.ts, which is committed with the code. check:devices runs in every build and refuses a catalogue row whose renderer is not in that table, so a hand-typed string now stops the build on the machine that typed it.

We do not trust the data as it arrives. The second most common Windows renderer in it is Microsoft Basic Render Driver, and the Linux side is full of SwiftShader. Those strings are real and honestly collected, from virtual machines, remote desktop sessions and headless bots. That is a large share of anybody's crawl traffic, and it is exactly the population a profile must not look like. So software rasterisers and hypervisor adapters are dropped, along with any string seen fewer than three times in forty thousand draws.

Sampling has its own failure. The model draws each field more or less on its own, so it will offer a two-thread M3 Pro, a Brave-branded Chrome 143 or a 1147x745 screen. Which parts go together in one laptop is still the catalogue's call, row by row. The table answers a narrower question, the one we had got wrong 41 times: how the driver spells its own name.

Two rules we keep for adding a machine

  1. Copy the strings off a real machine. On Windows that is Google Inc. (NVIDIA) as the vendor and Direct3D11 vs_5_0 ps_5_0, D3D11 at the end of the renderer; on macOS, ANGLE Metal Renderer: Apple M1 Pro and its siblings; on Linux, a Mesa build string with the kernel version in it. A spec sheet gives you none of these, and a plausible string no driver emits is a tell in itself.
  2. Write screens in CSS pixels at the display's default scaling. A 14-inch MacBook Pro's 3024x1964 panel reports 1512x982 at a device pixel ratio of 2. A 1920x1080 Windows laptop at 125%, the scaling Windows picks by default, reports 1536x864 at 1.25.

The resolutions had gone wrong the way the renderer strings had: somebody wrote down what the panel is, not what the browser reports. We already knew from the detection catalogue that guessing what a site "would" read costs weeks. Guessing what a driver "would" report cost the same.

What this means for a profile

A renderer string is read next to everything else WebGL exposes, and a detector compares them. Pixelscan alone reads sixty WebGL fields, so a correct name with the wrong capability record next to it is still a contradiction. That is why Mosaic draws the GPU, screen, threads and memory together from one catalogue row, and sets the values inside the engine rather than in page JavaScript (why we patch in C++ explains the second part). How a profile's machine is chosen is on the real fingerprints page.