From d9d99ec4a852381b4a695ccff13d15ceb8632c29 Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Thu, 26 May 2022 16:47:05 +0200 Subject: [PATCH 1/3] Add Android example Make the shared event loop suitable for creating and destroying windows in accordance with `winit`s `Resumed` and `Suspended` events. --- README.md | 10 +- glutin/src/api/egl/display.rs | 2 +- glutin_examples/Cargo.toml | 7 ++ glutin_examples/examples/android.rs | 6 + glutin_examples/src/lib.rs | 182 +++++++++++++++++----------- glutin_wgl_sys/src/lib.rs | 2 +- 6 files changed, 134 insertions(+), 75 deletions(-) create mode 100644 glutin_examples/examples/android.rs diff --git a/README.md b/README.md index 1f91a71425..358c9edac8 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ cargo run --example window Glutin is an OpenGL context creation library and doesn't directly provide OpenGL bindings for you. -For examples, please look [here.](https://github.com/rust-windowing/glutin/tree/master/glutin_examples) +For examples, please look [here](https://github.com/rust-windowing/glutin/tree/master/glutin_examples). Note that glutin aims at being a low-level brick in your rendering infrastructure. You are encouraged to write another layer of abstraction @@ -52,6 +52,10 @@ The minimum rust version target by glutin is `1.60.0`. ### Android -To compile the examples for android, you have to use the `cargo apk` utility. +Be sure to handle Android's lifecycle correctly when using a `winit` window by only creating a GL surface after `winit` raises `Event::Resumed`, and destroy it again upon receiving `Event::Suspended`. See this in action in the [`android.rs` example](./glutin_examples/examples/android.rs). -See [`cargo-apk` in the `android-ndk-rs` repository](https://github.com/rust-windowing/android-ndk-rs/tree/master/cargo-apk) for instructions. +To compile and run the Android example on your device, install [`cargo-apk`](https://crates.io/crates/cargo-apk) and start the app using: + +```console +$ cargo apk r -p glutin_examples --example android +``` diff --git a/glutin/src/api/egl/display.rs b/glutin/src/api/egl/display.rs index a529c724be..2f8ab85deb 100644 --- a/glutin/src/api/egl/display.rs +++ b/glutin/src/api/egl/display.rs @@ -45,7 +45,7 @@ impl Display { /// `raw_display` must point to a valid system display. Using zero or /// `[std::ptr::null]` for the display will result in using /// `EGL_DEFAULT_DISPLAY`, which is not recommended or will - /// work on a platfrom with a concept of native display, like Wayland. + /// work on a platform with a concept of native display, like Wayland. pub unsafe fn from_raw(raw_display: RawDisplayHandle) -> Result { let egl = match EGL.as_ref() { Some(egl) => egl, diff --git a/glutin_examples/Cargo.toml b/glutin_examples/Cargo.toml index 24e0429fda..d426da0a49 100644 --- a/glutin_examples/Cargo.toml +++ b/glutin_examples/Cargo.toml @@ -23,6 +23,13 @@ glutin = { path = "../glutin", default-features = false } winit = { version = "0.27.2", default-features = false } raw-window-handle = "0.5.0" +[target.'cfg(target_os = "android")'.dependencies] +ndk-glue = "0.7" # Keep in sync with winit dependency + [build-dependencies] gl_generator = "0.14" cfg_aliases = "0.1.1" + +[[example]] +name = "android" +crate-type = ["cdylib"] diff --git a/glutin_examples/examples/android.rs b/glutin_examples/examples/android.rs new file mode 100644 index 0000000000..2dd60ee238 --- /dev/null +++ b/glutin_examples/examples/android.rs @@ -0,0 +1,6 @@ +#![cfg(target_os = "android")] + +#[ndk_glue::main(backtrace = "on")] +fn main() { + glutin_examples::main() +} diff --git a/glutin_examples/src/lib.rs b/glutin_examples/src/lib.rs index 5d90dece19..45da186664 100644 --- a/glutin_examples/src/lib.rs +++ b/glutin_examples/src/lib.rs @@ -28,11 +28,16 @@ pub fn main() { let raw_display = event_loop.raw_display_handle(); - // We create a window before the display to accomodate for WGL, since it - // requires creating HDC for properly loading the WGL and it should be taken - // from the window you'll be rendering into. - let window = WindowBuilder::new().with_transparent(true).build(&event_loop).unwrap(); - let raw_window_handle = window.raw_window_handle(); + let mut window = cfg!(any(wgl_backend, glx_backend)).then(|| { + // We create a window before the display to accommodate for WGL, since it + // requires creating HDC for properly loading the WGL and it should be taken + // from the window you'll be rendering into. + + // On GLX the window should be available for config_template() + find_configs() + // to propagate its Xlib visual ID. + WindowBuilder::new().with_transparent(true).build(&event_loop).unwrap() + }); + let raw_window_handle = window.as_ref().map(|w| w.raw_window_handle()); // Create the GL display. This will create display automatically for the // underlying GL platform. See support module on how it's being done. @@ -41,73 +46,101 @@ pub fn main() { // Create the config we'll be used for window. We'll use the native window // raw-window-handle for it to get the right visual and use proper hdc. Note // that you can likely use it for other windows using the same config. - let template = config_template(window.raw_window_handle()); - let config = unsafe { - gl_display - .find_configs(template) - .unwrap() - .reduce(|accum, config| { - // Find the config with the maximum number of samples. - // - // In general if you're not sure what you want in template you can request or - // don't want to require multisampling for example, you can search for a - // specific option you want afterwards. - // - // XXX however on macOS you can request only one config, so you should do - // a search with the help of `find_configs` and adjusting your template. - if config.num_samples() > accum.num_samples() { - config - } else { - accum - } - }) - .unwrap() - }; + let template = config_template(raw_window_handle); + let config = unsafe { gl_display.find_configs(template) } + .unwrap() + .reduce(|accum, config| { + // Find the config with the maximum number of samples. + // + // In general if you're not sure what you want in template you can request or + // don't want to require multisampling for example, you can search for a + // specific option you want afterwards. + // + // XXX however on macOS you can request only one config, so you should do + // a search with the help of `find_configs` and adjusting your template. + if config.num_samples() > accum.num_samples() { + config + } else { + accum + } + }) + .unwrap(); println!("Picked a config with {} samples", config.num_samples()); - // Create a wrapper for GL window and surface. - let gl_window = GlWindow::from_existing(&gl_display, window, &config); - // The context creation part. It can be created before surface and that's how // it's expected in multithreaded + multiwindow operation mode, since you // can send NotCurrentContext, but not Surface. - let context_attributes = ContextAttributesBuilder::new().build(Some(raw_window_handle)); + let context_attributes = ContextAttributesBuilder::new().build(raw_window_handle); // Since glutin by default tries to create OpenGL core context, which may not be // present we should try gles. let fallback_context_attributes = ContextAttributesBuilder::new() .with_context_api(ContextApi::Gles(None)) - .build(Some(raw_window_handle)); - let gl_context = unsafe { + .build(raw_window_handle); + let mut not_current_gl_context = Some(unsafe { gl_display.create_context(&config, &context_attributes).unwrap_or_else(|_| { gl_display .create_context(&config, &fallback_context_attributes) .expect("failed to create context") }) - }; - - // Make it current and load symbols. - let gl_context = gl_context.make_current(&gl_window.surface).unwrap(); - - // WGL requires current context on the calling thread to load symbols properly, - // so the call here is for portability reasons. In case you don't target WGL - // you can call it right after display creation. - // - // The symbol loading is done by the renderer. - let renderer = Renderer::new(&gl_display); - - // Try setting vsync. - if let Err(res) = gl_window - .surface - .set_swap_interval(&gl_context, SwapInterval::Wait(NonZeroU32::new(1).unwrap())) - { - eprintln!("Error setting vsync: {:?}", res); - } + }); + + let mut state = None; + let mut renderer = None; - event_loop.run(move |event, _, control_flow| { + event_loop.run(move |event, event_loop_window_target, control_flow| { *control_flow = ControlFlow::Wait; match event { + Event::Resumed => { + // While this event is only relevant for Android, it is raised on all platforms + // to provide a consistent place to create windows + + #[cfg(target_os = "android")] + println!("Android window available"); + + // Take a possibly early created window, or create a new one + let window = window.take().unwrap_or_else(|| { + WindowBuilder::new().build(event_loop_window_target).unwrap() + }); + + // Create a wrapper for GL window and surface. + let gl_window = GlWindow::from_existing(&gl_display, window, &config); + + // Make it current. + let gl_context = not_current_gl_context + .take() + .unwrap() + .make_current(&gl_window.surface) + .unwrap(); + + // The context needs to be current for the Renderer to set up shaders and + // buffers. It also performs function loading, which needs a current context on + // WGL. + renderer.get_or_insert_with(|| Renderer::new(&gl_display)); + + // Try setting vsync. + if let Err(res) = gl_window + .surface + .set_swap_interval(&gl_context, SwapInterval::Wait(NonZeroU32::new(1).unwrap())) + { + eprintln!("Error setting vsync: {:?}", res); + } + + assert!(state.replace((gl_context, gl_window)).is_none()); + }, + Event::Suspended => { + // This event is only raised on Android, where the backing NativeWindow for a GL + // Surface can appear and disappear at any moment. + println!("Android window removed"); + // Destroy the GL Surface and un-current the GL Context before ndk-glue releases + // the window back to the system. + let (gl_context, _) = state.take().unwrap(); + assert!(not_current_gl_context + .replace(gl_context.make_not_current().unwrap()) + .is_none()); + }, + Event::WindowEvent { event, .. } => match event { WindowEvent::Resized(size) => { if size.width != 0 && size.height != 0 { @@ -115,12 +148,15 @@ pub fn main() { // Notable platforms here are Wayland and macOS, other don't require it // and the function is no-op, but it's wise to resize it for portability // reasons. - gl_window.surface.resize( - &gl_context, - NonZeroU32::new(size.width).unwrap(), - NonZeroU32::new(size.height).unwrap(), - ); - renderer.resize(size.width as i32, size.height as i32); + if let Some((gl_context, gl_window)) = &state { + gl_window.surface.resize( + gl_context, + NonZeroU32::new(size.width).unwrap(), + NonZeroU32::new(size.height).unwrap(), + ); + let renderer = renderer.as_ref().unwrap(); + renderer.resize(size.width as i32, size.height as i32); + } } }, WindowEvent::CloseRequested => { @@ -129,10 +165,13 @@ pub fn main() { _ => (), }, Event::RedrawEventsCleared => { - renderer.draw(); - gl_window.window.request_redraw(); + if let Some((gl_context, gl_window)) = &state { + let renderer = renderer.as_ref().unwrap(); + renderer.draw(); + gl_window.window.request_redraw(); - gl_window.surface.swap_buffers(&gl_context).unwrap(); + gl_window.surface.swap_buffers(gl_context).unwrap(); + } }, _ => (), } @@ -168,11 +207,14 @@ impl GlWindow { } /// Create template to find OpenGL config. -pub fn config_template(raw_window_handle: RawWindowHandle) -> ConfigTemplate { - let builder = ConfigTemplateBuilder::new() - .with_alpha_size(8) - .compatible_with_native_window(raw_window_handle) - .with_surface_type(ConfigSurfaceTypes::WINDOW); +pub fn config_template(raw_window_handle: Option) -> ConfigTemplate { + let mut builder = ConfigTemplateBuilder::new().with_alpha_size(8); + + if let Some(raw_window_handle) = raw_window_handle { + builder = builder + .compatible_with_native_window(raw_window_handle) + .with_surface_type(ConfigSurfaceTypes::WINDOW); + } #[cfg(cgl_backend)] let builder = builder.with_transparency(true).with_multisampling(8); @@ -194,7 +236,7 @@ pub fn surface_attributes(window: &Window) -> SurfaceAttributes { /// Create the display. pub fn create_display( raw_display: RawDisplayHandle, - raw_window_handle: RawWindowHandle, + raw_window_handle: Option, ) -> Display { #[cfg(egl_backend)] let preference = DisplayApiPreference::Egl; @@ -206,10 +248,10 @@ pub fn create_display( let preference = DisplayApiPreference::Cgl; #[cfg(wgl_backend)] - let preference = DisplayApiPreference::Wgl(Some(raw_window_handle)); + let preference = DisplayApiPreference::Wgl(Some(raw_window_handle.unwrap())); #[cfg(all(egl_backend, wgl_backend))] - let preference = DisplayApiPreference::WglThenEgl(Some(raw_window_handle)); + let preference = DisplayApiPreference::WglThenEgl(Some(raw_window_handle.unwrap())); #[cfg(all(egl_backend, glx_backend))] let preference = DisplayApiPreference::GlxThenEgl(Box::new(unix::register_xlib_error_hook)); diff --git a/glutin_wgl_sys/src/lib.rs b/glutin_wgl_sys/src/lib.rs index 777ee82d20..76df2ac12a 100644 --- a/glutin_wgl_sys/src/lib.rs +++ b/glutin_wgl_sys/src/lib.rs @@ -1,4 +1,4 @@ -#![cfg(any(target_os = "windows"))] +#![cfg(target_os = "windows")] #![allow(clippy::too_many_arguments)] #![allow(clippy::missing_safety_doc)] #![allow(clippy::manual_non_exhaustive)] From 15cbd6232cec681b154cc90fda4b7e42fc366ab8 Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Thu, 29 Sep 2022 18:03:05 +0200 Subject: [PATCH 2/3] Simplify all `target_os = "windows"` cfs to `windows` --- glutin/Cargo.toml | 4 ++-- glutin_egl_sys/Cargo.toml | 2 +- glutin_egl_sys/src/lib.rs | 4 ++-- glutin_wgl_sys/src/lib.rs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/glutin/Cargo.toml b/glutin/Cargo.toml index d96c4dbd57..4474aab69f 100644 --- a/glutin/Cargo.toml +++ b/glutin/Cargo.toml @@ -25,11 +25,11 @@ libloading = { version = "0.7.3", optional = true } once_cell = "1.13" raw-window-handle = "0.5.0" -[target.'cfg(target_os = "windows")'.dependencies] +[target.'cfg(windows)'.dependencies] glutin_egl_sys = { version = "0.2.0", path = "../glutin_egl_sys", optional = true } glutin_wgl_sys = { version = "0.2.0", path = "../glutin_wgl_sys", optional = true } -[target.'cfg(target_os = "windows")'.dependencies.windows-sys] +[target.'cfg(windows)'.dependencies.windows-sys] version = "0.36" features = [ "Win32_Foundation", diff --git a/glutin_egl_sys/Cargo.toml b/glutin_egl_sys/Cargo.toml index 058537ef48..2c5557442e 100644 --- a/glutin_egl_sys/Cargo.toml +++ b/glutin_egl_sys/Cargo.toml @@ -12,7 +12,7 @@ edition = "2021" [build-dependencies] gl_generator = "0.14" -[target.'cfg(target_os = "windows")'.dependencies.windows-sys] +[target.'cfg(windows)'.dependencies.windows-sys] version = "0.36" features = [ "Win32_Foundation", diff --git a/glutin_egl_sys/src/lib.rs b/glutin_egl_sys/src/lib.rs index 48494bb368..d291001cb5 100644 --- a/glutin_egl_sys/src/lib.rs +++ b/glutin_egl_sys/src/lib.rs @@ -1,5 +1,5 @@ #![cfg(any( - target_os = "windows", + windows, target_os = "linux", target_os = "android", target_os = "dragonfly", @@ -43,7 +43,7 @@ pub type EGLenum = raw::c_uint; pub type EGLNativeDisplayType = *const raw::c_void; pub type EGLNativePixmapType = *const raw::c_void; // FIXME: egl_native_pixmap_t instead -#[cfg(target_os = "windows")] +#[cfg(windows)] pub type EGLNativeWindowType = windows_sys::Win32::Foundation::HWND; #[cfg(target_os = "linux")] pub type EGLNativeWindowType = *const raw::c_void; diff --git a/glutin_wgl_sys/src/lib.rs b/glutin_wgl_sys/src/lib.rs index 76df2ac12a..c9d8048b77 100644 --- a/glutin_wgl_sys/src/lib.rs +++ b/glutin_wgl_sys/src/lib.rs @@ -1,4 +1,4 @@ -#![cfg(target_os = "windows")] +#![cfg(windows)] #![allow(clippy::too_many_arguments)] #![allow(clippy::missing_safety_doc)] #![allow(clippy::manual_non_exhaustive)] From de8557a6d602488d94dc1a8554cf41adf89269e7 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Tue, 4 Oct 2022 10:09:17 +0300 Subject: [PATCH 3/3] Fix x11 window creation --- glutin_examples/src/lib.rs | 53 +++++++++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/glutin_examples/src/lib.rs b/glutin_examples/src/lib.rs index 45da186664..6cb1a1c776 100644 --- a/glutin_examples/src/lib.rs +++ b/glutin_examples/src/lib.rs @@ -10,14 +10,18 @@ use raw_window_handle::{ }; use winit::event::{Event, WindowEvent}; -use winit::event_loop::{ControlFlow, EventLoop}; +use winit::event_loop::EventLoop; #[cfg(glx_backend)] use winit::platform::unix; +#[cfg(x11_platform)] +use winit::platform::unix::WindowBuilderExtUnix; use winit::window::{Window, WindowBuilder}; use glutin::config::{Config, ConfigSurfaceTypes, ConfigTemplate, ConfigTemplateBuilder}; use glutin::context::{ContextApi, ContextAttributesBuilder}; use glutin::display::{Display, DisplayApiPreference}; +#[cfg(x11_platform)] +use glutin::platform::x11::X11GlConfigExt; use glutin::prelude::*; use glutin::surface::{ Surface, SurfaceAttributes, SurfaceAttributesBuilder, SwapInterval, WindowSurface, @@ -28,13 +32,10 @@ pub fn main() { let raw_display = event_loop.raw_display_handle(); - let mut window = cfg!(any(wgl_backend, glx_backend)).then(|| { + let mut window = cfg!(wgl_backend).then(|| { // We create a window before the display to accommodate for WGL, since it // requires creating HDC for properly loading the WGL and it should be taken // from the window you'll be rendering into. - - // On GLX the window should be available for config_template() + find_configs() - // to propagate its Xlib visual ID. WindowBuilder::new().with_transparent(true).build(&event_loop).unwrap() }); let raw_window_handle = window.as_ref().map(|w| w.raw_window_handle()); @@ -58,7 +59,21 @@ pub fn main() { // // XXX however on macOS you can request only one config, so you should do // a search with the help of `find_configs` and adjusting your template. - if config.num_samples() > accum.num_samples() { + + // Since we try to show off transparency try to pick the config that supports it + // on X11 over the ones without it. XXX Configs that support + // transparency on X11 tend to not have multisapmling, so be aware + // of that. + + #[cfg(x11_platform)] + let transparency_check = + config.x11_visual().map(|v| v.supports_transparency()).unwrap_or(false) + & !accum.x11_visual().map(|v| v.supports_transparency()).unwrap_or(false); + + #[cfg(not(x11_platform))] + let transparency_check = false; + + if transparency_check || config.num_samples() > accum.num_samples() { config } else { accum @@ -90,7 +105,7 @@ pub fn main() { let mut renderer = None; event_loop.run(move |event, event_loop_window_target, control_flow| { - *control_flow = ControlFlow::Wait; + control_flow.set_wait(); match event { Event::Resumed => { // While this event is only relevant for Android, it is raised on all platforms @@ -101,7 +116,26 @@ pub fn main() { // Take a possibly early created window, or create a new one let window = window.take().unwrap_or_else(|| { - WindowBuilder::new().build(event_loop_window_target).unwrap() + // On X11 opacity is controlled by the visual we pass to the window latter on, + // other platforms decide on that by what you draw, so there's no need to pass + // this information to the window. + #[cfg(not(cgl_backend))] + let window = WindowBuilder::new(); + + // Request opacity for window on macOS explicitly. + #[cfg(cgl_backend)] + let window = WindowBuilder::new().with_transparent(true); + + // We must pass the visual into the X11 window upon creation, otherwise we + // could have mismatch errors during context activation and swap buffers. + #[cfg(x11_platform)] + let window = if let Some(visual) = config.x11_visual() { + window.with_x11_visual(visual.into_raw()) + } else { + window + }; + + window.build(event_loop_window_target).unwrap() }); // Create a wrapper for GL window and surface. @@ -133,6 +167,7 @@ pub fn main() { // This event is only raised on Android, where the backing NativeWindow for a GL // Surface can appear and disappear at any moment. println!("Android window removed"); + // Destroy the GL Surface and un-current the GL Context before ndk-glue releases // the window back to the system. let (gl_context, _) = state.take().unwrap(); @@ -160,7 +195,7 @@ pub fn main() { } }, WindowEvent::CloseRequested => { - *control_flow = ControlFlow::Exit; + control_flow.set_exit(); }, _ => (), },