Signal

WebGL pixel readback

Renders a deterministic radial pattern via vertex and fragment shaders, reads pixels via gl.readPixels, and hashes the mode-merged bytes across multiple runs. Captures GPU rasterisation behaviour the other WebGL collectors do not see.

Reviewed

Tier 2 engine

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

SignalWhat it captures
webgl_gpu_identityUNMASKED_VENDOR / RENDERER strings (hardware identity at the label level)
webgl_paramsgetParameter constants and extension lists (engine capability surface)
webgl_gpu_traceGPU 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

ConfidenceTrigger
normalWebGL context available, shader and program compiled and linked, all readPixels calls succeeded
absentNo WebGL context available (no GPU, blocked extension, software-rasterised headless without a usable rasteriser)
absentShader compile or program link failed
absentAny 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