Signal · signalsVersion 5

webgl_image

A 32 by 32 pixel WebGL drawing of one shaded triangle, read back twice and reported as a digest.

webgl_image at a glance (from the library code, signalsVersion 5)
Component namewebgl_image
In the visitorIdyes
structuredId groupgpu (graphics)
Hashingalways a digest (first 128 bits of SHA-256)
Excluded underBrave (brave_farbling); Firefox resistFingerprinting and Tor Browser (firefox_rfp)
Inner timeout1000 ms
Added insignalsVersion 1
In plain words

WhiskerJS asks the graphics chip to draw one small coloured triangle with a tiny shader program and reads the resulting pixels back. Different GPUs and drivers compute colours and edges in slightly different ways, so the pixels differ between devices. The drawing is done twice: if the two results are not identical, the browser is adding noise and the value is left out.

What it reads

webgl_image uses the shared WebGL context of the collection: a detached 32 by 32 pixel canvas with a webgl (or experimental-webgl) context created with preserveDrawingBuffer: true and antialias: false. It compiles a vertex shader and a fragment shader, draws one triangle whose colour is computed per pixel from its position (with fract and sin), and reads all 32 by 32 RGBA pixels back with readPixels(). Those 4096 bytes are the value.

How the technique works

  1. Compile and link a minimal shader program, upload three vertices, clear the canvas to a fixed colour and draw the triangle.
  2. Read the pixel buffer with readPixels().
  3. Draw and read again.
  4. Compare the two buffers byte by byte. Any difference means noisy with ruleId render_mismatch.
  5. If they match, the bytes are hashed with SHA-256 and the first 128 bits are reported.

The shader’s procedural colour depends on floating-point precision in the GPU’s fragment pipeline, and the triangle’s edges depend on the rasteriser, which is where devices tend to differ.

Why it helps

Shader arithmetic and rasterisation are implemented by the GPU, its driver and the browser’s graphics layer (for example ANGLE on many Chromium builds). Different combinations can round colours or place edge pixels differently. The same combination normally gives the same bytes. It is part of the gpu group of the structuredId.

Stability

It can change after a GPU driver update, a browser update that changes the graphics back end, switching hardware acceleration on or off, moving a window to a GPU that the browser treats differently, or a browser mode or extension that adds readback noise. A change of only the gpu structuredId part is the typical pattern; see Comparing two results.

Browser support and what blocks it

  • ok wherever WebGL 1 is available, which includes current desktop and mobile versions of Chrome, Edge, Safari, Firefox and Samsung Internet, unless WebGL is turned off or blocked.
  • Brave: excluded / brave_farbling.
  • Firefox resistFingerprinting and Tor Browser: excluded / firefox_rfp.
  • Without WebGL (turned off, blocklisted GPU, some containers): unavailable / no_context. In the headless Firefox test run in a container used for these pages, all four WebGL components were unavailable / no_context.

Privacy

The image is a fixed test pattern; it contains nothing about the page or the user. It reveals graphics hardware and driver behaviour. It leaves the library only as a 32-character digest. WebGL needs no permission and WhiskerJS makes no request.

How WhiskerJS handles noise and failures

Status ruleId When
ok none two identical reads; value is a digest
noisy render_mismatch the two reads differed
unavailable no_document, no_api, no_context no document, no getContext, or no WebGL context
unavailable no_digest no Web Crypto to hash the bytes
blocked security_error, threw a WebGL call threw
excluded brave_farbling, firefox_rfp, caller_excluded exclusion table or your exclude list
timed_out timeout, aborted 1000 ms inner timeout, overall budget, abort

Example value

From a headless Chromium test browser in a container (software rendering through SwiftShader):

"webgl_image": { "status": "ok", "value": "fc51c63a9379589055c4503f7b7a2cb6", "hashed": true }

Questions and answers

Why is webgl_image unavailable with no_context?

The browser could not create a WebGL 1 context: WebGL is turned off, the GPU is blocklisted, or the environment has no graphics support at all. The other WebGL components report the same reason because they share one context.

Does WhiskerJS keep WebGL running after collect()?

No. One WebGL context is created per collection and released with the WEBGL_lose_context extension once every collector has settled.

Why is it excluded in Brave and Firefox resistFingerprinting?

Both randomise or block WebGL readback on purpose. Reading it would only add noise, so the exclusion table skips it and reports brave_farbling or firefox_rfp.