From 6a308096584d093f950a394ba57ee67613dffb44 Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Fri, 16 Jun 2023 18:12:34 +0200 Subject: [PATCH] Initial Android backend --- .github/CODEOWNERS | 3 + Cargo.toml | 12 ++++ README.md | 2 +- examples/winit.rs | 7 +- examples/winit_android.rs | 18 +++++ src/android.rs | 145 ++++++++++++++++++++++++++++++++++++++ src/lib.rs | 10 +++ src/win32.rs | 4 +- 8 files changed, 197 insertions(+), 4 deletions(-) create mode 100644 examples/winit_android.rs create mode 100644 src/android.rs diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2644dce..cf24c9b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,6 +1,9 @@ # Core maintainers * @john01dav +# Android +/src/android.rs @marijns95 + # Apple platforms /src/cg.rs @madsmtm diff --git a/Cargo.toml b/Cargo.toml index b7be7ed..4dc769e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,9 @@ x11-dlopen = ["tiny-xlib/dlopen", "x11rb/dl-libxcb"] log = "0.4.17" raw_window_handle = { package = "raw-window-handle", version = "0.6", features = ["std"] } +[target.'cfg(target_os = "android")'.dependencies] +ndk = "0.8.0" + [target.'cfg(all(unix, not(any(target_vendor = "apple", target_os = "android", target_os = "redox"))))'.dependencies] as-raw-xcb-connection = { version = "1.0.0", optional = true } bytemuck = { version = "1.12.3", optional = true } @@ -82,6 +85,10 @@ instant = "0.1.12" winit = "0.29.2" winit-test = "0.1.0" +[target.'cfg(target_os = "android")'.dev-dependencies] +winit = { version = "0.29.2", features = ["android-native-activity"] } +android-activity = "0.5" + [dev-dependencies.image] version = "0.24.6" # Disable rayon on web @@ -109,6 +116,11 @@ name = "present_and_fetch" path = "tests/present_and_fetch.rs" harness = false +[[example]] +# Run with `cargo apk r --example winit_android` +name = "winit_android" +crate-type = ["cdylib"] + [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] diff --git a/README.md b/README.md index 095ed88..42f6530 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ For now, the priority for new platforms is: ✅: Present | ❌: Absent -- AndroidNdk ❌ +- AndroidNdk ✅ (thanks to [Marijn Suijten](https://github.com/marijns95)) - AppKit ✅ (Thanks to [Seo Sanghyeon](https://github.com/sanxiyn) and [lunixbochs](https://github.com/lunixbochs)!) - Orbital ✅ - UiKit ❌ diff --git a/examples/winit.rs b/examples/winit.rs index 192523a..b01eba3 100644 --- a/examples/winit.rs +++ b/examples/winit.rs @@ -3,10 +3,15 @@ use std::rc::Rc; use winit::event::{Event, KeyEvent, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; use winit::keyboard::{Key, NamedKey}; + use winit::window::WindowBuilder; +#[cfg(not(target_os = "android"))] fn main() { - let event_loop = EventLoop::new().unwrap(); + run(EventLoop::new().unwrap()) +} + +pub(crate) fn run(event_loop: EventLoop<()>) { let window = Rc::new(WindowBuilder::new().build(&event_loop).unwrap()); #[cfg(target_arch = "wasm32")] diff --git a/examples/winit_android.rs b/examples/winit_android.rs new file mode 100644 index 0000000..1a8a54d --- /dev/null +++ b/examples/winit_android.rs @@ -0,0 +1,18 @@ +#![cfg(target_os = "android")] + +use winit::event_loop::EventLoopBuilder; +pub use winit::platform::android::{activity::AndroidApp, EventLoopBuilderExtAndroid}; + +#[path = "winit.rs"] +mod desktop_example; + +/// Run with `cargo apk r --example winit_android` +#[no_mangle] +fn android_main(app: AndroidApp) { + let mut builder = EventLoopBuilder::new(); + + // Install the Android event loop extension if necessary. + builder.with_android_app(app); + + desktop_example::run(builder.build().unwrap()) +} diff --git a/src/android.rs b/src/android.rs new file mode 100644 index 0000000..18e7c96 --- /dev/null +++ b/src/android.rs @@ -0,0 +1,145 @@ +//! Implementation of software buffering for Android. + +use std::marker::PhantomData; +use std::num::{NonZeroI32, NonZeroU32}; + +use ndk::{ + hardware_buffer_format::HardwareBufferFormat, + native_window::{NativeWindow, NativeWindowBufferLockGuard}, +}; +use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawWindowHandle}; + +use crate::error::InitError; +use crate::{Rect, SoftBufferError}; + +/// The handle to a window for software buffering. +pub struct AndroidImpl { + native_window: NativeWindow, + + _display: PhantomData, + + /// The pointer to the window object. + /// + /// This is pretty useless because it gives us a pointer to [`NativeWindow`] that we have to increase the refcount on. + /// Alternatively we can use [`NativeWindow::from_ptr()`] wrapped in [`std::mem::ManuallyDrop`] + _window: W, +} + +impl AndroidImpl { + /// Create a new [`AndroidImpl`] from an [`AndroidNdkWindowHandle`]. + /// + /// # Safety + /// + /// The [`AndroidNdkWindowHandle`] must be a valid window handle. + // TODO: That's lame, why can't we get an AndroidNdkWindowHandle directly here + pub(crate) fn new(window: W) -> Result> { + // Get the raw Android window (surface). + let raw = window.window_handle()?.as_raw(); + let RawWindowHandle::AndroidNdk(a) = raw else { + return Err(InitError::Unsupported(window)); + }; + + // Acquire a new owned reference to the window, that will be freed on drop. + let native_window = unsafe { NativeWindow::clone_from_ptr(a.a_native_window.cast()) }; + + Ok(Self { + native_window, + // _display: DisplayHandle::borrow_raw(raw_window_handle::RawDisplayHandle::Android( + // AndroidDisplayHandle, + // )), + _display: PhantomData, + _window: window, + }) + } + + /// Also changes the pixel format to [`HardwareBufferFormat::R8G8B8A8_UNORM`]. + pub fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> { + let (width, height) = (|| { + let width = NonZeroI32::try_from(width).ok()?; + let height = NonZeroI32::try_from(height).ok()?; + Some((width, height)) + })() + .ok_or(SoftBufferError::SizeOutOfRange { width, height })?; + + // Do not change the format. + self.native_window + .set_buffers_geometry( + width.into(), + height.into(), + // Default is typically R5G6B5 16bpp, switch to 32bpp + Some(HardwareBufferFormat::R8G8B8A8_UNORM), + ) + .map_err(|err| { + SoftBufferError::PlatformError( + Some("Failed to set buffer geometry on ANativeWindow".to_owned()), + Some(Box::new(err)), + ) + }) + } + + pub fn buffer_mut(&mut self) -> Result, SoftBufferError> { + let lock_guard = self.native_window.lock(None).map_err(|err| { + SoftBufferError::PlatformError( + Some("Failed to lock ANativeWindow".to_owned()), + Some(Box::new(err)), + ) + })?; + + assert_eq!( + lock_guard.format().bytes_per_pixel(), + Some(4), + "Unexpected buffer format {:?}, please call .resize() first to change it to RGBA8888", + lock_guard.format() + ); + + Ok(BufferImpl(lock_guard, PhantomData, PhantomData)) + } + + /// Fetch the buffer from the window. + pub fn fetch(&mut self) -> Result, SoftBufferError> { + Err(SoftBufferError::Unimplemented) + } +} + +pub struct BufferImpl<'a, D: ?Sized, W>( + NativeWindowBufferLockGuard<'a>, + PhantomData<&'a D>, + PhantomData<&'a W>, +); + +impl<'a, D: HasDisplayHandle + ?Sized, W: HasWindowHandle> BufferImpl<'a, D, W> { + #[inline] + pub fn pixels(&self) -> &[u32] { + todo!() + // unsafe { + // std::slice::from_raw_parts( + // self.0.bits().cast_const().cast(), + // (self.0.stride() * self.0.height()) as usize, + // ) + // } + } + + #[inline] + pub fn pixels_mut(&mut self) -> &mut [u32] { + let bytes = self.0.bytes().expect("Nonplanar format"); + unsafe { + std::slice::from_raw_parts_mut( + bytes.as_mut_ptr().cast(), + bytes.len() / std::mem::size_of::(), + ) + } + } + + pub fn age(&self) -> u8 { + todo!() + } + + pub fn present(self) -> Result<(), SoftBufferError> { + // Dropping the guard automatically unlocks and posts it + Ok(()) + } + + pub fn present_with_damage(self, _damage: &[Rect]) -> Result<(), SoftBufferError> { + Err(SoftBufferError::Unimplemented) + } +} diff --git a/src/lib.rs b/src/lib.rs index 6ec267c..f33195d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,8 @@ extern crate objc; extern crate core; +#[cfg(target_os = "android")] +mod android; #[cfg(target_os = "macos")] mod cg; #[cfg(kms_platform)] @@ -176,6 +178,8 @@ macro_rules! make_dispatch { make_dispatch! { => + #[cfg(target_os = "android")] + Android(D, android::AndroidImpl, android::BufferImpl<'a, D, W>), #[cfg(x11_platform)] X11(Rc>, x11::X11Impl, x11::BufferImpl<'a, D, W>), #[cfg(wayland_platform)] @@ -211,6 +215,8 @@ impl Context { }}; } + #[cfg(target_os = "android")] + try_init!(Android, display => Ok(display)); #[cfg(x11_platform)] try_init!(X11, display => x11::X11DisplayImpl::new(display).map(Rc::new)); #[cfg(wayland_platform)] @@ -277,6 +283,10 @@ impl Surface { } let imple = match &context.context_impl { + #[cfg(target_os = "android")] + ContextDispatch::Android(_android_display_handle) => { + SurfaceDispatch::Android(leap!(android::AndroidImpl::new(window))) + } #[cfg(x11_platform)] ContextDispatch::X11(xcb_display_handle) => { SurfaceDispatch::X11(leap!(x11::X11Impl::new(window, xcb_display_handle.clone()))) diff --git a/src/win32.rs b/src/win32.rs index 225e9b4..88e054a 100644 --- a/src/win32.rs +++ b/src/win32.rs @@ -190,8 +190,8 @@ impl Win32Impl { pub fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> { let (width, height) = (|| { - let width = NonZeroI32::new(i32::try_from(width.get()).ok()?)?; - let height = NonZeroI32::new(i32::try_from(height.get()).ok()?)?; + let width = NonZeroI32::try_from(width).ok()?; + let height = NonZeroI32::try_from(height).ok()?; Some((width, height)) })() .ok_or(SoftBufferError::SizeOutOfRange { width, height })?;