1use oxiui_core::geometry::Rect;
17use oxiui_core::paint::{DrawCommand, DrawList, GradientStop, ImageFilter};
18use oxiui_core::Color;
19
20use crate::clip::{ClipRect, ClipStack};
21use crate::gpu::buffer::{
22 push_circle_quad, push_ellipse_quad, push_gradient_quad, push_line_quad, push_nine_slice_quads,
23 push_rect_quad, push_rounded_rect_per_corner_quad, push_rounded_rect_quad, push_textured_quad,
24 GradientUniforms, GradientVertex, LineQuadParams, TexQuadParams, Vertex, MAX_GRADIENT_STOPS,
25};
26use crate::gpu::tessellator::{tessellate_fill, tessellate_stroke};
27use crate::gpu::texture::TexturedDraw;
28
29#[derive(Clone, Copy, Debug)]
33pub(crate) struct DrawSegment {
34 pub(crate) start: u32,
35 pub(crate) end: u32,
36 pub(crate) scissor: Option<[u32; 4]>,
37}
38
39pub(crate) struct GradientDraw {
43 pub(crate) verts: Vec<GradientVertex>,
44 pub(crate) uniforms: GradientUniforms,
45 pub(crate) scissor: Option<[u32; 4]>,
46}
47
48#[allow(dead_code)] pub(crate) struct BackdropBlurDraw {
56 pub(crate) rect: [f32; 4],
58 pub(crate) blur_radius: f32,
60}
61
62pub(crate) fn scissor_from_stack(
66 stack: &ClipStack,
67 viewport_w: u32,
68 viewport_h: u32,
69) -> Option<[u32; 4]> {
70 let raw = stack.as_scissor()?;
71 Some(clamp_scissor(raw, viewport_w, viewport_h))
72}
73
74pub(crate) fn clamp_scissor([x, y, w, h]: [u32; 4], viewport_w: u32, viewport_h: u32) -> [u32; 4] {
76 let x = x.min(viewport_w);
77 let y = y.min(viewport_h);
78 let w = w.min(viewport_w - x);
79 let h = h.min(viewport_h - y);
80 [x, y, w, h]
81}
82
83pub(crate) type GeometryOutput = (
87 Vec<Vertex>,
88 Vec<DrawSegment>,
89 Vec<GradientDraw>,
90 Vec<TexturedDraw>,
91 Vec<BackdropBlurDraw>,
92);
93
94pub(crate) fn build_geometry(list: &DrawList, viewport_w: u32, viewport_h: u32) -> GeometryOutput {
99 let mut verts: Vec<Vertex> = Vec::new();
100 let mut segments: Vec<DrawSegment> = Vec::new();
101 let mut gradient_draws: Vec<GradientDraw> = Vec::new();
102 let mut textured_draws: Vec<TexturedDraw> = Vec::new();
103 let mut backdrop_blur_draws: Vec<BackdropBlurDraw> = Vec::new();
104 let mut stack = ClipStack::new();
105
106 let mut current_scissor = scissor_from_stack(&stack, viewport_w, viewport_h);
107 let mut segment_start: u32 = 0;
108
109 let flush = |segs: &mut Vec<DrawSegment>, start: u32, end: u32, sc: Option<[u32; 4]>| {
110 if end > start {
111 segs.push(DrawSegment {
112 start,
113 end,
114 scissor: sc,
115 });
116 }
117 };
118
119 for cmd in list.iter() {
120 match cmd {
122 DrawCommand::PushClip { rect } => {
123 flush(
124 &mut segments,
125 segment_start,
126 verts.len() as u32,
127 current_scissor,
128 );
129 stack.push(ClipRect::new(
130 rect.left(),
131 rect.top(),
132 rect.width(),
133 rect.height(),
134 ));
135 current_scissor = scissor_from_stack(&stack, viewport_w, viewport_h);
136 segment_start = verts.len() as u32;
137 continue;
138 }
139 DrawCommand::PopClip => {
140 flush(
141 &mut segments,
142 segment_start,
143 verts.len() as u32,
144 current_scissor,
145 );
146 stack.pop();
147 current_scissor = scissor_from_stack(&stack, viewport_w, viewport_h);
148 segment_start = verts.len() as u32;
149 continue;
150 }
151 _ => {}
152 }
153
154 if let Some(scissor) = current_scissor {
160 if scissor[2] == 0 || scissor[3] == 0 {
162 continue;
163 }
164 if let Some(bounds) = cmd_bounds(cmd) {
165 let scissor_rect = scissor_to_rect(scissor);
166 if !rects_intersect(&bounds, &scissor_rect) {
167 continue;
168 }
169 }
170 }
171
172 match cmd {
174 DrawCommand::PushClip { .. } | DrawCommand::PopClip => {}
176
177 DrawCommand::FillRect { rect, color } => {
178 push_rect_quad(
179 &mut verts,
180 rect.left(),
181 rect.top(),
182 rect.width(),
183 rect.height(),
184 *color,
185 );
186 }
187 DrawCommand::StrokeRect {
188 rect,
189 thickness,
190 color,
191 } => {
192 emit_stroke_rect(
193 &mut verts,
194 rect.left(),
195 rect.top(),
196 rect.width(),
197 rect.height(),
198 *thickness,
199 *color,
200 );
201 }
202 DrawCommand::FillRoundedRect {
203 rect,
204 radius,
205 color,
206 } => {
207 push_rounded_rect_quad(
208 &mut verts,
209 rect.left(),
210 rect.top(),
211 rect.width(),
212 rect.height(),
213 *radius,
214 *color,
215 );
216 }
217 DrawCommand::FillRoundedRectPerCorner { rect, radii, color } => {
218 push_rounded_rect_per_corner_quad(
219 &mut verts,
220 rect.left(),
221 rect.top(),
222 rect.width(),
223 rect.height(),
224 *radii,
225 *color,
226 );
227 }
228 DrawCommand::FillCircle {
229 center,
230 radius,
231 color,
232 } => {
233 push_circle_quad(&mut verts, center.x, center.y, *radius, *color);
234 }
235 DrawCommand::FillEllipse {
236 center,
237 rx,
238 ry,
239 color,
240 } => {
241 push_ellipse_quad(&mut verts, center.x, center.y, *rx, *ry, *color);
242 }
243 DrawCommand::Line { from, to, color } => {
244 push_line_quad(
245 &mut verts,
246 LineQuadParams {
247 from_x: from.x,
248 from_y: from.y,
249 to_x: to.x,
250 to_y: to.y,
251 half_width: 0.5,
252 color: *color,
253 aa_smooth: false,
254 },
255 );
256 }
257 DrawCommand::LineAa { from, to, color } => {
258 push_line_quad(
259 &mut verts,
260 LineQuadParams {
261 from_x: from.x,
262 from_y: from.y,
263 to_x: to.x,
264 to_y: to.y,
265 half_width: 0.5,
266 color: *color,
267 aa_smooth: true,
268 },
269 );
270 }
271 DrawCommand::LineThick {
272 from,
273 to,
274 width,
275 color,
276 } => {
277 push_line_quad(
278 &mut verts,
279 LineQuadParams {
280 from_x: from.x,
281 from_y: from.y,
282 to_x: to.x,
283 to_y: to.y,
284 half_width: width * 0.5,
285 color: *color,
286 aa_smooth: true,
287 },
288 );
289 }
290 DrawCommand::LineDashed {
291 from,
292 to,
293 dash_len,
294 gap_len,
295 color,
296 } => {
297 emit_dashed_line(
298 &mut verts,
299 DashedLineParams {
300 x0: from.x,
301 y0: from.y,
302 x1: to.x,
303 y1: to.y,
304 dash_len: *dash_len,
305 gap_len: *gap_len,
306 color: *color,
307 },
308 );
309 }
310 DrawCommand::FillPath { path, color } => {
311 tessellate_fill(&mut verts, path, *color);
312 }
313 DrawCommand::StrokePath { path, style, color } => {
314 tessellate_stroke(&mut verts, path, style, *color);
315 }
316 DrawCommand::LinearGradient {
317 rect,
318 start,
319 end,
320 stops,
321 } => {
322 if let Some(gd) = build_gradient_draw_linear(LinearGradientParams {
323 x: rect.left(),
324 y: rect.top(),
325 w: rect.width(),
326 h: rect.height(),
327 sx: start.x,
328 sy: start.y,
329 ex: end.x,
330 ey: end.y,
331 stops,
332 scissor: current_scissor,
333 }) {
334 gradient_draws.push(gd);
335 }
336 }
337 DrawCommand::RadialGradient {
338 rect,
339 center,
340 radius,
341 stops,
342 } => {
343 if let Some(gd) = build_gradient_draw_radial(RadialGradientParams {
344 x: rect.left(),
345 y: rect.top(),
346 w: rect.width(),
347 h: rect.height(),
348 cx: center.x,
349 cy: center.y,
350 radius: *radius,
351 stops,
352 scissor: current_scissor,
353 }) {
354 gradient_draws.push(gd);
355 }
356 }
357 DrawCommand::Image {
358 image,
359 dest,
360 filter,
361 } => {
362 let mut tex_verts = Vec::new();
363 push_textured_quad(
364 &mut tex_verts,
365 TexQuadParams {
366 x: dest.left(),
367 y: dest.top(),
368 w: dest.width(),
369 h: dest.height(),
370 u0: 0.0,
371 v0: 0.0,
372 u1: 1.0,
373 v1: 1.0,
374 tint: [1.0, 1.0, 1.0, 1.0],
375 },
376 );
377 textured_draws.push(TexturedDraw {
378 verts: tex_verts,
379 image: image.clone(),
380 filter: *filter,
381 scissor: current_scissor,
382 });
383 }
384 DrawCommand::NineSlice {
385 image,
386 dest,
387 insets,
388 } => {
389 let mut tex_verts = Vec::new();
390 push_nine_slice_quads(
391 &mut tex_verts,
392 [dest.left(), dest.top(), dest.width(), dest.height()],
393 image.width,
394 image.height,
395 *insets,
396 [1.0, 1.0, 1.0, 1.0],
397 );
398 textured_draws.push(TexturedDraw {
399 verts: tex_verts,
400 image: image.clone(),
401 filter: ImageFilter::Nearest,
402 scissor: current_scissor,
403 });
404 }
405 DrawCommand::BackdropBlur { rect, blur_radius } => {
406 backdrop_blur_draws.push(BackdropBlurDraw {
410 rect: [rect.left(), rect.top(), rect.width(), rect.height()],
411 blur_radius: *blur_radius,
412 });
413 }
414 _ => {}
424 }
425 }
426
427 flush(
428 &mut segments,
429 segment_start,
430 verts.len() as u32,
431 current_scissor,
432 );
433 (
434 verts,
435 segments,
436 gradient_draws,
437 textured_draws,
438 backdrop_blur_draws,
439 )
440}
441
442pub(crate) fn cmd_bounds(cmd: &DrawCommand) -> Option<Rect> {
451 match cmd {
452 DrawCommand::FillRect { rect, .. }
453 | DrawCommand::StrokeRect { rect, .. }
454 | DrawCommand::FillRoundedRect { rect, .. }
455 | DrawCommand::FillRoundedRectPerCorner { rect, .. }
456 | DrawCommand::LinearGradient { rect, .. }
457 | DrawCommand::RadialGradient { rect, .. }
458 | DrawCommand::Image { dest: rect, .. }
459 | DrawCommand::NineSlice { dest: rect, .. }
460 | DrawCommand::DrawText { rect, .. } => Some(*rect),
461
462 DrawCommand::FillCircle { center, radius, .. } => Some(Rect::new(
463 center.x - radius,
464 center.y - radius,
465 radius * 2.0,
466 radius * 2.0,
467 )),
468
469 DrawCommand::FillEllipse { center, rx, ry, .. } => {
470 Some(Rect::new(center.x - rx, center.y - ry, rx * 2.0, ry * 2.0))
471 }
472
473 DrawCommand::Line { from, to, .. } | DrawCommand::LineAa { from, to, .. } => {
474 Some(rect_from_points(*from, *to))
475 }
476
477 DrawCommand::LineThick {
478 from, to, width, ..
479 } => {
480 let r = rect_from_points(*from, *to);
481 Some(Rect::new(
482 r.left() - width,
483 r.top() - width,
484 r.width() + width * 2.0,
485 r.height() + width * 2.0,
486 ))
487 }
488
489 DrawCommand::LineDashed { from, to, .. } => Some(rect_from_points(*from, *to)),
490
491 DrawCommand::FillPath { path, .. } => path.bounds(),
492
493 DrawCommand::StrokePath { path, style, .. } => path.bounds().map(|b| {
494 let pad = style.width / 2.0;
495 Rect::new(
496 b.left() - pad,
497 b.top() - pad,
498 b.width() + style.width,
499 b.height() + style.width,
500 )
501 }),
502
503 DrawCommand::PushClip { .. } | DrawCommand::PopClip => None,
505
506 DrawCommand::BoxShadow { .. } => None,
510
511 _ => None,
513 }
514}
515
516pub(crate) fn rect_from_points(
519 a: oxiui_core::geometry::Point,
520 b: oxiui_core::geometry::Point,
521) -> Rect {
522 let x = a.x.min(b.x);
523 let y = a.y.min(b.y);
524 let w = (a.x - b.x).abs().max(1.0);
525 let h = (a.y - b.y).abs().max(1.0);
526 Rect::new(x, y, w, h)
527}
528
529pub(crate) fn rects_intersect(a: &Rect, b: &Rect) -> bool {
533 a.left() < b.left() + b.width()
534 && a.left() + a.width() > b.left()
535 && a.top() < b.top() + b.height()
536 && a.top() + a.height() > b.top()
537}
538
539pub(crate) fn scissor_to_rect(s: [u32; 4]) -> Rect {
542 Rect::new(s[0] as f32, s[1] as f32, s[2] as f32, s[3] as f32)
543}
544
545pub(crate) fn emit_stroke_rect(
548 out: &mut Vec<Vertex>,
549 x: f32,
550 y: f32,
551 w: f32,
552 h: f32,
553 t: f32,
554 color: Color,
555) {
556 push_rect_quad(out, x, y, w, t, color);
557 push_rect_quad(out, x, y + h - t, w, t, color);
558 push_rect_quad(out, x, y + t, t, h - 2.0 * t, color);
559 push_rect_quad(out, x + w - t, y + t, t, h - 2.0 * t, color);
560}
561
562pub(crate) struct DashedLineParams {
563 pub(crate) x0: f32,
564 pub(crate) y0: f32,
565 pub(crate) x1: f32,
566 pub(crate) y1: f32,
567 pub(crate) dash_len: f32,
568 pub(crate) gap_len: f32,
569 pub(crate) color: Color,
570}
571
572pub(crate) fn emit_dashed_line(out: &mut Vec<Vertex>, p: DashedLineParams) {
573 let DashedLineParams {
574 x0,
575 y0,
576 x1,
577 y1,
578 dash_len,
579 gap_len,
580 color,
581 } = p;
582 let dx = x1 - x0;
583 let dy = y1 - y0;
584 let total = (dx * dx + dy * dy).sqrt();
585 if total < 1e-6 || dash_len <= 0.0 {
586 return;
587 }
588 let ux = dx / total;
589 let uy = dy / total;
590 let period = dash_len + gap_len.max(0.0);
591 if period < 1e-6 {
592 return;
593 }
594 let mut t = 0.0_f32;
595 while t < total {
596 let end = (t + dash_len).min(total);
597 push_line_quad(
598 out,
599 LineQuadParams {
600 from_x: x0 + ux * t,
601 from_y: y0 + uy * t,
602 to_x: x0 + ux * end,
603 to_y: y0 + uy * end,
604 half_width: 0.5,
605 color,
606 aa_smooth: false,
607 },
608 );
609 t += period;
610 }
611}
612
613pub(crate) struct LinearGradientParams<'a> {
614 pub(crate) x: f32,
615 pub(crate) y: f32,
616 pub(crate) w: f32,
617 pub(crate) h: f32,
618 pub(crate) sx: f32,
619 pub(crate) sy: f32,
620 pub(crate) ex: f32,
621 pub(crate) ey: f32,
622 pub(crate) stops: &'a [GradientStop],
623 pub(crate) scissor: Option<[u32; 4]>,
624}
625
626pub(crate) fn build_gradient_draw_linear(p: LinearGradientParams<'_>) -> Option<GradientDraw> {
627 let LinearGradientParams {
628 x,
629 y,
630 w,
631 h,
632 sx,
633 sy,
634 ex,
635 ey,
636 stops,
637 scissor,
638 } = p;
639 let uniforms = build_gradient_uniforms(0, [sx, sy], [ex, ey], 0.0, stops)?;
640 let mut verts = Vec::new();
641 push_gradient_quad(&mut verts, x, y, w, h);
642 Some(GradientDraw {
643 verts,
644 uniforms,
645 scissor,
646 })
647}
648
649pub(crate) struct RadialGradientParams<'a> {
650 pub(crate) x: f32,
651 pub(crate) y: f32,
652 pub(crate) w: f32,
653 pub(crate) h: f32,
654 pub(crate) cx: f32,
655 pub(crate) cy: f32,
656 pub(crate) radius: f32,
657 pub(crate) stops: &'a [GradientStop],
658 pub(crate) scissor: Option<[u32; 4]>,
659}
660
661pub(crate) fn build_gradient_draw_radial(p: RadialGradientParams<'_>) -> Option<GradientDraw> {
662 let RadialGradientParams {
663 x,
664 y,
665 w,
666 h,
667 cx,
668 cy,
669 radius,
670 stops,
671 scissor,
672 } = p;
673 let uniforms = build_gradient_uniforms(1, [cx, cy], [0.0, 0.0], radius, stops)?;
674 let mut verts = Vec::new();
675 push_gradient_quad(&mut verts, x, y, w, h);
676 Some(GradientDraw {
677 verts,
678 uniforms,
679 scissor,
680 })
681}
682
683pub(crate) fn build_gradient_uniforms(
684 gradient_type: u32,
685 p0: [f32; 2],
686 p1: [f32; 2],
687 radius: f32,
688 stops: &[GradientStop],
689) -> Option<GradientUniforms> {
690 if stops.is_empty() {
691 return None;
692 }
693 let count = stops.len().min(MAX_GRADIENT_STOPS);
694 let mut stop_offsets = [[0.0f32; 4]; MAX_GRADIENT_STOPS];
695 let mut stop_colors = [[0.0f32; 4]; MAX_GRADIENT_STOPS];
696 for (i, s) in stops.iter().take(count).enumerate() {
697 stop_offsets[i] = [s.offset, 0.0, 0.0, 0.0];
698 stop_colors[i] = [
699 s.color.0 as f32 / 255.0,
700 s.color.1 as f32 / 255.0,
701 s.color.2 as f32 / 255.0,
702 s.color.3 as f32 / 255.0,
703 ];
704 }
705 Some(GradientUniforms {
706 p0,
707 p1,
708 radius,
709 gradient_type,
710 stop_count: count as u32,
711 _pad: 0,
712 stop_offsets,
713 stop_colors,
714 })
715}
716
717#[cfg(test)]
720mod tests {
721 use super::*;
722 use oxiui_core::geometry::Rect;
723 use oxiui_core::paint::{DrawList, ImageData, ImageFilter};
724 use oxiui_core::Color;
725
726 #[test]
728 fn solid_rect_produces_6_vertices() {
729 let mut list = DrawList::new();
730 list.push_rect(Rect::new(0.0, 0.0, 10.0, 10.0), Color(255, 0, 0, 255));
731 let (verts, segments, grads, textures, _blurs) = build_geometry(&list, 100, 100);
732 assert_eq!(verts.len(), 6, "one rect = 6 vertices");
733 assert_eq!(segments.len(), 1, "one draw segment");
734 assert!(grads.is_empty());
735 assert!(textures.is_empty());
736 }
737
738 #[test]
740 fn n_solid_rects_produce_n_times_6_vertices() {
741 const N: usize = 5;
742 let mut list = DrawList::new();
743 for i in 0..N {
744 list.push_rect(
745 Rect::new(i as f32 * 12.0, 0.0, 10.0, 10.0),
746 Color(255, 0, 0, 255),
747 );
748 }
749 let (verts, segments, _, _, _) = build_geometry(&list, 200, 100);
750 assert_eq!(verts.len(), N * 6, "{N} rects = {} vertices", N * 6);
751 assert_eq!(segments.len(), 1, "all in one draw segment");
752 }
753
754 #[test]
756 fn image_produces_one_textured_draw_with_6_vertices() {
757 let mut list = DrawList::new();
758 list.push_image(
759 ImageData::new(vec![255, 0, 0, 255], 1, 1),
760 Rect::new(0.0, 0.0, 10.0, 10.0),
761 ImageFilter::Nearest,
762 );
763 let (_verts, _segs, _grads, textures, _blurs) = build_geometry(&list, 100, 100);
764 assert_eq!(textures.len(), 1, "one textured draw");
765 assert_eq!(textures[0].verts.len(), 6, "2 triangles = 6 vertices");
767 }
768
769 #[test]
771 fn clip_pushpop_produces_correct_segments() {
772 let mut list = DrawList::new();
773 list.push_rect(Rect::new(0.0, 0.0, 5.0, 5.0), Color(255, 0, 0, 255));
775 list.push_clip(Rect::new(0.0, 0.0, 50.0, 50.0));
777 list.push_rect(Rect::new(1.0, 1.0, 4.0, 4.0), Color(0, 255, 0, 255));
778 list.pop_clip();
779 list.push_rect(Rect::new(10.0, 0.0, 5.0, 5.0), Color(0, 0, 255, 255));
781
782 let (verts, segments, _, _, _) = build_geometry(&list, 100, 100);
783 assert_eq!(verts.len(), 18, "3 rects = 18 vertices");
785 assert_eq!(segments.len(), 3, "three draw segments for push/pop clip");
787 }
788
789 #[test]
791 fn scissor_culls_offscreen_rects() {
792 let mut list = DrawList::new();
793 list.push_clip(Rect::new(0.0, 0.0, 10.0, 10.0));
795 list.push_rect(Rect::new(0.0, 0.0, 5.0, 5.0), Color(255, 0, 0, 255));
797 list.push_rect(Rect::new(100.0, 100.0, 5.0, 5.0), Color(0, 255, 0, 255));
799 list.pop_clip();
800
801 let (verts, _, _, _, _) = build_geometry(&list, 200, 200);
802 assert_eq!(
804 verts.len(),
805 6,
806 "outside rect must be culled; only 6 vertices expected"
807 );
808 }
809}