Last active
May 8, 2024 07:37
-
-
Save greggman/18b03ab1cbdb44ce7e12c9bae496f205 to your computer and use it in GitHub Desktop.
WebGPU test derivatives for CTS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*bug-in-github-api-content-can-not-be-empty*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*bug-in-github-api-content-can-not-be-empty*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{"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