Each implementation picks one native API per platform. Dawn 1,149 uses Direct3D 12 on Windows, Metal on Apple platforms, Vulkan on Linux, Android and ChromeOS, and OpenGL ES for older devices in compatibility mode, and Tint emits the matching shader language: HLSL (compiled by Microsoft's DXC), MSL, SPIR-V or GLSL.
A backend is more than a syntax converter: WebGPU's guarantees (zeroed resources, no out-of-bounds access, barriers computed for you) must hold everywhere, so each backend does work the native API leaves to the programmer, such as tracking resource states to insert Direct3D 12 barriers. This script reads the adapters Dawn found from chrome://gpu:
import re
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(channel='chrome', args=['--headless=new']) # installed Chrome
page = browser.new_page()
page.goto('chrome://gpu')
page.get_by_text('Dawn Info').wait_for() # filled in asynchronously
dawn = page.locator('#content').inner_text().split('Dawn Info')[1] # pierces shadow DOM
browser.close()
pattern = r'<(.+?)> (\w+) backend - (.+)\n\[WebGPU Status\]\n(.+)'
for kind, api, name, status in re.findall(pattern, dawn):
reason = re.search(r'crbug\.com/\d+: ([^.|]+)', status) # first blocklist reason, if any
print(f'{api:6} {"CPU" if kind == "CPU" else "GPU"} {name:29} '
f'{reason.group(1) if reason else status}')D3D12 GPU NVIDIA GeForce GTX 1650 Available D3D12 CPU Microsoft Basic Render Driver CPU adapters not fully tested or conformant Vulkan CPU SwiftShader Device (Subzero) CPU adapters not fully tested or conformant Vulkan GPU NVIDIA GeForce GTX 1650 Available D3D11 GPU NVIDIA GeForce GTX 1650 D3D11 backend not fully implemented D3D11 CPU Microsoft Basic Render Driver D3D11 backend not fully implemented
Dawn found six adapters but allows only two, and by default hands pages the GTX 1650 through Direct3D 12. The blocklisted SwiftShader 2,546 line explains why forced software WebGPU was unreliable here. The same page lists Dawn's toggles, such as use_dxc (compile HLSL with DXC rather than the older FXC).
<!doctype html>
<style>
body { margin: 0; background: #f7f4ee; font: 14px system-ui, sans-serif; }
.stage { position: relative; width: 100%; max-width: 600px; }
.stage canvas { display: block; width: 100%; }
.stage canvas + canvas { position: absolute; inset: 0; pointer-events: none; }
</style>
<div class="stage">
<canvas id="view" width="600" height="360"></canvas>
<canvas id="labels" width="600" height="360"></canvas>
</div>
<script>
const canvas = document.getElementById('view');
const ink = document.getElementById('labels').getContext('2d');
function showMessage(text) { // 2D fallback when WebGPU is missing
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#fbeaea'; ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#8a2b2b'; ctx.font = '18px system-ui, sans-serif'; ctx.textAlign = 'center';
ctx.fillText(text, canvas.width / 2, canvas.height / 2);
}
function label(text, x, y, size = 12, color = '#2b2b2b', align = 'center', weight = '') {
ink.font = `${weight} ${size}px system-ui, sans-serif`; ink.fillStyle = color; ink.textAlign = align;
ink.fillText(text, x, y);
}
// platform, native API, shader language Tint emits, colour
const routes = [['Windows', 'Direct3D 12', 'HLSL, compiled by DXC', [0.20, 0.50, 0.80]],
['macOS, iOS', 'Metal', 'MSL', [0.55, 0.55, 0.60]],
['Linux, Android, ChromeOS', 'Vulkan', 'SPIR-V', [0.75, 0.25, 0.25]],
['Older devices (compat)', 'OpenGL ES', 'GLSL', [0.30, 0.60, 0.40]]];
async function main() {
const adapter = await navigator.gpu?.requestAdapter();
if (!adapter) return showMessage('WebGPU is not available in this browser');
const device = await adapter.requestDevice();
const context = canvas.getContext('webgpu');
const format = navigator.gpu.getPreferredCanvasFormat();
context.configure({ device, format });
const boxes = [[14, 128, 100, 60, 0.98, 0.92, 0.75, 10]]; // the WGSL source
label('WGSL', 64, 155, 15, '#222', 'center', 'bold');
label('+ API calls', 64, 173, 11, '#555');
boxes.push([114, 157, 20, 2, 0.4, 0.4, 0.4, 1], [134, 40, 2, 262, 0.4, 0.4, 0.4, 1]);
label('Platform', 216, 28, 12, '#666'); label('Native API', 370, 28, 12, '#666'); label('Tint emits', 518, 28, 12, '#666');
routes.forEach(([platform, api, language, c], i) => {
const y = 40 + i * 66;
boxes.push([134, y + 24, 16, 2, 0.4, 0.4, 0.4, 1]);
boxes.push([150, y, 132, 50, 1, 1, 1, 9], [298, y, 144, 50, ...c, 9], [458, y, 128, 50, 1, 1, 1, 9]);
boxes.push([282, y + 24, 16, 2, 0.4, 0.4, 0.4, 1], [442, y + 24, 16, 2, 0.4, 0.4, 0.4, 1]);
label(platform, 216, y + 30, 11.5, '#222');
label(api, 370, y + 30, 14, '#fff', 'center', 'bold');
label(language, 522, y + 30, 11.5, '#222');
});
// What this browser's adapter reports (the backend itself is not exposed to pages)
const info = adapter.info;
label(`This page's adapter: vendor "${info.vendor || 'n/a'}", architecture "${info.architecture || 'n/a'}"`, 300, 324, 12, '#1f4f8a');
label('Backends also insert barriers, zero new resources and bounds-check every access.', 300, 344, 12, '#444');
const module = device.createShaderModule({ code: `
struct Box { rect: vec4f, style: vec4f } // style: r, g, b, corner radius
@group(0) @binding(0) var<storage> boxes: array<Box>;
struct Out { @builtin(position) pos: vec4f, @location(0) local: vec2f,
@location(1) @interpolate(flat) i: u32 }
@vertex fn vs(@builtin(vertex_index) v: u32, @builtin(instance_index) i: u32) -> Out {
let corner = vec2f(f32(v & 1), f32(v >> 1)); // triangle-strip corners
let r = boxes[i].rect;
let px = r.xy + corner * r.zw;
return Out(vec4f(px.x / 300 - 1, 1 - px.y / 180, 0, 1), corner * r.zw, i);
}
@fragment fn fs(in: Out) -> @location(0) vec4f {
let b = boxes[in.i];
let half = b.rect.zw / 2;
let q = abs(in.local - half) - half + b.style.w; // signed distance to a rounded box
let d = length(max(q, vec2f(0))) + min(max(q.x, q.y), 0) - b.style.w;
let a = clamp(0.5 - d, 0, 1);
return vec4f(b.style.rgb * a, a); // premultiplied alpha
}` });
const blend = { srcFactor: 'one', dstFactor: 'one-minus-src-alpha' };
const pipeline = device.createRenderPipeline({ layout: 'auto',
vertex: { module }, primitive: { topology: 'triangle-strip' },
fragment: { module, targets: [{ format, blend: { color: blend, alpha: blend } }] } });
const data = new Float32Array(boxes.flat());
const buffer = device.createBuffer({ size: data.byteLength, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST });
device.queue.writeBuffer(buffer, 0, data);
const group = device.createBindGroup({ layout: pipeline.getBindGroupLayout(0), entries: [{ binding: 0, resource: { buffer } }] });
const encoder = device.createCommandEncoder();
const pass = encoder.beginRenderPass({ colorAttachments: [{ view: context.getCurrentTexture().createView(),
clearValue: [0.97, 0.96, 0.93, 1], loadOp: 'clear', storeOp: 'store' }] });
pass.setPipeline(pipeline);
pass.setBindGroup(0, group);
pass.draw(4, boxes.length);
pass.end();
device.queue.submit([encoder.finish()]);
}
main();
</script>