Skip to content

Instantly share code, notes, and snippets.

@greggman
Created May 16, 2024 06:12
Show Gist options
  • Save greggman/39cc8a3cd26e0d88983cef891970804e to your computer and use it in GitHub Desktop.
Save greggman/39cc8a3cd26e0d88983cef891970804e to your computer and use it in GitHub Desktop.
WebGPU test mip-selection with offset
: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*/
/*
m.mmmmmmm
m.mmmmmmm
m.mmmmmmm
m.mmmmmmm
m.mmmmmmm
m.mmmmmmm
m.mmmmmmm
m.mmmmmmm
r.rr
r.rr
r.rr
r.rr
g.
g.
b
*/
async function main() {
const adapter = await navigator.gpu?.requestAdapter();
const device = await adapter?.requestDevice();
const preferredFormat = navigator.gpu.getPreferredCanvasFormat();
for (let dd = 1; dd <= 8; dd += dd) {
const canvas = document.createElement('canvas');
canvas.width = 8;
canvas.height = 8;
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, vec2i(1, 0));
}
`,
});
const pipeline = device.createRenderPipeline({
layout: 'auto',
vertex: { module },
fragment: {
module,
targets: [
{format: preferredFormat},
],
},
primitive: {
topology: 'triangle-strip',
}
});
const sampler = device.createSampler({
addressModeU: 'repeat',
addressModeV: 'repeat',
minFilter: 'linear',
magFilter: 'linear',
mipmapFilter: 'linear',
minFilter: 'nearest',
magFilter: 'nearest',
mipmapFilter: 'nearest',
});
const texture = device.createTexture({
size: [8, 8],
mipLevelCount: 4,
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];
});
}
const lerp = (a, b, t) => a + (b - a) * t;
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));
const d = 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;
return x === 1 ? lerp(128, 255, y / h) : color[ch] * 255;
});
return d;
}
const colors = { r, y, g, c, b, m, w, _ };
const valueToColor = Object.fromEntries(Object.entries(colors).map(([name, value]) => [aToS(value), name]));
[m, r, g, b].forEach((c, mipLevel) => {
const w = Math.max(1, texture.width >> mipLevel);
device.queue.writeTexture(
{ texture, mipLevel },
makeSolidTextureData(texture, mipLevel, c),
{ bytesPerRow: w * 4 },
[w, w],
);
});
/*
+---+---+
| | |
+---+---+
| | |
+---+---+
*/
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 with offset","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