Skip to content

Commit

Permalink
Add a very basic and crude implementation for software rendering to t…
Browse files Browse the repository at this point in the history
…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
tronical committed Jun 22, 2024
1 parent 011a3e8 commit d86ae62
Show file tree
Hide file tree
Showing 5 changed files with 266 additions and 62 deletions.
7 changes: 4 additions & 3 deletions internal/backends/linuxkms/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ path = "lib.rs"
[features]
renderer-skia = ["renderer-skia-opengl"]
renderer-skia-vulkan = ["i-slint-renderer-skia/vulkan", "vulkano"]
renderer-skia-opengl = ["i-slint-renderer-skia/opengl", "drm", "gbm", "gbm-sys", "glutin", "raw-window-handle"]
renderer-skia-opengl = ["i-slint-renderer-skia/opengl", "drm", "gbm", "gbm-sys", "glutin", "raw-window-handle", "dep:memmap2"]
renderer-femtovg = ["i-slint-renderer-femtovg", "drm", "gbm", "gbm-sys", "glutin", "raw-window-handle"]
renderer-software = ["i-slint-core/software-renderer-systemfonts", "drm", "dep:bytemuck"]
renderer-software = ["i-slint-core/software-renderer-systemfonts", "drm", "dep:bytemuck", "dep:memmap2"]
libseat = ["dep:libseat"]

#default = ["renderer-skia", "renderer-femtovg"]
Expand All @@ -37,11 +37,12 @@ input = { version = "0.8.2" }
xkbcommon = { version = "0.7.0" }
calloop = { version = "0.12.3" }
libseat = { version = "0.2.1", optional = true, default-features = false }
nix = { version = "0.27.0", features=["fs"] }
nix = { version = "0.27.0", features=["fs", "ioctl"] }
vulkano = { version = "0.34.0", optional = true, default-features = false }
drm = { version = "0.9.0", optional = true }
gbm = { version = "0.12.0", optional = true, default-features = false, features = ["drm-support"] }
gbm-sys = { version = "0.2.2", optional = true }
glutin = { workspace = true, optional = true, default-features = false, features = ["libloading", "egl"] }
raw-window-handle = { version = "0.6.2", optional = true }
bytemuck = { workspace = true, optional = true, features = ["derive"] }
memmap2 = { version = "0.9.4", optional = true }
59 changes: 59 additions & 0 deletions internal/backends/linuxkms/display.rs
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;

Expand Down Expand Up @@ -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(())
}
}
2 changes: 2 additions & 0 deletions internal/backends/linuxkms/display/swdisplay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ pub trait SoftwareBufferDisplay {
}

mod dumbbuffer;
mod linuxfb;

pub fn new(
device_opener: &crate::DeviceOpener,
) -> Result<Rc<dyn SoftwareBufferDisplay>, PlatformError> {
dumbbuffer::DumbBufferDisplay::new(device_opener)
.or_else(|_| linuxfb::LinuxFBDisplay::new(device_opener))
}
199 changes: 199 additions & 0 deletions internal/backends/linuxkms/display/swdisplay/linuxfb.rs
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);
61 changes: 2 additions & 59 deletions internal/backends/linuxkms/display/vulkandisplay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ use vulkano::instance::{Instance, InstanceCreateFlags, InstanceCreateInfo, Insta
use vulkano::swapchain::Surface;
use vulkano::VulkanLibrary;

use std::cell::Cell;
use std::rc::{Rc, Weak};
use std::rc::Rc;
use std::sync::Arc;

use super::Presenter;
Expand Down Expand Up @@ -174,62 +173,6 @@ pub fn create_vulkan_display() -> Result<VulkanDisplay, PlatformError> {
queue_family_index,
surface: vulkan_surface,
size,
presenter: TimerBasedAnimationDriver::new(),
presenter: crate::display::TimerBasedAnimationDriver::new(),
})
}

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 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(())
}
}

0 comments on commit d86ae62

Please sign in to comment.