Skip to main content

vello_cpu/
layer_manager.rs

1// Copyright 2025 the Vello Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! Layer management for filter effects rendering.
5//!
6//! This module provides infrastructure for managing persistent layer buffers
7//! that can be rendered to independently and then composited together. This is
8//! necessary for spatial filter effects that require access to a fully-rendered
9//! layer (e.g., Gaussian blur).
10//!
11//! Layers are stored as Pixmap instances (row-major, u8 RGBA format), which
12//! allows efficient filter operations without layout conversion overhead.
13//! Each layer maintains its own bounding box in wide tile coordinates, allowing
14//! efficient memory usage for layers that don't span the entire render target.
15
16use crate::region::Region;
17use hashbrown::HashMap;
18use vello_common::coarse::WideTilesBbox;
19use vello_common::pixmap::Pixmap;
20
21/// Manages persistent layer storage for filter effects.
22///
23/// Each layer is allocated as a `Pixmap` with row-major RGBA8 format.
24/// This eliminates conversion overhead for filter operations.
25#[derive(Debug)]
26pub struct LayerManager {
27    /// Map of layer ID to (Pixmap, wtile bounding box).
28    /// The Pixmap contains the layer's pixel data, and the Bbox defines which
29    /// wide tiles this layer occupies (in wide tile coordinates).
30    layers: HashMap<u32, (Pixmap, WideTilesBbox)>,
31    /// Next available layer ID for automatic allocation.
32    next_id: u32,
33    /// Reusable scratch buffer for filter operations that need temporary storage.
34    /// Examples include separable convolution passes (e.g., Gaussian blur) or
35    /// intermediate compositing results. This buffer is lazily allocated and
36    /// automatically resized as needed to avoid repeated allocations.
37    scratch_buffer: Option<Pixmap>,
38}
39
40impl Default for LayerManager {
41    /// Creates a new, empty layer manager with no allocated layers.
42    ///
43    /// Layer IDs start from 1 (ID 0 is reserved for internal use).
44    fn default() -> Self {
45        Self {
46            layers: HashMap::new(),
47            next_id: 1,
48            scratch_buffer: None,
49        }
50    }
51}
52
53impl LayerManager {
54    /// Create a new, empty layer manager.
55    ///
56    /// Layers can be registered on-demand using [`register_layer`](Self::register_layer).
57    pub fn new() -> Self {
58        Self::default()
59    }
60
61    /// Register a layer with a specific ID, wide tile bounding box, and pixel data.
62    ///
63    /// The provided `Pixmap` should already contain the layer's pixel data (e.g., the
64    /// result of rendering a layer or applying a filter effect).
65    ///
66    /// If a layer with this ID already exists, this method does nothing (no replacement).
67    ///
68    /// # Parameters
69    /// - `layer_id`: A unique identifier for this layer. Layer ID 0 is reserved for internal use.
70    /// - `wtile_bbox`: The bounding box of the layer in wide tile coordinates. This defines
71    ///   the region of the layer that contains valid pixel data, enabling efficient memory
72    ///   usage for layers that don't span the entire render target.
73    /// - `pixmap`: The pixel data for this layer in row-major RGBA8 format.
74    pub fn register_layer(&mut self, layer_id: u32, wtile_bbox: WideTilesBbox, pixmap: Pixmap) {
75        self.layers.insert(layer_id, (pixmap, wtile_bbox));
76
77        if layer_id >= self.next_id {
78            self.next_id = layer_id + 1;
79        }
80    }
81
82    /// Get a mutable `Region` view into a specific wide tile within a layer.
83    ///
84    /// This extracts a tile-sized region from the layer's Pixmap, allowing
85    /// rendering or compositing operations on individual tiles without copying
86    /// pixel data. The returned Region provides a mutable view into the underlying
87    /// layer buffer.
88    ///
89    /// Returns [`None`] if:
90    /// - The layer doesn't exist
91    /// - The tile position is outside the layer's bounding box
92    /// - The tile extraction fails (e.g., invalid coordinates)
93    ///
94    /// # Parameters
95    /// - `layer_id`: The ID of the layer to access
96    /// - `tile_x`: The x-coordinate of the wide tile in global wide tile coordinates
97    /// - `tile_y`: The y-coordinate of the wide tile in global wide tile coordinates
98    ///
99    /// # Coordinate Systems
100    /// The input coordinates (`tile_x`, `tile_y`) are in *global* wide tile space,
101    /// but they are automatically converted to *local* coordinates relative to the
102    /// layer's bounding box before extracting the region.
103    pub fn layer_tile_region_mut(
104        &mut self,
105        layer_id: u32,
106        tile_x: u16,
107        tile_y: u16,
108    ) -> Option<Region<'_>> {
109        let (pixmap, bbox) = self.layers.get_mut(&layer_id)?;
110
111        // Ensure the requested tile is within the layer's allocated bounds
112        if !bbox.contains(tile_x, tile_y) {
113            return None;
114        }
115
116        // Convert global tile coordinates to layer-local (bbox-relative) coordinates
117        let local_x = tile_x - bbox.x0();
118        let local_y = tile_y - bbox.y0();
119
120        // Extract a mutable Region view of the tile from the underlying pixmap
121        Region::from_pixmap_tile(pixmap, local_x, local_y)
122    }
123
124    /// Get or create a scratch buffer of at least the requested dimensions.
125    ///
126    /// This buffer is reused across filter operations to minimize allocations.
127    /// If the existing buffer is large enough, it's reused; otherwise, a new
128    /// (larger) buffer is allocated.
129    ///
130    /// # Parameters
131    /// - `width`: Minimum width in pixels
132    /// - `height`: Minimum height in pixels
133    ///
134    /// # Returns
135    /// A mutable reference to a `Pixmap` of at least `width × height` pixels.
136    /// The actual buffer may be larger than requested if it was previously allocated
137    /// with larger dimensions.
138    pub fn get_scratch_buffer(&mut self, width: u16, height: u16) -> &mut Pixmap {
139        match &mut self.scratch_buffer {
140            None => {
141                // No buffer exists yet, allocate a new one
142                self.scratch_buffer = Some(Pixmap::new(width, height));
143            }
144            Some(buf) if buf.width() < width || buf.height() < height => {
145                // Existing buffer is too small, resize it
146                buf.resize(width, height);
147            }
148            // Buffer is already large enough, reuse it without reallocation
149            Some(_) => {}
150        }
151
152        self.scratch_buffer.as_mut().unwrap()
153    }
154}