diff --git a/README.md b/README.md index cd049720..13ab3b1f 100644 --- a/README.md +++ b/README.md @@ -9,14 +9,13 @@ > ndk-build | https://github.com/rust-mobile/cargo-apk | > cargo-apk | https://github.com/rust-mobile/cargo-apk | -[![Rust](https://github.com/rust-mobile/ndk/workflows/Rust/badge.svg)](https://github.com/rust-mobile/ndk/actions) ![MIT license](https://img.shields.io/badge/License-MIT-green.svg) ![APACHE2 license](https://img.shields.io/badge/License-APACHE2-green.svg) +[![ci](https://github.com/rust-mobile/ndk/actions/workflows/rust.yml/badge.svg)](https://github.com/rust-mobile/ndk/actions/workflows/rust.yml) ![MIT license](https://img.shields.io/badge/License-MIT-green.svg) ![APACHE2 license](https://img.shields.io/badge/License-APACHE2-green.svg) Rust bindings to the [Android NDK](https://developer.android.com/ndk) Name | Description | Badges --- | --- | --- -[`ndk-sys`](./ndk-sys) | Raw FFI bindings to the NDK | [![crates.io](https://img.shields.io/crates/v/ndk-sys.svg)](https://crates.io/crates/ndk-sys) [![crates.io](https://docs.rs/ndk-sys/badge.svg)](https://docs.rs/ndk-sys) [![MSRV](https://img.shields.io/badge/rustc-1.60.0+-ab6000.svg)](https://blog.rust-lang.org/2022/04/07/Rust-1.60.0.html) -[`ndk`](./ndk) | Safe abstraction of the bindings | [![crates.io](https://img.shields.io/crates/v/ndk.svg)](https://crates.io/crates/ndk) [![crates.io](https://docs.rs/ndk/badge.svg)](https://docs.rs/ndk) [![MSRV](https://img.shields.io/badge/rustc-1.64.0+-ab6000.svg)](https://blog.rust-lang.org/2022/09/22/Rust-1.64.0.html) - +[`ndk-sys`](./ndk-sys) | Raw FFI bindings to the NDK | [![crates.io](https://img.shields.io/crates/v/ndk-sys.svg)](https://crates.io/crates/ndk-sys) [![Docs](https://docs.rs/ndk-sys/badge.svg)](https://docs.rs/ndk-sys) [![MSRV](https://img.shields.io/badge/rustc-1.60.0+-ab6000.svg)](https://blog.rust-lang.org/2022/04/07/Rust-1.60.0.html) +[`ndk`](./ndk) | Safe abstraction of the bindings | [![crates.io](https://img.shields.io/crates/v/ndk.svg)](https://crates.io/crates/ndk) [![Docs](https://docs.rs/ndk/badge.svg)](https://docs.rs/ndk) [![MSRV](https://img.shields.io/badge/rustc-1.64.0+-ab6000.svg)](https://blog.rust-lang.org/2022/09/22/Rust-1.64.0.html) See these [`ndk-examples`](https://github.com/rust-mobile/cargo-apk/tree/main/examples/examples) and these [`rust-android-examples`](https://github.com/rust-mobile/rust-android-examples) for examples using the NDK. diff --git a/ndk/CHANGELOG.md b/ndk/CHANGELOG.md index efa94711..ed244f69 100644 --- a/ndk/CHANGELOG.md +++ b/ndk/CHANGELOG.md @@ -11,6 +11,7 @@ - **Breaking:** Renamed and moved "`media`" error types and helpers to a new `media_error` module. (#399) - **Breaking:** media_codec: Wrap common dequeued-buffer status codes in enum. (#401) - **Breaking:** media_codec: Return `MaybeUninit` bytes in `buffer_mut()`. (#403) +- native_window: Add `lock()` to blit raw pixel data. (#404) - hardware_buffer_format: Add `YCbCr_P010` and `R8_UNORM` variants. (#405) # 0.7.0 (2022-07-24) diff --git a/ndk/src/hardware_buffer_format.rs b/ndk/src/hardware_buffer_format.rs index bbe04f48..7095099a 100644 --- a/ndk/src/hardware_buffer_format.rs +++ b/ndk/src/hardware_buffer_format.rs @@ -16,6 +16,7 @@ pub enum HardwareBufferFormat { R8G8B8_UNORM = ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM.0, /// Matches deprecated [`ffi::ANativeWindow_LegacyFormat::WINDOW_FORMAT_RGB_565`]. R5G6B5_UNORM = ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM.0, + #[cfg(feature = "api-level-26")] R16G16B16A16_FLOAT = ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT.0, #[cfg(feature = "api-level-26")] R10G10B10A2_UNORM = ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM.0, @@ -40,3 +41,40 @@ pub enum HardwareBufferFormat { #[cfg(feature = "api-level-26")] R8_UNORM = ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8_UNORM.0, } + +impl HardwareBufferFormat { + /// Returns [`None`] when there is no immediate byte size available for this format, for + /// example on planar buffer formats. + pub fn bytes_per_pixel(self) -> Option { + Some(match self { + Self::R8G8B8A8_UNORM | Self::R8G8B8X8_UNORM => 4, + #[cfg(feature = "api-level-26")] + Self::R8G8B8_UNORM => 3, + Self::R5G6B5_UNORM => 2, + #[cfg(feature = "api-level-26")] + Self::R16G16B16A16_FLOAT => 8, + #[cfg(feature = "api-level-26")] + Self::R10G10B10A2_UNORM => 4, + #[cfg(feature = "api-level-26")] + Self::BLOB => 1, + #[cfg(feature = "api-level-26")] + Self::D16_UNORM => 2, + #[cfg(feature = "api-level-26")] + Self::D24_UNORM => 3, + #[cfg(feature = "api-level-26")] + Self::D24_UNORM_S8_UINT => 4, + #[cfg(feature = "api-level-26")] + Self::D32_FLOAT => 4, + #[cfg(feature = "api-level-26")] + Self::D32_FLOAT_S8_UINT => 5, + #[cfg(feature = "api-level-26")] + Self::S8_UINT => 1, + #[cfg(feature = "api-level-26")] + Self::Y8Cb8Cr8_420 => 3, + #[cfg(feature = "api-level-26")] + Self::YCbCr_P010 => return None, + #[cfg(feature = "api-level-26")] + Self::R8_UNORM => 1, + }) + } +} diff --git a/ndk/src/native_activity.rs b/ndk/src/native_activity.rs index bc041e84..3f5fef3a 100644 --- a/ndk/src/native_activity.rs +++ b/ndk/src/native_activity.rs @@ -67,11 +67,11 @@ unsafe impl Send for NativeActivity {} unsafe impl Sync for NativeActivity {} impl NativeActivity { - /// Create a `NativeActivity` from a pointer + /// Create a [`NativeActivity`] from a pointer /// /// # Safety /// By calling this function, you assert that it is a valid pointer to a native - /// `ANativeActivity`. + /// [`ffi::ANativeActivity`]. pub unsafe fn from_ptr(ptr: NonNull) -> Self { Self { ptr } } diff --git a/ndk/src/native_window.rs b/ndk/src/native_window.rs index 84af1ac5..35cdd13d 100644 --- a/ndk/src/native_window.rs +++ b/ndk/src/native_window.rs @@ -7,7 +7,9 @@ use crate::utils::status_to_io_result; pub use super::hardware_buffer_format::HardwareBufferFormat; use jni_sys::{jobject, JNIEnv}; use raw_window_handle::{AndroidNdkWindowHandle, HasRawWindowHandle, RawWindowHandle}; -use std::{convert::TryFrom, ffi::c_void, io::Result, ptr::NonNull}; +use std::{convert::TryFrom, ffi::c_void, io::Result, mem::MaybeUninit, ptr::NonNull}; + +pub type Rect = ffi::ARect; // [`NativeWindow`] represents the producer end of an image queue /// @@ -126,4 +128,89 @@ impl NativeWindow { pub unsafe fn to_surface(&self, env: *mut JNIEnv) -> jobject { ffi::ANativeWindow_toSurface(env, self.ptr().as_ptr()) } + + /// Lock the window's next drawing surface for writing. + /// + /// Optionally pass the region you intend to draw into `dirty_bounds`. When this function + /// returns it is updated (commonly enlarged) with the actual area the caller needs to redraw, + /// and can be retrieved from [`NativeWindowBufferLockGuard::dirty_bounds()`]. + pub fn lock(&self, mut dirty_bounds: Option) -> Result { + let dirty_bounds_ptr = match &mut dirty_bounds { + Some(dirty_bounds) => dirty_bounds, + None => std::ptr::null_mut(), + }; + let mut buffer = MaybeUninit::uninit(); + let ret = unsafe { + ffi::ANativeWindow_lock(self.ptr.as_ptr(), buffer.as_mut_ptr(), dirty_bounds_ptr) + }; + status_to_io_result(ret, ())?; + + Ok(NativeWindowBufferLockGuard { + window: self, + buffer: unsafe { buffer.assume_init() }, + dirty_bounds, + }) + } +} + +/// Lock holding the next drawing surface for writing. It is unlocked and posted on [`drop()`]. +#[derive(Debug)] +pub struct NativeWindowBufferLockGuard<'a> { + window: &'a NativeWindow, + buffer: ffi::ANativeWindow_Buffer, + dirty_bounds: Option, +} + +impl<'a> NativeWindowBufferLockGuard<'a> { + /// The number of pixels that are shown horizontally. + pub fn width(&self) -> usize { + usize::try_from(self.buffer.width).unwrap() + } + + // The number of pixels that are shown vertically. + pub fn height(&self) -> usize { + usize::try_from(self.buffer.height).unwrap() + } + + /// The number of _pixels_ that a line in the buffer takes in memory. + /// + /// This may be `>= width`. + pub fn stride(&self) -> usize { + usize::try_from(self.buffer.stride).unwrap() + } + + /// The format of the buffer. One of [`HardwareBufferFormat`]. + pub fn format(&self) -> HardwareBufferFormat { + let format = u32::try_from(self.buffer.format).unwrap(); + HardwareBufferFormat::try_from(format).unwrap() + } + + /// The actual bits. + pub fn bits(&mut self) -> *mut std::ffi::c_void { + self.buffer.bits + } + + /// Safe write access to likely uninitialized pixel buffer data. + /// + /// Returns [`None`] when there is no [`HardwareBufferFormat::bytes_per_pixel()`] size + /// available for this [`format()`][Self::format()]. + /// + /// The returned slice consists of [`stride()`][Self::stride()] * [`height()`][Self::height()] + /// \* [`HardwareBufferFormat::bytes_per_pixel()`] bytes. + pub fn bytes(&mut self) -> Option<&mut [MaybeUninit]> { + let num_pixels = self.stride() * self.height(); + let num_bytes = num_pixels * self.format().bytes_per_pixel()?; + Some(unsafe { std::slice::from_raw_parts_mut(self.bits().cast(), num_bytes) }) + } + + pub fn dirty_bounds(&self) -> Option { + self.dirty_bounds + } +} + +impl<'a> Drop for NativeWindowBufferLockGuard<'a> { + fn drop(&mut self) { + let ret = unsafe { ffi::ANativeWindow_unlockAndPost(self.window.ptr.as_ptr()) }; + assert_eq!(ret, 0); + } }