1#import bevy_render::view::View3const TEXTURED = 1u;4const RIGHT_VERTEX = 2u;5const BOTTOM_VERTEX = 4u;6const BORDER: u32 = 8u;8fn enabled(flags: u32, mask: u32) -> bool {9 return (flags & mask) != 0u;10}12@group(0) @binding(0) var<uniform> view: View;14struct VertexOutput {15 @location(0) uv: vec2<f32>,16 @location(1) color: vec4<f32>,18 @location(2) @interpolate(flat) size: vec2<f32>,19 @location(3) @interpolate(flat) flags: u32,20 @location(4) @interpolate(flat) radius: vec4<f32>, 21 @location(5) @interpolate(flat) border: vec4<f32>, 23 // Position relative to the center of the rectangle.24 @location(6) point: vec2<f32>,25 @builtin(position) position: vec4<f32>,26};28@vertex29fn vertex(30 @location(0) vertex_position: vec3<f32>,31 @location(1) vertex_uv: vec2<f32>,32 @location(2) vertex_color: vec4<f32>,33 @location(3) flags: u32,35 // x: top left, y: top right, z: bottom right, w: bottom left.36 @location(4) radius: vec4<f32>,38 // x: left, y: top, z: right, w: bottom.39 @location(5) border: vec4<f32>,40 @location(6) size: vec2<f32>,41 @location(7) point: vec2<f32>,42) -> VertexOutput {43 var out: VertexOutput;44 out.uv = vertex_uv;45 out.position = view.clip_from_world * vec4(vertex_position, 1.0);46 out.color = vertex_color;47 out.flags = flags;48 out.radius = radius;49 out.size = size;50 out.border = border;51 out.point = point;53 return out;54}56@group(1) @binding(0) var sprite_texture: texture_2d<f32>;57@group(1) @binding(1) var sprite_sampler: sampler;59// The returned value is the shortest distance from the given point to the boundary of the rounded 60// box.61// 62// Negative values indicate that the point is inside the rounded box, positive values that the point 63// is outside, and zero is exactly on the boundary.64//65// Arguments: 66// - `point` -> The function will return the distance from this point to the closest point on 67// the boundary.68// - `size` -> The maximum width and height of the box.69// - `corner_radii` -> The radius of each rounded corner. Ordered counter clockwise starting 70// top left:71// x: top left, y: top right, z: bottom right, w: bottom left.72fn sd_rounded_box(point: vec2<f32>, size: vec2<f32>, corner_radii: vec4<f32>) -> f32 {73 // If 0.0 < y then select bottom left (w) and bottom right corner radius (z).74 // Else select top left (x) and top right corner radius (y).75 let rs = select(corner_radii.xy, corner_radii.wz, 0.0 < point.y);76 // w and z are swapped above so that both pairs are in left to right order, otherwise this second 77 // select statement would return the incorrect value for the bottom pair.78 let radius = select(rs.x, rs.y, 0.0 < point.x);79 // Vector from the corner closest to the point, to the point.80 let corner_to_point = abs(point) - 0.5 * size;81 // Vector from the center of the radius circle to the point.82 let q = corner_to_point + radius;83 // Length from center of the radius circle to the point, zeros a component if the point is not 84 // within the quadrant of the radius circle that is part of the curved corner.85 let l = length(max(q, vec2(0.0)));86 let m = min(max(q.x, q.y), 0.0);87 return l + m - radius;88}90fn sd_inset_rounded_box(point: vec2<f32>, size: vec2<f32>, radius: vec4<f32>, inset: vec4<f32>) -> f32 {91 let inner_size = size - inset.xy - inset.zw;92 let inner_center = inset.xy + 0.5 * inner_size - 0.5 * size;93 let inner_point = point - inner_center;95 var r = radius;97 // Top left corner.98 r.x = r.x - max(inset.x, inset.y);100 // Top right corner.101 r.y = r.y - max(inset.z, inset.y);103 // Bottom right corner.104 r.z = r.z - max(inset.z, inset.w); 106 // Bottom left corner.107 r.w = r.w - max(inset.x, inset.w);109 let half_size = inner_size * 0.5;110 let min_size = min(half_size.x, half_size.y);112 r = min(max(r, vec4(0.0)), vec4<f32>(min_size));114 return sd_rounded_box(inner_point, inner_size, r);115}117// get alpha for antialiasing for sdf118fn antialias(distance: f32) -> f32 {119 // Using the fwidth(distance) was causing artifacts, so just use the distance.120 return saturate(0.5 - distance);121}123fn draw(in: VertexOutput, texture_color: vec4<f32>) -> vec4<f32> {124 // Only use the color sampled from the texture if the `TEXTURED` flag is enabled. 125 // This allows us to draw both textured and untextured shapes together in the same batch.126 let color = select(in.color, in.color * texture_color, enabled(in.flags, TEXTURED));128 // Signed distances. The magnitude is the distance of the point from the edge of the shape.129 // * Negative values indicate that the point is inside the shape.130 // * Zero values indicate the point is on the edge of the shape.131 // * Positive values indicate the point is outside the shape.133 // Signed distance from the exterior boundary.134 let external_distance = sd_rounded_box(in.point, in.size, in.radius);136 // Signed distance from the border's internal edge (the signed distance is negative if the point 137 // is inside the rect but not on the border).138 // If the border size is set to zero, this is the same as the external distance.139 let internal_distance = sd_inset_rounded_box(in.point, in.size, in.radius, in.border);141 // Signed distance from the border (the intersection of the rect with its border).142 // Points inside the border have negative signed distance. Any point outside the border, whether 143 // outside the outside edge, or inside the inner edge have positive signed distance.144 let border_distance = max(external_distance, -internal_distance);146#ifdef ANTI_ALIAS147 // At external edges with no border, `border_distance` is equal to zero. 148 // This select statement ensures we only perform anti-aliasing where a non-zero width border 149 // is present, otherwise an outline about the external boundary would be drawn even without 150 // a border.151 let t = select(1.0 - step(0.0, border_distance), antialias(border_distance), external_distance < internal_distance);152#else153 let t = 1.0 - step(0.0, border_distance);154#endif156 // Blend mode ALPHA_BLENDING is used for UI elements, so we don't premultiply alpha here.157 return vec4(color.rgb, saturate(color.a * t));158}160fn draw_background(in: VertexOutput, texture_color: vec4<f32>) -> vec4<f32> {161 let color = select(in.color, in.color * texture_color, enabled(in.flags, TEXTURED));163 // When drawing the background only draw the internal area and not the border.164 let internal_distance = sd_inset_rounded_box(in.point, in.size, in.radius, in.border);166#ifdef ANTI_ALIAS167 let t = antialias(internal_distance);168#else169 let t = 1.0 - step(0.0, internal_distance);170#endif172 return vec4(color.rgb, saturate(color.a * t));173}175@fragment176fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {177 let texture_color = textureSample(sprite_texture, sprite_sampler, in.uv);179 if enabled(in.flags, BORDER) {180 return draw(in, texture_color);181 } else {182 return draw_background(in, texture_color);183 }184}