Skip to content

Commit

Permalink
Add Android example
Browse files Browse the repository at this point in the history
  • Loading branch information
MarijnS95 committed Sep 20, 2022
1 parent b9c5173 commit a2ee665
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 41 deletions.
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
```
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
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"]
118 changes: 118 additions & 0 deletions glutin_examples/examples/android.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
use std::num::NonZeroU32;

use raw_window_handle::HasRawDisplayHandle;

use winit::event::{Event, WindowEvent};
use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::WindowBuilder;

use glutin::config::ConfigTemplate;
use glutin::context::ContextAttributes;
use glutin::prelude::*;
use glutin::surface::SwapInterval;

mod support;

use support::*;

#[cfg_attr(target_os = "android", ndk_glue::main(backtrace = "on"))]
fn main() {
let event_loop = EventLoop::new();

let raw_display = event_loop.raw_display_handle();

// Create the GL display. This will create display automatically for the
// underlying GL platform. See support module on how it's being done.
let gl_display = create_display(raw_display, None);

let template = ConfigTemplate::default();
let config = unsafe { gl_display.find_configs(template) }.unwrap().next().unwrap();

// 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 = ContextAttributes::default();
let mut not_current_gl_context =
Some(unsafe { gl_display.create_context(&config, &context_attributes) }.unwrap());

let mut state = None;
let mut renderer = None;

event_loop.run(move |event, event_loop_window_target, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::Resumed => {
println!("Android window available");

let window = 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 renderer only needs to be initialized once, but needs the context to be current
// (which in turn requires a window).
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 => {
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.
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;
},
_ => (),
},
Event::RedrawEventsCleared => {
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();
}
},
_ => (),
}
});
}
6 changes: 3 additions & 3 deletions glutin_examples/examples/support/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,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 @@ -84,10 +84,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));
Expand Down
60 changes: 27 additions & 33 deletions glutin_examples/examples/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,41 +19,38 @@ fn main() {

let raw_display = event_loop.raw_display_handle();

// We create a window before the display to accomodate for WGL, since it
// 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.
let window = WindowBuilder::new().with_transparent(true).build(&event_loop).unwrap();
let raw_window_handle = window.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.
let gl_display = create_display(raw_display, raw_window_handle);
let gl_display = create_display(raw_display, Some(raw_window_handle));

// 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 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());

Expand All @@ -70,22 +67,19 @@ fn main() {
let fallback_context_attributes = ContextAttributesBuilder::new()
.with_context_api(ContextApi::Gles(None))
.build(Some(raw_window_handle));
let gl_context = unsafe {
gl_display.create_context(&config, &context_attributes).unwrap_or_else(|_| {
gl_display
.create_context(&config, &fallback_context_attributes)
let gl_context = unsafe { gl_display.create_context(&config, &context_attributes) }
.unwrap_or_else(|_| {
unsafe { 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.
// which is done inside the renderer.
// This call also sets up shaders and buffers which require a current context
// much the same.
let renderer = Renderer::new(&gl_display);

// Try setting vsync.
Expand Down
2 changes: 1 addition & 1 deletion glutin_wgl_sys/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand Down

0 comments on commit a2ee665

Please sign in to comment.