mirror of
https://github.com/slint-ui/slint.git
synced 2025-08-04 18:58:36 +00:00
FemtoVG: Abstract the rest of the FemtoVGRenderer type to be independent from OpenGL
The main visible external change is that FemtoVGRenderer is now a generic type, but in the public Slint API the type alias that uses the OpenGL backend is re-exported under the old name. This looks a little different in rustdoc but appears to be source compatible.
This commit is contained in:
parent
96e422d43d
commit
98dc4a8657
6 changed files with 355 additions and 215 deletions
|
@ -417,8 +417,8 @@ pub mod platform {
|
|||
/// It is only enabled when the `renderer-femtovg` Slint feature is enabled.
|
||||
#[cfg(all(feature = "renderer-femtovg", not(target_os = "android")))]
|
||||
pub mod femtovg_renderer {
|
||||
pub use i_slint_renderer_femtovg::FemtoVGRenderer;
|
||||
pub use i_slint_renderer_femtovg::OpenGLInterface;
|
||||
pub use i_slint_renderer_femtovg::opengl::OpenGLInterface;
|
||||
pub use i_slint_renderer_femtovg::FemtoVGOpenGLRenderer as FemtoVGRenderer;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,8 @@ use crate::display::{gbmdisplay::GbmDisplay, Presenter, RenderingRotation};
|
|||
use crate::drmoutput::DrmOutput;
|
||||
|
||||
pub struct FemtoVGRendererAdapter {
|
||||
renderer: i_slint_renderer_femtovg::FemtoVGRenderer,
|
||||
renderer:
|
||||
i_slint_renderer_femtovg::FemtoVGRenderer<i_slint_renderer_femtovg::opengl::OpenGLBackend>,
|
||||
gbm_display: Rc<GbmDisplay>,
|
||||
}
|
||||
|
||||
|
@ -114,7 +115,7 @@ impl GlContextWrapper {
|
|||
}
|
||||
}
|
||||
|
||||
unsafe impl i_slint_renderer_femtovg::OpenGLInterface for GlContextWrapper {
|
||||
unsafe impl i_slint_renderer_femtovg::opengl::OpenGLInterface for GlContextWrapper {
|
||||
fn ensure_current(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
if !self.glutin_context.is_current() {
|
||||
self.glutin_context.make_current(&self.glutin_surface).map_err(
|
||||
|
|
|
@ -6,7 +6,9 @@ use std::sync::Arc;
|
|||
|
||||
use i_slint_core::renderer::Renderer;
|
||||
use i_slint_core::{graphics::RequestedGraphicsAPI, platform::PlatformError};
|
||||
use i_slint_renderer_femtovg::{FemtoVGRenderer, FemtoVGRendererExt};
|
||||
use i_slint_renderer_femtovg::{
|
||||
opengl, FemtoVGOpenGLRendererExt, FemtoVGRenderer, FemtoVGRendererExt,
|
||||
};
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use winit::platform::web::WindowExtWebSys;
|
||||
|
@ -17,16 +19,13 @@ use super::WinitCompatibleRenderer;
|
|||
mod glcontext;
|
||||
|
||||
pub struct GlutinFemtoVGRenderer {
|
||||
renderer: FemtoVGRenderer,
|
||||
renderer: FemtoVGRenderer<opengl::OpenGLBackend>,
|
||||
suspended: Cell<bool>,
|
||||
}
|
||||
|
||||
impl GlutinFemtoVGRenderer {
|
||||
pub fn new_suspended() -> Box<dyn WinitCompatibleRenderer> {
|
||||
Box::new(Self {
|
||||
renderer: FemtoVGRenderer::new_without_context(),
|
||||
suspended: Cell::new(true),
|
||||
})
|
||||
Box::new(Self { renderer: FemtoVGRenderer::new_suspended(), suspended: Cell::new(true) })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,7 +77,7 @@ impl super::WinitCompatibleRenderer for GlutinFemtoVGRenderer {
|
|||
}
|
||||
|
||||
fn suspend(&self) -> Result<(), PlatformError> {
|
||||
self.renderer.clear_opengl_context()
|
||||
self.renderer.clear_graphics_context()
|
||||
}
|
||||
|
||||
fn is_suspended(&self) -> bool {
|
||||
|
|
|
@ -19,7 +19,7 @@ pub struct OpenGLContext {
|
|||
winit_window: Arc<winit::window::Window>,
|
||||
}
|
||||
|
||||
unsafe impl i_slint_renderer_femtovg::OpenGLInterface for OpenGLContext {
|
||||
unsafe impl i_slint_renderer_femtovg::opengl::OpenGLInterface for OpenGLContext {
|
||||
fn ensure_current(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
if !self.context.is_current() {
|
||||
self.context.make_current(&self.surface).map_err(|glutin_error| -> PlatformError {
|
||||
|
|
|
@ -23,6 +23,7 @@ use i_slint_core::platform::PlatformError;
|
|||
use i_slint_core::renderer::RendererSealed;
|
||||
use i_slint_core::window::{WindowAdapter, WindowInner};
|
||||
use i_slint_core::Brush;
|
||||
use images::OpenGLTextureImporter;
|
||||
|
||||
type PhysicalLength = euclid::Length<f32, PhysicalPx>;
|
||||
type PhysicalRect = euclid::Rect<f32, PhysicalPx>;
|
||||
|
@ -35,25 +36,29 @@ use self::itemrenderer::CanvasRc;
|
|||
mod fonts;
|
||||
mod images;
|
||||
mod itemrenderer;
|
||||
pub mod opengl;
|
||||
|
||||
/// This trait describes the interface GPU accelerated renderers in Slint require to render with OpenGL.
|
||||
///
|
||||
/// It serves the purpose to ensure that the OpenGL context is current before running any OpenGL
|
||||
/// commands, as well as providing access to the OpenGL implementation by function pointers.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This trait is unsafe because an implementation of get_proc_address could return dangling
|
||||
/// pointers. In practice an implementation of this trait should just forward to the EGL/WGL/CGL
|
||||
/// C library that implements EGL/CGL/WGL.
|
||||
#[allow(unsafe_code)]
|
||||
pub unsafe trait OpenGLInterface {
|
||||
/// Ensures that the OpenGL context is current when returning from this function.
|
||||
fn ensure_current(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
|
||||
/// This function is called by the renderers when all OpenGL commands have been issued and
|
||||
/// the back buffer is reading for on-screen presentation. Typically implementations forward
|
||||
/// this to platform specific APIs such as eglSwapBuffers.
|
||||
fn swap_buffers(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
|
||||
pub trait WindowSurface<R: femtovg::Renderer> {
|
||||
fn render_surface(&self) -> &R::Surface;
|
||||
}
|
||||
|
||||
pub trait GraphicsBackend {
|
||||
type Renderer: femtovg::Renderer + OpenGLTextureImporter;
|
||||
type WindowSurface: WindowSurface<Self::Renderer>;
|
||||
fn new_suspended() -> Self;
|
||||
fn clear_graphics_context(&self);
|
||||
fn begin_surface_rendering(
|
||||
&self,
|
||||
) -> Result<Self::WindowSurface, Box<dyn std::error::Error + Send + Sync>>;
|
||||
fn submit_commands(&self, commands: <Self::Renderer as femtovg::Renderer>::CommandBuffer);
|
||||
fn present_surface(
|
||||
&self,
|
||||
surface: Self::WindowSurface,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
|
||||
fn with_graphics_api<R>(
|
||||
&self,
|
||||
callback: impl FnOnce(Option<i_slint_core::api::GraphicsAPI<'_>>) -> R,
|
||||
) -> Result<R, i_slint_core::platform::PlatformError>;
|
||||
/// This function is called by the renderers when the surface needs to be resized, typically
|
||||
/// in response to the windowing system notifying of a change in the window system.
|
||||
/// For most implementations this is a no-op, with the exception for wayland for example.
|
||||
|
@ -62,94 +67,26 @@ pub unsafe trait OpenGLInterface {
|
|||
width: NonZeroU32,
|
||||
height: NonZeroU32,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
|
||||
/// Returns the address of the OpenGL function specified by name, or a null pointer if the
|
||||
/// function does not exist.
|
||||
fn get_proc_address(&self, name: &std::ffi::CStr) -> *const std::ffi::c_void;
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
struct WebGLNeedsNoCurrentContext;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
unsafe impl OpenGLInterface for WebGLNeedsNoCurrentContext {
|
||||
fn ensure_current(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn swap_buffers(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resize(
|
||||
&self,
|
||||
_width: NonZeroU32,
|
||||
_height: NonZeroU32,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_proc_address(&self, _: &std::ffi::CStr) -> *const std::ffi::c_void {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
struct SuspendedRenderer {}
|
||||
|
||||
unsafe impl OpenGLInterface for SuspendedRenderer {
|
||||
fn ensure_current(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
Err("ensure current called on suspended renderer".to_string().into())
|
||||
}
|
||||
|
||||
fn swap_buffers(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
Err("swap_buffers called on suspended renderer".to_string().into())
|
||||
}
|
||||
|
||||
fn resize(
|
||||
&self,
|
||||
_: NonZeroU32,
|
||||
_: NonZeroU32,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_proc_address(&self, _: &std::ffi::CStr) -> *const std::ffi::c_void {
|
||||
panic!("get_proc_address called on suspended renderer")
|
||||
}
|
||||
}
|
||||
|
||||
/// Use the FemtoVG renderer when implementing a custom Slint platform where you deliver events to
|
||||
/// Slint and want the scene to be rendered using OpenGL. The rendering is done using the [FemtoVG](https://github.com/femtovg/femtovg)
|
||||
/// library.
|
||||
pub struct FemtoVGRenderer {
|
||||
pub struct FemtoVGRenderer<B: GraphicsBackend> {
|
||||
maybe_window_adapter: RefCell<Option<Weak<dyn WindowAdapter>>>,
|
||||
rendering_notifier: RefCell<Option<Box<dyn RenderingNotifier>>>,
|
||||
canvas: RefCell<Option<CanvasRc<femtovg::renderer::OpenGl>>>,
|
||||
graphics_cache: itemrenderer::ItemGraphicsCache<femtovg::renderer::OpenGl>,
|
||||
texture_cache: RefCell<images::TextureCache<femtovg::renderer::OpenGl>>,
|
||||
canvas: RefCell<Option<CanvasRc<B::Renderer>>>,
|
||||
graphics_cache: itemrenderer::ItemGraphicsCache<B::Renderer>,
|
||||
texture_cache: RefCell<images::TextureCache<B::Renderer>>,
|
||||
rendering_metrics_collector: RefCell<Option<Rc<RenderingMetricsCollector>>>,
|
||||
rendering_first_time: Cell<bool>,
|
||||
// Last field, so that it's dropped last and context exists and is current when destroying the FemtoVG canvas
|
||||
opengl_context: RefCell<Box<dyn OpenGLInterface>>,
|
||||
// Last field, so that it's dropped last and for example the OpenGL context exists and is current when destroying the FemtoVG canvas
|
||||
graphics_backend: B,
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
canvas_id: RefCell<String>,
|
||||
}
|
||||
|
||||
impl FemtoVGRenderer {
|
||||
/// Creates a new renderer that renders using OpenGL. An implementation of the OpenGLInterface
|
||||
/// trait needs to supplied.
|
||||
pub fn new(
|
||||
#[cfg(not(target_arch = "wasm32"))] opengl_context: impl OpenGLInterface + 'static,
|
||||
#[cfg(target_arch = "wasm32")] html_canvas: web_sys::HtmlCanvasElement,
|
||||
) -> Result<Self, PlatformError> {
|
||||
let this = Self::new_without_context();
|
||||
this.set_opengl_context(
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
opengl_context,
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
html_canvas,
|
||||
)?;
|
||||
Ok(this)
|
||||
}
|
||||
|
||||
impl<B: GraphicsBackend> FemtoVGRenderer<B> {
|
||||
/// Render the scene using OpenGL.
|
||||
pub fn render(&self) -> Result<(), i_slint_core::platform::PlatformError> {
|
||||
self.internal_render_with_post_callback(
|
||||
|
@ -167,7 +104,7 @@ impl FemtoVGRenderer {
|
|||
surface_size: i_slint_core::api::PhysicalSize,
|
||||
post_render_cb: Option<&dyn Fn(&mut dyn ItemRenderer)>,
|
||||
) -> Result<(), i_slint_core::platform::PlatformError> {
|
||||
self.opengl_context.borrow().ensure_current()?;
|
||||
let surface = self.graphics_backend.begin_surface_rendering()?;
|
||||
|
||||
if self.rendering_first_time.take() {
|
||||
*self.rendering_metrics_collector.borrow_mut() =
|
||||
|
@ -239,7 +176,8 @@ impl FemtoVGRenderer {
|
|||
// the back buffer, in order to allow the callback to provide its own rendering of the background.
|
||||
// femtovg's clear_rect() will merely schedule a clear call, so flush right away to make it immediate.
|
||||
|
||||
femtovg_canvas.flush();
|
||||
let commands = femtovg_canvas.flush_to_surface(surface.render_surface());
|
||||
self.graphics_backend.submit_commands(commands);
|
||||
|
||||
femtovg_canvas.set_size(width.get(), height.get(), scale);
|
||||
drop(femtovg_canvas);
|
||||
|
@ -298,7 +236,8 @@ impl FemtoVGRenderer {
|
|||
collector.measure_frame_rendered(&mut item_renderer);
|
||||
}
|
||||
|
||||
canvas.borrow_mut().flush();
|
||||
let commands = canvas.borrow_mut().flush_to_surface(surface.render_surface());
|
||||
self.graphics_backend.submit_commands(commands);
|
||||
|
||||
// Delete any images and layer images (and their FBOs) before making the context not current anymore, to
|
||||
// avoid GPU memory leaks.
|
||||
|
@ -312,7 +251,7 @@ impl FemtoVGRenderer {
|
|||
self.with_graphics_api(|api| callback.notify(RenderingState::AfterRendering, &api))?;
|
||||
}
|
||||
|
||||
self.opengl_context.borrow().swap_buffers()?;
|
||||
self.graphics_backend.present_surface(surface)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -321,14 +260,7 @@ impl FemtoVGRenderer {
|
|||
&self,
|
||||
callback: impl FnOnce(i_slint_core::api::GraphicsAPI<'_>),
|
||||
) -> Result<(), PlatformError> {
|
||||
use i_slint_core::api::GraphicsAPI;
|
||||
|
||||
self.opengl_context.borrow().ensure_current()?;
|
||||
let api = GraphicsAPI::NativeOpenGL {
|
||||
get_proc_address: &|name| self.opengl_context.borrow().get_proc_address(name),
|
||||
};
|
||||
callback(api);
|
||||
Ok(())
|
||||
self.graphics_backend.with_graphics_api(|api| callback(api.unwrap()))
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
|
@ -351,10 +283,15 @@ impl FemtoVGRenderer {
|
|||
"Renderer must be associated with component before use".to_string().into()
|
||||
})
|
||||
}
|
||||
|
||||
fn reset_canvas(&self, canvas: CanvasRc<B::Renderer>) {
|
||||
*self.canvas.borrow_mut() = canvas.into();
|
||||
self.rendering_first_time.set(true);
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl RendererSealed for FemtoVGRenderer {
|
||||
impl<B: GraphicsBackend> RendererSealed for FemtoVGRenderer<B> {
|
||||
fn text_size(
|
||||
&self,
|
||||
font_request: i_slint_core::graphics::FontRequest,
|
||||
|
@ -508,62 +445,63 @@ impl RendererSealed for FemtoVGRenderer {
|
|||
_items: &mut dyn Iterator<Item = Pin<i_slint_core::items::ItemRef<'_>>>,
|
||||
) -> Result<(), i_slint_core::platform::PlatformError> {
|
||||
if !self.graphics_cache.is_empty() {
|
||||
self.opengl_context.borrow().ensure_current()?;
|
||||
self.graphics_cache.component_destroyed(component);
|
||||
self.graphics_backend.with_graphics_api(|_| {
|
||||
self.graphics_cache.component_destroyed(component);
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_window_adapter(&self, window_adapter: &Rc<dyn WindowAdapter>) {
|
||||
*self.maybe_window_adapter.borrow_mut() = Some(Rc::downgrade(window_adapter));
|
||||
if self.opengl_context.borrow().ensure_current().is_ok() {
|
||||
self.graphics_cache.clear_all();
|
||||
self.texture_cache.borrow_mut().clear();
|
||||
}
|
||||
self.graphics_backend
|
||||
.with_graphics_api(|_| {
|
||||
self.graphics_cache.clear_all();
|
||||
self.texture_cache.borrow_mut().clear();
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
fn resize(&self, size: i_slint_core::api::PhysicalSize) -> Result<(), PlatformError> {
|
||||
if let Some((width, height)) = size.width.try_into().ok().zip(size.height.try_into().ok()) {
|
||||
self.opengl_context.borrow().resize(width, height)?;
|
||||
self.graphics_backend.resize(width, height)?;
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns an image buffer of what was rendered last by reading the previous front buffer (using glReadPixels).
|
||||
fn take_snapshot(&self) -> Result<SharedPixelBuffer<Rgba8Pixel>, PlatformError> {
|
||||
self.opengl_context.borrow().ensure_current()?;
|
||||
let Some(canvas) = self.canvas.borrow().as_ref().cloned() else {
|
||||
return Err("FemtoVG renderer cannot take screenshot without a window".into());
|
||||
};
|
||||
let screenshot = canvas
|
||||
.borrow_mut()
|
||||
.screenshot()
|
||||
.map_err(|e| format!("FemtoVG error reading current back buffer: {e}"))?;
|
||||
self.graphics_backend.with_graphics_api(|_| {
|
||||
let Some(canvas) = self.canvas.borrow().as_ref().cloned() else {
|
||||
return Err("FemtoVG renderer cannot take screenshot without a window".into());
|
||||
};
|
||||
let screenshot = canvas
|
||||
.borrow_mut()
|
||||
.screenshot()
|
||||
.map_err(|e| format!("FemtoVG error reading current back buffer: {e}"))?;
|
||||
|
||||
use rgb::ComponentBytes;
|
||||
Ok(SharedPixelBuffer::clone_from_slice(
|
||||
screenshot.buf().as_bytes(),
|
||||
screenshot.width() as u32,
|
||||
screenshot.height() as u32,
|
||||
))
|
||||
use rgb::ComponentBytes;
|
||||
Ok(SharedPixelBuffer::clone_from_slice(
|
||||
screenshot.buf().as_bytes(),
|
||||
screenshot.width() as u32,
|
||||
screenshot.height() as u32,
|
||||
))
|
||||
})?
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for FemtoVGRenderer {
|
||||
impl<B: GraphicsBackend> Drop for FemtoVGRenderer<B> {
|
||||
fn drop(&mut self) {
|
||||
self.clear_opengl_context().ok();
|
||||
self.clear_graphics_context().ok();
|
||||
}
|
||||
}
|
||||
|
||||
/// The purpose of this trait is to add internal API that's accessed from the winit/linuxkms backends, but not
|
||||
/// public (as the trait isn't re-exported).
|
||||
#[doc(hidden)]
|
||||
pub trait FemtoVGRendererExt {
|
||||
fn new_without_context() -> Self;
|
||||
fn set_opengl_context(
|
||||
&self,
|
||||
#[cfg(not(target_arch = "wasm32"))] opengl_context: impl OpenGLInterface + 'static,
|
||||
#[cfg(target_arch = "wasm32")] html_canvas: web_sys::HtmlCanvasElement,
|
||||
) -> Result<(), i_slint_core::platform::PlatformError>;
|
||||
fn clear_opengl_context(&self) -> Result<(), i_slint_core::platform::PlatformError>;
|
||||
fn new_suspended() -> Self;
|
||||
fn clear_graphics_context(&self) -> Result<(), i_slint_core::platform::PlatformError>;
|
||||
fn render_transformed_with_post_callback(
|
||||
&self,
|
||||
rotation_angle_degrees: f32,
|
||||
|
@ -573,13 +511,22 @@ pub trait FemtoVGRendererExt {
|
|||
) -> Result<(), i_slint_core::platform::PlatformError>;
|
||||
}
|
||||
|
||||
/// The purpose of this trait is to add internal API specific to the OpenGL renderer that's accessed from the winit
|
||||
/// backend. In this case, the ability to resume a suspended OpenGL renderer by providing a new context.
|
||||
#[doc(hidden)]
|
||||
impl FemtoVGRendererExt for FemtoVGRenderer {
|
||||
pub trait FemtoVGOpenGLRendererExt {
|
||||
fn set_opengl_context(
|
||||
&self,
|
||||
#[cfg(not(target_arch = "wasm32"))] opengl_context: impl opengl::OpenGLInterface + 'static,
|
||||
#[cfg(target_arch = "wasm32")] html_canvas: web_sys::HtmlCanvasElement,
|
||||
) -> Result<(), i_slint_core::platform::PlatformError>;
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl<B: GraphicsBackend> FemtoVGRendererExt for FemtoVGRenderer<B> {
|
||||
/// Creates a new renderer in suspended state without OpenGL. Any attempts at rendering, etc. will produce an error,
|
||||
/// until [`Self::set_opengl_context()`] was called successfully.
|
||||
fn new_without_context() -> Self {
|
||||
let opengl_context = Box::new(SuspendedRenderer {});
|
||||
|
||||
fn new_suspended() -> Self {
|
||||
Self {
|
||||
maybe_window_adapter: Default::default(),
|
||||
rendering_notifier: Default::default(),
|
||||
|
@ -588,15 +535,15 @@ impl FemtoVGRendererExt for FemtoVGRenderer {
|
|||
texture_cache: Default::default(),
|
||||
rendering_metrics_collector: Default::default(),
|
||||
rendering_first_time: Cell::new(true),
|
||||
opengl_context: RefCell::new(opengl_context),
|
||||
graphics_backend: B::new_suspended(),
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
canvas_id: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_opengl_context(&self) -> Result<(), i_slint_core::platform::PlatformError> {
|
||||
fn clear_graphics_context(&self) -> Result<(), i_slint_core::platform::PlatformError> {
|
||||
// Ensure the context is current before the renderer is destroyed
|
||||
if self.opengl_context.borrow().ensure_current().is_ok() {
|
||||
self.graphics_backend.with_graphics_api(|_| {
|
||||
// If we've rendered a frame before, then we need to invoke the RenderingTearDown notifier.
|
||||
if !self.rendering_first_time.get() {
|
||||
if let Some(callback) = self.rendering_notifier.borrow_mut().as_mut() {
|
||||
|
@ -609,7 +556,7 @@ impl FemtoVGRendererExt for FemtoVGRenderer {
|
|||
|
||||
self.graphics_cache.clear_all();
|
||||
self.texture_cache.borrow_mut().clear();
|
||||
}
|
||||
})?;
|
||||
|
||||
if let Some(canvas) = self.canvas.borrow_mut().take() {
|
||||
if Rc::strong_count(&canvas) != 1 {
|
||||
|
@ -617,69 +564,11 @@ impl FemtoVGRendererExt for FemtoVGRenderer {
|
|||
}
|
||||
}
|
||||
|
||||
*self.opengl_context.borrow_mut() = Box::new(SuspendedRenderer {});
|
||||
self.graphics_backend.clear_graphics_context();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_opengl_context(
|
||||
&self,
|
||||
#[cfg(not(target_arch = "wasm32"))] opengl_context: impl OpenGLInterface + 'static,
|
||||
#[cfg(target_arch = "wasm32")] html_canvas: web_sys::HtmlCanvasElement,
|
||||
) -> Result<(), i_slint_core::platform::PlatformError> {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let opengl_context = WebGLNeedsNoCurrentContext {};
|
||||
|
||||
let opengl_context = Box::new(opengl_context);
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let gl_renderer = unsafe {
|
||||
femtovg::renderer::OpenGl::new_from_function_cstr(|name| {
|
||||
opengl_context.get_proc_address(name)
|
||||
})
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let gl_renderer = match femtovg::renderer::OpenGl::new_from_html_canvas(&html_canvas) {
|
||||
Ok(gl_renderer) => gl_renderer,
|
||||
Err(_) => {
|
||||
use wasm_bindgen::JsCast;
|
||||
|
||||
// I don't believe that there's a way of disabling the 2D canvas.
|
||||
let context_2d = html_canvas
|
||||
.get_context("2d")
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.dyn_into::<web_sys::CanvasRenderingContext2d>()
|
||||
.unwrap();
|
||||
context_2d.set_font("20px serif");
|
||||
// We don't know if we're rendering on dark or white background, so choose a "color" in the middle for the text.
|
||||
context_2d.set_fill_style_str("red");
|
||||
context_2d
|
||||
.fill_text("Slint requires WebGL to be enabled in your browser", 0., 30.)
|
||||
.unwrap();
|
||||
panic!("Cannot proceed without WebGL - aborting")
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
*self.canvas_id.borrow_mut() = html_canvas.id();
|
||||
}
|
||||
|
||||
let femtovg_canvas = femtovg::Canvas::new_with_text_context(
|
||||
gl_renderer,
|
||||
self::fonts::FONT_CACHE.with(|cache| cache.borrow().text_context.clone()),
|
||||
)
|
||||
.unwrap();
|
||||
let canvas = Rc::new(RefCell::new(femtovg_canvas));
|
||||
|
||||
*self.canvas.borrow_mut() = canvas.into();
|
||||
*self.opengl_context.borrow_mut() = opengl_context;
|
||||
self.rendering_first_time.set(true);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn render_transformed_with_post_callback(
|
||||
&self,
|
||||
rotation_angle_degrees: f32,
|
||||
|
@ -695,3 +584,21 @@ impl FemtoVGRendererExt for FemtoVGRenderer {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl FemtoVGOpenGLRendererExt for FemtoVGRenderer<opengl::OpenGLBackend> {
|
||||
fn set_opengl_context(
|
||||
&self,
|
||||
#[cfg(not(target_arch = "wasm32"))] opengl_context: impl opengl::OpenGLInterface + 'static,
|
||||
#[cfg(target_arch = "wasm32")] html_canvas: web_sys::HtmlCanvasElement,
|
||||
) -> Result<(), i_slint_core::platform::PlatformError> {
|
||||
self.graphics_backend.set_opengl_context(
|
||||
self,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
opengl_context,
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
html_canvas,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub type FemtoVGOpenGLRenderer = FemtoVGRenderer<opengl::OpenGLBackend>;
|
||||
|
|
233
internal/renderers/femtovg/opengl.rs
Normal file
233
internal/renderers/femtovg/opengl.rs
Normal file
|
@ -0,0 +1,233 @@
|
|||
// 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
|
||||
|
||||
use std::{cell::RefCell, num::NonZeroU32, rc::Rc};
|
||||
|
||||
use i_slint_core::api::PlatformError;
|
||||
|
||||
use crate::{FemtoVGRenderer, GraphicsBackend, WindowSurface};
|
||||
|
||||
/// This trait describes the interface GPU accelerated renderers in Slint require to render with OpenGL.
|
||||
///
|
||||
/// It serves the purpose to ensure that the OpenGL context is current before running any OpenGL
|
||||
/// commands, as well as providing access to the OpenGL implementation by function pointers.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This trait is unsafe because an implementation of get_proc_address could return dangling
|
||||
/// pointers. In practice an implementation of this trait should just forward to the EGL/WGL/CGL
|
||||
/// C library that implements EGL/CGL/WGL.
|
||||
#[allow(unsafe_code)]
|
||||
pub unsafe trait OpenGLInterface {
|
||||
/// Ensures that the OpenGL context is current when returning from this function.
|
||||
fn ensure_current(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
|
||||
/// This function is called by the renderers when all OpenGL commands have been issued and
|
||||
/// the back buffer is reading for on-screen presentation. Typically implementations forward
|
||||
/// this to platform specific APIs such as eglSwapBuffers.
|
||||
fn swap_buffers(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
|
||||
/// This function is called by the renderers when the surface needs to be resized, typically
|
||||
/// in response to the windowing system notifying of a change in the window system.
|
||||
/// For most implementations this is a no-op, with the exception for wayland for example.
|
||||
fn resize(
|
||||
&self,
|
||||
width: NonZeroU32,
|
||||
height: NonZeroU32,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
|
||||
/// Returns the address of the OpenGL function specified by name, or a null pointer if the
|
||||
/// function does not exist.
|
||||
fn get_proc_address(&self, name: &std::ffi::CStr) -> *const std::ffi::c_void;
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
struct WebGLNeedsNoCurrentContext;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
unsafe impl OpenGLInterface for WebGLNeedsNoCurrentContext {
|
||||
fn ensure_current(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn swap_buffers(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resize(
|
||||
&self,
|
||||
_width: NonZeroU32,
|
||||
_height: NonZeroU32,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_proc_address(&self, _: &std::ffi::CStr) -> *const std::ffi::c_void {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
struct SuspendedRenderer {}
|
||||
|
||||
unsafe impl OpenGLInterface for SuspendedRenderer {
|
||||
fn ensure_current(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
Err("ensure current called on suspended renderer".to_string().into())
|
||||
}
|
||||
|
||||
fn swap_buffers(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
Err("swap_buffers called on suspended renderer".to_string().into())
|
||||
}
|
||||
|
||||
fn resize(
|
||||
&self,
|
||||
_: NonZeroU32,
|
||||
_: NonZeroU32,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_proc_address(&self, _: &std::ffi::CStr) -> *const std::ffi::c_void {
|
||||
panic!("get_proc_address called on suspended renderer")
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OpenGLBackend {
|
||||
opengl_context: RefCell<Box<dyn OpenGLInterface>>,
|
||||
}
|
||||
|
||||
impl OpenGLBackend {
|
||||
pub fn set_opengl_context(
|
||||
&self,
|
||||
renderer: &FemtoVGRenderer<Self>,
|
||||
#[cfg(not(target_arch = "wasm32"))] opengl_context: impl OpenGLInterface + 'static,
|
||||
#[cfg(target_arch = "wasm32")] html_canvas: web_sys::HtmlCanvasElement,
|
||||
) -> Result<(), i_slint_core::platform::PlatformError> {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let opengl_context = WebGLNeedsNoCurrentContext {};
|
||||
|
||||
let opengl_context = Box::new(opengl_context);
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let gl_renderer = unsafe {
|
||||
femtovg::renderer::OpenGl::new_from_function_cstr(|name| {
|
||||
opengl_context.get_proc_address(name)
|
||||
})
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let gl_renderer = match femtovg::renderer::OpenGl::new_from_html_canvas(&html_canvas) {
|
||||
Ok(gl_renderer) => gl_renderer,
|
||||
Err(_) => {
|
||||
use wasm_bindgen::JsCast;
|
||||
|
||||
// I don't believe that there's a way of disabling the 2D canvas.
|
||||
let context_2d = html_canvas
|
||||
.get_context("2d")
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.dyn_into::<web_sys::CanvasRenderingContext2d>()
|
||||
.unwrap();
|
||||
context_2d.set_font("20px serif");
|
||||
// We don't know if we're rendering on dark or white background, so choose a "color" in the middle for the text.
|
||||
context_2d.set_fill_style_str("red");
|
||||
context_2d
|
||||
.fill_text("Slint requires WebGL to be enabled in your browser", 0., 30.)
|
||||
.unwrap();
|
||||
panic!("Cannot proceed without WebGL - aborting")
|
||||
}
|
||||
};
|
||||
|
||||
let femtovg_canvas = femtovg::Canvas::new_with_text_context(
|
||||
gl_renderer,
|
||||
crate::fonts::FONT_CACHE.with(|cache| cache.borrow().text_context.clone()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
*self.opengl_context.borrow_mut() = opengl_context;
|
||||
|
||||
let canvas = Rc::new(RefCell::new(femtovg_canvas));
|
||||
renderer.reset_canvas(canvas);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GLWindowSurface {}
|
||||
|
||||
impl WindowSurface<femtovg::renderer::OpenGl> for GLWindowSurface {
|
||||
fn render_surface(&self) -> &<femtovg::renderer::OpenGl as femtovg::Renderer>::Surface {
|
||||
&()
|
||||
}
|
||||
}
|
||||
|
||||
impl GraphicsBackend for OpenGLBackend {
|
||||
type Renderer = femtovg::renderer::OpenGl;
|
||||
type WindowSurface = GLWindowSurface;
|
||||
|
||||
fn new_suspended() -> Self {
|
||||
Self { opengl_context: RefCell::new(Box::new(SuspendedRenderer {})) }
|
||||
}
|
||||
|
||||
fn clear_graphics_context(&self) {
|
||||
*self.opengl_context.borrow_mut() = Box::new(SuspendedRenderer {});
|
||||
}
|
||||
|
||||
/// Ensures that the OpenGL context is current when returning from this function.
|
||||
fn begin_surface_rendering(
|
||||
&self,
|
||||
) -> Result<GLWindowSurface, Box<dyn std::error::Error + Send + Sync>> {
|
||||
self.opengl_context.borrow().ensure_current()?;
|
||||
Ok(GLWindowSurface {})
|
||||
}
|
||||
|
||||
fn submit_commands(&self, _commands: <Self::Renderer as femtovg::Renderer>::CommandBuffer) {}
|
||||
|
||||
/// This function is called by the renderers when all OpenGL commands have been issued and
|
||||
/// the back buffer is reading for on-screen presentation. Typically implementations forward
|
||||
/// this to platform specific APIs such as eglSwapBuffers.
|
||||
fn present_surface(
|
||||
&self,
|
||||
_surface: GLWindowSurface,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
self.opengl_context.borrow().swap_buffers()
|
||||
}
|
||||
|
||||
fn with_graphics_api<R>(
|
||||
&self,
|
||||
callback: impl FnOnce(Option<i_slint_core::api::GraphicsAPI<'_>>) -> R,
|
||||
) -> Result<R, i_slint_core::platform::PlatformError> {
|
||||
use i_slint_core::api::GraphicsAPI;
|
||||
|
||||
self.opengl_context.borrow().ensure_current()?;
|
||||
let api = GraphicsAPI::NativeOpenGL {
|
||||
get_proc_address: &|name| self.opengl_context.borrow().get_proc_address(name),
|
||||
};
|
||||
Ok(callback(Some(api)))
|
||||
}
|
||||
|
||||
/// This function is called by the renderers when the surface needs to be resized, typically
|
||||
/// in response to the windowing system notifying of a change in the window system.
|
||||
/// For most implementations this is a no-op, with the exception for wayland for example.
|
||||
fn resize(
|
||||
&self,
|
||||
width: NonZeroU32,
|
||||
height: NonZeroU32,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
self.opengl_context.borrow().resize(width, height)
|
||||
}
|
||||
}
|
||||
|
||||
impl FemtoVGRenderer<OpenGLBackend> {
|
||||
/// Creates a new renderer that renders using OpenGL. An implementation of the OpenGLInterface
|
||||
/// trait needs to supplied.
|
||||
pub fn new(
|
||||
#[cfg(not(target_arch = "wasm32"))] opengl_context: impl OpenGLInterface + 'static,
|
||||
#[cfg(target_arch = "wasm32")] html_canvas: web_sys::HtmlCanvasElement,
|
||||
) -> Result<Self, PlatformError> {
|
||||
use super::FemtoVGRendererExt;
|
||||
let this = Self::new_suspended();
|
||||
this.graphics_backend.set_opengl_context(
|
||||
&this,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
opengl_context,
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
html_canvas,
|
||||
)?;
|
||||
Ok(this)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue