From a7923178ff5102a0181127acddc0327dc9b49325 Mon Sep 17 00:00:00 2001 From: David Craven Date: Mon, 17 Feb 2020 19:07:29 +0100 Subject: [PATCH 1/2] Implement android backend. --- .github/workflows/ci.yml | 44 +++--- Cargo.toml | 1 - README.md | 2 +- src/config.rs | 2 +- src/platform/android.rs | 54 ++++--- src/platform_impl.rs | 6 +- src/platform_impl/android/android.rs | 224 ++++++++++++++++++++++++++- 7 files changed, 287 insertions(+), 46 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4b0d940df5..4cd577845e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,31 +64,32 @@ jobs: matrix: rust_version: [stable, nightly] platform: - - { target: x86_64-pc-windows-msvc, os: windows-latest, } - - { target: i686-pc-windows-msvc, os: windows-latest, } - - { target: x86_64-pc-windows-gnu, os: windows-latest, host: -x86_64-pc-windows-gnu } - - { target: i686-pc-windows-gnu, os: windows-latest, host: -i686-pc-windows-gnu } + # - { target: x86_64-pc-windows-msvc, os: windows-latest, } + # - { target: i686-pc-windows-msvc, os: windows-latest, } + # - { target: x86_64-pc-windows-gnu, os: windows-latest, host: -x86_64-pc-windows-gnu } + # - { target: i686-pc-windows-gnu, os: windows-latest, host: -i686-pc-windows-gnu } - { target: i686-unknown-linux-gnu, os: ubuntu-latest, } - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, } - - { target: x86_64-apple-darwin, os: macos-latest, } - - { target: x86_64-apple-ios, os: macos-latest, } - - { target: armv7-apple-ios, os: macos-latest, } - - { target: aarch64-apple-ios, os: macos-latest, } + - { target: aarch64-linux-android, os: ubuntu-latest, cmd: 'apk --' } + # - { target: x86_64-apple-darwin, os: macos-latest, } + # - { target: x86_64-apple-ios, os: macos-latest, } + # - { target: armv7-apple-ios, os: macos-latest, } + # - { target: aarch64-apple-ios, os: macos-latest, } # We're using Windows rather than Ubuntu to run the wasm tests because caching cargo-web # doesn't currently work on Linux. - #- { target: wasm32-unknown-unknown, os: windows-latest, features: stdweb, web: web } - #- { target: wasm32-unknown-unknown, os: windows-latest, features: web-sys, web: web } + #- { target: wasm32-unknown-unknown, os: windows-latest, features: stdweb, cmd: web } + #- { target: wasm32-unknown-unknown, os: windows-latest, features: web-sys, cmd: web } env: RUST_BACKTRACE: 1 CARGO_INCREMENTAL: 0 RUSTFLAGS: "-C debuginfo=0" FEATURES: ${{ format(',{0}', matrix.platform.features ) }} - WEB: ${{ matrix.platform.web }} + CMD: ${{ matrix.platform.cmd }} runs-on: ${{ matrix.platform.os }} steps: - + # FIXME - uses: actions/checkout@v1 with: @@ -142,6 +143,10 @@ jobs: if: matrix.platform.os == 'ubuntu-latest' run: sudo apt-get update && sudo apt-get install libgbm-dev libdrm-dev + - name: Install cargo-apk + if: contains(matrix.platform.target, 'android') + run: cargo install cargo-apk + - name: Install cargo-web continue-on-error: true if: contains(matrix.platform.target, 'wasm32') @@ -150,19 +155,22 @@ jobs: - name: Check documentation shell: bash if: matrix.platform.target != 'wasm32-unknown-unknown' - run: cargo doc --no-deps --target ${{ matrix.platform.target }} --features $FEATURES + run: cargo $CMD doc --no-deps --target ${{ matrix.platform.target }} --features $FEATURES - - name: Build + - name: Build shell: bash - run: cargo $WEB build --verbose --target ${{ matrix.platform.target }} --features $FEATURES + run: cargo $CMD build --verbose --target ${{ matrix.platform.target }} --features $FEATURES - name: Build tests shell: bash - run: cargo $WEB test --no-run --verbose --target ${{ matrix.platform.target }} --features $FEATURES + run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} --features $FEATURES - name: Run tests shell: bash - if: (!contains(matrix.platform.target, 'ios') && !contains(matrix.platform.target, 'wasm32')) - run: cargo $WEB test --verbose --target ${{ matrix.platform.target }} --features $FEATURES + if: ( + !contains(matrix.platform.target, 'android') && + !contains(matrix.platform.target, 'ios') && + !contains(matrix.platform.target, 'wasm32')) + run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} --features $FEATURES - name: iOS example shell: bash diff --git a/Cargo.toml b/Cargo.toml index d53b116cfd..3898294614 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,6 @@ gbm = { path = "../gbm.rs" } drm = "0.3.4" [target.'cfg(target_os = "android")'.dependencies] -android_glue = "0.2.3" glutin_egl_sys = { version = "0.1.4", path = "../glutin_sys/glutin_egl_sys" } parking_lot = "0.10.0" diff --git a/README.md b/README.md index 0373d6932d..d518529610 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ Glutin is only officially supported on the latest stable version of the Rust com To compile the examples for android, you have to use the `cargo apk` utility. -See [the `android-rs-glue` repository](https://github.com/rust-windowing/android-rs-glue) for instructions. +See [the `android-ndk-rs` repository](https://github.com/rust-windowing/android-ndk-rs) for instructions. ### Emscripten with asmjs diff --git a/src/config.rs b/src/config.rs index ec7a69ed6b..d468363c13 100644 --- a/src/config.rs +++ b/src/config.rs @@ -324,7 +324,7 @@ impl Default for ConfigsFinder { #[cfg(not(any(target_os = "android", target_os = "ios")))] version: (Api::OpenGl, Version(3, 3)), #[cfg(any(target_os = "android", target_os = "ios"))] - version: (Api::OpenGlEs, Version(2, 0)), + version: (Api::OpenGlEs, Version(3, 1)), plat_attr: Default::default(), } } diff --git a/src/platform/android.rs b/src/platform/android.rs index 7da2a82051..f9e8bd8b6b 100644 --- a/src/platform/android.rs +++ b/src/platform/android.rs @@ -1,29 +1,43 @@ #![cfg(any(target_os = "android"))] +use crate::config::Config; +use crate::context::Context; +use crate::surface::{Surface, SurfaceTypeTrait}; +use std::os::raw; -use crate::platform::ContextTraitExt; -use crate::{Context, ContextCurrentState}; -use crate::{SupportsPBuffersTrait, SupportsSurfacelessTrait, SupportsWindowSurfacesTrait}; +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct ConfigPlatformAttributes; -pub use glutin_egl_sys::EGLContext; +pub trait ConfigExt { + unsafe fn raw_config(&self) -> *const raw::c_void; + unsafe fn raw_display(&self) -> *mut raw::c_void; +} -use std::os::raw; +impl ConfigExt for Config { + unsafe fn raw_config(&self) -> *const raw::c_void { + self.config.raw_config() + } + + unsafe fn raw_display(&self) -> *mut raw::c_void { + self.config.raw_display() + } +} + +pub trait SurfaceExt { + unsafe fn raw_surface(&self) -> *const raw::c_void; +} -impl< - IC: ContextCurrentState, - PBT: SupportsPBuffersTrait, - WST: SupportsWindowSurfacesTrait, - ST: SupportsSurfacelessTrait, - > ContextTraitExt for Context -{ - type Handle = EGLContext; - - #[inline] - unsafe fn raw_handle(&self) -> Self::Handle { - self.context.raw_handle() +impl SurfaceExt for Surface { + unsafe fn raw_surface(&self) -> *const raw::c_void { + self.0.raw_surface() } +} + +pub trait ContextExt { + unsafe fn raw_context(&self) -> *mut raw::c_void; +} - #[inline] - unsafe fn get_egl_display(&self) -> Option<*const raw::c_void> { - Some(self.context.get_egl_display()) +impl ContextExt for Context { + unsafe fn raw_context(&self) -> *mut raw::c_void { + self.0.raw_context() } } diff --git a/src/platform_impl.rs b/src/platform_impl.rs index 8bb37665f4..b01608cc78 100644 --- a/src/platform_impl.rs +++ b/src/platform_impl.rs @@ -15,9 +15,9 @@ mod platform_impl; //#[cfg(target_os = "macos")] //#[path = "platform_impl/macos/macos.rs"] //mod platform_impl; -//#[cfg(target_os = "android")] -//#[path = "platform_impl/android/android.rs"] -//mod platform_impl; +#[cfg(target_os = "android")] +#[path = "platform_impl/android/android.rs"] +mod platform_impl; //#[cfg(target_os = "ios")] //#[path = "platform_impl/ios/ios.rs"] //mod platform_impl; diff --git a/src/platform_impl/android/android.rs b/src/platform_impl/android/android.rs index 867a8eecdb..b4f608fdc3 100644 --- a/src/platform_impl/android/android.rs +++ b/src/platform_impl/android/android.rs @@ -1,4 +1,224 @@ #![cfg(target_os = "android")] -pub use crate::api::android::*; -pub use winit::event_loop::{EventLoop, EventLoopWindowTarget}; +use crate::api::egl; +use crate::config::{ConfigAttribs, ConfigWrapper, ConfigsFinder, SwapInterval}; +use crate::context::ContextBuilderWrapper; +pub use crate::platform::android::ConfigPlatformAttributes; +use crate::surface::{PBuffer, Pixmap, SurfaceTypeTrait, Window}; +use glutin_interface::{ + AndroidWindowParts, NativeDisplay, NativePixmap, NativePixmapSource, NativeWindow, + NativeWindowSource, RawWindow, Seal, +}; +use std::ops::Deref; +use std::os::raw; +use winit_types::dpi; +use winit_types::error::{Error, ErrorType}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Config(egl::Config); + +impl Deref for Config { + type Target = egl::Config; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Config { + pub fn new( + cf: &ConfigsFinder, + nd: &ND, + ) -> Result, Error> { + Ok( + egl::Config::new(cf, nd, |confs, _| confs.into_iter().map(Ok).collect())? + .into_iter() + .map(|(attribs, config)| (attribs, Config(config))) + .collect(), + ) + } + + pub fn raw_config(&self) -> *const raw::c_void { + (**self).raw_config() + } + + pub fn raw_display(&self) -> *mut raw::c_void { + (**self).raw_display() + } +} + +#[derive(Debug, Eq, PartialEq)] +pub(crate) struct Surface(egl::Surface); + +impl Deref for Surface { + type Target = egl::Surface; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Surface { + pub fn is_current(&self) -> bool { + (&**self).is_current() + } + + pub fn get_config(&self) -> ConfigWrapper { + (&**self).get_config().map_config(Config) + } + + pub unsafe fn make_not_current(&self) -> Result<(), Error> { + (&**self).make_not_current() + } + + pub fn raw_surface(&self) -> *const raw::c_void { + (&**self).raw_surface() + } + + pub fn size(&self) -> Result, Error> { + (&**self).size() + } +} + +impl Surface { + pub fn build_window( + _conf: ConfigWrapper<&Config, &ConfigAttribs>, + nws: &NWS, + wb: NWS::WindowBuilder, + ) -> Result { + #[allow(deprecated)] + nws.build_android( + wb, + AndroidWindowParts { + _non_exhaustive_do_not_use: Seal, + }, + ) + } + + pub unsafe fn new_existing( + conf: ConfigWrapper<&Config, &ConfigAttribs>, + nw: &NW, + ) -> Result { + let a_native_window = match nw.raw_window() { + RawWindow::Android { + a_native_window, .. + } => a_native_window, + _ => { + return Err(make_error!(ErrorType::NotSupported( + "Expected android window.".to_string(), + ))) + } + }; + let config = conf.map_config(|conf| &conf.0); + let surface = egl::Surface::::new(config, a_native_window)?; + Ok(Surface(surface)) + } + + pub fn swap_buffers(&self) -> Result<(), Error> { + (&**self).swap_buffers() + } + + pub fn swap_buffers_with_damage(&self, rects: &[dpi::Rect]) -> Result<(), Error> { + (&**self).swap_buffers_with_damage(rects) + } + + pub fn modify_swap_interval(&self, swap_interval: SwapInterval) -> Result<(), Error> { + (&**self).modify_swap_interval(swap_interval) + } +} + +impl Surface { + pub unsafe fn new( + conf: ConfigWrapper<&Config, &ConfigAttribs>, + size: dpi::PhysicalSize, + largest: bool, + ) -> Result { + let config = conf.clone().map_config(|conf| &**conf); + let surface = egl::Surface::::new(config, size, largest)?; + Ok(Surface(surface)) + } +} + +impl Surface { + pub unsafe fn build_pixmap( + _conf: ConfigWrapper<&Config, &ConfigAttribs>, + _nps: &NPS, + _pb: NPS::PixmapBuilder, + ) -> Result { + Err(make_error!(ErrorType::NotSupported( + "pixmaps not supported on android".into() + ))) + } + + pub unsafe fn new_existing( + _conf: ConfigWrapper<&Config, &ConfigAttribs>, + _np: &NP, + ) -> Result { + Err(make_error!(ErrorType::NotSupported( + "pixmaps not supported on android".into() + ))) + } +} + +#[derive(Debug, Eq, PartialEq)] +pub(crate) struct Context(egl::Context); + +impl Deref for Context { + type Target = egl::Context; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Context { + pub(crate) fn new( + cb: ContextBuilderWrapper<&Context>, + conf: ConfigWrapper<&Config, &ConfigAttribs>, + ) -> Result { + let context = egl::Context::new( + cb.map_sharing(|ctx| &**ctx), + conf.map_config(|conf| &**conf), + )?; + Ok(Context(context)) + } + + pub unsafe fn make_current_surfaceless(&self) -> Result<(), Error> { + (**self).make_current_surfaceless() + } + + pub(crate) unsafe fn make_current( + &self, + surf: &Surface, + ) -> Result<(), Error> { + (**self).make_current(&**surf) + } + + pub(crate) unsafe fn make_current_rw( + &self, + read_surf: &Surface, + write_surf: &Surface, + ) -> Result<(), Error> { + (**self).make_current_rw(&**read_surf, &**write_surf) + } + + pub unsafe fn make_not_current(&self) -> Result<(), Error> { + (**self).make_not_current() + } + + pub fn is_current(&self) -> bool { + (**self).is_current() + } + + pub fn get_proc_address(&self, addr: &str) -> Result<*const raw::c_void, Error> { + (**self).get_proc_address(addr) + } + + pub fn get_config(&self) -> ConfigWrapper { + (**self).get_config().map_config(Config) + } + + pub fn raw_context(&self) -> *mut raw::c_void { + (**self).raw_context() + } +} From ad4ae795c5fedc399597950e1cfac625f07464bb Mon Sep 17 00:00:00 2001 From: David Craven Date: Mon, 17 Feb 2020 19:08:26 +0100 Subject: [PATCH 2/2] Update window example. --- README.md | 5 ++-- android/Cargo.toml | 26 +++++++++++++++++++++ android/src/lib.rs | 0 examples/window.rs | 57 +++++++++++++++++++++++++++++++++------------- 4 files changed, 70 insertions(+), 18 deletions(-) create mode 100644 android/Cargo.toml create mode 100644 android/src/lib.rs diff --git a/README.md b/README.md index d518529610..4bb88389da 100644 --- a/README.md +++ b/README.md @@ -49,9 +49,10 @@ Glutin is only officially supported on the latest stable version of the Rust com ### Android -To compile the examples for android, you have to use the `cargo apk` utility. +To compile the examples for android, you have to use the `cargo apk` utility. You can run an example +with `cargo apk run --example $EXAMPLE --manifest-path android/Cargo.toml`. -See [the `android-ndk-rs` repository](https://github.com/rust-windowing/android-ndk-rs) for instructions. +See [the `android-ndk-rs` repository](https://github.com/rust-windowing/android-ndk-rs) for more information. ### Emscripten with asmjs diff --git a/android/Cargo.toml b/android/Cargo.toml new file mode 100644 index 0000000000..7733c5bd1c --- /dev/null +++ b/android/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "android-examples" +version = "0.1.0" +authors = ["David Craven "] +edition = "2018" +publish = false +build = "../build.rs" + +[dependencies] +glutin = { path = ".." } + +[build-dependencies] +gl_generator = "0.14.0" + +[dev-dependencies] +image = "0.22.4" +ndk-glue = { path = "../../android-ndk-rs/ndk-glue" } +simple_logger = "1.4.0" +takeable-option = "0.5.0" +winit = { path = "../../winit" } +winit_types = { path = "../../winit_types" } + +[[example]] +name = "window" +path = "../examples/window.rs" +crate-type = ["cdylib"] diff --git a/android/src/lib.rs b/android/src/lib.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/window.rs b/examples/window.rs index 621288f4a2..4406c23a25 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -7,45 +7,70 @@ use winit::event::{Event, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; use winit::window::WindowBuilder; +#[cfg(target_os = "android")] +ndk_glue::ndk_glue!(main); + fn main() { simple_logger::init().unwrap(); let el = EventLoop::new(); let wb = WindowBuilder::new().with_title("A fantastic window!"); let confs = unsafe { ConfigsFinder::new().find(&*el).unwrap() }; - let conf = &confs[0]; + let conf = confs[0].clone(); println!("Configeration chosen: {:?}", conf); - let ctx = unsafe { ContextBuilder::new().build(conf).unwrap() }; - let (win, surf) = unsafe { Surface::new_window(conf, &*el, wb).unwrap() }; - - unsafe { ctx.make_current(&surf).unwrap() } - let gl = support::Gl::load(|s| ctx.get_proc_address(s).unwrap()); + let ctx = unsafe { ContextBuilder::new().build(&conf).unwrap() }; + let win = unsafe { Surface::build_window(&conf, &*el, wb).unwrap() }; + // On android the surface can only be created after the resume event + // was received. + let mut surf = None; + let mut gl = None; el.run(move |event, _, control_flow| { println!("{:?}", event); *control_flow = ControlFlow::Wait; match event { - Event::LoopDestroyed => return, - Event::MainEventsCleared => { - win.request_redraw(); + Event::Resumed => { + let surface = unsafe { Surface::new_from_existing_window(&conf, &win).unwrap() }; + unsafe { + ctx.make_current(&surface).unwrap(); + } + if gl.is_none() { + gl = Some(support::Gl::load(|s| ctx.get_proc_address(s).unwrap())); + } + surf = Some(surface) } + Event::Suspended => surf = None, + Event::LoopDestroyed => return, + Event::MainEventsCleared => win.request_redraw(), Event::RedrawRequested(_) => { - gl.draw_frame([1.0, 0.5, 0.7, 1.0]); - surf.swap_buffers().unwrap(); + if let (Some(gl), Some(surf)) = (&gl, &surf) { + gl.draw_frame([1.0, 0.5, 0.7, 1.0]); + surf.swap_buffers().unwrap(); + } } - Event::WindowEvent { ref event, .. } => match event { - WindowEvent::Resized(size) => { + Event::WindowEvent { ref event, .. } => { + let size = match event { + WindowEvent::ScaleFactorChanged { + new_inner_size: size, + .. + } => size, + WindowEvent::Resized(size) => size, + WindowEvent::CloseRequested => { + *control_flow = ControlFlow::Exit; + return; + } + _ => return, + }; + if let (Some(gl), Some(surf)) = (&gl, &surf) { ctx.update_after_resize(); surf.update_after_resize(*size); unsafe { gl.gl.Viewport(0, 0, size.width as _, size.height as _); } } - WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, - _ => (), - }, + } _ => (), } });