diff --git a/Cargo.lock b/Cargo.lock index 421a5df6c..c808688b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1261,11 +1261,12 @@ dependencies = [ "egui-winit", "egui_glow", "glow", - "glutin", + "glutin 0.30.0", "image", "js-sys", "percent-encoding", "puffin", + "raw-window-handle 0.5.0", "ron", "serde", "tracing", @@ -1399,7 +1400,7 @@ dependencies = [ "egui", "egui-winit", "glow", - "glutin", + "glutin 0.29.1", "memoffset", "puffin", "tracing", @@ -1863,7 +1864,7 @@ dependencies = [ "backtrace", "fnv", "gl_generator", - "glutin", + "glutin 0.29.1", "lazy_static", "memoffset", "smallvec", @@ -1897,10 +1898,10 @@ dependencies = [ "cgl", "cocoa", "core-foundation", - "glutin_egl_sys", + "glutin_egl_sys 0.1.6", "glutin_gles2_sys", - "glutin_glx_sys", - "glutin_wgl_sys", + "glutin_glx_sys 0.1.8", + "glutin_wgl_sys 0.1.5", "libloading", "log", "objc", @@ -1914,6 +1915,29 @@ dependencies = [ "winit", ] +[[package]] +name = "glutin" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34acbf502536f1d125f0fc09b6ad8824e93e6da7b99e86d3383e6b8310ba3554" +dependencies = [ + "bitflags", + "cfg_aliases", + "cgl", + "cocoa", + "core-foundation", + "glutin_egl_sys 0.3.0", + "glutin_glx_sys 0.3.0", + "glutin_wgl_sys 0.3.0", + "libloading", + "objc", + "once_cell", + "raw-window-handle 0.5.0", + "wayland-sys 0.30.0-beta.12", + "windows-sys", + "x11-dl", +] + [[package]] name = "glutin_egl_sys" version = "0.1.6" @@ -1924,6 +1948,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "glutin_egl_sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3c95a2d7a54bab0c74759794efb5cd63470d4504cbe85ed4114dc82c98bdc1" +dependencies = [ + "gl_generator", + "windows-sys", +] + [[package]] name = "glutin_gles2_sys" version = "0.1.5" @@ -1944,6 +1978,16 @@ dependencies = [ "x11-dl", ] +[[package]] +name = "glutin_glx_sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "947c4850c58211c9627969c2b4e2674764b81ae5b47bab2c9a477d7942f96e0f" +dependencies = [ + "gl_generator", + "x11-dl", +] + [[package]] name = "glutin_wgl_sys" version = "0.1.5" @@ -1953,6 +1997,15 @@ dependencies = [ "gl_generator", ] +[[package]] +name = "glutin_wgl_sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20c33975a6c9d49d72c8f032a60079bf8df536954fbf9e4cee90396ace815c57" +dependencies = [ + "gl_generator", +] + [[package]] name = "gobject-sys" version = "0.15.10" @@ -4246,7 +4299,7 @@ dependencies = [ "scoped-tls", "wayland-commons", "wayland-scanner", - "wayland-sys", + "wayland-sys 0.29.4", ] [[package]] @@ -4258,7 +4311,7 @@ dependencies = [ "nix 0.22.3", "once_cell", "smallvec", - "wayland-sys", + "wayland-sys 0.29.4", ] [[package]] @@ -4279,7 +4332,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83281d69ee162b59031c666385e93bde4039ec553b90c4191cdb128ceea29a3a" dependencies = [ "wayland-client", - "wayland-sys", + "wayland-sys 0.29.4", ] [[package]] @@ -4316,6 +4369,18 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "wayland-sys" +version = "0.30.0-beta.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1117fe4570fe063122ba2b1b1e39e56fb1a73921d395f9288af06af0dd1c7f55" +dependencies = [ + "dlib", + "lazy_static", + "log", + "pkg-config", +] + [[package]] name = "web-sys" version = "0.3.60" diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index 68586210b..8fedb9c05 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -87,7 +87,16 @@ egui-winit = { version = "0.19.0", path = "../egui-winit", default-features = fa "clipboard", "links", ] } -glutin = { version = "0.29.0" } +# we can expose these to user so that they can select which backends they want to enable to avoid compiling useless deps. +# this can be done at the same time we expose x11/wayland features of winit crate. +glutin = { version = "0.30.0", features = [ + "egl", + "glx", + "x11", + "wayland", + "wgl", +] } +raw-window-handle = { version = "0.5.0" } winit = "0.27.2" # optional native: @@ -95,7 +104,7 @@ dark-light = { version = "0.2.1", optional = true } directories-next = { version = "2", optional = true } egui-wgpu = { version = "0.19.0", path = "../egui-wgpu", optional = true, features = [ "winit", -] } +] } # if wgpu is used, use it with winit image = { version = "0.24", optional = true, default-features = false, features = [ "png", ] } diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index 9739cde9e..71bf37ec1 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -305,7 +305,145 @@ mod glow_integration { // Conceptually this will be split out eventually so that the rest of the state // can be persistent. - gl_window: glutin::WindowedContext, + gl_window: GlutinWindowContext, + } + struct GlutinWindowContext { + window: winit::window::Window, + gl_context: glutin::context::PossiblyCurrentContext, + gl_display: glutin::display::Display, + gl_surface: glutin::surface::Surface, + } + + impl GlutinWindowContext { + // refactor this function to use `glutin-winit` crate eventually. + // preferably add android support at the same time. + #[allow(unsafe_code)] + unsafe fn new( + winit_window: winit::window::Window, + native_options: &epi::NativeOptions, + ) -> Self { + use glutin::prelude::*; + use raw_window_handle::*; + let hardware_acceleration = match native_options.hardware_acceleration { + crate::HardwareAcceleration::Required => Some(true), + crate::HardwareAcceleration::Preferred => None, + crate::HardwareAcceleration::Off => Some(false), + }; + + let raw_display_handle = winit_window.raw_display_handle(); + let raw_window_handle = winit_window.raw_window_handle(); + + // EGL is crossplatform and the official khronos way + // but sometimes platforms/drivers may not have it, so we use back up options where possible. + // TODO: check whether we can expose these options as "features", so that users can select the relevant backend they want. + + // try egl and fallback to windows wgl. Windows is the only platform that *requires* window handle to create display. + #[cfg(target_os = "windows")] + let preference = glutin::display::DisplayApiPreference::EglThenWgl(Some(window_handle)); + // try egl and fallback to x11 glx + #[cfg(target_os = "linux")] + let preference = glutin::display::DisplayApiPreference::EglThenGlx(Box::new( + winit::platform::unix::register_xlib_error_hook, + )); + #[cfg(target_os = "macos")] + let preference = glutin::display::DisplayApiPreference::Cgl; + #[cfg(target_os = "android")] + let preference = glutin::display::DisplayApiPreference::Egl; + + let gl_display = glutin::display::Display::new(raw_display_handle, preference) + .expect("failed to create glutin display"); + let swap_interval = if native_options.vsync { + glutin::surface::SwapInterval::Wait(std::num::NonZeroU32::new(1).unwrap()) + } else { + glutin::surface::SwapInterval::DontWait + }; + + let config_template = glutin::config::ConfigTemplateBuilder::new() + .prefer_hardware_accelerated(hardware_acceleration) + .with_depth_size(native_options.depth_buffer); + // we don't know if multi sampling option is set. so, check if its more than 0. + let config_template = if native_options.multisampling > 0 { + config_template.with_multisampling( + native_options + .multisampling + .try_into() + .expect("failed to fit multisamples into u8"), + ) + } else { + config_template + }; + let config_template = config_template + .with_stencil_size(native_options.stencil_buffer) + .with_transparency(native_options.transparent) + .compatible_with_native_window(raw_window_handle) + .build(); + // finds all valid configurations supported by this display that match the config_template + // this is where we will try to get a "fallback" config if we are okay with ignoring some native + // options required by user like multi sampling, srgb, transparency etc.. + // TODO: need to figure out a good fallback config template + let config = gl_display + .find_configs(config_template) + .expect("failed to find even a single matching configuration") + .next() + .expect("failed to find a matching configuration for creating opengl context"); + + let context_attributes = + glutin::context::ContextAttributesBuilder::new().build(Some(raw_window_handle)); + // for surface creation. + let (width, height): (u32, u32) = winit_window.inner_size().into(); + let surface_attributes = + glutin::surface::SurfaceAttributesBuilder::::new() + .build( + raw_window_handle, + std::num::NonZeroU32::new(width).unwrap(), + std::num::NonZeroU32::new(height).unwrap(), + ); + // start creating the gl objects + let gl_context = gl_display + .create_context(&config, &context_attributes) + .expect("failed to create opengl context"); + + let gl_surface = gl_display + .create_window_surface(&config, &surface_attributes) + .expect("failed to create glutin window surface"); + let gl_context = gl_context + .make_current(&gl_surface) + .expect("failed to make gl context current"); + gl_surface + .set_swap_interval(&gl_context, swap_interval) + .expect("failed to set vsync swap interval"); + GlutinWindowContext { + window: winit_window, + gl_context, + gl_display, + gl_surface, + } + } + fn window(&self) -> &winit::window::Window { + &self.window + } + fn resize(&self, physical_size: winit::dpi::PhysicalSize) { + use glutin::surface::GlSurface; + self.gl_surface.resize( + &self.gl_context, + physical_size + .width + .try_into() + .expect("physical size must not be zero"), + physical_size + .height + .try_into() + .expect("physical size must not be zero"), + ); + } + fn swap_buffers(&self) -> glutin::error::Result<()> { + use glutin::surface::GlSurface; + self.gl_surface.swap_buffers(&self.gl_context) + } + fn get_proc_address(&self, addr: &std::ffi::CStr) -> *const std::ffi::c_void { + use glutin::display::GlDisplay; + self.gl_display.get_proc_address(addr) + } } struct GlowWinitApp { @@ -347,44 +485,33 @@ mod glow_integration { storage: Option<&dyn epi::Storage>, title: &String, native_options: &NativeOptions, - ) -> ( - glutin::WindowedContext, - glow::Context, - ) { + ) -> (GlutinWindowContext, glow::Context) { crate::profile_function!(); - use crate::HardwareAcceleration; - - let hardware_acceleration = match native_options.hardware_acceleration { - HardwareAcceleration::Required => Some(true), - HardwareAcceleration::Preferred => None, - HardwareAcceleration::Off => Some(false), - }; let window_settings = epi_integration::load_window_settings(storage); let window_builder = epi_integration::window_builder(native_options, &window_settings) .with_title(title) + .with_transparent(native_options.transparent) // Keep hidden until we've painted something. See https://github.com/emilk/egui/pull/2279 // We must also keep the window hidden until AccessKit is initialized. .with_visible(false); - - let gl_window = unsafe { - glutin::ContextBuilder::new() - .with_hardware_acceleration(hardware_acceleration) - .with_depth_buffer(native_options.depth_buffer) - .with_multisampling(native_options.multisampling) - .with_stencil_buffer(native_options.stencil_buffer) - .with_vsync(native_options.vsync) - .build_windowed(window_builder, event_loop) - .unwrap() - .make_current() - .unwrap() + let winit_window = window_builder + .build(event_loop) + .expect("failed to create winit window"); + // a lot of the code below has been lifted from glutin example in their repo. + let glutin_window_context = + unsafe { GlutinWindowContext::new(winit_window, native_options) }; + let gl = unsafe { + glow::Context::from_loader_function(|s| { + let s = std::ffi::CString::new(s) + .expect("failed to construct C string from string for gl proc address"); + + glutin_window_context.get_proc_address(&s) + }) }; - let gl = - unsafe { glow::Context::from_loader_function(|s| gl_window.get_proc_address(s)) }; - - (gl_window, gl) + (glutin_window_context, gl) } fn init_run_state(&mut self, event_loop: &EventLoopWindowTarget) {