Signal

WebGL GPU identity

Identifies the physical GPU by reading WEBGL_debug_renderer_info vendor and renderer strings, normalising them, and hashing a coarse family bucket that is stable across Chrome, Safari, and Firefox on the same device.

Tier 1 hardware src/signals/webgl.ts

What it measures

The signal reads the unmasked GPU vendor and renderer strings exposed by the WEBGL_debug_renderer_info WebGL extension. These raw strings are normalised to remove ANGLE wrappers, trademark symbols, and trailing suffixes, then bucketed into a coarse GPU family (such as apple silicon, intel integrated, nvidia, or amd). The hash is computed from the family bucket alone, not from the granular normalised string.

The granular vendor and renderer strings are preserved in value.normalised for diagnostics and are available to callers, but they do not influence the hash. Per-browser entropy from GPU caps and extension lists is captured separately by the engine-bound webgl_params signal.

Why a coarse family bucket

The three browser engines report the same GPU at different levels of granularity on Apple Silicon Macs. Chromium reports 'ANGLE (Apple, Apple M2 Pro, OpenGL 4.1)', Safari reports 'Apple GPU', and Firefox reports 'Apple M1' regardless of the actual chip. Hashing the granular renderer string produces three different hashes on the same machine. Hashing the coarse family bucket — apple silicon — produces one hash across all three engines, which is required for hardwareFingerprint to be cross-browser stable.

The M2-vs-M3-vs-M4 granularity within Chromium is sacrificed for stability, but that granularity was never recoverable cross-browser anyway. It remains available in value.normalised.renderer for callers that run only within Chromium.

GPU family buckets

BucketMatches
apple siliconApple M-series chips, Safari's generic 'Apple GPU', Firefox's generic 'Apple M1'
apple legacyPre-M1 Apple GPUs (older iOS / Intel Mac PowerVR)
intel integratedIntel HD, UHD, Iris, Iris Pro, Iris Plus, Iris Xe
nvidiaAny NVIDIA card — GeForce, RTX, GTX, Quadro, Tesla
amdAMD Radeon and RX series
adrenoQualcomm Adreno (mobile)
maliARM Mali (mobile)
powervrImagination PowerVR (mobile)
swiftshaderChromium CPU fallback renderer (also caught by headless_gpu_detected flag)
llvmpipeMesa software fallback (also caught by headless_gpu_detected flag)
unknownEmpty vendor and renderer strings
<renderer-string>Any unclassified GPU — falls back to the normalised renderer string

Confidence rules

ConfidenceTrigger
normalWEBGL_debug_renderer_info extension was available and data was collected
absentWebGL context could not be created, or any unhandled exception

Why hardware-bound

The GPU installed in a device does not change between browser sessions or between browser engines. While the string representation of that GPU differs by engine — Chromium wraps it in an ANGLE prefix, Safari reports a generic Apple GPU, Firefox may append 'or similar' — the normalisation pipeline canonicalises all of these to the same lowercase family bucket. The underlying hardware is what the bucket describes, making the hash stable across browsers on the same machine.

Context creation is fast (sub-millisecond) because only a 1x1 surface is needed to query the extension. The context is explicitly released via WEBGL_lose_context.loseContext() immediately after data collection to free GPU resources before garbage collection.

Things worth knowing

  • getWebGLData() is not cached at module level. Each invocation creates a fresh context to prevent a concurrent getFingerprint() call from observing skewed timeMs values.
  • When WEBGL_debug_renderer_info is absent but a WebGL context was obtained, the raw strings are empty and the collector still returns normal with a hash of the empty-string family bucket.
  • The normaliseGpuRenderer function strips ANGLE wrappers, 'or similar' suffixes, trailing 'GPU' suffixes, and trademark symbols (R) and (TM) before lowercasing.
  • Total collection time is typically under 5 ms. No timeouts or yield points are used.