# WebGL MAX parameters fingerprint: limits are evidence

> WebGL MAX parameters, shader precision formats and the extension list come from the real driver. Our engine moves them only with data from a real device.

Source: https://mosaicbrowser.com/blog/webgl-capability-record

## A WebGL renderer string is a claim and the limits are the evidence

WebGL MAX parameters, shader precision formats and the extension list come from the real driver. Our engine moves them only with data from a real device.

![0108: The renderer is one string beside sixty answers, beside a screenshot: mosaic settings showing the installed Tesseract Chrome engine and its version](https://mosaicbrowser.com/covers/webgl-capability-record.webp)

WebGL MAX parameters are a fingerprint because the browser does not make them up. `MAX_TEXTURE_SIZE`, `MAX_VERTEX_UNIFORM_VECTORS` and the other limits a page reads through `getParameter` come from the GPU driver, along with the shader precision formats and the list of supported extensions. A renderer string names a GPU. Those values show which GPU is really there. So Tesseract Chrome, our engine, is built to move the limits only from data collected off the device being claimed, and never to fill them in with numbers that merely look plausible.

## What the WebGL MAX parameters fingerprint reads besides the renderer

A script that wants the GPU's capabilities does not have to draw anything. It creates a context and asks:

- `getParameter(pname)` for each limit: texture size, viewport size, uniform and varying vector counts, texture units, renderbuffer size, and so on
- `getShaderPrecisionFormat(shader, precision)` for the range and precision of each float and int type in vertex and fragment shaders
- `getSupportedExtensions()` for the list of extensions, whose names and order vary by driver and platform

Pixelscan's collector, which we decoded, puts about sixty of these fields beside the unmasked vendor and renderer in what it calls `webglMeta`. Our measurements of how it uses them are in [Pixelscan reads sixty WebGL fields](https://mosaicbrowser.com/blog/pixelscan-sixty-webgl-fields). The short version: a profile on our Mac claiming another Apple chip failed its consistency verdict, and so did `Apple M9 Ultra`, a chip that does not exist. The string moved. Sixty honest values next to it did not.

That is the general shape of the problem. A renderer string is a claim. The limits are evidence, because they are produced by the thing the claim is about.

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)

## What the patch does

The gpu-info patch we vendor moves `UNMASKED_VENDOR_WEBGL` and `UNMASKED_RENDERER_WEBGL` and stops there. Everything else answers for the real driver.

Patch `0108-webgl-capability-record` adds the rest of the record, in `webgl_rendering_context_base.cc`, behind three switches:

```
--fingerprint-webgl-params=3379:16384,34930:32
--fingerprint-webgl-extensions=EXT_blend_minmax,OES_texture_float
--fingerprint-webgl-precision=35633:36338:127:127:23

```

The params switch is a list of `pname:value` pairs, where 3379 is `MAX_TEXTURE_SIZE` and 34930 is `MAX_TEXTURE_IMAGE_UNITS` as decimal enums. The precision switch is five numbers per entry: shader type, precision type, range minimum, range maximum and precision bits, so `35633:36338` is a vertex shader's `HIGH_FLOAT`. The format is a flat list, not JSON, because a command line is a poor place for nested quoting, and a flat list fails loudly instead of half-parsing.

Three choices in it matter more than the parsing.

There is no fallback. If a `pname` is not in the list, `getParameter` asks the driver as stock Chrome does. There is no seeded random value, no table of "typical" limits and no default for a GPU we have not recorded. A limit that no real driver reports is a louder tell than the honest number, so the code has nowhere to put one.

An empty extension list and a missing switch are different answers. An empty list is a context with no extensions, which is a real thing a device can report. A missing switch means the engine asks the driver. The code keeps those two apart on purpose.

WebGL1 and WebGL2 are covered by one hook. `WebGL2RenderingContextBase` inherits these entry points, so the two contexts cannot disagree with each other, which would be a contradiction of its own.

With no switch passed, the engine's behaviour is byte-identical to stock, which is what our `check:stock` comparison against a real Chrome asserts.

## Why Mosaic does not send it

The patch is built and Mosaic does not use it. `mosaicArgs()`, the function that writes every engine switch for a launch, leaves all three out, and says why in its own comment: the switches "take real data, and we have none".

Sixty driver-derived fields for a device we do not own cannot be reasoned out. We know how badly hand-written GPU values go, because we wrote some. Renderer strings typed in by hand for Windows rows were wrong on 41 of 45 when we checked them against real machines, and that was one string per row. The record is about fifty times the surface. The real-device data we already sample carries one renderer string per GPU and nothing else, so the limits would have to be collected from real machines, one GPU and driver at a time.

Until they are, the honest answer is the host's own. A Mosaic profile on a Mac with an M-series chip claims that chip, and its WebGL limits, precision formats and extensions are the true ones for that chip. Everything else a site re-identifies a profile by (screen, timezone, language, canvas) is drawn per profile, and the list is on [real fingerprints](https://mosaicbrowser.com/features/real-fingerprints).

## Checking your own

Open a browser console on any page and run:

```
const gl = document.createElement('canvas').getContext('webgl2')
gl.getParameter(gl.MAX_TEXTURE_SIZE)
gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_FLOAT)
gl.getSupportedExtensions().length

```

Then compare the result with the renderer your browser claims. If you see `null` for the unmasked renderer before calling `getExtension('WEBGL_debug_renderer_info')`, that is correct Chrome behaviour, and [UNMASKED_RENDERER is null until a page asks for it](https://mosaicbrowser.com/blog/webgl-debug-renderer-info-fresh-context) explains why a spoof that answers earlier gives itself away. A tool that changes the renderer and leaves these four lines alone has changed the claim and kept the evidence.

## Questions

### What are WebGL MAX parameters?

They are the limits a WebGL context reports through getParameter, such as MAX_TEXTURE_SIZE, MAX_VERTEX_UNIFORM_VECTORS and MAX_TEXTURE_IMAGE_UNITS. The browser reads them from the GPU driver, so they differ by GPU and driver.

### Can a site fingerprint you from WebGL parameters?

Yes. The limits, the shader precision formats and the extension list together vary by GPU, driver and operating system, and a script reads all of them in a few milliseconds without drawing anything.

### Why not just change the WebGL renderer string?

Because the limits next to it still answer for the real GPU. A renderer naming one chip beside limits that belong to another is a contradiction a detector can check, and Pixelscan's collector reads both.

### Does Mosaic change WebGL MAX parameters?

The engine can, from a record collected off a real device. Mosaic does not send one, because we have no such records yet, so the limits are the true values of your own GPU.

## Related

[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.

[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.

[readPixels and toDataURL read a canvas from opposite ends](https://mosaicbrowser.com/blog/webgl-readpixels-vs-todataurl): readPixels starts at the bottom row and toDataURL at the top, so position-keyed WebGL noise disagreed with itself. Keyed on colour, 4096 of 4096 pixels agree.

[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.









## 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.
