// Solid-fill rounded-rect shader. One draw call per rect; 4 vertices
// rendered as a triangle strip; rounded corners produced by an SDF
// mask in the fragment shader.
struct Viewport {
// Window size in physical pixels.
size: vec2<f32>,
_pad: vec2<f32>,
};
struct Rect {
// Top-left in physical pixels.
pos: vec2<f32>,
// Width / height in physical pixels.
size: vec2<f32>,
// Straight RGBA, premultiplied at the end of the fragment shader.
color: vec4<f32>,
// .x = corner radius in physical pixels (0 = square). .yzw unused —
// the wrapping vec4 is here so the Rust-side struct stays at 48
// bytes (vec3 forces 16-byte alignment, which would round to 64).
radius_pad: vec4<f32>,
};
@group(0) @binding(0) var<uniform> view: Viewport;
@group(0) @binding(1) var<uniform> rect: Rect;
struct VertexOut {
@builtin(position) clip: vec4<f32>,
@location(0) local: vec2<f32>,
};
@vertex
fn vs_main(@builtin(vertex_index) idx: u32) -> VertexOut {
// Triangle strip:
// idx 0: top-left (0, 0)
// idx 1: top-right (1, 0)
// idx 2: bottom-left (0, 1)
// idx 3: bottom-right (1, 1)
let lx = f32(idx & 1u);
let ly = f32((idx >> 1u) & 1u);
let pixel = rect.pos + rect.size * vec2<f32>(lx, ly);
// Flip Y: mirui uses screen-space (Y down), NDC is Y up.
let ndc = vec2<f32>(
(pixel.x / view.size.x) * 2.0 - 1.0,
1.0 - (pixel.y / view.size.y) * 2.0,
);
var out: VertexOut;
out.clip = vec4<f32>(ndc, 0.0, 1.0);
out.local = vec2<f32>(lx, ly);
return out;
}
@fragment
fn fs_main(v: VertexOut) -> @location(0) vec4<f32> {
let half_size = rect.size * 0.5;
let pixel_local = v.local * rect.size;
// Vector from rect centre to current pixel.
let centre = pixel_local - half_size;
// Clamp the requested radius so it never exceeds half the smaller side.
let r = min(rect.radius_pad.x, min(half_size.x, half_size.y));
// SDF for a rounded rectangle (inigo quilez style):
// |centre| - half + r, then collapse outside-corner distance.
let q = abs(centre) - half_size + vec2<f32>(r);
let dist = length(max(q, vec2<f32>(0.0, 0.0))) + min(max(q.x, q.y), 0.0) - r;
// 1-pixel-wide anti-alias edge.
let coverage = clamp(0.5 - dist, 0.0, 1.0);
return vec4<f32>(rect.color.rgb, rect.color.a * coverage);
}