Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Android example #1417

Merged
merged 3 commits into from
Oct 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
```
4 changes: 2 additions & 2 deletions glutin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion glutin/src/api/egl/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Self> {
let egl = match EGL.as_ref() {
Some(egl) => egl,
Expand Down
2 changes: 1 addition & 1 deletion glutin_egl_sys/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 2 additions & 2 deletions glutin_egl_sys/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#![cfg(any(
target_os = "windows",
windows,
target_os = "linux",
target_os = "android",
target_os = "dragonfly",
Expand Down Expand Up @@ -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;
Expand Down
7 changes: 7 additions & 0 deletions glutin_examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
MarijnS95 marked this conversation as resolved.
Show resolved Hide resolved
6 changes: 6 additions & 0 deletions glutin_examples/examples/android.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#![cfg(target_os = "android")]

#[ndk_glue::main(backtrace = "on")]
fn main() {
glutin_examples::main()
}
223 changes: 150 additions & 73 deletions glutin_examples/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -28,11 +32,13 @@ 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!(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.
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.
Expand All @@ -41,98 +47,166 @@ 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.

// 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
}
})
.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();
Comment on lines -90 to -91
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kchibisov If I understood things correctly make_current() now doesn't load any functions anymore, that's done by Renderer::new() right? Or does it load a limited set of functions?


// 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| {
*control_flow = ControlFlow::Wait;
event_loop.run(move |event, event_loop_window_target, control_flow| {
control_flow.set_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(|| {
// 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.
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 {
// Some platforms like EGL require resizing GL surface to update the size
// 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 => {
*control_flow = ControlFlow::Exit;
control_flow.set_exit();
},
_ => (),
},
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();
}
},
_ => (),
}
Expand Down Expand Up @@ -168,11 +242,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<RawWindowHandle>) -> 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);
Expand All @@ -194,7 +271,7 @@ pub fn surface_attributes(window: &Window) -> SurfaceAttributes<WindowSurface> {
/// Create the display.
pub fn create_display(
raw_display: RawDisplayHandle,
raw_window_handle: RawWindowHandle,
raw_window_handle: Option<RawWindowHandle>,
) -> Display {
#[cfg(egl_backend)]
let preference = DisplayApiPreference::Egl;
Expand All @@ -206,10 +283,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()));
MarijnS95 marked this conversation as resolved.
Show resolved Hide resolved

#[cfg(all(egl_backend, glx_backend))]
let preference = DisplayApiPreference::GlxThenEgl(Box::new(unix::register_xlib_error_hook));
Expand Down
Loading