Introduce error handling in the FemtoVG and Skia renderers (#2402)

Avoid unwrap() and expect() and instead propagate errors all the way
down to run_event_loop(), show(), and hide() in the Slint AIP.
This commit is contained in:
Simon Hausmann 2023-03-24 14:18:11 +01:00 committed by GitHub
parent 7f95614a98
commit 8ffb5131c7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 627 additions and 357 deletions

View file

@ -53,8 +53,9 @@ impl WindowAdapterSealed for CppWindowAdapter {
unsafe { (self.show)(self.user_data) };
Ok(())
}
fn hide(&self) {
fn hide(&self) -> Result<(), PlatformError> {
unsafe { (self.hide)(self.user_data) }
Ok(())
}
fn request_redraw(&self) {
@ -230,7 +231,7 @@ pub unsafe extern "C" fn slint_skia_renderer_show_win32(
RawWindowHandle::Win32(init_raw!(raw_window_handle::Win32WindowHandle { hwnd, hinstance })),
RawDisplayHandle::Windows(raw_window_handle::WindowsDisplayHandle::empty()),
);
r.show(handle, PhysicalSize { width: size.width, height: size.height })
r.show(handle, PhysicalSize { width: size.width, height: size.height }).unwrap()
}
#[no_mangle]
@ -248,7 +249,7 @@ pub unsafe extern "C" fn slint_skia_renderer_show_x11(
RawWindowHandle::Xcb(init_raw!(XcbWindowHandle { window, visual_id })),
RawDisplayHandle::Xcb(init_raw!(XcbDisplayHandle { connection, screen })),
);
r.show(handle, PhysicalSize { width: size.width, height: size.height })
r.show(handle, PhysicalSize { width: size.width, height: size.height }).unwrap();
}
#[no_mangle]
@ -265,7 +266,7 @@ pub unsafe extern "C" fn slint_skia_renderer_show_wayland(
RawWindowHandle::Wayland(init_raw!(WaylandWindowHandle { surface })),
RawDisplayHandle::Wayland(init_raw!(WaylandDisplayHandle { display })),
);
r.show(handle, PhysicalSize { width: size.width, height: size.height })
r.show(handle, PhysicalSize { width: size.width, height: size.height }).unwrap();
}
#[no_mangle]
@ -282,25 +283,25 @@ pub unsafe extern "C" fn slint_skia_renderer_show_appkit(
RawWindowHandle::AppKit(init_raw!(AppKitWindowHandle { ns_view, ns_window })),
RawDisplayHandle::AppKit(AppKitDisplayHandle::empty()),
);
r.show(handle, PhysicalSize { width: size.width, height: size.height })
r.show(handle, PhysicalSize { width: size.width, height: size.height }).unwrap();
}
#[no_mangle]
pub unsafe extern "C" fn slint_skia_renderer_hide(r: SkiaRendererOpaque) {
let r = &*(r as *const SkiaRenderer);
r.hide()
r.hide().unwrap()
}
#[no_mangle]
pub unsafe extern "C" fn slint_skia_renderer_resize(r: SkiaRendererOpaque, size: IntSize) {
let r = &*(r as *const SkiaRenderer);
r.resize_event(PhysicalSize { width: size.width, height: size.height });
r.resize_event(PhysicalSize { width: size.width, height: size.height }).unwrap();
}
#[no_mangle]
pub unsafe extern "C" fn slint_skia_renderer_render(r: SkiaRendererOpaque, size: IntSize) {
let r = &*(r as *const SkiaRenderer);
r.render(PhysicalSize { width: size.width, height: size.height });
r.render(PhysicalSize { width: size.width, height: size.height }).unwrap();
}
#[no_mangle]

View file

@ -498,7 +498,7 @@ declare_types! {
let this = cx.this();
let window = cx.borrow(&this, |x| x.0.as_ref().cloned());
let window_adapter = window.ok_or(()).or_else(|()| cx.throw_error("Invalid type"))?;
window_adapter.hide();
window_adapter.hide().unwrap();
Ok(JsUndefined::new().as_value(&mut cx))
}

View file

@ -110,7 +110,7 @@ pub mod generated_code {
/// Marks the window of this component to be hidden on the screen. This de-registers
/// the window from the windowing system and it will not receive any further events.
fn hide(&self) {
fn hide(&self) -> Result<(), crate::PlatformError> {
unimplemented!();
}

View file

@ -191,8 +191,8 @@ impl WrappedInstance {
}
/// Hides this instance and prevents further updates of the canvas element.
#[wasm_bindgen]
pub fn hide(&self) {
self.0.hide();
pub fn hide(&self) -> Result<(), JsValue> {
self.0.hide().map_err(|e| -> JsValue { format!("{e}").into() })
}
/// THIS FUNCTION IS NOT PART THE PUBLIC API!

View file

@ -48,7 +48,7 @@ pub fn main() {
let weak_window = main_window.as_weak();
main_window.on_popup_confirmed(move || {
let window = weak_window.unwrap();
window.hide();
window.hide().unwrap();
});
{

View file

@ -1498,7 +1498,7 @@ impl WindowAdapterSealed for QtWindow {
Ok(())
}
fn hide(&self) {
fn hide(&self) -> Result<(), i_slint_core::platform::PlatformError> {
self.rendering_metrics_collector.take();
let widget_ptr = self.widget_ptr();
cpp! {unsafe [widget_ptr as "QWidget*"] {
@ -1507,6 +1507,7 @@ impl WindowAdapterSealed for QtWindow {
// visible windows, and ends the application if needed
QEventLoopLocker();
}};
Ok(())
}
fn request_redraw(&self) {
@ -1930,9 +1931,10 @@ impl Renderer for QtWindow {
&self,
component: ComponentRef,
_items: &mut dyn Iterator<Item = Pin<i_slint_core::items::ItemRef<'_>>>,
) {
) -> Result<(), i_slint_core::platform::PlatformError> {
// Invalidate caches:
self.cache.component_destroyed(component);
Ok(())
}
}

View file

@ -60,8 +60,9 @@ impl WindowAdapterSealed for TestingWindow {
Ok(())
}
fn hide(&self) {
fn hide(&self) -> Result<(), i_slint_core::platform::PlatformError> {
self.shown.set(false);
Ok(())
}
fn renderer(&self) -> &dyn Renderer {

View file

@ -31,7 +31,7 @@ pub(crate) static QUIT_ON_LAST_WINDOW_CLOSED: std::sync::atomic::AtomicBool =
pub trait WinitWindow: WindowAdapter {
fn currently_pressed_key_code(&self) -> &Cell<Option<winit::event::VirtualKeyCode>>;
/// Returns true if during the drawing request_redraw() was called.
fn draw(&self) -> bool;
fn draw(&self) -> Result<bool, i_slint_core::platform::PlatformError>;
fn with_window_handle(&self, callback: &mut dyn FnMut(&winit::window::Window));
fn constraints(&self) -> (corelib::layout::LayoutInfo, corelib::layout::LayoutInfo);
fn set_constraints(
@ -40,7 +40,12 @@ pub trait WinitWindow: WindowAdapter {
);
/// Called by the event loop when a WindowEvent::Resized is received.
fn resize_event(&self, _size: winit::dpi::PhysicalSize<u32>) {}
fn resize_event(
&self,
_size: winit::dpi::PhysicalSize<u32>,
) -> Result<(), i_slint_core::platform::PlatformError> {
Ok(())
}
/// Return true if the proxy element used for input method has the focus
fn input_method_focused(&self) -> bool {
@ -267,15 +272,15 @@ fn process_window_event(
event: WindowEvent,
cursor_pos: &mut LogicalPoint,
pressed: &mut bool,
) {
) -> Result<(), i_slint_core::platform::PlatformError> {
let runtime_window = WindowInner::from_pub(window.window());
match event {
WindowEvent::Resized(size) => {
window.resize_event(size);
window.resize_event(size)?;
}
WindowEvent::CloseRequested => {
if runtime_window.request_close() {
window.hide();
window.hide()?;
}
}
WindowEvent::ReceivedCharacter(ch) => {
@ -293,7 +298,7 @@ fn process_window_event(
{
ch
} else {
return;
return Ok(());
}
} else {
ch
@ -437,7 +442,7 @@ fn process_window_event(
runtime_window.set_window_item_geometry(LogicalSize::new(size.width, size.height));
runtime_window.set_scale_factor(scale_factor as f32);
// Resize the underlying graphics surface
window.resize_event(*new_inner_size);
window.resize_event(*new_inner_size)?;
}
}
WindowEvent::ThemeChanged(theme) => {
@ -445,6 +450,7 @@ fn process_window_event(
}
_ => {}
}
Ok(())
}
/// Runs the event loop and renders the items in the provided `component` in its
@ -493,110 +499,142 @@ pub fn run() -> Result<(), corelib::platform::PlatformError> {
let mut cursor_pos = LogicalPoint::default();
let mut pressed = false;
let mut run_fn = move |event: Event<CustomEvent>, control_flow: &mut ControlFlow| match event {
Event::WindowEvent { event, window_id } => {
if let Some(window) = window_by_id(window_id) {
process_window_event(window, event, &mut cursor_pos, &mut pressed);
};
}
let outer_event_loop_error = Rc::new(RefCell::new(None));
let inner_event_loop_error = outer_event_loop_error.clone();
Event::RedrawRequested(id) => {
if let Some(window) = window_by_id(id) {
if let Ok(pos) = windows_with_pending_redraw_requests.binary_search(&id) {
windows_with_pending_redraw_requests.remove(pos);
}
let redraw_requested_during_draw = window.draw();
if redraw_requested_during_draw {
// If during rendering a new redraw_request() was issued (for example in a rendering notifier callback), then
// pretend that an animation is running, so that we return Poll from the event loop to ensure a repaint as
// soon as possible.
*control_flow = ControlFlow::Poll;
}
let mut run_fn = move |event: Event<CustomEvent>, control_flow: &mut ControlFlow| {
match event {
Event::WindowEvent { event, window_id } => {
if let Some(window) = window_by_id(window_id) {
*inner_event_loop_error.borrow_mut() =
process_window_event(window, event, &mut cursor_pos, &mut pressed).err();
};
}
}
Event::UserEvent(CustomEvent::UpdateWindowProperties(window_id)) => {
if let Err(insert_pos) = windows_with_pending_property_updates.binary_search(&window_id)
{
windows_with_pending_property_updates.insert(insert_pos, window_id);
}
}
Event::UserEvent(CustomEvent::WindowHidden) => {
if QUIT_ON_LAST_WINDOW_CLOSED.load(std::sync::atomic::Ordering::Relaxed) {
let window_count = ALL_WINDOWS.with(|windows| windows.borrow().len());
if window_count == 0 {
*control_flow = ControlFlow::Exit;
}
}
}
Event::UserEvent(CustomEvent::Exit) => {
*control_flow = ControlFlow::Exit;
}
Event::UserEvent(CustomEvent::UserEvent(user)) => {
user();
}
#[cfg(target_arch = "wasm32")]
Event::UserEvent(CustomEvent::WakeEventLoopWorkaround) => {
*control_flow = ControlFlow::Poll;
}
Event::NewEvents(_) => {
*control_flow = ControlFlow::Wait;
windows_with_pending_redraw_requests.clear();
ALL_WINDOWS.with(|windows| {
for (window_id, window_weak) in windows.borrow().iter() {
if window_weak.upgrade().map_or(false, |window| window.take_pending_redraw()) {
if let Err(insert_pos) =
windows_with_pending_redraw_requests.binary_search(window_id)
{
windows_with_pending_redraw_requests.insert(insert_pos, *window_id);
Event::RedrawRequested(id) => {
if let Some(window) = window_by_id(id) {
if let Ok(pos) = windows_with_pending_redraw_requests.binary_search(&id) {
windows_with_pending_redraw_requests.remove(pos);
}
match window.draw() {
Ok(redraw_requested_during_draw) => {
if redraw_requested_during_draw {
// If during rendering a new redraw_request() was issued (for example in a rendering notifier callback), then
// pretend that an animation is running, so that we return Poll from the event loop to ensure a repaint as
// soon as possible.
*control_flow = ControlFlow::Poll;
}
}
Err(rendering_error) => {
*inner_event_loop_error.borrow_mut() = Some(rendering_error)
}
};
}
}
Event::UserEvent(CustomEvent::UpdateWindowProperties(window_id)) => {
if let Err(insert_pos) =
windows_with_pending_property_updates.binary_search(&window_id)
{
windows_with_pending_property_updates.insert(insert_pos, window_id);
}
}
Event::UserEvent(CustomEvent::WindowHidden) => {
if QUIT_ON_LAST_WINDOW_CLOSED.load(std::sync::atomic::Ordering::Relaxed) {
let window_count = ALL_WINDOWS.with(|windows| windows.borrow().len());
if window_count == 0 {
*control_flow = ControlFlow::Exit;
}
}
});
corelib::platform::update_timers_and_animations();
}
Event::MainEventsCleared => {
for window in windows_with_pending_property_updates.drain(..).filter_map(window_by_id) {
WindowInner::from_pub(window.window()).update_window_properties();
}
}
Event::RedrawEventsCleared => {
if *control_flow != ControlFlow::Exit
&& ALL_WINDOWS.with(|windows| {
windows.borrow().iter().any(|(_, w)| {
w.upgrade().map_or(false, |w| w.window().has_active_animations())
})
})
{
Event::UserEvent(CustomEvent::Exit) => {
*control_flow = ControlFlow::Exit;
}
Event::UserEvent(CustomEvent::UserEvent(user)) => {
user();
}
#[cfg(target_arch = "wasm32")]
Event::UserEvent(CustomEvent::WakeEventLoopWorkaround) => {
*control_flow = ControlFlow::Poll;
}
for window in windows_with_pending_redraw_requests.drain(..).filter_map(window_by_id) {
let redraw_requested_during_draw = window.draw();
if redraw_requested_during_draw {
// If during rendering a new redraw_request() was issued (for example in a rendering notifier callback), then
// pretend that an animation is running, so that we return Poll from the event loop to ensure a repaint as
// soon as possible.
Event::NewEvents(_) => {
*control_flow = ControlFlow::Wait;
windows_with_pending_redraw_requests.clear();
ALL_WINDOWS.with(|windows| {
for (window_id, window_weak) in windows.borrow().iter() {
if window_weak
.upgrade()
.map_or(false, |window| window.take_pending_redraw())
{
if let Err(insert_pos) =
windows_with_pending_redraw_requests.binary_search(window_id)
{
windows_with_pending_redraw_requests.insert(insert_pos, *window_id);
}
}
}
});
corelib::platform::update_timers_and_animations();
}
Event::MainEventsCleared => {
for window in
windows_with_pending_property_updates.drain(..).filter_map(window_by_id)
{
WindowInner::from_pub(window.window()).update_window_properties();
}
}
Event::RedrawEventsCleared => {
if *control_flow != ControlFlow::Exit
&& ALL_WINDOWS.with(|windows| {
windows.borrow().iter().any(|(_, w)| {
w.upgrade().map_or(false, |w| w.window().has_active_animations())
})
})
{
*control_flow = ControlFlow::Poll;
}
}
if *control_flow == ControlFlow::Wait {
if let Some(next_timer) = corelib::platform::duration_until_next_timer_update() {
*control_flow = ControlFlow::WaitUntil(instant::Instant::now() + next_timer);
for window in
windows_with_pending_redraw_requests.drain(..).filter_map(window_by_id)
{
match window.draw() {
Ok(redraw_requested_during_draw) => {
if redraw_requested_during_draw {
// If during rendering a new redraw_request() was issued (for example in a rendering notifier callback), then
// pretend that an animation is running, so that we return Poll from the event loop to ensure a repaint as
// soon as possible.
*control_flow = ControlFlow::Poll;
}
}
Err(rendering_error) => {
*inner_event_loop_error.borrow_mut() = Some(rendering_error);
}
}
}
if *control_flow == ControlFlow::Wait {
if let Some(next_timer) = corelib::platform::duration_until_next_timer_update()
{
*control_flow =
ControlFlow::WaitUntil(instant::Instant::now() + next_timer);
}
}
}
}
_ => (),
_ => (),
};
if inner_event_loop_error.borrow().is_some() {
*control_flow = ControlFlow::Exit;
}
};
#[cfg(not(target_arch = "wasm32"))]
@ -620,6 +658,10 @@ pub fn run() -> Result<(), corelib::platform::PlatformError> {
// Winit does not support creating multiple instances of the event loop.
let nre = NotRunningEventLoop { clipboard, instance: winit_loop, event_loop_proxy };
MAYBE_LOOP_INSTANCE.with(|loop_instance| *loop_instance.borrow_mut() = Some(nre));
if let Some(error) = outer_event_loop_error.borrow_mut().take() {
return Err(error);
}
Ok(())
}

View file

@ -145,37 +145,43 @@ impl<Renderer: WinitCompatibleRenderer + 'static> GLWindow<Renderer> {
}
}
fn unmap(&self) {
fn unmap(&self) -> Result<(), PlatformError> {
let old_mapped = match self.map_state.replace(GraphicsWindowBackendState::Unmapped {
requested_position: None,
requested_size: None,
}) {
GraphicsWindowBackendState::Unmapped { .. } => return,
GraphicsWindowBackendState::Unmapped { .. } => return Ok(()),
GraphicsWindowBackendState::Mapped(old_mapped) => old_mapped,
};
crate::event_loop::unregister_window(old_mapped.winit_window.id());
self.renderer.hide();
self.renderer.hide()
}
fn call_with_event_loop(&self, callback: fn(&Self)) {
fn call_with_event_loop(
&self,
callback: fn(&Self) -> Result<(), PlatformError>,
) -> Result<(), PlatformError> {
// With wasm, winit's `run()` consumes the event loop and access to it from outside the event handler yields
// loop and thus ends up trying to create a new event loop instance, which panics in winit. Instead, forward
// the call to be invoked from within the event loop
#[cfg(target_arch = "wasm32")]
corelib::api::invoke_from_event_loop({
return corelib::api::invoke_from_event_loop({
let self_weak = send_wrapper::SendWrapper::new(self.self_weak.clone());
move || {
if let Some(this) = self_weak.take().upgrade() {
callback(&this)
// Can't propagate the returned error because we're in an async callback, so throw.
callback(&this).unwrap()
}
}
})
.unwrap();
.map_err(|_| {
format!("internal error in winit backend: invoke_from_event_loop failed").into()
});
#[cfg(not(target_arch = "wasm32"))]
callback(self)
return callback(self);
}
}
@ -189,17 +195,17 @@ impl<Renderer: WinitCompatibleRenderer + 'static> WinitWindow for GLWindow<Rende
}
/// Draw the items of the specified `component` in the given window.
fn draw(&self) -> bool {
fn draw(&self) -> Result<bool, PlatformError> {
let window = match self.borrow_mapped_window() {
Some(window) => window,
None => return false, // caller bug, doesn't make sense to call draw() when not mapped
None => return Ok(false), // caller bug, doesn't make sense to call draw() when not mapped
};
self.pending_redraw.set(false);
self.renderer.render(physical_size_to_slint(&window.winit_window.inner_size()));
self.renderer.render(physical_size_to_slint(&window.winit_window.inner_size()))?;
self.pending_redraw.get()
Ok(self.pending_redraw.get())
}
fn with_window_handle(&self, callback: &mut dyn FnMut(&winit::window::Window)) {
@ -231,7 +237,7 @@ impl<Renderer: WinitCompatibleRenderer + 'static> WinitWindow for GLWindow<Rende
}
}
fn resize_event(&self, size: winit::dpi::PhysicalSize<u32>) {
fn resize_event(&self, size: winit::dpi::PhysicalSize<u32>) -> Result<(), PlatformError> {
// slint::Window::set_size will call set_size() on this type, which would call
// set_inner_size on the winit Window. On Windows that triggers an new resize event
// in the next event loop iteration for mysterious reasons, with slightly different sizes.
@ -248,7 +254,9 @@ impl<Renderer: WinitCompatibleRenderer + 'static> WinitWindow for GLWindow<Rende
if size.width > 0 && size.height > 0 {
let physical_size = physical_size_to_slint(&size);
self.window.set_size(physical_size);
self.renderer.resize_event(physical_size);
self.renderer.resize_event(physical_size)
} else {
Ok(())
}
}
@ -282,8 +290,10 @@ impl<Renderer: WinitCompatibleRenderer + 'static> WindowAdapterSealed for GLWind
)
})
.ok();
})
});
});
Ok(()) // Doesn't matter if the eventloop is already closed, nothing to update then.
})
.ok();
}
fn apply_window_properties(&self, window_item: Pin<&i_slint_core::items::WindowItem>) {
@ -402,7 +412,7 @@ impl<Renderer: WinitCompatibleRenderer + 'static> WindowAdapterSealed for GLWind
GraphicsWindowBackendState::Unmapped { requested_position, requested_size } => {
(requested_position.clone(), requested_size.clone())
}
GraphicsWindowBackendState::Mapped(_) => return,
GraphicsWindowBackendState::Mapped(_) => return Ok(()),
};
let mut window_builder = winit::window::WindowBuilder::new();
@ -460,21 +470,34 @@ impl<Renderer: WinitCompatibleRenderer + 'static> WindowAdapterSealed for GLWind
);
#[cfg(target_arch = "wasm32")]
{
let html_canvas = {
use wasm_bindgen::JsCast;
let canvas = web_sys::window()
.unwrap()
web_sys::window()
.ok_or_else(|| "winit backend: Could not retrieve DOM window".to_string())?
.document()
.unwrap()
.ok_or_else(|| "winit backend: Could not retrieve DOM document".to_string())?
.get_element_by_id(&self_.canvas_id)
.unwrap()
.ok_or_else(|| {
format!(
"winit backend: Could not retrieve existing HTML Canvas element '{}'",
self_.canvas_id
)
})?
.dyn_into::<web_sys::HtmlCanvasElement>()
.unwrap();
.map_err(|_| {
format!(
"winit backend: Specified DOM element '{}' is not a HTML Canvas",
self_.canvas_id
)
})?
};
#[cfg(target_arch = "wasm32")]
{
let existing_canvas_size = winit::dpi::LogicalSize::new(
canvas.client_width() as f32,
canvas.client_height() as f32,
html_canvas.client_width() as f32,
html_canvas.client_height() as f32,
);
// Try to maintain the existing size of the canvas element. A window created with winit
@ -533,26 +556,15 @@ impl<Renderer: WinitCompatibleRenderer + 'static> WindowAdapterSealed for GLWind
#[cfg(target_arch = "wasm32")]
let window_builder = {
use wasm_bindgen::JsCast;
let canvas = web_sys::window()
.unwrap()
.document()
.unwrap()
.get_element_by_id(&self_.canvas_id)
.unwrap()
.dyn_into::<web_sys::HtmlCanvasElement>()
.unwrap();
use winit::platform::web::WindowBuilderExtWebSys;
window_builder.with_canvas(Some(canvas.clone()))
window_builder.with_canvas(Some(html_canvas.clone()))
};
let winit_window = self_.renderer.show(
window_builder,
#[cfg(target_arch = "wasm32")]
&self_.canvas_id,
);
)?;
WindowInner::from_pub(&self_.window).set_scale_factor(
scale_factor_override.unwrap_or_else(|| winit_window.scale_factor()) as _,
@ -570,13 +582,13 @@ impl<Renderer: WinitCompatibleRenderer + 'static> WindowAdapterSealed for GLWind
}));
crate::event_loop::register_window(id, self_.self_weak.upgrade().unwrap());
});
Ok(())
Ok(())
})
}
fn hide(&self) {
fn hide(&self) -> Result<(), PlatformError> {
self.call_with_event_loop(|self_| {
self_.unmap();
self_.unmap()?;
/* FIXME:
if let Some(existing_blinker) = self.cursor_blinker.borrow().upgrade() {
@ -587,8 +599,9 @@ impl<Renderer: WinitCompatibleRenderer + 'static> WindowAdapterSealed for GLWind
.event_loop_proxy()
.send_event(crate::event_loop::CustomEvent::WindowHidden)
})
.unwrap();
});
.ok(); // It's okay to call hide() even after the event loop is closed. We don't need the logic for quitting the event loop anymore at this point.
Ok(())
})
}
fn set_mouse_cursor(&self, cursor: MouseCursor) {
@ -746,7 +759,7 @@ impl<Renderer: WinitCompatibleRenderer + 'static> WindowAdapterSealed for GLWind
impl<Renderer: WinitCompatibleRenderer + 'static> Drop for GLWindow<Renderer> {
fn drop(&mut self) {
self.unmap();
self.unmap().expect("winit backend: error unmapping window");
}
}

View file

@ -31,14 +31,17 @@ mod renderer {
&self,
window_builder: winit::window::WindowBuilder,
#[cfg(target_arch = "wasm32")] canvas_id: &str,
) -> Rc<winit::window::Window>;
fn hide(&self);
) -> Result<Rc<winit::window::Window>, i_slint_core::platform::PlatformError>;
fn hide(&self) -> Result<(), i_slint_core::platform::PlatformError>;
fn render(&self, size: PhysicalSize);
fn render(&self, size: PhysicalSize) -> Result<(), i_slint_core::platform::PlatformError>;
fn as_core_renderer(&self) -> &dyn i_slint_core::renderer::Renderer;
fn resize_event(&self, size: PhysicalSize);
fn resize_event(
&self,
size: PhysicalSize,
) -> Result<(), i_slint_core::platform::PlatformError>;
#[cfg(target_arch = "wasm32")]
fn html_canvas_element(&self) -> web_sys::HtmlCanvasElement;

View file

@ -10,6 +10,7 @@ use i_slint_core::api::{
SetRenderingNotifierError,
};
use i_slint_core::lengths::{LogicalLength, LogicalPoint, LogicalRect, LogicalSize, ScaleFactor};
use i_slint_core::platform::PlatformError;
use i_slint_core::renderer::Renderer;
use i_slint_core::window::WindowAdapter;
use i_slint_renderer_femtovg::FemtoVGRenderer;
@ -27,25 +28,27 @@ impl GlutinFemtoVGRenderer {
fn with_graphics_api(
opengl_context: &glcontext::OpenGLContext,
callback: impl FnOnce(i_slint_core::api::GraphicsAPI<'_>),
) {
opengl_context.ensure_current();
) -> Result<(), PlatformError> {
opengl_context.ensure_current()?;
let api = GraphicsAPI::NativeOpenGL {
get_proc_address: &|name| opengl_context.get_proc_address(name),
};
callback(api)
callback(api);
Ok(())
}
#[cfg(target_arch = "wasm32")]
fn with_graphics_api(
opengl_context: &glcontext::OpenGLContext,
callback: impl FnOnce(i_slint_core::api::GraphicsAPI<'_>),
) {
) -> Result<(), PlatformError> {
let canvas_element_id = opengl_context.html_canvas_element().id();
let api = GraphicsAPI::WebGL {
canvas_element_id: canvas_element_id.as_str(),
context_type: "webgl",
};
callback(api)
callback(api);
Ok(())
}
}
@ -64,7 +67,7 @@ impl super::WinitCompatibleRenderer for GlutinFemtoVGRenderer {
&self,
window_builder: winit::window::WindowBuilder,
#[cfg(target_arch = "wasm32")] canvas_id: &str,
) -> Rc<winit::window::Window> {
) -> Result<Rc<winit::window::Window>, PlatformError> {
let (window, opengl_context) = crate::event_loop::with_window_target(|event_loop| {
glcontext::OpenGLContext::new_context(
window_builder,
@ -72,8 +75,7 @@ impl super::WinitCompatibleRenderer for GlutinFemtoVGRenderer {
#[cfg(target_arch = "wasm32")]
canvas_id,
)
})
.expect("Unable to create OpenGL context");
})?;
self.renderer.show(
#[cfg(not(target_arch = "wasm32"))]
@ -85,36 +87,37 @@ impl super::WinitCompatibleRenderer for GlutinFemtoVGRenderer {
if let Some(callback) = self.rendering_notifier.borrow_mut().as_mut() {
Self::with_graphics_api(&opengl_context, |api| {
callback.notify(RenderingState::RenderingSetup, &api)
});
})?;
}
*self.opengl_context.borrow_mut() = Some(opengl_context);
Rc::new(window)
Ok(Rc::new(window))
}
fn hide(&self) {
fn hide(&self) -> Result<(), PlatformError> {
if let Some(opengl_context) = self.opengl_context.borrow_mut().take() {
opengl_context.ensure_current();
opengl_context.ensure_current()?;
if let Some(callback) = self.rendering_notifier.borrow_mut().as_mut() {
Self::with_graphics_api(&opengl_context, |api| {
callback.notify(RenderingState::RenderingTeardown, &api)
});
})?;
}
self.renderer.hide();
}
Ok(())
}
fn render(&self, size: PhysicalWindowSize) {
fn render(&self, size: PhysicalWindowSize) -> Result<(), PlatformError> {
let opengl_context = if self.opengl_context.borrow().is_some() {
std::cell::Ref::map(self.opengl_context.borrow(), |context_opt| {
context_opt.as_ref().unwrap()
})
} else {
return;
return Ok(());
};
opengl_context.ensure_current();
opengl_context.ensure_current()?;
self.renderer.render(
size,
@ -122,31 +125,31 @@ impl super::WinitCompatibleRenderer for GlutinFemtoVGRenderer {
|| {
Self::with_graphics_api(&opengl_context, |api| {
notifier_fn.notify(RenderingState::BeforeRendering, &api)
});
})
}
}),
);
)?;
if let Some(callback) = self.rendering_notifier.borrow_mut().as_mut() {
Self::with_graphics_api(&opengl_context, |api| {
callback.notify(RenderingState::AfterRendering, &api)
});
})?;
}
opengl_context.swap_buffers();
opengl_context.swap_buffers()
}
fn as_core_renderer(&self) -> &dyn Renderer {
self
}
fn resize_event(&self, size: PhysicalWindowSize) {
fn resize_event(&self, size: PhysicalWindowSize) -> Result<(), PlatformError> {
let opengl_context = if self.opengl_context.borrow().is_some() {
std::cell::Ref::map(self.opengl_context.borrow(), |context_opt| {
context_opt.as_ref().unwrap()
})
} else {
return;
return Ok(());
};
opengl_context.ensure_resized(size)
@ -219,16 +222,16 @@ impl Renderer for GlutinFemtoVGRenderer {
&self,
component: i_slint_core::component::ComponentRef,
_items: &mut dyn Iterator<Item = Pin<i_slint_core::items::ItemRef<'_>>>,
) {
) -> Result<(), PlatformError> {
let opengl_context = if self.opengl_context.borrow().is_some() {
std::cell::Ref::map(self.opengl_context.borrow(), |context_opt| {
context_opt.as_ref().unwrap()
})
} else {
return;
return Ok(());
};
opengl_context.ensure_current();
opengl_context.ensure_current()?;
self.renderer.free_graphics_resources(component, _items)
}
}

View file

@ -8,7 +8,7 @@ use glutin::{
prelude::*,
surface::{SurfaceAttributesBuilder, WindowSurface},
};
use i_slint_core::api::PhysicalSize;
use i_slint_core::{api::PhysicalSize, platform::PlatformError};
#[cfg(not(target_arch = "wasm32"))]
use raw_window_handle::HasRawWindowHandle;
@ -27,35 +27,52 @@ impl OpenGLContext {
self.canvas.clone()
}
pub fn ensure_current(&self) {
pub fn ensure_current(&self) -> Result<(), PlatformError> {
#[cfg(not(target_arch = "wasm32"))]
if !self.context.is_current() {
self.context.make_current(&self.surface).unwrap();
self.context.make_current(&self.surface).map_err(|glutin_error| -> PlatformError {
format!("FemtoVG: Error making context current: {glutin_error}").into()
})?;
}
Ok(())
}
pub fn swap_buffers(&self) {
pub fn swap_buffers(&self) -> Result<(), PlatformError> {
#[cfg(not(target_arch = "wasm32"))]
self.surface.swap_buffers(&self.context).unwrap();
self.surface.swap_buffers(&self.context).map_err(|glutin_error| -> PlatformError {
format!("FemtoVG: Error swapping buffers: {glutin_error}").into()
})?;
Ok(())
}
pub fn ensure_resized(&self, _size: PhysicalSize) {
pub fn ensure_resized(&self, _size: PhysicalSize) -> Result<(), PlatformError> {
#[cfg(not(target_arch = "wasm32"))]
{
self.ensure_current();
self.surface.resize(
&self.context,
_size.width.try_into().unwrap(),
_size.height.try_into().unwrap(),
);
let width = _size.width.try_into().map_err(|_| {
format!(
"Attempting to create window surface with an invalid width: {}",
_size.width
)
})?;
let height = _size.height.try_into().map_err(|_| {
format!(
"Attempting to create window surface with an invalid height: {}",
_size.height
)
})?;
self.ensure_current()?;
self.surface.resize(&self.context, width, height);
}
Ok(())
}
#[cfg(not(target_arch = "wasm32"))]
pub fn new_context<T>(
window_builder: winit::window::WindowBuilder,
window_target: &winit::event_loop::EventLoopWindowTarget<T>,
) -> Result<(winit::window::Window, Self), Box<dyn std::error::Error>> {
) -> Result<(winit::window::Window, Self), PlatformError> {
let config_template_builder = glutin::config::ConfigTemplateBuilder::new();
let (window, gl_config) = glutin_winit::DisplayBuilder::new()
@ -73,6 +90,9 @@ impl OpenGLContext {
}
})
.expect("internal error: Could not find any matching GL configuration")
})
.map_err(|glutin_err| {
format!("Error creating OpenGL display with glutin: {}", glutin_err)
})?;
let gl_display = gl_config.display();
@ -91,20 +111,28 @@ impl OpenGLContext {
let not_current_gl_context = unsafe {
gl_display
.create_context(&gl_config, &gles_context_attributes)
.or_else(|_| gl_display.create_context(&gl_config, &fallback_context_attributes))?
.or_else(|_| gl_display.create_context(&gl_config, &fallback_context_attributes))
.map_err(|glutin_err| format!("Cannot create OpenGL context: {}", glutin_err))?
};
let window = match window {
Some(window) => window,
None => glutin_winit::finalize_window(window_target, window_builder, &gl_config)?,
None => glutin_winit::finalize_window(window_target, window_builder, &gl_config)
.map_err(|winit_os_error| {
format!("Error finalizing window for OpenGL rendering: {}", winit_os_error)
})?,
};
let size: winit::dpi::PhysicalSize<u32> = window.inner_size();
let width: std::num::NonZeroU32 =
size.width.try_into().expect("new context called with zero width window");
let height: std::num::NonZeroU32 =
size.height.try_into().expect("new context called with zero height window");
let width: std::num::NonZeroU32 = size.width.try_into().map_err(|e| {
format!("Attempting to create window surface with width that doesn't fit into U32: {e}")
})?;
let height: std::num::NonZeroU32 = size.height.try_into().map_err(|e| {
format!(
"Attempting to create window surface with height that doesn't fit into U32: {e}"
)
})?;
let attrs = SurfaceAttributesBuilder::<WindowSurface>::new().build(
window.raw_window_handle(),
@ -112,7 +140,11 @@ impl OpenGLContext {
height,
);
let surface = unsafe { gl_display.create_window_surface(&gl_config, &attrs)? };
let surface = unsafe {
gl_display.create_window_surface(&gl_config, &attrs).map_err(|glutin_err| {
format!("Error creating OpenGL Window surface: {}", glutin_err)
})?
};
// Align the GL layer to the top-left, so that resizing only invalidates the bottom/right
// part of the window.
@ -129,10 +161,13 @@ impl OpenGLContext {
}
}
Ok((
window,
Self { context: not_current_gl_context.make_current(&surface).unwrap(), surface },
))
let context = not_current_gl_context.make_current(&surface)
.map_err(|glutin_error: glutin::error::Error| -> PlatformError {
format!("FemtoVG Renderer: Failed to make newly created OpenGL context current: {glutin_error}")
.into()
})?;
Ok((window, Self { context, surface }))
}
#[cfg(target_arch = "wasm32")]
@ -140,19 +175,28 @@ impl OpenGLContext {
window_builder: winit::window::WindowBuilder,
window_target: &winit::event_loop::EventLoopWindowTarget<T>,
canvas_id: &str,
) -> Result<(winit::window::Window, Self), Box<dyn std::error::Error>> {
let window = window_builder.build(window_target)?;
) -> Result<(winit::window::Window, Self), PlatformError> {
let window = window_builder.build(window_target).map_err(|winit_os_err| {
format!(
"FemtoVG Renderer: Could not create winit window wrapper for DOM canvas: {}",
winit_os_err
)
})?;
use wasm_bindgen::JsCast;
let canvas = web_sys::window()
.unwrap()
.ok_or_else(|| "FemtoVG Renderer: Could not retrieve DOM window".to_string())?
.document()
.unwrap()
.ok_or_else(|| "FemtoVG Renderer: Could not retrieve DOM document".to_string())?
.get_element_by_id(canvas_id)
.unwrap()
.ok_or_else(|| {
format!("FemtoVG Renderer: Could not retrieve existing HTML Canvas element '{canvas_id}'")
})?
.dyn_into::<web_sys::HtmlCanvasElement>()
.unwrap();
.map_err(|_| {
format!("FemtoVG Renderer: Specified DOM element '{canvas_id}' is not a HTML Canvas")
})?;
Ok((window, Self { canvas }))
}

View file

@ -17,30 +17,39 @@ impl super::WinitCompatibleRenderer for SkiaRenderer {
Self { renderer: i_slint_renderer_skia::SkiaRenderer::new(window_adapter_weak.clone()) }
}
fn show(&self, window_builder: winit::window::WindowBuilder) -> Rc<winit::window::Window> {
fn show(
&self,
window_builder: winit::window::WindowBuilder,
) -> Result<Rc<winit::window::Window>, i_slint_core::platform::PlatformError> {
let window = Rc::new(crate::event_loop::with_window_target(|event_loop| {
window_builder.build(event_loop.event_loop_target()).unwrap()
}));
let size: winit::dpi::PhysicalSize<u32> = window.inner_size();
self.renderer.show(window.clone(), PhysicalWindowSize::new(size.width, size.height));
self.renderer.show(window.clone(), PhysicalWindowSize::new(size.width, size.height))?;
window
Ok(window)
}
fn hide(&self) {
self.renderer.hide();
fn hide(&self) -> Result<(), i_slint_core::platform::PlatformError> {
self.renderer.hide()
}
fn render(&self, size: PhysicalWindowSize) {
self.renderer.render(size);
fn render(
&self,
size: PhysicalWindowSize,
) -> Result<(), i_slint_core::platform::PlatformError> {
self.renderer.render(size)
}
fn as_core_renderer(&self) -> &dyn i_slint_core::renderer::Renderer {
&self.renderer
}
fn resize_event(&self, size: PhysicalWindowSize) {
fn resize_event(
&self,
size: PhysicalWindowSize,
) -> Result<(), i_slint_core::platform::PlatformError> {
self.renderer.resize_event(size)
}
}

View file

@ -28,29 +28,43 @@ impl super::WinitCompatibleRenderer for WinitSoftwareRenderer {
}
}
fn show(&self, window_builder: winit::window::WindowBuilder) -> Rc<winit::window::Window> {
let window = Rc::new(crate::event_loop::with_window_target(|event_loop| {
window_builder.build(event_loop.event_loop_target()).unwrap()
}));
fn show(
&self,
window_builder: winit::window::WindowBuilder,
) -> Result<Rc<winit::window::Window>, i_slint_core::platform::PlatformError> {
let window = crate::event_loop::with_window_target(|event_loop| {
window_builder.build(event_loop.event_loop_target()).map_err(|winit_os_error| {
format!("Error creating native window for software rendering: {}", winit_os_error)
})
})?;
let window = Rc::new(window);
*self.canvas.borrow_mut() = Some(unsafe {
softbuffer::GraphicsContext::new(window.as_ref(), window.as_ref()).unwrap()
softbuffer::GraphicsContext::new(window.as_ref(), window.as_ref()).map_err(
|softbuffer_error| {
format!("Error creating softbuffer graphics context: {}", softbuffer_error)
},
)?
});
window
Ok(window)
}
fn hide(&self) {
fn hide(&self) -> Result<(), i_slint_core::platform::PlatformError> {
self.canvas.borrow_mut().take();
Ok(())
}
fn render(&self, size: PhysicalWindowSize) {
fn render(
&self,
size: PhysicalWindowSize,
) -> Result<(), i_slint_core::platform::PlatformError> {
let mut canvas = if self.canvas.borrow().is_some() {
std::cell::RefMut::map(self.canvas.borrow_mut(), |canvas_opt| {
canvas_opt.as_mut().unwrap()
})
} else {
return;
return Ok(());
};
let width = size.width as usize;
@ -95,9 +109,15 @@ impl super::WinitCompatibleRenderer for WinitSoftwareRenderer {
);
canvas.set_buffer(&softbuffer_buffer, width as u16, height as u16);
Ok(())
}
fn resize_event(&self, _size: PhysicalWindowSize) {}
fn resize_event(
&self,
_size: PhysicalWindowSize,
) -> Result<(), i_slint_core::platform::PlatformError> {
Ok(())
}
fn as_core_renderer(&self) -> &dyn i_slint_core::renderer::Renderer {
&self.renderer

View file

@ -381,7 +381,7 @@ fn generate_public_component(llr: &llr::PublicComponent) -> TokenStream {
fn run(&self) -> core::result::Result<(), slint::PlatformError> {
self.show()?;
slint::run_event_loop()?;
self.hide();
self.hide()?;
core::result::Result::Ok(())
}
@ -389,7 +389,7 @@ fn generate_public_component(llr: &llr::PublicComponent) -> TokenStream {
self.window().show()
}
fn hide(&self) {
fn hide(&self) -> core::result::Result<(), slint::PlatformError> {
self.window().hide()
}

View file

@ -348,8 +348,8 @@ impl Window {
}
/// De-registers the window from the windowing system, therefore hiding it.
pub fn hide(&self) {
self.0.hide();
pub fn hide(&self) -> Result<(), PlatformError> {
self.0.hide()
}
/// This function allows registering a callback that's invoked during the different phases of
@ -552,7 +552,7 @@ pub trait ComponentHandle {
/// Marks the window of this component to be hidden on the screen. This de-registers
/// the window from the windowing system and it will not receive any further events.
fn hide(&self);
fn hide(&self) -> Result<(), PlatformError>;
/// Returns the Window associated with this component. The window API can be used
/// to control different aspects of the integration into the windowing system,

View file

@ -142,7 +142,7 @@ pub fn unregister_component<Base>(
window_adapter.renderer().free_graphics_resources(
component,
&mut item_array.iter().map(|item| item.apply_pin(base)),
);
).expect("Fatal error encountered when freeing graphics resources while destroying Slint component");
window_adapter
.unregister_component(component, &mut item_array.iter().map(|item| item.apply_pin(base)));
}

View file

@ -42,7 +42,8 @@ pub trait Renderer {
&self,
_component: ComponentRef,
_items: &mut dyn Iterator<Item = Pin<crate::items::ItemRef<'_>>>,
) {
) -> Result<(), crate::platform::PlatformError> {
Ok(())
}
/// Mark a given region as dirty regardless whether the items actually are dirty.

View file

@ -302,13 +302,14 @@ impl Renderer for SoftwareRenderer {
&self,
_component: crate::component::ComponentRef,
items: &mut dyn Iterator<Item = Pin<crate::items::ItemRef<'_>>>,
) {
) -> Result<(), crate::platform::PlatformError> {
for item in items {
item.cached_rendering_data_offset().release(&mut self.partial_cache.borrow_mut());
}
// We don't have a way to determine the screen region of the delete items, what's in the cache is relative. So
// as a last resort, refresh everything.
self.force_screen_refresh.set(true);
Ok(())
}
fn mark_dirty_region(&self, region: crate::item_rendering::DirtyRegion) {

View file

@ -72,7 +72,9 @@ pub trait WindowAdapterSealed {
Ok(())
}
/// De-registers the window from the windowing system.
fn hide(&self) {}
fn hide(&self) -> Result<(), PlatformError> {
Ok(())
}
/// Issue a request to the windowing system to re-render the contents of the window. This is typically an asynchronous
/// request.
fn request_redraw(&self) {}
@ -591,7 +593,10 @@ impl WindowInner {
/// Calls the render_components to render the main component and any sub-window components, tracked by a
/// property dependency tracker.
pub fn draw_contents(&self, render_components: impl FnOnce(&[(&ComponentRc, LogicalPoint)])) {
pub fn draw_contents<T>(
&self,
render_components: impl FnOnce(&[(&ComponentRc, LogicalPoint)]) -> T,
) -> T {
let draw_fn = || {
let component_rc = self.component();
@ -609,7 +614,7 @@ impl WindowInner {
(&popup_component, popup_coordinates),
])
} else {
render_components(&[(&component_rc, LogicalPoint::default())]);
render_components(&[(&component_rc, LogicalPoint::default())])
}
};
@ -625,8 +630,8 @@ impl WindowInner {
}
/// De-registers the window with the windowing system.
pub fn hide(&self) {
self.window_adapter().hide();
pub fn hide(&self) -> Result<(), PlatformError> {
self.window_adapter().hide()
}
/// Show a popup at the given position relative to the item
@ -833,7 +838,7 @@ pub mod ffi {
#[no_mangle]
pub unsafe extern "C" fn slint_windowrc_hide(handle: *const WindowAdapterRcOpaque) {
let window = &*(handle as *const Rc<dyn WindowAdapter>);
window.hide();
window.hide().unwrap();
}
/// Returns the visibility state of the window. This function can return false even if you previously called show()

View file

@ -1045,17 +1045,16 @@ impl ComponentHandle for ComponentInstance {
comp.borrow_instance().window_adapter().window().show()
}
fn hide(&self) {
fn hide(&self) -> Result<(), PlatformError> {
generativity::make_guard!(guard);
let comp = self.inner.unerase(guard);
comp.borrow_instance().window_adapter().window().hide();
comp.borrow_instance().window_adapter().window().hide()
}
fn run(&self) -> Result<(), PlatformError> {
self.show()?;
run_event_loop()?;
self.hide();
Ok(())
self.hide()
}
fn window(&self) -> &Window {

View file

@ -562,7 +562,7 @@ pub extern "C" fn slint_interpreter_component_instance_show(
if is_visible {
comp.borrow_instance().window_adapter().show().unwrap();
} else {
comp.borrow_instance().window_adapter().hide();
comp.borrow_instance().window_adapter().hide().unwrap();
}
}

View file

@ -14,6 +14,7 @@ use i_slint_core::items::Item;
use i_slint_core::lengths::{
LogicalLength, LogicalPoint, LogicalRect, LogicalSize, PhysicalPx, ScaleFactor,
};
use i_slint_core::platform::PlatformError;
use i_slint_core::renderer::Renderer;
use i_slint_core::window::{WindowAdapter, WindowInner};
use i_slint_core::Brush;
@ -114,21 +115,21 @@ impl FemtoVGRenderer {
pub fn render(
&self,
size: PhysicalWindowSize,
mut before_rendering_callback: Option<impl FnOnce()>,
) {
mut before_rendering_callback: Option<impl FnOnce() -> Result<(), PlatformError>>,
) -> Result<(), i_slint_core::platform::PlatformError> {
let width = size.width;
let height = size.height;
let canvas = if self.canvas.borrow().is_some() {
std::cell::Ref::map(self.canvas.borrow(), |canvas_opt| canvas_opt.as_ref().unwrap())
} else {
return;
return Err(format!("FemtoVG renderer: render() called before show()").into());
};
let window_adapter = self.window_adapter_weak.upgrade().unwrap();
let window = WindowInner::from_pub(window_adapter.window());
window.draw_contents(|components| {
window.draw_contents(|components| -> Result<(), PlatformError> {
let window_background_brush = window.window_item().map(|w| w.as_pin_ref().background());
{
@ -161,7 +162,7 @@ impl FemtoVGRenderer {
femtovg_canvas.set_size(width, height, 1.0);
drop(femtovg_canvas);
callback();
callback()?;
}
let window_adapter = self.window_adapter_weak.upgrade().unwrap();
@ -201,7 +202,8 @@ impl FemtoVGRenderer {
// avoid GPU memory leaks.
canvas.texture_cache.borrow_mut().drain();
drop(item_renderer);
});
Ok(())
})
}
}
@ -389,14 +391,15 @@ impl Renderer for FemtoVGRenderer {
&self,
component: i_slint_core::component::ComponentRef,
_items: &mut dyn Iterator<Item = Pin<i_slint_core::items::ItemRef<'_>>>,
) {
) -> Result<(), i_slint_core::platform::PlatformError> {
let canvas = if self.canvas.borrow().is_some() {
std::cell::Ref::map(self.canvas.borrow(), |canvas_opt| canvas_opt.as_ref().unwrap())
} else {
return;
return Ok(());
};
canvas.graphics_cache.component_destroyed(component);
Ok(())
}
}

View file

@ -224,7 +224,11 @@ impl SwapChain {
[make_surface(0), make_surface(1)]
}
fn resize(&mut self, width: u32, height: u32) {
fn resize(
&mut self,
width: u32,
height: u32,
) -> Result<(), i_slint_core::platform::PlatformError> {
self.gr_context.flush_submit_and_sync_cpu();
self.wait_for_buffer(0);
@ -236,7 +240,9 @@ impl SwapChain {
let resize_result =
self.swap_chain.ResizeBuffers(0, width, height, DEFAULT_SURFACE_FORMAT, 0);
if resize_result != S_OK {
panic!("Error resizing swap chain buffers: {:x}", resize_result);
return Err(
format!("Error resizing swap chain buffers: {:x}", resize_result).into()
);
}
}
@ -246,6 +252,7 @@ impl SwapChain {
width as i32,
height as i32,
));
Ok(())
}
fn wait_for_buffer(&mut self, buffer_index: usize) {
@ -281,7 +288,7 @@ impl super::Surface for D3DSurface {
window: &dyn raw_window_handle::HasRawWindowHandle,
_display: &dyn raw_window_handle::HasRawDisplayHandle,
size: PhysicalWindowSize,
) -> Self {
) -> Result<Self, i_slint_core::platform::PlatformError> {
let factory_flags = 0;
/*
let factory_flags = dxgi1_3::DXGI_CREATE_FACTORY_DEBUG;
@ -401,7 +408,7 @@ impl super::Surface for D3DSurface {
let swap_chain =
RefCell::new(SwapChain::new(queue, &device, gr_context, &window, size, &dxgi_factory));
Self { swap_chain }
Ok(Self { swap_chain })
}
fn name(&self) -> &'static str {
@ -412,26 +419,34 @@ impl super::Surface for D3DSurface {
unimplemented!()
}
fn resize_event(&self, size: PhysicalWindowSize) {
self.swap_chain.borrow_mut().resize(size.width, size.height);
fn resize_event(
&self,
size: PhysicalWindowSize,
) -> Result<(), i_slint_core::platform::PlatformError> {
self.swap_chain.borrow_mut().resize(size.width, size.height)
}
fn render(
&self,
_size: PhysicalWindowSize,
callback: impl FnOnce(&mut skia_safe::Canvas, &mut skia_safe::gpu::DirectContext),
) {
) -> Result<(), i_slint_core::platform::PlatformError> {
self.swap_chain
.borrow_mut()
.render_and_present(|surface, gr_context| callback(surface.canvas(), gr_context))
.render_and_present(|surface, gr_context| callback(surface.canvas(), gr_context));
Ok(())
}
fn bits_per_pixel(&self) -> u8 {
fn bits_per_pixel(&self) -> Result<u8, i_slint_core::platform::PlatformError> {
let mut desc = dxgi::DXGI_SWAP_CHAIN_DESC::default();
unsafe { self.swap_chain.borrow().swap_chain.GetDesc(&mut desc) };
match desc.BufferDesc.Format {
Ok(match desc.BufferDesc.Format {
DEFAULT_SURFACE_FORMAT => 32,
_ => 0, // Not mapped yet
}
fmt @ _ => {
return Err(
format!("Skia D3D Renderer: Unsupported buffer format found {fmt:?}").into()
)
}
})
}
}

View file

@ -18,6 +18,7 @@ use i_slint_core::items::Item;
use i_slint_core::lengths::{
LogicalLength, LogicalPoint, LogicalRect, LogicalSize, PhysicalPx, ScaleFactor,
};
use i_slint_core::platform::PlatformError;
use i_slint_core::window::{WindowAdapter, WindowInner};
use i_slint_core::Brush;
@ -70,15 +71,19 @@ impl<
/// Use the provided window and display for rendering the Slint scene in future calls to [`Self::render()`].
/// The size must be identical to the size of the window in physical pixels that is providing the window handle.
pub fn show(&self, native_window: NativeWindowWrapper, size: PhysicalWindowSize) {
let surface = DefaultSurface::new(&native_window, &native_window, size);
pub fn show(
&self,
native_window: NativeWindowWrapper,
size: PhysicalWindowSize,
) -> Result<(), PlatformError> {
let surface = DefaultSurface::new(&native_window, &native_window, size)?;
let rendering_metrics_collector = RenderingMetricsCollector::new(
self.window_adapter_weak.clone(),
&format!(
"Skia renderer (skia backend {}; surface: {} bpp)",
surface.name(),
surface.bits_per_pixel()
surface.bits_per_pixel()?
),
);
@ -95,11 +100,13 @@ impl<
}
*self.canvas.borrow_mut() = Some(canvas);
Ok(())
}
/// Release any graphics resources and disconnect the rendere from a window that it was previously associated when when
/// calling [`Self::show()]`.
pub fn hide(&self) {
pub fn hide(&self) -> Result<(), i_slint_core::platform::PlatformError> {
if let Some(canvas) = self.canvas.borrow_mut().take() {
canvas.surface.with_active_surface(|| {
if let Some(callback) = self.rendering_notifier.borrow_mut().as_mut() {
@ -107,16 +114,20 @@ impl<
callback.notify(RenderingState::RenderingTeardown, &api)
})
}
});
})?;
}
Ok(())
}
/// Render the scene in the previously associated window. The size parameter must match the size of the window.
pub fn render(&self, size: PhysicalWindowSize) {
pub fn render(
&self,
size: PhysicalWindowSize,
) -> Result<(), i_slint_core::platform::PlatformError> {
let canvas = if self.canvas.borrow().is_some() {
std::cell::Ref::map(self.canvas.borrow(), |canvas_opt| canvas_opt.as_ref().unwrap())
} else {
return;
return Err(format!("Skia renderer: render() called before show()").into());
};
let window_adapter = self.window_adapter_weak.upgrade().unwrap();
@ -185,15 +196,18 @@ impl<
canvas
.with_graphics_api(|api| callback.notify(RenderingState::AfterRendering, &api))
}
});
})
}
/// Call this when you receive a notification from the windowing system that the size of the window has changed.
pub fn resize_event(&self, size: PhysicalWindowSize) {
pub fn resize_event(
&self,
size: PhysicalWindowSize,
) -> Result<(), i_slint_core::platform::PlatformError> {
let canvas = if self.canvas.borrow().is_some() {
std::cell::Ref::map(self.canvas.borrow(), |canvas_opt| canvas_opt.as_ref().unwrap())
} else {
return;
return Ok(());
};
canvas.surface.resize_event(size)
@ -372,15 +386,16 @@ impl<NativeWindowWrapper> i_slint_core::renderer::Renderer for SkiaRenderer<Nati
&self,
component: i_slint_core::component::ComponentRef,
_items: &mut dyn Iterator<Item = std::pin::Pin<i_slint_core::items::ItemRef<'_>>>,
) {
) -> Result<(), i_slint_core::platform::PlatformError> {
let canvas = if self.canvas.borrow().is_some() {
std::cell::Ref::map(self.canvas.borrow(), |canvas_opt| canvas_opt.as_ref().unwrap())
} else {
return;
return Ok(());
};
canvas.image_cache.component_destroyed(component);
canvas.path_cache.component_destroyed(component);
Ok(())
}
}
@ -390,19 +405,28 @@ trait Surface {
window: &dyn raw_window_handle::HasRawWindowHandle,
display: &dyn raw_window_handle::HasRawDisplayHandle,
size: PhysicalWindowSize,
) -> Self;
) -> Result<Self, PlatformError>
where
Self: Sized;
fn name(&self) -> &'static str;
fn with_graphics_api(&self, callback: impl FnOnce(GraphicsAPI<'_>));
fn with_active_surface(&self, callback: impl FnOnce()) {
callback()
fn with_active_surface(
&self,
callback: impl FnOnce(),
) -> Result<(), i_slint_core::platform::PlatformError> {
callback();
Ok(())
}
fn render(
&self,
size: PhysicalWindowSize,
callback: impl FnOnce(&mut skia_safe::Canvas, &mut skia_safe::gpu::DirectContext),
);
fn resize_event(&self, size: PhysicalWindowSize);
fn bits_per_pixel(&self) -> u8;
) -> Result<(), i_slint_core::platform::PlatformError>;
fn resize_event(
&self,
size: PhysicalWindowSize,
) -> Result<(), i_slint_core::platform::PlatformError>;
fn bits_per_pixel(&self) -> Result<u8, PlatformError>;
}
struct SkiaCanvas<SurfaceType: Surface, NativeWindowWrapper> {

View file

@ -25,8 +25,9 @@ impl super::Surface for MetalSurface {
window: &dyn raw_window_handle::HasRawWindowHandle,
_display: &dyn raw_window_handle::HasRawDisplayHandle,
size: PhysicalWindowSize,
) -> Self {
let device = metal::Device::system_default().expect("no metal device found");
) -> Result<Self, i_slint_core::platform::PlatformError> {
let device = metal::Device::system_default()
.ok_or_else(|| format!("Skia Renderer: No metal device found"))?;
let layer = metal::MetalLayer::new();
layer.set_device(&device);
@ -40,7 +41,9 @@ impl super::Surface for MetalSurface {
raw_window_handle::RawWindowHandle::AppKit(
raw_window_handle::AppKitWindowHandle { ns_view, .. },
) => ns_view,
_ => panic!("Metal surface is only supported with AppKit"),
_ => {
return Err("Skia Renderer: Metal surface is only supported with AppKit".into())
}
} as cocoa_id;
view.setWantsLayer(YES);
view.setLayer(layer.as_ref() as *const _ as _);
@ -58,7 +61,7 @@ impl super::Surface for MetalSurface {
let gr_context = skia_safe::gpu::DirectContext::new_metal(&backend, None).unwrap().into();
Self { command_queue, layer, gr_context }
Ok(Self { command_queue, layer, gr_context })
}
fn name(&self) -> &'static str {
@ -69,19 +72,28 @@ impl super::Surface for MetalSurface {
unimplemented!()
}
fn resize_event(&self, size: PhysicalWindowSize) {
fn resize_event(
&self,
size: PhysicalWindowSize,
) -> Result<(), i_slint_core::platform::PlatformError> {
self.layer.set_drawable_size(CGSize::new(size.width as f64, size.height as f64));
Ok(())
}
fn render(
&self,
_size: PhysicalWindowSize,
callback: impl FnOnce(&mut skia_safe::Canvas, &mut skia_safe::gpu::DirectContext),
) {
) -> Result<(), i_slint_core::platform::PlatformError> {
autoreleasepool(|| {
let drawable = match self.layer.next_drawable() {
Some(drawable) => drawable,
None => return,
None => {
return Err(format!(
"Skia Metal Renderer: Failed to retrieve next drawable for rendering"
)
.into())
}
};
let gr_context = &mut self.gr_context.borrow_mut();
@ -118,15 +130,17 @@ impl super::Surface for MetalSurface {
let command_buffer = self.command_queue.new_command_buffer();
command_buffer.present_drawable(drawable);
command_buffer.commit();
Ok(())
})
}
fn bits_per_pixel(&self) -> u8 {
fn bits_per_pixel(&self) -> Result<u8, i_slint_core::platform::PlatformError> {
// From https://developer.apple.com/documentation/metal/mtlpixelformat:
// The storage size of each pixel format is determined by the sum of its components.
// For example, the storage size of BGRA8Unorm is 32 bits (four 8-bit components) and
// the storage size of BGR5A1Unorm is 16 bits (three 5-bit components and one 1-bit component).
match self.layer.pixel_format() {
Ok(match self.layer.pixel_format() {
MTLPixelFormat::B5G6R5Unorm
| MTLPixelFormat::A1BGR5Unorm
| MTLPixelFormat::ABGR4Unorm
@ -146,7 +160,12 @@ impl super::Surface for MetalSurface {
| MTLPixelFormat::RGBA16Uint
| MTLPixelFormat::RGBA16Sint => 64,
MTLPixelFormat::RGBA32Uint | MTLPixelFormat::RGBA32Sint => 128,
_ => 0, // Not mapped yet
}
fmt @ _ => {
return Err(format!(
"Skia Metal Renderer: Unsupported layer pixel format found {fmt:?}"
)
.into())
}
})
}
}

View file

@ -1,7 +1,7 @@
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
use std::cell::RefCell;
use std::{cell::RefCell, num::NonZeroU32};
use glutin::{
config::GetGlConfig,
@ -10,8 +10,8 @@ use glutin::{
prelude::*,
surface::{SurfaceAttributesBuilder, WindowSurface},
};
use i_slint_core::api::GraphicsAPI;
use i_slint_core::api::PhysicalSize as PhysicalWindowSize;
use i_slint_core::{api::GraphicsAPI, platform::PlatformError};
pub struct OpenGLSurface {
fb_info: skia_safe::gpu::gl::FramebufferInfo,
@ -28,8 +28,16 @@ impl super::Surface for OpenGLSurface {
window: &dyn raw_window_handle::HasRawWindowHandle,
display: &dyn raw_window_handle::HasRawDisplayHandle,
size: PhysicalWindowSize,
) -> Self {
let (current_glutin_context, glutin_surface) = Self::init_glutin(window, display, size);
) -> Result<Self, PlatformError> {
let width: std::num::NonZeroU32 = size.width.try_into().map_err(|_| {
format!("Attempting to create window surface with an invalid width: {}", size.width)
})?;
let height: std::num::NonZeroU32 = size.height.try_into().map_err(|_| {
format!("Attempting to create window surface with an invalid height: {}", size.height)
})?;
let (current_glutin_context, glutin_surface) =
Self::init_glutin(window, display, width, height)?;
let fb_info = {
use glow::HasContext;
@ -42,7 +50,9 @@ impl super::Surface for OpenGLSurface {
let fboid = unsafe { gl.get_parameter_i32(glow::FRAMEBUFFER_BINDING) };
skia_safe::gpu::gl::FramebufferInfo {
fboid: fboid.try_into().unwrap(),
fboid: fboid.try_into().map_err(|_| {
format!("Skia Renderer: Internal error, framebuffer binding returned signed id")
})?,
format: skia_safe::gpu::gl::Format::RGBA8.into(),
}
};
@ -51,19 +61,36 @@ impl super::Surface for OpenGLSurface {
current_glutin_context.display().get_proc_address(name) as *const _
});
let mut gr_context = skia_safe::gpu::DirectContext::new_gl(gl_interface, None).unwrap();
let mut gr_context =
skia_safe::gpu::DirectContext::new_gl(gl_interface, None).ok_or_else(|| {
format!("Skia Renderer: Internal Error: Could not create Skia OpenGL interface")
})?;
let surface =
Self::create_internal_surface(fb_info, &current_glutin_context, &mut gr_context, size)
.into();
let width: i32 = size.width.try_into().map_err(|e| {
format!("Attempting to create window surface with width that doesn't fit into non-zero i32: {e}")
})?;
let height: i32 = size.height.try_into().map_err(|e| {
format!(
"Attempting to create window surface with height that doesn't fit into non-zero i32: {e}"
)
})?;
Self {
let surface = Self::create_internal_surface(
fb_info,
&current_glutin_context,
&mut gr_context,
width,
height,
)?
.into();
Ok(Self {
fb_info,
surface,
gr_context: RefCell::new(gr_context),
glutin_context: current_glutin_context,
glutin_surface,
}
})
}
fn name(&self) -> &'static str {
@ -79,57 +106,79 @@ impl super::Surface for OpenGLSurface {
callback(api)
}
fn with_active_surface(&self, callback: impl FnOnce()) {
self.ensure_context_current();
fn with_active_surface(&self, callback: impl FnOnce()) -> Result<(), PlatformError> {
self.ensure_context_current()?;
callback();
Ok(())
}
fn render(
&self,
size: PhysicalWindowSize,
callback: impl FnOnce(&mut skia_safe::Canvas, &mut skia_safe::gpu::DirectContext),
) {
let width = size.width;
let height = size.height;
self.ensure_context_current();
) -> Result<(), PlatformError> {
self.ensure_context_current()?;
let current_context = &self.glutin_context;
let gr_context = &mut self.gr_context.borrow_mut();
let mut surface = self.surface.borrow_mut();
if width != surface.width() as u32 || height != surface.height() as u32 {
*surface =
Self::create_internal_surface(self.fb_info, &current_context, gr_context, size);
let width = size.width.try_into().ok();
let height = size.height.try_into().ok();
if let Some((width, height)) = width.zip(height) {
if width != surface.width() || height != surface.height() {
*surface = Self::create_internal_surface(
self.fb_info,
&current_context,
gr_context,
width,
height,
)?;
}
}
let skia_canvas = surface.canvas();
callback(skia_canvas, gr_context);
self.glutin_surface.swap_buffers(&current_context).unwrap();
self.glutin_surface.swap_buffers(&current_context).map_err(|glutin_error| {
format!("Skia OpenGL Renderer: Error swapping buffers: {glutin_error}").into()
})
}
fn resize_event(&self, size: PhysicalWindowSize) {
self.ensure_context_current();
fn resize_event(&self, size: PhysicalWindowSize) -> Result<(), PlatformError> {
self.ensure_context_current()?;
self.glutin_surface.resize(
&self.glutin_context,
size.width.try_into().unwrap(),
size.height.try_into().unwrap(),
);
let width = size.width.try_into().map_err(|e| {
format!("Skia: Attempting to resize window surface with width that doesn't fit into U32: {e}")
})?;
let height = size.height.try_into().map_err(|e| {
format!(
"Skia: Attempting to resize window surface with height that doesn't fit into U32: {e}"
)
})?;
self.glutin_surface.resize(&self.glutin_context, width, height);
Ok(())
}
fn bits_per_pixel(&self) -> u8 {
fn bits_per_pixel(&self) -> Result<u8, PlatformError> {
let config = self.glutin_context.config();
let rgb_bits = match config.color_buffer_type() {
Some(glutin::config::ColorBufferType::Rgb { r_size, g_size, b_size }) => {
r_size + g_size + b_size
}
_ => panic!("unsupported color buffer used with Skia OpenGL renderer"),
other @ _ => {
return Err(format!(
"Skia OpenGL Renderer: unsupported color buffer {other:?} encountered"
)
.into())
}
};
rgb_bits + config.alpha_size()
Ok(rgb_bits + config.alpha_size())
}
}
@ -137,11 +186,15 @@ impl OpenGLSurface {
fn init_glutin(
_window: &dyn raw_window_handle::HasRawWindowHandle,
_display: &dyn raw_window_handle::HasRawDisplayHandle,
_size: PhysicalWindowSize,
) -> (
glutin::context::PossiblyCurrentContext,
glutin::surface::Surface<glutin::surface::WindowSurface>,
) {
width: NonZeroU32,
height: NonZeroU32,
) -> Result<
(
glutin::context::PossiblyCurrentContext,
glutin::surface::Surface<glutin::surface::WindowSurface>,
),
PlatformError,
> {
cfg_if::cfg_if! {
if #[cfg(target_os = "macos")] {
let prefs = [glutin::display::DisplayApiPreference::Cgl];
@ -154,11 +207,6 @@ impl OpenGLSurface {
}
}
let width: std::num::NonZeroU32 =
_size.width.try_into().expect("new context called with zero width window");
let height: std::num::NonZeroU32 =
_size.height.try_into().expect("new context called with zero height window");
let try_create_surface =
|display_api_preference| -> Result<(_, _), Box<dyn std::error::Error>> {
let gl_display = unsafe {
@ -229,16 +277,16 @@ impl OpenGLSurface {
let is_last = i == num_prefs - 1;
match try_create_surface(pref) {
Ok(result) => Some(result),
Ok(result) => Some(Ok(result)),
Err(glutin_error) => {
if is_last {
panic!("Glutin error creating GL surface: {}", glutin_error);
return Some(Err(format!("Skia OpenGL Renderer: Failed to create OpenGL Window Surface: {glutin_error}")));
}
None
}
}
})
.unwrap();
.unwrap()?;
// Align the GL layer to the top-left, so that resizing only invalidates the bottom/right
// part of the window.
@ -255,44 +303,61 @@ impl OpenGLSurface {
}
}
(not_current_gl_context.make_current(&surface).unwrap(), surface)
let context = not_current_gl_context.make_current(&surface)
.map_err(|glutin_error: glutin::error::Error| -> PlatformError {
format!("FemtoVG Renderer: Failed to make newly created OpenGL context current: {glutin_error}")
.into()
})?;
Ok((context, surface))
}
fn create_internal_surface(
fb_info: skia_safe::gpu::gl::FramebufferInfo,
gl_context: &glutin::context::PossiblyCurrentContext,
gr_context: &mut skia_safe::gpu::DirectContext,
size: PhysicalWindowSize,
) -> skia_safe::Surface {
width: i32,
height: i32,
) -> Result<skia_safe::Surface, PlatformError> {
let config = gl_context.config();
let backend_render_target = skia_safe::gpu::BackendRenderTarget::new_gl(
(size.width.try_into().unwrap(), size.height.try_into().unwrap()),
(width, height),
Some(config.num_samples() as _),
config.stencil_size() as _,
fb_info,
);
let surface = skia_safe::Surface::from_backend_render_target(
match skia_safe::Surface::from_backend_render_target(
gr_context,
&backend_render_target,
skia_safe::gpu::SurfaceOrigin::BottomLeft,
skia_safe::ColorType::RGBA8888,
None,
None,
)
.unwrap();
surface
) {
Some(surface) => Ok(surface),
None => {
Err("Skia OpenGL Renderer: Failed to allocate internal backend rendering target"
.into())
}
}
}
fn ensure_context_current(&self) {
fn ensure_context_current(&self) -> Result<(), PlatformError> {
if !self.glutin_context.is_current() {
self.glutin_context.make_current(&self.glutin_surface).unwrap();
self.glutin_context.make_current(&self.glutin_surface).map_err(
|glutin_error| -> PlatformError {
format!("Skia Renderer: Error making context current: {glutin_error}").into()
},
)?;
}
Ok(())
}
}
impl Drop for OpenGLSurface {
fn drop(&mut self) {
// Make sure that the context is current before Skia calls glDelete***
self.ensure_context_current();
self.ensure_context_current().expect("Skia OpenGL Renderer: Failed to make OpenGL context current before deleting graphics resources");
}
}

View file

@ -189,7 +189,7 @@ let instance = Hello::new().unwrap();
assert!(!instance.window().is_visible());
instance.window().show().unwrap();
assert!(instance.window().is_visible());
instance.window().hide();
instance.window().hide().unwrap();
assert!(!instance.window().is_visible());
```