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
11mod window;
12
13pub use window::GlWindow;
14
15use std::error::Error;
16
17use glutin::config::{Config, ConfigTemplateBuilder};
18use glutin::display::{Display, DisplayApiPreference};
19#[cfg(x11_platform)]
20use glutin::platform::x11::X11GlConfigExt;
21use glutin::prelude::*;
22
23#[cfg(wgl_backend)]
24use raw_window_handle::HasRawWindowHandle;
25
26use raw_window_handle::{HasRawDisplayHandle, RawWindowHandle};
27use winit::error::OsError;
28use winit::event_loop::EventLoopWindowTarget;
29use winit::window::{Window, WindowBuilder};
30
31#[cfg(glx_backend)]
32use winit::platform::x11::register_xlib_error_hook;
33#[cfg(x11_platform)]
34use winit::platform::x11::WindowBuilderExtX11;
35
36#[cfg(all(not(egl_backend), not(glx_backend), not(wgl_backend), not(cgl_backend)))]
37compile_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)]
51pub struct DisplayBuilder {
52 preference: ApiPreference,
53 window_builder: Option<WindowBuilder>,
54}
55
56impl 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
140fn 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
182pub 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)]
209pub 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