diff --git a/api/rs/slint/lib.rs b/api/rs/slint/lib.rs index 8f06888d28c..66a5b7f7c06 100644 --- a/api/rs/slint/lib.rs +++ b/api/rs/slint/lib.rs @@ -409,8 +409,6 @@ macro_rules! init_translations { pub mod platform { pub use i_slint_core::platform::*; - pub use i_slint_backend_selector::PlatformBuilder; - /// This module contains the [`femtovg_renderer::FemtoVGRenderer`] and related types. /// /// It is only enabled when the `renderer-femtovg` Slint feature is enabled. @@ -430,6 +428,8 @@ pub mod platform { ))] pub mod android; +pub use i_slint_backend_selector::api::*; + /// Helper type that helps checking that the generated code is generated for the right version #[doc(hidden)] #[allow(non_camel_case_types)] diff --git a/examples/opengl_texture/main.rs b/examples/opengl_texture/main.rs index ce42f813159..c965acb650b 100644 --- a/examples/opengl_texture/main.rs +++ b/examples/opengl_texture/main.rs @@ -398,11 +398,10 @@ impl DemoRenderer { } fn main() { - let platform = slint::platform::PlatformBuilder::new() - .with_opengl_api(slint::OpenGLAPI::GLES(None)) - .build() + slint::BackendSelector::new() + .require_opengl_es() + .select() .expect("Unable to create Slint backend with OpenGL ES renderer"); - slint::platform::set_platform(platform).unwrap(); let app = App::new().unwrap(); diff --git a/examples/opengl_underlay/main.rs b/examples/opengl_underlay/main.rs index 07e89223629..73aed4976cb 100644 --- a/examples/opengl_underlay/main.rs +++ b/examples/opengl_underlay/main.rs @@ -176,11 +176,10 @@ pub fn main() { #[cfg(all(debug_assertions, target_arch = "wasm32"))] console_error_panic_hook::set_once(); - let platform = slint::platform::PlatformBuilder::new() - .with_opengl_api(slint::OpenGLAPI::GLES(None)) - .build() + slint::BackendSelector::new() + .require_opengl_es() + .select() .expect("Unable to create Slint backend with OpenGL ES renderer"); - slint::platform::set_platform(platform).unwrap(); let app = App::new().unwrap(); diff --git a/internal/backends/selector/api.rs b/internal/backends/selector/api.rs new file mode 100644 index 00000000000..bd77f54d77c --- /dev/null +++ b/internal/backends/selector/api.rs @@ -0,0 +1,168 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 + +use alloc::boxed::Box; +use alloc::{format, string::String}; + +use i_slint_core::api::PlatformError; +use i_slint_core::graphics::{RequestedGraphicsAPI, RequestedOpenGLVersion}; + +/// Use the BackendSelector to configure one of Slint's built-in backends with a renderer +/// to accomodate specific needs of your application. This is a programmatic substitute for +/// the `SLINT_BACKEND` environment variable. +/// +/// For example, to configure Slint to use a renderer that supports OpenGL ES 3.0, configure +/// the `BackendSelector` as follows: +/// ```rust,no_run +/// # use i_slint_backend_selector::api::BackendSelector; +/// let selector = BackendSelector::new().require_opengl_es_with_version(3, 0); +/// if let Err(err) = selector.select() { +/// eprintln!("Error selecting backend with OpenGL ES support: {err}"); +/// } +/// ``` +pub struct BackendSelector { + requested_graphics_api: Option, + backend: Option, + renderer: Option, +} + +impl BackendSelector { + /// Creates a new BackendSelector. + #[must_use] + pub fn new() -> BackendSelector { + BackendSelector { requested_graphics_api: None, backend: None, renderer: None } + } + + /// Adds the requirement to the selector that the backend must render with OpenGL ES + /// and the specified major and minor version. + #[must_use] + pub fn require_opengl_es_with_version(mut self, major: u8, minor: u8) -> Self { + self.requested_graphics_api = + Some(RequestedOpenGLVersion::OpenGLES(Some((major, minor))).into()); + self + } + + /// Adds the requirement to the selector that the backend must render with OpenGL ES. + #[must_use] + pub fn require_opengl_es(mut self) -> Self { + self.requested_graphics_api = Some(RequestedOpenGLVersion::OpenGLES(None).into()); + self + } + + /// Adds the requirement to the selector that the backend must render with OpenGL. + #[must_use] + pub fn require_opengl(mut self) -> Self { + self.requested_graphics_api = Some(RequestedOpenGLVersion::OpenGL(None).into()); + self + } + + /// Adds the requirement to the selector that the backend must render with OpenGL + /// and the specified major and minor version. + #[must_use] + pub fn require_opengl_with_version(mut self, major: u8, minor: u8) -> Self { + self.requested_graphics_api = + Some(RequestedOpenGLVersion::OpenGL(Some((major, minor))).into()); + self + } + + /// Adds the requirement to the selector that the backend must render with Apple's Metal framework. + #[must_use] + pub fn require_metal(mut self) -> Self { + self.requested_graphics_api = Some(RequestedGraphicsAPI::Metal); + self + } + + /// Adds the requirement to the selector that the backend must render with Vulkan. + #[must_use] + pub fn require_vulkan(mut self) -> Self { + self.requested_graphics_api = Some(RequestedGraphicsAPI::Vulkan); + self + } + + /// Adds the requirement to the selector that the backend must render with Direct 3D. + #[must_use] + pub fn require_d3d(mut self) -> Self { + self.requested_graphics_api = Some(RequestedGraphicsAPI::Direct3D); + self + } + + /// Adds the requirement that the selected renderer must match the given name. This is + /// equivalent to setting the `SLINT_BACKEND=name` environment variable and requires + /// that the corresponding renderer feature is enabled. For example, to select the Skia renderer, + /// enable the `renderer-skia` feature and call this function with `skia` as argument. + #[must_use] + pub fn renderer_name(mut self, name: String) -> Self { + self.renderer = Some(name); + self + } + + /// Adds the requirement that the selected backend must match the given name. This is + /// equivalent to setting the `SLINT_BACKEND=name` environment variable and requires + /// that the corresponding backend feature is enabled. For example, to select the winit backend, + /// enable the `backend-winit` feature and call this function with `winit` as argument. + #[must_use] + pub fn backend_name(mut self, name: String) -> Self { + self.backend = Some(name); + self + } + + /// Completes the backend selection process and tries to combine with specified requirements + /// with the different backends and renderers enabled at compile time. On success, the selected + /// backend is automatically set to be active. Returns an error if the requirements could not be met. + pub fn select(self) -> Result<(), PlatformError> { + let backend_name = self.backend.as_deref().unwrap_or(super::DEFAULT_BACKEND_NAME); + + let backend: Box = match backend_name { + #[cfg(all(feature = "i-slint-backend-linuxkms", target_os = "linux"))] + "linuxkms" => { + if self.requested_graphics_api.is_some() { + return Err("The linuxkms backend does not implement renderer selection by graphics API".into()); + } + + Box::new(i_slint_backend_linuxkms::Backend::new_with_renderer_by_name( + self.renderer.as_deref(), + )?) + } + #[cfg(feature = "i-slint-backend-winit")] + "winit" => { + let builder = i_slint_backend_winit::Backend::builder(); + + let builder = match self.requested_graphics_api { + Some(api) => builder.request_graphics_api(api), + None => builder, + }; + + let builder = match self.renderer { + Some(name) => builder.with_renderer_name(name), + None => builder, + }; + + Box::new(builder.build()?) + } + #[cfg(feature = "i-slint-backend-qt")] + "qt" => { + if self.requested_graphics_api.is_some() { + return Err( + "The qt backend does not implement renderer selection by graphics API" + .into(), + ); + } + if self.renderer.is_some() { + return Err( + "The qt backend does not implement renderer selection by name".into() + ); + } + Box::new(i_slint_backend_qt::Backend::new()) + } + requested_backend @ _ => { + return Err(format!( + "{requested_backend} backend requested but it is not available" + ) + .into()); + } + }; + + i_slint_core::platform::set_platform(backend) + .map_err(|set_platform_error| PlatformError::SetPlatformError(set_platform_error)) + } +} diff --git a/internal/backends/selector/lib.rs b/internal/backends/selector/lib.rs index 766794de481..b2fc0801d14 100644 --- a/internal/backends/selector/lib.rs +++ b/internal/backends/selector/lib.rs @@ -18,9 +18,7 @@ extern crate alloc; use alloc::boxed::Box; use i_slint_core::platform::Platform; use i_slint_core::platform::PlatformError; -use i_slint_core::OpenGLAPI; use i_slint_core::SlintContext; -use i_slint_core::SlintRenderer; #[cfg(all(feature = "i-slint-backend-qt", not(no_qt), not(target_os = "android")))] fn create_qt_backend() -> Result, PlatformError> { @@ -39,78 +37,18 @@ fn create_linuxkms_backend() -> Result, PlatformErro cfg_if::cfg_if! { if #[cfg(target_os = "android")] { + const DEFAULT_BACKEND_NAME: &'static str = ""; } else if #[cfg(all(feature = "i-slint-backend-qt", not(no_qt)))] { use i_slint_backend_qt as default_backend; + const DEFAULT_BACKEND_NAME: &'static str = "qt"; } else if #[cfg(feature = "i-slint-backend-winit")] { use i_slint_backend_winit as default_backend; + const DEFAULT_BACKEND_NAME: &'static str = "winit"; } else if #[cfg(all(feature = "i-slint-backend-linuxkms", target_os = "linux"))] { use i_slint_backend_linuxkms as default_backend; + const DEFAULT_BACKEND_NAME: &'static str = "linuxkms"; } else { - - } -} - -pub struct PlatformBuilder { - opengl_api: Option, - renderer: Option, -} - -impl PlatformBuilder { - /// Creates a new PlatformBuilder for configuring aspects of the Platform. - pub fn new() -> PlatformBuilder { - PlatformBuilder { opengl_api: None, renderer: None } - } - - /// Configures this builder to use the specified OpenGL API when building the platform later. - pub fn with_opengl_api(mut self, opengl_api: OpenGLAPI) -> Self { - self.opengl_api = Some(opengl_api); - self - } - - /// Configures this builder to use the specified renderer when building the platform later. - pub fn with_renderer(mut self, renderer: SlintRenderer) -> Self { - self.renderer = Some(renderer); - self - } - - /// Builds the platform with the parameters configured previously. Set the resulting platform - /// with `slint::platform::set_platform()`: - /// - /// # Example - /// - /// ```rust,no_run - /// use i_slint_core::OpenGLAPI; - /// use i_slint_core::platform; - /// use i_slint_backend_selector::PlatformBuilder; - /// - /// let platform = PlatformBuilder::new() - /// .with_opengl_api(OpenGLAPI::GL(None)) - /// .build() - /// .unwrap(); - /// platform::set_platform(platform).unwrap(); - /// ``` - pub fn build(self) -> Result, PlatformError> { - cfg_if::cfg_if! { - if #[cfg(feature = "i-slint-backend-winit")] { - let builder = i_slint_backend_winit::Backend::builder().with_allow_fallback(false); - - let builder = match self.opengl_api { - Some(api) => builder.with_opengl_api(api), - None => builder, - }; - - let builder = match self.renderer { - Some(SlintRenderer::FemtoVG) => builder.with_renderer_name("femtovg"), - Some(SlintRenderer::Skia) => builder.with_renderer_name("skia"), - Some(SlintRenderer::Software) => builder.with_renderer_name("software"), - None => builder, - }; - - Ok(Box::new(builder.build()?)) - } else { - Err(PlatformError::NoPlatform) - } - } + const DEFAULT_BACKEND_NAME: &'static str = ""; } } @@ -223,3 +161,5 @@ pub fn with_global_context(f: impl FnOnce(&SlintContext) -> R) -> Result Result<(), PlatformError>; @@ -60,7 +60,7 @@ mod renderer { fn resume( &self, window_attributes: winit::window::WindowAttributes, - opengl_api: Option, + requested_graphics_api: Option, ) -> Result, PlatformError>; fn is_suspended(&self) -> bool; @@ -121,19 +121,6 @@ fn default_renderer_factory() -> Box { } } -#[cfg(supports_opengl)] -fn default_opengl_renderer_factory() -> Box { - cfg_if::cfg_if! { - if #[cfg(enable_skia_renderer)] { - renderer::skia::WinitSkiaRenderer::new_opengl_suspended() - } else if #[cfg(feature = "renderer-femtovg")] { - renderer::femtovg::GlutinFemtoVGRenderer::new_suspended() - } else { - compile_error!("Please select a feature to build with the winit OpenGL backend: `renderer-femtovg` or `renderer-skia-opengl`"); - } - } -} - fn try_create_window_with_fallback_renderer( attrs: winit::window::WindowAttributes, _proxy: &winit::event_loop::EventLoopProxy, @@ -177,7 +164,7 @@ pub mod native_widgets {} /// and build the backend using [`Self::build`]. pub struct BackendBuilder { allow_fallback: bool, - opengl_api: Option, + requested_graphics_api: Option, window_attributes_hook: Option winit::window::WindowAttributes>>, renderer_name: Option, @@ -185,20 +172,16 @@ pub struct BackendBuilder { } impl BackendBuilder { - /// Allows or disallows a fallback backend if no backend is matched. - pub fn with_allow_fallback(mut self, allow_fallback: bool) -> Self { - self.allow_fallback = allow_fallback; - self - } - - /// Configures this builder to use the specified OpenGL API when building the backend later. - pub fn with_opengl_api(mut self, opengl_api: OpenGLAPI) -> Self { - self.opengl_api = Some(opengl_api); + /// Configures this builder to require a renderer that supports the specified graphics API. + #[must_use] + pub fn request_graphics_api(mut self, graphics_api: RequestedGraphicsAPI) -> Self { + self.requested_graphics_api = Some(graphics_api); self } /// Configures this builder to use the specified renderer name when building the backend later. /// Pass `renderer-software` for example to configure the backend to use the Slint software renderer. + #[must_use] pub fn with_renderer_name(mut self, name: impl Into) -> Self { self.renderer_name = Some(name.into()); self @@ -217,6 +200,7 @@ impl BackendBuilder { /// .unwrap(); /// slint::platform::set_platform(Box::new(backend)); /// ``` + #[must_use] pub fn with_window_attributes_hook( mut self, hook: impl Fn(winit::window::WindowAttributes) -> winit::window::WindowAttributes + 'static, @@ -227,6 +211,7 @@ impl BackendBuilder { /// Configures this builder to use the specified event loop builder when creating the event /// loop during a subsequent call to [`Self::build`]. + #[must_use] pub fn with_event_loop_builder( mut self, event_loop_builder: winit::event_loop::EventLoopBuilder, @@ -264,15 +249,28 @@ impl BackendBuilder { *loop_instance.borrow_mut() = Some(nre); }); - let renderer_factory_fn = match (self.renderer_name.as_deref(), self.opengl_api.as_ref()) { + let renderer_factory_fn = match ( + self.renderer_name.as_deref(), + self.requested_graphics_api.as_ref(), + ) { #[cfg(feature = "renderer-femtovg")] - (Some("gl"), _) | (Some("femtovg"), _) => { + (Some("gl"), maybe_graphics_api @ _) | (Some("femtovg"), maybe_graphics_api @ _) => { + // If a graphics API was requested, double check that it's GL. FemtoVG doesn't support Metal, etc. + if let Some(api) = maybe_graphics_api { + RequestedOpenGLVersion::try_from(api.clone())?; + } renderer::femtovg::GlutinFemtoVGRenderer::new_suspended } #[cfg(enable_skia_renderer)] - (Some("skia"), None) => renderer::skia::WinitSkiaRenderer::new_suspended, + (Some("skia"), maybe_graphics_api) => { + renderer::skia::WinitSkiaRenderer::factory_for_graphics_api(maybe_graphics_api)? + } #[cfg(all(enable_skia_renderer, supports_opengl))] - (Some("skia-opengl"), _) | (Some("skia"), Some(_)) => { + (Some("skia-opengl"), maybe_graphics_api @ _) => { + // If a graphics API was requested, double check that it's GL. FemtoVG doesn't support Metal, etc. + if let Some(api) = maybe_graphics_api { + RequestedOpenGLVersion::try_from(api.clone())?; + } renderer::skia::WinitSkiaRenderer::new_opengl_suspended } #[cfg(all(enable_skia_renderer, not(target_os = "android")))] @@ -295,19 +293,23 @@ impl BackendBuilder { return Err(PlatformError::NoPlatform); } } - (None, Some(_)) => { + (None, Some(requested_graphics_api)) => { cfg_if::cfg_if! { - if #[cfg(supports_opengl)] { - default_opengl_renderer_factory + if #[cfg(enable_skia_renderer)] { + renderer::skia::WinitSkiaRenderer::factory_for_graphics_api(Some(requested_graphics_api))? + } else if #[cfg(feature = "renderer-femtovg")] { + // If a graphics API was requested, double check that it's GL. FemtoVG doesn't support Metal, etc. + RequestedOpenGLVersion::try_from(requested_graphics_api.clone())?; + renderer::femtovg::GlutinFemtoVGRenderer::new_suspended } else { - return Err(PlatformError::NoPlatform); + return Err(format!("Graphics API use requested by the compile-time enabled renderers don't support that")) } } } }; Ok(Backend { - opengl_api: self.opengl_api, + requested_graphics_api: self.requested_graphics_api, renderer_factory_fn, event_loop_state: Default::default(), window_attributes_hook: self.window_attributes_hook, @@ -328,7 +330,7 @@ impl BackendBuilder { /// slint::platform::set_platform(Box::new(Backend::new().unwrap())); /// ``` pub struct Backend { - opengl_api: Option, + requested_graphics_api: Option, renderer_factory_fn: fn() -> Box, event_loop_state: std::cell::RefCell>, proxy: winit::event_loop::EventLoopProxy, @@ -381,7 +383,7 @@ impl Backend { pub fn builder() -> BackendBuilder { BackendBuilder { allow_fallback: true, - opengl_api: None, + requested_graphics_api: None, window_attributes_hook: None, renderer_name: None, event_loop_builder: None, @@ -434,7 +436,7 @@ impl i_slint_core::platform::Platform for Backend { let adapter = WinitWindowAdapter::new( (self.renderer_factory_fn)(), attrs.clone(), - self.opengl_api.clone(), + self.requested_graphics_api.clone(), #[cfg(enable_accesskit)] self.proxy.clone(), ) diff --git a/internal/backends/winit/renderer/femtovg.rs b/internal/backends/winit/renderer/femtovg.rs index 691c2eee169..bb0bf30d32f 100644 --- a/internal/backends/winit/renderer/femtovg.rs +++ b/internal/backends/winit/renderer/femtovg.rs @@ -5,7 +5,7 @@ use std::cell::Cell; use std::rc::Rc; use i_slint_core::renderer::Renderer; -use i_slint_core::{platform::PlatformError, OpenGLAPI}; +use i_slint_core::{graphics::RequestedGraphicsAPI, platform::PlatformError}; use i_slint_renderer_femtovg::{FemtoVGRenderer, FemtoVGRendererExt}; #[cfg(target_arch = "wasm32")] @@ -42,14 +42,16 @@ impl super::WinitCompatibleRenderer for GlutinFemtoVGRenderer { fn resume( &self, window_attributes: winit::window::WindowAttributes, - #[cfg_attr(target_arch = "wasm32", allow(unused_variables))] opengl_api: Option, + #[cfg_attr(target_arch = "wasm32", allow(unused_variables))] requested_graphics_api: Option< + RequestedGraphicsAPI, + >, ) -> Result, PlatformError> { #[cfg(not(target_arch = "wasm32"))] let (winit_window, opengl_context) = crate::event_loop::with_window_target(|event_loop| { Ok(glcontext::OpenGLContext::new_context( window_attributes, event_loop.event_loop(), - opengl_api, + requested_graphics_api.map(TryInto::try_into).transpose()?, )?) })?; diff --git a/internal/backends/winit/renderer/femtovg/glcontext.rs b/internal/backends/winit/renderer/femtovg/glcontext.rs index bbfbdbb391f..ae381ca77c1 100644 --- a/internal/backends/winit/renderer/femtovg/glcontext.rs +++ b/internal/backends/winit/renderer/femtovg/glcontext.rs @@ -10,7 +10,7 @@ use glutin::{ prelude::*, surface::{SurfaceAttributesBuilder, WindowSurface}, }; -use i_slint_core::{api::APIVersion, platform::PlatformError, OpenGLAPI}; +use i_slint_core::{graphics::RequestedOpenGLVersion, platform::PlatformError}; use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; pub struct OpenGLContext { @@ -58,7 +58,7 @@ impl OpenGLContext { pub(crate) fn new_context( window_attributes: winit::window::WindowAttributes, event_loop: crate::event_loop::ActiveOrInactiveEventLoop<'_>, - opengl_api: Option, + requested_opengl_version: Option, ) -> Result<(Rc, Self), PlatformError> { let config_template_builder = glutin::config::ConfigTemplateBuilder::new(); @@ -122,23 +122,19 @@ impl OpenGLContext { })? .map(|h| h.as_raw()); - let opengl_api = - opengl_api.unwrap_or(OpenGLAPI::GLES(Some(APIVersion { major: 2, minor: 0 }))); - let preferred_context_attributes = match opengl_api { - OpenGLAPI::GL(version) => { - let version = version.map(|version| glutin::context::Version { - major: version.major, - minor: version.minor, - }); + let requested_opengl_version = + requested_opengl_version.unwrap_or(RequestedOpenGLVersion::OpenGLES(Some((2, 0)))); + let preferred_context_attributes = match requested_opengl_version { + RequestedOpenGLVersion::OpenGL(version) => { + let version = + version.map(|(major, minor)| glutin::context::Version { major, minor }); ContextAttributesBuilder::new() .with_context_api(ContextApi::OpenGl(version)) .build(raw_window_handle) } - OpenGLAPI::GLES(version) => { - let version = version.map(|version| glutin::context::Version { - major: version.major, - minor: version.minor, - }); + RequestedOpenGLVersion::OpenGLES(version) => { + let version = + version.map(|(major, minor)| glutin::context::Version { major, minor }); ContextAttributesBuilder::new() .with_context_api(ContextApi::Gles(version)) diff --git a/internal/backends/winit/renderer/skia.rs b/internal/backends/winit/renderer/skia.rs index a8204cb519a..c65ba8ac211 100644 --- a/internal/backends/winit/renderer/skia.rs +++ b/internal/backends/winit/renderer/skia.rs @@ -5,8 +5,8 @@ use std::cell::Cell; use std::rc::Rc; use crate::winitwindowadapter::physical_size_to_slint; +use i_slint_core::graphics::RequestedGraphicsAPI; use i_slint_core::platform::PlatformError; -use i_slint_core::OpenGLAPI; pub struct WinitSkiaRenderer { renderer: i_slint_renderer_skia::SkiaRenderer, @@ -36,6 +36,75 @@ impl WinitSkiaRenderer { suspended: Default::default(), }) } + + #[cfg(target_vendor = "apple")] + pub fn new_metal_suspended() -> Box { + Box::new(Self { + renderer: i_slint_renderer_skia::SkiaRenderer::default_metal(), + suspended: Default::default(), + }) + } + + #[cfg(feature = "renderer-skia-vulkan")] + pub fn new_vulkan_suspended() -> Box { + Box::new(Self { + renderer: i_slint_renderer_skia::SkiaRenderer::default_vulkan(), + suspended: Default::default(), + }) + } + + #[cfg(target_family = "windows")] + pub fn new_direct3d_suspended() -> Box { + Box::new(Self { + renderer: i_slint_renderer_skia::SkiaRenderer::default_direct3d(), + suspended: Default::default(), + }) + } + + pub fn factory_for_graphics_api( + requested_graphics_api: Option<&RequestedGraphicsAPI>, + ) -> Result Box, PlatformError> { + match requested_graphics_api { + Some(api) => { + match api { + RequestedGraphicsAPI::OpenGL(_) => { + #[cfg(not(target_os = "ios"))] + return Ok(Self::new_opengl_suspended); + #[cfg(target_os = "ios")] + return Err(format!( + "OpenGL rendering requested but this is not supported on iOS" + ) + .into()); + } + RequestedGraphicsAPI::Metal => { + #[cfg(target_vendor = "apple")] + return Ok(Self::new_metal_suspended); + #[cfg(not(target_vendor = "apple"))] + return Err(format!("Metal rendering requested but this is only supported on Apple platforms").into()); + } + RequestedGraphicsAPI::Vulkan => { + #[cfg(feature = "renderer-skia-vulkan")] + return Ok(Self::new_vulkan_suspended); + #[cfg(not(feature = "renderer-skia-vulkan"))] + return Err(format!( + "Vulkan rendering requested but renderer-skia-vulkan is not enabled" + ) + .into()); + } + RequestedGraphicsAPI::Direct3D => { + #[cfg(target_family = "windows")] + return Ok(Self::new_direct3d_suspended); + #[cfg(not(target_family = "windows"))] + return Err(format!( + "Direct3D rendering requested but this is only supported on Windows" + ) + .into()); + } + } + } + None => Ok(Self::new_suspended), + } + } } impl super::WinitCompatibleRenderer for WinitSkiaRenderer { @@ -56,7 +125,7 @@ impl super::WinitCompatibleRenderer for WinitSkiaRenderer { fn resume( &self, window_attributes: winit::window::WindowAttributes, - opengl_api: Option, + requested_graphics_api: Option, ) -> Result, PlatformError> { let winit_window = Rc::new(crate::event_loop::with_window_target(|event_loop| { event_loop.create_window(window_attributes).map_err(|winit_os_error| { @@ -71,7 +140,7 @@ impl super::WinitCompatibleRenderer for WinitSkiaRenderer { winit_window.clone(), winit_window.clone(), physical_size_to_slint(&size), - opengl_api, + requested_graphics_api, )?; self.renderer.set_pre_present_callback(Some(Box::new({ diff --git a/internal/backends/winit/renderer/sw.rs b/internal/backends/winit/renderer/sw.rs index 9120c17d5ea..86ff42eeb38 100644 --- a/internal/backends/winit/renderer/sw.rs +++ b/internal/backends/winit/renderer/sw.rs @@ -8,7 +8,7 @@ use core::ops::DerefMut; use i_slint_core::platform::PlatformError; pub use i_slint_core::software_renderer::SoftwareRenderer; use i_slint_core::software_renderer::{PremultipliedRgbaColor, RepaintBufferType, TargetPixel}; -use i_slint_core::{graphics::Rgb8Pixel, OpenGLAPI}; +use i_slint_core::{graphics::RequestedGraphicsAPI, graphics::Rgb8Pixel}; use std::{cell::RefCell, rc::Rc}; use super::WinitCompatibleRenderer; @@ -173,7 +173,7 @@ impl super::WinitCompatibleRenderer for WinitSoftwareRenderer { fn resume( &self, window_attributes: winit::window::WindowAttributes, - _opengl_api: Option, + _requested_graphics_api: Option, ) -> Result, PlatformError> { let winit_window = crate::event_loop::with_window_target(|event_loop| { event_loop.create_window(window_attributes).map_err(|winit_os_error| { diff --git a/internal/backends/winit/winitwindowadapter.rs b/internal/backends/winit/winitwindowadapter.rs index cfcc14d0780..a095b375856 100644 --- a/internal/backends/winit/winitwindowadapter.rs +++ b/internal/backends/winit/winitwindowadapter.rs @@ -36,7 +36,7 @@ use corelib::platform::{PlatformError, WindowEvent}; use corelib::window::{WindowAdapter, WindowAdapterInternal, WindowInner}; use corelib::Property; use corelib::{graphics::*, Coord}; -use i_slint_core::{self as corelib, OpenGLAPI}; +use i_slint_core::{self as corelib, graphics::RequestedGraphicsAPI}; use once_cell::unsync::OnceCell; #[cfg(enable_accesskit)] use winit::event_loop::EventLoopProxy; @@ -258,7 +258,7 @@ pub struct WinitWindowAdapter { fullscreen: Cell, pub(crate) renderer: Box, - opengl_api: Option, + requested_graphics_api: Option, /// We cache the size because winit_window.inner_size() can return different value between calls (eg, on X11) /// And we wan see the newer value before the Resized event was received, leading to inconsistencies size: Cell, @@ -297,7 +297,7 @@ impl WinitWindowAdapter { pub(crate) fn new( renderer: Box, window_attributes: winit::window::WindowAttributes, - opengl_api: Option, + requested_graphics_api: Option, #[cfg(enable_accesskit)] proxy: EventLoopProxy, ) -> Result, PlatformError> { let self_rc = Rc::new_cyclic(|self_weak| Self { @@ -317,7 +317,7 @@ impl WinitWindowAdapter { has_explicit_size: Default::default(), pending_resize_event_after_show: Default::default(), renderer, - opengl_api, + requested_graphics_api, #[cfg(target_arch = "wasm32")] virtual_keyboard_helper: Default::default(), #[cfg(enable_accesskit)] @@ -371,7 +371,8 @@ impl WinitWindowAdapter { let mut winit_window_or_none = self.winit_window_or_none.borrow_mut(); - let winit_window = self.renderer.resume(window_attributes, self.opengl_api.clone())?; + let winit_window = + self.renderer.resume(window_attributes, self.requested_graphics_api.clone())?; *winit_window_or_none = WinitWindowOrNone::HasWindow { window: winit_window.clone(), diff --git a/internal/core/api.rs b/internal/core/api.rs index c82813248a7..2be58e0b046 100644 --- a/internal/core/api.rs +++ b/internal/core/api.rs @@ -283,35 +283,6 @@ impl<'a> core::fmt::Debug for GraphicsAPI<'a> { } } -/// API Version -#[derive(Debug, Clone, PartialEq)] -pub struct APIVersion { - /// Major API version - pub major: u8, - /// Minor API version - pub minor: u8, -} - -/// This enum specifies which OpenGL API should be used. -#[derive(Debug, Clone, PartialEq)] -pub enum OpenGLAPI { - /// OpenGL - GL(Option), - /// OpenGL ES - GLES(Option), -} - -/// This enum specifies which renderer should be used. -#[derive(Debug, Clone, PartialEq)] -pub enum SlintRenderer { - /// The FemtoVG renderer - FemtoVG, - /// The Skia renderer - Skia, - /// The software renderer - Software, -} - /// This enum describes the different rendering states, that will be provided /// to the parameter of the callback for `set_rendering_notifier` on the `slint::Window`. #[derive(Debug, Clone)] diff --git a/internal/core/graphics.rs b/internal/core/graphics.rs index 5664ef5617e..72a841b6e10 100644 --- a/internal/core/graphics.rs +++ b/internal/core/graphics.rs @@ -11,11 +11,14 @@ created by the backend in a type-erased manner. */ extern crate alloc; +use crate::api::PlatformError; use crate::lengths::LogicalLength; use crate::Coord; use crate::SharedString; #[cfg(not(feature = "std"))] use alloc::boxed::Box; +#[cfg(not(feature = "std"))] +use alloc::format; pub use euclid; /// 2D Rectangle @@ -165,6 +168,55 @@ impl FontRequest { } } +/// Internal enum to specify which version of OpenGL to request +/// from the windowing system. +#[derive(Debug, Clone, PartialEq)] +pub enum RequestedOpenGLVersion { + /// OpenGL + OpenGL(Option<(u8, u8)>), + /// OpenGL ES + OpenGLES(Option<(u8, u8)>), +} + +/// Internal enum specify which graphics API should be used, when +/// the backend selector requests that from a built-in backend. +#[derive(Debug, Clone, PartialEq)] +pub enum RequestedGraphicsAPI { + /// OpenGL (ES) + OpenGL(RequestedOpenGLVersion), + /// Metal + Metal, + /// Vulkan + Vulkan, + /// Direct 3D + Direct3D, +} + +impl TryFrom for RequestedOpenGLVersion { + type Error = PlatformError; + + fn try_from(requested_graphics_api: RequestedGraphicsAPI) -> Result { + match requested_graphics_api { + RequestedGraphicsAPI::OpenGL(requested_open_glversion) => Ok(requested_open_glversion), + RequestedGraphicsAPI::Metal => { + Err(format!("Metal rendering is not supported with an OpenGL renderer").into()) + } + RequestedGraphicsAPI::Vulkan => { + Err(format!("Vulkan rendering is not supported with an OpenGL renderer").into()) + } + RequestedGraphicsAPI::Direct3D => { + Err(format!("Direct3D rendering is not supported with an OpenGL renderer").into()) + } + } + } +} + +impl From for RequestedGraphicsAPI { + fn from(version: RequestedOpenGLVersion) -> Self { + Self::OpenGL(version) + } +} + /// Internal module for use by cbindgen and the C++ platform API layer. #[cfg(feature = "ffi")] pub mod ffi { diff --git a/internal/core/lib.rs b/internal/core/lib.rs index 9c715be80f2..a6285b259c4 100644 --- a/internal/core/lib.rs +++ b/internal/core/lib.rs @@ -84,9 +84,6 @@ pub use graphics::BorderRadius; pub use context::{with_global_context, SlintContext}; -#[doc(inline)] -pub use api::{OpenGLAPI, SlintRenderer}; - #[cfg(not(slint_int_coord))] pub type Coord = f32; #[cfg(slint_int_coord)] diff --git a/internal/interpreter/api.rs b/internal/interpreter/api.rs index 170500647ae..a081404d524 100644 --- a/internal/interpreter/api.rs +++ b/internal/interpreter/api.rs @@ -22,6 +22,7 @@ pub use i_slint_compiler::diagnostics::{Diagnostic, DiagnosticLevel}; pub use i_slint_core::api::*; // keep in sync with api/rs/slint/lib.rs +pub use i_slint_backend_selector::api::*; pub use i_slint_core::graphics::{ Brush, Color, Image, LoadImageError, Rgb8Pixel, Rgba8Pixel, RgbaColor, SharedPixelBuffer, }; diff --git a/internal/renderers/skia/d3d_surface.rs b/internal/renderers/skia/d3d_surface.rs index 8ed39e6dd16..25a45fc3907 100644 --- a/internal/renderers/skia/d3d_surface.rs +++ b/internal/renderers/skia/d3d_surface.rs @@ -1,7 +1,8 @@ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 -use i_slint_core::api::{OpenGLAPI, PhysicalSize as PhysicalWindowSize, Window}; +use i_slint_core::api::{PhysicalSize as PhysicalWindowSize, Window}; +use i_slint_core::graphics::RequestedGraphicsAPI; use i_slint_core::item_rendering::DirtyRegion; use i_slint_core::platform::PlatformError; use std::cell::RefCell; @@ -257,8 +258,12 @@ impl super::Surface for D3DSurface { window_handle: Rc, _display_handle: Rc, size: PhysicalWindowSize, - _opengl_api: Option, + requested_graphics_api: Option, ) -> Result { + if !matches!(requested_graphics_api, Some(RequestedGraphicsAPI::Direct3D)) { + return Err(format!("Requested non-Direct3D rendering with Direct3D renderer").into()); + } + let factory_flags = 0; /* let factory_flags = dxgi1_3::DXGI_CREATE_FACTORY_DEBUG; diff --git a/internal/renderers/skia/lib.rs b/internal/renderers/skia/lib.rs index 5bde28c0e6b..74f3a11ac5e 100644 --- a/internal/renderers/skia/lib.rs +++ b/internal/renderers/skia/lib.rs @@ -13,7 +13,7 @@ use i_slint_core::api::{ }; use i_slint_core::graphics::euclid::{self, Vector2D}; use i_slint_core::graphics::rendering_metrics_collector::RenderingMetricsCollector; -use i_slint_core::graphics::{BorderRadius, FontRequest, SharedPixelBuffer}; +use i_slint_core::graphics::{BorderRadius, FontRequest, RequestedGraphicsAPI, SharedPixelBuffer}; use i_slint_core::item_rendering::RepaintBufferType; use i_slint_core::item_rendering::{DirtyRegion, ItemCache, ItemRenderer, PartialRenderingState}; use i_slint_core::lengths::{ @@ -21,7 +21,7 @@ use i_slint_core::lengths::{ }; use i_slint_core::platform::PlatformError; use i_slint_core::window::{WindowAdapter, WindowInner}; -use i_slint_core::{Brush, OpenGLAPI}; +use i_slint_core::Brush; type PhysicalLength = euclid::Length; type PhysicalRect = euclid::Rect; @@ -68,9 +68,14 @@ fn create_default_surface( window_handle: Rc, display_handle: Rc, size: PhysicalWindowSize, - opengl_api: Option, + requested_graphics_api: Option, ) -> Result, PlatformError> { - match DefaultSurface::new(window_handle.clone(), display_handle.clone(), size, opengl_api) { + match DefaultSurface::new( + window_handle.clone(), + display_handle.clone(), + size, + requested_graphics_api, + ) { Ok(gpu_surface) => Ok(Box::new(gpu_surface) as Box), #[cfg(skia_backend_software)] Err(err) => { @@ -110,7 +115,7 @@ pub struct SkiaRenderer { window_handle: Rc, display_handle: Rc, size: PhysicalWindowSize, - opengl_api: Option, + requested_graphics_api: Option, ) -> Result, PlatformError>, pre_present_callback: RefCell>>, partial_rendering_state: Option, @@ -149,12 +154,12 @@ impl SkiaRenderer { rendering_metrics_collector: Default::default(), rendering_first_time: Default::default(), surface: Default::default(), - surface_factory: |window_handle, display_handle, size, opengl_api| { + surface_factory: |window_handle, display_handle, size, requested_graphics_api| { software_surface::SoftwareSurface::new( window_handle, display_handle, size, - opengl_api, + requested_graphics_api, ) .map(|r| Box::new(r) as Box) }, @@ -176,9 +181,95 @@ impl SkiaRenderer { rendering_metrics_collector: Default::default(), rendering_first_time: Default::default(), surface: Default::default(), - surface_factory: |window_handle, display_handle, size, opengl_api| { - opengl_surface::OpenGLSurface::new(window_handle, display_handle, size, opengl_api) - .map(|r| Box::new(r) as Box) + surface_factory: |window_handle, display_handle, size, requested_graphics_api| { + opengl_surface::OpenGLSurface::new( + window_handle, + display_handle, + size, + requested_graphics_api, + ) + .map(|r| Box::new(r) as Box) + }, + pre_present_callback: Default::default(), + partial_rendering_state, + visualize_dirty_region, + } + } + + #[cfg(target_vendor = "apple")] + /// Creates a new SkiaRenderer that will always use Skia's Metal renderer. + pub fn default_metal() -> Self { + let (partial_rendering_state, visualize_dirty_region) = create_partial_renderer_state(); + Self { + maybe_window_adapter: Default::default(), + rendering_notifier: Default::default(), + image_cache: Default::default(), + path_cache: Default::default(), + rendering_metrics_collector: Default::default(), + rendering_first_time: Default::default(), + surface: Default::default(), + surface_factory: |window_handle, display_handle, size, requested_graphics_api| { + metal_surface::MetalSurface::new( + window_handle, + display_handle, + size, + requested_graphics_api, + ) + .map(|r| Box::new(r) as Box) + }, + pre_present_callback: Default::default(), + partial_rendering_state, + visualize_dirty_region, + } + } + + #[cfg(skia_backend_vulkan)] + /// Creates a new SkiaRenderer that will always use Skia's Vulkan renderer. + pub fn default_vulkan() -> Self { + let (partial_rendering_state, visualize_dirty_region) = create_partial_renderer_state(); + Self { + maybe_window_adapter: Default::default(), + rendering_notifier: Default::default(), + image_cache: Default::default(), + path_cache: Default::default(), + rendering_metrics_collector: Default::default(), + rendering_first_time: Default::default(), + surface: Default::default(), + surface_factory: |window_handle, display_handle, size, requested_graphics_api| { + vulkan_surface::VulkanSurface::new( + window_handle, + display_handle, + size, + requested_graphics_api, + ) + .map(|r| Box::new(r) as Box) + }, + pre_present_callback: Default::default(), + partial_rendering_state, + visualize_dirty_region, + } + } + + #[cfg(target_family = "windows")] + /// Creates a new SkiaRenderer that will always use Skia's Direct3D renderer. + pub fn default_direct3d() -> Self { + let (partial_rendering_state, visualize_dirty_region) = create_partial_renderer_state(); + Self { + maybe_window_adapter: Default::default(), + rendering_notifier: Default::default(), + image_cache: Default::default(), + path_cache: Default::default(), + rendering_metrics_collector: Default::default(), + rendering_first_time: Default::default(), + surface: Default::default(), + surface_factory: |window_handle, display_handle, size, requested_graphics_api| { + d3d_surface::D3DSurface::new( + window_handle, + display_handle, + size, + requested_graphics_api, + ) + .map(|r| Box::new(r) as Box) }, pre_present_callback: Default::default(), partial_rendering_state, @@ -268,11 +359,12 @@ impl SkiaRenderer { window_handle: Rc, display_handle: Rc, size: PhysicalWindowSize, - opengl_api: Option, + requested_graphics_api: Option, ) -> Result<(), PlatformError> { // just in case self.suspend()?; - let surface = (self.surface_factory)(window_handle, display_handle, size, opengl_api)?; + let surface = + (self.surface_factory)(window_handle, display_handle, size, requested_graphics_api)?; self.set_surface(surface); Ok(()) } @@ -774,7 +866,7 @@ pub trait Surface { window_handle: Rc, display_handle: Rc, size: PhysicalWindowSize, - opengl_api: Option, + requested_graphics_api: Option, ) -> Result where Self: Sized; diff --git a/internal/renderers/skia/metal_surface.rs b/internal/renderers/skia/metal_surface.rs index 573eeceab90..816483b2356 100644 --- a/internal/renderers/skia/metal_surface.rs +++ b/internal/renderers/skia/metal_surface.rs @@ -1,7 +1,8 @@ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 -use i_slint_core::api::{OpenGLAPI, PhysicalSize as PhysicalWindowSize, Window}; +use i_slint_core::api::{PhysicalSize as PhysicalWindowSize, Window}; +use i_slint_core::graphics::RequestedGraphicsAPI; use i_slint_core::item_rendering::DirtyRegion; use objc2::rc::autoreleasepool; use objc2::{rc::Retained, runtime::ProtocolObject}; @@ -27,8 +28,12 @@ impl super::Surface for MetalSurface { window_handle: Rc, _display_handle: Rc, size: PhysicalWindowSize, - _opengl_api: Option, + requested_graphics_api: Option, ) -> Result { + if !matches!(requested_graphics_api, Some(RequestedGraphicsAPI::Metal)) { + return Err(format!("Requested non-Metal rendering with Metal renderer").into()); + } + let layer = match window_handle .window_handle() .map_err(|e| format!("Error obtaining window handle for skia metal renderer: {e}"))? diff --git a/internal/renderers/skia/opengl_surface.rs b/internal/renderers/skia/opengl_surface.rs index 0dade3234d0..137891cf7a1 100644 --- a/internal/renderers/skia/opengl_surface.rs +++ b/internal/renderers/skia/opengl_surface.rs @@ -12,12 +12,13 @@ use glutin::{ prelude::*, surface::{SurfaceAttributesBuilder, WindowSurface}, }; -use i_slint_core::api::{PhysicalSize as PhysicalWindowSize, Window}; +use i_slint_core::graphics::RequestedGraphicsAPI; +use i_slint_core::item_rendering::DirtyRegion; +use i_slint_core::{api::GraphicsAPI, platform::PlatformError}; use i_slint_core::{ - api::{APIVersion, GraphicsAPI}, - platform::PlatformError, + api::{PhysicalSize as PhysicalWindowSize, Window}, + graphics::RequestedOpenGLVersion, }; -use i_slint_core::{item_rendering::DirtyRegion, OpenGLAPI}; /// This surface type renders into the given window with OpenGL, using glutin and glow libraries. pub struct OpenGLSurface { @@ -33,13 +34,13 @@ impl super::Surface for OpenGLSurface { window_handle: Rc, display_handle: Rc, size: PhysicalWindowSize, - opengl_api: Option, + requested_graphics_api: Option, ) -> Result { Self::new_with_config( window_handle, display_handle, size, - opengl_api, + requested_graphics_api.map(TryInto::try_into).transpose()?, glutin::config::ConfigTemplateBuilder::new(), None, ) @@ -157,7 +158,7 @@ impl OpenGLSurface { window_handle: Rc, display_handle: Rc, size: PhysicalWindowSize, - opengl_api: Option, + requested_opengl_version: Option, config_builder: glutin::config::ConfigTemplateBuilder, config_filter: Option<&dyn Fn(&glutin::config::Config) -> bool>, ) -> Result { @@ -180,7 +181,7 @@ impl OpenGLSurface { display_handle, width, height, - opengl_api, + requested_opengl_version, config_builder, config_filter, )?; @@ -250,7 +251,7 @@ impl OpenGLSurface { _display_handle: raw_window_handle::DisplayHandle<'_>, width: NonZeroU32, height: NonZeroU32, - opengl_api: Option, + requested_opengl_version: Option, config_template_builder: glutin::config::ConfigTemplateBuilder, config_filter: Option<&dyn Fn(&glutin::config::Config) -> bool>, ) -> Result< @@ -316,23 +317,19 @@ impl OpenGLSurface { .ok_or("Unable to find suitable GL config")? }; - let opengl_api = - opengl_api.unwrap_or(OpenGLAPI::GLES(Some(APIVersion { major: 3, minor: 0 }))); - let preferred_context_attributes = match opengl_api { - OpenGLAPI::GL(version) => { - let version = version.map(|version| glutin::context::Version { - major: version.major, - minor: version.minor, - }); + let requested_opengl_version = + requested_opengl_version.unwrap_or(RequestedOpenGLVersion::OpenGLES(Some((3, 0)))); + let preferred_context_attributes = match requested_opengl_version { + RequestedOpenGLVersion::OpenGL(version) => { + let version = + version.map(|(major, minor)| glutin::context::Version { major, minor }); ContextAttributesBuilder::new() .with_context_api(ContextApi::OpenGl(version)) .build(Some(_window_handle.as_raw())) } - OpenGLAPI::GLES(version) => { - let version = version.map(|version| glutin::context::Version { - major: version.major, - minor: version.minor, - }); + RequestedOpenGLVersion::OpenGLES(version) => { + let version = + version.map(|(major, minor)| glutin::context::Version { major, minor }); ContextAttributesBuilder::new() .with_context_api(ContextApi::Gles(version)) diff --git a/internal/renderers/skia/software_surface.rs b/internal/renderers/skia/software_surface.rs index 26b41fbf838..264753fa76a 100644 --- a/internal/renderers/skia/software_surface.rs +++ b/internal/renderers/skia/software_surface.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 use i_slint_core::api::{PhysicalSize as PhysicalWindowSize, Window}; +use i_slint_core::graphics::RequestedGraphicsAPI; use i_slint_core::item_rendering::DirtyRegion; use i_slint_core::lengths::ScaleFactor; -use i_slint_core::OpenGLAPI; use std::cell::RefCell; use std::num::NonZeroU32; @@ -119,7 +119,7 @@ impl super::Surface for SoftwareSurface { window_handle: Rc, display_handle: Rc, _size: PhysicalWindowSize, - _opengl_api: Option, + _requested_graphics_api: Option, ) -> Result { let _context = softbuffer::Context::new(display_handle) .map_err(|e| format!("Error creating softbuffer context: {e}"))?; diff --git a/internal/renderers/skia/vulkan_surface.rs b/internal/renderers/skia/vulkan_surface.rs index 3463aa04042..95645199a5b 100644 --- a/internal/renderers/skia/vulkan_surface.rs +++ b/internal/renderers/skia/vulkan_surface.rs @@ -5,7 +5,8 @@ use std::cell::{Cell, RefCell}; use std::rc::Rc; use std::sync::Arc; -use i_slint_core::api::{OpenGLAPI, PhysicalSize as PhysicalWindowSize, Window}; +use i_slint_core::api::{PhysicalSize as PhysicalWindowSize, Window}; +use i_slint_core::graphics::RequestedGraphicsAPI; use i_slint_core::item_rendering::DirtyRegion; use vulkano::device::physical::{PhysicalDevice, PhysicalDeviceType}; @@ -161,8 +162,11 @@ impl super::Surface for VulkanSurface { window_handle: Rc, display_handle: Rc, size: PhysicalWindowSize, - _opengl_api: Option, + requested_graphics_api: Option, ) -> Result { + if !matches!(requested_graphics_api, Some(RequestedGraphicsAPI::Vulkan)) { + return Err(format!("Requested non-Vulkan rendering with Vulkan renderer").into()); + } let library = VulkanLibrary::new() .map_err(|load_err| format!("Error loading vulkan library: {load_err}"))?;