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