Skip to main content

vello_cpu/
region.rs

1// Copyright 2025 the Vello Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! Splitting a single mutable buffer into regions that can be accessed concurrently.
5
6use crate::fine::COLOR_COMPONENTS;
7use alloc::vec::Vec;
8use vello_common::coarse::WideTile;
9use vello_common::pixmap::Pixmap;
10use vello_common::tile::Tile;
11
12#[derive(Debug)]
13pub struct Regions<'a> {
14    regions: Vec<Region<'a>>,
15}
16
17impl<'a> Regions<'a> {
18    pub fn new(width: u16, height: u16, mut buffer: &'a mut [u8]) -> Self {
19        let buf_width = usize::from(width);
20        let buf_height = usize::from(height);
21
22        let row_advance = buf_width * COLOR_COMPONENTS;
23
24        let height_regions = buf_height.div_ceil(usize::from(Tile::HEIGHT));
25        let width_regions = buf_width.div_ceil(usize::from(WideTile::WIDTH));
26
27        let mut regions = Vec::with_capacity(height_regions * width_regions);
28
29        let mut next_lines: [&'a mut [u8]; Tile::HEIGHT as usize] =
30            [&mut [], &mut [], &mut [], &mut []];
31
32        for y in 0..height_regions {
33            let base_y = y * usize::from(Tile::HEIGHT);
34            let region_height = usize::from(Tile::HEIGHT).min(buf_height - base_y);
35
36            for line in next_lines.iter_mut().take(region_height) {
37                let (head, tail) = buffer.split_at_mut(row_advance);
38                *line = head;
39                buffer = tail;
40            }
41
42            for x in 0..width_regions {
43                let mut areas: [&mut [u8]; Tile::HEIGHT as usize] =
44                    [&mut [], &mut [], &mut [], &mut []];
45
46                // All rows have the same width, so we can just take the first row.
47                let region_width =
48                    (usize::from(WideTile::WIDTH) * COLOR_COMPONENTS).min(next_lines[0].len());
49
50                for h in 0..region_height {
51                    let next = core::mem::take(&mut next_lines[h]);
52                    let (head, tail) = next.split_at_mut(region_width);
53                    areas[h] = head;
54                    next_lines[h] = tail;
55                }
56
57                regions.push(Region::new(
58                    areas,
59                    u16::try_from(x).unwrap(),
60                    u16::try_from(y).unwrap(),
61                    region_width as u16 / COLOR_COMPONENTS as u16,
62                    region_height as u16,
63                ));
64            }
65        }
66
67        Self { regions }
68    }
69
70    /// Apply the given function to each region. The functions will be applied
71    /// in parallel in the current threadpool.
72    #[cfg(feature = "multithreading")]
73    pub fn update_regions_par(&mut self, func: impl Fn(&mut Region<'_>) + Send + Sync) {
74        use rayon::iter::ParallelIterator;
75        use rayon::prelude::IntoParallelRefMutIterator;
76
77        self.regions.par_iter_mut().for_each(func);
78    }
79
80    /// Apply the given function to each region.
81    pub fn update_regions(&mut self, func: impl FnMut(&mut Region<'_>)) {
82        self.regions.iter_mut().for_each(func);
83    }
84}
85
86/// A rectangular region containing the pixels from one wide tile.
87///
88/// For wide tiles at the right/bottom edge, it might contain less pixels
89/// than the actual wide tile, if the pixmap width/height isn't a multiple of the
90/// tile width/height.
91#[derive(Default, Debug)]
92pub struct Region<'a> {
93    /// The x coordinate of the wide tile this region covers.
94    pub(crate) x: u16,
95    /// The y coordinate of the wide tile this region covers.
96    pub(crate) y: u16,
97    pub width: u16,
98    pub height: u16,
99    areas: [&'a mut [u8]; Tile::HEIGHT as usize],
100}
101
102impl<'a> Region<'a> {
103    pub(crate) fn new(
104        areas: [&'a mut [u8]; Tile::HEIGHT as usize],
105        x: u16,
106        y: u16,
107        width: u16,
108        height: u16,
109    ) -> Self {
110        Self {
111            areas,
112            x,
113            y,
114            width,
115            height,
116        }
117    }
118
119    /// Extracts a `Region` from a pixmap at the specified tile coordinates.
120    ///
121    /// The region corresponds to a wide tile area (`WideTile::WIDTH` × `Tile::HEIGHT` pixels),
122    /// starting at pixel coordinates `(tile_x * WideTile::WIDTH, tile_y * Tile::HEIGHT)`.
123    /// Regions at the right or bottom edges may be smaller if they extend beyond the pixmap bounds.
124    ///
125    /// Returns `None` if the tile coordinates are completely outside the pixmap bounds.
126    ///
127    /// # Arguments
128    /// * `pixmap` - The pixmap to extract from
129    /// * `tile_x` - Tile column index (in tile units, not pixels)
130    /// * `tile_y` - Tile row index (in tile units, not pixels)
131    pub(crate) fn from_pixmap_tile(
132        pixmap: &'a mut Pixmap,
133        tile_x: u16,
134        tile_y: u16,
135    ) -> Option<Self> {
136        let pixmap_width = pixmap.width();
137        let pixmap_height = pixmap.height();
138
139        // Calculate pixel coordinates for this tile
140        let base_x = tile_x * WideTile::WIDTH;
141        let base_y = tile_y * Tile::HEIGHT;
142
143        // Check bounds
144        if base_x >= pixmap_width || base_y >= pixmap_height {
145            return None;
146        }
147
148        // Calculate actual region dimensions (might be smaller at edges)
149        let region_width = WideTile::WIDTH.min(pixmap_width - base_x);
150        let region_height = Tile::HEIGHT.min(pixmap_height - base_y);
151
152        // Get mutable access to the pixmap's buffer
153        let buffer = pixmap.data_as_u8_slice_mut();
154
155        // Split buffer into row slices for this tile
156        let row_stride = pixmap_width as usize * COLOR_COMPONENTS;
157        let start_offset = (base_y as usize * row_stride) + (base_x as usize * COLOR_COMPONENTS);
158        let region_width_bytes = region_width as usize * COLOR_COMPONENTS;
159
160        // Skip to the start of our tile region
161        let tile_buffer = &mut buffer[start_offset..];
162
163        // Extract individual row slices using safe split operations
164        let mut areas: [&mut [u8]; Tile::HEIGHT as usize] = [&mut [], &mut [], &mut [], &mut []];
165
166        // Use split_at_mut to safely extract each row
167        let mut remaining = tile_buffer;
168        for (i, area) in areas.iter_mut().take(region_height as usize).enumerate() {
169            if i > 0 {
170                // Skip rows we've already processed (advance by stride - region_width_bytes)
171                let skip = row_stride - region_width_bytes;
172                remaining = &mut remaining[skip..];
173            }
174
175            let (row, rest) = remaining.split_at_mut(region_width_bytes.min(remaining.len()));
176            *area = row;
177            remaining = rest;
178        }
179
180        Some(Self::new(
181            areas,
182            tile_x,
183            tile_y,
184            region_width,
185            region_height,
186        ))
187    }
188
189    pub(crate) fn row_mut(&mut self, y: u16) -> &mut [u8] {
190        self.areas[usize::from(y)]
191    }
192
193    pub fn areas(&mut self) -> &mut [&'a mut [u8]; Tile::HEIGHT as usize] {
194        &mut self.areas
195    }
196}