// The feature grid, drawn as a head-locked 2D overlay. // // A quad in clip space with a per-eye scale and offset. No 3D: the content // is flat and pinned to the head, so nothing here needs a view or a // projection matrix. // // The per-eye transform is not optional decoration, though. A headset's // frusta are asymmetric — the view axis is not at the centre of the eye // buffer, and the two eyes differ — so painting identical pixels into both // buffers puts the same content at different apparent angles and the views // refuse to fuse. `offset` recentres on each eye's view axis; `scale` sizes // the image to the angle the camera actually saw, instead of stretching it // across the whole display and looking zoomed. struct Params { scale: vec2, offset: vec2, grid: u32, pad0: u32, pad1: u32, pad2: u32, }; var params: Params; var cells: array; struct VsOut { @builtin(position) position: vec4, @location(0) uv: vec2, }; @vertex fn vs_main(@builtin(vertex_index) vi: u32) -> VsOut { // Two triangles over the unit square. var xs = array(-1.0, 1.0, -1.0, -1.0, 1.0, 1.0); var ys = array(-1.0, -1.0, 1.0, 1.0, -1.0, 1.0); let x = xs[vi]; let y = ys[vi]; var out: VsOut; out.position = vec4( x * params.scale.x + params.offset.x, y * params.scale.y + params.offset.y, 0.0, 1.0, ); // Blade uses a negative-height viewport, so clip +y is the top of the // framebuffer. Row 0 of the grid is the top of the image, so v runs // opposite to y. out.uv = vec2(x * 0.5 + 0.5, 0.5 - y * 0.5); return out; } fn cell(ix: i32, iy: i32) -> vec3 { let g = i32(params.grid); let x = clamp(ix, 0, g - 1); let y = clamp(iy, 0, g - 1); let base = u32((y * g + x) * 3); return vec3(cells[base], cells[base + 1u], cells[base + 2u]); } @fragment fn fs_main(in: VsOut) -> @location(0) vec4 { let p = in.uv * f32(params.grid) - 0.5; let corner = floor(p); let frac = p - corner; let ix = i32(corner.x); let iy = i32(corner.y); let top = mix(cell(ix, iy), cell(ix + 1, iy), frac.x); let bottom = mix(cell(ix, iy + 1), cell(ix + 1, iy + 1), frac.x); let rgb = clamp(mix(top, bottom, frac.y), vec3(0.0), vec3(1.0)); return vec4(rgb, 1.0); }