Skip to content

Instantly share code, notes, and snippets.

@greggman
Last active May 8, 2024 07:37
Show Gist options
  • Save greggman/18b03ab1cbdb44ce7e12c9bae496f205 to your computer and use it in GitHub Desktop.
Save greggman/18b03ab1cbdb44ce7e12c9bae496f205 to your computer and use it in GitHub Desktop.
WebGPU test derivatives for CTS
/*bug-in-github-api-content-can-not-be-empty*/
/*bug-in-github-api-content-can-not-be-empty*/
async function main() {
const adapter = await navigator.gpu?.requestAdapter();
const device = await adapter?.requestDevice();
const module = device.createShaderModule({code: `
@vertex fn vs(@builtin(vertex_index) vndx: u32) -> @builtin(position) vec4f {
let verts = array(
vec2f(-1, -1),
vec2f( 1, -1),
vec2f(-1, 1),
vec2f(-1, 1),
vec2f( 1, -1),
vec2f( 1, 1),
);
let v = verts[vndx];
return vec4f(v, 0, 1);
}
@group(0) @binding(0) var tex: texture_2d<f32>;
@group(0) @binding(1) var smp: sampler;
@group(0) @binding(2) var<storage, read_write> data: array<vec4f>;
@group(0) @binding(3) var<storage, read> fragPositions: array<vec4f>;
// this can't be used because affects the derivatives.
fn fragPositionForDataIndex(ndx: u32, targetWidth: u32) -> vec2f {
let x = ndx % targetWidth;
let y = ndx / targetWidth;
let pos = vec2f(f32(x), f32(y)) + 0.5;
return pos;
}
const kMult = 2.0;
const kTargetSize = u32(4);
const kSourceSize = u32(4);
const kMipLevel = u32(1);
const kTexelCoordAtMipLevel = vec2f(1, 1);
const kSizeAtMipLevel = f32(kSourceSize) / pow(2.0, f32(kMipLevel));
const kTestCoord = (kTexelCoordAtMipLevel + 0.5) / kSizeAtMipLevel;
const kIndexOfTestCoord = 6;
const kXIndexOfTestCoord = kIndexOfTestCoord % kTargetSize;
const kYIndexOfTestCoord = kIndexOfTestCoord / kTargetSize;
const kFragPositionOfTestCoord = vec2f(f32(kXIndexOfTestCoord), f32(kYIndexOfTestCoord)) + 0.5;
const kExpectedUVWhenTestCoordIsRead = kFragPositionOfTestCoord / f32(kTargetSize);
@fragment fn fs(@builtin(position) position: vec4f) -> @location(0) vec4f {
_ = tex;
_ = smp;
_ = &fragPositions;
let p = position.xy;
let i = u32(floor(p.y)) * kTargetSize + u32(floor(p.x));
let dxdy = (p - kFragPositionOfTestCoord) / f32(kSourceSize) * kMult;
// let dxdy = (p - fragPositions[i].xy) / f32(kSourceSize) * kMult;
let d = textureSample(tex, smp, kTestCoord + dxdy) * 255.0;
// let d = vec4f(fragPositions[i].xy, position.xy);
data[i] = select(vec4f(-1), d, distance(p, kFragPositionOfTestCoord) < 1.0);
return vec4f(0);
}
`,
});
const pipeline = device.createRenderPipeline({
layout: 'auto',
vertex: { module },
fragment: {
module,
targets: [
{format: 'rgba8unorm'},
],
},
});
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 outTex = device.createTexture({
format: 'rgba8unorm',
size: [4, 4],
usage: GPUTextureUsage.RENDER_ATTACHMENT,
})
const outBuffer = device.createBuffer({
size: outTex.width * outTex.height * 4 * 4,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
});
const resultBuffer = device.createBuffer({
size: outBuffer.size,
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
});
const fragPositionbuffer = device.createBuffer({
size: outTex.width * outTex.height * 4 * 4,
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE,
});
device.queue.writeBuffer(fragPositionbuffer, 0, new Float32Array(outTex.width * outTex.height * 4).map((_, i) => {
const p = i / 4 | 0;
const x = p % outTex.width;
const y = p / outTex.height | 0;
return [x + 0.5, y + 0.5, 0, 0][i % 4];
}));
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 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 },
makeTextureData(texture, 0),
{ bytesPerRow: 16 },
[4, 4],
);
device.queue.writeTexture(
{ texture, mipLevel: 1 },
makeTextureData(texture, 1),
{ bytesPerRow: 8 },
[2, 2],
);
device.queue.writeTexture(
{ texture, mipLevel: 2 },
makeTextureData(texture, 2),
{ bytesPerRow: 4 },
[1, 1],
);
/*
+---+---+
| | |
+---+---+
| | |
+---+---+
*/
const bindGroup = device.createBindGroup({
layout: pipeline.getBindGroupLayout(0),
entries: [
{ binding: 0, resource: texture.createView() },
{ binding: 1, resource: sampler },
{ binding: 2, resource: { buffer: outBuffer }},
{ binding: 3, resource: { buffer: fragPositionbuffer }},
],
});
const encoder = device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [
{
view: outTex.createView(),
clearValue: [1, 1, 1, 1],
loadOp: 'clear',
storeOp: 'store',
},
],
});
pass.setPipeline(pipeline);
pass.setBindGroup(0, bindGroup);
pass.draw(6);
pass.end();
encoder.copyBufferToBuffer(
outBuffer, 0,
resultBuffer, 0, resultBuffer.size);
device.queue.submit([encoder.finish()]);
await resultBuffer.mapAsync(GPUMapMode.READ);
const data = new Float32Array(resultBuffer.getMappedRange());
const colorToString = a => {
const key = aToS(Array.from(a));
return valueToColor[key] || key;
}
for (let y = 0; y < outTex.height; ++y) {
for (let x = 0; x < outTex.width; ++x) {
const offset = (y * outTex.width + x) * 4;
console.log(`x${x}, y${y}: ${colorToString(data.subarray(offset, offset + 4))}`);
}
console.log('');
}
}
function fail(msg) {
const elem = document.querySelector('#fail');
const contentElem = elem.querySelector('.content');
elem.style.display = '';
contentElem.textContent = msg;
}
main();
{"name":"WebGPU test derivatives for CTS","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