Merge branch 'graphics_refactorings' into 'master'

graphics: Various refactorings

See merge request redox-os/drivers!271
This commit is contained in:
Jeremy Soller
2025-06-30 15:18:49 -06:00
6 changed files with 241 additions and 221 deletions
+149 -135
View File
@@ -1,5 +1,6 @@
use std::collections::{BTreeMap, HashMap};
use std::io;
use std::sync::Arc;
use graphics_ipc::v1::{CursorDamage, Damage};
use inputd::{VtEvent, VtEventKind};
@@ -11,7 +12,10 @@ pub trait GraphicsAdapter {
type Framebuffer: Framebuffer;
type Cursor: CursorFramebuffer;
fn displays(&self) -> Vec<usize>;
/// The maximum amount of displays that could be attached.
///
/// This must be constant for the lifetime of the graphics adapter.
fn display_count(&self) -> usize;
fn display_size(&self, display_id: usize) -> (u32, u32);
fn create_dumb_framebuffer(&mut self, width: u32, height: u32) -> Self::Framebuffer;
@@ -22,7 +26,7 @@ pub trait GraphicsAdapter {
fn supports_hw_cursor(&self) -> bool;
fn create_cursor_framebuffer(&mut self) -> Self::Cursor;
fn map_cursor_framebuffer(&mut self, cursor: &Self::Cursor) -> *mut u8;
fn handle_cursor(&mut self, cursor: &mut CursorPlane<Self::Cursor>, dirty_fb: bool);
fn handle_cursor(&mut self, cursor: &CursorPlane<Self::Cursor>, dirty_fb: bool);
}
pub trait Framebuffer {
@@ -49,12 +53,16 @@ pub struct GraphicsScheme<T: GraphicsAdapter> {
handles: BTreeMap<usize, Handle>,
active_vt: usize,
vts_fb: HashMap<usize, HashMap<usize, T::Framebuffer>>,
cursor_planes: HashMap<usize, CursorPlane<T::Cursor>>,
vts: HashMap<usize, VtState<T>>,
}
struct VtState<T: GraphicsAdapter> {
display_fbs: Vec<Arc<T::Framebuffer>>,
cursor_plane: Option<CursorPlane<T::Cursor>>,
}
enum Handle {
Screen { vt: usize, screen: usize },
V1Screen { vt: usize, screen: usize },
}
impl<T: GraphicsAdapter> GraphicsScheme<T> {
@@ -69,8 +77,7 @@ impl<T: GraphicsAdapter> GraphicsScheme<T> {
next_id: 0,
handles: BTreeMap::new(),
active_vt: 0,
vts_fb: HashMap::new(),
cursor_planes: HashMap::new(),
vts: HashMap::new(),
}
}
@@ -91,29 +98,18 @@ impl<T: GraphicsAdapter> GraphicsScheme<T> {
VtEventKind::Activate => {
log::info!("activate {}", vt_event.vt);
for display_id in self.adapter.displays() {
let framebuffer = self
.vts_fb
.entry(vt_event.vt)
.or_default()
.entry(display_id)
.or_insert_with(|| {
let (width, height) = self.adapter.display_size(display_id);
self.adapter.create_dumb_framebuffer(width, height)
});
Self::update_whole_screen(&mut self.adapter, display_id, framebuffer);
let vt_state =
Self::get_or_create_vt(&mut self.adapter, &mut self.vts, vt_event.vt);
self.active_vt = vt_event.vt;
if self.adapter.supports_hw_cursor() {
let cursor_plane = Self::cursor_plane_for_vt(
&mut self.adapter,
&mut self.cursor_planes,
self.active_vt,
);
self.adapter.handle_cursor(cursor_plane, true);
}
for (display_id, fb) in vt_state.display_fbs.iter().enumerate() {
Self::update_whole_screen(&mut self.adapter, display_id, fb);
}
if let Some(cursor_plane) = &vt_state.cursor_plane {
self.adapter.handle_cursor(cursor_plane, true);
}
self.active_vt = vt_event.vt;
}
VtEventKind::Resize => {
@@ -168,17 +164,30 @@ impl<T: GraphicsAdapter> GraphicsScheme<T> {
);
}
fn cursor_plane_for_vt<'a>(
fn get_or_create_vt<'a>(
adapter: &mut T,
cursor_planes: &'a mut HashMap<usize, CursorPlane<T::Cursor>>,
vts: &'a mut HashMap<usize, VtState<T>>,
vt: usize,
) -> &'a mut CursorPlane<T::Cursor> {
cursor_planes.entry(vt).or_insert_with(|| CursorPlane {
x: 0,
y: 0,
hot_x: 0,
hot_y: 0,
framebuffer: adapter.create_cursor_framebuffer(),
) -> &'a mut VtState<T> {
vts.entry(vt).or_insert_with(|| {
let mut display_fbs = vec![];
for display_id in 0..adapter.display_count() {
let (width, height) = adapter.display_size(display_id);
display_fbs.push(Arc::new(adapter.create_dumb_framebuffer(width, height)));
}
let cursor_plane = adapter.supports_hw_cursor().then(|| CursorPlane {
x: 0,
y: 0,
hot_x: 0,
hot_y: 0,
framebuffer: adapter.create_cursor_framebuffer(),
});
VtState {
display_fbs,
cursor_plane,
}
})
}
}
@@ -195,48 +204,51 @@ impl<T: GraphicsAdapter> Scheme for GraphicsScheme<T> {
let vt = screen.next().unwrap_or("").parse::<usize>().unwrap();
let id = screen.next().unwrap_or("").parse::<usize>().unwrap_or(0);
if id >= self.adapter.displays().len() {
if id >= self.adapter.display_count() {
return Err(Error::new(EINVAL));
}
self.vts_fb
.entry(vt)
.or_default()
.entry(id)
.or_insert_with(|| {
let (width, height) = self.adapter.display_size(id);
self.adapter.create_dumb_framebuffer(width, height)
});
// Ensure the VT exists such that the rest of the methods can freely access it.
Self::get_or_create_vt(&mut self.adapter, &mut self.vts, vt);
self.next_id += 1;
self.handles
.insert(self.next_id, Handle::Screen { vt, screen: id });
.insert(self.next_id, Handle::V1Screen { vt, screen: id });
Ok(self.next_id)
}
fn fpath(&mut self, id: usize, buf: &mut [u8]) -> syscall::Result<usize> {
let Handle::Screen { vt, screen } = self.handles.get(&id).ok_or(Error::new(EBADF))?;
let framebuffer = &self.vts_fb[vt][screen];
let path = format!(
"{}:{vt}.{screen}/{}/{}",
self.scheme_name,
framebuffer.width(),
framebuffer.height()
);
let path = match self.handles.get(&id).ok_or(Error::new(EBADF))? {
Handle::V1Screen { vt, screen } => {
let framebuffer = &self.vts[vt].display_fbs[*screen];
format!(
"{}:{vt}.{screen}/{}/{}",
self.scheme_name,
framebuffer.width(),
framebuffer.height()
)
}
};
buf[..path.len()].copy_from_slice(path.as_bytes());
Ok(path.len())
}
fn fsync(&mut self, id: usize) -> syscall::Result<usize> {
let Handle::Screen { vt, screen } = self.handles.get(&id).ok_or(Error::new(EBADF))?;
if *vt != self.active_vt {
// This is a protection against background VT's spamming us with flush requests. We will
// flush the framebuffer on the next VT switch anyway
return Ok(0);
match self.handles.get(&id).ok_or(Error::new(EBADF))? {
Handle::V1Screen { vt, screen } => {
if *vt != self.active_vt {
// This is a protection against background VT's spamming us with flush requests. We will
// flush the framebuffer on the next VT switch anyway
return Ok(0);
}
Self::update_whole_screen(
&mut self.adapter,
*screen,
&self.vts[vt].display_fbs[*screen],
);
Ok(0)
}
}
let framebuffer = &self.vts_fb[vt][screen];
Self::update_whole_screen(&mut self.adapter, *screen, framebuffer);
Ok(0)
}
fn read(
@@ -246,88 +258,90 @@ impl<T: GraphicsAdapter> Scheme for GraphicsScheme<T> {
_offset: u64,
_fcntl_flags: u32,
) -> Result<usize> {
let _handle = self.handles.get(&id).ok_or(Error::new(EBADF))?;
match self.handles.get(&id).ok_or(Error::new(EBADF))? {
Handle::V1Screen { .. } => {
//Currently read is only used for Orbital to check GPU cursor support
//and only expects a buf to pass a 0 or 1 flag
if self.adapter.supports_hw_cursor() {
buf[0] = 1;
} else {
buf[0] = 0;
}
//Currently read is only used for Orbital to check GPU cursor support
//and only expects a buf to pass a 0 or 1 flag
if self.adapter.supports_hw_cursor() {
buf[0] = 1;
} else {
buf[0] = 0;
Ok(1)
}
}
Ok(1)
}
fn write(&mut self, id: usize, buf: &[u8], _offset: u64, _fcntl_flags: u32) -> Result<usize> {
let Handle::Screen { vt, screen } = self.handles.get(&id).ok_or(Error::new(EBADF))?;
if *vt != self.active_vt {
// This is a protection against background VT's spamming us with flush requests. We will
// flush the framebuffer on the next VT switch anyway
return Ok(buf.len());
}
if size_of_val(buf) == std::mem::size_of::<CursorDamage>()
&& self.adapter.supports_hw_cursor()
{
let cursor_damage = unsafe { *buf.as_ptr().cast::<CursorDamage>() };
let cursor_plane = Self::cursor_plane_for_vt(
&mut self.adapter,
&mut self.cursor_planes,
self.active_vt,
);
cursor_plane.x = cursor_damage.x;
cursor_plane.y = cursor_damage.y;
if cursor_damage.header == 0 {
self.adapter.handle_cursor(cursor_plane, false);
} else {
cursor_plane.hot_x = cursor_damage.hot_x;
cursor_plane.hot_y = cursor_damage.hot_y;
let w: i32 = cursor_damage.width;
let h: i32 = cursor_damage.height;
let cursor_image = cursor_damage.cursor_img_bytes;
let cursor_ptr = self
.adapter
.map_cursor_framebuffer(&cursor_plane.framebuffer);
//Clear previous image from backing storage
unsafe {
core::ptr::write_bytes(cursor_ptr as *mut u8, 0, 64 * 64 * 4);
match self.handles.get(&id).ok_or(Error::new(EBADF))? {
Handle::V1Screen { vt, screen } => {
if *vt != self.active_vt {
// This is a protection against background VT's spamming us with flush requests. We will
// flush the framebuffer on the next VT switch anyway
return Ok(buf.len());
}
//Write image to backing storage
for row in 0..h {
let start: usize = (w * row) as usize;
let end: usize = (w * row + w) as usize;
let vt_state = self.vts.get_mut(vt).unwrap();
unsafe {
core::ptr::copy_nonoverlapping(
cursor_image[start..end].as_ptr(),
cursor_ptr.cast::<u32>().offset(64 * row as isize),
w as usize,
);
if size_of_val(buf) == std::mem::size_of::<CursorDamage>() {
let Some(cursor_plane) = &mut vt_state.cursor_plane else {
// Hardware cursor not supported
return Err(Error::new(EINVAL));
};
let cursor_damage = unsafe { *buf.as_ptr().cast::<CursorDamage>() };
cursor_plane.x = cursor_damage.x;
cursor_plane.y = cursor_damage.y;
if cursor_damage.header == 0 {
self.adapter.handle_cursor(cursor_plane, false);
} else {
cursor_plane.hot_x = cursor_damage.hot_x;
cursor_plane.hot_y = cursor_damage.hot_y;
let w: i32 = cursor_damage.width;
let h: i32 = cursor_damage.height;
let cursor_image = cursor_damage.cursor_img_bytes;
let cursor_ptr = self
.adapter
.map_cursor_framebuffer(&cursor_plane.framebuffer);
//Clear previous image from backing storage
unsafe {
core::ptr::write_bytes(cursor_ptr as *mut u8, 0, 64 * 64 * 4);
}
//Write image to backing storage
for row in 0..h {
let start: usize = (w * row) as usize;
let end: usize = (w * row + w) as usize;
unsafe {
core::ptr::copy_nonoverlapping(
cursor_image[start..end].as_ptr(),
cursor_ptr.cast::<u32>().offset(64 * row as isize),
w as usize,
);
}
}
self.adapter.handle_cursor(cursor_plane, true);
}
return Ok(buf.len());
}
self.adapter.handle_cursor(cursor_plane, true);
assert_eq!(buf.len(), std::mem::size_of::<Damage>());
let damage = unsafe { *buf.as_ptr().cast::<Damage>() };
self.adapter
.update_plane(*screen, &vt_state.display_fbs[*screen], damage);
Ok(buf.len())
}
return Ok(buf.len());
}
let framebuffer = &self.vts_fb[vt][screen];
assert_eq!(buf.len(), std::mem::size_of::<Damage>());
let damage = unsafe { *buf.as_ptr().cast::<Damage>() };
self.adapter.update_plane(*screen, framebuffer, damage);
Ok(buf.len())
}
fn mmap_prep(
@@ -338,9 +352,9 @@ impl<T: GraphicsAdapter> Scheme for GraphicsScheme<T> {
_flags: MapFlags,
) -> syscall::Result<usize> {
// log::trace!("KSMSG MMAP {} {:?} {} {}", id, _flags, _offset, _size);
let handle = self.handles.get(&id).ok_or(Error::new(EINVAL))?;
let Handle::Screen { vt, screen } = handle;
let framebuffer = &self.vts_fb[vt][screen];
let framebuffer = match self.handles.get(&id).ok_or(Error::new(EINVAL))? {
Handle::V1Screen { vt, screen } => &self.vts[vt].display_fbs[*screen],
};
let ptr = T::map_dumb_framebuffer(&mut self.adapter, framebuffer);
Ok(ptr as usize)
}
+75
View File
@@ -0,0 +1,75 @@
use std::cmp;
// Keep synced with orbital's SyncRect
// Technically orbital uses i32 rather than u32, but values larger than i32::MAX
// would be a bug anyway.
#[derive(Debug, Copy, Clone)]
#[repr(C, packed)]
pub struct Damage {
pub x: u32,
pub y: u32,
pub width: u32,
pub height: u32,
}
impl Damage {
#[must_use]
pub fn clip(mut self, width: u32, height: u32) -> Self {
// Clip damage
let x2 = self.x + self.width;
self.x = cmp::min(self.x, width);
if x2 > width {
self.width = width - self.x;
}
let y2 = self.y + self.height;
self.y = cmp::min(self.y, height);
if y2 > height {
self.height = height - self.y;
}
self
}
}
pub struct DisplayMap {
offscreen: *mut [u32],
width: usize,
height: usize,
}
impl DisplayMap {
pub(crate) unsafe fn new(offscreen: *mut [u32], width: usize, height: usize) -> Self {
DisplayMap {
offscreen,
width,
height,
}
}
pub fn ptr(&self) -> *const [u32] {
self.offscreen
}
pub fn ptr_mut(&mut self) -> *mut [u32] {
self.offscreen
}
pub fn width(&self) -> usize {
self.width
}
pub fn height(&self) -> usize {
self.height
}
}
unsafe impl Send for DisplayMap {}
unsafe impl Sync for DisplayMap {}
impl Drop for DisplayMap {
fn drop(&mut self) {
unsafe {
let _ = libredox::call::munmap(self.offscreen as *mut (), self.offscreen.len());
}
}
}
+1
View File
@@ -1 +1,2 @@
mod common;
pub mod v1;
+7 -74
View File
@@ -1,13 +1,16 @@
use std::fs::File;
use std::os::unix::io::AsRawFd;
use std::{cmp, io, mem, ptr, slice};
use std::{io, mem, ptr, slice};
use libredox::flag;
pub use crate::common::Damage;
pub use crate::common::DisplayMap;
/// A graphics handle using the v1 graphics API.
///
/// The v1 graphics API only allows a single framebuffer for each VT and supports neither page
/// flipping nor cursor planes.
/// The v1 graphics API only allows a single framebuffer for each VT, requires each display to be
/// handled separately and doesn't support page flipping.
pub struct V1GraphicsHandle {
file: File,
}
@@ -52,11 +55,7 @@ impl V1GraphicsHandle {
};
let offscreen = ptr::slice_from_raw_parts_mut(display_ptr as *mut u32, width * height);
Ok(DisplayMap {
offscreen,
width,
height,
})
Ok(unsafe { DisplayMap::new(offscreen, width, height) })
}
pub fn sync_full_screen(&self) -> io::Result<()> {
@@ -75,41 +74,6 @@ impl V1GraphicsHandle {
}
}
pub struct DisplayMap {
offscreen: *mut [u32],
width: usize,
height: usize,
}
impl DisplayMap {
pub fn ptr(&self) -> *const [u32] {
self.offscreen
}
pub fn ptr_mut(&mut self) -> *mut [u32] {
self.offscreen
}
pub fn width(&self) -> usize {
self.width
}
pub fn height(&self) -> usize {
self.height
}
}
unsafe impl Send for DisplayMap {}
unsafe impl Sync for DisplayMap {}
impl Drop for DisplayMap {
fn drop(&mut self) {
unsafe {
let _ = libredox::call::munmap(self.offscreen as *mut (), self.offscreen.len());
}
}
}
#[derive(Debug, Copy, Clone)]
#[repr(C, packed)]
pub struct CursorDamage {
@@ -122,34 +86,3 @@ pub struct CursorDamage {
pub height: i32,
pub cursor_img_bytes: [u32; 4096],
}
// Keep synced with orbital's SyncRect
// Technically orbital uses i32 rather than u32, but values larger than i32::MAX
// would be a bug anyway.
#[derive(Debug, Copy, Clone)]
#[repr(packed)]
pub struct Damage {
pub x: u32,
pub y: u32,
pub width: u32,
pub height: u32,
}
impl Damage {
#[must_use]
pub fn clip(mut self, width: u32, height: u32) -> Self {
// Clip damage
let x2 = self.x + self.width;
self.x = cmp::min(self.x, width);
if x2 > width {
self.width = width - self.x;
}
let y2 = self.y + self.height;
self.y = cmp::min(self.y, height);
if y2 > height {
self.height = height - self.y;
}
self
}
}
+3 -3
View File
@@ -20,8 +20,8 @@ impl GraphicsAdapter for FbAdapter {
type Framebuffer = GraphicScreen;
type Cursor = VesadCursor;
fn displays(&self) -> Vec<usize> {
(0..self.framebuffers.len()).collect()
fn display_count(&self) -> usize {
self.framebuffers.len()
}
fn display_size(&self, display_id: usize) -> (u32, u32) {
@@ -55,7 +55,7 @@ impl GraphicsAdapter for FbAdapter {
unimplemented!("Vesad does not support this function");
}
fn handle_cursor(&mut self, _cursor: &mut CursorPlane<VesadCursor>, _dirty_fb: bool) {
fn handle_cursor(&mut self, _cursor: &CursorPlane<VesadCursor>, _dirty_fb: bool) {
unimplemented!("Vesad does not support this function");
}
}
+6 -9
View File
@@ -149,8 +149,8 @@ impl GraphicsAdapter for VirtGpuAdapter<'_> {
type Framebuffer = VirtGpuFramebuffer;
type Cursor = VirtGpuCursor;
fn displays(&self) -> Vec<usize> {
self.displays.iter().enumerate().map(|(i, _)| i).collect()
fn display_count(&self) -> usize {
self.displays.len()
}
fn display_size(&self, display_id: usize) -> (u32, u32) {
@@ -331,7 +331,7 @@ impl GraphicsAdapter for VirtGpuAdapter<'_> {
cursor.sgl.as_ptr()
}
fn handle_cursor(&mut self, cursor: &mut CursorPlane<VirtGpuCursor>, dirty_fb: bool) {
fn handle_cursor(&mut self, cursor: &CursorPlane<VirtGpuCursor>, dirty_fb: bool) {
if dirty_fb {
self.update_cursor(
&cursor.framebuffer,
@@ -390,11 +390,8 @@ impl<'a> GpuScheme {
}
}
let inputd_handle = DisplayHandle::new("virtio-gpu").unwrap();
Ok((
GraphicsScheme::new(adapter, "display.virtio-gpu".to_owned()),
inputd_handle,
))
let scheme = GraphicsScheme::new(adapter, "display.virtio-gpu".to_owned());
let handle = DisplayHandle::new("virtio-gpu").unwrap();
Ok((scheme, handle))
}
}