Skip to content

Commit

Permalink
Add support for RGB565 linux framebuffer rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
tronical committed Jul 7, 2024
1 parent 14593a5 commit 11e61b6
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 28 deletions.
6 changes: 5 additions & 1 deletion internal/backends/linuxkms/display/swdisplay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ pub trait SoftwareBufferDisplay {
fn size(&self) -> (u32, u32);
fn map_back_buffer(
&self,
callback: &mut dyn FnMut(&'_ mut [u8], u8) -> Result<(), PlatformError>,
callback: &mut dyn FnMut(
&'_ mut [u8],
u8,
drm::buffer::DrmFourcc,
) -> Result<(), PlatformError>,
) -> Result<(), PlatformError>;
fn as_presenter(self: Rc<Self>) -> Rc<dyn super::Presenter>;
}
Expand Down
65 changes: 53 additions & 12 deletions internal/backends/linuxkms/display/swdisplay/dumbbuffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,16 @@ impl DumbBufferDisplay {

//eprintln!("mode {}/{}", width, height);

let front_buffer = DumbBuffer::allocate(&drm_output.drm_device, drm_output.size())?.into();
let back_buffer = DumbBuffer::allocate(&drm_output.drm_device, drm_output.size())?.into();
let front_buffer: RefCell<DumbBuffer> =
DumbBuffer::allocate(&drm_output.drm_device, drm_output.size())?.into();
let back_buffer = DumbBuffer::allocate_with_format(
&drm_output.drm_device,
drm_output.size(),
front_buffer.borrow().format,
front_buffer.borrow().depth,
front_buffer.borrow().bpp,
)?
.into();

Ok(Rc::new(Self { drm_output, front_buffer, back_buffer }))
}
Expand All @@ -36,15 +44,20 @@ impl super::SoftwareBufferDisplay for DumbBufferDisplay {

fn map_back_buffer(
&self,
callback: &mut dyn FnMut(&'_ mut [u8], u8) -> Result<(), PlatformError>,
callback: &mut dyn FnMut(
&'_ mut [u8],
u8,
drm::buffer::DrmFourcc,
) -> Result<(), PlatformError>,
) -> Result<(), PlatformError> {
let mut back_buffer = self.back_buffer.borrow_mut();
let age = back_buffer.age;
let format = back_buffer.format;
self.drm_output
.drm_device
.map_dumb_buffer(&mut back_buffer.buffer_handle)
.map_err(|e| PlatformError::Other(format!("Error mapping dumb buffer: {e}").into()))
.and_then(|mut buffer| callback(buffer.as_mut(), age))
.and_then(|mut buffer| callback(buffer.as_mut(), age, format))
}

fn as_presenter(self: Rc<Self>) -> Rc<dyn crate::display::Presenter> {
Expand Down Expand Up @@ -90,20 +103,48 @@ struct DumbBuffer {
fb_handle: drm::control::framebuffer::Handle,
buffer_handle: drm::control::dumbbuffer::DumbBuffer,
age: u8,
format: drm::buffer::DrmFourcc,
depth: u32,
bpp: u32,
}

impl DumbBuffer {
fn allocate(
device: &impl drm::control::Device,
(width, height): (u32, u32),
) -> Result<Self, PlatformError> {
let buffer_handle = device
.create_dumb_buffer((width, height), drm::buffer::DrmFourcc::Xrgb8888, 32)
.map_err(|e| format!("Error creating dumb buffer ({}/{}): {}", width, height, e))?;
let fb_handle = device
.add_framebuffer(&buffer_handle, 24, 32)
.map_err(|e| format!("Error creating framebuffer for dumb buffer: {e}"))?;

Ok(Self { fb_handle, buffer_handle, age: 0 })
let mut last_err = None;
for (format, depth, bpp) in [
(drm::buffer::DrmFourcc::Xrgb8888, 24, 32),
(drm::buffer::DrmFourcc::Rgb565, 16, 16),
] {
match Self::allocate_with_format(device, (width, height), format, depth, bpp) {
Ok(buf) => return Ok(buf),
Err(err) => last_err = Some(err),
}
}

Err(last_err.unwrap_or_else(|| "Could not allocate drm dumb buffer".into()))
}

fn allocate_with_format(
device: &impl drm::control::Device,
(width, height): (u32, u32),
format: drm::buffer::DrmFourcc,
depth: u32,
bpp: u32,
) -> Result<Self, PlatformError> {
let buffer_handle =
device.create_dumb_buffer((width, height), format, bpp).map_err(|e| {
format!(
"Error creating dumb buffer ({}/{}): {} for format {format}",
width, height, e
)
})?;
let fb_handle = device.add_framebuffer(&buffer_handle, depth, bpp).map_err(|e| {
format!("Error creating framebuffer for dumb buffer for format {format}: {e}")
})?;

Ok(Self { fb_handle, buffer_handle, age: 0, format, depth, bpp })
}
}
35 changes: 30 additions & 5 deletions internal/backends/linuxkms/display/swdisplay/linuxfb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub struct LinuxFBDisplay {
height: u32,
presenter: Rc<crate::display::timeranimations::TimerBasedAnimationDriver>,
first_frame: Cell<bool>,
format: drm::buffer::DrmFourcc,
}

impl LinuxFBDisplay {
Expand Down Expand Up @@ -55,8 +56,18 @@ impl LinuxFBDisplay {
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 format = if vinfo.bits_per_pixel == 32 {
drm::buffer::DrmFourcc::Xrgb8888
} else if vinfo.bits_per_pixel == 16 {
if vinfo.red != RGB565_EXPECTED_RED_CHANNEL
|| vinfo.green != RGB565_EXPECTED_GREEN_CHANNEL
|| vinfo.blue != RGB565_EXPECTED_BLUE_CHANNEL
{
return Err(format!("Error using linux framebuffer: 16-bpp framebuffer does not have expected 565 format. Found {}/{}/{}", vinfo.red.length, vinfo.green.length, vinfo.blue.length).into());
}
drm::buffer::DrmFourcc::Rgb565
} else {
return Err(format!("Error using linux framebuffer: Only 32- and 16-bpp framebuffers are supported right now, found {}", vinfo.bits_per_pixel).into());
};

let bpp = vinfo.bits_per_pixel / 8;
Expand Down Expand Up @@ -89,6 +100,7 @@ impl LinuxFBDisplay {
height,
presenter: crate::display::timeranimations::TimerBasedAnimationDriver::new(),
first_frame: Cell::new(true),
format,
}))
}
}
Expand All @@ -100,11 +112,15 @@ impl super::SoftwareBufferDisplay for LinuxFBDisplay {

fn map_back_buffer(
&self,
callback: &mut dyn FnMut(&'_ mut [u8], u8) -> Result<(), PlatformError>,
callback: &mut dyn FnMut(
&'_ mut [u8],
u8,
drm::buffer::DrmFourcc,
) -> 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)?;
callback(self.back_buffer.borrow_mut().as_mut(), age, self.format)?;

let mut fb = self.fb.borrow_mut();
fb.as_mut().copy_from_slice(&self.back_buffer.borrow());
Expand All @@ -116,10 +132,19 @@ impl super::SoftwareBufferDisplay for LinuxFBDisplay {
}
}

const RGB565_EXPECTED_RED_CHANNEL: fb_bitfield =
fb_bitfield { offset: 11, length: 5, msb_right: 0 };

const RGB565_EXPECTED_GREEN_CHANNEL: fb_bitfield =
fb_bitfield { offset: 5, length: 6, msb_right: 0 };

const RGB565_EXPECTED_BLUE_CHANNEL: fb_bitfield =
fb_bitfield { offset: 0, length: 5, msb_right: 0 };

const FBIOGET_VSCREENINFO: u32 = 0x4600;

#[repr(C)]
#[derive(Debug)]
#[derive(Debug, PartialEq)]
#[allow(non_camel_case_types)]

pub struct fb_bitfield {
Expand Down
18 changes: 16 additions & 2 deletions internal/backends/linuxkms/renderer/skia.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,22 @@ impl i_slint_renderer_skia::software_surface::RenderBuffer for DrmDumbBufferAcce
return Ok(());
};

self.display.map_back_buffer(&mut |pixels, _age| {
render_callback(width, height, skia_safe::ColorType::BGRA8888, pixels.as_mut())
self.display.map_back_buffer(&mut |pixels, _age, format| {
render_callback(
width,
height,
match format {
drm::buffer::DrmFourcc::Xrgb8888 => skia_safe::ColorType::BGRA8888,
drm::buffer::DrmFourcc::Rgb565 => skia_safe::ColorType::RGB565,
_ => {
return Err(format!(
"Unsupported frame buffer format {format} used with skia software renderer"
)
.into())
}
},
pixels.as_mut(),
)
})
}
}
32 changes: 24 additions & 8 deletions internal/backends/linuxkms/renderer/sw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ pub struct SoftwareRendererAdapter {

#[repr(transparent)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct DumbBufferPixel(pub u32);
struct DumbBufferPixelXrgb888(pub u32);

impl From<DumbBufferPixel> for PremultipliedRgbaColor {
impl From<DumbBufferPixelXrgb888> for PremultipliedRgbaColor {
#[inline]
fn from(pixel: DumbBufferPixel) -> Self {
fn from(pixel: DumbBufferPixelXrgb888) -> Self {
let v = pixel.0;
PremultipliedRgbaColor {
red: (v >> 16) as u8,
Expand All @@ -35,7 +35,7 @@ impl From<DumbBufferPixel> for PremultipliedRgbaColor {
}
}

impl From<PremultipliedRgbaColor> for DumbBufferPixel {
impl From<PremultipliedRgbaColor> for DumbBufferPixelXrgb888 {
#[inline]
fn from(pixel: PremultipliedRgbaColor) -> Self {
Self(
Expand All @@ -47,7 +47,7 @@ impl From<PremultipliedRgbaColor> for DumbBufferPixel {
}
}

impl TargetPixel for DumbBufferPixel {
impl TargetPixel for DumbBufferPixelXrgb888 {
fn blend(&mut self, color: PremultipliedRgbaColor) {
let mut x = PremultipliedRgbaColor::from(*self);
x.blend(color);
Expand Down Expand Up @@ -100,7 +100,7 @@ impl crate::fullscreenwindowadapter::FullscreenRenderer for SoftwareRendererAdap
_draw_mouse_cursor_callback: &dyn Fn(&mut dyn i_slint_core::item_rendering::ItemRenderer),
ready_for_next_animation_frame: Box<dyn FnOnce()>,
) -> Result<(), PlatformError> {
self.display.map_back_buffer(&mut |pixels, age| {
self.display.map_back_buffer(&mut |pixels, age, format| {
self.renderer.set_repaint_buffer_type(match age {
1 => RepaintBufferType::ReusedBuffer,
2 => RepaintBufferType::SwappedBuffers,
Expand All @@ -122,8 +122,24 @@ impl crate::fullscreenwindowadapter::FullscreenRenderer for SoftwareRendererAdap
}
});

let buffer: &mut [DumbBufferPixel] = bytemuck::cast_slice_mut(pixels.as_mut());
self.renderer.render(buffer, self.size.width as usize);
match format {
drm::buffer::DrmFourcc::Xrgb8888 => {
let buffer: &mut [DumbBufferPixelXrgb888] =
bytemuck::cast_slice_mut(pixels.as_mut());
self.renderer.render(buffer, self.size.width as usize);
}
drm::buffer::DrmFourcc::Rgb565 => {
let buffer: &mut [i_slint_core::software_renderer::Rgb565Pixel] =
bytemuck::cast_slice_mut(pixels.as_mut());
self.renderer.render(buffer, self.size.width as usize);
}
_ => {
return Err(format!(
"Unsupported frame buffer format {format} used with software renderer"
)
.into())
}
}

Ok(())
})?;
Expand Down

0 comments on commit 11e61b6

Please sign in to comment.