i_slint_renderer_software/
scene.rs1use super::{
7 Fixed, PhysicalBorderRadius, PhysicalLength, PhysicalPoint, PhysicalRect, PhysicalRegion,
8 PhysicalSize, PremultipliedRgbaColor, RenderingRotation,
9};
10use alloc::rc::Rc;
11use alloc::vec::Vec;
12use euclid::Length;
13use i_slint_core::Color;
14use i_slint_core::graphics::{SharedImageBuffer, TexturePixelFormat};
15use i_slint_core::lengths::{PointLengths as _, SizeLengths as _};
16
17#[derive(Default)]
18pub struct SceneVectors {
19 pub textures: Vec<SceneTexture<'static>>,
20 pub rounded_rectangles: Vec<RoundedRectangle>,
21 pub shared_buffers: Vec<SharedBufferCommand>,
22 pub linear_gradients: Vec<LinearGradientCommand>,
23 pub radial_gradients: Vec<RadialGradientCommand>,
24 pub conic_gradients: Vec<ConicGradientCommand>,
25}
26
27pub struct Scene {
28 pub(super) current_line: PhysicalLength,
30
31 pub(super) items: Vec<SceneItem>,
37
38 pub(super) vectors: SceneVectors,
39
40 pub(super) future_items_index: usize,
41 pub(super) current_items_index: usize,
42
43 pub(super) dirty_region: PhysicalRegion,
44
45 pub(super) current_line_ranges: Vec<core::ops::Range<i16>>,
46 pub(super) range_valid_until_line: PhysicalLength,
47}
48
49impl Scene {
50 pub fn new(
51 mut items: Vec<SceneItem>,
52 vectors: SceneVectors,
53 dirty_region: PhysicalRegion,
54 ) -> Self {
55 let current_line =
56 dirty_region.iter_box().map(|x| x.min.y_length()).min().unwrap_or_default();
57 items.retain(|i| i.pos.y_length() + i.size.height_length() > current_line);
58 items.sort_unstable_by(compare_scene_item);
59 let current_items_index = items.partition_point(|i| i.pos.y_length() <= current_line);
60 items[..current_items_index].sort_unstable_by(|a, b| b.z.cmp(&a.z));
61 let mut r = Self {
62 items,
63 current_line,
64 current_items_index,
65 future_items_index: current_items_index,
66 vectors,
67 dirty_region,
68 current_line_ranges: Default::default(),
69 range_valid_until_line: Default::default(),
70 };
71 r.recompute_ranges();
72 debug_assert_eq!(r.current_line, r.dirty_region.bounding_rect().origin.y_length());
73 r
74 }
75
76 pub fn next_line(&mut self) {
78 self.current_line += PhysicalLength::new(1);
79
80 let skipped = self.current_line >= self.range_valid_until_line && self.recompute_ranges();
81
82 let (mut i, mut j, mut tmp1, mut tmp2) =
95 (0, 0, self.current_items_index, self.current_items_index);
96
97 if skipped {
98 while j < self.current_items_index {
100 let item = self.items[j];
101 if item.pos.y_length() + item.size.height_length() > self.current_line {
102 self.items[i] = item;
103 i += 1;
104 }
105 j += 1;
106 }
107 while self.future_items_index < self.items.len() {
108 let item = self.items[self.future_items_index];
109 if item.pos.y_length() > self.current_line {
110 break;
111 }
112 self.future_items_index += 1;
113 if item.pos.y_length() + item.size.height_length() < self.current_line {
114 continue;
115 }
116 self.items[i] = item;
117 i += 1;
118 }
119 self.items[0..i].sort_unstable_by(|a, b| b.z.cmp(&a.z));
120 self.current_items_index = i;
121 return;
122 }
123
124 'outer: loop {
125 let future_next_z = self
126 .items
127 .get(self.future_items_index)
128 .filter(|i| i.pos.y_length() <= self.current_line)
129 .map(|i| i.z);
130 let item = loop {
131 if tmp1 != tmp2 {
132 if future_next_z.map_or(true, |z| self.items[tmp1].z > z) {
133 let idx = tmp1;
134 tmp1 += 1;
135 if tmp1 == tmp2 {
136 tmp1 = self.current_items_index;
137 tmp2 = self.current_items_index;
138 }
139 break self.items[idx];
140 }
141 } else if j < self.current_items_index {
142 let item = &self.items[j];
143 if item.pos.y_length() + item.size.height_length() <= self.current_line {
144 j += 1;
145 continue;
146 }
147 if future_next_z.map_or(true, |z| item.z > z) {
148 j += 1;
149 break *item;
150 }
151 }
152 if future_next_z.is_some() {
153 self.future_items_index += 1;
154 break self.items[self.future_items_index - 1];
155 }
156 break 'outer;
157 };
158 if i != j {
159 } else if j >= self.current_items_index && tmp1 == tmp2 {
161 j += 1
163 } else if self.items[j].pos.y_length() + self.items[j].size.height_length()
164 <= self.current_line
165 {
166 j += 1;
168 } else if tmp2 < self.future_items_index && j < self.current_items_index {
169 let to_move = self.items[j];
171 self.items[tmp2] = to_move;
172 j += 1;
173 tmp2 += 1;
174 } else {
175 debug_assert!(tmp1 >= self.current_items_index);
176 let sort_begin = i;
177 while j < self.current_items_index {
179 let item = self.items[j];
180 if item.pos.y_length() + item.size.height_length() > self.current_line {
181 self.items[i] = item;
182 i += 1;
183 }
184 j += 1;
185 }
186 self.items.copy_within(tmp1..tmp2, i);
187 i += tmp2 - tmp1;
188 debug_assert!(i < self.future_items_index);
189 self.items[i] = item;
190 i += 1;
191 while self.future_items_index < self.items.len() {
192 let item = self.items[self.future_items_index];
193 if item.pos.y_length() > self.current_line {
194 break;
195 }
196 self.future_items_index += 1;
197 self.items[i] = item;
198 i += 1;
199 }
200 self.items[sort_begin..i].sort_unstable_by(|a, b| b.z.cmp(&a.z));
201 break;
202 }
203 self.items[i] = item;
204 i += 1;
205 }
206 self.current_items_index = i;
207 debug_assert!(self.items[0..self.current_items_index].windows(2).all(|x| x[0].z >= x[1].z));
209 }
210
211 fn recompute_ranges(&mut self) -> bool {
213 let validity = super::region_line_ranges(
214 &self.dirty_region,
215 self.current_line.get(),
216 &mut self.current_line_ranges,
217 );
218 if self.current_line_ranges.is_empty() {
219 if let Some(next) = validity {
220 self.current_line = Length::new(next);
221 self.range_valid_until_line = Length::new(
222 super::region_line_ranges(
223 &self.dirty_region,
224 self.current_line.get(),
225 &mut self.current_line_ranges,
226 )
227 .unwrap_or_default(),
228 );
229 return true;
230 }
231 }
232 self.range_valid_until_line = Length::new(validity.unwrap_or_default());
233 false
234 }
235}
236
237#[derive(Clone, Copy, Debug)]
238pub struct SceneItem {
239 pub pos: PhysicalPoint,
240 pub size: PhysicalSize,
241 pub z: u16,
243 pub command: SceneCommand,
244}
245
246fn compare_scene_item(a: &SceneItem, b: &SceneItem) -> core::cmp::Ordering {
247 match a.pos.y.partial_cmp(&b.pos.y) {
249 None | Some(core::cmp::Ordering::Equal) => {}
250 Some(ord) => return ord,
251 }
252 match a.z.partial_cmp(&b.z) {
254 None | Some(core::cmp::Ordering::Equal) => {}
255 Some(ord) => return ord.reverse(),
256 }
257
258 core::cmp::Ordering::Equal
260}
261
262#[derive(Clone, Copy, Debug)]
263#[repr(u8)]
264pub enum SceneCommand {
265 Rectangle {
266 color: PremultipliedRgbaColor,
267 },
268 Texture {
270 texture_index: u16,
271 },
272 SharedBuffer {
274 shared_buffer_index: u16,
275 },
276 RoundedRectangle {
278 rectangle_index: u16,
279 },
280 LinearGradient {
282 linear_gradient_index: u16,
283 },
284 RadialGradient {
286 radial_gradient_index: u16,
287 },
288 ConicGradient {
290 conic_gradient_index: u16,
291 },
292}
293
294pub struct SceneTexture<'a> {
295 pub data: &'a [u8],
297 pub format: TexturePixelFormat,
298 pub pixel_stride: u16,
300
301 pub extra: SceneTextureExtra,
302}
303
304impl<'a> SceneTexture<'a> {
305 pub fn source_size(&self) -> PhysicalSize {
306 let mut len = self.data.len();
307 if self.format == TexturePixelFormat::SignedDistanceField {
308 len -= 1;
309 } else {
310 len /= self.format.bpp();
311 }
312 let stride = self.pixel_stride as usize;
313 let h = len / stride;
314 let w = len % stride;
315 if w == 0 {
316 PhysicalSize::new(stride as _, h as _)
317 } else {
318 PhysicalSize::new(w as _, (h + 1) as _)
319 }
320 }
321
322 pub fn from_target_texture(
323 texture: &'a super::target_pixel_buffer::DrawTextureArgs,
324 clip: &PhysicalRect,
325 ) -> Option<(Self, PhysicalRect)> {
326 let (extra, geometry) = SceneTextureExtra::from_target_texture(texture, clip)?;
327 let source = texture.source();
328 Some((
329 Self {
330 data: source.data,
331 pixel_stride: (source.byte_stride / source.pixel_format.bpp()) as u16,
332 format: source.pixel_format,
333 extra,
334 },
335 geometry,
336 ))
337 }
338}
339
340#[derive(Clone, Copy, Debug)]
341pub struct SceneTextureExtra {
342 pub dx: Fixed<u16, 8>,
344 pub dy: Fixed<u16, 8>,
345 pub off_x: Fixed<u16, 4>,
347 pub off_y: Fixed<u16, 4>,
348 pub colorize: Color,
351 pub alpha: u8,
352 pub rotation: RenderingRotation,
353}
354
355impl SceneTextureExtra {
356 pub fn from_target_texture(
357 texture: &super::target_pixel_buffer::DrawTextureArgs,
358 clip: &PhysicalRect,
359 ) -> Option<(Self, PhysicalRect)> {
360 let geometry: PhysicalRect = euclid::rect(
361 texture.dst_x as i16,
362 texture.dst_y as i16,
363 texture.dst_width as i16,
364 texture.dst_height as i16,
365 );
366 let geometry = geometry.to_box2d();
367 let clipped_geometry = geometry.intersection(&clip.to_box2d())?;
368
369 let mut offset = match texture.rotation {
370 RenderingRotation::NoRotation => clipped_geometry.min - geometry.min,
371 RenderingRotation::Rotate90 => euclid::vec2(
372 clipped_geometry.min.y - geometry.min.y,
373 geometry.max.x - clipped_geometry.max.x,
374 ),
375 RenderingRotation::Rotate180 => geometry.max - clipped_geometry.max,
376 RenderingRotation::Rotate270 => euclid::vec2(
377 geometry.max.y - clipped_geometry.max.y,
378 clipped_geometry.min.x - geometry.min.x,
379 ),
380 };
381
382 let source_size = texture.source_size().cast::<i32>();
383 let (dx, dy) = if let Some(tiling) = &texture.tiling {
384 offset -= euclid::vec2(tiling.offset_x, tiling.offset_y).cast();
385
386 tiling.gap_x;
388 tiling.gap_y;
389
390 (Fixed::from_f32(tiling.scale_x)?, Fixed::from_f32(tiling.scale_y)?)
391 } else {
392 let (dst_w, dst_h) = if texture.rotation.is_transpose() {
393 (texture.dst_height as i32, texture.dst_width as i32)
394 } else {
395 (texture.dst_width as i32, texture.dst_height as i32)
396 };
397 let dx = Fixed::<i32, 8>::from_fraction(source_size.width, dst_w);
398 let dy = Fixed::<i32, 8>::from_fraction(source_size.height, dst_h);
399 (dx, dy)
400 };
401
402 Some((
403 Self {
404 colorize: texture.colorize.unwrap_or_default(),
405 alpha: texture.alpha,
406 rotation: texture.rotation,
407 dx: Fixed::try_from_fixed(dx).ok()?,
408 dy: Fixed::try_from_fixed(dy).ok()?,
409 off_x: Fixed::try_from_fixed(dx * offset.x as i32).ok()?,
410 off_y: Fixed::try_from_fixed(dy * offset.y as i32).ok()?,
411 },
412 clipped_geometry.to_rect(),
413 ))
414 }
415}
416
417#[derive(Clone)]
418pub enum SharedBufferData {
419 SharedImage(SharedImageBuffer),
420 AlphaMap { data: Rc<[u8]>, width: u16 },
421}
422
423impl SharedBufferData {
424 pub fn width(&self) -> usize {
425 match self {
426 SharedBufferData::SharedImage(image) => image.width() as usize,
427 SharedBufferData::AlphaMap { width, .. } => *width as usize,
428 }
429 }
430 #[allow(unused)]
431 pub fn height(&self) -> usize {
432 match self {
433 SharedBufferData::SharedImage(image) => image.height() as usize,
434 SharedBufferData::AlphaMap { data, width, .. } => data.len() / *width as usize,
435 }
436 }
437}
438
439pub struct SharedBufferCommand {
440 pub buffer: SharedBufferData,
441 pub source_rect: PhysicalRect,
443 pub extra: SceneTextureExtra,
444}
445
446impl SharedBufferCommand {
447 pub fn as_texture(&self) -> SceneTexture<'_> {
448 let stride = self.buffer.width();
449 let core::ops::Range { start, end } = compute_range_in_buffer(&self.source_rect, stride);
450
451 match &self.buffer {
452 SharedBufferData::SharedImage(SharedImageBuffer::RGB8(b)) => SceneTexture {
453 data: &b.as_bytes()[start * 3..end * 3],
454 pixel_stride: stride as u16,
455 format: TexturePixelFormat::Rgb,
456 extra: self.extra,
457 },
458 SharedBufferData::SharedImage(SharedImageBuffer::RGBA8(b)) => SceneTexture {
459 data: &b.as_bytes()[start * 4..end * 4],
460 pixel_stride: stride as u16,
461 format: TexturePixelFormat::Rgba,
462 extra: self.extra,
463 },
464 SharedBufferData::SharedImage(SharedImageBuffer::RGBA8Premultiplied(b)) => {
465 SceneTexture {
466 data: &b.as_bytes()[start * 4..end * 4],
467 pixel_stride: stride as u16,
468 format: TexturePixelFormat::RgbaPremultiplied,
469 extra: self.extra,
470 }
471 }
472 SharedBufferData::AlphaMap { data, width } => SceneTexture {
473 data: &data[start..end],
474 pixel_stride: *width,
475 format: TexturePixelFormat::AlphaMap,
476 extra: self.extra,
477 },
478 }
479 }
480}
481
482pub fn compute_range_in_buffer(
484 source_rect: &PhysicalRect,
485 pixel_stride: usize,
486) -> core::ops::Range<usize> {
487 let start = pixel_stride * source_rect.min_y() as usize + source_rect.min_x() as usize;
488 let end = pixel_stride * (source_rect.max_y() - 1) as usize + source_rect.max_x() as usize;
489 start..end
490}
491
492#[derive(Debug)]
493pub struct RoundedRectangle {
494 pub radius: PhysicalBorderRadius,
495 pub width: PhysicalLength,
497 pub border_color: PremultipliedRgbaColor,
498 pub inner_color: PremultipliedRgbaColor,
499 pub left_clip: PhysicalLength,
503 pub right_clip: PhysicalLength,
504 pub top_clip: PhysicalLength,
505 pub bottom_clip: PhysicalLength,
506}
507
508#[derive(Debug)]
514pub struct LinearGradientCommand {
515 pub color1: PremultipliedRgbaColor,
516 pub color2: PremultipliedRgbaColor,
517 pub start: u8,
518 pub flags: u8,
522 pub left_clip: PhysicalLength,
525 pub right_clip: PhysicalLength,
526 pub top_clip: PhysicalLength,
527 pub bottom_clip: PhysicalLength,
528}
529
530#[derive(Debug)]
536pub struct RadialGradientCommand {
537 pub stops: i_slint_core::SharedVector<i_slint_core::graphics::GradientStop>,
539 pub center_x: PhysicalLength,
541 pub center_y: PhysicalLength,
542}
543
544#[derive(Debug)]
551pub struct ConicGradientCommand {
552 pub stops: i_slint_core::SharedVector<i_slint_core::graphics::GradientStop>,
555}