# 41 of 45 hand-written Windows renderer strings were wrong

> Chrome's ANGLE Direct3D11 renderer string carries the GPU's PCI device id. Our hand-typed Windows rows left it out, a string no real driver produces.

Source: https://mosaicbrowser.com/blog/hand-written-renderer-strings

## 41 of 45 hand-written Windows renderer strings were wrong

Chrome's ANGLE Direct3D11 renderer string carries the GPU's PCI device id. Our hand-typed Windows rows left it out, a string no real driver produces.

![41 of 45: Hand-typed renderer strings, wrong in one field](https://mosaicbrowser.com/covers/hand-written-renderer-strings.webp)

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](https://mosaicbrowser.com/blog/webgl-debug-renderer-info-fresh-context) 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.

Mosaic is a desktop app

Email yourself the link and open it on your computer.

[How it works: Machines that exist](https://mosaicbrowser.com/features/real-fingerprints)

## 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](https://mosaicbrowser.com/blog/pixelscan-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++](https://mosaicbrowser.com/blog/cpp-not-javascript) explains the second part). How a profile's machine is chosen is on the [real fingerprints page](https://mosaicbrowser.com/features/real-fingerprints).

## Questions

### What does the ANGLE Direct3D11 renderer string look like?

On Windows, Chrome reports something like ANGLE (NVIDIA, NVIDIA GeForce RTX 4060 (0x00002882) Direct3D11 vs_5_0 ps_5_0, D3D11). That is the vendor, the GPU name, the PCI device id in brackets, the backend, the shader models and the backend again.

### What does vs_5_0 ps_5_0 mean in a WebGL renderer?

Shader model 5.0 for vertex and pixel shaders, the level Direct3D 11 brought in. A GTX 1050 and an RTX 4090 print the same pair, so it tells a site that ANGLE picked its D3D11 backend and nothing about the card.

### Where does the hex number in the renderer string come from?

From the driver. It is the PCI device id, and 0x2882 is the chip inside an RTX 4060, so a site can check that the name and the number belong together. A wrong number gives a profile away as surely as a missing one.

### Why is the macOS renderer string easier to get right?

On a Mac the string is built from Metal's renderer name, for example ANGLE Metal Renderer: Apple M1 Pro, and it carries no device id. There is less in it to get wrong, which is why our macOS rows were the ones that were correct.

## Related

[Machines that exist: fingerprints drawn from real hardware](https://mosaicbrowser.com/features/real-fingerprints): Mosaic draws each profile's GPU, screen, CPU, memory and fonts from one real device, so the parts of a fingerprint agree with each other and with the host.

[UNMASKED_RENDERER is null until a page asks for it](https://mosaicbrowser.com/blog/webgl-debug-renderer-info-fresh-context): Chrome returns null for the unmasked WebGL renderer until the page calls getExtension, so a spoof that answers it earlier is a two-line tell.

[Pixelscan reads sixty WebGL fields, and the renderer string is one](https://mosaicbrowser.com/blog/pixelscan-sixty-webgl-fields): Pixelscan's WebGL fingerprint is a render hash plus about sixty driver fields beside the renderer string. A chip that never existed failed like a real one.

[Why we change fingerprints in C++, not JavaScript](https://mosaicbrowser.com/blog/cpp-not-javascript): A JavaScript spoof is one realm deep. An iframe read the real source of 33 of our patched functions; the same values changed in C++ agree everywhere.

[What is an anti-detect browser?](https://mosaicbrowser.com/guides/what-is-an-anti-detect-browser): An anti-detect browser gives each profile its own fingerprint, proxy and cookies, so sites see separate visitors. What it changes, how, and its limits.







## Mosaic, on your own computer

The browser this blog is about: its own Chromium, fingerprints from real machines and a proxy exit per profile. Free holds 3 profiles. Pro is $49 a month for any number.

Mosaic is a desktop app

Email yourself the link and open it on your computer.
