cvkg_compositor/layer.rs
1//! # Layer Tree & Material Definitions
2//!
3//! Defines the retained-mode layer orchestration structures.
4//! The compositor organizes UI elements into a `LayerTree`, where each `Layer`
5//! has an explicit `Material` property that dictates which GPU pass it belongs to
6//! in the Backdrop Capture Architecture.
7
8use cvkg_core::Rect;
9use std::collections::HashMap;
10
11/// Unique identifier for a layer in the tree.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
13pub struct LayerId(pub u64);
14
15/// Material type that determines which GPU pass a layer's draw calls are routed to
16/// in the Backdrop Capture Architecture.
17///
18/// The blend mode variants correspond to the 16 SVG 1.1 blend modes from
19/// the CSS Compositing and Blending Level 1 specification. When a blend mode
20/// is set, the draw call's fragment shader uses the corresponding blend function
21/// instead of standard alpha compositing.
22///
23/// `Isolated` triggers off-screen buffer rendering: the layer and all its
24/// children are rendered to a separate texture, then composited back into the
25/// main scene. This matches the SVG `isolation` property.
26#[derive(Debug, Clone, PartialEq, Default)]
27pub enum Material {
28 /// Opaque or standard UI. Rendered in the initial Scene Capture pass
29 /// with standard alpha compositing (src-over).
30 #[default]
31 Opaque,
32 /// Glassmorphism elements. Rendered in the Material Composite pass,
33 /// sampling from the Kawase Blur pyramid.
34 /// The `blur_radius` controls the blur intensity.
35 /// The `depth_index` (0=foreground, higher=more background) controls
36 /// depth-aware tinting: background windows get stronger tint.
37 Glass {
38 blur_radius: f32,
39 /// Z-order depth for depth-aware tinting. 0 = key/foreground window.
40 /// Higher values = more background. Default: 0.
41 depth_index: u32,
42 },
43 /// Overlay UI (crisp text, focus rings, edge lighting).
44 /// Rendered in the final Foreground pass, on top of glass.
45 Overlay,
46
47 // ── SVG Blend Modes (CSS Compositing Level 1) ──────────────────────────
48 /// Multiplied blend: multiplies source and destination colors.
49 /// Formula: result = src * dst
50 Multiply,
51 /// Screen blend: inverse of multiply.
52 /// Formula: result = 1 - (1 - src) * (1 - dst)
53 Screen,
54 /// Overlay blend: combines multiply and screen based on destination.
55 /// Formula: if dst < 0.5 then 2*src*dst else 1-2*(1-src)*(1-dst)
56 BlendOverlay,
57 /// Darken blend: keeps the darker of source and destination per channel.
58 /// Formula: result = min(src, dst)
59 Darken,
60 /// Lighten blend: keeps the lighter of source and destination per channel.
61 /// Formula: result = max(src, dst)
62 Lighten,
63 /// Color-dodge blend: brightens destination to reflect source.
64 /// Formula: result = dst / (1 - src)
65 ColorDodge,
66 /// Color-burn blend: darkens destination to reflect source.
67 /// Formula: result = 1 - (1 - dst) / src
68 ColorBurn,
69 /// Hard-light blend: like overlay, but based on source instead of dest.
70 /// Formula: if src < 0.5 then 2*src*dst else 1-2*(1-src)*(1-dst)
71 HardLight,
72 /// Soft-light blend: subtle highlights/shadows.
73 /// Formula: result = (1-2*src)*dst^2 + 2*src*dst (simplified Pegtop)
74 SoftLight,
75 /// Difference blend: subtracts colors and takes absolute value.
76 /// Formula: result = |src - dst|
77 Difference,
78 /// Exclusion blend: similar to difference but lower contrast.
79 /// Formula: result = src + dst - 2*src*dst
80 Exclusion,
81 /// Hue blend: applies source hue to destination saturation/luminosity.
82 Hue,
83 /// Saturation blend: applies source saturation to destination hue/luminosity.
84 Saturation,
85 /// Color blend: applies source hue/saturation to destination luminosity.
86 Color,
87 /// Luminosity blend: applies source luminosity to destination hue/saturation.
88 Luminosity,
89
90 /// Isolated rendering: layer and children are rendered to an off-screen
91 /// buffer, then composited back into the main scene. This matches the
92 /// SVG `isolation` property and is required for correct blend mode
93 /// behavior when child elements should not blend with the background.
94 Isolated,
95
96 /// Renders the layer and its children into an offscreen buffer, then applies
97 /// a custom post-processing WGSL shader when blending back into the scene.
98 ShaderEffect {
99 /// Name of the registered shader effect (e.g. "HeatShimmer")
100 effect_name: String,
101 /// Dynamic parameters for the shader, serialized as a JSON string
102 params_json: String,
103 },
104}
105
106/// A draw command within a layer.
107/// This is a simplified representation that the compositor produces
108/// and the renderer consumes.
109#[derive(Debug, Clone)]
110pub struct DrawCommand {
111 /// Texture binding index (None for solid color).
112 pub texture_id: Option<u32>,
113 /// Scissor rectangle for clipping.
114 pub scissor_rect: Option<Rect>,
115 /// Range in the shared index buffer.
116 pub index_start: u32,
117 pub index_count: u32,
118 /// Instance ID for instanced rendering transform data.
119 pub instance_id: u32,
120}
121
122/// A node in the retained-mode layer tree.
123/// Each layer represents a compositable unit with its own material,
124/// transform, and draw list.
125#[derive(Debug, Clone)]
126pub struct Layer {
127 /// Unique identifier.
128 pub id: LayerId,
129 /// Screen-space bounding rectangle.
130 pub bounds: Rect,
131 /// 4x4 transformation matrix (column-major).
132 pub transform: [f32; 16],
133 /// Material determining which GPU pass this layer belongs to.
134 pub material: Material,
135 /// Draw commands for this layer.
136 pub draw_list: Vec<DrawCommand>,
137 /// Child layer IDs in painter's order (back to front).
138 pub children: Vec<LayerId>,
139 /// Visibility flag.
140 pub visible: bool,
141 /// Opacity multiplier. Defaults to 1.0.
142 pub opacity: f32,
143}
144
145impl Default for Layer {
146 fn default() -> Self {
147 Self {
148 id: LayerId::default(),
149 bounds: Rect::zero(),
150 transform: [
151 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
152 ],
153 material: Material::Opaque,
154 draw_list: Vec::new(),
155 children: Vec::new(),
156 visible: true,
157 opacity: 1.0,
158 }
159 }
160}
161
162/// The retained-mode layer tree.
163/// Maintained across frames by the `CompositorEngine`.
164pub struct LayerTree {
165 /// All layers indexed by ID.
166 layers: HashMap<LayerId, Layer>,
167 /// Root layer IDs in painter's order (back to front).
168 roots: Vec<LayerId>,
169 /// Next available layer ID counter.
170 next_id: u64,
171 /// Generation counter for damage tracking.
172 generation: u64,
173 /// Per-layer generation stamps for change detection.
174 layer_generations: HashMap<LayerId, u64>,
175}
176
177impl Default for LayerTree {
178 fn default() -> Self {
179 Self::new()
180 }
181}
182
183impl LayerTree {
184 /// Creates a new empty layer tree.
185 pub fn new() -> Self {
186 Self {
187 layers: HashMap::new(),
188 roots: Vec::new(),
189 next_id: 1,
190 generation: 0,
191 layer_generations: HashMap::new(),
192 }
193 }
194
195 /// Allocates and returns a new layer ID.
196 pub fn allocate_id(&mut self) -> LayerId {
197 let id = LayerId(self.next_id);
198 self.next_id += 1;
199 id
200 }
201
202 /// Inserts a new layer into the tree.
203 pub fn insert_layer(&mut self, layer: Layer) {
204 let id = layer.id;
205 self.layer_generations.insert(id, self.generation);
206 self.layers.insert(id, layer);
207 }
208
209 /// Removes a layer from the tree.
210 pub fn remove_layer(&mut self, id: LayerId) -> Option<Layer> {
211 self.layer_generations.remove(&id);
212 self.layers.remove(&id)
213 }
214
215 /// Returns a reference to a layer by ID.
216 pub fn get_layer(&self, id: LayerId) -> Option<&Layer> {
217 self.layers.get(&id)
218 }
219
220 /// Returns a mutable reference to a layer by ID.
221 pub fn get_layer_mut(&mut self, id: LayerId) -> Option<&mut Layer> {
222 self.layers.get_mut(&id)
223 }
224
225 /// Returns the root layer IDs in painter's order.
226 pub fn roots(&self) -> &[LayerId] {
227 &self.roots
228 }
229
230 /// Sets the root layer IDs.
231 pub fn set_roots(&mut self, roots: Vec<LayerId>) {
232 self.roots = roots;
233 }
234
235 /// Marks a layer as dirty (modified since last frame).
236 pub fn mark_dirty(&mut self, id: LayerId) {
237 self.layer_generations.insert(id, self.generation);
238 }
239
240 /// Returns true if the layer has been modified since the given generation.
241 pub fn is_dirty(&self, id: LayerId, since_generation: u64) -> bool {
242 self.layer_generations
243 .get(&id)
244 .is_some_and(|&g| g > since_generation)
245 }
246
247 /// Advances the global generation counter.
248 /// Call once per frame after processing damage.
249 pub fn advance_generation(&mut self) {
250 self.generation += 1;
251 }
252
253 /// Returns the current global generation.
254 pub fn generation(&self) -> u64 {
255 self.generation
256 }
257
258 /// Iterates over all layers in the tree.
259 pub fn iter_layers(&self) -> impl Iterator<Item = &Layer> {
260 self.layers.values()
261 }
262
263 /// Returns the number of layers in the tree.
264 pub fn len(&self) -> usize {
265 self.layers.len()
266 }
267
268 /// Returns true if the tree has no layers.
269 pub fn is_empty(&self) -> bool {
270 self.layers.is_empty()
271 }
272
273 /// Clears all layers from the tree.
274 pub fn clear(&mut self) {
275 self.layers.clear();
276 self.roots.clear();
277 self.layer_generations.clear();
278 self.generation += 1;
279 }
280}