| 1 | // Copyright © SixtyFPS GmbH <info@slint.dev> |
| 2 | // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 |
| 3 | |
| 4 | use std::{num::NonZeroU32, rc::Rc}; |
| 5 | |
| 6 | use glutin::{ |
| 7 | config::GlConfig, |
| 8 | context::{ContextApi, ContextAttributesBuilder}, |
| 9 | display::GetGlDisplay, |
| 10 | prelude::*, |
| 11 | surface::{SurfaceAttributesBuilder, WindowSurface}, |
| 12 | }; |
| 13 | use i_slint_core::{graphics::RequestedOpenGLVersion, platform::PlatformError}; |
| 14 | use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; |
| 15 | |
| 16 | pub struct OpenGLContext { |
| 17 | context: glutin::context::PossiblyCurrentContext, |
| 18 | surface: glutin::surface::Surface<glutin::surface::WindowSurface>, |
| 19 | winit_window: Rc<winit::window::Window>, |
| 20 | } |
| 21 | |
| 22 | unsafe impl i_slint_renderer_femtovg::OpenGLInterface for OpenGLContext { |
| 23 | fn ensure_current(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { |
| 24 | if !self.context.is_current() { |
| 25 | self.context.make_current(&self.surface).map_err(|glutin_error| -> PlatformError { |
| 26 | format!("FemtoVG: Error making context current: {glutin_error}" ).into() |
| 27 | })?; |
| 28 | } |
| 29 | Ok(()) |
| 30 | } |
| 31 | fn swap_buffers(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { |
| 32 | self.winit_window.pre_present_notify(); |
| 33 | |
| 34 | self.surface.swap_buffers(&self.context).map_err(|glutin_error| -> PlatformError { |
| 35 | format!("FemtoVG: Error swapping buffers: {glutin_error}" ).into() |
| 36 | })?; |
| 37 | |
| 38 | Ok(()) |
| 39 | } |
| 40 | |
| 41 | fn resize( |
| 42 | &self, |
| 43 | width: NonZeroU32, |
| 44 | height: NonZeroU32, |
| 45 | ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { |
| 46 | self.ensure_current()?; |
| 47 | self.surface.resize(&self.context, width, height); |
| 48 | |
| 49 | Ok(()) |
| 50 | } |
| 51 | |
| 52 | fn get_proc_address(&self, name: &std::ffi::CStr) -> *const std::ffi::c_void { |
| 53 | self.context.display().get_proc_address(name) |
| 54 | } |
| 55 | } |
| 56 | |
| 57 | impl OpenGLContext { |
| 58 | pub(crate) fn new_context( |
| 59 | window_attributes: winit::window::WindowAttributes, |
| 60 | event_loop: crate::event_loop::ActiveOrInactiveEventLoop<'_>, |
| 61 | requested_opengl_version: Option<RequestedOpenGLVersion>, |
| 62 | ) -> Result<(Rc<winit::window::Window>, Self), PlatformError> { |
| 63 | let config_template_builder = glutin::config::ConfigTemplateBuilder::new(); |
| 64 | |
| 65 | // On macOS, there's only one GL config and that's initialized based on the values in the config template |
| 66 | // builder. So if that one has transparency enabled, it'll show up in the config, and will be set on the |
| 67 | // context later. So we must enable it here, there's no way of enabling it later. |
| 68 | // On EGL/GLX/WGL there are system provided configs that may or may not support transparency. Here in case |
| 69 | // the system doesn't support transparency, we want to fall back to a config that doesn't - better than not |
| 70 | // rendering anything at all. So we don't want to limit the configurations we get to see early on. |
| 71 | // Commented out due to https://github.com/rust-windowing/glutin/issues/1640 |
| 72 | #[cfg (target_os = "macos" )] |
| 73 | let config_template_builder = config_template_builder.with_transparency(true); |
| 74 | |
| 75 | let display_builder = glutin_winit::DisplayBuilder::new() |
| 76 | .with_preference(glutin_winit::ApiPreference::FallbackEgl) |
| 77 | .with_window_attributes(Some(window_attributes.clone())); |
| 78 | let config_picker = |it: Box<dyn Iterator<Item = glutin::config::Config> + '_>| { |
| 79 | it.reduce(|accum, config| { |
| 80 | let transparency_check = config.supports_transparency().unwrap_or(false) |
| 81 | & !accum.supports_transparency().unwrap_or(false); |
| 82 | |
| 83 | if transparency_check || config.num_samples() < accum.num_samples() { |
| 84 | config |
| 85 | } else { |
| 86 | accum |
| 87 | } |
| 88 | }) |
| 89 | .expect("internal error: Could not find any matching GL configuration" ) |
| 90 | }; |
| 91 | let (window, gl_config) = match event_loop { |
| 92 | crate::event_loop::ActiveOrInactiveEventLoop::Active(l) => display_builder |
| 93 | .build(l, config_template_builder, config_picker) |
| 94 | .map_err(|glutin_err| { |
| 95 | format!( |
| 96 | "Error creating OpenGL display ( {:#?}) with glutin: {}" , |
| 97 | l.display_handle(), |
| 98 | glutin_err |
| 99 | ) |
| 100 | }), |
| 101 | crate::event_loop::ActiveOrInactiveEventLoop::Inactive(l) => display_builder |
| 102 | .build(l, config_template_builder, config_picker) |
| 103 | .map_err(|glutin_err| { |
| 104 | format!( |
| 105 | "Error creating OpenGL display ( {:#?}) with glutin: {}" , |
| 106 | l.display_handle(), |
| 107 | glutin_err |
| 108 | ) |
| 109 | }), |
| 110 | }?; |
| 111 | |
| 112 | let gl_display = gl_config.display(); |
| 113 | |
| 114 | let raw_window_handle = window |
| 115 | .as_ref() |
| 116 | .map(|w| w.window_handle()) |
| 117 | .transpose() |
| 118 | .map_err(|err| { |
| 119 | format!( |
| 120 | "Failed to retrieve a window handle while creating an OpenGL display: {err:?}" |
| 121 | ) |
| 122 | })? |
| 123 | .map(|h| h.as_raw()); |
| 124 | |
| 125 | let requested_opengl_version = |
| 126 | requested_opengl_version.unwrap_or(RequestedOpenGLVersion::OpenGLES(Some((2, 0)))); |
| 127 | let preferred_context_attributes = match requested_opengl_version { |
| 128 | RequestedOpenGLVersion::OpenGL(version) => { |
| 129 | let version = |
| 130 | version.map(|(major, minor)| glutin::context::Version { major, minor }); |
| 131 | ContextAttributesBuilder::new() |
| 132 | .with_context_api(ContextApi::OpenGl(version)) |
| 133 | .build(raw_window_handle) |
| 134 | } |
| 135 | RequestedOpenGLVersion::OpenGLES(version) => { |
| 136 | let version = |
| 137 | version.map(|(major, minor)| glutin::context::Version { major, minor }); |
| 138 | |
| 139 | ContextAttributesBuilder::new() |
| 140 | .with_context_api(ContextApi::Gles(version)) |
| 141 | .build(raw_window_handle) |
| 142 | } |
| 143 | }; |
| 144 | |
| 145 | let fallback_context_attributes = ContextAttributesBuilder::new().build(raw_window_handle); |
| 146 | |
| 147 | let not_current_gl_context = unsafe { |
| 148 | gl_display |
| 149 | .create_context(&gl_config, &preferred_context_attributes) |
| 150 | .or_else(|_| gl_display.create_context(&gl_config, &fallback_context_attributes)) |
| 151 | .map_err(|glutin_err| format!("Cannot create OpenGL context: {glutin_err}" ))? |
| 152 | }; |
| 153 | |
| 154 | let window = match window { |
| 155 | Some(window) => window, |
| 156 | None => match event_loop { |
| 157 | crate::event_loop::ActiveOrInactiveEventLoop::Active(l) => { |
| 158 | glutin_winit::finalize_window(l, window_attributes, &gl_config) |
| 159 | } |
| 160 | crate::event_loop::ActiveOrInactiveEventLoop::Inactive(l) => { |
| 161 | glutin_winit::finalize_window(l, window_attributes, &gl_config) |
| 162 | } |
| 163 | } |
| 164 | .map_err(|winit_os_error| { |
| 165 | format!("Error finalizing window for OpenGL rendering: {winit_os_error}" ) |
| 166 | })?, |
| 167 | }; |
| 168 | |
| 169 | let raw_window_handle = window.window_handle().map_err(|err| { |
| 170 | format!("Failed to retrieve a window handle for window we just created: {err:?}" ) |
| 171 | })?; |
| 172 | |
| 173 | let size: winit::dpi::PhysicalSize<u32> = window.inner_size(); |
| 174 | |
| 175 | let width: std::num::NonZeroU32 = size.width.try_into().map_err(|_| { |
| 176 | format!( |
| 177 | "Attempting to create an OpenGL window surface with an invalid width: {}" , |
| 178 | size.width |
| 179 | ) |
| 180 | })?; |
| 181 | let height: std::num::NonZeroU32 = size.height.try_into().map_err(|_| { |
| 182 | format!( |
| 183 | "Attempting to create an OpenGL window surface with an invalid height: {}" , |
| 184 | size.height |
| 185 | ) |
| 186 | })?; |
| 187 | |
| 188 | let attrs = SurfaceAttributesBuilder::<WindowSurface>::new().build( |
| 189 | raw_window_handle.as_raw(), |
| 190 | width, |
| 191 | height, |
| 192 | ); |
| 193 | |
| 194 | let surface = unsafe { |
| 195 | gl_display.create_window_surface(&gl_config, &attrs).map_err(|glutin_err| { |
| 196 | format!("Error creating OpenGL Window surface: {glutin_err}" ) |
| 197 | })? |
| 198 | }; |
| 199 | |
| 200 | let context = not_current_gl_context.make_current(&surface) |
| 201 | .map_err(|glutin_error: glutin::error::Error| -> PlatformError { |
| 202 | format!("FemtoVG Renderer: Failed to make newly created OpenGL context current: {glutin_error}" ) |
| 203 | .into() |
| 204 | })?; |
| 205 | |
| 206 | // Align the GL layer to the top-left, so that resizing only invalidates the bottom/right |
| 207 | // part of the window. |
| 208 | #[cfg (target_os = "macos" )] |
| 209 | if let raw_window_handle::RawWindowHandle::AppKit(raw_window_handle::AppKitWindowHandle { |
| 210 | ns_view, |
| 211 | .. |
| 212 | }) = window |
| 213 | .window_handle() |
| 214 | .map_err(|e| { |
| 215 | format!( |
| 216 | "Error obtaining window handle to adjust nsview layer contents placement: {e}" |
| 217 | ) |
| 218 | })? |
| 219 | .as_raw() |
| 220 | { |
| 221 | let ns_view: &objc2_app_kit::NSView = unsafe { ns_view.cast().as_ref() }; |
| 222 | unsafe { |
| 223 | ns_view.setLayerContentsPlacement( |
| 224 | objc2_app_kit::NSViewLayerContentsPlacement::TopLeft, |
| 225 | ); |
| 226 | } |
| 227 | } |
| 228 | |
| 229 | // Sanity check, as all this might succeed on Windows without working GL drivers, but this will fail: |
| 230 | if context |
| 231 | .display() |
| 232 | .get_proc_address(&std::ffi::CString::new("glCreateShader" ).unwrap()) |
| 233 | .is_null() |
| 234 | { |
| 235 | return Err( |
| 236 | "Failed to initialize OpenGL driver: Could not locate glCreateShader symbol" |
| 237 | .to_string() |
| 238 | .into(), |
| 239 | ); |
| 240 | } |
| 241 | |
| 242 | // Try to default to vsync and ignore if the driver doesn't support it. |
| 243 | surface |
| 244 | .set_swap_interval( |
| 245 | &context, |
| 246 | glutin::surface::SwapInterval::Wait(NonZeroU32::new(1).unwrap()), |
| 247 | ) |
| 248 | .ok(); |
| 249 | |
| 250 | let window = Rc::new(window); |
| 251 | |
| 252 | Ok((window.clone(), Self { context, surface, winit_window: window })) |
| 253 | } |
| 254 | } |
| 255 | |