Diagnostic Directives

Diagnostic Directives and Severity Control

Some diagnostics have a named triggering rule whose severity you can set to error, warning, info or off. WGSL defines derivative_uniformity and subgroup_uniformity, both errors by default; Chrome 1 adds rules under a chromium. prefix. A diagnostic(...) directive sets a rule for the module, and a @diagnostic(...) attribute narrows it to a function or statement. Here Built-in Functions's uniformity error becomes a warning:

Downgrading derivative_uniformity for a whole moduleJavaScript
// out: 0 x u32
diagnostic(warning, derivative_uniformity);
@group(0) @binding(0) var cover: texture_2d<f32>;
@group(0) @binding(1) var linear: sampler;
@fragment fn fs(@location(0) uv: vec2f) -> @location(0) vec4f {
  if (uv.x > 0.5) {
    return textureSample(cover, linear, uv);
  }
  return vec4f(0.08, 0.40, 0.75, 1);
}
Output
7:12 warning: 'textureSample' must only be called from uniform control flow
6:3 info: control flow depends on possibly non-uniform value
6:7 info: user-defined input 'uv' of 'fs' may be non-uniform

if (uv.x > 0.5) @diagnostic(off, derivative_uniformity) { ... } compiled silently. diagnostic(off, chromium.unreachable_code) hid the "code is unreachable" warning of getCompilationInfo(), error turned it into an error, an unknown rule gave "unrecognized diagnostic rule 'my_rule'", and off plus warning for one rule gave "conflicting diagnostic directive". Silencing uniformity does not fix the code: derivatives in divergent flow are undefined (seams, wrong mipmap levels), so scope it to a branch you know is uniform.

diagnostic(warning, derivative_uniformity) turns a uniformity error into a warning, so the half-textured cover rendersHTMLLive
<!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);
}

const fragment = (directive, attribute = '') => /* wgsl */ `${directive}
@group(0) @binding(0) var cover: texture_2d<f32>;
@group(0) @binding(1) var linear: sampler;
struct Out { @builtin(position) pos: vec4f, @location(0) uv: vec2f }
@vertex fn vs(@builtin(vertex_index) v: u32) -> Out {
  let q = vec2f(f32(v & 1), f32(v >> 1));
  return Out(vec4f(q * 2 - 1, 0, 1), vec2f(q.x, 1 - q.y));
}
@fragment fn fs(@location(0) uv: vec2f) -> @location(0) vec4f {
  if (uv.x > 0.5) ${attribute}{
    return textureSample(cover, linear, uv);
  }
  return vec4f(0.08, 0.40, 0.75, 1);
}`;
const variants = [
  ['no directive (error by default)', fragment('')],
  ['diagnostic(warning, derivative_uniformity);', fragment('diagnostic(warning, derivative_uniformity);')],
  ['@diagnostic(off, derivative_uniformity) on if', fragment('', '@diagnostic(off, derivative_uniformity) ')],
  ['diagnostic(off, my_rule);', fragment('diagnostic(off, my_rule);')]];

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 report = [];
  for (const [name, code] of variants) {
    device.pushErrorScope('validation');           // variants 1 and 4 fail on purpose
    const { messages } = await device.createShaderModule({ code }).getCompilationInfo();
    await device.popErrorScope();
    report.push([name, messages]);
  }

  // A cover texture painted with the 2D API, then drawn with the warning-level shader.
  const art = new OffscreenCanvas(128, 192), g = art.getContext('2d');
  g.fillStyle = '#b5452e'; g.fillRect(0, 0, 128, 192);
  g.fillStyle = '#f3e6c8'; g.fillRect(0, 60, 128, 34);
  g.fillStyle = '#3a1d12'; g.font = 'bold 15px serif'; g.textAlign = 'center';
  g.fillText('Salt and', 64, 76); g.fillText('Saffron', 64, 90);
  const texture = device.createTexture({ size: [128, 192], format: 'rgba8unorm',
    usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT });
  device.queue.copyExternalImageToTexture({ source: art }, { texture }, [128, 192]);
  const module = device.createShaderModule({ code: variants[1][1] });
  const pipeline = device.createRenderPipeline({ layout: 'auto', primitive: { topology: 'triangle-strip' },
    vertex: { module }, fragment: { module, targets: [{ format }] } });
  const group = device.createBindGroup({ layout: pipeline.getBindGroupLayout(0), entries: [
    { binding: 0, resource: texture.createView() }, { binding: 1, resource: device.createSampler({ magFilter: 'linear', minFilter: 'linear' }) }] });
  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.setViewport(20, 40, 200, 280, 0, 1);
  pass.draw(4);
  pass.end();
  device.queue.submit([encoder.finish()]);

  ink.font = 'bold 13px system-ui, sans-serif'; ink.fillStyle = '#222';
  ink.fillText('Rendered with the warning-level module:', 20, 26);
  report.forEach(([name, messages], i) => {
    const y = 50 + i * 72;
    ink.fillStyle = '#222'; ink.font = 'bold 12px ui-monospace, monospace';
    ink.fillText(name.slice(0, 46), 240, y);
    const m = messages[0];
    ink.fillStyle = !m ? '#1e6b3a' : m.type === 'error' ? '#8a2b2b' : '#8a5a00';
    ink.font = '11.5px system-ui, sans-serif';
    const text = m ? `${m.type}: ${m.message}` : 'compiled silently';
    (text.match(/.{1,58}(\s|$)|.{1,58}/g) || []).slice(0, 3).forEach((l, k) => ink.fillText(l.trim(), 240, y + 18 + k * 15));
  });
}
main();
</script>