| name | webgpu-core-cross-browser |
| description | Use when writing WebGPU code that must run across Chrome, Safari, and Firefox, or when a feature works in one browser but not another. Prevents apps breaking on Safari or Firefox from Chrome-only assumptions. Covers Chrome / Safari / Firefox WebGPU support differences, feature-detection patterns, version gating, wgslLanguageFeatures, and getPreferredCanvasFormat. Keywords: cross-browser, Chrome, Safari, Firefox, WebGPU support, feature detection, getPreferredCanvasFormat, wgslLanguageFeatures, works in Chrome not Safari, WebGPU not working in Firefox, browser compatibility.
|
| license | MIT |
| compatibility | Designed for Claude Code. Requires WebGPU 1.0-stable. |
| metadata | {"author":"OpenAEC-Foundation","version":"1.0"} |
WebGPU Cross-Browser Compatibility
Write WebGPU code that runs on Chrome, Safari, and Firefox by feature-detecting
every optional capability instead of assuming the Chrome feature set everywhere.
Quick Reference
WebGPU 1.0-stable baseline: Chrome 113+, Safari 26+, Firefox 141+.
Browser support matrix (verified against caniuse and the Chrome WebGPU blog,
2026-05):
| Browser | WebGPU support | Engine | Notes |
|---|
| Chrome / Edge desktop | 113+ | Dawn | Leads feature rollout. Full core support. |
| Chrome for Android | 121+ | Dawn | Broadly available on 148+. |
| Safari / WebKit desktop | 26.0+ | WebKit | Shipped mid-2025. 26.0-26.5 reported as partial: optional features and formats lag Chrome. |
| Safari iOS | 26.0+ | WebKit | Full support on iOS 26.0+. |
| Firefox desktop | 141+ | wgpu | Shipped enabled on 141 on Windows first. caniuse reports it disabled by default through 153 on other desktop platforms. |
| Firefox for Android | not enabled | wgpu | Disabled by default through 150. |
The masterplan label "Safari 18" was wrong: WebKit shipped WebGPU in the Safari
26 / iOS 26 family. NEVER cite "Safari 18" as the WebGPU baseline.
Dawn (Chrome) optional-feature timeline. Safari and Firefox lag these dates and
may never expose some entries:
| Capability | Chrome version |
|---|
| shader-f16 | 120 |
| multi-draw-indirect (experimental) | 131 |
| clip distances | 131 |
| subgroups | 134 |
| subgroup_id / num_subgroups | 144 |
| subgroup_uniformity | 145 |
Three feature-detection surfaces (full signatures in references/methods.md):
| Check | What it gates |
|---|
adapter.features.has(name) | Whether a GPUFeatureName may go in requiredFeatures. |
device.features.has(name) | What the negotiated device actually enabled. |
navigator.gpu.wgslLanguageFeatures.has(name) | Optional WGSL language features. |
navigator.gpu.getPreferredCanvasFormat() | The portable canvas texture format. |
Decision Tree
Code uses a capability beyond the WebGPU 1.0 core set?
├── It is a host-side optional FEATURE (shader-f16, timestamp-query,
│ texture-compression-bc, subgroups, ...)?
│ ├── adapter.features.has("<name>") === true?
│ │ └── Add "<name>" to requiredFeatures. After requestDevice,
│ │ confirm device.features.has("<name>").
│ └── adapter.features.has("<name>") === false?
│ └── OMIT it. Run a degraded code path that does not need it.
│
├── It is an optional WGSL language feature?
│ ├── navigator.gpu.wgslLanguageFeatures.has("<name>") === true?
│ │ └── Emit shader code that uses it.
│ └── false?
│ └── Emit the baseline-WGSL shader variant instead.
│
├── It is the canvas texture format?
│ └── ALWAYS navigator.gpu.getPreferredCanvasFormat(). NEVER hard-code.
│
├── It needs a non-default LIMIT?
│ └── Read adapter.limits.<name>. Request via requiredLimits only if the
│ adapter reports a good-enough value; otherwise reduce the workload.
│
└── It is a featureLevel tier?
└── Default "core". Use "compatibility" only to deliberately target
OpenGL ES 3.1 / D3D11-class hardware (reduced limits and features).
Core Patterns
Pattern 1: ALWAYS feature-detect on the adapter before requiredFeatures
requestDevice rejects when requiredFeatures names a feature the adapter does
not list. Chrome exposes a feature; Safari or Firefox may not. ALWAYS filter the
wanted features against adapter.features.has so the same code degrades instead
of breaking.
const adapter = await navigator.gpu.requestAdapter();
if (!adapter) throw new Error("No WebGPU adapter");
const wanted = ["timestamp-query", "shader-f16", "texture-compression-bc"];
const requiredFeatures = wanted.filter((f) => adapter.features.has(f));
const device = await adapter.requestDevice({ requiredFeatures });
Pattern 2: ALWAYS confirm enabled features on device.features
adapter.features is what the adapter could grant. device.features is what
the negotiated device actually has. ALWAYS branch runtime code paths on
device.features.has(name), NEVER on adapter.features, because a feature is
usable only when it was both supported and requested.
const useF16 = device.features.has("shader-f16");
const shaderCode = useF16 ? shaderF16Variant : shaderF32Variant;
Pattern 3: ALWAYS detect optional WGSL features before emitting them
Optional WGSL language features are reported by
navigator.gpu.wgslLanguageFeatures, a set-like object. An enable or
requires directive for a feature the browser lacks is a shader-creation error.
ALWAYS gate the shader variant on wgslLanguageFeatures.has(name).
const wgsl = navigator.gpu.wgslLanguageFeatures;
const hasReadonlyWriteable =
wgsl.has("readonly_and_readwrite_storage_textures");
Pattern 4: ALWAYS use getPreferredCanvasFormat for the canvas
navigator.gpu.getPreferredCanvasFormat() returns "bgra8unorm" or
"rgba8unorm" depending on the platform. Safari can differ from Chrome. ALWAYS
configure the canvas with this value. NEVER hard-code a format string.
const format = navigator.gpu.getPreferredCanvasFormat();
context.configure({ device, format, alphaMode: "premultiplied" });
Pattern 5: ALWAYS query adapter.limits, request only what you need
Default limits differ by browser and adapter. requiredLimits starts from the
spec defaults. ALWAYS read adapter.limits.<name> before requesting a higher
value, and request only the limits the code actually needs so the app stays
portable to weaker adapters.
const needed = 256 * 1024 * 1024;
const requiredLimits =
adapter.limits.maxStorageBufferBindingSize >= needed
? { maxStorageBufferBindingSize: needed }
: {};
const device = await adapter.requestDevice({ requiredLimits });
Pattern 6: NEVER depend on timeline or batching timing side effects
Browsers batch and schedule GPU work differently. Chrome, Safari, and Firefox
flush the queue at different points. ALWAYS synchronize explicitly with
queue.onSubmittedWorkDone() or buffer.mapAsync(). NEVER assume work has
completed because a later call appeared to see its result in one browser.
queue.submit([commandBuffer]);
await queue.onSubmittedWorkDone();
await stagingBuffer.mapAsync(GPUMapMode.READ);
Common Anti-Patterns
-
Putting an optional feature in requiredFeatures without checking
adapter.features. WHY it fails: requestDevice rejects on every browser
lacking the feature, so the app runs in Chrome and breaks in Safari and
Firefox.
-
Hard-coding "bgra8unorm" (or "rgba8unorm") as the canvas format. WHY it
fails: the preferred format is platform-dependent. A hard-coded format costs a
conversion on some devices and can mismatch the swap chain on Safari.
-
Assuming a capability exists because Chrome supports it. WHY it fails: Dawn
ships features first (shader-f16 Chrome 120, subgroups Chrome 134). Safari
26.0-26.5 is partial and Firefox lags on compression sets, timestamp queries,
and new WGSL extensions. Code built on Chrome-only assumptions throws or
produces wrong output on the other engines.
Critical Warnings
- NEVER add a
GPUFeatureName to requiredFeatures without
adapter.features.has(name) returning true first.
- NEVER hard-code the canvas format. ALWAYS call
navigator.gpu.getPreferredCanvasFormat().
- NEVER emit an
enable or requires WGSL directive without checking
navigator.gpu.wgslLanguageFeatures.has(name).
- NEVER assume Safari ships every feature Chrome ships. Safari 26.0-26.5 is
partial support; Firefox is disabled by default on most platforms.
- NEVER cite "Safari 18" as the WebGPU baseline. WebGPU shipped in Safari 26 /
iOS 26.
- NEVER depend on how aggressively a browser batches GPU work. Synchronize with
onSubmittedWorkDone() or mapAsync().
- NEVER size resources against
adapter.limits. Default limits differ per
browser; request via requiredLimits and size against device.limits.
Reference Files
references/methods.md : the feature-detection API surface, including
adapter.features.has, device.features.has,
navigator.gpu.wgslLanguageFeatures, and getPreferredCanvasFormat.
references/examples.md : verified feature-detection and graceful-degradation
code for cross-browser WebGPU apps.
references/anti-patterns.md : cross-browser mistakes with WHY-it-fails
explanations.
Related Skills
webgpu-core-limits-features : the limit and feature negotiation algorithm.
webgpu-core-architecture : the adapter / device / queue initialization chain.
webgpu-syntax-canvas-context : configuring GPUCanvasContext and the canvas
texture format.