Skip to content

Commit

Permalink
Add a new backend and examples for Android
Browse files Browse the repository at this point in the history
  • Loading branch information
MarijnS95 committed Dec 16, 2024
1 parent dbc1246 commit e59bcde
Show file tree
Hide file tree
Showing 11 changed files with 232 additions and 10 deletions.
3 changes: 3 additions & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Android
/src/android.rs @MarijnS95

# Apple platforms
/src/cg.rs @madsmtm

Expand Down
17 changes: 17 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down Expand Up @@ -89,6 +92,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
Expand All @@ -110,6 +117,16 @@ members = [
"run-wasm",
]

[[example]]
# Run with `cargo apk r --example winit_android`
name = "winit_android"
crate-type = ["cdylib"]

[[example]]
# Run with `cargo apk r --example winit_multithread_android`
name = "winit_multithread_android"
crate-type = ["cdylib"]

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ For now, the priority for new platforms is:

| Platform ||
|-----------|--|
|Android NDK||
|Android NDK||
| AppKit ||
| Orbital ||
| UIKit ||
Expand All @@ -61,6 +61,10 @@ For now, the priority for new platforms is:

To run an example with the web backend: `cargo run-wasm --example winit`

## Android

To run the Android-specific example on an Android phone: `cargo apk r --example winit_android` or `cargo apk r --example winit_multithread_android`.

## Example

```rust,no_run
Expand Down
5 changes: 4 additions & 1 deletion examples/winit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
entry(EventLoop::new().unwrap())
}

pub(crate) fn entry(event_loop: EventLoop<()>) {
let app = winit_app::WinitAppBuilder::with_init(
|elwt| {
let window = winit_app::make_window(elwt, |w| w);
Expand Down
18 changes: 18 additions & 0 deletions examples/winit_android.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#![cfg(target_os = "android")]

use winit::event_loop::EventLoop;
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 = EventLoop::builder();

// Install the Android event loop extension if necessary.
builder.with_android_app(app);

desktop_example::entry(builder.build().unwrap())
}
13 changes: 7 additions & 6 deletions examples/winit_multithread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
mod winit_app;

#[cfg(not(target_family = "wasm"))]
mod ex {
pub mod ex {
use std::num::NonZeroU32;
use std::sync::{mpsc, Arc, Mutex};
use winit::event::{Event, KeyEvent, WindowEvent};
Expand Down Expand Up @@ -59,9 +59,7 @@ mod ex {
}
}

pub(super) fn entry() {
let event_loop = EventLoop::new().unwrap();

pub fn entry(event_loop: EventLoop<()>) {
let app = winit_app::WinitAppBuilder::with_init(
|elwt| {
let attributes = Window::default_attributes();
Expand Down Expand Up @@ -135,11 +133,14 @@ mod ex {

#[cfg(target_family = "wasm")]
mod ex {
pub(crate) fn entry() {
use winit::event_loop::EventLoop;
pub(crate) fn entry(event_loop: EventLoop<()>) {
eprintln!("winit_multithreaded doesn't work on WASM");
}
}

#[cfg(not(target_os = "android"))]
fn main() {
ex::entry();
use winit::event_loop::EventLoop;
ex::entry(EventLoop::new().unwrap())
}
18 changes: 18 additions & 0 deletions examples/winit_multithread_android.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#![cfg(target_os = "android")]

use winit::event_loop::EventLoop;
pub use winit::platform::android::{activity::AndroidApp, EventLoopBuilderExtAndroid};

#[path = "winit_multithread.rs"]
mod desktop_example;

/// Run with `cargo apk r --example winit_android`
#[no_mangle]
fn android_main(app: AndroidApp) {
let mut builder = EventLoop::builder();

// Install the Android event loop extension if necessary.
builder.with_android_app(app);

desktop_example::ex::entry(builder.build().unwrap())
}
2 changes: 2 additions & 0 deletions src/backend_dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ macro_rules! make_dispatch {

make_dispatch! {
<D, W> =>
#[cfg(target_os = "android")]
Android(D, backends::android::AndroidImpl<D, W>, backends::android::BufferImpl<'a, D, W>),
#[cfg(x11_platform)]
X11(Arc<backends::x11::X11DisplayImpl<D>>, backends::x11::X11Impl<D, W>, backends::x11::BufferImpl<'a, D, W>),
#[cfg(wayland_platform)]
Expand Down
154 changes: 154 additions & 0 deletions src/backends/android.rs
Original file line number Diff line number Diff line change
@@ -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<D: ?Sized, W: ?Sized> {
native_window: NativeWindow,

_display: PhantomData<D>,

/// 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<D: HasDisplayHandle, W: HasWindowHandle> AndroidImpl<D, W> {
/// 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<Self, InitError<W>> {
// 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<BufferImpl<'_, D, W>, 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<Vec<u32>, 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::<u32>(),
)
}
}

pub fn age(&self) -> u8 {
0
}

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)
}
}
2 changes: 2 additions & 0 deletions src/backends/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use crate::{ContextInterface, InitError};
use raw_window_handle::HasDisplayHandle;

#[cfg(target_os = "android")]
pub(crate) mod android;
#[cfg(target_vendor = "apple")]
pub(crate) mod cg;
#[cfg(kms_platform)]
Expand Down
4 changes: 2 additions & 2 deletions src/backends/win32.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,8 +250,8 @@ impl<D: HasDisplayHandle, W: HasWindowHandle> SurfaceInterface<D, W> 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 })?;
Expand Down

0 comments on commit e59bcde

Please sign in to comment.