forma_render/cpu/buffer/layout/
mod.rs

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