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 | |