From 32fe7336c1210bea0d77a59c6bd967450f59e478 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 | 5 +- examples/winit_android.rs | 18 +++++ src/backend_dispatch.rs | 2 + src/backends/android.rs | 154 ++++++++++++++++++++++++++++++++++++++ src/backends/mod.rs | 2 + src/backends/win32.rs | 4 +- 9 files changed, 198 insertions(+), 4 deletions(-) create mode 100644 examples/winit_android.rs create mode 100644 src/backends/android.rs diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2644dce6..e2369a50 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 1f41c0bd..b1514819 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.9.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 } @@ -83,6 +86,10 @@ criterion = { version = "0.4.0", default-features = false, features = ["cargo_be web-time = "1.0.0" winit = "0.30.0" +[target.'cfg(target_os = "android")'.dev-dependencies] +winit = { version = "0.30.0", features = ["android-native-activity"] } +android-activity = "0.6" + [dev-dependencies.image] version = "0.25.0" # Disable rayon on web @@ -104,6 +111,11 @@ members = [ "run-wasm", ] +[[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 8c8c6b83..d2f0cb33 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 fc055bf3..e84a6714 100644 --- a/examples/winit.rs +++ b/examples/winit.rs @@ -6,9 +6,12 @@ use winit::keyboard::{Key, NamedKey}; #[path = "utils/winit_app.rs"] mod winit_app; +#[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 app = winit_app::WinitAppBuilder::with_init(|elwt| { let window = winit_app::make_window(elwt, |w| w); diff --git a/examples/winit_android.rs b/examples/winit_android.rs new file mode 100644 index 00000000..1a8a54d2 --- /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/backend_dispatch.rs b/src/backend_dispatch.rs index d8d23aa2..e3b626f7 100644 --- a/src/backend_dispatch.rs +++ b/src/backend_dispatch.rs @@ -178,6 +178,8 @@ macro_rules! make_dispatch { make_dispatch! { => + #[cfg(target_os = "android")] + Android(D, backends::android::AndroidImpl, backends::android::BufferImpl<'a, D, W>), #[cfg(x11_platform)] X11(Arc>, backends::x11::X11Impl, backends::x11::BufferImpl<'a, D, W>), #[cfg(wayland_platform)] diff --git a/src/backends/android.rs b/src/backends/android.rs new file mode 100644 index 00000000..26cd0553 --- /dev/null +++ b/src/backends/android.rs @@ -0,0 +1,154 @@ +//! 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, +} + +// TODO: Current system doesn't require a trait to be implemented here, even though it exists. +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, _display: &D) -> 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, + }) + } + + #[inline] + pub fn window(&self) -> &W { + &self.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>, +); + +// TODO: Move to NativeWindowBufferLockGuard? +unsafe impl<'a, D, W> Send for BufferImpl<'a, D, 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/backends/mod.rs b/src/backends/mod.rs index 8402b441..04594708 100644 --- a/src/backends/mod.rs +++ b/src/backends/mod.rs @@ -1,6 +1,8 @@ use crate::{ContextInterface, InitError}; use raw_window_handle::HasDisplayHandle; +#[cfg(target_os = "android")] +pub(crate) mod android; #[cfg(target_os = "macos")] pub(crate) mod cg; #[cfg(kms_platform)] diff --git a/src/backends/win32.rs b/src/backends/win32.rs index 4471d37a..b5ca5863 100644 --- a/src/backends/win32.rs +++ b/src/backends/win32.rs @@ -237,8 +237,8 @@ impl SurfaceInterface for Win32Im 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 })?;