1use oxiui_core::Color;
25
26#[repr(C)]
30#[derive(Clone, Copy, Debug, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
31pub struct Vertex {
32 pub position: [f32; 2],
34 pub color: [f32; 4],
36 pub local: [f32; 2],
38 pub shape_xy: [f32; 2],
41 pub shape_r: f32,
45 pub kind: f32,
49 pub extra: [f32; 2],
56}
57
58pub const KIND_RECT: f32 = 0.0;
62pub const KIND_CIRCLE: f32 = 1.0;
64pub const KIND_ROUNDED_RECT: f32 = 2.0;
66pub const KIND_ROUNDED_RECT_PC: f32 = 3.0;
68pub const KIND_ELLIPSE: f32 = 4.0;
70pub const KIND_LINE_SEG: f32 = 5.0;
72
73const _: () = assert!(core::mem::size_of::<Vertex>() == 56);
75const _: () = assert!(core::mem::align_of::<Vertex>() == 4);
76
77impl Vertex {
78 #[inline]
80 pub fn color_to_f32(color: Color) -> [f32; 4] {
81 [
82 color.0 as f32 / 255.0,
83 color.1 as f32 / 255.0,
84 color.2 as f32 / 255.0,
85 color.3 as f32 / 255.0,
86 ]
87 }
88}
89
90#[repr(C)]
96#[derive(Clone, Copy, Debug, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
97pub struct Globals {
98 pub viewport: [f32; 2],
100 pub _pad: [f32; 2],
102}
103
104const _: () = assert!(core::mem::size_of::<Globals>() == 16);
105
106impl Globals {
107 #[inline]
109 pub fn new(width: u32, height: u32) -> Self {
110 Self {
111 viewport: [width as f32, height as f32],
112 _pad: [0.0, 0.0],
113 }
114 }
115}
116
117#[repr(C)]
121#[derive(Clone, Copy, Debug, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
122pub struct GradientVertex {
123 pub position: [f32; 2],
125 pub local: [f32; 2],
128}
129
130const _: () = assert!(core::mem::size_of::<GradientVertex>() == 16);
131
132pub const MAX_GRADIENT_STOPS: usize = 8;
134
135#[repr(C)]
140#[derive(Clone, Copy, Debug, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
141pub struct GradientUniforms {
142 pub p0: [f32; 2],
145 pub p1: [f32; 2],
148 pub radius: f32,
150 pub gradient_type: u32,
152 pub stop_count: u32,
154 pub _pad: u32,
156 pub stop_offsets: [[f32; 4]; MAX_GRADIENT_STOPS],
158 pub stop_colors: [[f32; 4]; MAX_GRADIENT_STOPS],
160}
161
162const _: () = assert!(core::mem::size_of::<GradientUniforms>() == 288);
165
166#[inline]
173pub fn pack_u16_pair(hi: u16, lo: u16) -> f32 {
174 f32::from_bits(((hi as u32) << 16) | (lo as u32))
175}
176
177pub fn push_rect_quad(out: &mut Vec<Vertex>, x: f32, y: f32, w: f32, h: f32, color: Color) {
182 let rgba = Vertex::color_to_f32(color);
183 let x1 = x + w;
184 let y1 = y + h;
185 let corners = [[x, y], [x, y1], [x1, y1], [x, y], [x1, y1], [x1, y]];
186 for c in corners {
187 out.push(Vertex {
188 position: c,
189 color: rgba,
190 local: c,
191 shape_xy: [0.0, 0.0],
192 shape_r: 0.0,
193 kind: KIND_RECT,
194 extra: [0.0, 0.0],
195 });
196 }
197}
198
199pub fn push_circle_quad(out: &mut Vec<Vertex>, cx: f32, cy: f32, radius: f32, color: Color) {
202 let rgba = Vertex::color_to_f32(color);
203 let r = radius + 1.0;
204 let x0 = cx - r;
205 let y0 = cy - r;
206 let x1 = cx + r;
207 let y1 = cy + r;
208 let corners = [[x0, y0], [x0, y1], [x1, y1], [x0, y0], [x1, y1], [x1, y0]];
209 for c in corners {
210 out.push(Vertex {
211 position: c,
212 color: rgba,
213 local: c,
214 shape_xy: [cx, cy],
215 shape_r: radius,
216 kind: KIND_CIRCLE,
217 extra: [0.0, 0.0],
218 });
219 }
220}
221
222pub fn push_rounded_rect_quad(
226 out: &mut Vec<Vertex>,
227 x: f32,
228 y: f32,
229 w: f32,
230 h: f32,
231 radius: f32,
232 color: Color,
233) {
234 let rgba = Vertex::color_to_f32(color);
235 let r = radius.min(w * 0.5).min(h * 0.5).max(0.0);
236 let cx = x + w * 0.5;
237 let cy = y + h * 0.5;
238 let hw = w * 0.5;
239 let hh = h * 0.5;
240 let pad = 1.0_f32;
241 let x0 = x - pad;
242 let y0 = y - pad;
243 let x1 = x + w + pad;
244 let y1 = y + h + pad;
245 let corners = [[x0, y0], [x0, y1], [x1, y1], [x0, y0], [x1, y1], [x1, y0]];
246 for c in corners {
247 out.push(Vertex {
248 position: c,
249 color: rgba,
250 local: c,
251 shape_xy: [cx, cy],
252 shape_r: r,
253 kind: KIND_ROUNDED_RECT,
254 extra: [hw, hh],
255 });
256 }
257}
258
259pub fn push_rounded_rect_per_corner_quad(
273 out: &mut Vec<Vertex>,
274 x: f32,
275 y: f32,
276 w: f32,
277 h: f32,
278 radii: [f32; 4],
279 color: Color,
280) {
281 let rgba = Vertex::color_to_f32(color);
282 let [tl, tr, br, bl] = radii;
283 let hw = w * 0.5;
284 let hh = h * 0.5;
285 let cx = x + hw;
286 let cy = y + hh;
287 let clamp_r = |r: f32| r.clamp(0.0, hw.min(hh).min(255.0));
288 let tl = clamp_r(tl);
289 let tr = clamp_r(tr);
290 let br = clamp_r(br);
291 let bl = clamp_r(bl);
292 let hw_c = hw.clamp(0.0, 4095.0);
293 let hh_c = hh.clamp(0.0, 4095.0);
294 let r_packed = tl.floor() * 256.0 + tr.floor();
296 let brbl_packed = br.floor() * 256.0 + bl.floor();
297 let hwhh_packed = hw_c.floor() * 4096.0 + hh_c.floor();
298 let pad = 1.0_f32;
299 let x0 = x - pad;
300 let y0 = y - pad;
301 let x1 = x + w + pad;
302 let y1 = y + h + pad;
303 let corners = [[x0, y0], [x0, y1], [x1, y1], [x0, y0], [x1, y1], [x1, y0]];
304 for c in corners {
305 out.push(Vertex {
306 position: c,
307 color: rgba,
308 local: c,
309 shape_xy: [cx, cy],
310 shape_r: r_packed,
311 kind: KIND_ROUNDED_RECT_PC,
312 extra: [brbl_packed, hwhh_packed],
313 });
314 }
315}
316
317pub fn push_ellipse_quad(out: &mut Vec<Vertex>, cx: f32, cy: f32, rx: f32, ry: f32, color: Color) {
320 let rgba = Vertex::color_to_f32(color);
321 let pad = 1.0_f32;
322 let x0 = cx - rx - pad;
323 let y0 = cy - ry - pad;
324 let x1 = cx + rx + pad;
325 let y1 = cy + ry + pad;
326 let corners = [[x0, y0], [x0, y1], [x1, y1], [x0, y0], [x1, y1], [x1, y0]];
327 for c in corners {
328 out.push(Vertex {
329 position: c,
330 color: rgba,
331 local: c,
332 shape_xy: [cx, cy],
333 shape_r: 0.0,
334 kind: KIND_ELLIPSE,
335 extra: [rx, ry],
336 });
337 }
338}
339
340pub struct LineQuadParams {
342 pub from_x: f32,
344 pub from_y: f32,
346 pub to_x: f32,
348 pub to_y: f32,
350 pub half_width: f32,
352 pub color: Color,
354 pub aa_smooth: bool,
356}
357
358pub fn push_line_quad(out: &mut Vec<Vertex>, params: LineQuadParams) {
366 let LineQuadParams {
367 from_x,
368 from_y,
369 to_x,
370 to_y,
371 half_width,
372 color,
373 aa_smooth,
374 } = params;
375 let rgba = Vertex::color_to_f32(color);
376 let dx = to_x - from_x;
377 let dy = to_y - from_y;
378 let len = (dx * dx + dy * dy).sqrt().max(1e-6);
379 let nx = -dy / len;
381 let ny = dx / len;
382 let lx = dx / len;
384 let ly = dy / len;
385 let expand = half_width + 1.0;
386 let cap = half_width + 1.0;
387 let ax = from_x - lx * cap + nx * expand;
390 let ay = from_y - ly * cap + ny * expand;
391 let bx = from_x - lx * cap - nx * expand;
392 let by = from_y - ly * cap - ny * expand;
393 let cx = to_x + lx * cap - nx * expand;
394 let cy_v = to_y + ly * cap - ny * expand;
395 let dxp = to_x + lx * cap + nx * expand;
396 let dyp = to_y + ly * cap + ny * expand;
397 let corners = [
399 [ax, ay],
400 [bx, by],
401 [cx, cy_v],
402 [ax, ay],
403 [cx, cy_v],
404 [dxp, dyp],
405 ];
406 let shape_r_val = if aa_smooth {
409 half_width + 0.5
410 } else {
411 half_width
412 };
413 for c in corners {
414 out.push(Vertex {
415 position: c,
416 color: rgba,
417 local: c,
418 shape_xy: [from_x, from_y],
419 shape_r: shape_r_val,
420 kind: KIND_LINE_SEG,
421 extra: [to_x, to_y],
422 });
423 }
424}
425
426pub fn push_triangle(
428 out: &mut Vec<Vertex>,
429 p0: [f32; 2],
430 p1: [f32; 2],
431 p2: [f32; 2],
432 color: Color,
433) {
434 let rgba = Vertex::color_to_f32(color);
435 for c in [p0, p1, p2] {
436 out.push(Vertex {
437 position: c,
438 color: rgba,
439 local: c,
440 shape_xy: [0.0, 0.0],
441 shape_r: 0.0,
442 kind: KIND_RECT,
443 extra: [0.0, 0.0],
444 });
445 }
446}
447
448pub fn push_gradient_quad(out: &mut Vec<GradientVertex>, x: f32, y: f32, w: f32, h: f32) {
450 let x1 = x + w;
451 let y1 = y + h;
452 let corners = [[x, y], [x, y1], [x1, y1], [x, y], [x1, y1], [x1, y]];
453 for c in corners {
454 out.push(GradientVertex {
455 position: c,
456 local: c,
457 });
458 }
459}
460
461#[cfg(test)]
464mod tests {
465 use super::*;
466
467 #[test]
468 fn vertex_size_is_56_bytes() {
469 assert_eq!(core::mem::size_of::<Vertex>(), 56);
470 }
471
472 #[test]
473 fn globals_size_is_16_bytes() {
474 assert_eq!(core::mem::size_of::<Globals>(), 16);
475 }
476
477 #[test]
478 fn gradient_vertex_size_is_16_bytes() {
479 assert_eq!(core::mem::size_of::<GradientVertex>(), 16);
480 }
481
482 #[test]
483 fn gradient_uniforms_size_is_288_bytes() {
484 assert_eq!(core::mem::size_of::<GradientUniforms>(), 288);
485 }
486
487 #[test]
488 fn color_to_f32_maps_full_range() {
489 let white = Vertex::color_to_f32(Color(255, 255, 255, 255));
490 assert!((white[0] - 1.0).abs() < 1e-6);
491 assert!((white[3] - 1.0).abs() < 1e-6);
492 let black = Vertex::color_to_f32(Color(0, 0, 0, 0));
493 assert_eq!(black, [0.0, 0.0, 0.0, 0.0]);
494 }
495
496 #[test]
497 fn rect_quad_emits_six_vertices() {
498 let mut v = Vec::new();
499 push_rect_quad(&mut v, 1.0, 2.0, 3.0, 4.0, Color(255, 0, 0, 255));
500 assert_eq!(v.len(), 6);
501 for vert in &v {
502 assert_eq!(vert.kind, KIND_RECT);
503 }
504 let xs: Vec<f32> = v.iter().map(|vt| vt.position[0]).collect();
505 assert!(xs.contains(&1.0));
506 assert!(xs.contains(&4.0));
507 }
508
509 #[test]
510 fn circle_quad_emits_six_vertices_with_center() {
511 let mut v = Vec::new();
512 push_circle_quad(&mut v, 10.0, 10.0, 5.0, Color(0, 255, 0, 255));
513 assert_eq!(v.len(), 6);
514 for vert in &v {
515 assert_eq!(vert.kind, KIND_CIRCLE);
516 assert_eq!(vert.shape_xy, [10.0, 10.0]);
517 assert!((vert.shape_r - 5.0).abs() < 1e-6);
518 }
519 }
520
521 #[test]
522 fn vertices_are_pod_castable() {
523 let mut v = Vec::new();
524 push_rect_quad(&mut v, 0.0, 0.0, 1.0, 1.0, Color(1, 2, 3, 4));
525 let bytes: &[u8] = bytemuck::cast_slice(&v);
526 assert_eq!(bytes.len(), 6 * 56);
527 }
528
529 #[test]
530 fn rounded_rect_quad_emits_six_vertices() {
531 let mut v = Vec::new();
532 push_rounded_rect_quad(&mut v, 10.0, 10.0, 80.0, 40.0, 8.0, Color(0, 0, 255, 255));
533 assert_eq!(v.len(), 6);
534 for vert in &v {
535 assert_eq!(vert.kind, KIND_ROUNDED_RECT);
536 }
537 }
538
539 #[test]
540 fn rounded_rect_pc_quad_emits_six_vertices() {
541 let mut v = Vec::new();
542 push_rounded_rect_per_corner_quad(
543 &mut v,
544 10.0,
545 10.0,
546 80.0,
547 40.0,
548 [4.0, 8.0, 4.0, 8.0],
549 Color(0, 0, 255, 255),
550 );
551 assert_eq!(v.len(), 6);
552 for vert in &v {
553 assert_eq!(vert.kind, KIND_ROUNDED_RECT_PC);
554 }
555 }
556
557 #[test]
558 fn ellipse_quad_emits_six_vertices() {
559 let mut v = Vec::new();
560 push_ellipse_quad(&mut v, 50.0, 50.0, 30.0, 20.0, Color(255, 255, 0, 255));
561 assert_eq!(v.len(), 6);
562 for vert in &v {
563 assert_eq!(vert.kind, KIND_ELLIPSE);
564 assert!((vert.extra[0] - 30.0).abs() < 1e-4);
565 assert!((vert.extra[1] - 20.0).abs() < 1e-4);
566 }
567 }
568
569 #[test]
570 fn line_quad_emits_six_vertices() {
571 let mut v = Vec::new();
572 push_line_quad(
573 &mut v,
574 LineQuadParams {
575 from_x: 0.0,
576 from_y: 0.0,
577 to_x: 100.0,
578 to_y: 0.0,
579 half_width: 2.0,
580 color: Color(255, 0, 0, 255),
581 aa_smooth: true,
582 },
583 );
584 assert_eq!(v.len(), 6);
585 for vert in &v {
586 assert_eq!(vert.kind, KIND_LINE_SEG);
587 }
588 }
589
590 #[test]
591 fn push_triangle_emits_three_vertices() {
592 let mut v = Vec::new();
593 push_triangle(
594 &mut v,
595 [0.0, 0.0],
596 [10.0, 0.0],
597 [5.0, 8.0],
598 Color(255, 255, 255, 255),
599 );
600 assert_eq!(v.len(), 3);
601 for vert in &v {
602 assert_eq!(vert.kind, KIND_RECT);
603 }
604 }
605
606 #[test]
607 fn gradient_quad_emits_six_vertices() {
608 let mut v = Vec::new();
609 push_gradient_quad(&mut v, 0.0, 0.0, 100.0, 50.0);
610 assert_eq!(v.len(), 6);
611 }
612
613 #[test]
614 fn pack_u16_pair_round_trips() {
615 let packed = pack_u16_pair(42, 1000);
616 let bits = packed.to_bits();
617 let hi = (bits >> 16) as u16;
618 let lo = (bits & 0xffff) as u16;
619 assert_eq!(hi, 42);
620 assert_eq!(lo, 1000);
621 }
622}