W3C Groups

The W3C GPU for the Web Community and Working Groups

WebGPU was designed in the open at the W3C in two stages. The GPU for the Web Community Group formed in February 2017 at Apple's proposal (the same WebKit 15,343 post sketched an API called "WebGPU") to map a web API onto Direct3D 12, Metal and Vulkan and expose GPU compute. Anyone may join a community group, which suited the early years of debate.

The GPU for the Web Working Group, chartered on 20 August 2020, turns that work into W3C Recommendations under the W3C Patent Policy, so members license their essential patents royalty-free and every browser can implement the result. Its current charter runs to 30 November 2026, chaired by Jim Blandy (Mozilla) and Corentin Wallez (Google). Both specifications live in gpuweb/gpuweb (https://github.com/gpuweb/gpuweb 5,484 ), whose issues, proposals/ folder and Implementation Status wiki page are the best places to check why a rule exists and what each browser ships.

The GPU for the Web Community Group and Working Group on one timelineHTMLLive
<!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="340"></canvas>
  <canvas id="labels" width="600" height="340"></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);
}

const x = t => 30 + (t - 2016) / 11 * 540;          // decimal years to pixels
const CG = [0.20, 0.45, 0.75], WG = [0.55, 0.30, 0.65];

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 = [];                                  // x, y, w, h, r, g, b, corner radius
  for (let year = 2016; year <= 2027; year++) {
    boxes.push([x(year), 60, 1, 200, 0.85, 0.83, 0.80, 0]);
    label(String(year), x(year), 280, 11, '#666');
  }
  // Community group: open to anyone, from February 2017 on
  boxes.push([x(2017.1), 90, x(2026.8) - x(2017.1), 34, ...CG, 17]);
  label('GPU for the Web Community Group', x(2017.1) + 14, 112, 13, '#fff', 'left', 'bold');
  label('Feb 2017: formed at Apple\'s proposal', x(2017.1), 80, 11, '#1f4f8a', 'left');
  // Working group: chartered 20 August 2020, current charter to 30 November 2026
  boxes.push([x(2020.63), 170, x(2026.91) - x(2020.63), 34, ...WG, 17]);
  label('GPU for the Web Working Group', x(2020.63) + 14, 192, 13, '#fff', 'left', 'bold');
  label('20 Aug 2020: chartered', x(2020.63), 160, 11, '#5b2b6e', 'left');
  label('charter to 30 Nov 2026', x(2026.91), 222, 11, '#5b2b6e', 'right');
  // Hand-over: the community group's drafts become Recommendation-track specs
  boxes.push([x(2020.63) - 1, 124, 3, 46, 0.4, 0.4, 0.4, 1]);
  for (const [t, name] of [[2021.38, 'First Public WD'], [2024.97, 'CR Snapshot']]) {
    boxes.push([x(t) - 6, 231, 12, 12, 0.85, 0.55, 0.10, 6]);
    label(name, x(t), 258, 11, '#8a5a00');
  }
  label('Chairs: Jim Blandy (Mozilla), Corentin Wallez (Google)', 30, 30, 12, '#444', 'left');
  label('Specs, issues and proposals: github.com/gpuweb/gpuweb', 30, 46, 12, '#444', 'left');
  label('W3C Patent Policy: royalty-free licensing, so every browser can implement it', 300, 320, 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 / 170, 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>