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:
// 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);
}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.
<!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>