cvkg_compositor/
engine.rs1use crate::layer::{DrawCommand, Layer, LayerId, LayerTree, Material};
15use cvkg_core::Rect;
16use log::warn;
17
18#[derive(Debug, Clone)]
21pub struct RoutedDrawCommand {
22 pub command: DrawCommand,
24 pub material: Material,
26 pub source_layer: LayerId,
28 pub z_index: u32,
30}
31
32#[derive(Debug, Clone)]
34pub enum RenderCommand {
35 Draw(RoutedDrawCommand),
37 PushOffscreen {
39 source_layer: LayerId,
40 material: Material,
41 bounds: Rect,
42 },
43 PopOffscreen,
45}
46
47#[derive(Debug, Default)]
50pub struct CommandBuckets {
51 pub scene_commands: Vec<RenderCommand>,
53 pub glass_commands: Vec<RenderCommand>,
55 pub overlay_commands: Vec<RenderCommand>,
57}
58
59impl CommandBuckets {
60 pub fn total_count(&self) -> usize {
62 self.scene_commands.len() + self.glass_commands.len() + self.overlay_commands.len()
63 }
64
65 pub fn is_empty(&self) -> bool {
67 self.scene_commands.is_empty()
68 && self.glass_commands.is_empty()
69 && self.overlay_commands.is_empty()
70 }
71
72 pub fn clear(&mut self) {
74 self.scene_commands.clear();
75 self.glass_commands.clear();
76 self.overlay_commands.clear();
77 }
78}
79
80#[derive(Debug, Default)]
82pub struct DamageInfo {
83 pub dirty_layers: Vec<LayerId>,
85 pub frame_generation: u64,
87 pub full_rebuild_needed: bool,
89}
90
91pub struct CompositorEngine {
93 layer_tree: LayerTree,
95 flatten_buffer: Vec<RenderCommand>,
97 last_flatten_generation: u64,
99 current_damage: DamageInfo,
101 z_counter: u32,
103 has_active_shaders: bool,
105}
106
107impl Default for CompositorEngine {
108 fn default() -> Self {
109 Self::new()
110 }
111}
112
113impl CompositorEngine {
114 pub fn new() -> Self {
116 Self {
117 layer_tree: LayerTree::new(),
118 flatten_buffer: Vec::new(),
119 last_flatten_generation: 0,
120 current_damage: DamageInfo::default(),
121 z_counter: 0,
122 has_active_shaders: false,
123 }
124 }
125
126 pub fn layer_tree(&self) -> &LayerTree {
128 &self.layer_tree
129 }
130
131 pub fn layer_tree_mut(&mut self) -> &mut LayerTree {
133 &mut self.layer_tree
134 }
135
136 pub fn create_layer(&mut self, layer: Layer) -> LayerId {
139 let id = layer.id;
140 self.layer_tree.insert_layer(layer);
141 self.current_damage.dirty_layers.push(id);
142 self.current_damage.full_rebuild_needed = true;
143 id
144 }
145
146 pub fn remove_layer(&mut self, id: LayerId) -> Option<Layer> {
148 self.current_damage.dirty_layers.push(id);
149 self.current_damage.full_rebuild_needed = true;
150 self.layer_tree.remove_layer(id)
151 }
152
153 pub fn mark_dirty(&mut self, id: LayerId) {
155 if self.layer_tree.get_layer(id).is_some() {
156 self.layer_tree.mark_dirty(id);
157 self.current_damage.dirty_layers.push(id);
158 }
159 }
160
161 pub fn damage_info(&self) -> &DamageInfo {
163 &self.current_damage
164 }
165
166 pub fn flatten_and_route(&mut self) -> CommandBuckets {
177 let mut buckets = CommandBuckets::default();
178
179 if self.layer_tree.is_empty() {
180 return buckets;
181 }
182
183 self.flatten_buffer.clear();
185 self.z_counter = 0;
186 self.has_active_shaders = false;
187
188 let roots = self.layer_tree.roots().to_vec();
190 Self::flatten_tree(
191 &mut self.layer_tree,
192 &roots,
193 &mut self.flatten_buffer,
194 &mut self.z_counter,
195 &mut self.has_active_shaders,
196 );
197
198 for cmd in &self.flatten_buffer {
200 match cmd {
201 RenderCommand::Draw(routed) => match routed.material {
202 Material::Opaque
203 | Material::Multiply
204 | Material::Screen
205 | Material::BlendOverlay
206 | Material::Darken
207 | Material::Lighten
208 | Material::ColorDodge
209 | Material::ColorBurn
210 | Material::HardLight
211 | Material::SoftLight
212 | Material::Difference
213 | Material::Exclusion
214 | Material::Hue
215 | Material::Saturation
216 | Material::Color
217 | Material::Luminosity => {
218 buckets.scene_commands.push(cmd.clone());
219 }
220 Material::Isolated | Material::ShaderEffect { .. } => {
221 buckets.scene_commands.push(cmd.clone());
222 }
223 Material::Glass { .. } => {
224 buckets.glass_commands.push(cmd.clone());
225 }
226 Material::Overlay => {
227 buckets.overlay_commands.push(cmd.clone());
228 }
229 },
230 RenderCommand::PushOffscreen { .. } | RenderCommand::PopOffscreen => {
231 buckets.scene_commands.push(cmd.clone());
233 }
234 }
235 }
236
237 self.last_flatten_generation = self.layer_tree.generation();
239 self.current_damage.frame_generation = self.last_flatten_generation;
240 self.current_damage.dirty_layers.clear();
241 self.current_damage.full_rebuild_needed = false;
242
243 buckets
244 }
245
246 fn flatten_tree(
251 layer_tree: &mut LayerTree,
252 layer_ids: &[LayerId],
253 buffer: &mut Vec<RenderCommand>,
254 z_counter: &mut u32,
255 has_active_shaders: &mut bool,
256 ) {
257 for layer_id in layer_ids {
258 Self::flatten_layer(layer_tree, *layer_id, buffer, z_counter, has_active_shaders);
259 }
260 }
261
262 fn flatten_layer(
263 layer_tree: &mut LayerTree,
264 layer_id: LayerId,
265 buffer: &mut Vec<RenderCommand>,
266 z_counter: &mut u32,
267 has_active_shaders: &mut bool,
268 ) {
269 let layer = match layer_tree.get_layer(layer_id) {
270 Some(l) => l,
271 None => {
272 warn!(
273 "CompositorEngine: referenced layer {:?} not found in tree",
274 layer_id
275 );
276 return;
277 }
278 };
279
280 if !layer.visible {
281 return;
282 }
283
284 let material = layer.material.clone();
285 let draw_list: Vec<_> = layer.draw_list.to_vec();
286 let children: Vec<_> = layer.children.iter().rev().cloned().collect();
287 let bounds = layer.bounds;
288
289 let is_offscreen = matches!(material, Material::Isolated | Material::ShaderEffect { .. });
290
291 if is_offscreen {
292 buffer.push(RenderCommand::PushOffscreen {
293 source_layer: layer_id,
294 material: material.clone(),
295 bounds,
296 });
297
298 if matches!(material, Material::ShaderEffect { .. }) {
299 *has_active_shaders = true;
300 }
301 }
302
303 for cmd in &draw_list {
304 buffer.push(RenderCommand::Draw(RoutedDrawCommand {
305 command: cmd.clone(),
306 material: material.clone(),
307 source_layer: layer_id,
308 z_index: *z_counter,
309 }));
310 *z_counter += 1;
311 }
312
313 for child_id in &children {
314 Self::flatten_layer(layer_tree, *child_id, buffer, z_counter, has_active_shaders);
315 }
316
317 if is_offscreen {
318 buffer.push(RenderCommand::PopOffscreen);
319 }
320 }
321
322 pub fn needs_reflatten(&self) -> bool {
324 if self.has_active_shaders {
325 return true;
326 }
327 if self.current_damage.full_rebuild_needed {
328 return true;
329 }
330 if !self.current_damage.dirty_layers.is_empty() {
331 return true;
332 }
333 self.layer_tree.generation() > self.last_flatten_generation
334 }
335
336 pub fn end_frame(&mut self) {
338 self.layer_tree.advance_generation();
339 }
340
341 pub fn clear(&mut self) {
343 self.layer_tree.clear();
344 self.flatten_buffer.clear();
345 self.last_flatten_generation = 0;
346 self.current_damage = DamageInfo::default();
347 self.z_counter = 0;
348 self.has_active_shaders = false;
349 }
350}