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}