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
4use std::{num::NonZeroU32, rc::Rc};
5
6use glutin::{
7 config::GlConfig,
8 context::{ContextApi, ContextAttributesBuilder},
9 display::GetGlDisplay,
10 prelude::*,
11 surface::{SurfaceAttributesBuilder, WindowSurface},
12};
13use i_slint_core::{graphics::RequestedOpenGLVersion, platform::PlatformError};
14use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
15
16pub struct OpenGLContext {
17 context: glutin::context::PossiblyCurrentContext,
18 surface: glutin::surface::Surface<glutin::surface::WindowSurface>,
19 winit_window: Rc<winit::window::Window>,
20}
21
22unsafe 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
57impl 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