1//! This library provides helpers for cross-platform [`glutin`] bootstrapping
2//! with [`winit`].
34#![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))]
1011mod event_loop;
12mod window;
1314use event_loop::GlutinEventLoop;
15pub use window::GlWindow;
1617use std::error::Error;
1819use glutin::config::{Config, ConfigTemplateBuilder};
20use glutin::display::{Display, DisplayApiPreference};
21#[cfg(x11_platform)]
22use glutin::platform::x11::X11GlConfigExt;
23use glutin::prelude::*;
2425#[cfg(wgl_backend)]
26use raw_window_handle::HasWindowHandle;
2728use raw_window_handle::RawWindowHandle;
29use winit::error::OsError;
30use winit::window::{Window, WindowAttributes};
3132#[cfg(glx_backend)]
33use winit::platform::x11::register_xlib_error_hook;
34#[cfg(x11_platform)]
35use winit::platform::x11::WindowAttributesExtX11;
3637#[cfg(all(not(egl_backend), not(glx_backend), not(wgl_backend), not(cgl_backend)))]
38compile_error!("Please select at least one api backend");
3940pub(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.
44pub trait Sealed {}
45}
4647/// 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}
6364impl DisplayBuilder {
65/// Create new display builder.
66pub fn new() -> Self {
67 Default::default()
68 }
6970/// The preference in picking the configuration.
71pub fn with_preference(mut self, preference: ApiPreference) -> Self {
72self.preference = preference;
73self
74}
7576/// The window attributes to use when building a window.
77 ///
78 /// By default no window is created.
79pub fn with_window_attributes(mut self, window_attributes: Option<WindowAttributes>) -> Self {
80self.window_attributes = window_attributes;
81self
82}
8384/// 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.
97pub fn build<Picker>(
98mut self,
99 event_loop: &impl GlutinEventLoop,
100 template_builder: ConfigTemplateBuilder,
101 config_picker: Picker,
102 ) -> Result<(Option<Window>, Config), Box<dyn Error>>
103where
104Picker: FnOnce(Box<dyn Iterator<Item = Config> + '_>) -> Config,
105 {
106// XXX with WGL backend window should be created first.
107#[cfg(wgl_backend)]
108let window = if let Some(wa) = self.window_attributes.take() {
109Some(event_loop.create_window(wa)?)
110 } else {
111None
112};
113114#[cfg(wgl_backend)]
115let 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))]
120let raw_window_handle = None;
121122let gl_display = create_display(event_loop, self.preference, raw_window_handle)?;
123124// 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)]
127let 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 };
132133let template = template_builder.build();
134135let gl_config = unsafe {
136let configs = gl_display.find_configs(template)?;
137 config_picker(configs)
138 };
139140#[cfg(not(wgl_backend))]
141let window = if let Some(wa) = self.window_attributes.take() {
142Some(finalize_window(event_loop, wa, &gl_config)?)
143 } else {
144None
145};
146147Ok((window, gl_config))
148 }
149}
150151fn 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)]
157let _preference = DisplayApiPreference::Egl;
158159#[cfg(glx_backend)]
160let _preference = DisplayApiPreference::Glx(Box::new(register_xlib_error_hook));
161162#[cfg(cgl_backend)]
163let _preference = DisplayApiPreference::Cgl;
164165#[cfg(wgl_backend)]
166let _preference = DisplayApiPreference::Wgl(_raw_window_handle);
167168#[cfg(all(egl_backend, glx_backend))]
169let _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 };
177178#[cfg(all(wgl_backend, egl_backend))]
179let _preference = match _api_preference {
180 ApiPreference::PreferEgl => DisplayApiPreference::EglThenWgl(_raw_window_handle),
181 ApiPreference::FallbackEgl => DisplayApiPreference::WglThenEgl(_raw_window_handle),
182 };
183184let handle = event_loop.glutin_display_handle()?.as_raw();
185unsafe { Ok(Display::new(handle, _preference)?) }
186}
187188/// 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,
196mut attributes: WindowAttributes,
197 gl_config: &Config,
198) -> Result<Window, OsError> {
199// Disable transparency if the end config doesn't support it.
200if gl_config.supports_transparency() == Some(false) {
201 attributes = attributes.with_transparent(false);
202 }
203204#[cfg(x11_platform)]
205let attributes = if let Some(x11_visual) = gl_config.x11_visual() {
206 attributes.with_x11_visual(x11_visual.visual_id() as _)
207 } else {
208 attributes
209 };
210211 event_loop.create_window(attributes)
212}
213214/// 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`.
223PreferEgl,
224225/// 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]
230FallbackEgl,
231}