surpass/layout/
mod.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Buffer-layout-specific traits for user-defined behavior.
6//!
7//! [`Layout`]'s job is to split a buffer into sub-slices that will then be distributed to tile to
8//! be rendered, and to write color data to these sub-slices.
9
10use std::fmt;
11
12use rayon::prelude::*;
13
14use crate::{TILE_HEIGHT, TILE_HEIGHT_SHIFT, TILE_WIDTH, TILE_WIDTH_SHIFT};
15
16mod slice_cache;
17pub use slice_cache::{Chunks, Ref, Slice, SliceCache, Span};
18
19/// Listener that gets called after every write to the buffer. Its main use is to flush freshly
20/// written memory slices.
21pub trait Flusher: fmt::Debug + Send + Sync {
22    /// Called after `slice` was written to.
23    fn flush(&self, slice: &mut [u8]);
24}
25
26/// A fill that the [`Layout`] uses to write to tiles.
27pub enum TileFill<'c> {
28    /// Fill tile with a solid color.
29    Solid([u8; 4]),
30    /// Fill tile with provided colors buffer. They are provided in [column-major] order.
31    ///
32    /// [column-major]: https://en.wikipedia.org/wiki/Row-_and_column-major_order
33    Full(&'c [[u8; 4]]),
34}
35
36/// A buffer's layout description.
37///
38/// Implementors are supposed to cache sub-slices between uses provided they are being used with
39/// exactly the same buffer. This is achieved by storing a [`SliceCache`] in every layout
40/// implementation.
41pub trait Layout {
42    /// Width in pixels.
43    ///
44    /// # Examples
45    ///
46    /// ```
47    /// # use surpass::layout::{Layout, LinearLayout};
48    /// let layout = LinearLayout::new(2, 3 * 4, 4);
49    ///
50    /// assert_eq!(layout.width(), 2);
51    /// ```
52    fn width(&self) -> usize;
53
54    /// Height in pixels.
55    ///
56    /// # Examples
57    ///
58    /// ```
59    /// # use surpass::layout::{Layout, LinearLayout};
60    /// let layout = LinearLayout::new(2, 3 * 4, 4);
61    ///
62    /// assert_eq!(layout.height(), 4);
63    /// ```
64    fn height(&self) -> usize;
65
66    /// Number of buffer sub-slices that will be passes to [`Layout::write`].
67    ///
68    /// # Examples
69    ///
70    /// ```
71    /// # use surpass::{layout::{Layout, LinearLayout}, TILE_HEIGHT};
72    /// let layout = LinearLayout::new(2, 3 * 4, 4);
73    ///
74    /// assert_eq!(layout.slices_per_tile(), TILE_HEIGHT);
75    /// ```
76    fn slices_per_tile(&self) -> usize;
77
78    /// Returns self-stored sub-slices of `buffer` which are stored in a [`SliceCache`].
79    ///
80    /// # Examples
81    ///
82    /// ```
83    /// # use surpass::layout::{Layout, LinearLayout};
84    /// let mut buffer = [
85    ///     [1; 4], [2; 4], [3; 4],
86    ///     [4; 4], [5; 4], [6; 4],
87    /// ].concat();
88    /// let mut layout = LinearLayout::new(2, 3 * 4, 2);
89    /// let slices = layout.slices(&mut buffer);
90    ///
91    /// assert_eq!(&*slices[0], &[[1; 4], [2; 4]].concat());
92    /// assert_eq!(&*slices[1], &[[4; 4], [5; 4]].concat());
93    /// ```
94    fn slices<'l, 'b>(&'l mut self, buffer: &'b mut [u8]) -> Ref<'l, [Slice<'b, u8>]>;
95
96    /// Writes `fill` to `slices`, optionally calling the `flusher`.
97    ///
98    /// # Examples
99    ///
100    /// ```
101    /// # use surpass::layout::{Layout, LinearLayout, TileFill};
102    /// let mut buffer = [
103    ///     [1; 4], [2; 4], [3; 4],
104    ///     [4; 4], [5; 4], [6; 4],
105    /// ].concat();
106    /// let mut layout = LinearLayout::new(2, 3 * 4, 2);
107    ///
108    /// LinearLayout::write(&mut *layout.slices(&mut buffer), None, TileFill::Solid([0; 4]));
109    ///
110    /// assert_eq!(buffer, [
111    ///     [0; 4], [0; 4], [3; 4],
112    ///     [0; 4], [0; 4], [6; 4],
113    /// ].concat());
114    fn write(slices: &mut [Slice<'_, u8>], flusher: Option<&dyn Flusher>, fill: TileFill<'_>);
115
116    /// Width in tiles.
117    ///
118    /// # Examples
119    ///
120    /// ```
121    /// # use surpass::{layout::{Layout, LinearLayout}, TILE_HEIGHT, TILE_WIDTH};
122    /// let layout = LinearLayout::new(2 * TILE_WIDTH, 3 * TILE_WIDTH * 4, 4 * TILE_HEIGHT);
123    ///
124    /// assert_eq!(layout.width_in_tiles(), 2);
125    /// ```
126    #[inline]
127    fn width_in_tiles(&self) -> usize {
128        (self.width() + TILE_WIDTH - 1) >> TILE_WIDTH_SHIFT
129    }
130
131    /// Height in tiles.
132    ///
133    /// # Examples
134    ///
135    /// ```
136    /// # use surpass::{layout::{Layout, LinearLayout}, TILE_HEIGHT, TILE_WIDTH};
137    /// let layout = LinearLayout::new(2 * TILE_WIDTH, 3 * TILE_WIDTH * 4, 4 * TILE_HEIGHT);
138    ///
139    /// assert_eq!(layout.height_in_tiles(), 4);
140    /// ```
141    #[inline]
142    fn height_in_tiles(&self) -> usize {
143        (self.height() + TILE_HEIGHT - 1) >> TILE_HEIGHT_SHIFT
144    }
145}
146
147/// A linear buffer layout where each optionally strided pixel row of an image is saved
148/// sequentially into the buffer.
149#[derive(Debug)]
150pub struct LinearLayout {
151    cache: SliceCache,
152    width: usize,
153    width_stride: usize,
154    height: usize,
155}
156
157impl LinearLayout {
158    /// Creates a new linear layout from `width`, `width_stride` (in bytes) and `height`.
159    ///
160    /// # Examples
161    ///
162    /// ```
163    /// # use surpass::layout::{Layout, LinearLayout};
164    /// let layout = LinearLayout::new(2, 3 * 4, 4);
165    ///
166    /// assert_eq!(layout.width(), 2);
167    /// ```
168    #[inline]
169    pub fn new(width: usize, width_stride: usize, height: usize) -> Self {
170        assert!(
171            width * 4 <= width_stride,
172            "width exceeds width stride: {} * 4 > {}",
173            width,
174            width_stride
175        );
176
177        let cache = SliceCache::new(width_stride * height, move |buffer| {
178            let mut layout: Vec<_> = buffer
179                .chunks(width_stride)
180                .enumerate()
181                .flat_map(|(tile_y, row)| {
182                    row.slice(..width * 4).unwrap().chunks(TILE_WIDTH * 4).enumerate().map(
183                        move |(tile_x, slice)| {
184                            let tile_y = tile_y >> TILE_HEIGHT_SHIFT;
185                            (tile_x, tile_y, slice)
186                        },
187                    )
188                })
189                .collect();
190            layout.par_sort_by_key(|&(tile_x, tile_y, _)| (tile_y, tile_x));
191
192            layout.into_iter().map(|(_, _, slice)| slice).collect()
193        });
194
195        LinearLayout { cache, width, width_stride, height }
196    }
197}
198
199impl Layout for LinearLayout {
200    #[inline]
201    fn width(&self) -> usize {
202        self.width
203    }
204
205    #[inline]
206    fn height(&self) -> usize {
207        self.height
208    }
209
210    #[inline]
211    fn slices_per_tile(&self) -> usize {
212        TILE_HEIGHT
213    }
214
215    #[inline]
216    fn slices<'l, 'b>(&'l mut self, buffer: &'b mut [u8]) -> Ref<'l, [Slice<'b, u8>]> {
217        assert!(
218            self.width <= buffer.len(),
219            "width exceeds buffer length: {} > {}",
220            self.width,
221            buffer.len()
222        );
223        assert!(
224            self.width_stride <= buffer.len(),
225            "width_stride exceeds buffer length: {} > {}",
226            self.width_stride,
227            buffer.len(),
228        );
229        assert!(
230            self.height * self.width_stride <= buffer.len(),
231            "height * width_stride exceeds buffer length: {} > {}",
232            self.height * self.width_stride,
233            buffer.len(),
234        );
235
236        self.cache.access(buffer).unwrap()
237    }
238
239    #[inline]
240    fn write(slices: &mut [Slice<'_, u8>], flusher: Option<&dyn Flusher>, fill: TileFill<'_>) {
241        let tiles_len = slices.len();
242        match fill {
243            TileFill::Solid(solid) => {
244                for row in slices.iter_mut().take(tiles_len) {
245                    for color in row.chunks_exact_mut(4) {
246                        color.copy_from_slice(&solid);
247                    }
248                }
249            }
250            TileFill::Full(colors) => {
251                for (y, row) in slices.iter_mut().enumerate().take(tiles_len) {
252                    for (x, color) in row.chunks_exact_mut(4).enumerate() {
253                        color.copy_from_slice(&colors[x * TILE_HEIGHT + y]);
254                    }
255                }
256            }
257        }
258
259        if let Some(flusher) = flusher {
260            for row in slices.iter_mut().take(tiles_len) {
261                flusher.flush(if let Some(subslice) = row.get_mut(..TILE_WIDTH * 4) {
262                    subslice
263                } else {
264                    &mut **row
265                });
266            }
267        }
268    }
269}