LinuxKMS: Add support for synthetic display rotations (#4166)

This patch adds support for the `SLINT_KMS_ROTATION` environment
variable, that instructs the Skia/FemtoVG renderers to rotate
the scene before rendering.
This commit is contained in:
Simon Hausmann 2023-12-18 17:49:05 +01:00 committed by GitHub
parent b8ebc084b6
commit 3b51c8e30a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 217 additions and 53 deletions

View file

@ -78,7 +78,7 @@ pub struct Backend {
&'a crate::DeviceOpener,
) -> Result<
Box<dyn crate::fullscreenwindowadapter::FullscreenRenderer>,
i_slint_core::platform::PlatformError,
PlatformError,
>,
sel_clipboard: RefCell<Option<String>>,
clipboard: RefCell<Option<String>>,
@ -152,10 +152,7 @@ impl Backend {
impl i_slint_core::platform::Platform for Backend {
fn create_window_adapter(
&self,
) -> Result<
std::rc::Rc<dyn i_slint_core::window::WindowAdapter>,
i_slint_core::platform::PlatformError,
> {
) -> Result<std::rc::Rc<dyn i_slint_core::window::WindowAdapter>, PlatformError> {
#[cfg(feature = "libseat")]
let device_accessor = |device: &std::path::Path| -> Result<Arc<dyn AsFd>, PlatformError> {
let device = self
@ -189,8 +186,17 @@ impl i_slint_core::platform::Platform for Backend {
Ok(Arc::new(device))
};
// This could be per-screen, once we support multiple outputs
let rotation =
std::env::var("SLINT_KMS_ROTATION").map_or(Ok(Default::default()), |rot_str| {
rot_str
.as_str()
.try_into()
.map_err(|e| format!("Failed to parse SLINT_KMS_ROTATION: {e}"))
})?;
let renderer = (self.renderer_factory)(&device_accessor)?;
let adapter = FullscreenWindowAdapter::new(renderer)?;
let adapter = FullscreenWindowAdapter::new(renderer, rotation)?;
*self.window.borrow_mut() = Some(adapter.clone());

View file

@ -0,0 +1,82 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
use i_slint_core::api::PhysicalSize;
pub trait Presenter {
// Present updated front-buffer to the screen
fn present(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
}
#[cfg(any(feature = "renderer-skia-opengl", feature = "renderer-femtovg"))]
pub mod egldisplay;
#[cfg(feature = "renderer-skia-vulkan")]
pub mod vulkandisplay;
/// This enum describes the way the output is supposed to be rotated to simulate
/// a screen rotation. This is implemented entirely inside the actual renderer.
#[non_exhaustive]
#[derive(Default, Copy, Clone, Eq, PartialEq, Debug)]
pub enum RenderingRotation {
/// No rotation
#[default]
NoRotation,
/// Rotate 90° to the left
Rotate90,
/// 180° rotation (upside-down)
Rotate180,
/// Rotate 90° to the right
Rotate270,
}
impl TryFrom<&str> for RenderingRotation {
type Error = String;
fn try_from(value: &str) -> Result<Self, Self::Error> {
let angle: usize = value.parse().map_err(|_| {
format!("Invalid value for rotation. Must be unsigned integral, found {value}")
})?;
Ok(match angle {
0 => Self::NoRotation,
90 => Self::Rotate90,
180 => Self::Rotate180,
270 => Self::Rotate270,
_ => {
return Err(format!(
"Invalid value for rotation. Must be one of 0, 90, 180, or 270"
))
}
})
}
}
impl RenderingRotation {
pub fn screen_size_to_rotated_window_size(&self, screen_size: PhysicalSize) -> PhysicalSize {
match self {
RenderingRotation::NoRotation | RenderingRotation::Rotate180 => screen_size,
RenderingRotation::Rotate90 | RenderingRotation::Rotate270 => {
PhysicalSize::new(screen_size.height, screen_size.width)
}
}
}
pub fn degrees(&self) -> f32 {
match self {
RenderingRotation::NoRotation => 0.,
RenderingRotation::Rotate90 => 90.,
RenderingRotation::Rotate180 => 180.,
RenderingRotation::Rotate270 => 270.,
}
}
pub fn translation_after_rotation(&self, screen_size: PhysicalSize) -> (f32, f32) {
match self {
RenderingRotation::NoRotation => (0., 0.),
RenderingRotation::Rotate90 => (0., -(screen_size.width as f32)),
RenderingRotation::Rotate180 => {
(-(screen_size.width as f32), -(screen_size.height as f32))
}
RenderingRotation::Rotate270 => (-(screen_size.height as f32), 0.),
}
}
}

View file

@ -15,10 +15,13 @@ use i_slint_core::slice::Slice;
use i_slint_core::Property;
use i_slint_core::{platform::PlatformError, window::WindowAdapter};
use crate::display::RenderingRotation;
pub trait FullscreenRenderer {
fn as_core_renderer(&self) -> &dyn i_slint_core::renderer::Renderer;
fn render_and_present(
&self,
rotation: RenderingRotation,
draw_mouse_cursor_callback: &dyn Fn(&mut dyn ItemRenderer),
) -> Result<(), PlatformError>;
fn size(&self) -> PhysicalWindowSize;
@ -28,6 +31,7 @@ pub struct FullscreenWindowAdapter {
window: i_slint_core::api::Window,
renderer: Box<dyn FullscreenRenderer>,
needs_redraw: Cell<bool>,
rotation: RenderingRotation,
}
impl WindowAdapter for FullscreenWindowAdapter {
@ -36,7 +40,7 @@ impl WindowAdapter for FullscreenWindowAdapter {
}
fn size(&self) -> i_slint_core::api::PhysicalSize {
self.renderer.size()
self.rotation.screen_size_to_rotated_window_size(self.renderer.size())
}
fn renderer(&self) -> &dyn i_slint_core::renderer::Renderer {
@ -64,11 +68,15 @@ impl WindowAdapter for FullscreenWindowAdapter {
}
impl FullscreenWindowAdapter {
pub fn new(renderer: Box<dyn FullscreenRenderer>) -> Result<Rc<Self>, PlatformError> {
pub fn new(
renderer: Box<dyn FullscreenRenderer>,
rotation: RenderingRotation,
) -> Result<Rc<Self>, PlatformError> {
Ok(Rc::<FullscreenWindowAdapter>::new_cyclic(|self_weak| FullscreenWindowAdapter {
window: i_slint_core::api::Window::new(self_weak.clone()),
renderer,
needs_redraw: Cell::new(true),
rotation,
}))
}
@ -77,7 +85,7 @@ impl FullscreenWindowAdapter {
mouse_position: Pin<&Property<Option<LogicalPosition>>>,
) -> Result<(), PlatformError> {
if self.needs_redraw.replace(false) {
self.renderer.render_and_present(&|item_renderer| {
self.renderer.render_and_present(self.rotation, &|item_renderer| {
if let Some(mouse_position) = mouse_position.get() {
item_renderer.save_state();
item_renderer.translate(

View file

@ -15,17 +15,7 @@ type DeviceOpener<'a> = dyn Fn(&std::path::Path) -> Result<std::sync::Arc<dyn As
+ 'a;
#[cfg(target_os = "linux")]
mod display {
pub trait Presenter {
// Present updated front-buffer to the screen
fn present(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
}
#[cfg(any(feature = "renderer-skia-opengl", feature = "renderer-femtovg"))]
pub mod egldisplay;
#[cfg(feature = "renderer-skia-vulkan")]
pub mod vulkandisplay;
}
mod display;
#[cfg(target_os = "linux")]
mod renderer {

View file

@ -18,7 +18,7 @@ use glutin::{
surface::{SurfaceAttributesBuilder, WindowSurface},
};
use crate::display::{egldisplay::EglDisplay, Presenter};
use crate::display::{egldisplay::EglDisplay, Presenter, RenderingRotation};
pub struct FemtoVGRendererAdapter {
renderer: i_slint_renderer_femtovg::FemtoVGRenderer,
@ -143,16 +143,11 @@ unsafe impl i_slint_renderer_femtovg::OpenGLInterface for GlContextWrapper {
fn resize(
&self,
width: NonZeroU32,
height: NonZeroU32,
_width: NonZeroU32,
_height: NonZeroU32,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
if self.egl_display.size.height != height.get()
|| self.egl_display.size.width != width.get()
{
Err("Resizing a fullscreen window is not supported".into())
} else {
Ok(())
}
// Ignore resize requests
Ok(())
}
fn get_proc_address(&self, name: &std::ffi::CStr) -> *const std::ffi::c_void {
@ -187,11 +182,17 @@ impl crate::fullscreenwindowadapter::FullscreenRenderer for FemtoVGRendererAdapt
}
fn render_and_present(
&self,
rotation: RenderingRotation,
draw_mouse_cursor_callback: &dyn Fn(&mut dyn ItemRenderer),
) -> Result<(), PlatformError> {
self.renderer.render_with_post_callback(Some(&|item_renderer| {
draw_mouse_cursor_callback(item_renderer);
}))
self.renderer.render_transformed_with_post_callback(
rotation.degrees(),
rotation.translation_after_rotation(self.size),
self.size,
Some(&|item_renderer| {
draw_mouse_cursor_callback(item_renderer);
}),
)
}
fn size(&self) -> i_slint_core::api::PhysicalSize {
self.size

View file

@ -1,6 +1,7 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
use crate::display::RenderingRotation;
use i_slint_core::api::PhysicalSize as PhysicalWindowSize;
use i_slint_core::item_rendering::ItemRenderer;
use i_slint_core::platform::PlatformError;
@ -95,11 +96,17 @@ impl crate::fullscreenwindowadapter::FullscreenRenderer for SkiaRendererAdapter
}
fn render_and_present(
&self,
rotation: RenderingRotation,
draw_mouse_cursor_callback: &dyn Fn(&mut dyn ItemRenderer),
) -> Result<(), PlatformError> {
self.renderer.render_with_post_callback(Some(&|item_renderer| {
draw_mouse_cursor_callback(item_renderer);
}))?;
self.renderer.render_transformed_with_post_callback(
rotation.degrees(),
rotation.translation_after_rotation(self.size),
self.size,
Some(&|item_renderer| {
draw_mouse_cursor_callback(item_renderer);
}),
)?;
if let Some(presenter) = self.presenter.as_ref() {
presenter.present()?;
}