Merge branch 'misc_graphics_changes' into 'main'

Misc graphics subsystem changes

See merge request redox-os/base!74
This commit is contained in:
Jeremy Soller
2025-12-18 13:28:12 -07:00
11 changed files with 185 additions and 348 deletions
Generated
+1 -1
View File
@@ -469,6 +469,7 @@ dependencies = [
name = "console-draw"
version = "0.1.0"
dependencies = [
"drm",
"graphics-ipc",
"orbclient",
"ransid",
@@ -956,7 +957,6 @@ version = "0.1.0"
dependencies = [
"common",
"drm",
"drm-fourcc",
"libredox",
"log",
"redox-ioctl",
+1
View File
@@ -4,6 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
drm = "0.14"
orbclient = "0.3.27"
ransid = "0.4"
+90 -18
View File
@@ -2,15 +2,51 @@ extern crate ransid;
use std::collections::VecDeque;
use std::convert::{TryFrom, TryInto};
use std::{cmp, ptr};
use std::{cmp, io, mem, ptr};
use drm::buffer::{Buffer, DrmFourcc};
use drm::control::dumbbuffer::{DumbBuffer, DumbMapping};
use drm::control::Device;
use graphics_ipc::v1::Damage;
use graphics_ipc::v2::V2GraphicsHandle;
use orbclient::FONT;
pub struct DisplayMap {
pub offscreen: *mut [u32],
pub width: usize,
pub height: usize,
pub struct V2DisplayMap {
pub display_handle: V2GraphicsHandle,
pub fb: DumbBuffer,
mapping: DumbMapping<'static>,
}
impl V2DisplayMap {
pub fn new(display_handle: V2GraphicsHandle, width: u32, height: u32) -> io::Result<Self> {
let mut fb = display_handle.create_dumb_buffer((width, height), DrmFourcc::Argb8888, 32)?;
let map = display_handle.map_dumb_buffer(&mut fb)?;
let map = unsafe { mem::transmute::<DumbMapping<'_>, DumbMapping<'static>>(map) };
Ok(Self {
display_handle,
fb,
mapping: map,
})
}
unsafe fn console_map(&mut self) -> DisplayMap {
DisplayMap {
offscreen: ptr::slice_from_raw_parts_mut(
self.mapping.as_mut_ptr() as *mut u32,
self.mapping.len() / 4,
),
width: self.fb.size().0 as usize,
height: self.fb.size().1 as usize,
}
}
}
struct DisplayMap {
offscreen: *mut [u32],
width: usize,
height: usize,
}
pub struct TextScreen {
@@ -116,7 +152,14 @@ impl TextScreen {
}
impl TextScreen {
pub fn write(&mut self, map: &mut DisplayMap, buf: &[u8], input: &mut VecDeque<u8>) -> Damage {
pub fn write(
&mut self,
map: &mut V2DisplayMap,
buf: &[u8],
input: &mut VecDeque<u8>,
) -> Damage {
let map = unsafe { &mut map.console_map() };
let mut min_changed = map.height;
let mut max_changed = 0;
let mut line_changed = |line| {
@@ -227,7 +270,7 @@ impl TextScreen {
damage
}
pub fn resize(&mut self, old_map: &mut DisplayMap, new_map: &mut DisplayMap) {
pub fn resize(&mut self, map: &mut V2DisplayMap, width: u32, height: u32) -> io::Result<()> {
// FIXME fold row when target is narrower and maybe unfold when it is wider
fn copy_row(
old_map: &mut DisplayMap,
@@ -244,20 +287,49 @@ impl TextScreen {
}
}
if new_map.height >= old_map.height {
for row in 0..old_map.height {
copy_row(old_map, new_map, row, row);
}
} else {
let deleted_rows = (old_map.height - new_map.height).div_ceil(16);
for row in 0..new_map.height {
if row + (deleted_rows + 1) * 16 >= old_map.height {
break;
let mut new_fb =
map.display_handle
.create_dumb_buffer((width, height), DrmFourcc::Argb8888, 32)?;
let new_mapping = map.display_handle.map_dumb_buffer(&mut new_fb)?;
let mut new_mapping =
unsafe { mem::transmute::<DumbMapping<'_>, DumbMapping<'static>>(new_mapping) };
new_mapping.fill(0);
{
let old_map = unsafe { &mut map.console_map() };
let new_map = &mut DisplayMap {
offscreen: ptr::slice_from_raw_parts_mut(
new_mapping.as_mut_ptr() as *mut u32,
new_mapping.len() / 4,
),
width: new_fb.size().0 as usize,
height: new_fb.size().1 as usize,
};
if new_map.height >= old_map.height {
for row in 0..old_map.height {
copy_row(old_map, new_map, row, row);
}
copy_row(old_map, new_map, row + deleted_rows * 16, row);
} else {
let deleted_rows = (old_map.height - new_map.height).div_ceil(16);
for row in 0..new_map.height {
if row + (deleted_rows + 1) * 16 >= old_map.height {
break;
}
copy_row(old_map, new_map, row + deleted_rows * 16, row);
}
self.console.state.y = self.console.state.y.saturating_sub(deleted_rows);
}
self.console.state.y = self.console.state.y.saturating_sub(deleted_rows);
}
let old_fb = mem::replace(&mut map.fb, new_fb);
map.mapping = new_mapping;
let _ = map.display_handle.destroy_dumb_buffer(old_fb);
Ok(())
}
}
+2 -1
View File
@@ -8,7 +8,8 @@ use std::mem;
use std::mem::transmute;
use std::sync::Arc;
use graphics_ipc::v1::{CursorDamage, Damage};
use graphics_ipc::v1::CursorDamage;
use graphics_ipc::v2::Damage;
use inputd::{VtEvent, VtEventKind};
use libredox::Fd;
use redox_scheme::scheme::SchemeSync;
+32 -97
View File
@@ -1,10 +1,10 @@
use std::cmp;
use std::collections::VecDeque;
use std::{cmp, mem, ptr};
use console_draw::TextScreen;
use console_draw::{TextScreen, V2DisplayMap};
use drm::buffer::Buffer;
use drm::control::dumbbuffer::{DumbBuffer, DumbMapping};
use graphics_ipc::v2::V2GraphicsHandle;
use drm::control::Device;
use graphics_ipc::v2::{Damage, V2GraphicsHandle};
use inputd::ConsumerHandle;
use orbclient::{Event, EventOption};
use redox_scheme::scheme::SchemeSync;
@@ -12,28 +12,9 @@ use redox_scheme::{CallerCtx, OpenResult};
use syscall::schemev2::NewFdFlags;
use syscall::{Error, Result, EINVAL, ENOENT};
pub struct DisplayMap {
display_handle: V2GraphicsHandle,
fb: DumbBuffer,
mapping: DumbMapping<'static>,
}
impl DisplayMap {
unsafe fn console_map(&mut self) -> console_draw::DisplayMap {
console_draw::DisplayMap {
offscreen: ptr::slice_from_raw_parts_mut(
self.mapping.as_mut_ptr() as *mut u32,
self.mapping.len() / 4,
),
width: self.fb.size().0 as usize,
height: self.fb.size().1 as usize,
}
}
}
pub struct FbbootlogScheme {
pub input_handle: ConsumerHandle,
display_map: Option<DisplayMap>,
display_map: Option<V2DisplayMap>,
text_screen: console_draw::TextScreen,
text_buffer: console_draw::TextBuffer,
is_scrollback: bool,
@@ -68,28 +49,19 @@ impl FbbootlogScheme {
};
let (width, height) = new_display_handle
.display_size(new_display_handle.first_display().unwrap())
.unwrap();
let mut fb = new_display_handle
.create_dumb_framebuffer(width, height)
.unwrap();
.get_connector(new_display_handle.first_display().unwrap(), true)
.unwrap()
.modes()[0]
.size();
let display_map = match new_display_handle.map_dumb_framebuffer(&mut fb) {
Ok(display_map) => unsafe {
mem::transmute::<DumbMapping<'_>, DumbMapping<'static>>(display_map)
},
match V2DisplayMap::new(new_display_handle, width.into(), height.into()) {
Ok(display_map) => self.display_map = Some(display_map),
Err(err) => {
eprintln!("fbbootlogd: failed to open display: {}", err);
return;
}
};
self.display_map = Some(DisplayMap {
display_handle: new_display_handle,
mapping: display_map,
fb,
});
eprintln!("fbbootlogd: mapped display");
}
@@ -145,46 +117,44 @@ impl FbbootlogScheme {
return;
};
let buffer_len = self.text_buffer.lines.len();
let dmap = unsafe { &mut map.console_map() };
// for both extra space on wrapping text and a scrollback indicator
let spare_lines = 3;
self.is_scrollback = true;
self.scrollback_offset = cmp::min(
self.scrollback_offset,
buffer_len - dmap.height / 16 + spare_lines,
buffer_len - map.fb.size().1 as usize / 16 + spare_lines,
);
let mut i = self.scrollback_offset;
self.text_screen
.write(dmap, b"\x1B[1;1H\x1B[2J", &mut VecDeque::new());
.write(map, b"\x1B[1;1H\x1B[2J", &mut VecDeque::new());
let mut total_damage = Damage::NONE;
while i < buffer_len {
let mut damage =
self.text_screen
.write(dmap, &self.text_buffer.lines[i][..], &mut VecDeque::new());
.write(map, &self.text_buffer.lines[i][..], &mut VecDeque::new());
i += 1;
let yd = (damage.y + damage.height) as usize;
if i == buffer_len || yd + spare_lines * 16 > dmap.height {
if i == buffer_len || yd + spare_lines * 16 > map.fb.size().1 as usize {
// render until end of screen
damage.height = (dmap.height as u32) - damage.y;
map.display_handle
.update_plane(0, u32::from(map.fb.handle()), damage)
.unwrap();
damage.height = map.fb.size().1 - damage.y;
total_damage = total_damage.merge(damage);
self.is_scrollback = i < buffer_len;
break;
} else {
map.display_handle
.update_plane(0, u32::from(map.fb.handle()), damage)
.unwrap();
total_damage = total_damage.merge(damage);
}
}
map.display_handle
.update_plane(0, u32::from(map.fb.handle()), total_damage)
.unwrap();
}
fn handle_resize(map: &mut DisplayMap, text_screen: &mut TextScreen) {
let (width, height) = match map
.display_handle
.first_display()
.and_then(|handle| map.display_handle.display_size(handle))
{
Ok((width, height)) => (width, height),
fn handle_resize(map: &mut V2DisplayMap, text_screen: &mut TextScreen) {
let (width, height) = match map.display_handle.first_display().and_then(|handle| {
Ok(map.display_handle.get_connector(handle, true)?.modes()[0].size())
}) {
Ok((width, height)) => (width.into(), height.into()),
Err(err) => {
eprintln!("fbbootlogd: failed to get display size: {}", err);
map.fb.size()
@@ -192,41 +162,10 @@ impl FbbootlogScheme {
};
if (width, height) != map.fb.size() {
match map.display_handle.create_dumb_framebuffer(width, height) {
Ok(mut fb) => {
let mut new_map = match map.display_handle.map_dumb_framebuffer(&mut fb) {
Ok(new_map) => unsafe {
mem::transmute::<DumbMapping<'_>, DumbMapping<'static>>(new_map)
},
Err(err) => {
eprintln!("fbbootlogd: failed to open display: {}", err);
return;
}
};
new_map.fill(0);
text_screen.resize(
unsafe { &mut map.console_map() },
&mut console_draw::DisplayMap {
offscreen: ptr::slice_from_raw_parts_mut(
new_map.as_mut_ptr() as *mut u32,
new_map.len() / 4,
),
width: fb.size().0 as usize,
height: fb.size().1 as usize,
},
);
let old_fb = mem::replace(&mut map.fb, fb);
map.mapping = new_map;
let _ = map.display_handle.destroy_dumb_framebuffer(old_fb);
eprintln!("fbbootlogd: mapped display");
}
match text_screen.resize(map, width, height) {
Ok(()) => eprintln!("fbbootlogd: mapped display"),
Err(err) => {
eprintln!("fbbootlogd: failed to create framebuffer: {}", err);
eprintln!("fbbootlogd: failed to create or map framebuffer: {}", err);
return;
}
}
@@ -286,11 +225,7 @@ impl SchemeSync for FbbootlogScheme {
self.text_buffer.write(buf);
if !self.is_scrollback {
let damage = self.text_screen.write(
unsafe { &mut map.console_map() },
buf,
&mut VecDeque::new(),
);
let damage = self.text_screen.write(map, buf, &mut VecDeque::new());
if let Some(map) = &self.display_map {
map.display_handle
+28 -85
View File
@@ -1,32 +1,13 @@
use console_draw::TextScreen;
use console_draw::{TextScreen, V2DisplayMap};
use drm::buffer::Buffer;
use drm::control::dumbbuffer::{DumbBuffer, DumbMapping};
use drm::control::Device;
use graphics_ipc::v2::{Damage, V2GraphicsHandle};
use inputd::ConsumerHandle;
use std::{io, mem, ptr};
use std::io;
pub struct Display {
pub input_handle: ConsumerHandle,
pub map: Option<DisplayMap>,
}
pub struct DisplayMap {
display_handle: V2GraphicsHandle,
fb: DumbBuffer,
mapping: DumbMapping<'static>,
}
impl DisplayMap {
pub unsafe fn console_map(&mut self) -> console_draw::DisplayMap {
console_draw::DisplayMap {
offscreen: ptr::slice_from_raw_parts_mut(
self.mapping.as_mut_ptr() as *mut u32,
self.mapping.len() / 4,
),
width: self.fb.size().0 as usize,
height: self.fb.size().1 as usize,
}
}
pub map: Option<V2DisplayMap>,
}
impl Display {
@@ -49,40 +30,32 @@ impl Display {
log::debug!("fbcond: Opened new display");
let (width, height) = new_display_handle
.display_size(new_display_handle.first_display().unwrap())
.unwrap();
let mut fb = new_display_handle
.create_dumb_framebuffer(width, height)
.unwrap();
.get_connector(new_display_handle.first_display().unwrap(), true)
.unwrap()
.modes()[0]
.size();
let map = match new_display_handle.map_dumb_framebuffer(&mut fb) {
Ok(map) => unsafe { mem::transmute::<DumbMapping<'_>, DumbMapping<'static>>(map) },
match V2DisplayMap::new(new_display_handle, width.into(), height.into()) {
Ok(map) => {
log::debug!(
"fbcond: Mapped new display with size {}x{}",
map.fb.size().0,
map.fb.size().1,
);
self.map = Some(map)
}
Err(err) => {
log::error!("failed to map display: {}", err);
eprintln!("fbcond: failed to open display: {}", err);
return;
}
};
log::debug!(
"fbcond: Mapped new display with size {}x{}",
fb.size().0,
fb.size().1,
);
self.map = Some(DisplayMap {
display_handle: new_display_handle,
fb,
mapping: map,
});
}
}
pub fn handle_resize(map: &mut DisplayMap, text_screen: &mut TextScreen) {
let (width, height) = match map
.display_handle
.first_display()
.and_then(|handle| map.display_handle.display_size(handle))
{
Ok((width, height)) => (width, height),
pub fn handle_resize(map: &mut V2DisplayMap, text_screen: &mut TextScreen) {
let (width, height) = match map.display_handle.first_display().and_then(|handle| {
Ok(map.display_handle.get_connector(handle, true)?.modes()[0].size())
}) {
Ok((width, height)) => (width.into(), height.into()),
Err(err) => {
log::error!("fbcond: failed to get display size: {}", err);
map.fb.size()
@@ -90,41 +63,11 @@ impl Display {
};
if (width, height) != map.fb.size() {
match map.display_handle.create_dumb_framebuffer(width, height) {
Ok(mut fb) => {
let mut new_map = match map.display_handle.map_dumb_framebuffer(&mut fb) {
Ok(new_map) => unsafe {
mem::transmute::<DumbMapping<'_>, DumbMapping<'static>>(new_map)
},
Err(err) => {
eprintln!("fbcond: failed to open display: {}", err);
return;
}
};
new_map.fill(0);
text_screen.resize(
unsafe { &mut map.console_map() },
&mut console_draw::DisplayMap {
offscreen: ptr::slice_from_raw_parts_mut(
new_map.as_mut_ptr() as *mut u32,
new_map.len() / 4,
),
width: fb.size().0 as usize,
height: fb.size().1 as usize,
},
);
let old_fb = mem::replace(&mut map.fb, fb);
map.mapping = new_map;
let _ = map.display_handle.destroy_dumb_framebuffer(old_fb);
eprintln!("fbcond: mapped display");
}
match text_screen.resize(map, width, height) {
Ok(()) => eprintln!("fbcond: mapped display"),
Err(err) => {
log::error!("fbcond: failed to create framebuffer: {}", err);
eprintln!("fbcond: failed to create or map framebuffer: {}", err);
return;
}
}
}
+1 -3
View File
@@ -124,9 +124,7 @@ impl TextScreen {
if let Some(map) = &mut self.display.map {
Display::handle_resize(map, &mut self.inner);
let damage = self
.inner
.write(unsafe { &mut map.console_map() }, buf, &mut self.input);
let damage = self.inner.write(map, buf, &mut self.input);
self.display.sync_rect(damage);
}
-1
View File
@@ -5,7 +5,6 @@ edition = "2021"
[dependencies]
drm = "0.14"
drm-fourcc = "2.2.0"
log = "0.4"
libredox = "0.1.3"
redox-ioctl = { git = "https://gitlab.redox-os.org/redox-os/relibc.git" }
+29 -43
View File
@@ -13,6 +13,35 @@ pub struct Damage {
}
impl Damage {
pub const NONE: Self = Damage {
x: 0,
y: 0,
width: 0,
height: 0,
};
pub fn merge(self, other: Self) -> Self {
if self.width == 0 || self.height == 0 {
return other;
}
if other.width == 0 || other.height == 0 {
return self;
}
let x = cmp::min(self.x, other.x);
let y = cmp::min(self.y, other.y);
let x2 = cmp::max(self.x + self.width, other.x + other.width);
let y2 = cmp::max(self.y + self.height, other.y + other.height);
Damage {
x,
y,
width: x2 - x,
height: y2 - y,
}
}
#[must_use]
pub fn clip(mut self, width: u32, height: u32) -> Self {
// Clip damage
@@ -30,46 +59,3 @@ impl Damage {
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());
}
}
}
-76
View File
@@ -1,80 +1,4 @@
use std::fs::File;
use std::os::unix::io::AsRawFd;
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, requires each display to be
/// handled separately and doesn't support page flipping.
///
/// This API is stable. No breaking changes are allowed to be made without a version bump.
pub struct V1GraphicsHandle {
file: File,
}
impl V1GraphicsHandle {
pub fn from_file(file: File) -> io::Result<Self> {
Ok(V1GraphicsHandle { file })
}
pub fn map_display(&self) -> io::Result<DisplayMap> {
let mut buf: [u8; 4096] = [0; 4096];
let count =
libredox::call::fpath(self.file.as_raw_fd() as usize, &mut buf).unwrap_or_else(|e| {
panic!("Could not read display path with fpath(): {e}");
});
let url =
String::from_utf8(Vec::from(&buf[..count])).expect("Could not create Utf8 Url String");
let path = url.split(':').nth(1).expect("Could not get path from url");
let mut path_parts = path.split('/').skip(1);
let width = path_parts
.next()
.unwrap_or("")
.parse::<usize>()
.unwrap_or(0);
let height = path_parts
.next()
.unwrap_or("")
.parse::<usize>()
.unwrap_or(0);
let display_ptr = unsafe {
libredox::call::mmap(libredox::call::MmapArgs {
fd: self.file.as_raw_fd() as usize,
offset: 0,
length: (width * height * 4),
prot: flag::PROT_READ | flag::PROT_WRITE,
flags: flag::MAP_SHARED,
addr: core::ptr::null_mut(),
})?
};
let offscreen = ptr::slice_from_raw_parts_mut(display_ptr as *mut u32, width * height);
Ok(unsafe { DisplayMap::new(offscreen, width, height) })
}
pub fn sync_full_screen(&self) -> io::Result<()> {
libredox::call::fsync(self.file.as_raw_fd() as usize)?;
Ok(())
}
pub fn sync_rect(&self, sync_rect: Damage) -> io::Result<()> {
libredox::call::write(self.file.as_raw_fd() as usize, unsafe {
slice::from_raw_parts(
ptr::addr_of!(sync_rect).cast::<u8>(),
mem::size_of::<Damage>(),
)
})?;
Ok(())
}
}
#[derive(Debug, Copy, Clone)]
#[repr(C, packed)]
+1 -23
View File
@@ -4,12 +4,10 @@ use std::os::unix::io::AsRawFd;
use std::{io, mem};
use drm::control::connector::{self, State};
use drm::control::dumbbuffer::{DumbBuffer, DumbMapping};
use drm::control::Device as _;
use drm::{ClientCapability, Device as _, DriverCapability};
use drm_fourcc::DrmFourcc;
pub use crate::common::{Damage, DisplayMap};
pub use crate::common::Damage;
extern "C" {
fn redox_sys_call_v0(
@@ -75,26 +73,6 @@ impl V2GraphicsHandle {
Err(io::Error::other("no connected display"))
}
pub fn display_size(&self, handle: connector::Handle) -> io::Result<(u32, u32)> {
let (width, height) = self.get_connector(handle, true)?.modes()[0].size();
Ok((u32::from(width), u32::from(height)))
}
pub fn create_dumb_framebuffer(&self, width: u32, height: u32) -> io::Result<DumbBuffer> {
self.create_dumb_buffer((width, height), DrmFourcc::Argb8888, 32)
}
pub fn map_dumb_framebuffer<'a>(
&self,
buffer: &'a mut DumbBuffer,
) -> io::Result<DumbMapping<'a>> {
self.map_dumb_buffer(buffer)
}
pub fn destroy_dumb_framebuffer(&self, buffer: DumbBuffer) -> io::Result<()> {
self.destroy_dumb_buffer(buffer)
}
pub fn update_plane(&self, display_id: usize, fb_id: u32, damage: Damage) -> io::Result<()> {
let mut cmd = ipc::UpdatePlane {
display_id,