Skip to content

Instantly share code, notes, and snippets.

@greggman
Created May 13, 2024 03:30
Show Gist options
  • Save greggman/8f54c518b7125d3cf48d767de859b284 to your computer and use it in GitHub Desktop.
Save greggman/8f54c518b7125d3cf48d767de859b284 to your computer and use it in GitHub Desktop.
WebGPU test mip-selection
:root {
color-scheme: light dark;
}
canvas {
border: 1px solid #888;
width: 64px;
height: 64px;
image-rendering: pixelated;
margin: 3px;
}
/*bug-in-github-api-content-can-not-be-empty*/
async function main() {
const adapter = await navigator.gpu?.requestAdapter();
const device = await adapter?.requestDevice();
const preferredFormat = navigator.gpu.getPreferredCanvasFormat();
for (let dd = 1; dd <= 4; dd += dd) {
const canvas = document.createElement('canvas');
canvas.width = 4;
canvas.height = 4;
document.body.appendChild(canvas);
const context = canvas.getContext('webgpu');
context.configure({
format: preferredFormat,
device,
});
const options = {
ddx: dd,
ddy: dd,
};
const module = device.createShaderModule({code: `
struct InOut {
@builtin(position) pos: vec4f,
@location(0) uv: vec2f,
};
@vertex fn vs(@builtin(vertex_index) vertex_index : u32) -> InOut {
let positions = array(
vec2f(-1, 1), vec2f( 1, 1),
vec2f(-1, -1), vec2f( 1, -1),
);
let pos = positions[vertex_index];
return InOut(
vec4f(pos, 0, 1),
(pos * 0.5 + 0.5) * vec2f(${options.ddx}, ${options.ddy}),
);
}
@group(0) @binding(0) var T : texture_2d<f32>;
@group(0) @binding(1) var S : sampler;
@fragment fn fs(v: InOut) -> @location(0) vec4f {
return textureSample(T, S, v.uv);
}
`,
});
const pipeline = device.createRenderPipeline({
layout: 'auto',
vertex: { module },
fragment: {
module,
targets: [
{format: preferredFormat},
],
},
primitive: {
topology: 'triangle-strip',
}
});
const sampler = device.createSampler({
minFilter: 'linear',
magFilter: 'linear',
mipmapFilter: 'linear',
minFilter: 'nearest',
magFilter: 'nearest',
mipmapFilter: 'nearest',
});
const texture = device.createTexture({
size: [4, 4],
mipLevelCount: 3,
format: 'rgba8unorm',
usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING,
});
const aToS = a => a.map(v => v.toString()).join(',');
const r = [1, 0, 0, 1];
const y = [1, 1, 0, 1];
const g = [0, 1, 0, 1];
const c = [0, 1, 1, 1];
const b = [0, 0, 1, 1];
const m = [1, 0, 1, 1];
const w = [1, 1, 1, 1];
const _ = [0.5, 0.5, 0.5, 1];
function makeTextureData({width, height}, mipLevel) {
const p = 2 ** mipLevel;
const w = Math.max(1, Math.floor(width / p));
const h = Math.max(1, Math.floor(height / p));
return new Uint8Array(w * h * 4).map((_, i) => {
const p = i / 4 | 0;
const x = p % w;
const y = p / w | 0;
const ch = i % 4;
const o = mipLevel * 10;
return [o + x, o + y, 0, 100 + mipLevel][ch];
});
}
function makeSolidTextureData({width, height}, mipLevel, color) {
const p = 2 ** mipLevel;
const w = Math.max(1, Math.floor(width / p));
const h = Math.max(1, Math.floor(height / p));
return new Uint8Array(w * h * 4).map((_, i) => {
const p = i / 4 | 0;
const ch = i % 4;
return color[ch] * 255;
});
}
const colors = { r, y, g, c, b, m, w, _ };
const valueToColor = Object.fromEntries(Object.entries(colors).map(([name, value]) => [aToS(value), name]));
device.queue.writeTexture(
{ texture, mipLevel: 0 },
makeSolidTextureData(texture, 0, r),
{ bytesPerRow: 16 },
[4, 4],
);
device.queue.writeTexture(
{ texture, mipLevel: 1 },
makeSolidTextureData(texture, 1, g),
{ bytesPerRow: 8 },
[2, 2],
);
device.queue.writeTexture(
{ texture, mipLevel: 2 },
makeSolidTextureData(texture, 2, b),
{ bytesPerRow: 4 },
[1, 1],
);
/*
+---+---+
| | |
+---+---+
| | |
+---+---+
*/
const bindGroup = device.createBindGroup({
layout: pipeline.getBindGroupLayout(0),
entries: [
{ binding: 0, resource: texture.createView() },
{ binding: 1, resource: sampler },
],
});
const encoder = device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [
{
view: context.getCurrentTexture().createView(),
clearValue: [1, 1, 1, 1],
loadOp: 'clear',
storeOp: 'store',
},
],
});
pass.setPipeline(pipeline);
pass.setBindGroup(0, bindGroup);
pass.draw(4);
pass.end();
device.queue.submit([encoder.finish()]);
}
}
function fail(msg) {
const elem = document.querySelector('#fail');
const contentElem = elem.querySelector('.content');
elem.style.display = '';
contentElem.textContent = msg;
}
main();
{"name":"WebGPU test mip-selection","settings":{},"filenames":["index.html","index.css","index.js"]}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment