What it measures
Pixel output of the rasterisation pipeline. The signal captures how the GPU driver, browser engine, and OS combine to turn a fixed shader workload and draw call into pixels: anti-aliasing strategy, sub-pixel positioning, floating-point precision in the vertex stage, line endpoint rules, and any engine-level farbling applied to WebGL output.
Classified engine-bound because per-browser anti-fingerprinting modes alter the output: some engines apply deterministic per-origin farbling, others zero the readback buffer, and others may inject per-call noise. Including this signal in the hardware fingerprint would shatter cross-browser stability on those engines.
How it differs from the other WebGL collectors
| Signal | What it captures |
|---|---|
| webgl_gpu_identity | UNMASKED_VENDOR / RENDERER strings (hardware identity at the label level) |
| webgl_params | getParameter constants and extension lists (engine capability surface) |
| webgl_gpu_trace | GPU execution-unit timing pattern (timing-based fingerprint) |
| webgl_pixel_readback (this) | Rasterisation output pixels (driver / engine rendering decisions) |
Algorithm
The collector creates a small hidden WebGL context, compiles and links a minimal vertex+fragment shader pair that draws a deterministic radial pattern as line segments in a fixed colour, and renders the same scene multiple times. Each render allocates a fresh byte buffer and reads the framebuffer via gl.readPixels.
The byte arrays from the runs are mode-merged per byte position using the same algorithm canvas hashing uses (the byte that appears in at least two of the runs wins; ties fall back to the first run). The merged bytes are encoded as a hex string and hashed via xxHash64. The WebGL context is released via WEBGL_lose_context.loseContext() in a finally block.
Exact framebuffer dimensions, spoke count, fragment-shader colour constants, readback byte count, and run count are part of the internal recipe and not part of the public contract.
Confidence rules
| Confidence | Trigger |
|---|---|
| normal | WebGL context available, shader and program compiled and linked, all readPixels calls succeeded |
| absent | No WebGL context available (no GPU, blocked extension, software-rasterised headless without a usable rasteriser) |
| absent | Shader compile or program link failed |
| absent | Any of the readPixels calls threw |
Things worth knowing
- Tier 2 by classification, so it is not in the default getFingerprint() collection. Set tiers: [1, 2] to include it.
- The fragment colour is chosen as an internal recipe constant; what matters for entropy is that the GPU rasterises the pattern consistently across runs on the same device, not the specific colour.
- Pinned to run after webgl_params in the GPU collector ordering so the GPU is already warm by the time this collector runs.
- Headless browsers without a real GPU typically fall back to a software rasteriser whose pixel output is deterministic but distinctly different from any hardware GPU, making this signal a useful corroboration for headless detection.
- Real-device target: low single-digit milliseconds for all runs on desktop GPUs, tens of milliseconds on mobile.
Last reviewed 2026-06-04

