// Original file: // https://github.com/rust-windowing/glutin/blob/ // 0433af9018febe0696c485ed9d66c40dad41f2d4/glutin-winit/src/lib.rs // // Copyright © 2022 Kirill Chibisov // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the “Software”), to deal // in the Software without restriction, including without limitation the rights to // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies // of the Software, and to permit persons to whom the Software is furnished to do // so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. //! This library provides helpers for cross-platform [`glutin`] bootstrapping //! with [`winit`]. #![deny(rust_2018_idioms)] #![deny(rustdoc::broken_intra_doc_links)] #![deny(clippy::all)] #![deny(missing_debug_implementations)] #![deny(missing_docs)] #![cfg_attr(clippy, deny(warnings))] use glutin::config::{Config, ConfigTemplateBuilder}; use glutin::display::{Display, DisplayApiPreference}; use glutin::error::Error as GlutinError; #[cfg(x11_platform)] use glutin::platform::x11::X11GlConfigExt; use glutin::prelude::*; use raw_window_handle::{DisplayHandle, RawWindowHandle, WindowHandle}; use crate::windowing::window::CreationAttributes as WindowCreationAttributes; #[cfg(all(not(egl_backend), not(glx_backend), not(wgl_backend), not(cgl_backend)))] compile_error!("Please select at least one api backend"); /// The helper to perform [`Display`] creation and OpenGL platform /// bootstrapping with the help of [`winit`] with little to no platform specific /// code. /// /// This is only required for the initial setup. If you want to create /// additional windows just use the [`finalize_window`] function and the /// configuration you've used either for the original window or picked with the /// existing [`Display`]. /// /// [`winit`]: winit /// [`Display`]: glutin::display::Display #[derive(Default, Debug, Clone)] pub struct DisplayBuilder { preference: ApiPreference, window_attributes: WindowCreationAttributes, } impl DisplayBuilder { /// Create new display builder. pub fn new() -> Self { Default::default() } /// The preference in picking the configuration. #[allow(dead_code)] pub fn with_preference(mut self, preference: ApiPreference) -> Self { self.preference = preference; self } /// The window attributes to use when building a window. /// /// By default no window is created. pub fn with_window_attributes( mut self, window_creation_attrs: WindowCreationAttributes, ) -> Self { self.window_attributes = window_creation_attrs; self } /// Initialize the OpenGL platform and create a compatible window to use /// with it when the [`WindowAttributes`] was passed with /// [`Self::with_window_attributes()`]. It's optional, since on some /// platforms like `Android` it is not available early on, so you want to /// find configuration and later use it with the [`finalize_window`]. /// But if you don't care about such platform you can always pass /// [`WindowAttributes`]. /// /// # Api-specific /// /// **WGL:** - [`WindowAttributes`] **must** be passed in /// [`Self::with_window_attributes()`] if modern OpenGL(ES) is desired, /// otherwise only builtin functions like `glClear` will be available. pub fn build( self, window_handle: Option>, display_handle: &DisplayHandle<'_>, template_builder: ConfigTemplateBuilder, config_picker_fn: ConfigPickerFn, ) -> Result<(WindowCreationAttributes, Config), Error> where ConfigPickerFn: FnOnce(Box + '_>) -> Option, { // XXX with WGL backend window should be created first. let raw_window_handle = if cfg!(wgl_backend) { let Some(window_handle) = window_handle else { return Err(Error::WindowRequired); }; Some(window_handle.as_raw()) } else { None }; let gl_display = create_display(display_handle, self.preference, raw_window_handle) .map_err(Error::CreateDisplayFailed)?; // XXX the native window must be passed to config picker when WGL is used // otherwise very limited OpenGL features will be supported. #[cfg(wgl_backend)] let template_builder = if let Some(raw_window_handle) = raw_window_handle { template_builder.compatible_with_native_window(raw_window_handle) } else { template_builder }; let template = template_builder.build(); // SAFETY: The RawWindowHandle passed on the config template // (when cfg(wgl_backend)) will always point to a valid object since it is // derived from the window_handle argument which when Some is a WindowHandle and // WindowHandles always point to a valid object let gl_configs = unsafe { gl_display.find_configs(template) } .map_err(Error::FindConfigsFailed)?; let picked_gl_config = config_picker_fn(gl_configs).ok_or(Error::NoConfigPicked)?; #[cfg(not(wgl_backend))] let window_attrs = { finalize_window_creation_attrs(self.window_attributes, &picked_gl_config) }; #[cfg(wgl_backend)] let window_attrs = self.window_attributes; Ok((window_attrs, picked_gl_config)) } } #[derive(Debug, thiserror::Error)] pub enum Error { #[error("Failed to create display")] CreateDisplayFailed(#[source] GlutinError), #[error("Failed to find configs")] FindConfigsFailed(#[source] GlutinError), #[error("No config was picked by config picker function")] NoConfigPicked, #[error("Window required for building display on current platform")] WindowRequired, } fn create_display( display_handle: &DisplayHandle<'_>, _api_preference: ApiPreference, _raw_window_handle: Option, ) -> Result { #[cfg(egl_backend)] let _preference = DisplayApiPreference::Egl; #[cfg(glx_backend)] let _preference = DisplayApiPreference::Glx(Box::new( crate::windowing::window::platform::x11::register_xlib_error_hook, )); #[cfg(cgl_backend)] let _preference = DisplayApiPreference::Cgl; #[cfg(wgl_backend)] let _preference = DisplayApiPreference::Wgl(_raw_window_handle); #[cfg(all(egl_backend, glx_backend))] let _preference = match _api_preference { ApiPreference::PreferEgl => DisplayApiPreference::EglThenGlx(Box::new( crate::windowing::window::platform::x11::register_xlib_error_hook, )), ApiPreference::FallbackEgl => DisplayApiPreference::GlxThenEgl(Box::new( crate::windowing::window::platform::x11::register_xlib_error_hook, )), }; #[cfg(all(wgl_backend, egl_backend))] let _preference = match _api_preference { ApiPreference::PreferEgl => DisplayApiPreference::EglThenWgl(_raw_window_handle), ApiPreference::FallbackEgl => { DisplayApiPreference::WglThenEgl(_raw_window_handle) } }; let handle = display_handle.as_raw(); unsafe { Ok(Display::new(handle, _preference)?) } } /// Finalize [`Window`] creation by applying the options from the [`Config`], be /// aware that it could remove incompatible options from the window builder like /// `transparency`, when the provided config doesn't support it. /// /// [`Window`]: winit::window::Window /// [`Config`]: glutin::config::Config #[cfg(not(wgl_backend))] fn finalize_window_creation_attrs( mut attributes: WindowCreationAttributes, gl_config: &Config, ) -> WindowCreationAttributes { // Disable transparency if the end config doesn't support it. if gl_config.supports_transparency() == Some(false) { attributes = attributes.with_transparent(false); } #[cfg(x11_platform)] let attributes = if let Some(x11_visual) = gl_config.x11_visual() { attributes.with_x11_visual(x11_visual.visual_id() as _) } else { attributes }; attributes } /// Simplified version of the [`DisplayApiPreference`] which is used to simplify /// cross platform window creation. /// /// To learn about platform differences the [`DisplayApiPreference`] variants. /// /// [`DisplayApiPreference`]: glutin::display::DisplayApiPreference #[allow(dead_code)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum ApiPreference { /// Prefer `EGL` over system provider like `GLX` and `WGL`. PreferEgl, /// Fallback to `EGL` when failed to create the system profile. /// /// This behavior is used by default. However consider using /// [`Self::PreferEgl`] if you don't care about missing EGL features. #[default] FallbackEgl, }