⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/desktop/wayland/
compositor.rs

1//! Wayland Compositor
2//!
3//! Manages surfaces and composites them into a back-buffer that can be
4//! presented to the hardware framebuffer. Surfaces are drawn in Z-order
5//! with per-pixel alpha blending for ARGB8888 and fast memcpy for XRGB8888.
6#![allow(dead_code)]
7
8use alloc::{collections::BTreeMap, vec, vec::Vec};
9
10use spin::RwLock;
11
12use super::{buffer, surface::Surface};
13use crate::{error::KernelError, graphics::PixelFormat};
14
15// ---------------------------------------------------------------------------
16// Desktop background color (dark blue-grey)
17// ---------------------------------------------------------------------------
18
19/// Desktop background color: ARGB packed as 0xAARRGGBB.
20const DESKTOP_BG_COLOR: u32 = 0xFF2D_3436;
21
22// ---------------------------------------------------------------------------
23// Compositor
24// ---------------------------------------------------------------------------
25
26/// Wayland compositor state.
27///
28/// Owns all surfaces and the software back-buffer used for compositing.
29pub struct Compositor {
30    /// All surfaces keyed by surface ID
31    surfaces: RwLock<BTreeMap<u32, Surface>>,
32    /// Z-order: first element is bottom-most, last is top-most
33    z_order: RwLock<Vec<u32>>,
34    /// Software back-buffer (XRGB8888, row-major, width*height u32 pixels)
35    back_buffer: RwLock<Vec<u32>>,
36    /// Back-buffer dimensions (atomic for interior mutability)
37    fb_width: core::sync::atomic::AtomicU32,
38    fb_height: core::sync::atomic::AtomicU32,
39    /// Whether any surface is dirty and compositing is needed
40    needs_composite: core::sync::atomic::AtomicBool,
41}
42
43impl Compositor {
44    /// Create a new compositor. Dimensions default to 0 until
45    /// `set_output_size`.
46    pub fn new() -> Self {
47        Self {
48            surfaces: RwLock::new(BTreeMap::new()),
49            z_order: RwLock::new(Vec::new()),
50            back_buffer: RwLock::new(Vec::new()),
51            fb_width: core::sync::atomic::AtomicU32::new(0),
52            fb_height: core::sync::atomic::AtomicU32::new(0),
53            needs_composite: core::sync::atomic::AtomicBool::new(false),
54        }
55    }
56
57    /// Configure the output resolution. Allocates the back-buffer.
58    pub fn set_output_size(&self, width: u32, height: u32) {
59        self.fb_width
60            .store(width, core::sync::atomic::Ordering::Release);
61        self.fb_height
62            .store(height, core::sync::atomic::Ordering::Release);
63        let pixel_count = (width as usize) * (height as usize);
64        *self.back_buffer.write() = vec![DESKTOP_BG_COLOR; pixel_count];
65    }
66
67    /// Register a new surface.
68    pub fn create_surface(&self, id: u32) -> Result<(), KernelError> {
69        let surface = Surface::new(id);
70        self.surfaces.write().insert(id, surface);
71        self.z_order.write().push(id);
72        Ok(())
73    }
74
75    /// Register a new surface owned by a specific client.
76    pub fn create_surface_for_client(&self, id: u32, client_id: u32) -> Result<(), KernelError> {
77        let surface = Surface::with_client(id, client_id);
78        self.surfaces.write().insert(id, surface);
79        self.z_order.write().push(id);
80        Ok(())
81    }
82
83    /// Destroy a surface and remove it from Z-order.
84    pub fn destroy_surface(&self, id: u32) -> Result<(), KernelError> {
85        self.surfaces.write().remove(&id);
86        self.z_order.write().retain(|&sid| sid != id);
87        Ok(())
88    }
89
90    /// Get a read reference to a surface.
91    pub fn get_surface(&self, id: u32) -> Option<Surface> {
92        let surfaces = self.surfaces.read();
93        // Clone the surface data we need -- avoids holding the lock across
94        // caller code.
95        surfaces.get(&id).map(|s| Surface {
96            id: s.id,
97            committed: s.committed.clone(),
98            pending: s.pending.clone(),
99            position: s.position,
100            size: s.size,
101            dirty: s.dirty,
102            mapped: s.mapped,
103            client_id: s.client_id,
104        })
105    }
106
107    /// Execute a closure with mutable access to a surface.
108    pub fn with_surface_mut<R, F: FnOnce(&mut Surface) -> R>(&self, id: u32, f: F) -> Option<R> {
109        let mut surfaces = self.surfaces.write();
110        surfaces.get_mut(&id).map(f)
111    }
112
113    /// Raise a surface to the top of the Z-order stack.
114    pub fn raise_surface(&self, id: u32) {
115        let mut z = self.z_order.write();
116        z.retain(|&sid| sid != id);
117        z.push(id);
118    }
119
120    /// Set a surface's position in compositor coordinates.
121    pub fn set_surface_position(&self, id: u32, x: i32, y: i32) {
122        if let Some(surface) = self.surfaces.write().get_mut(&id) {
123            surface.position = (x, y);
124        }
125    }
126
127    /// Mark that compositing is needed.
128    pub fn request_composite(&self) {
129        self.needs_composite
130            .store(true, core::sync::atomic::Ordering::Release);
131    }
132
133    /// Composite all visible, mapped surfaces in Z-order into the back-buffer.
134    ///
135    /// 1. Clear back-buffer to desktop background color.
136    /// 2. For each surface (bottom to top), blit its committed buffer.
137    /// 3. Mark surfaces as clean.
138    ///
139    /// Returns `true` if any pixels were actually drawn.
140    pub fn composite(&self) -> Result<bool, KernelError> {
141        let fb_w_val = self.fb_width.load(core::sync::atomic::Ordering::Acquire);
142        let fb_h_val = self.fb_height.load(core::sync::atomic::Ordering::Acquire);
143        if fb_w_val == 0 || fb_h_val == 0 {
144            return Ok(false);
145        }
146
147        let z_order = self.z_order.read().clone();
148        let mut surfaces = self.surfaces.write();
149        let mut bb = self.back_buffer.write();
150
151        let fb_w = fb_w_val as usize;
152        let fb_h = fb_h_val as usize;
153
154        // Step 1: clear to background
155        for pixel in bb.iter_mut() {
156            *pixel = DESKTOP_BG_COLOR;
157        }
158
159        let mut any_drawn = false;
160
161        // Step 2: blit surfaces in Z-order
162        for &sid in &z_order {
163            let surface = match surfaces.get(&sid) {
164                Some(s) => s,
165                None => continue,
166            };
167
168            if !surface.mapped || surface.size.0 == 0 || surface.size.1 == 0 {
169                continue;
170            }
171
172            let buf = match &surface.committed.buffer {
173                Some(b) => b,
174                None => continue,
175            };
176
177            // Blit the surface buffer into the back-buffer directly from
178            // the SHM pool, avoiding a multi-MB `.to_vec()` clone per surface.
179            let sx = surface.position.0;
180            let sy = surface.position.1;
181            let sw = buf.width as usize;
182            let sh = buf.height as usize;
183            let stride = buf.stride as usize;
184            let format = buf.format;
185            let pool_id = buf.pool_id;
186            let pool_buffer_id = buf.pool_buffer_id;
187
188            if pool_id == 0 {
189                continue;
190            }
191
192            let drew = buffer::with_pool(pool_id, |pool| {
193                let pixels = match pool.read_buffer_pixels(pool_buffer_id) {
194                    Some(p) => p,
195                    None => return false,
196                };
197
198                for row in 0..sh {
199                    let dst_y = sy as isize + row as isize;
200                    if dst_y < 0 || dst_y >= fb_h as isize {
201                        continue;
202                    }
203                    let dst_y = dst_y as usize;
204
205                    for col in 0..sw {
206                        let dst_x = sx as isize + col as isize;
207                        if dst_x < 0 || dst_x >= fb_w as isize {
208                            continue;
209                        }
210                        let dst_x = dst_x as usize;
211
212                        let src_off = row * stride + col * format.bpp() as usize;
213                        if src_off + 3 >= pixels.len() {
214                            break;
215                        }
216
217                        let dst_idx =
218                            match dst_y.checked_mul(fb_w).and_then(|v| v.checked_add(dst_x)) {
219                                Some(idx) if idx < bb.len() => idx,
220                                _ => continue,
221                            };
222
223                        match format {
224                            PixelFormat::Xrgb8888 => {
225                                let b_val = pixels[src_off] as u32;
226                                let g_val = pixels[src_off + 1] as u32;
227                                let r_val = pixels[src_off + 2] as u32;
228                                bb[dst_idx] = 0xFF00_0000 | (r_val << 16) | (g_val << 8) | b_val;
229                            }
230                            PixelFormat::Argb8888 => {
231                                let b_src = pixels[src_off] as u32;
232                                let g_src = pixels[src_off + 1] as u32;
233                                let r_src = pixels[src_off + 2] as u32;
234                                let a_src = pixels[src_off + 3] as u32;
235
236                                if a_src == 255 {
237                                    bb[dst_idx] =
238                                        0xFF00_0000 | (r_src << 16) | (g_src << 8) | b_src;
239                                } else if a_src > 0 {
240                                    let dst_pixel = bb[dst_idx];
241                                    let r_dst = (dst_pixel >> 16) & 0xFF;
242                                    let g_dst = (dst_pixel >> 8) & 0xFF;
243                                    let b_dst = dst_pixel & 0xFF;
244
245                                    let inv_a = 255 - a_src;
246                                    let r_out = (r_src * a_src + r_dst * inv_a) / 255;
247                                    let g_out = (g_src * a_src + g_dst * inv_a) / 255;
248                                    let b_out = (b_src * a_src + b_dst * inv_a) / 255;
249
250                                    bb[dst_idx] =
251                                        0xFF00_0000 | (r_out << 16) | (g_out << 8) | b_out;
252                                }
253                            }
254                            PixelFormat::Rgb565 => {
255                                if src_off + 1 < pixels.len() {
256                                    let raw = (pixels[src_off] as u16)
257                                        | ((pixels[src_off + 1] as u16) << 8);
258                                    let r5 = ((raw >> 11) & 0x1F) as u32;
259                                    let g6 = ((raw >> 5) & 0x3F) as u32;
260                                    let b5 = (raw & 0x1F) as u32;
261                                    let r8 = (r5 * 255 + 15) / 31;
262                                    let g8 = (g6 * 255 + 31) / 63;
263                                    let b8 = (b5 * 255 + 15) / 31;
264                                    bb[dst_idx] = 0xFF00_0000 | (r8 << 16) | (g8 << 8) | b8;
265                                }
266                            }
267                            // Other formats: treat as opaque XRGB
268                            _ => {
269                                if src_off + 2 < pixels.len() {
270                                    let b_val = pixels[src_off] as u32;
271                                    let g_val = pixels[src_off + 1] as u32;
272                                    let r_val = pixels[src_off + 2] as u32;
273                                    bb[dst_idx] =
274                                        0xFF00_0000 | (r_val << 16) | (g_val << 8) | b_val;
275                                }
276                            }
277                        }
278                    }
279                }
280                true
281            });
282            if drew == Some(true) {
283                any_drawn = true;
284            }
285        }
286
287        // Step 3: clear dirty flags
288        for surface in surfaces.values_mut() {
289            surface.clear_dirty();
290        }
291
292        self.needs_composite
293            .store(false, core::sync::atomic::Ordering::Release);
294
295        Ok(any_drawn)
296    }
297
298    /// Get a snapshot of the back-buffer for presentation to hardware.
299    pub fn back_buffer(&self) -> Vec<u32> {
300        self.back_buffer.read().clone()
301    }
302
303    /// Execute a closure with a read-only reference to the back-buffer.
304    ///
305    /// Avoids the 4MB clone that `back_buffer()` performs each call.
306    pub fn with_back_buffer<R, F: FnOnce(&[u32]) -> R>(&self, f: F) -> R {
307        let bb = self.back_buffer.read();
308        f(&bb)
309    }
310
311    /// Execute a closure with a mutable reference to the back-buffer.
312    ///
313    /// Used for overlay rendering (app switcher, launcher, notifications,
314    /// screen lock) that draws directly into the composited back-buffer
315    /// after `composite()` runs and before the hardware framebuffer blit.
316    pub fn with_back_buffer_mut<R, F: FnOnce(&mut [u32]) -> R>(&self, f: F) -> R {
317        let mut bb = self.back_buffer.write();
318        f(&mut bb)
319    }
320
321    /// Set whether a surface is mapped (visible during compositing).
322    ///
323    /// The compositor skips unmapped surfaces during `composite()`.
324    /// Used for virtual workspace switching to hide/show window surfaces.
325    pub fn set_surface_mapped(&self, id: u32, mapped: bool) {
326        self.with_surface_mut(id, |surface| {
327            surface.mapped = mapped;
328        });
329    }
330
331    /// Back-buffer dimensions.
332    pub fn output_size(&self) -> (u32, u32) {
333        (
334            self.fb_width.load(core::sync::atomic::Ordering::Acquire),
335            self.fb_height.load(core::sync::atomic::Ordering::Acquire),
336        )
337    }
338
339    /// Number of surfaces.
340    pub fn surface_count(&self) -> usize {
341        self.surfaces.read().len()
342    }
343}
344
345impl Default for Compositor {
346    fn default() -> Self {
347        Self::new()
348    }
349}
350
351// ---------------------------------------------------------------------------
352// WM-5: Compositing effects
353// ---------------------------------------------------------------------------
354
355/// Render a soft shadow behind a window surface.
356///
357/// `shadow_buffer` should be pre-allocated with
358/// `(width + 2*radius) * (height + 2*radius)` u32 elements.
359/// Uses a 3-pass box blur approximating a Gaussian shadow.
360pub fn render_shadow(
361    shadow_buffer: &mut [u32],
362    buf_width: u32,
363    width: u32,
364    height: u32,
365    radius: u32,
366    opacity: u8,
367) {
368    crate::desktop::animation::render_shadow(
369        shadow_buffer,
370        buf_width,
371        width,
372        height,
373        radius,
374        opacity,
375    );
376}
377
378/// Alpha-blend a single source pixel (ARGB8888) onto a destination pixel
379/// (XRGB8888). Returns the blended pixel as 0xFFRRGGBB.
380pub fn alpha_blend(src: u32, dst: u32) -> u32 {
381    let src_a = (src >> 24) & 0xFF;
382    if src_a == 255 {
383        return src | 0xFF00_0000;
384    }
385    if src_a == 0 {
386        return dst;
387    }
388
389    let src_r = (src >> 16) & 0xFF;
390    let src_g = (src >> 8) & 0xFF;
391    let src_b = src & 0xFF;
392
393    let dst_r = (dst >> 16) & 0xFF;
394    let dst_g = (dst >> 8) & 0xFF;
395    let dst_b = dst & 0xFF;
396
397    let inv_a = 255 - src_a;
398    let r = (src_r * src_a + dst_r * inv_a) / 255;
399    let g = (src_g * src_a + dst_g * inv_a) / 255;
400    let b = (src_b * src_a + dst_b * inv_a) / 255;
401
402    0xFF00_0000 | (r << 16) | (g << 8) | b
403}
404
405/// Apply a per-surface opacity to a u32 pixel (XRGB8888 -> ARGB8888).
406///
407/// Multiplies the existing alpha channel by `opacity / 255`.
408pub fn apply_opacity(pixel: u32, opacity: u8) -> u32 {
409    if opacity == 255 {
410        return pixel;
411    }
412    let a = (pixel >> 24) & 0xFF;
413    let new_a = (a * opacity as u32) / 255;
414    (new_a << 24) | (pixel & 0x00FF_FFFF)
415}
416
417#[cfg(test)]
418mod tests {
419    use super::{super::buffer::Buffer, *};
420    use crate::graphics::PixelFormat;
421
422    #[test]
423    fn test_create_destroy_surface() {
424        let comp = Compositor::new();
425        comp.create_surface(1).unwrap();
426        assert_eq!(comp.surface_count(), 1);
427        comp.destroy_surface(1).unwrap();
428        assert_eq!(comp.surface_count(), 0);
429    }
430
431    #[test]
432    fn test_composite_empty() {
433        let comp = Compositor::new();
434        comp.set_output_size(64, 64);
435        let drawn = comp.composite().unwrap();
436        assert!(!drawn); // no surfaces
437                         // Back-buffer should be filled with background color
438        let bb = comp.back_buffer();
439        assert_eq!(bb.len(), 64 * 64);
440        assert_eq!(bb[0], DESKTOP_BG_COLOR);
441    }
442
443    #[test]
444    fn test_z_order_raise() {
445        let comp = Compositor::new();
446        comp.create_surface(1).unwrap();
447        comp.create_surface(2).unwrap();
448        comp.create_surface(3).unwrap();
449
450        comp.raise_surface(1);
451        let z = comp.z_order.read().clone();
452        assert_eq!(z, vec![2, 3, 1]);
453    }
454}