From 04aac6909ec68aa746ecf38d0e73f804519210e4 Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Fri, 16 Jun 2023 17:49:54 +0200 Subject: [PATCH 1/4] Reformat markdown documents --- CHANGELOG.md | 46 +++++++++++++++++++++++----------------------- README.md | 32 ++++++++++++++------------------ 2 files changed, 37 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7736994..4e45763 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,38 +73,38 @@ # 0.3.0 -* On MacOS, the contents scale is updated when set_buffer() is called, to adapt when the window is on a new screen (#68). -* **Breaking:** Split the `GraphicsContext` type into `Context` and `Surface` (#64). -* On Web, cache the document in the `Context` type (#66). -* **Breaking:** Introduce a new "owned buffer" for no-copy presentation (#65). -* Enable support for multi-threaded WASM (#77). -* Fix buffer resizing on X11 (#69). -* Add a set of functions for handling buffer damage (#99). -* Add a `fetch()` function for getting the window contents (#104). -* Bump MSRV to 1.64 (#81). +- On MacOS, the contents scale is updated when set_buffer() is called, to adapt when the window is on a new screen (#68). +- **Breaking:** Split the `GraphicsContext` type into `Context` and `Surface` (#64). +- On Web, cache the document in the `Context` type (#66). +- **Breaking:** Introduce a new "owned buffer" for no-copy presentation (#65). +- Enable support for multi-threaded WASM (#77). +- Fix buffer resizing on X11 (#69). +- Add a set of functions for handling buffer damage (#99). +- Add a `fetch()` function for getting the window contents (#104). +- Bump MSRV to 1.64 (#81). # 0.2.1 -* Bump `windows-sys` to 0.48 +- Bump `windows-sys` to 0.48 # 0.2.0 -* Add support for Redox/Orbital. -* Add support for BSD distributions. -* Ported Windows backend from `winapi` to `windows-sys`. -* **Breaking:** Take a reference to a window instead of owning the window. -* Add a `from_raw` function for directly using raw handles. -* Improvements for Wayland support. -* Support for HiDPI on macOS. -* **Breaking:** Add feature flags for `x11` and `wayland` backends. -* Use static dispatch instead of dynamic dispatch for the backends. -* Add `libxcb` support to the X11 backend. -* Use X11 MIT-SHM extension, if available. +- Add support for Redox/Orbital. +- Add support for BSD distributions. +- Ported Windows backend from `winapi` to `windows-sys`. +- **Breaking:** Take a reference to a window instead of owning the window. +- Add a `from_raw` function for directly using raw handles. +- Improvements for Wayland support. +- Support for HiDPI on macOS. +- **Breaking:** Add feature flags for `x11` and `wayland` backends. +- Use static dispatch instead of dynamic dispatch for the backends. +- Add `libxcb` support to the X11 backend. +- Use X11 MIT-SHM extension, if available. # 0.1.1 -* Added WASM support (Thanks to [Liamolucko](https://github.com/Liamolucko)!) -* CALayer is now used for Mac OS backend, which is more flexible about what happens in the windowing library (Thanks to [lunixbochs](https://github.com/lunixbochs)!) +- Added WASM support (Thanks to [Liamolucko](https://github.com/Liamolucko)!) +- CALayer is now used for Mac OS backend, which is more flexible about what happens in the windowing library (Thanks to [lunixbochs](https://github.com/lunixbochs)!) # 0.1.0 diff --git a/README.md b/README.md index f3a556b..d24b184 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -Overview -== +# Softbuffer Enables software rendering via drawing an image straight to a window. @@ -7,8 +6,7 @@ Softbuffer integrates with the [`raw-window-handle`](https://crates.io/crates/ra to allow writing pixels to a window in a cross-platform way while using the very high quality dedicated window management libraries that are available in the Rust ecosystem. -Alternatives -== +## Alternatives [minifb](https://crates.io/crates/minifb) also allows putting a 2D buffer/image on a window in a platform-independent way. Minifb's approach to doing window management itself, however, is problematic code duplication. We already have very high quality @@ -24,21 +22,21 @@ hardware accelerated graphics stack in any way, and is thus more portable to ins hardware acceleration (e.g. VMs, older computers, computers with misconfigured drivers). Softbuffer should be used over pixels when its GPU-accelerated post-processing effects are not needed. -License & Credits -== +## License & Credits This library is dual-licensed under MIT or Apache-2.0, just like minifb and rust. Significant portions of code were taken from the minifb library to do platform-specific work. -Platform support: -== +## Platform support: + Some, but not all, platforms supported in [raw-window-handle](https://crates.io/crates/raw-window-handle) are supported by Softbuffer. Pull requests are welcome to add new platforms! **Nonetheless, all major desktop platforms that winit uses on desktop are supported.** For now, the priority for new platforms is: -1) to have at least one platform on each OS working (e.g. one of Win32 or WinRT, or one of Xlib, Xcb, and Wayland) and -2) for that one platform on each OS to be the one that winit uses. + +1. to have at least one platform on each OS working (e.g. one of Win32 or WinRT, or one of Xlib, Xcb, and Wayland) and +2. for that one platform on each OS to be the one that winit uses. (PRs will be accepted for any platform, even if it does not follow the above priority.) @@ -59,13 +57,12 @@ For now, the priority for new platforms is: ❔: Immature\ ❌: Absent -WebAssembly ------------ +## WebAssembly To run an example with the web backend: `cargo run-wasm --example winit` -Example -== +## Example + ```rust,no_run use std::num::NonZeroU32; use std::rc::Rc; @@ -138,8 +135,8 @@ fn main() { } ``` -MSRV Policy -== +## MSRV Policy + This crate's Minimum Supported Rust Version (MSRV) is **1.70**. Changes to the MSRV will be accompanied by a minor version bump. @@ -163,7 +160,6 @@ same MSRV policy. [`rust-windowing`]: https://github.com/rust-windowing -Changelog ---------- +## Changelog See the [changelog](CHANGELOG.md) for a list of this package's versions and the changes made in each version. From 44234b2b542da2a49185dfb0881db8788c65a277 Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Thu, 9 Nov 2023 00:35:50 +0100 Subject: [PATCH 2/4] Switch to `let-else` and `ok_or()` to reduce nesting --- src/backends/kms.rs | 20 +++++++++----------- src/backends/orbital.rs | 5 ++--- src/backends/wayland/mod.rs | 14 ++++++-------- src/backends/web.rs | 7 +++---- src/backends/win32.rs | 5 ++--- src/backends/x11.rs | 29 +++++++++++------------------ 6 files changed, 33 insertions(+), 47 deletions(-) diff --git a/src/backends/kms.rs b/src/backends/kms.rs index 1ff6682..71b2515 100644 --- a/src/backends/kms.rs +++ b/src/backends/kms.rs @@ -43,16 +43,15 @@ impl ContextInterface for Arc where D: Sized, { - let fd = match display.display_handle()?.as_raw() { - RawDisplayHandle::Drm(drm) => drm.fd, - _ => return Err(InitError::Unsupported(display)), + let RawDisplayHandle::Drm(drm) = display.display_handle()?.as_raw() else { + return Err(InitError::Unsupported(display)); }; - if fd == -1 { + if drm.fd == -1 { return Err(SoftBufferError::IncompleteDisplayHandle.into()); } // SAFETY: Invariants guaranteed by the user. - let fd = unsafe { BorrowedFd::borrow_raw(fd) }; + let fd = unsafe { BorrowedFd::borrow_raw(drm.fd) }; Ok(Arc::new(KmsDisplayImpl { fd, @@ -142,13 +141,12 @@ impl SurfaceInterface fo /// Create a new KMS backend. fn new(window: W, display: &Arc>) -> Result> { // Make sure that the window handle is valid. - let plane_handle = match window.window_handle()?.as_raw() { - RawWindowHandle::Drm(drm) => match NonZeroU32::new(drm.plane) { - Some(handle) => plane::Handle::from(handle), - None => return Err(SoftBufferError::IncompleteWindowHandle.into()), - }, - _ => return Err(InitError::Unsupported(window)), + let RawWindowHandle::Drm(drm) = window.window_handle()?.as_raw() else { + return Err(InitError::Unsupported(window)); }; + let plane_handle = + NonZeroU32::new(drm.plane).ok_or(SoftBufferError::IncompleteWindowHandle)?; + let plane_handle = plane::Handle::from(plane_handle); let plane_info = display .get_plane(plane_handle) diff --git a/src/backends/orbital.rs b/src/backends/orbital.rs index cd5dd05..d54683f 100644 --- a/src/backends/orbital.rs +++ b/src/backends/orbital.rs @@ -135,9 +135,8 @@ impl SurfaceInterface for Orbital fn new(window: W, _display: &D) -> Result> { let raw = window.window_handle()?.as_raw(); - let handle = match raw { - RawWindowHandle::Orbital(handle) => handle, - _ => return Err(InitError::Unsupported(window)), + let RawWindowHandle::Orbital(handle) = raw else { + return Err(InitError::Unsupported(window)); }; Ok(Self { diff --git a/src/backends/wayland/mod.rs b/src/backends/wayland/mod.rs index 2c1e33b..000e553 100644 --- a/src/backends/wayland/mod.rs +++ b/src/backends/wayland/mod.rs @@ -45,12 +45,11 @@ impl ContextInterface for Arc w.display, - _ => return Err(InitError::Unsupported(display)), + let RawDisplayHandle::Wayland(w) = raw else { + return Err(InitError::Unsupported(display)); }; - let backend = unsafe { Backend::from_foreign_display(wayland_handle.as_ptr().cast()) }; + let backend = unsafe { Backend::from_foreign_display(w.display.as_ptr().cast()) }; let conn = Connection::from_backend(backend); let (globals, event_queue) = registry_queue_init(&conn).swbuf_err("Failed to make round trip to server")?; @@ -159,15 +158,14 @@ impl SurfaceInterface fn new(window: W, display: &Arc>) -> Result> { // Get the raw Wayland window. let raw = window.window_handle()?.as_raw(); - let wayland_handle = match raw { - RawWindowHandle::Wayland(w) => w.surface, - _ => return Err(InitError::Unsupported(window)), + let RawWindowHandle::Wayland(w) = raw else { + return Err(InitError::Unsupported(window)); }; let surface_id = unsafe { ObjectId::from_ptr( wl_surface::WlSurface::interface(), - wayland_handle.as_ptr().cast(), + w.surface.as_ptr().cast(), ) } .swbuf_err("Failed to create proxy for surface ID.")?; diff --git a/src/backends/web.rs b/src/backends/web.rs index 10b0ccd..4eb8fe0 100644 --- a/src/backends/web.rs +++ b/src/backends/web.rs @@ -26,10 +26,9 @@ pub struct WebDisplayImpl { impl ContextInterface for WebDisplayImpl { fn new(display: D) -> Result> { let raw = display.display_handle()?.as_raw(); - match raw { - RawDisplayHandle::Web(..) => {} - _ => return Err(InitError::Unsupported(display)), - } + let RawDisplayHandle::Web(..) = raw else { + return Err(InitError::Unsupported(display)); + }; let document = web_sys::window() .swbuf_err("`Window` is not present in this runtime")? diff --git a/src/backends/win32.rs b/src/backends/win32.rs index 01f6c83..89c7ef4 100644 --- a/src/backends/win32.rs +++ b/src/backends/win32.rs @@ -216,9 +216,8 @@ impl SurfaceInterface for Win32Im /// Create a new `Win32Impl` from a `Win32WindowHandle`. fn new(window: W, _display: &D) -> Result> { let raw = window.window_handle()?.as_raw(); - let handle = match raw { - RawWindowHandle::Win32(handle) => handle, - _ => return Err(crate::InitError::Unsupported(window)), + let RawWindowHandle::Win32(handle) = raw else { + return Err(crate::InitError::Unsupported(window)); }; // Get the handle to the device context. diff --git a/src/backends/x11.rs b/src/backends/x11.rs index b91081c..460ad36 100644 --- a/src/backends/x11.rs +++ b/src/backends/x11.rs @@ -196,10 +196,8 @@ impl SurfaceInterface fo let window_handle = match raw { RawWindowHandle::Xcb(xcb) => xcb, RawWindowHandle::Xlib(xlib) => { - let window = match NonZeroU32::new(xlib.window as u32) { - Some(window) => window, - None => return Err(SoftBufferError::IncompleteWindowHandle.into()), - }; + let window = NonZeroU32::new(xlib.window as u32) + .ok_or(SoftBufferError::IncompleteWindowHandle)?; let mut xcb_window_handle = XcbWindowHandle::new(window); xcb_window_handle.visual_id = NonZeroU32::new(xlib.visual_id as u32); xcb_window_handle @@ -705,26 +703,21 @@ impl ShmSegment { id.set_len(size as u64)?; // Map the shared memory to our file descriptor space. - let ptr = unsafe { - let ptr = mm::mmap( + let ptr = NonNull::new(unsafe { + mm::mmap( null_mut(), size, mm::ProtFlags::READ | mm::ProtFlags::WRITE, mm::MapFlags::SHARED, &id, 0, - )?; - - match NonNull::new(ptr.cast()) { - Some(ptr) => ptr, - None => { - return Err(io::Error::new( - io::ErrorKind::Other, - "unexpected null when mapping SHM segment", - )); - } - } - }; + )? + }) + .ok_or(io::Error::new( + io::ErrorKind::Other, + "unexpected null when mapping SHM segment", + ))? + .cast(); Ok(Self { id, From bf94162f9ed9246336fc0e153ef2de05f5d9712e Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Wed, 10 Jul 2024 14:37:35 +0200 Subject: [PATCH 3/4] Add a new backend and examples for Android --- .github/CODEOWNERS | 3 + Cargo.toml | 17 +++ README.md | 6 +- examples/winit.rs | 5 +- examples/winit_android.rs | 18 +++ examples/winit_multithread.rs | 13 ++- examples/winit_multithread_android.rs | 18 +++ src/backend_dispatch.rs | 2 + src/backends/android.rs | 154 ++++++++++++++++++++++++++ src/backends/mod.rs | 2 + src/backends/win32.rs | 4 +- 11 files changed, 232 insertions(+), 10 deletions(-) create mode 100644 examples/winit_android.rs create mode 100644 examples/winit_multithread_android.rs create mode 100644 src/backends/android.rs diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b48a288..d377245 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,3 +1,6 @@ +# Android +/src/android.rs @MarijnS95 + # Apple platforms /src/cg.rs @madsmtm diff --git a/Cargo.toml b/Cargo.toml index fdce087..57a963e 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 } @@ -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 @@ -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"] diff --git a/README.md b/README.md index d24b184..39cd0db 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ For now, the priority for new platforms is: | Platform || |-----------|--| -|Android NDK|❌| +|Android NDK|✅| | AppKit |✅| | Orbital |✅| | UIKit |✅| @@ -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 diff --git a/examples/winit.rs b/examples/winit.rs index 09c6c10..27a3cef 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(); + 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); diff --git a/examples/winit_android.rs b/examples/winit_android.rs new file mode 100644 index 0000000..375de37 --- /dev/null +++ b/examples/winit_android.rs @@ -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()) +} diff --git a/examples/winit_multithread.rs b/examples/winit_multithread.rs index c0d208d..03afcc5 100644 --- a/examples/winit_multithread.rs +++ b/examples/winit_multithread.rs @@ -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}; @@ -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(); @@ -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()) } diff --git a/examples/winit_multithread_android.rs b/examples/winit_multithread_android.rs new file mode 100644 index 0000000..6ce92cb --- /dev/null +++ b/examples/winit_multithread_android.rs @@ -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()) +} diff --git a/src/backend_dispatch.rs b/src/backend_dispatch.rs index 208f82c..a86832e 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 0000000..995e03a --- /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 { + 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) + } +} diff --git a/src/backends/mod.rs b/src/backends/mod.rs index f700b05..703401d 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_vendor = "apple")] pub(crate) mod cg; #[cfg(kms_platform)] diff --git a/src/backends/win32.rs b/src/backends/win32.rs index 89c7ef4..72dc6d0 100644 --- a/src/backends/win32.rs +++ b/src/backends/win32.rs @@ -250,8 +250,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 })?; From b82e269b40893f934c2ac1a5ebf2075cbeae74cf Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Sat, 3 Aug 2024 12:02:59 +0200 Subject: [PATCH 4/4] android: Implement format swizzle via buffer copies --- Cargo.toml | 1 + src/backends/android.rs | 142 ++++++++++++++++++++++------------------ src/lib.rs | 5 +- 3 files changed, 85 insertions(+), 63 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 57a963e..153e692 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ log = "0.4.17" raw_window_handle = { package = "raw-window-handle", version = "0.6", features = ["std"] } [target.'cfg(target_os = "android")'.dependencies] +bytemuck = "1.12.3" ndk = "0.9.0" [target.'cfg(all(unix, not(any(target_vendor = "apple", target_os = "android", target_os = "redox"))))'.dependencies] diff --git a/src/backends/android.rs b/src/backends/android.rs index 995e03a..a14aaef 100644 --- a/src/backends/android.rs +++ b/src/backends/android.rs @@ -7,59 +7,52 @@ use ndk::{ hardware_buffer_format::HardwareBufferFormat, native_window::{NativeWindow, NativeWindowBufferLockGuard}, }; +#[cfg(doc)] +use raw_window_handle::AndroidNdkWindowHandle; use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawWindowHandle}; use crate::error::InitError; -use crate::{Rect, SoftBufferError}; +use crate::{BufferInterface, Rect, SoftBufferError, SurfaceInterface}; /// The handle to a window for software buffering. -pub struct AndroidImpl { +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, + _display: PhantomData, } -// TODO: Current system doesn't require a trait to be implemented here, even though it exists. -impl AndroidImpl { +impl SurfaceInterface for AndroidImpl { + type Context = D; + type Buffer<'a> + = BufferImpl<'a, D, W> + where + Self: 'a; + /// 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). + fn new(window: W, _display: &Self::Context) -> Result> { 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. + // SAFETY: We have confirmed that the window handle is valid. 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 { + 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> { + 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()?; @@ -67,13 +60,12 @@ impl AndroidImpl { })() .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), + Some(HardwareBufferFormat::R8G8B8X8_UNORM), ) .map_err(|err| { SoftBufferError::PlatformError( @@ -83,72 +75,98 @@ impl AndroidImpl { }) } - pub fn buffer_mut(&mut self) -> Result, SoftBufferError> { - let lock_guard = self.native_window.lock(None).map_err(|err| { + fn buffer_mut(&mut self) -> Result, SoftBufferError> { + let native_window_buffer = 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() - ); + if !matches!( + native_window_buffer.format(), + // These are the only formats we support + HardwareBufferFormat::R8G8B8A8_UNORM | HardwareBufferFormat::R8G8B8X8_UNORM + ) { + return Err(SoftBufferError::PlatformError( + Some(format!( + "Unexpected buffer format {:?}, please call \ + .resize() first to change it to RGBx8888", + native_window_buffer.format() + )), + None, + )); + } + + let buffer = vec![0; native_window_buffer.width() * native_window_buffer.height()]; - Ok(BufferImpl(lock_guard, PhantomData, PhantomData)) + Ok(BufferImpl { + native_window_buffer, + buffer, + marker: PhantomData, + }) } /// Fetch the buffer from the window. - pub fn fetch(&mut self) -> Result, SoftBufferError> { + fn fetch(&mut self) -> Result, SoftBufferError> { Err(SoftBufferError::Unimplemented) } } -pub struct BufferImpl<'a, D: ?Sized, W>( - NativeWindowBufferLockGuard<'a>, - PhantomData<&'a D>, - PhantomData<&'a W>, -); +pub struct BufferImpl<'a, D: ?Sized, W> { + native_window_buffer: NativeWindowBufferLockGuard<'a>, + buffer: Vec, + marker: PhantomData<(&'a D, &'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> { +impl<'a, D: HasDisplayHandle, W: HasWindowHandle> BufferInterface for 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, - // ) - // } + fn pixels(&self) -> &[u32] { + &self.buffer } #[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::(), - ) - } + fn pixels_mut(&mut self) -> &mut [u32] { + &mut self.buffer } - pub fn age(&self) -> u8 { + #[inline] + fn age(&self) -> u8 { 0 } - pub fn present(self) -> Result<(), SoftBufferError> { - // Dropping the guard automatically unlocks and posts it + // TODO: This function is pretty slow this way + fn present(mut self) -> Result<(), SoftBufferError> { + let input_lines = self.buffer.chunks(self.native_window_buffer.width()); + for (output, input) in self + .native_window_buffer + .lines() + // Unreachable as we checked before that this is a valid, mappable format + .unwrap() + .zip(input_lines) + { + // .lines() removed the stride + assert_eq!(output.len(), input.len() * 4); + + for (i, pixel) in input.iter().enumerate() { + // Swizzle colors from RGBX to BGR + let [b, g, r, _] = pixel.to_le_bytes(); + output[i * 4].write(b); + output[i * 4 + 1].write(g); + output[i * 4 + 2].write(r); + // TODO alpha? + } + } Ok(()) } - pub fn present_with_damage(self, _damage: &[Rect]) -> Result<(), SoftBufferError> { - Err(SoftBufferError::Unimplemented) + fn present_with_damage(self, _damage: &[Rect]) -> Result<(), SoftBufferError> { + // TODO: Android requires the damage rect _at lock time_ + // Since we're faking the backing buffer _anyway_, we could even fake the surface lock + // and lock it here (if it doesn't influence timings). + self.present() } } diff --git a/src/lib.rs b/src/lib.rs index 6647f59..2e25967 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -196,13 +196,16 @@ impl HasWindowHandle for Surface /// - Web /// - AppKit /// - UIKit +/// +/// Buffer copies an channel swizzling happen on: +/// - Android pub struct Buffer<'a, D, W> { buffer_impl: BufferDispatch<'a, D, W>, _marker: PhantomData<(Arc, Cell<()>)>, } impl Buffer<'_, D, W> { - /// Is age is the number of frames ago this buffer was last presented. So if the value is + /// `age` is the number of frames ago this buffer was last presented. So if the value is /// `1`, it is the same as the last frame, and if it is `2`, it is the same as the frame /// before that (for backends using double buffering). If the value is `0`, it is a new /// buffer that has unspecified contents.