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}