Search across packages, versions, files, declarations, and comments. Use multiple terms to narrow results (AND), and add @vertex, @fragment, or @compute to filter by shader stage.
1import bevy_render::view::View;
3const TEXTURED = 1u;
4const RIGHT_VERTEX = 2u;
5const BOTTOM_VERTEX = 4u;
6// must align with BORDER_* shader_flags from bevy_ui/render/mod.rs
7const BORDER_LEFT: u32 = 256u;
8const BORDER_TOP: u32 = 512u;
9const BORDER_RIGHT: u32 = 1024u;
10const BORDER_BOTTOM: u32 = 2048u;
11const BORDER_ANY: u32 = BORDER_LEFT + BORDER_TOP + BORDER_RIGHT + BORDER_BOTTOM;
12const INVERT: u32 = 4096u;
14fn enabled(flags: u32, mask: u32) -> bool {
15 return (flags & mask) != 0u;
18@group(0) @binding(0) var<uniform> view: View;
20struct VertexOutput {
21 @location(0) uv: vec2<f32>,
22 @location(1) color: vec4<f32>,
24 @location(2) @interpolate(flat) size: vec2<f32>,
25 @location(3) @interpolate(flat) flags: u32,
26 @location(4) @interpolate(flat) radius_x: vec4<f32>,
27 @location(5) @interpolate(flat) radius_y: vec4<f32>,
28 @location(6) @interpolate(flat) border: vec4<f32>,
30 /// Position relative to the center of the rectangle.
31 @location(7) point: vec2<f32>,
32 @builtin(position) position: vec4<f32>,
33};
35@vertex
36fn vertex(
37 @location(0) vertex_position: vec3<f32>,
38 @location(1) vertex_uv: vec2<f32>,
39 @location(2) vertex_color: vec4<f32>,
40 @location(3) flags: u32,
42 /// x: top left, y: top right, z: bottom right, w: bottom left.
43 @location(4) radius_x: vec4<f32>,
44 @location(5) radius_y: vec4<f32>,
46 /// x: left, y: top, z: right, w: bottom.
47 @location(6) border: vec4<f32>,
48 @location(7) size: vec2<f32>,
49 @location(8) point: vec2<f32>,
50) -> VertexOutput {
51 var out: VertexOutput;
52 out.uv = vertex_uv;
53 out.position = view.clip_from_world * vec4(vertex_position, 1.0);
54 out.color = vertex_color;
55 out.flags = flags;
56 out.radius_x = radius_x;
57 out.radius_y = radius_y;
58 out.size = size;
59 out.border = border;
60 out.point = point;
62 return out;
65@group(1) @binding(0) var sprite_texture: texture_2d<f32>;
66@group(1) @binding(1) var sprite_sampler: sampler;
69/// Returns the radius of the corner closest to the given point.
70///
71/// Arguments:
72/// - `point` -> The point used to choose the closest corner.
73/// - `corner_radii_x` -> The horizontal radius of each rounded corner.
74/// - `corner_radii_y` -> The vertical radius of each rounded corner.
75/// Both ordered x: top left, y: top right, z: bottom right, w: bottom left.
76fn select_corner_radius(
77 point: vec2<f32>,
78 corner_radii_x: vec4<f32>,
79 corner_radii_y: vec4<f32>,
80) -> vec2<f32> {
81 // If 0.0 < y then select bottom left (w) and bottom right corner radius (z).
82 // Else select top left (x) and top right corner radius (y).
83 let rxs = select(corner_radii_x.xy, corner_radii_x.wz, 0.0 < point.y);
84 let rys = select(corner_radii_y.xy, corner_radii_y.wz, 0.0 < point.y);
85 // w and z are swapped above so that both pairs are in left to right order, otherwise this second
86 // select statement would return the incorrect value for the bottom pair.
87 return vec2(select(rxs.x, rxs.y, 0.0 < point.x), select(rys.x, rys.y, 0.0 < point.x));
90/// The returned value is the shortest distance from the given point to the boundary of the rounded
91/// box.
92///
93/// Negative values indicate that the point is inside the rounded box, positive values that the point
94/// is outside, and zero is exactly on the boundary.
95///
96/// Arguments:
97/// - `point` -> The function will return the distance from this point to the closest point on
98/// the boundary.
99/// - `size` -> The maximum width and height of the box.
100/// - `corner_radii_x` -> The horizontal semi-axis of each rounded corner. Ordered counter clockwise starting top left.
101/// - `corner_radii_y` -> The vertical semi-axis of each rounded corner. Ordered counter clockwise starting top left.
102fn sd_rounded_box(
103 point: vec2<f32>,
104 size: vec2<f32>,
105 corner_radii_x: vec4<f32>,
106 corner_radii_y: vec4<f32>,
107) -> f32 {
108 let radius = select_corner_radius(point, corner_radii_x, corner_radii_y);
109 // Vector from the corner closest to the point, to the point.
110 let corner_to_point = abs(point) - 0.5 * size;
111 let straight_distance = max(corner_to_point.x, corner_to_point.y);
112 if min(radius.x, radius.y) <= 0.0 {
113 return straight_distance;
114 }
115 // Vector from the center of the corner ellipse to the point.
116 let q = corner_to_point + radius;
117 let edge_distance = max(q.x - radius.x, q.y - radius.y);
118 let inv_radii_sq = 1.0 / (radius * radius);
119 let corner_distance = distance_to_ellipse_approx(q, inv_radii_sq, 1.0);
120 return select(edge_distance, corner_distance, q.x > 0.0 && q.y > 0.0);
123fn sd_inset_rounded_box(
124 point: vec2<f32>,
125 size: vec2<f32>,
126 radius_x: vec4<f32>,
127 radius_y: vec4<f32>,
128 inset: vec4<f32>,
129) -> f32 {
130 let inner_size = size - inset.xy - inset.zw;
131 let inner_center = inset.xy + 0.5 * inner_size - 0.5 * size;
132 let inner_point = point - inner_center;
134 var rx = radius_x;
135 var ry = radius_y;
137 // Top left corner.
138 rx.x = rx.x - inset.x;
139 ry.x = ry.x - inset.y;
141 // Top right corner.
142 rx.y = rx.y - inset.z;
143 ry.y = ry.y - inset.y;
145 // Bottom right corner.
146 rx.z = rx.z - inset.z;
147 ry.z = ry.z - inset.w;
149 // Bottom left corner.
150 rx.w = rx.w - inset.x;
151 ry.w = ry.w - inset.w;
153 let half_size = inner_size * 0.5;
155 rx = min(max(rx, vec4(0.0)), vec4<f32>(half_size.x));
156 ry = min(max(ry, vec4(0.0)), vec4<f32>(half_size.y));
157 let is_zero_radius = min(rx, ry) <= vec4(0.0);
158 rx = select(rx, vec4(0.0), is_zero_radius);
159 ry = select(ry, vec4(0.0), is_zero_radius);
161 return sd_rounded_box(inner_point, inner_size, rx, ry);
164fn nearest_border_active(point_vs_mid: vec2<f32>, size: vec2<f32>, width: vec4<f32>, flags: u32) -> bool {
165 if (flags & BORDER_ANY) == BORDER_ANY {
166 return true;
167 }
169 // get point vs top left
170 let point = clamp(point_vs_mid + size * 0.49999, vec2(0.0), size);
172 let left = point.x / width.x;
173 let top = point.y / width.y;
174 let right = (size.x - point.x) / width.z;
175 let bottom = (size.y - point.y) / width.w;
177 let min_dist = min(min(left, top), min(right, bottom));
179 return (enabled(flags, BORDER_LEFT) && min_dist == left) ||
180 (enabled(flags, BORDER_TOP) && min_dist == top) ||
181 (enabled(flags, BORDER_RIGHT) && min_dist == right) ||
182 (enabled(flags, BORDER_BOTTOM) && min_dist == bottom);
185/// get alpha for antialiasing for sdf
186fn antialias(distance: f32) -> f32 {
187 // Using the fwidth(distance) was causing artifacts, so just use the distance.
188 return saturate(0.5 - distance);
191fn draw_uinode_border(
192 color: vec4<f32>,
193 point: vec2<f32>,
194 size: vec2<f32>,
195 radius_x: vec4<f32>,
196 radius_y: vec4<f32>,
197 border: vec4<f32>,
198 flags: u32,
199) -> vec4<f32> {
200 // Signed distances. The magnitude is the distance of the point from the edge of the shape.
201 // * Negative values indicate that the point is inside the shape.
202 // * Zero values indicate the point is on the edge of the shape.
203 // * Positive values indicate the point is outside the shape.
205 // Signed distance from the exterior boundary.
206 let external_distance = sd_rounded_box(point, size, radius_x, radius_y);
208 // Signed distance from the border's internal edge (the signed distance is negative if the point
209 // is inside the rect but not on the border).
210 // If the border size is set to zero, this is the same as the external distance.
211 let internal_distance = sd_inset_rounded_box(point, size, radius_x, radius_y, border);
213 // Signed distance from the border (the intersection of the rect with its border).
214 // Points inside the border have negative signed distance. Any point outside the border, whether
215 // outside the outside edge, or inside the inner edge have positive signed distance.
216 let border_distance = max(external_distance, -internal_distance);
218 // check if this node should apply color for the nearest border
219 let nearest_border = select(0.0, 1.0, nearest_border_active(point, size, border, flags));
221 @if(ANTI_ALIAS)
222 // At external edges with no border, `border_distance` is equal to zero.
223 // This select statement ensures we only perform anti-aliasing where a non-zero width border
224 // is present, otherwise an outline about the external boundary would be drawn even without
225 // a border.
226 let t = select(1.0 - step(0.0, border_distance), antialias(border_distance), external_distance < internal_distance);
227 @else
228 let t = 1.0 - step(0.0, border_distance);
230 // Blend mode ALPHA_BLENDING is used for UI elements, so we don't premultiply alpha here.
231 return vec4(color.rgb, saturate(color.a * t * nearest_border));
234fn draw_uinode_background(
235 color: vec4<f32>,
236 point: vec2<f32>,
237 size: vec2<f32>,
238 radius_x: vec4<f32>,
239 radius_y: vec4<f32>,
240 border: vec4<f32>,
241 flags: u32,
242) -> vec4<f32> {
243 // When drawing the background only draw the internal area and not the border.
244 let internal_distance = sd_inset_rounded_box(point, size, radius_x, radius_y, border) * select(1., -1, enabled(flags, INVERT));
246 @if(ANTI_ALIAS)
247 let t = antialias(internal_distance);
248 @else
249 let t = 1.0 - step(0.0, internal_distance);
251 return vec4(color.rgb, saturate(color.a * t));
254@fragment
255fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
256 let texture_color = textureSample(sprite_texture, sprite_sampler, in.uv);
258 // Only use the color sampled from the texture if the `TEXTURED` flag is enabled.
259 // This allows us to draw both textured and untextured shapes together in the same batch.
260 let color = select(in.color, in.color * texture_color, enabled(in.flags, TEXTURED));
262 if enabled(in.flags, BORDER_ANY) {
263 return draw_uinode_border(color, in.point, in.size, in.radius_x, in.radius_y, in.border, in.flags);
264 } else {
265 return draw_uinode_background(color, in.point, in.size, in.radius_x, in.radius_y, in.border, in.flags);
266 }
269/// One iteration of Newton's method on the 2D equation of an ellipse:
270///
271/// E(x, y) = x^2/a^2 + y^2/b^2 - 1
272///
273/// The Jacobian of this equation is:
274///
275/// J(E(x, y)) = [ 2*x/a^2 2*y/b^2 ]
276///
277/// We approximate the distance with:
278///
279/// E(x, y) / ||J(E(x, y))||
280///
281/// See G. Taubin, "Distance Approximations for Rasterizing Implicit
282/// Curves", section 3.
283///
284/// A scale relative to the unit scale of the ellipse may be passed in to cause
285/// the math to degenerate to length(p) when scale is 0, or otherwise give the
286/// normal distance approximation if scale is 1.
287fn distance_to_ellipse_approx(p: vec2<f32>, inv_radii_sq: vec2<f32>, scale: f32) -> f32 {
288 let p_r = p * inv_radii_sq;
289 let g = dot(p, p_r) - scale;
290 let dG = (1.0 + scale) * p_r;
291 return g * inverseSqrt(dot(dG, dG));
Contents