| 1 | //! This library provides helpers for cross-platform [`glutin`] bootstrapping |
| 2 | //! with [`winit`]. |
| 3 | |
| 4 | #![deny (rust_2018_idioms)] |
| 5 | #![deny (rustdoc::broken_intra_doc_links)] |
| 6 | #![deny (clippy::all)] |
| 7 | #![deny (missing_debug_implementations)] |
| 8 | #![deny (missing_docs)] |
| 9 | #![cfg_attr (clippy, deny(warnings))] |
| 10 | |
| 11 | mod event_loop; |
| 12 | mod window; |
| 13 | |
| 14 | use event_loop::GlutinEventLoop; |
| 15 | pub use window::GlWindow; |
| 16 | |
| 17 | use std::error::Error; |
| 18 | |
| 19 | use glutin::config::{Config, ConfigTemplateBuilder}; |
| 20 | use glutin::display::{Display, DisplayApiPreference}; |
| 21 | #[cfg (x11_platform)] |
| 22 | use glutin::platform::x11::X11GlConfigExt; |
| 23 | use glutin::prelude::*; |
| 24 | |
| 25 | #[cfg (wgl_backend)] |
| 26 | use raw_window_handle::HasWindowHandle; |
| 27 | |
| 28 | use raw_window_handle::RawWindowHandle; |
| 29 | use winit::error::OsError; |
| 30 | use winit::window::{Window, WindowAttributes}; |
| 31 | |
| 32 | #[cfg (glx_backend)] |
| 33 | use winit::platform::x11::register_xlib_error_hook; |
| 34 | #[cfg (x11_platform)] |
| 35 | use winit::platform::x11::WindowAttributesExtX11; |
| 36 | |
| 37 | #[cfg (all(not(egl_backend), not(glx_backend), not(wgl_backend), not(cgl_backend)))] |
| 38 | compile_error!("Please select at least one api backend" ); |
| 39 | |
| 40 | pub(crate) mod private { |
| 41 | /// Prevent traits from being implemented downstream, since those are used |
| 42 | /// purely for documentation organization and simplify platform api |
| 43 | /// implementation maintenance. |
| 44 | pub trait Sealed {} |
| 45 | } |
| 46 | |
| 47 | /// The helper to perform [`Display`] creation and OpenGL platform |
| 48 | /// bootstrapping with the help of [`winit`] with little to no platform specific |
| 49 | /// code. |
| 50 | /// |
| 51 | /// This is only required for the initial setup. If you want to create |
| 52 | /// additional windows just use the [`finalize_window`] function and the |
| 53 | /// configuration you've used either for the original window or picked with the |
| 54 | /// existing [`Display`]. |
| 55 | /// |
| 56 | /// [`winit`]: winit |
| 57 | /// [`Display`]: glutin::display::Display |
| 58 | #[derive (Default, Debug, Clone)] |
| 59 | pub struct DisplayBuilder { |
| 60 | preference: ApiPreference, |
| 61 | window_attributes: Option<WindowAttributes>, |
| 62 | } |
| 63 | |
| 64 | impl DisplayBuilder { |
| 65 | /// Create new display builder. |
| 66 | pub fn new() -> Self { |
| 67 | Default::default() |
| 68 | } |
| 69 | |
| 70 | /// The preference in picking the configuration. |
| 71 | pub fn with_preference(mut self, preference: ApiPreference) -> Self { |
| 72 | self.preference = preference; |
| 73 | self |
| 74 | } |
| 75 | |
| 76 | /// The window attributes to use when building a window. |
| 77 | /// |
| 78 | /// By default no window is created. |
| 79 | pub fn with_window_attributes(mut self, window_attributes: Option<WindowAttributes>) -> Self { |
| 80 | self.window_attributes = window_attributes; |
| 81 | self |
| 82 | } |
| 83 | |
| 84 | /// Initialize the OpenGL platform and create a compatible window to use |
| 85 | /// with it when the [`WindowAttributes`] was passed with |
| 86 | /// [`Self::with_window_attributes()`]. It's optional, since on some |
| 87 | /// platforms like `Android` it is not available early on, so you want to |
| 88 | /// find configuration and later use it with the [`finalize_window`]. |
| 89 | /// But if you don't care about such platform you can always pass |
| 90 | /// [`WindowAttributes`]. |
| 91 | /// |
| 92 | /// # Api-specific |
| 93 | /// |
| 94 | /// **WGL:** - [`WindowAttributes`] **must** be passed in |
| 95 | /// [`Self::with_window_attributes()`] if modern OpenGL(ES) is desired, |
| 96 | /// otherwise only builtin functions like `glClear` will be available. |
| 97 | pub fn build<Picker>( |
| 98 | mut self, |
| 99 | event_loop: &impl GlutinEventLoop, |
| 100 | template_builder: ConfigTemplateBuilder, |
| 101 | config_picker: Picker, |
| 102 | ) -> Result<(Option<Window>, Config), Box<dyn Error>> |
| 103 | where |
| 104 | Picker: FnOnce(Box<dyn Iterator<Item = Config> + '_>) -> Config, |
| 105 | { |
| 106 | // XXX with WGL backend window should be created first. |
| 107 | #[cfg (wgl_backend)] |
| 108 | let window = if let Some(wa) = self.window_attributes.take() { |
| 109 | Some(event_loop.create_window(wa)?) |
| 110 | } else { |
| 111 | None |
| 112 | }; |
| 113 | |
| 114 | #[cfg (wgl_backend)] |
| 115 | let raw_window_handle = window |
| 116 | .as_ref() |
| 117 | .and_then(|window| window.window_handle().ok()) |
| 118 | .map(|handle| handle.as_raw()); |
| 119 | #[cfg (not(wgl_backend))] |
| 120 | let raw_window_handle = None; |
| 121 | |
| 122 | let gl_display = create_display(event_loop, self.preference, raw_window_handle)?; |
| 123 | |
| 124 | // XXX the native window must be passed to config picker when WGL is used |
| 125 | // otherwise very limited OpenGL features will be supported. |
| 126 | #[cfg (wgl_backend)] |
| 127 | let template_builder = if let Some(raw_window_handle) = raw_window_handle { |
| 128 | template_builder.compatible_with_native_window(raw_window_handle) |
| 129 | } else { |
| 130 | template_builder |
| 131 | }; |
| 132 | |
| 133 | let template = template_builder.build(); |
| 134 | |
| 135 | let gl_config = unsafe { |
| 136 | let configs = gl_display.find_configs(template)?; |
| 137 | config_picker(configs) |
| 138 | }; |
| 139 | |
| 140 | #[cfg (not(wgl_backend))] |
| 141 | let window = if let Some(wa) = self.window_attributes.take() { |
| 142 | Some(finalize_window(event_loop, wa, &gl_config)?) |
| 143 | } else { |
| 144 | None |
| 145 | }; |
| 146 | |
| 147 | Ok((window, gl_config)) |
| 148 | } |
| 149 | } |
| 150 | |
| 151 | fn create_display( |
| 152 | event_loop: &impl GlutinEventLoop, |
| 153 | _api_preference: ApiPreference, |
| 154 | _raw_window_handle: Option<RawWindowHandle>, |
| 155 | ) -> Result<Display, Box<dyn Error>> { |
| 156 | #[cfg (egl_backend)] |
| 157 | let _preference = DisplayApiPreference::Egl; |
| 158 | |
| 159 | #[cfg (glx_backend)] |
| 160 | let _preference = DisplayApiPreference::Glx(Box::new(register_xlib_error_hook)); |
| 161 | |
| 162 | #[cfg (cgl_backend)] |
| 163 | let _preference = DisplayApiPreference::Cgl; |
| 164 | |
| 165 | #[cfg (wgl_backend)] |
| 166 | let _preference = DisplayApiPreference::Wgl(_raw_window_handle); |
| 167 | |
| 168 | #[cfg (all(egl_backend, glx_backend))] |
| 169 | let _preference = match _api_preference { |
| 170 | ApiPreference::PreferEgl => { |
| 171 | DisplayApiPreference::EglThenGlx(Box::new(register_xlib_error_hook)) |
| 172 | }, |
| 173 | ApiPreference::FallbackEgl => { |
| 174 | DisplayApiPreference::GlxThenEgl(Box::new(register_xlib_error_hook)) |
| 175 | }, |
| 176 | }; |
| 177 | |
| 178 | #[cfg (all(wgl_backend, egl_backend))] |
| 179 | let _preference = match _api_preference { |
| 180 | ApiPreference::PreferEgl => DisplayApiPreference::EglThenWgl(_raw_window_handle), |
| 181 | ApiPreference::FallbackEgl => DisplayApiPreference::WglThenEgl(_raw_window_handle), |
| 182 | }; |
| 183 | |
| 184 | let handle = event_loop.glutin_display_handle()?.as_raw(); |
| 185 | unsafe { Ok(Display::new(handle, _preference)?) } |
| 186 | } |
| 187 | |
| 188 | /// Finalize [`Window`] creation by applying the options from the [`Config`], be |
| 189 | /// aware that it could remove incompatible options from the window builder like |
| 190 | /// `transparency`, when the provided config doesn't support it. |
| 191 | /// |
| 192 | /// [`Window`]: winit::window::Window |
| 193 | /// [`Config`]: glutin::config::Config |
| 194 | pub fn finalize_window( |
| 195 | event_loop: &impl GlutinEventLoop, |
| 196 | mut attributes: WindowAttributes, |
| 197 | gl_config: &Config, |
| 198 | ) -> Result<Window, OsError> { |
| 199 | // Disable transparency if the end config doesn't support it. |
| 200 | if gl_config.supports_transparency() == Some(false) { |
| 201 | attributes = attributes.with_transparent(false); |
| 202 | } |
| 203 | |
| 204 | #[cfg (x11_platform)] |
| 205 | let attributes: WindowAttributes = if let Some(x11_visual: X11VisualInfo) = gl_config.x11_visual() { |
| 206 | attributes.with_x11_visual(x11_visual.visual_id() as _) |
| 207 | } else { |
| 208 | attributes |
| 209 | }; |
| 210 | |
| 211 | event_loop.create_window(attributes) |
| 212 | } |
| 213 | |
| 214 | /// Simplified version of the [`DisplayApiPreference`] which is used to simplify |
| 215 | /// cross platform window creation. |
| 216 | /// |
| 217 | /// To learn about platform differences the [`DisplayApiPreference`] variants. |
| 218 | /// |
| 219 | /// [`DisplayApiPreference`]: glutin::display::DisplayApiPreference |
| 220 | #[derive (Debug, Clone, Copy, PartialEq, Eq, Default)] |
| 221 | pub enum ApiPreference { |
| 222 | /// Prefer `EGL` over system provider like `GLX` and `WGL`. |
| 223 | PreferEgl, |
| 224 | |
| 225 | /// Fallback to `EGL` when failed to create the system profile. |
| 226 | /// |
| 227 | /// This behavior is used by default. However consider using |
| 228 | /// [`Self::PreferEgl`] if you don't care about missing EGL features. |
| 229 | #[default] |
| 230 | FallbackEgl, |
| 231 | } |
| 232 | |