diff options
Diffstat (limited to 'engine/src/renderer/opengl/glutin_compat.rs')
| -rw-r--r-- | engine/src/renderer/opengl/glutin_compat.rs | 268 | 
1 files changed, 268 insertions, 0 deletions
diff --git a/engine/src/renderer/opengl/glutin_compat.rs b/engine/src/renderer/opengl/glutin_compat.rs new file mode 100644 index 0000000..cfd6ea7 --- /dev/null +++ b/engine/src/renderer/opengl/glutin_compat.rs @@ -0,0 +1,268 @@ +// Original file: +//   https://github.com/rust-windowing/glutin/blob/ +//   0433af9018febe0696c485ed9d66c40dad41f2d4/glutin-winit/src/lib.rs +// +// Copyright © 2022 Kirill Chibisov +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the “Software”), to deal +// in the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +//! This library provides helpers for cross-platform [`glutin`] bootstrapping +//! with [`winit`]. + +#![deny(rust_2018_idioms)] +#![deny(rustdoc::broken_intra_doc_links)] +#![deny(clippy::all)] +#![deny(missing_debug_implementations)] +#![deny(missing_docs)] +#![cfg_attr(clippy, deny(warnings))] + +use glutin::config::{Config, ConfigTemplateBuilder}; +use glutin::display::{Display, DisplayApiPreference}; +use glutin::error::Error as GlutinError; +#[cfg(x11_platform)] +use glutin::platform::x11::X11GlConfigExt; +use glutin::prelude::*; +use raw_window_handle::{DisplayHandle, RawWindowHandle, WindowHandle}; + +use crate::windowing::window::CreationAttributes as WindowCreationAttributes; + +#[cfg(all(not(egl_backend), not(glx_backend), not(wgl_backend), not(cgl_backend)))] +compile_error!("Please select at least one api backend"); + +/// The helper to perform [`Display`] creation and OpenGL platform +/// bootstrapping with the help of [`winit`] with little to no platform specific +/// code. +/// +/// This is only required for the initial setup. If you want to create +/// additional windows just use the [`finalize_window`] function and the +/// configuration you've used either for the original window or picked with the +/// existing [`Display`]. +/// +/// [`winit`]: winit +/// [`Display`]: glutin::display::Display +#[derive(Default, Debug, Clone)] +pub struct DisplayBuilder +{ +    preference: ApiPreference, +    window_attributes: WindowCreationAttributes, +} + +impl DisplayBuilder +{ +    /// Create new display builder. +    pub fn new() -> Self +    { +        Default::default() +    } + +    /// The preference in picking the configuration. +    #[allow(dead_code)] +    pub fn with_preference(mut self, preference: ApiPreference) -> Self +    { +        self.preference = preference; +        self +    } + +    /// The window attributes to use when building a window. +    /// +    /// By default no window is created. +    pub fn with_window_attributes( +        mut self, +        window_creation_attrs: WindowCreationAttributes, +    ) -> Self +    { +        self.window_attributes = window_creation_attrs; +        self +    } + +    /// Initialize the OpenGL platform and create a compatible window to use +    /// with it when the [`WindowAttributes`] was passed with +    /// [`Self::with_window_attributes()`]. It's optional, since on some +    /// platforms like `Android` it is not available early on, so you want to +    /// find configuration and later use it with the [`finalize_window`]. +    /// But if you don't care about such platform you can always pass +    /// [`WindowAttributes`]. +    /// +    /// # Api-specific +    /// +    /// **WGL:** - [`WindowAttributes`] **must** be passed in +    /// [`Self::with_window_attributes()`] if modern OpenGL(ES) is desired, +    /// otherwise only builtin functions like `glClear` will be available. +    pub fn build<ConfigPickerFn>( +        self, +        window_handle: Option<WindowHandle<'_>>, +        display_handle: &DisplayHandle<'_>, +        template_builder: ConfigTemplateBuilder, +        config_picker_fn: ConfigPickerFn, +    ) -> Result<(WindowCreationAttributes, Config), Error> +    where +        ConfigPickerFn: FnOnce(Box<dyn Iterator<Item = Config> + '_>) -> Option<Config>, +    { +        // XXX with WGL backend window should be created first. +        let raw_window_handle = if cfg!(wgl_backend) { +            let Some(window_handle) = window_handle else { +                return Err(Error::WindowRequired); +            }; + +            Some(window_handle.as_raw()) +        } else { +            None +        }; + +        let gl_display = +            create_display(display_handle, self.preference, raw_window_handle) +                .map_err(Error::CreateDisplayFailed)?; + +        // XXX the native window must be passed to config picker when WGL is used +        // otherwise very limited OpenGL features will be supported. +        #[cfg(wgl_backend)] +        let template_builder = if let Some(raw_window_handle) = raw_window_handle { +            template_builder.compatible_with_native_window(raw_window_handle) +        } else { +            template_builder +        }; + +        let template = template_builder.build(); + +        // SAFETY: The RawWindowHandle passed on the config template +        // (when cfg(wgl_backend)) will always point to a valid object since it is +        // derived from the window_handle argument which when Some is a WindowHandle and +        // WindowHandles always point to a valid object +        let gl_configs = unsafe { gl_display.find_configs(template) } +            .map_err(Error::FindConfigsFailed)?; + +        let picked_gl_config = +            config_picker_fn(gl_configs).ok_or(Error::NoConfigPicked)?; + +        #[cfg(not(wgl_backend))] +        let window_attrs = +            { finalize_window_creation_attrs(self.window_attributes, &picked_gl_config) }; + +        #[cfg(wgl_backend)] +        let window_attrs = self.window_attributes; + +        Ok((window_attrs, picked_gl_config)) +    } +} + +#[derive(Debug, thiserror::Error)] +pub enum Error +{ +    #[error("Failed to create display")] +    CreateDisplayFailed(#[source] GlutinError), + +    #[error("Failed to find configs")] +    FindConfigsFailed(#[source] GlutinError), + +    #[error("No config was picked by config picker function")] +    NoConfigPicked, + +    #[error("Window required for building display on current platform")] +    WindowRequired, +} + +fn create_display( +    display_handle: &DisplayHandle<'_>, +    _api_preference: ApiPreference, +    _raw_window_handle: Option<RawWindowHandle>, +) -> Result<Display, GlutinError> +{ +    #[cfg(egl_backend)] +    let _preference = DisplayApiPreference::Egl; + +    #[cfg(glx_backend)] +    let _preference = DisplayApiPreference::Glx(Box::new( +        crate::windowing::window::platform::x11::register_xlib_error_hook, +    )); + +    #[cfg(cgl_backend)] +    let _preference = DisplayApiPreference::Cgl; + +    #[cfg(wgl_backend)] +    let _preference = DisplayApiPreference::Wgl(_raw_window_handle); + +    #[cfg(all(egl_backend, glx_backend))] +    let _preference = match _api_preference { +        ApiPreference::PreferEgl => DisplayApiPreference::EglThenGlx(Box::new( +            crate::windowing::window::platform::x11::register_xlib_error_hook, +        )), +        ApiPreference::FallbackEgl => DisplayApiPreference::GlxThenEgl(Box::new( +            crate::windowing::window::platform::x11::register_xlib_error_hook, +        )), +    }; + +    #[cfg(all(wgl_backend, egl_backend))] +    let _preference = match _api_preference { +        ApiPreference::PreferEgl => DisplayApiPreference::EglThenWgl(_raw_window_handle), +        ApiPreference::FallbackEgl => { +            DisplayApiPreference::WglThenEgl(_raw_window_handle) +        } +    }; + +    let handle = display_handle.as_raw(); +    unsafe { Ok(Display::new(handle, _preference)?) } +} + +/// Finalize [`Window`] creation by applying the options from the [`Config`], be +/// aware that it could remove incompatible options from the window builder like +/// `transparency`, when the provided config doesn't support it. +/// +/// [`Window`]: winit::window::Window +/// [`Config`]: glutin::config::Config +#[cfg(not(wgl_backend))] +fn finalize_window_creation_attrs( +    mut attributes: WindowCreationAttributes, +    gl_config: &Config, +) -> WindowCreationAttributes +{ +    // Disable transparency if the end config doesn't support it. +    if gl_config.supports_transparency() == Some(false) { +        attributes = attributes.with_transparent(false); +    } + +    #[cfg(x11_platform)] +    let attributes = if let Some(x11_visual) = gl_config.x11_visual() { +        attributes.with_x11_visual(x11_visual.visual_id() as _) +    } else { +        attributes +    }; + +    attributes +} + +/// Simplified version of the [`DisplayApiPreference`] which is used to simplify +/// cross platform window creation. +/// +/// To learn about platform differences the [`DisplayApiPreference`] variants. +/// +/// [`DisplayApiPreference`]: glutin::display::DisplayApiPreference +#[allow(dead_code)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum ApiPreference +{ +    /// Prefer `EGL` over system provider like `GLX` and `WGL`. +    PreferEgl, + +    /// Fallback to `EGL` when failed to create the system profile. +    /// +    /// This behavior is used by default. However consider using +    /// [`Self::PreferEgl`] if you don't care about missing EGL features. +    #[default] +    FallbackEgl, +}  | 
