What it measures
The signal queries three categories of WebGL data: eight capability limit parameters (such as MAX_TEXTURE_SIZE and MAX_VIEWPORT_DIMS), six shader precision format results (vertex and fragment shader, low/medium/high float), and the full sorted list of supported extension names. Together these 15 elements form the hash input.
Although the raw GPU hardware determines physical capabilities, browser engines apply their own caps and ship different subsets of optional extensions. Chrome's ANGLE translation layer, Firefox's native OpenGL path, and Safari's Metal backend each report different parameter sets for the same GPU.
How it's collected
The collector creates a 1x1 WebGL context (WebGL2 preferred, WebGL1 fallback) using OffscreenCanvas if available, otherwise a hidden DOM canvas. It reads the 8 GL parameter constants via getParameter, queries 6 shader precision combinations via getShaderPrecisionFormat (vertex and fragment shader crossed with LOW_FLOAT, MEDIUM_FLOAT, and HIGH_FLOAT), and calls getSupportedExtensions. The extension list is sorted alphabetically and joined with commas into a single string before being appended to the parts array.
The 15 parts are joined with the pipe delimiter and hashed with xxHash64. The WEBGL_lose_context extension is called after collection on both canvas paths to release GPU resources. Cleanup errors are silently swallowed.
GL_PARAMS (8, in order):
MAX_TEXTURE_SIZE
MAX_RENDERBUFFER_SIZE
MAX_VIEWPORT_DIMS
MAX_VERTEX_ATTRIBS
MAX_VARYING_VECTORS
MAX_FRAGMENT_UNIFORM_VECTORS
ALIASED_LINE_WIDTH_RANGE
ALIASED_POINT_SIZE_RANGE
SHADER_PRECISION (6, in order):
VERTEX_SHADER x [LOW_FLOAT, MEDIUM_FLOAT, HIGH_FLOAT]
FRAGMENT_SHADER x [LOW_FLOAT, MEDIUM_FLOAT, HIGH_FLOAT]
Each: rangeMin,rangeMax,precision
EXTENSIONS (1):
getSupportedExtensions() sorted alphabetically, joined with ','
concatenated = parts.join('|') // 15 parts, 14 pipe separators
hash = xxHash64(concatenated) -> 16-char hexThe 15-part hash construction for webgl_params
Confidence rules
| Confidence | Trigger |
|---|---|
| normal | WebGL context obtained and data collected |
| absent | No WebGL context available, or top-level catch fires |
Why engine-bound
Browser engines apply their own policy caps on many WebGL parameters independent of the GPU driver. MAX_TEXTURE_SIZE can be limited by policy, extension availability varies between Blink and WebKit, and shader precision format values can differ between Chrome's ANGLE translation layer and Firefox's native OpenGL path. The extension list is especially engine-discriminating because browsers ship different subsets of optional WebGL extensions.
Because the reported values are shaped by the engine, not exclusively by the GPU, the hash differs between Chrome and Firefox on identical hardware. This makes webgl_params a strong per-browser discriminator and a complement to the hardware-bound webgl_gpu_identity signal.
Things worth knowing
- Float32Array and Int32Array parameter values are serialised as Array.from(value).join(',') before joining into the parts array.
- The value field returned to callers contains paramCount and extensionCount as integers — not the raw parameter data.
- Collection is synchronous (no timeouts, no yield points). Typical timing is 5-20 ms.
- WEBGL_debug_renderer_info is not used by this collector; that extension belongs exclusively to webgl_gpu_identity.

