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
11mod event_loop;
12mod window;
13
14use event_loop::GlutinEventLoop;
15pub use window::GlWindow;
16
17use std::error::Error;
18
19use glutin::config::{Config, ConfigTemplateBuilder};
20use glutin::display::{Display, DisplayApiPreference};
21#[cfg(x11_platform)]
22use glutin::platform::x11::X11GlConfigExt;
23use glutin::prelude::*;
24
25#[cfg(wgl_backend)]
26use raw_window_handle::HasWindowHandle;
27
28use raw_window_handle::RawWindowHandle;
29use winit::error::OsError;
30use winit::window::{Window, WindowAttributes};
31
32#[cfg(glx_backend)]
33use winit::platform::x11::register_xlib_error_hook;
34#[cfg(x11_platform)]
35use winit::platform::x11::WindowAttributesExtX11;
36
37#[cfg(all(not(egl_backend), not(glx_backend), not(wgl_backend), not(cgl_backend)))]
38compile_error!("Please select at least one api backend");
39
40pub(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)]
59pub struct DisplayBuilder {
60 preference: ApiPreference,
61 window_attributes: Option<WindowAttributes>,
62}
63
64impl 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
151fn 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
194pub 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)]
221pub 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