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