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}