struct GlobalUniforms {
projection: mat4x4<f32>,
}
struct RectInstance {
position_size: vec4<f32>,
color: vec4<f32>,
border_color: vec4<f32>,
clip_rect: vec4<f32>,
params: vec4<f32>,
}
@group(0) @binding(0) var<uniform> globals: GlobalUniforms;
@group(1) @binding(0) var<storage, read> instances: array<RectInstance>;
struct VertexInput {
@location(0) position: vec2<f32>,
@builtin(instance_index) instance_index: u32,
}
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) local_pos: vec2<f32>,
@location(1) rect_size: vec2<f32>,
@location(2) screen_pos: vec2<f32>,
@location(3) @interpolate(flat) instance_index: u32,
}
@vertex
fn vs_main(vertex: VertexInput) -> VertexOutput {
var output: VertexOutput;
let inst = instances[vertex.instance_index];
let rect_pos = inst.position_size.xy;
let rect_size = inst.position_size.zw;
let local = vertex.position * rect_size;
let rotation = inst.params.w;
let center = rect_size * 0.5;
let centered = local - center;
let cos_r = cos(rotation);
let sin_r = sin(rotation);
let rotated = vec2<f32>(
centered.x * cos_r - centered.y * sin_r,
centered.x * sin_r + centered.y * cos_r,
);
let world_pos = rect_pos + center + rotated;
output.position = globals.projection * vec4<f32>(world_pos, 0.0, 1.0);
output.position.z = inst.params.z;
output.local_pos = local;
output.rect_size = rect_size;
output.screen_pos = world_pos;
output.instance_index = vertex.instance_index;
return output;
}
fn rounded_rect_sdf(pos: vec2<f32>, size: vec2<f32>, radius: f32) -> f32 {
let half_size = size * 0.5;
let center_pos = pos - half_size;
let clamped_radius = min(radius, min(half_size.x, half_size.y));
let q = abs(center_pos) - half_size + clamped_radius;
return length(max(q, vec2<f32>(0.0))) + min(max(q.x, q.y), 0.0) - clamped_radius;
}
@fragment
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
let inst = instances[input.instance_index];
let clip = inst.clip_rect;
if clip.z > clip.x {
let frag_pos = input.screen_pos;
if frag_pos.x < clip.x || frag_pos.x > clip.z ||
frag_pos.y < clip.y || frag_pos.y > clip.w {
discard;
}
}
let corner_radius = inst.params.x;
let border_width = inst.params.y;
let dist = rounded_rect_sdf(input.local_pos, input.rect_size, corner_radius);
let edge_softness = 1.0;
var alpha = 1.0 - smoothstep(-edge_softness, 0.0, dist);
var final_color = inst.color;
if border_width > 0.0 {
let inner_dist = dist + border_width;
let border_alpha = 1.0 - smoothstep(-edge_softness, 0.0, inner_dist);
let is_border = alpha - border_alpha;
final_color = mix(final_color, inst.border_color, is_border);
}
if alpha < 0.01 {
discard;
}
return vec4<f32>(final_color.rgb, final_color.a * alpha);
}