| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| //! Buffer-layout-specific traits for user-defined behavior. |
| //! |
| //! [`Layout`]'s job is to split a buffer into sub-slices that will then be distributed to tile to |
| //! be rendered, and to write color data to these sub-slices. |
| |
| use std::fmt; |
| |
| use rayon::prelude::*; |
| |
| use crate::{TILE_HEIGHT, TILE_HEIGHT_SHIFT, TILE_WIDTH, TILE_WIDTH_SHIFT}; |
| |
| mod slice_cache; |
| pub use slice_cache::{Chunks, Ref, Slice, SliceCache, Span}; |
| |
| /// Listener that gets called after every write to the buffer. Its main use is to flush freshly |
| /// written memory slices. |
| pub trait Flusher: fmt::Debug + Send + Sync { |
| /// Called after `slice` was written to. |
| fn flush(&self, slice: &mut [u8]); |
| } |
| |
| /// A fill that the [`Layout`] uses to write to tiles. |
| pub enum TileFill<'c> { |
| /// Fill tile with a solid color. |
| Solid([u8; 4]), |
| /// Fill tile with provided colors buffer. They are provided in [column-major] order. |
| /// |
| /// [column-major]: https://en.wikipedia.org/wiki/Row-_and_column-major_order |
| Full(&'c [[u8; 4]]), |
| } |
| |
| /// A buffer's layout description. |
| /// |
| /// Implementors are supposed to cache sub-slices between uses provided they are being used with |
| /// exactly the same buffer. This is achieved by storing a [`SliceCache`] in every layout |
| /// implementation. |
| pub trait Layout { |
| /// Width in pixels. |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// # use surpass::layout::{Layout, LinearLayout}; |
| /// let layout = LinearLayout::new(2, 3 * 4, 4); |
| /// |
| /// assert_eq!(layout.width(), 2); |
| /// ``` |
| fn width(&self) -> usize; |
| |
| /// Height in pixels. |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// # use surpass::layout::{Layout, LinearLayout}; |
| /// let layout = LinearLayout::new(2, 3 * 4, 4); |
| /// |
| /// assert_eq!(layout.height(), 4); |
| /// ``` |
| fn height(&self) -> usize; |
| |
| /// Number of buffer sub-slices that will be passes to [`Layout::write`]. |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// # use surpass::{layout::{Layout, LinearLayout}, TILE_HEIGHT}; |
| /// let layout = LinearLayout::new(2, 3 * 4, 4); |
| /// |
| /// assert_eq!(layout.slices_per_tile(), TILE_HEIGHT); |
| /// ``` |
| fn slices_per_tile(&self) -> usize; |
| |
| /// Returns self-stored sub-slices of `buffer` which are stored in a [`SliceCache`]. |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// # use surpass::layout::{Layout, LinearLayout}; |
| /// let mut buffer = [ |
| /// [1; 4], [2; 4], [3; 4], |
| /// [4; 4], [5; 4], [6; 4], |
| /// ].concat(); |
| /// let mut layout = LinearLayout::new(2, 3 * 4, 2); |
| /// let slices = layout.slices(&mut buffer); |
| /// |
| /// assert_eq!(&*slices[0], &[[1; 4], [2; 4]].concat()); |
| /// assert_eq!(&*slices[1], &[[4; 4], [5; 4]].concat()); |
| /// ``` |
| fn slices<'l, 'b>(&'l mut self, buffer: &'b mut [u8]) -> Ref<'l, [Slice<'b, u8>]>; |
| |
| /// Writes `fill` to `slices`, optionally calling the `flusher`. |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// # use surpass::layout::{Layout, LinearLayout, TileFill}; |
| /// let mut buffer = [ |
| /// [1; 4], [2; 4], [3; 4], |
| /// [4; 4], [5; 4], [6; 4], |
| /// ].concat(); |
| /// let mut layout = LinearLayout::new(2, 3 * 4, 2); |
| /// |
| /// LinearLayout::write(&mut *layout.slices(&mut buffer), None, TileFill::Solid([0; 4])); |
| /// |
| /// assert_eq!(buffer, [ |
| /// [0; 4], [0; 4], [3; 4], |
| /// [0; 4], [0; 4], [6; 4], |
| /// ].concat()); |
| fn write(slices: &mut [Slice<'_, u8>], flusher: Option<&dyn Flusher>, fill: TileFill<'_>); |
| |
| /// Width in tiles. |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// # use surpass::{layout::{Layout, LinearLayout}, TILE_HEIGHT, TILE_WIDTH}; |
| /// let layout = LinearLayout::new(2 * TILE_WIDTH, 3 * TILE_WIDTH * 4, 4 * TILE_HEIGHT); |
| /// |
| /// assert_eq!(layout.width_in_tiles(), 2); |
| /// ``` |
| #[inline] |
| fn width_in_tiles(&self) -> usize { |
| (self.width() + TILE_WIDTH - 1) >> TILE_WIDTH_SHIFT |
| } |
| |
| /// Height in tiles. |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// # use surpass::{layout::{Layout, LinearLayout}, TILE_HEIGHT, TILE_WIDTH}; |
| /// let layout = LinearLayout::new(2 * TILE_WIDTH, 3 * TILE_WIDTH * 4, 4 * TILE_HEIGHT); |
| /// |
| /// assert_eq!(layout.height_in_tiles(), 4); |
| /// ``` |
| #[inline] |
| fn height_in_tiles(&self) -> usize { |
| (self.height() + TILE_HEIGHT - 1) >> TILE_HEIGHT_SHIFT |
| } |
| } |
| |
| /// A linear buffer layout where each optionally strided pixel row of an image is saved |
| /// sequentially into the buffer. |
| #[derive(Debug)] |
| pub struct LinearLayout { |
| cache: SliceCache, |
| width: usize, |
| width_stride: usize, |
| height: usize, |
| } |
| |
| impl LinearLayout { |
| /// Creates a new linear layout from `width`, `width_stride` (in bytes) and `height`. |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// # use surpass::layout::{Layout, LinearLayout}; |
| /// let layout = LinearLayout::new(2, 3 * 4, 4); |
| /// |
| /// assert_eq!(layout.width(), 2); |
| /// ``` |
| #[inline] |
| pub fn new(width: usize, width_stride: usize, height: usize) -> Self { |
| assert!( |
| width * 4 <= width_stride, |
| "width exceeds width stride: {} * 4 > {}", |
| width, |
| width_stride |
| ); |
| |
| let cache = SliceCache::new(width_stride * height, move |buffer| { |
| let mut layout: Vec<_> = buffer |
| .chunks(width_stride) |
| .enumerate() |
| .flat_map(|(tile_y, row)| { |
| row.slice(..width * 4).unwrap().chunks(TILE_WIDTH * 4).enumerate().map( |
| move |(tile_x, slice)| { |
| let tile_y = tile_y >> TILE_HEIGHT_SHIFT; |
| (tile_x, tile_y, slice) |
| }, |
| ) |
| }) |
| .collect(); |
| layout.par_sort_by_key(|&(tile_x, tile_y, _)| (tile_y, tile_x)); |
| |
| layout.into_iter().map(|(_, _, slice)| slice).collect() |
| }); |
| |
| LinearLayout { cache, width, width_stride, height } |
| } |
| } |
| |
| impl Layout for LinearLayout { |
| #[inline] |
| fn width(&self) -> usize { |
| self.width |
| } |
| |
| #[inline] |
| fn height(&self) -> usize { |
| self.height |
| } |
| |
| #[inline] |
| fn slices_per_tile(&self) -> usize { |
| TILE_HEIGHT |
| } |
| |
| #[inline] |
| fn slices<'l, 'b>(&'l mut self, buffer: &'b mut [u8]) -> Ref<'l, [Slice<'b, u8>]> { |
| assert!( |
| self.width <= buffer.len(), |
| "width exceeds buffer length: {} > {}", |
| self.width, |
| buffer.len() |
| ); |
| assert!( |
| self.width_stride <= buffer.len(), |
| "width_stride exceeds buffer length: {} > {}", |
| self.width_stride, |
| buffer.len(), |
| ); |
| assert!( |
| self.height * self.width_stride <= buffer.len(), |
| "height * width_stride exceeds buffer length: {} > {}", |
| self.height * self.width_stride, |
| buffer.len(), |
| ); |
| |
| self.cache.access(buffer).unwrap() |
| } |
| |
| #[inline] |
| fn write(slices: &mut [Slice<'_, u8>], flusher: Option<&dyn Flusher>, fill: TileFill<'_>) { |
| let tiles_len = slices.len(); |
| match fill { |
| TileFill::Solid(solid) => { |
| for row in slices.iter_mut().take(tiles_len) { |
| for color in row.chunks_exact_mut(4) { |
| color.copy_from_slice(&solid); |
| } |
| } |
| } |
| TileFill::Full(colors) => { |
| for (y, row) in slices.iter_mut().enumerate().take(tiles_len) { |
| for (x, color) in row.chunks_exact_mut(4).enumerate() { |
| color.copy_from_slice(&colors[x * TILE_HEIGHT + y]); |
| } |
| } |
| } |
| } |
| |
| if let Some(flusher) = flusher { |
| for row in slices.iter_mut().take(tiles_len) { |
| flusher.flush(if let Some(subslice) = row.get_mut(..TILE_WIDTH * 4) { |
| subslice |
| } else { |
| &mut **row |
| }); |
| } |
| } |
| } |
| } |