1use crabslab::{Id, Slab, SlabItem};
7use glam::{Vec2, Vec4, Vec4Swizzles};
8use spirv_std::{image::Image2dArray, spirv, Sampler};
9
10use super::{GradientType, UiDrawCallDescriptor, UiElementType, UiVertex, UiViewport};
11use crate::atlas::shader::AtlasTextureDescriptor;
12
13fn sdf_rounded_rect(p: Vec2, half_ext: Vec2, radii: Vec4) -> f32 {
20 let r = if p.x > 0.0 {
22 if p.y > 0.0 {
23 radii.z
25 } else {
26 radii.y
28 }
29 } else if p.y > 0.0 {
30 radii.w
32 } else {
33 radii.x
35 };
36 let q = p.abs() - half_ext + Vec2::splat(r);
37 let outside = q.max(Vec2::ZERO).length();
38 let inside = q.x.max(q.y).min(0.0);
39 outside + inside - r
40}
41
42fn sdf_circle(p: Vec2, radius: f32) -> f32 {
44 p.length() - radius
45}
46
47fn sdf_ellipse(p: Vec2, radii: Vec2) -> f32 {
49 let p_norm = p / radii;
51 let d = p_norm.length() - 1.0;
52 d * radii.x.min(radii.y)
53}
54
55fn eval_gradient(
57 gradient_type: u32,
58 start: Vec2,
59 end: Vec2,
60 radius: f32,
61 color_start: Vec4,
62 color_end: Vec4,
63 local_uv: Vec2,
64) -> Vec4 {
65 let gt = GradientType::from_u32(gradient_type);
66 match gt {
67 GradientType::None => color_start,
68 GradientType::Linear => {
69 let dir = end - start;
70 let len_sq = dir.dot(dir);
71 let t = if len_sq > 0.0 {
72 let t = (local_uv - start).dot(dir) / len_sq;
73 t.clamp(0.0, 1.0)
74 } else {
75 0.0
76 };
77 color_start + (color_end - color_start) * t
78 }
79 GradientType::Radial => {
80 let d = (local_uv - start).length();
81 let t = if radius > 0.0 {
82 (d / radius).clamp(0.0, 1.0)
83 } else {
84 0.0
85 };
86 color_start + (color_end - color_start) * t
87 }
88 }
89}
90
91#[spirv(vertex)]
101pub fn ui_vertex(
102 #[spirv(vertex_index)] vertex_index: u32,
103 #[spirv(instance_index)] draw_call_id: Id<UiDrawCallDescriptor>,
104 #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] slab: &[u32],
105 out_uv: &mut Vec2,
106 out_color: &mut Vec4,
107 #[spirv(flat)] out_draw_call_id: &mut u32,
108 #[spirv(position)] out_clip_pos: &mut Vec4,
109) {
110 let viewport: UiViewport = slab.read_unchecked(Id::new(0));
111 let draw_call: UiDrawCallDescriptor = slab.read_unchecked(draw_call_id);
112
113 *out_draw_call_id = draw_call_id.inner();
114
115 match draw_call.element_type {
116 UiElementType::Path => {
117 let vertex_offset = draw_call.atlas_descriptor_id.inner();
121 let vertex_id =
122 Id::<UiVertex>::new(vertex_offset + vertex_index * UiVertex::SLAB_SIZE as u32);
123 let vertex: UiVertex = slab.read_unchecked(vertex_id);
124 *out_uv = vertex.uv;
125 *out_color = vertex.color;
126
127 let pos4 = viewport.projection
128 * Vec4::new(vertex.position.x, vertex.position.y, draw_call.z, 1.0);
129 *out_clip_pos = pos4;
130 }
131 _ => {
132 let vi = vertex_index % 6;
137 let (corner_x, corner_y) = match vi {
138 0 => (0.0f32, 0.0f32), 1 => (0.0, 1.0), 2 => (1.0, 1.0), 3 => (1.0, 1.0), 4 => (1.0, 0.0), _ => (0.0, 0.0), };
145
146 let local_uv = Vec2::new(corner_x, corner_y);
147 *out_uv = local_uv;
148 *out_color = draw_call.fill_color;
149
150 let screen_pos = draw_call.position
151 + Vec2::new(corner_x * draw_call.size.x, corner_y * draw_call.size.y);
152
153 let pos4 =
154 viewport.projection * Vec4::new(screen_pos.x, screen_pos.y, draw_call.z, 1.0);
155 *out_clip_pos = pos4;
156 }
157 }
158}
159
160#[spirv(fragment)]
165pub fn ui_fragment(
166 #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] slab: &[u32],
167 #[spirv(descriptor_set = 0, binding = 1)] atlas: &Image2dArray,
168 #[spirv(descriptor_set = 0, binding = 2)] atlas_sampler: &Sampler,
169 in_uv: Vec2,
170 in_color: Vec4,
171 #[spirv(flat)] in_draw_call_id: u32,
172 frag_color: &mut Vec4,
173) {
174 let draw_call_id = Id::<UiDrawCallDescriptor>::new(in_draw_call_id);
175 let draw_call: UiDrawCallDescriptor = slab.read_unchecked(draw_call_id);
176 #[allow(unused_assignments)]
177 let mut color = Vec4::ZERO;
178
179 match draw_call.element_type {
180 UiElementType::Path => {
181 color = in_color;
183 if !draw_call.atlas_texture_id.is_none() {
185 let atlas_tex: AtlasTextureDescriptor =
186 slab.read_unchecked(draw_call.atlas_texture_id);
187 let viewport: UiViewport = slab.read_unchecked(Id::new(0));
188 let atlas_uv = atlas_tex.uv(in_uv, viewport.atlas_size);
189 let sample: Vec4 = atlas.sample_by_lod(*atlas_sampler, atlas_uv, 0.0);
190 color *= sample;
191 }
192 }
193 UiElementType::TextGlyph => {
194 let atlas_tex: AtlasTextureDescriptor = slab.read_unchecked(draw_call.atlas_texture_id);
196 let viewport: UiViewport = slab.read_unchecked(Id::new(0));
197 let atlas_uv = atlas_tex.uv(in_uv, viewport.atlas_size);
198 let sample: Vec4 = atlas.sample_by_lod(*atlas_sampler, atlas_uv, 0.0);
199 color = draw_call.fill_color;
200 color.w *= sample.w;
201 }
202 UiElementType::Image => {
203 let atlas_tex: AtlasTextureDescriptor = slab.read_unchecked(draw_call.atlas_texture_id);
205 let viewport: UiViewport = slab.read_unchecked(Id::new(0));
206 let atlas_uv = atlas_tex.uv(in_uv, viewport.atlas_size);
207 color = atlas.sample_by_lod(*atlas_sampler, atlas_uv, 0.0);
208 color *= draw_call.fill_color;
210 }
211 _ => {
212 let half_size = draw_call.size * 0.5;
214 let local_pos = (in_uv - Vec2::splat(0.5)) * draw_call.size;
217
218 let distance = match draw_call.element_type {
219 UiElementType::Rectangle => {
220 sdf_rounded_rect(local_pos, half_size, draw_call.corner_radii)
221 }
222 UiElementType::Circle => {
223 let radius = half_size.x.min(half_size.y);
224 sdf_circle(local_pos, radius)
225 }
226 UiElementType::Ellipse => sdf_ellipse(local_pos, half_size),
227 _ => 0.0,
228 };
229
230 let fill = eval_gradient(
232 draw_call.gradient.gradient_type,
233 draw_call.gradient.start,
234 draw_call.gradient.end,
235 draw_call.gradient.radius,
236 draw_call.gradient.color_start,
237 draw_call.gradient.color_end,
238 in_uv,
239 );
240 let fill = if draw_call.gradient.gradient_type == 0 {
242 draw_call.fill_color
243 } else {
244 fill
245 };
246
247 let aa_width = 1.0; let fill_alpha = 1.0 - crate::math::smoothstep(-aa_width, aa_width, distance);
250
251 if draw_call.border_width > 0.0 {
252 let inner_distance = distance + draw_call.border_width;
255 let border_alpha =
256 1.0 - crate::math::smoothstep(-aa_width, aa_width, inner_distance);
257 let border_weight = fill_alpha - border_alpha;
259 let fill_weight = border_alpha;
260 let total = fill_alpha;
261 if total > 0.0 {
263 let rgb = (draw_call.border_color.xyz() * border_weight
264 + fill.xyz() * fill_weight)
265 / total;
266 let a =
267 (draw_call.border_color.w * border_weight + fill.w * fill_weight) / total;
268 color = rgb.extend(a * total);
269 } else {
270 color = Vec4::ZERO;
271 }
272 } else {
273 color = fill;
274 color.w *= fill_alpha;
275 }
276 }
277 }
278
279 color.w *= draw_call.opacity;
281
282 color = (color.xyz() * color.w).extend(color.w);
284
285 *frag_color = color;
286}