Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LinuxKMS: Add support for the Slint software renderer #4334

Merged
merged 5 commits into from
Jun 10, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/reference/src/advanced/backend_linuxkms.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ The LinuxKMS backend supports different renderers. They can be explicitly select
|---------------|------------------------|-----------------------------------------------------------------------------|
| FemtoVG | OpenGL ES 2.0 | `linuxkms-femtovg` |
| Skia | OpenGL ES 2.0, Vulkan | `linuxkms-skia-opengl`, `linuxkms-skia-vulkan`, or `linuxkms-skia-software` |
| software | None | `linuxkms-software` |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the missing cursor be documented, perhaps?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, I'll add that.


:::{note}
This backend is still experimental. The backend has not undergone a great variety of testing on different devices
Expand Down
2 changes: 2 additions & 0 deletions internal/backends/linuxkms/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ 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-femtovg = ["i-slint-renderer-femtovg", "drm", "gbm", "gbm-sys", "glutin", "raw-window-handle"]
renderer-software = ["i-slint-core/software-renderer-systemfonts", "dep:bytemuck"]
libseat = ["dep:libseat"]

#default = ["renderer-skia", "renderer-femtovg"]
Expand All @@ -43,3 +44,4 @@ gbm = { version = "0.12.0", optional = true, default-features = false, features
gbm-sys = { version = "0.2.2", optional = true }
glutin = { workspace = true, optional = true, default-features = false, features = ["libloading", "egl"] }
raw-window-handle = { version = "0.5.2", optional = true }
bytemuck = { workspace = true, optional = true, features = ["derive"] }
6 changes: 4 additions & 2 deletions internal/backends/linuxkms/calloop_backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,15 @@ impl Backend {
Some("skia-software") => crate::renderer::skia::SkiaRendererAdapter::new_software,
#[cfg(feature = "renderer-femtovg")]
Some("femtovg") => crate::renderer::femtovg::FemtoVGRendererAdapter::new,
None => crate::renderer::try_skia_then_femtovg,
#[cfg(feature = "renderer-software")]
Some("software") => crate::renderer::sw::SoftwareRendererAdapter::new,
None => crate::renderer::try_skia_then_femtovg_then_software,
Some(renderer_name) => {
eprintln!(
"slint linuxkms backend: unrecognized renderer {}, falling back default",
renderer_name
);
crate::renderer::try_skia_then_femtovg
crate::renderer::try_skia_then_femtovg_then_software
}
};

Expand Down
6 changes: 5 additions & 1 deletion internal/backends/linuxkms/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ pub trait Presenter {

#[cfg(any(feature = "renderer-skia-opengl", feature = "renderer-femtovg"))]
pub mod gbmdisplay;
#[cfg(any(feature = "renderer-skia-opengl", feature = "renderer-skia-vulkan"))]
#[cfg(any(
feature = "renderer-skia-opengl",
feature = "renderer-skia-vulkan",
feature = "renderer-software"
))]
pub mod swdisplay;
#[cfg(feature = "renderer-skia-vulkan")]
pub mod vulkandisplay;
Expand Down
14 changes: 12 additions & 2 deletions internal/backends/linuxkms/display/swdisplay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,16 @@ impl SoftwareBufferDisplay {
&self,
callback: &mut dyn FnMut(
drm::control::dumbbuffer::DumbMapping<'_>,
u8,
) -> Result<(), PlatformError>,
) -> Result<(), PlatformError> {
let mut back_buffer = self.back_buffer.borrow_mut();
let age = back_buffer.age;
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(callback)
.and_then(|buffer| callback(buffer, age))
}
}

Expand All @@ -52,6 +54,13 @@ impl super::Presenter for SoftwareBufferDisplay {
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// TODO: dirty framebuffer
self.front_buffer.swap(&self.back_buffer);
self.front_buffer.borrow_mut().age = 1;
{
let mut back_buffer = self.back_buffer.borrow_mut();
if back_buffer.age != 0 {
back_buffer.age += 1;
}
}
self.drm_output.present(
self.front_buffer.borrow().buffer_handle,
self.front_buffer.borrow().fb_handle,
Expand All @@ -68,6 +77,7 @@ impl super::Presenter for SoftwareBufferDisplay {
struct DumbBuffer {
fb_handle: drm::control::framebuffer::Handle,
buffer_handle: drm::control::dumbbuffer::DumbBuffer,
age: u8,
}

impl DumbBuffer {
Expand All @@ -82,6 +92,6 @@ impl DumbBuffer {
.add_framebuffer(&buffer_handle, 24, 32)
.map_err(|e| format!("Error creating framebuffer for dumb buffer: {e}"))?;

Ok(Self { fb_handle, buffer_handle })
Ok(Self { fb_handle, buffer_handle, age: 0 })
}
}
9 changes: 8 additions & 1 deletion internal/backends/linuxkms/fullscreenwindowadapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ use std::pin::Pin;
use std::rc::Rc;

use i_slint_core::api::{LogicalPosition, PhysicalSize as PhysicalWindowSize};
use i_slint_core::graphics::Image;
use i_slint_core::graphics::{euclid, Image};
use i_slint_core::item_rendering::ItemRenderer;
use i_slint_core::lengths::LogicalRect;
use i_slint_core::platform::WindowEvent;
use i_slint_core::slice::Slice;
use i_slint_core::Property;
Expand Down Expand Up @@ -108,13 +109,19 @@ impl FullscreenWindowAdapter {
self.rotation,
&|item_renderer| {
if let Some(mouse_position) = mouse_position.get() {
let cursor_image = mouse_cursor_image();
item_renderer.save_state();
item_renderer.translate(
i_slint_core::lengths::logical_point_from_api(mouse_position)
.to_vector(),
);
item_renderer.draw_image_direct(mouse_cursor_image());
item_renderer.restore_state();
let cursor_rect = LogicalRect::new(
euclid::point2(mouse_position.x, mouse_position.y),
euclid::Size2D::from_untyped(cursor_image.size().cast()),
);
self.renderer.as_core_renderer().mark_dirty_region(cursor_rect.into());
Comment on lines +120 to +124
Copy link
Member Author

@tronical tronical Jun 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is harmless, but I can also remove it if desired. It's a no-op as there won't be a mouse with the sw renderer, and otherwise mark_dirty_region is a no-op with the GPU renderers.

But for strictly speaking this is the correct thing to do while we have the code for this approach here (to be removed later with the image we discussed).

}
},
Box::new({
Expand Down
59 changes: 41 additions & 18 deletions internal/backends/linuxkms/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,33 +22,56 @@ mod display;

#[cfg(target_os = "linux")]
mod renderer {
use i_slint_core::platform::PlatformError;

use crate::fullscreenwindowadapter::FullscreenRenderer;

#[cfg(any(feature = "renderer-skia-opengl", feature = "renderer-skia-vulkan"))]
pub mod skia;

#[cfg(feature = "renderer-femtovg")]
pub mod femtovg;

pub fn try_skia_then_femtovg(
_device_opener: &crate::DeviceOpener,
) -> Result<
Box<dyn crate::fullscreenwindowadapter::FullscreenRenderer>,
i_slint_core::platform::PlatformError,
> {
#[allow(unused_mut, unused_assignments)]
let mut result = Err(format!("No renderer configured").into());

#[cfg(any(feature = "renderer-skia-opengl", feature = "renderer-skia-vulkan"))]
{
result =
skia::SkiaRendererAdapter::new_try_vulkan_then_opengl_then_software(_device_opener);
}
#[cfg(feature = "renderer-software")]
pub mod sw;

#[cfg(feature = "renderer-femtovg")]
if result.is_err() {
result = femtovg::FemtoVGRendererAdapter::new(_device_opener);
pub fn try_skia_then_femtovg_then_software(
_device_opener: &crate::DeviceOpener,
) -> Result<Box<dyn FullscreenRenderer>, PlatformError> {
type FactoryFn =
fn(&crate::DeviceOpener) -> Result<Box<(dyn FullscreenRenderer)>, PlatformError>;

let renderers = [
#[cfg(any(feature = "renderer-skia-opengl", feature = "renderer-skia-vulkan"))]
(
"Skia",
skia::SkiaRendererAdapter::new_try_vulkan_then_opengl_then_software as FactoryFn,
),
#[cfg(feature = "renderer-femtovg")]
("FemtoVG", femtovg::FemtoVGRendererAdapter::new as FactoryFn),
#[cfg(feature = "renderer-software")]
("Software", sw::SoftwareRendererAdapter::new as FactoryFn),
("", |_| Err(PlatformError::NoPlatform)),
];

let mut renderer_errors: Vec<String> = Vec::new();
for (name, factory) in renderers {
match factory(_device_opener) {
Ok(renderer) => return Ok(renderer),
Err(err) => {
renderer_errors.push(if !name.is_empty() {
format!("Error from {} renderer: {}", name, err).into()
} else {
"No renderers configured.".into()
});
}
}
}

result
Err(PlatformError::Other(format!(
"Could not initialize any renderer for LinuxKMS backend.\n{}",
renderer_errors.join("\n")
)))
}
}

Expand Down
2 changes: 1 addition & 1 deletion internal/backends/linuxkms/renderer/skia.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ impl i_slint_renderer_skia::software_surface::RenderBuffer for DrmDumbBufferAcce
return Ok(());
};

self.display.map_back_buffer(&mut |mut pixels| {
self.display.map_back_buffer(&mut |mut pixels, _age| {
render_callback(width, height, skia_safe::ColorType::BGRA8888, pixels.as_mut())
})
}
Expand Down
140 changes: 140 additions & 0 deletions internal/backends/linuxkms/renderer/sw.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0

//! Delegate the rendering to the [`i_slint_core::software_renderer::SoftwareRenderer`]

use i_slint_core::api::PhysicalSize as PhysicalWindowSize;
use i_slint_core::platform::PlatformError;
pub use i_slint_core::software_renderer::SoftwareRenderer;
use i_slint_core::software_renderer::{PremultipliedRgbaColor, RepaintBufferType, TargetPixel};
use std::rc::Rc;

use crate::display::{Presenter, RenderingRotation};
use crate::drmoutput::DrmOutput;

pub struct SoftwareRendererAdapter {
renderer: SoftwareRenderer,
display: Rc<crate::display::swdisplay::SoftwareBufferDisplay>,
size: PhysicalWindowSize,
}

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can't the existing argb8pixel be used?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see an argb8pixel in our code base.

I see these:

  • Rgba8Pixel: TargetPixel is not implemented for it and it's the wrong order. (Softbuffer expects 0RGB)
  • PremultipliedRgbaColor: TargetPixel is implemented for it, but it's the wrong order.

This is basically the same as in commit 46ec787 . If you have a suggestion where it could be shared, then I'm all for it. Should I introduce a "softbuffer-pixel" feature in i-slint-core perhaps? I wonder in which module to place it.


impl From<DumbBufferPixel> for PremultipliedRgbaColor {
#[inline]
fn from(pixel: DumbBufferPixel) -> Self {
let v = pixel.0;
PremultipliedRgbaColor {
red: (v >> 16) as u8,
green: (v >> 8) as u8,
blue: (v >> 0) as u8,
alpha: (v >> 24) as u8,
}
}
}

impl From<PremultipliedRgbaColor> for DumbBufferPixel {
#[inline]
fn from(pixel: PremultipliedRgbaColor) -> Self {
Self(
(pixel.alpha as u32) << 24
| ((pixel.red as u32) << 16)
| ((pixel.green as u32) << 8)
| (pixel.blue as u32),
)
}
}

impl TargetPixel for DumbBufferPixel {
fn blend(&mut self, color: PremultipliedRgbaColor) {
let mut x = PremultipliedRgbaColor::from(*self);
x.blend(color);
*self = x.into();
}

fn from_rgb(r: u8, g: u8, b: u8) -> Self {
Self(0xff000000 | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32))
}

fn background() -> Self {
Self(0)
}
}

impl SoftwareRendererAdapter {
pub fn new(
device_opener: &crate::DeviceOpener,
) -> Result<Box<dyn crate::fullscreenwindowadapter::FullscreenRenderer>, PlatformError> {
let drm_output = DrmOutput::new(device_opener)?;
let display = Rc::new(crate::display::swdisplay::SoftwareBufferDisplay::new(drm_output)?);

let (width, height) = display.drm_output.size();
let size = i_slint_core::api::PhysicalSize::new(width, height);

let renderer = Box::new(Self { renderer: SoftwareRenderer::new(), display, size });

eprintln!("Using Software renderer");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

leftover?

Copy link
Member Author

@tronical tronical Jun 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional , all the renderers currently print when they’re in use.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok. Althoug they probably shouldn't in a production binary. (it would be shown with the FPS env variable, right?)


Ok(renderer)
}
}

impl crate::fullscreenwindowadapter::FullscreenRenderer for SoftwareRendererAdapter {
fn as_core_renderer(&self) -> &dyn i_slint_core::renderer::Renderer {
&self.renderer
}

fn is_ready_to_present(&self) -> bool {
self.display.drm_output.is_ready_to_present()
}

fn render_and_present(
&self,
rotation: RenderingRotation,
_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 |mut pixels, age| {
self.renderer.set_repaint_buffer_type(match age {
1 => RepaintBufferType::ReusedBuffer,
2 => RepaintBufferType::SwappedBuffers,
_ => RepaintBufferType::NewBuffer,
});

self.renderer.set_rendering_rotation(match rotation {
RenderingRotation::NoRotation => {
i_slint_core::software_renderer::RenderingRotation::NoRotation
}
RenderingRotation::Rotate90 => {
i_slint_core::software_renderer::RenderingRotation::Rotate90
}
RenderingRotation::Rotate180 => {
i_slint_core::software_renderer::RenderingRotation::Rotate180
}
RenderingRotation::Rotate270 => {
i_slint_core::software_renderer::RenderingRotation::Rotate270
}
});

let buffer: &mut [DumbBufferPixel] = bytemuck::cast_slice_mut(pixels.as_mut());
self.renderer.render(buffer, self.size.width as usize);

Ok(())
})?;
self.display.present_with_next_frame_callback(ready_for_next_animation_frame)?;
Ok(())
}

fn size(&self) -> i_slint_core::api::PhysicalSize {
self.size
}

fn register_page_flip_handler(
&self,
event_loop_handle: crate::calloop_backend::EventLoopHandle,
) -> Result<(), PlatformError> {
self.display.drm_output.register_page_flip_handler(event_loop_handle)
}
}
2 changes: 1 addition & 1 deletion internal/backends/selector/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ renderer-femtovg = ["i-slint-backend-winit?/renderer-femtovg", "i-slint-backend-
renderer-skia = ["i-slint-backend-winit?/renderer-skia", "i-slint-backend-linuxkms?/renderer-skia"]
renderer-skia-opengl = ["i-slint-backend-winit?/renderer-skia-opengl", "i-slint-backend-linuxkms?/renderer-skia-opengl", "i-slint-renderer-skia/opengl"]
renderer-skia-vulkan = ["i-slint-backend-winit?/renderer-skia-vulkan", "i-slint-backend-linuxkms?/renderer-skia-vulkan", "i-slint-renderer-skia/vulkan"]
renderer-software = ["i-slint-backend-winit?/renderer-software", "i-slint-core/software-renderer"]
renderer-software = ["i-slint-backend-winit?/renderer-software", "i-slint-backend-linuxkms?/renderer-software", "i-slint-core/software-renderer"]

rtti = ["i-slint-core/rtti", "i-slint-backend-qt?/rtti"]
accessibility = ["i-slint-backend-winit?/accessibility"]
Expand Down
Loading