Created
May 13, 2024 03:30
-
-
Save greggman/8f54c518b7125d3cf48d767de859b284 to your computer and use it in GitHub Desktop.
WebGPU test mip-selection
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
:root { | |
color-scheme: light dark; | |
} | |
canvas { | |
border: 1px solid #888; | |
width: 64px; | |
height: 64px; | |
image-rendering: pixelated; | |
margin: 3px; | |
} |
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 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(); |
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 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