Skip to main content

strat9_kernel/hardware/video/
graphics_adapter.rs

1//! Graphics adapter abstraction for multi-display support.
2//!
3//! Modeled after Redox OS `driver-graphics` crate but adapted for
4//! Strat9-OS kernel scheme architecture.
5//!
6//! # Architecture
7//!
8//! ```text
9//! ┌─────────────────────────────────────────────────┐
10//! │                GraphicsScheme                   │
11//! │  /dev/display/0.0  /dev/display/0.1  ...        │
12//! ├─────────────────────────────────────────────────┤
13//! │              GraphicsAdapter impl               │
14//! │  display_count() => N displays                   │
15//! │  display_size(id) => (w, h)                      │
16//! │  create_framebuffer(w, h) => DisplayScreen       │
17//! │  update_plane(id, screen, damage)               │
18//! ├─────────────────────────────────────────────────┤
19//! │  DisplayScreen (offscreen buffer)               │
20//! │  sync() => copy to onscreen                      │
21//! └─────────────────────────────────────────────────┘
22//! ```
23
24use alloc::{boxed::Box, sync::Arc, vec::Vec};
25use core::fmt;
26use spin::RwLock;
27
28/// Dirty region for partial screen updates.
29#[derive(Debug, Clone, Copy, Default)]
30pub struct Damage {
31    pub x: u32,
32    pub y: u32,
33    pub width: u32,
34    pub height: u32,
35}
36
37impl Damage {
38    /// Create a full-screen damage region.
39    pub fn full(width: u32, height: u32) -> Self {
40        Self {
41            x: 0,
42            y: 0,
43            width,
44            height,
45        }
46    }
47
48    /// Returns true if this damage region has non-zero area.
49    pub fn is_valid(&self) -> bool {
50        self.width > 0 && self.height > 0
51    }
52
53    /// Clip this damage to the given bounds.
54    pub fn clip(&self, max_w: u32, max_h: u32) -> Self {
55        let x = self.x.min(max_w);
56        let y = self.y.min(max_h);
57        let w = self.width.min(max_w.saturating_sub(x));
58        let h = self.height.min(max_h.saturating_sub(y));
59        Self {
60            x,
61            y,
62            width: w,
63            height: h,
64        }
65    }
66}
67
68/// Trait for an individual display screen (offscreen buffer).
69pub trait DisplayScreen: Send + Sync {
70    /// Width in pixels.
71    fn width(&self) -> u32;
72    /// Height in pixels.
73    fn height(&self) -> u32;
74    /// Bytes per row (stride).
75    fn stride(&self) -> u32;
76    /// Bits per pixel.
77    fn bpp(&self) -> u8;
78    /// Pointer to the raw pixel data.
79    fn pixels(&self) -> *const u8;
80    /// Mutable pointer to the raw pixel data.
81    fn pixels_mut(&mut self) -> *mut u8;
82}
83
84/// Trait for a graphics adapter managing one or more displays.
85pub trait GraphicsAdapter: Send + Sync {
86    /// The screen type created by this adapter.
87    type Screen: DisplayScreen;
88
89    /// Number of connected displays.
90    fn display_count(&self) -> usize;
91
92    /// Resolution of a specific display.
93    fn display_size(&self, display_id: usize) -> (u32, u32);
94
95    /// Create an offscreen framebuffer of the given size.
96    fn create_framebuffer(&self, width: u32, height: u32) -> Self::Screen;
97
98    /// Present an offscreen buffer to a physical display.
99    ///
100    /// Only the `damage` region needs to be copied.
101    fn update_plane(&self, display_id: usize, screen: &Self::Screen, damage: Damage);
102
103    /// Returns true if the adapter supports hardware cursor planes.
104    fn supports_hw_cursor(&self) -> bool {
105        false
106    }
107}
108
109// ============================================================================
110// Concrete implementation: UEFI bootloader/VirtIO framebuffer adapter
111// ============================================================================
112
113/// A heap-allocated offscreen pixel buffer.
114#[derive(Clone)]
115pub struct HeapScreen {
116    width: u32,
117    height: u32,
118    stride: u32,
119    bpp: u8,
120    data: Box<[u8]>,
121}
122
123impl fmt::Debug for HeapScreen {
124    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125        f.debug_struct("HeapScreen")
126            .field("width", &self.width)
127            .field("height", &self.height)
128            .field("stride", &self.stride)
129            .field("bpp", &self.bpp)
130            .finish()
131    }
132}
133
134impl HeapScreen {
135    /// Create a new zero-initialized screen.
136    pub fn new(width: u32, height: u32, bpp: u8) -> Self {
137        let stride = width * (bpp as u32 / 8);
138        let size = (stride * height) as usize;
139        let data = alloc::vec![0u8; size].into_boxed_slice();
140        Self {
141            width,
142            height,
143            stride,
144            bpp,
145            data,
146        }
147    }
148}
149
150impl DisplayScreen for HeapScreen {
151    fn width(&self) -> u32 {
152        self.width
153    }
154    fn height(&self) -> u32 {
155        self.height
156    }
157    fn stride(&self) -> u32 {
158        self.stride
159    }
160    fn bpp(&self) -> u8 {
161        self.bpp
162    }
163    fn pixels(&self) -> *const u8 {
164        self.data.as_ptr()
165    }
166    fn pixels_mut(&mut self) -> *mut u8 {
167        self.data.as_mut_ptr()
168    }
169}
170
171/// Adapter backed by a single physical framebuffer (UEFI bootloader or VirtIO).
172pub struct SimpleDisplayAdapter {
173    /// Physical framebuffer info.
174    fb_virt: usize,
175    fb_width: u32,
176    fb_height: u32,
177    fb_stride: u32,
178    fb_bpp: u8,
179    /// Number of displays (1 for simple adapter).
180    display_count: usize,
181}
182
183impl SimpleDisplayAdapter {
184    /// Create from an existing framebuffer.
185    pub fn new(virt: usize, width: u32, height: u32, stride: u32, bpp: u8) -> Self {
186        Self {
187            fb_virt: virt,
188            fb_width: width,
189            fb_height: height,
190            fb_stride: stride,
191            fb_bpp: bpp,
192            display_count: 1,
193        }
194    }
195
196    /// Create from the global Framebuffer if available.
197    pub fn from_framebuffer() -> Option<Self> {
198        let info = super::framebuffer::Framebuffer::info()?;
199        Some(Self::new(
200            info.base_virt,
201            info.width,
202            info.height,
203            info.stride,
204            info.format.bits_per_pixel,
205        ))
206    }
207}
208
209impl GraphicsAdapter for SimpleDisplayAdapter {
210    type Screen = HeapScreen;
211
212    fn display_count(&self) -> usize {
213        self.display_count
214    }
215
216    fn display_size(&self, display_id: usize) -> (u32, u32) {
217        if display_id < self.display_count {
218            (self.fb_width, self.fb_height)
219        } else {
220            (0, 0)
221        }
222    }
223
224    fn create_framebuffer(&self, width: u32, height: u32) -> HeapScreen {
225        HeapScreen::new(width, height, self.fb_bpp)
226    }
227
228    fn update_plane(&self, display_id: usize, screen: &HeapScreen, damage: Damage) {
229        if display_id >= self.display_count {
230            return;
231        }
232        if self.fb_virt == 0 {
233            return;
234        }
235
236        let damage = damage.clip(screen.width(), screen.height());
237        if !damage.is_valid() {
238            return;
239        }
240
241        let bpp = self.fb_bpp as usize;
242        let dst_stride = self.fb_stride as usize;
243        let src_stride = screen.stride() as usize;
244        let dst = self.fb_virt as *mut u8;
245        let src = screen.pixels();
246
247        if bpp == 32 {
248            let bytes_per_row = damage.width as usize * 4;
249            for row in 0..damage.height as usize {
250                let src_off = (damage.y as usize + row) * src_stride + damage.x as usize * 4;
251                let dst_off = (damage.y as usize + row) * dst_stride + damage.x as usize * 4;
252                unsafe {
253                    core::ptr::copy_nonoverlapping(
254                        src.add(src_off),
255                        dst.add(dst_off),
256                        bytes_per_row,
257                    );
258                }
259            }
260        } else if bpp == 24 {
261            let bytes_per_row = damage.width as usize * 3;
262            for row in 0..damage.height as usize {
263                let src_off = (damage.y as usize + row) * src_stride + damage.x as usize * 3;
264                let dst_off = (damage.y as usize + row) * dst_stride + damage.x as usize * 3;
265                unsafe {
266                    core::ptr::copy_nonoverlapping(
267                        src.add(src_off),
268                        dst.add(dst_off),
269                        bytes_per_row,
270                    );
271                }
272            }
273        }
274    }
275}
276
277// ============================================================================
278// Global adapter registry
279// ============================================================================
280
281/// Global list of registered graphics adapters.
282static ADAPTERS: RwLock<Vec<Arc<dyn GraphicsAdapter<Screen = HeapScreen>>>> =
283    RwLock::new(Vec::new());
284
285/// Total display count across all adapters.
286pub fn total_display_count() -> usize {
287    ADAPTERS.read().iter().map(|a| a.display_count()).sum()
288}
289
290/// Register a graphics adapter.
291pub fn register_adapter(adapter: Arc<dyn GraphicsAdapter<Screen = HeapScreen>>) {
292    ADAPTERS.write().push(adapter);
293}
294
295/// Get the adapter and local display ID for a global display index.
296pub fn get_adapter_for_display(
297    global_id: usize,
298) -> Option<(Arc<dyn GraphicsAdapter<Screen = HeapScreen>>, usize)> {
299    let adapters = ADAPTERS.read();
300    let mut remaining = global_id;
301    for adapter in adapters.iter() {
302        let count = adapter.display_count();
303        if remaining < count {
304            return Some((adapter.clone(), remaining));
305        }
306        remaining -= count;
307    }
308    None
309}