1use crate::kurbo::Rect;
7#[cfg(not(feature = "std"))]
8use crate::kurbo::common::FloatFuncs as _;
9use crate::strip::Strip;
10use crate::tile::Tile;
11use alloc::vec::Vec;
12use fearless_simd::*;
13
14pub fn render(level: Level, rect: Rect, strip_buf: &mut Vec<Strip>, alpha_buf: &mut Vec<u8>) {
21 dispatch!(level, simd => render_impl(simd, rect, strip_buf, alpha_buf));
22}
23
24#[inline(always)]
44fn render_impl<S: Simd>(s: S, rect: Rect, strip_buf: &mut Vec<Strip>, alpha_buf: &mut Vec<u8>) {
45 if rect.is_zero_area() {
46 return;
47 }
48
49 let rect_x0 = rect.x0 as f32;
50 let rect_y0 = rect.y0 as f32;
51 let rect_x1 = rect.x1 as f32;
52 let rect_y1 = rect.y1 as f32;
53
54 let px_x0 = rect_x0.floor() as u16;
56 let px_y0 = rect_y0.floor() as u16;
57 let px_y1 = rect_y1.ceil() as u16;
58
59 let left_tile_x = (px_x0 / Tile::WIDTH) * Tile::WIDTH;
60 let right_tile_x = (rect_x1 as u16 / Tile::WIDTH) * Tile::WIDTH;
62
63 let y0 = u32::from((px_y0 / Tile::HEIGHT) * Tile::HEIGHT);
64 let y1 = (u32::from(px_y1) + u32::from(Tile::HEIGHT - 1)) / u32::from(Tile::HEIGHT)
65 * u32::from(Tile::HEIGHT);
66 let x_end = u32::from(right_tile_x) + u32::from(Tile::WIDTH);
69
70 if x_end <= u32::from(left_tile_x) || y1 <= y0 {
71 return;
72 }
73
74 let tile_start_y = y0 / u32::from(Tile::HEIGHT);
75 let tile_end_y = y1 / u32::from(Tile::HEIGHT);
76
77 let needs_right_strip = right_tile_x > left_tile_x;
79
80 let left_x_cov = coverage(left_tile_x, rect_x0, rect_x1);
81 let right_x_cov = coverage(right_tile_x, rect_x0, rect_x1);
82 let left_x_mask = alpha_mask_from_x_coverage(s, &left_x_cov);
83 let right_x_mask = alpha_mask_from_x_coverage(s, &right_x_cov);
84
85 for tile_y in tile_start_y..tile_end_y {
86 let strip_y = tile_y * u32::from(Tile::HEIGHT);
87 let strip_y = strip_y as u16;
88 let strip_y_f = strip_y as f32;
89 let strip_y_end_f = strip_y as f32 + Tile::HEIGHT as f32;
90
91 let is_top_edge = strip_y_f < rect_y0 && rect_y0 < strip_y_end_f;
94 let is_bottom_edge = strip_y_f < rect_y1 && rect_y1 < strip_y_end_f;
95
96 if is_top_edge || is_bottom_edge {
97 let alpha_start = alpha_buf.len() as u32;
98
99 let y_cov = coverage(strip_y, rect_y0, rect_y1);
100 let mut col = u32::from(left_tile_x);
101 while col + u32::from(Tile::WIDTH) <= x_end {
102 let x_cov = coverage(col as u16, rect_x0, rect_x1);
105 let combined = combined_tile_alpha(s, &x_cov, &y_cov);
106 alpha_buf.extend_from_slice(combined.as_slice());
107 col += u32::from(Tile::WIDTH);
108 }
109
110 strip_buf.push(Strip::new(left_tile_x, strip_y, alpha_start, false));
111 } else {
112 let alpha_start = alpha_buf.len() as u32;
113 alpha_buf.extend_from_slice(left_x_mask.as_slice());
114 strip_buf.push(Strip::new(left_tile_x, strip_y, alpha_start, false));
115
116 if needs_right_strip {
117 let alpha_start = alpha_buf.len() as u32;
120 alpha_buf.extend_from_slice(right_x_mask.as_slice());
121 strip_buf.push(Strip::new(right_tile_x, strip_y, alpha_start, true));
122 }
123 }
124 }
125
126 let last_strip_y = ((tile_end_y - 1) * u32::from(Tile::HEIGHT)) as u16;
128 strip_buf.push(Strip::sentinel(last_strip_y, alpha_buf.len() as u32));
129}
130
131#[inline(always)]
133fn coverage<const N: usize>(start: u16, rect_lo: f32, rect_hi: f32) -> [f32; N] {
134 let mut cov = [0.0_f32; N];
135
136 #[allow(clippy::needless_range_loop, reason = "better clarity")]
137 for i in 0..N {
138 let px = (start as usize + i) as f32;
139 cov[i] = (rect_hi.min(px + 1.0) - rect_lo.max(px)).clamp(0.0, 1.0);
140 }
141 cov
142}
143
144#[inline(always)]
147fn alpha_mask_from_x_coverage<S: Simd>(s: S, cov: &[f32; Tile::WIDTH as usize]) -> u8x16<S> {
148 let mut buf = [0_u8; 16];
149
150 #[allow(clippy::needless_range_loop, reason = "better clarity")]
151 for col in 0..Tile::WIDTH as usize {
152 let alpha = (cov[col] * 255.0 + 0.5) as u8;
153 let base = col * Tile::HEIGHT as usize;
154 buf[base..base + Tile::HEIGHT as usize].fill(alpha);
155 }
156
157 u8x16::from_slice(s, &buf)
158}
159
160#[inline(always)]
163fn combined_tile_alpha<S: Simd>(
164 s: S,
165 x_cov: &[f32; Tile::WIDTH as usize],
166 y_cov: &[f32; Tile::HEIGHT as usize],
167) -> u8x16<S> {
168 let mut buf = [0_u8; 16];
169 for (col, xc) in x_cov.iter().copied().enumerate() {
170 for (row, yc) in y_cov.iter().copied().enumerate() {
171 buf[col * Tile::HEIGHT as usize + row] = (xc * yc * 255.0 + 0.5) as u8;
172 }
173 }
174
175 u8x16::from_slice(s, &buf)
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181 use alloc::vec::Vec;
182
183 #[test]
184 fn render_edge_row_at_u16_right_edge() {
185 let mut strips = Vec::new();
186 let mut alphas = Vec::new();
187 let rect = Rect::new(f64::from(u16::MAX - 3), 0.5, f64::from(u16::MAX), 3.5);
188
189 render(Level::baseline(), rect, &mut strips, &mut alphas);
190
191 assert_eq!(strips.len(), 2);
192 assert_eq!(strips[0].x, u16::MAX - 3);
193 assert_eq!(strips[0].alpha_idx(), 0);
194 assert_eq!(
195 alphas.len(),
196 usize::from(Tile::WIDTH) * usize::from(Tile::HEIGHT)
197 );
198 assert!(strips[1].is_sentinel());
199 }
200
201 #[test]
202 fn render_edge_row_at_u16_bottom_edge() {
203 let mut strips = Vec::new();
204 let mut alphas = Vec::new();
205 let rect = Rect::new(0.5, f64::from(u16::MAX - 3), 3.5, f64::from(u16::MAX));
206
207 render(Level::baseline(), rect, &mut strips, &mut alphas);
208
209 assert_eq!(strips.len(), 2);
210 assert_eq!(strips[0].y, u16::MAX - 3);
211 assert_eq!(strips[0].alpha_idx(), 0);
212 assert_eq!(
213 alphas.len(),
214 usize::from(Tile::WIDTH) * usize::from(Tile::HEIGHT)
215 );
216 assert!(strips[1].is_sentinel());
217 }
218}