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

veridian_kernel/graphics/
gl_compositor.rs

1//! OpenGL-style Compositor
2//!
3//! Provides a GPU-accelerated compositing pipeline that manages surfaces,
4//! performs alpha-over blending, and renders to a back buffer. Uses the
5//! texture atlas for efficient surface packing and supports multiple blend
6//! modes with integer-only arithmetic.
7//!
8//! All blending uses integer math: `result = (src * alpha + dst * (255 -
9//! alpha)) / 255`.
10
11#![allow(dead_code)]
12
13use alloc::{collections::BTreeMap, vec, vec::Vec};
14
15use super::texture_atlas::{AtlasRegion, ShelfAllocator};
16
17// ---------------------------------------------------------------------------
18// Blend mode
19// ---------------------------------------------------------------------------
20
21/// Blending operation applied when compositing a surface onto the back buffer.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
23pub enum BlendMode {
24    /// Standard alpha-over compositing (Porter-Duff SRC_OVER).
25    #[default]
26    Over,
27    /// Additive blending: `dst + src * alpha` (clamped to 255).
28    Add,
29    /// Multiplicative blending: `(dst * src) / 255`.
30    Multiply,
31}
32
33// ---------------------------------------------------------------------------
34// Surface
35// ---------------------------------------------------------------------------
36
37/// Unique surface identifier.
38pub type SurfaceId = u32;
39
40/// A compositable surface managed by the GL compositor.
41#[derive(Debug, Clone)]
42pub struct GlSurface {
43    /// Unique identifier.
44    pub id: SurfaceId,
45    /// Texture region in the atlas.
46    pub atlas_region: AtlasRegion,
47    /// Z ordering (higher = closer to viewer).
48    pub z_order: i32,
49    /// Whether this surface is visible.
50    pub visible: bool,
51    /// Surface opacity (0 = transparent, 255 = opaque).
52    pub opacity: u8,
53    /// Blend mode for compositing.
54    pub blend_mode: BlendMode,
55    /// Position in viewport coordinates.
56    pub x: i32,
57    /// Position in viewport coordinates.
58    pub y: i32,
59    /// Pixel data (ARGB8888), row-major.
60    pub pixels: Vec<u32>,
61}
62
63// ---------------------------------------------------------------------------
64// GL compositor
65// ---------------------------------------------------------------------------
66
67/// GPU-style compositor that manages surfaces and composites them to a back
68/// buffer.
69#[derive(Debug)]
70pub struct GlCompositor {
71    /// Registered surfaces keyed by ID.
72    surfaces: BTreeMap<SurfaceId, GlSurface>,
73    /// Texture atlas for surface packing.
74    atlas: ShelfAllocator,
75    /// Viewport width.
76    viewport_width: u32,
77    /// Viewport height.
78    viewport_height: u32,
79    /// Composited back buffer (ARGB8888).
80    back_buffer: Vec<u32>,
81    /// Next surface ID to assign.
82    next_id: SurfaceId,
83    /// Background clear colour (ARGB8888).
84    clear_color: u32,
85}
86
87impl GlCompositor {
88    /// Create a new compositor with the given viewport dimensions.
89    pub fn new(viewport_width: u32, viewport_height: u32) -> Self {
90        let pixel_count = (viewport_width as usize) * (viewport_height as usize);
91        Self {
92            surfaces: BTreeMap::new(),
93            atlas: ShelfAllocator::new(viewport_width.max(2048), viewport_height.max(2048)),
94            viewport_width,
95            viewport_height,
96            back_buffer: vec![0xFF000000u32; pixel_count],
97            next_id: 1,
98            clear_color: 0xFF000000,
99        }
100    }
101
102    /// Set the background clear colour.
103    pub fn set_clear_color(&mut self, color: u32) {
104        self.clear_color = color;
105    }
106
107    /// Viewport width.
108    pub fn width(&self) -> u32 {
109        self.viewport_width
110    }
111
112    /// Viewport height.
113    pub fn height(&self) -> u32 {
114        self.viewport_height
115    }
116
117    /// Number of registered surfaces.
118    pub fn surface_count(&self) -> usize {
119        self.surfaces.len()
120    }
121
122    /// Create a new surface with the given dimensions.
123    ///
124    /// Returns the surface ID, or `None` if the atlas is full.
125    pub fn create_surface(&mut self, width: u32, height: u32) -> Option<SurfaceId> {
126        let region = self.atlas.allocate(width, height)?;
127        let pixel_count = (width as usize) * (height as usize);
128        let id = self.next_id;
129        self.next_id += 1;
130
131        let surface = GlSurface {
132            id,
133            atlas_region: region,
134            z_order: 0,
135            visible: true,
136            opacity: 255,
137            blend_mode: BlendMode::Over,
138            x: 0,
139            y: 0,
140            pixels: vec![0x00000000u32; pixel_count],
141        };
142
143        self.surfaces.insert(id, surface);
144        Some(id)
145    }
146
147    /// Destroy a surface and free its atlas region.
148    pub fn destroy_surface(&mut self, id: SurfaceId) -> bool {
149        if let Some(surface) = self.surfaces.remove(&id) {
150            self.atlas.deallocate(&surface.atlas_region);
151            true
152        } else {
153            false
154        }
155    }
156
157    /// Update a surface's pixel data.
158    ///
159    /// `pixels` must be exactly `width * height` ARGB8888 values.
160    pub fn update_surface(&mut self, id: SurfaceId, pixels: &[u32]) -> bool {
161        if let Some(surface) = self.surfaces.get_mut(&id) {
162            let expected =
163                (surface.atlas_region.width as usize) * (surface.atlas_region.height as usize);
164            if pixels.len() == expected {
165                surface.pixels.clear();
166                surface.pixels.extend_from_slice(pixels);
167                true
168            } else {
169                false
170            }
171        } else {
172            false
173        }
174    }
175
176    /// Set a surface's position.
177    pub fn set_surface_position(&mut self, id: SurfaceId, x: i32, y: i32) {
178        if let Some(surface) = self.surfaces.get_mut(&id) {
179            surface.x = x;
180            surface.y = y;
181        }
182    }
183
184    /// Set a surface's z-order.
185    pub fn set_surface_z_order(&mut self, id: SurfaceId, z: i32) {
186        if let Some(surface) = self.surfaces.get_mut(&id) {
187            surface.z_order = z;
188        }
189    }
190
191    /// Set a surface's visibility.
192    pub fn set_surface_visible(&mut self, id: SurfaceId, visible: bool) {
193        if let Some(surface) = self.surfaces.get_mut(&id) {
194            surface.visible = visible;
195        }
196    }
197
198    /// Set a surface's opacity.
199    pub fn set_surface_opacity(&mut self, id: SurfaceId, opacity: u8) {
200        if let Some(surface) = self.surfaces.get_mut(&id) {
201            surface.opacity = opacity;
202        }
203    }
204
205    /// Composite all visible surfaces to the back buffer.
206    ///
207    /// Surfaces are drawn in z-order (lowest first). The back buffer is
208    /// cleared before compositing.
209    pub fn composite(&mut self) {
210        // Clear
211        for px in self.back_buffer.iter_mut() {
212            *px = self.clear_color;
213        }
214
215        // Collect and sort surfaces by z-order
216        let mut order: Vec<SurfaceId> = self
217            .surfaces
218            .values()
219            .filter(|s| s.visible)
220            .map(|s| s.id)
221            .collect();
222
223        // Sort by z_order using surface lookup
224        order.sort_by(|a, b| {
225            let za = self.surfaces.get(a).map_or(0, |s| s.z_order);
226            let zb = self.surfaces.get(b).map_or(0, |s| s.z_order);
227            za.cmp(&zb)
228        });
229
230        let vw = self.viewport_width as i32;
231        let vh = self.viewport_height as i32;
232
233        for sid in &order {
234            let surface = match self.surfaces.get(sid) {
235                Some(s) => s,
236                None => continue,
237            };
238
239            let sw = surface.atlas_region.width as i32;
240            let sh = surface.atlas_region.height as i32;
241            let sx = surface.x;
242            let sy = surface.y;
243            let opacity = surface.opacity;
244            let blend = surface.blend_mode;
245
246            for row in 0..sh {
247                let dst_y = sy + row;
248                if dst_y < 0 || dst_y >= vh {
249                    continue;
250                }
251                for col in 0..sw {
252                    let dst_x = sx + col;
253                    if dst_x < 0 || dst_x >= vw {
254                        continue;
255                    }
256
257                    let src_pixel = surface.pixels[(row * sw + col) as usize];
258                    let dst_idx = (dst_y * vw + dst_x) as usize;
259
260                    let blended = blend_pixel(src_pixel, self.back_buffer[dst_idx], opacity, blend);
261                    self.back_buffer[dst_idx] = blended;
262                }
263            }
264        }
265    }
266
267    /// Return a reference to the composited back buffer.
268    pub fn present(&self) -> &[u32] {
269        &self.back_buffer
270    }
271
272    /// Get a surface by ID.
273    pub fn get_surface(&self, id: SurfaceId) -> Option<&GlSurface> {
274        self.surfaces.get(&id)
275    }
276
277    /// Get a mutable reference to a surface by ID.
278    pub fn get_surface_mut(&mut self, id: SurfaceId) -> Option<&mut GlSurface> {
279        self.surfaces.get_mut(&id)
280    }
281}
282
283// ---------------------------------------------------------------------------
284// Blending
285// ---------------------------------------------------------------------------
286
287/// Blend a source pixel onto a destination pixel using the given mode and
288/// opacity.
289///
290/// All channels use integer arithmetic only.
291fn blend_pixel(src: u32, dst: u32, opacity: u8, mode: BlendMode) -> u32 {
292    let sa = (src >> 24) & 0xFF;
293    let sr = (src >> 16) & 0xFF;
294    let sg = (src >> 8) & 0xFF;
295    let sb = src & 0xFF;
296
297    let da = (dst >> 24) & 0xFF;
298    let dr = (dst >> 16) & 0xFF;
299    let dg = (dst >> 8) & 0xFF;
300    let db = dst & 0xFF;
301
302    // Effective alpha = src_alpha * opacity / 255
303    let alpha = (sa * opacity as u32) / 255;
304    let inv_alpha = 255 - alpha;
305
306    let (ra, rr, rg, rb) = match mode {
307        BlendMode::Over => {
308            let oa = alpha + (da * inv_alpha) / 255;
309            let or = (sr * alpha + dr * inv_alpha) / 255;
310            let og = (sg * alpha + dg * inv_alpha) / 255;
311            let ob = (sb * alpha + db * inv_alpha) / 255;
312            (oa.min(255), or.min(255), og.min(255), ob.min(255))
313        }
314        BlendMode::Add => {
315            let oa = (alpha + da).min(255);
316            let or = (sr * alpha / 255 + dr).min(255);
317            let og = (sg * alpha / 255 + dg).min(255);
318            let ob = (sb * alpha / 255 + db).min(255);
319            (oa, or, og, ob)
320        }
321        BlendMode::Multiply => {
322            let oa = (da * alpha) / 255;
323            let or = (dr * sr) / 255;
324            let og = (dg * sg) / 255;
325            let ob = (db * sb) / 255;
326            (oa.min(255), or.min(255), og.min(255), ob.min(255))
327        }
328    };
329
330    (ra << 24) | (rr << 16) | (rg << 8) | rb
331}
332
333// ---------------------------------------------------------------------------
334// Tests
335// ---------------------------------------------------------------------------
336
337#[cfg(test)]
338mod tests {
339    use super::*;
340
341    #[test]
342    fn test_create_surface() {
343        let mut comp = GlCompositor::new(640, 480);
344        let id = comp.create_surface(100, 50);
345        assert!(id.is_some());
346        assert_eq!(comp.surface_count(), 1);
347    }
348
349    #[test]
350    fn test_destroy_surface() {
351        let mut comp = GlCompositor::new(640, 480);
352        let id = comp.create_surface(100, 50).unwrap();
353        assert!(comp.destroy_surface(id));
354        assert_eq!(comp.surface_count(), 0);
355        assert!(!comp.destroy_surface(id)); // already gone
356    }
357
358    #[test]
359    fn test_update_surface() {
360        let mut comp = GlCompositor::new(640, 480);
361        let id = comp.create_surface(4, 4).unwrap();
362        let pixels = vec![0xFFFF0000u32; 16];
363        assert!(comp.update_surface(id, &pixels));
364        // Wrong size should fail
365        let bad = vec![0u32; 10];
366        assert!(!comp.update_surface(id, &bad));
367    }
368
369    #[test]
370    fn test_composite_empty() {
371        let mut comp = GlCompositor::new(8, 8);
372        comp.set_clear_color(0xFF112233);
373        comp.composite();
374        assert_eq!(comp.present()[0], 0xFF112233);
375    }
376
377    #[test]
378    fn test_composite_opaque_surface() {
379        let mut comp = GlCompositor::new(8, 8);
380        comp.set_clear_color(0xFF000000);
381        let id = comp.create_surface(4, 4).unwrap();
382        let pixels = vec![0xFFFF0000u32; 16]; // opaque red
383        comp.update_surface(id, &pixels);
384        comp.set_surface_position(id, 0, 0);
385        comp.composite();
386        // Top-left pixel should be red
387        assert_eq!(comp.present()[0], 0xFFFF0000);
388        // Pixel at (4,0) should be clear colour
389        assert_eq!(comp.present()[4], 0xFF000000);
390    }
391
392    #[test]
393    fn test_z_order() {
394        let mut comp = GlCompositor::new(8, 8);
395        let id1 = comp.create_surface(4, 4).unwrap();
396        let id2 = comp.create_surface(4, 4).unwrap();
397        comp.update_surface(id1, &vec![0xFFFF0000u32; 16]);
398        comp.update_surface(id2, &vec![0xFF00FF00u32; 16]);
399        comp.set_surface_position(id1, 0, 0);
400        comp.set_surface_position(id2, 0, 0);
401        comp.set_surface_z_order(id1, 0);
402        comp.set_surface_z_order(id2, 1);
403        comp.composite();
404        // Surface 2 (green) is on top
405        assert_eq!(comp.present()[0], 0xFF00FF00);
406    }
407
408    #[test]
409    fn test_visibility() {
410        let mut comp = GlCompositor::new(8, 8);
411        comp.set_clear_color(0xFF000000);
412        let id = comp.create_surface(4, 4).unwrap();
413        comp.update_surface(id, &vec![0xFFFF0000u32; 16]);
414        comp.set_surface_visible(id, false);
415        comp.composite();
416        assert_eq!(comp.present()[0], 0xFF000000);
417    }
418
419    #[test]
420    fn test_blend_over_semitransparent() {
421        // 50% alpha red over black
422        let result = blend_pixel(0x80FF0000, 0xFF000000, 255, BlendMode::Over);
423        let r = (result >> 16) & 0xFF;
424        // Should be roughly half red
425        assert!(r > 100 && r < 140);
426    }
427
428    #[test]
429    fn test_blend_add() {
430        let result = blend_pixel(0xFF800000, 0xFF400000, 255, BlendMode::Add);
431        let r = (result >> 16) & 0xFF;
432        assert_eq!(r, 192); // 0x40 + 0x80
433    }
434
435    #[test]
436    fn test_blend_multiply() {
437        let result = blend_pixel(0xFF800000, 0xFF800000, 255, BlendMode::Multiply);
438        let r = (result >> 16) & 0xFF;
439        // (128 * 128) / 255 ~ 64
440        assert!(r > 60 && r < 68);
441    }
442}