Skip to content

Commit 9e3c594

Browse files
MarijnS95notgull
authored andcommitted
Add a new backend and examples for Android
1 parent 54918d4 commit 9e3c594

11 files changed

+232
-10
lines changed

.github/CODEOWNERS

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Android
2+
/src/android.rs @MarijnS95
3+
14
# Apple platforms
25
/src/cg.rs @madsmtm
36

Cargo.toml

+17
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ x11-dlopen = ["tiny-xlib/dlopen", "x11rb/dl-libxcb"]
2828
log = "0.4.17"
2929
raw_window_handle = { package = "raw-window-handle", version = "0.6", features = ["std"] }
3030

31+
[target.'cfg(target_os = "android")'.dependencies]
32+
ndk = "0.9.0"
33+
3134
[target.'cfg(all(unix, not(any(target_vendor = "apple", target_os = "android", target_os = "redox"))))'.dependencies]
3235
as-raw-xcb-connection = { version = "1.0.0", optional = true }
3336
bytemuck = { version = "1.12.3", optional = true }
@@ -89,6 +92,10 @@ criterion = { version = "0.4.0", default-features = false, features = ["cargo_be
8992
web-time = "1.0.0"
9093
winit = "0.30.0"
9194

95+
[target.'cfg(target_os = "android")'.dev-dependencies]
96+
winit = { version = "0.30.0", features = ["android-native-activity"] }
97+
android-activity = "0.6"
98+
9299
[dev-dependencies.image]
93100
version = "0.25.0"
94101
# Disable rayon on web
@@ -110,6 +117,16 @@ members = [
110117
"run-wasm",
111118
]
112119

120+
[[example]]
121+
# Run with `cargo apk r --example winit_android`
122+
name = "winit_android"
123+
crate-type = ["cdylib"]
124+
125+
[[example]]
126+
# Run with `cargo apk r --example winit_multithread_android`
127+
name = "winit_multithread_android"
128+
crate-type = ["cdylib"]
129+
113130
[package.metadata.docs.rs]
114131
all-features = true
115132
rustdoc-args = ["--cfg", "docsrs"]

README.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ For now, the priority for new platforms is:
4242

4343
| Platform ||
4444
|-----------|--|
45-
|Android NDK||
45+
|Android NDK||
4646
| AppKit ||
4747
| Orbital ||
4848
| UIKit ||
@@ -61,6 +61,10 @@ For now, the priority for new platforms is:
6161

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

64+
## Android
65+
66+
To run the Android-specific example on an Android phone: `cargo apk r --example winit_android` or `cargo apk r --example winit_multithread_android`.
67+
6468
## Example
6569

6670
```rust,no_run

examples/winit.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@ use winit::keyboard::{Key, NamedKey};
66
#[path = "utils/winit_app.rs"]
77
mod winit_app;
88

9+
#[cfg(not(target_os = "android"))]
910
fn main() {
10-
let event_loop = EventLoop::new().unwrap();
11+
entry(EventLoop::new().unwrap())
12+
}
1113

14+
pub(crate) fn entry(event_loop: EventLoop<()>) {
1215
let app = winit_app::WinitAppBuilder::with_init(
1316
|elwt| {
1417
let window = winit_app::make_window(elwt, |w| w);

examples/winit_android.rs

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#![cfg(target_os = "android")]
2+
3+
use winit::event_loop::EventLoop;
4+
pub use winit::platform::android::{activity::AndroidApp, EventLoopBuilderExtAndroid};
5+
6+
#[path = "winit.rs"]
7+
mod desktop_example;
8+
9+
/// Run with `cargo apk r --example winit_android`
10+
#[no_mangle]
11+
fn android_main(app: AndroidApp) {
12+
let mut builder = EventLoop::builder();
13+
14+
// Install the Android event loop extension if necessary.
15+
builder.with_android_app(app);
16+
17+
desktop_example::entry(builder.build().unwrap())
18+
}

examples/winit_multithread.rs

+7-6
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
mod winit_app;
66

77
#[cfg(not(target_family = "wasm"))]
8-
mod ex {
8+
pub mod ex {
99
use std::num::NonZeroU32;
1010
use std::sync::{mpsc, Arc, Mutex};
1111
use winit::event::{Event, KeyEvent, WindowEvent};
@@ -59,9 +59,7 @@ mod ex {
5959
}
6060
}
6161

62-
pub(super) fn entry() {
63-
let event_loop = EventLoop::new().unwrap();
64-
62+
pub fn entry(event_loop: EventLoop<()>) {
6563
let app = winit_app::WinitAppBuilder::with_init(
6664
|elwt| {
6765
let attributes = Window::default_attributes();
@@ -135,11 +133,14 @@ mod ex {
135133

136134
#[cfg(target_family = "wasm")]
137135
mod ex {
138-
pub(crate) fn entry() {
136+
use winit::event_loop::EventLoop;
137+
pub(crate) fn entry(_event_loop: EventLoop<()>) {
139138
eprintln!("winit_multithreaded doesn't work on WASM");
140139
}
141140
}
142141

142+
#[cfg(not(target_os = "android"))]
143143
fn main() {
144-
ex::entry();
144+
use winit::event_loop::EventLoop;
145+
ex::entry(EventLoop::new().unwrap())
145146
}

examples/winit_multithread_android.rs

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#![cfg(target_os = "android")]
2+
3+
use winit::event_loop::EventLoop;
4+
pub use winit::platform::android::{activity::AndroidApp, EventLoopBuilderExtAndroid};
5+
6+
#[path = "winit_multithread.rs"]
7+
mod desktop_example;
8+
9+
/// Run with `cargo apk r --example winit_android`
10+
#[no_mangle]
11+
fn android_main(app: AndroidApp) {
12+
let mut builder = EventLoop::builder();
13+
14+
// Install the Android event loop extension if necessary.
15+
builder.with_android_app(app);
16+
17+
desktop_example::ex::entry(builder.build().unwrap())
18+
}

src/backend_dispatch.rs

+2
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,8 @@ macro_rules! make_dispatch {
178178

179179
make_dispatch! {
180180
<D, W> =>
181+
#[cfg(target_os = "android")]
182+
Android(D, backends::android::AndroidImpl<D, W>, backends::android::BufferImpl<'a, D, W>),
181183
#[cfg(x11_platform)]
182184
X11(Arc<backends::x11::X11DisplayImpl<D>>, backends::x11::X11Impl<D, W>, backends::x11::BufferImpl<'a, D, W>),
183185
#[cfg(wayland_platform)]

src/backends/android.rs

+154
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
//! Implementation of software buffering for Android.
2+
3+
use std::marker::PhantomData;
4+
use std::num::{NonZeroI32, NonZeroU32};
5+
6+
use ndk::{
7+
hardware_buffer_format::HardwareBufferFormat,
8+
native_window::{NativeWindow, NativeWindowBufferLockGuard},
9+
};
10+
use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawWindowHandle};
11+
12+
use crate::error::InitError;
13+
use crate::{Rect, SoftBufferError};
14+
15+
/// The handle to a window for software buffering.
16+
pub struct AndroidImpl<D: ?Sized, W: ?Sized> {
17+
native_window: NativeWindow,
18+
19+
_display: PhantomData<D>,
20+
21+
/// The pointer to the window object.
22+
///
23+
/// This is pretty useless because it gives us a pointer to [`NativeWindow`] that we have to increase the refcount on.
24+
/// Alternatively we can use [`NativeWindow::from_ptr()`] wrapped in [`std::mem::ManuallyDrop`]
25+
window: W,
26+
}
27+
28+
// TODO: Current system doesn't require a trait to be implemented here, even though it exists.
29+
impl<D: HasDisplayHandle, W: HasWindowHandle> AndroidImpl<D, W> {
30+
/// Create a new [`AndroidImpl`] from an [`AndroidNdkWindowHandle`].
31+
///
32+
/// # Safety
33+
///
34+
/// The [`AndroidNdkWindowHandle`] must be a valid window handle.
35+
// TODO: That's lame, why can't we get an AndroidNdkWindowHandle directly here
36+
pub(crate) fn new(window: W, _display: &D) -> Result<Self, InitError<W>> {
37+
// Get the raw Android window (surface).
38+
let raw = window.window_handle()?.as_raw();
39+
let RawWindowHandle::AndroidNdk(a) = raw else {
40+
return Err(InitError::Unsupported(window));
41+
};
42+
43+
// Acquire a new owned reference to the window, that will be freed on drop.
44+
let native_window = unsafe { NativeWindow::clone_from_ptr(a.a_native_window.cast()) };
45+
46+
Ok(Self {
47+
native_window,
48+
// _display: DisplayHandle::borrow_raw(raw_window_handle::RawDisplayHandle::Android(
49+
// AndroidDisplayHandle,
50+
// )),
51+
_display: PhantomData,
52+
window,
53+
})
54+
}
55+
56+
#[inline]
57+
pub fn window(&self) -> &W {
58+
&self.window
59+
}
60+
61+
/// Also changes the pixel format to [`HardwareBufferFormat::R8G8B8A8_UNORM`].
62+
pub fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> {
63+
let (width, height) = (|| {
64+
let width = NonZeroI32::try_from(width).ok()?;
65+
let height = NonZeroI32::try_from(height).ok()?;
66+
Some((width, height))
67+
})()
68+
.ok_or(SoftBufferError::SizeOutOfRange { width, height })?;
69+
70+
// Do not change the format.
71+
self.native_window
72+
.set_buffers_geometry(
73+
width.into(),
74+
height.into(),
75+
// Default is typically R5G6B5 16bpp, switch to 32bpp
76+
Some(HardwareBufferFormat::R8G8B8A8_UNORM),
77+
)
78+
.map_err(|err| {
79+
SoftBufferError::PlatformError(
80+
Some("Failed to set buffer geometry on ANativeWindow".to_owned()),
81+
Some(Box::new(err)),
82+
)
83+
})
84+
}
85+
86+
pub fn buffer_mut(&mut self) -> Result<BufferImpl<'_, D, W>, SoftBufferError> {
87+
let lock_guard = self.native_window.lock(None).map_err(|err| {
88+
SoftBufferError::PlatformError(
89+
Some("Failed to lock ANativeWindow".to_owned()),
90+
Some(Box::new(err)),
91+
)
92+
})?;
93+
94+
assert_eq!(
95+
lock_guard.format().bytes_per_pixel(),
96+
Some(4),
97+
"Unexpected buffer format {:?}, please call .resize() first to change it to RGBA8888",
98+
lock_guard.format()
99+
);
100+
101+
Ok(BufferImpl(lock_guard, PhantomData, PhantomData))
102+
}
103+
104+
/// Fetch the buffer from the window.
105+
pub fn fetch(&mut self) -> Result<Vec<u32>, SoftBufferError> {
106+
Err(SoftBufferError::Unimplemented)
107+
}
108+
}
109+
110+
pub struct BufferImpl<'a, D: ?Sized, W>(
111+
NativeWindowBufferLockGuard<'a>,
112+
PhantomData<&'a D>,
113+
PhantomData<&'a W>,
114+
);
115+
116+
// TODO: Move to NativeWindowBufferLockGuard?
117+
unsafe impl<'a, D, W> Send for BufferImpl<'a, D, W> {}
118+
119+
impl<'a, D: HasDisplayHandle + ?Sized, W: HasWindowHandle> BufferImpl<'a, D, W> {
120+
#[inline]
121+
pub fn pixels(&self) -> &[u32] {
122+
todo!()
123+
// unsafe {
124+
// std::slice::from_raw_parts(
125+
// self.0.bits().cast_const().cast(),
126+
// (self.0.stride() * self.0.height()) as usize,
127+
// )
128+
// }
129+
}
130+
131+
#[inline]
132+
pub fn pixels_mut(&mut self) -> &mut [u32] {
133+
let bytes = self.0.bytes().expect("Nonplanar format");
134+
unsafe {
135+
std::slice::from_raw_parts_mut(
136+
bytes.as_mut_ptr().cast(),
137+
bytes.len() / std::mem::size_of::<u32>(),
138+
)
139+
}
140+
}
141+
142+
pub fn age(&self) -> u8 {
143+
0
144+
}
145+
146+
pub fn present(self) -> Result<(), SoftBufferError> {
147+
// Dropping the guard automatically unlocks and posts it
148+
Ok(())
149+
}
150+
151+
pub fn present_with_damage(self, _damage: &[Rect]) -> Result<(), SoftBufferError> {
152+
Err(SoftBufferError::Unimplemented)
153+
}
154+
}

src/backends/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use crate::{ContextInterface, InitError};
22
use raw_window_handle::HasDisplayHandle;
33

4+
#[cfg(target_os = "android")]
5+
pub(crate) mod android;
46
#[cfg(target_vendor = "apple")]
57
pub(crate) mod cg;
68
#[cfg(kms_platform)]

src/backends/win32.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -250,8 +250,8 @@ impl<D: HasDisplayHandle, W: HasWindowHandle> SurfaceInterface<D, W> for Win32Im
250250

251251
fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> {
252252
let (width, height) = (|| {
253-
let width = NonZeroI32::new(i32::try_from(width.get()).ok()?)?;
254-
let height = NonZeroI32::new(i32::try_from(height.get()).ok()?)?;
253+
let width = NonZeroI32::try_from(width).ok()?;
254+
let height = NonZeroI32::try_from(height).ok()?;
255255
Some((width, height))
256256
})()
257257
.ok_or(SoftBufferError::SizeOutOfRange { width, height })?;

0 commit comments

Comments
 (0)