use std::fmt;
use rayon::prelude::*;
mod slice_cache;
pub use slice_cache::{Chunks, Ref, Slice, SliceCache, Span};
use crate::consts;
pub trait Flusher: fmt::Debug + Send + Sync {
fn flush(&self, slice: &mut [u8]);
}
pub enum TileFill<'c> {
Solid([u8; 4]),
Full(&'c [[u8; 4]]),
}
pub trait Layout {
fn width(&self) -> usize;
fn height(&self) -> usize;
fn slices_per_tile(&self) -> usize;
fn slices<'l, 'b>(&'l mut self, buffer: &'b mut [u8]) -> Ref<'l, [Slice<'b, u8>]>;
fn write(slices: &mut [Slice<'_, u8>], flusher: Option<&dyn Flusher>, fill: TileFill<'_>);
#[inline]
fn width_in_tiles(&self) -> usize {
(self.width() + consts::cpu::TILE_WIDTH - 1) >> consts::cpu::TILE_WIDTH_SHIFT
}
#[inline]
fn height_in_tiles(&self) -> usize {
(self.height() + consts::cpu::TILE_HEIGHT - 1) >> consts::cpu::TILE_HEIGHT_SHIFT
}
}
#[derive(Debug)]
pub struct LinearLayout {
cache: SliceCache,
width: usize,
width_stride: usize,
height: usize,
}
impl LinearLayout {
#[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(consts::cpu::TILE_WIDTH * 4)
.enumerate()
.map(move |(tile_x, slice)| {
let tile_y = tile_y >> consts::cpu::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 {
consts::cpu::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 * consts::cpu::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(..consts::cpu::TILE_WIDTH * 4) {
subslice
} else {
row
},
);
}
}
}
}