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");
// Emit the shader variant that matches what this browser supports.
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(); // explicit, portable synchronization
await stagingBuffer.mapAsync(GPUMapMode.READ);
Common Anti-Patterns
Putting an optional feature in
requiredFeatureswithout checkingadapter.features. WHY it fails:requestDevicerejects 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-f16Chrome 120,subgroupsChrome 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
GPUFeatureNametorequiredFeatureswithoutadapter.features.has(name)returningtruefirst. - NEVER hard-code the canvas format. ALWAYS call
navigator.gpu.getPreferredCanvasFormat(). - NEVER emit an
enableorrequiresWGSL directive without checkingnavigator.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()ormapAsync(). - NEVER size resources against
adapter.limits. Default limits differ per browser; request viarequiredLimitsand size againstdevice.limits.
Reference Files
references/methods.md: the feature-detection API surface, includingadapter.features.has,device.features.has,navigator.gpu.wgslLanguageFeatures, andgetPreferredCanvasFormat.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: configuringGPUCanvasContextand the canvas texture format.