-
Notifications
You must be signed in to change notification settings - Fork 604
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a very basic and crude implementation for software rendering to t…
…he legacy Linux framebuffer interface This assumes the same xrgb888 interface that the drm dumb buffer uses, too. Missing, beyond different pixel depths is: - mode setting (requires fbset on the command line right now) - vsync (not sure if possible) - line padding
- Loading branch information
Showing
5 changed files
with
266 additions
and
62 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,9 @@ | ||
// Copyright © SixtyFPS GmbH <[email protected]> | ||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 | ||
|
||
use std::cell::Cell; | ||
use std::rc::{Rc, Weak}; | ||
|
||
use i_slint_core::api::PhysicalSize; | ||
use i_slint_core::platform::PlatformError; | ||
|
||
|
@@ -97,3 +100,59 @@ impl RenderingRotation { | |
} | ||
} | ||
} | ||
|
||
struct TimerBasedAnimationDriver { | ||
timer: i_slint_core::timers::Timer, | ||
next_animation_frame_callback: Cell<Option<Box<dyn FnOnce()>>>, | ||
} | ||
|
||
impl TimerBasedAnimationDriver { | ||
fn new() -> Rc<Self> { | ||
Rc::new_cyclic(|self_weak: &Weak<Self>| { | ||
let self_weak = self_weak.clone(); | ||
let timer = i_slint_core::timers::Timer::default(); | ||
timer.start( | ||
i_slint_core::timers::TimerMode::Repeated, | ||
std::time::Duration::from_millis(16), | ||
move || { | ||
let Some(this) = self_weak.upgrade() else { return }; | ||
// Stop the timer and let the callback decide if we need to continue. It will set | ||
// `needs_redraw` to true of animations should continue, render() will be called, | ||
// present_with_next_frame_callback() will be called and then the timer restarted. | ||
this.timer.stop(); | ||
if let Some(next_animation_frame_callback) = | ||
this.next_animation_frame_callback.take() | ||
{ | ||
next_animation_frame_callback(); | ||
} | ||
}, | ||
); | ||
// Activate it only when we present a frame. | ||
timer.stop(); | ||
|
||
Self { timer, next_animation_frame_callback: Default::default() } | ||
}) | ||
} | ||
} | ||
|
||
impl crate::display::Presenter for TimerBasedAnimationDriver { | ||
fn is_ready_to_present(&self) -> bool { | ||
true | ||
} | ||
|
||
fn register_page_flip_handler( | ||
&self, | ||
_event_loop_handle: crate::calloop_backend::EventLoopHandle, | ||
) -> Result<(), PlatformError> { | ||
Ok(()) | ||
} | ||
|
||
fn present_with_next_frame_callback( | ||
&self, | ||
ready_for_next_animation_frame: Box<dyn FnOnce()>, | ||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { | ||
self.next_animation_frame_callback.set(Some(ready_for_next_animation_frame)); | ||
self.timer.restart(); | ||
Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
199 changes: 199 additions & 0 deletions
199
internal/backends/linuxkms/display/swdisplay/linuxfb.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
// Copyright © SixtyFPS GmbH <[email protected]> | ||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 | ||
|
||
use std::cell::{Cell, RefCell}; | ||
use std::os::fd::AsRawFd; | ||
use std::rc::Rc; | ||
|
||
use i_slint_core::platform::PlatformError; | ||
|
||
pub struct LinuxFBDisplay { | ||
fb: RefCell<memmap2::MmapMut>, | ||
back_buffer: RefCell<Box<[u8]>>, | ||
width: u32, | ||
height: u32, | ||
presenter: Rc<crate::display::TimerBasedAnimationDriver>, | ||
first_frame: Cell<bool>, | ||
} | ||
|
||
impl LinuxFBDisplay { | ||
pub fn new( | ||
device_opener: &crate::DeviceOpener, | ||
) -> Result<Rc<dyn super::SoftwareBufferDisplay>, PlatformError> { | ||
let mut last_err = None; | ||
|
||
for fbnum in 0..10 { | ||
match Self::new_with_path( | ||
device_opener, | ||
std::path::Path::new(&format!("/dev/fb{fbnum}")), | ||
) { | ||
Ok(dsp) => return Ok(dsp), | ||
Err(e) => last_err = Some(e), | ||
} | ||
} | ||
|
||
Err(last_err.unwrap_or_else(|| "Could not create a linuxfb display".into())) | ||
} | ||
|
||
fn new_with_path( | ||
device_opener: &crate::DeviceOpener, | ||
path: &std::path::Path, | ||
) -> Result<Rc<dyn super::SoftwareBufferDisplay>, PlatformError> { | ||
let fd = device_opener(path)?; | ||
|
||
let vinfo = unsafe { | ||
let mut vinfo: fb_var_screeninfo = std::mem::zeroed(); | ||
fbioget_vscreeninfo(fd.as_raw_fd(), &mut vinfo as *mut _) | ||
.map_err(|errno| format!("Error reading framebuffer variable info: {errno}"))?; | ||
vinfo | ||
}; | ||
|
||
let finfo = unsafe { | ||
let mut finfo: fb_fix_screeninfo = std::mem::zeroed(); | ||
fbioget_fscreeninfo(fd.as_raw_fd(), &mut finfo as *mut _) | ||
.map_err(|errno| format!("Error reading framebuffer fixed info: {errno}"))?; | ||
finfo | ||
}; | ||
|
||
if vinfo.bits_per_pixel != 32 { | ||
return Err(format!("Error using linux framebuffer: Only 32-bpp framebuffers are supported right now, found {}", vinfo.bits_per_pixel).into()); | ||
}; | ||
|
||
let bpp = vinfo.bits_per_pixel / 8; | ||
|
||
let width = vinfo.xres; | ||
let height = vinfo.yres; | ||
|
||
if finfo.line_length != width * bpp { | ||
return Err(format!("Error using linux framebuffer: padded lines are not supported yet (width: {}, bpp: {}, line length: {})", width, bpp, finfo.line_length).into()); | ||
} | ||
|
||
let size_bytes = width as usize * height as usize * bpp as usize; | ||
|
||
let fb = unsafe { | ||
memmap2::MmapOptions::new() | ||
.len(size_bytes) | ||
.map_mut(&fd) | ||
.map_err(|err| format!("Error mmapping framebuffer: {err}"))? | ||
}; | ||
|
||
//eprintln!("vinfo {:#?}", vinfo); | ||
//eprintln!("finfo {:#?}", finfo); | ||
|
||
let back_buffer = RefCell::new(vec![0u8; size_bytes].into_boxed_slice()); | ||
|
||
Ok(Rc::new(Self { | ||
fb: RefCell::new(fb), | ||
back_buffer, | ||
width, | ||
height, | ||
presenter: crate::display::TimerBasedAnimationDriver::new(), | ||
first_frame: Cell::new(true), | ||
})) | ||
} | ||
} | ||
|
||
impl super::SoftwareBufferDisplay for LinuxFBDisplay { | ||
fn size(&self) -> (u32, u32) { | ||
(self.width, self.height) | ||
} | ||
|
||
fn map_back_buffer( | ||
&self, | ||
callback: &mut dyn FnMut(&'_ mut [u8], u8) -> Result<(), PlatformError>, | ||
) -> Result<(), PlatformError> { | ||
let age = if self.first_frame.get() { 0 } else { 1 }; | ||
self.first_frame.set(false); | ||
callback(self.back_buffer.borrow_mut().as_mut(), age)?; | ||
|
||
let mut fb = self.fb.borrow_mut(); | ||
fb.as_mut().copy_from_slice(&self.back_buffer.borrow()); | ||
Ok(()) | ||
} | ||
|
||
fn as_presenter(self: Rc<Self>) -> Rc<dyn crate::display::Presenter> { | ||
self.presenter.clone() | ||
} | ||
} | ||
|
||
const FBIOGET_VSCREENINFO: u32 = 0x4600; | ||
|
||
#[repr(C)] | ||
#[derive(Debug)] | ||
#[allow(non_camel_case_types)] | ||
|
||
pub struct fb_bitfield { | ||
offset: u32, | ||
length: u32, | ||
msb_right: u32, | ||
} | ||
|
||
#[repr(C)] | ||
#[derive(Debug)] | ||
#[allow(non_camel_case_types)] | ||
pub struct fb_var_screeninfo { | ||
xres: u32, | ||
yres: u32, | ||
xres_virtual: u32, | ||
yres_virtual: u32, | ||
xoffset: u32, | ||
yoffset: u32, | ||
bits_per_pixel: u32, | ||
grayscale: u32, | ||
red: fb_bitfield, | ||
green: fb_bitfield, | ||
blue: fb_bitfield, | ||
transp: fb_bitfield, | ||
|
||
nonstd: u32, | ||
|
||
activate: u32, | ||
|
||
height: u32, | ||
width: u32, | ||
|
||
accel_flags: u32, | ||
|
||
pixclock: u32, | ||
left_margin: u32, | ||
right_margin: u32, | ||
upper_margin: u32, | ||
lower_margin: u32, | ||
hsync_len: u32, | ||
vsync_len: u32, | ||
sync: u32, | ||
vmode: u32, | ||
rotate: u32, | ||
colorspace: u32, | ||
reserved: [u32; 4], | ||
} | ||
|
||
nix::ioctl_read_bad!(fbioget_vscreeninfo, FBIOGET_VSCREENINFO, fb_var_screeninfo); | ||
|
||
const FBIOGET_FSCREENINFO: u32 = 0x4602; | ||
|
||
#[repr(C)] | ||
#[derive(Debug)] | ||
#[allow(non_camel_case_types)] | ||
pub struct fb_fix_screeninfo { | ||
id: [u8; 16], | ||
smem_start: std::ffi::c_ulong, | ||
|
||
smem_len: u32, | ||
r#type: u32, | ||
type_aux: u32, | ||
visual: u32, | ||
xpanstep: u16, | ||
ypanstep: u16, | ||
ywrapstep: u16, | ||
line_length: u32, | ||
mmio_start: std::ffi::c_ulong, | ||
|
||
mmio_len: u32, | ||
accel: u32, | ||
|
||
capabilities: u16, | ||
reserved: [u16; 2], | ||
} | ||
|
||
nix::ioctl_read_bad!(fbioget_fscreeninfo, FBIOGET_FSCREENINFO, fb_fix_screeninfo); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters