Skip to content

Commit

Permalink
ndk/native_window: Add lock() to blit raw pixel data
Browse files Browse the repository at this point in the history
  • Loading branch information
MarijnS95 committed Jun 23, 2023
1 parent 53aa779 commit dd12f8d
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 7 deletions.
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
1 change: 1 addition & 0 deletions ndk/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
38 changes: 38 additions & 0 deletions ndk/src/hardware_buffer_format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<usize> {
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,
})
}
}
4 changes: 2 additions & 2 deletions ndk/src/native_activity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ffi::ANativeActivity>) -> Self {
Self { ptr }
}
Expand Down
111 changes: 110 additions & 1 deletion ndk/src/native_window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
///
Expand Down Expand Up @@ -126,4 +128,111 @@ 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.
pub fn lock(&self, dirty_bounds: Option<&mut Rect>) -> Result<NativeWindowBufferLockGuard> {
let dirty_bounds = match 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)
};
status_to_io_result(ret, ())?;

Ok(NativeWindowBufferLockGuard {
window: self,
buffer: unsafe { buffer.assume_init() },
})
}
}

/// 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,
}

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.
///
/// This points to a memory segment of [`stride()`][Self::stride()] *
/// [`height()`][Self::height()] * [`HardwareBufferFormat::bytes_per_pixel()`] bytes.
///
/// Only [`width()`][Self::width()] pixels are visible for each [`stride()`][Self::stride()]
/// line of pixels in the buffer.
///
/// See [`bytes()`][Self::bytes()] for safe access to these bytes.
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.
///
/// Only [`width()`][Self::width()] pixels are visible for each [`stride()`][Self::stride()]
/// line of pixels in the buffer.
pub fn bytes(&mut self) -> Option<&mut [MaybeUninit<u8>]> {
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) })
}

/// Returns a slice of bytes for each line of visible pixels in the buffer, ignoring any
/// padding pixels incurred by the stride.
///
/// See [`bits()`][Self::bits()] and [`bytes()`][Self::bytes()] for contiguous access to the
/// underlying buffer.
pub fn lines(&mut self) -> Option<impl Iterator<Item = &mut [MaybeUninit<u8>]>> {
let bpp = self.format().bytes_per_pixel()?;
let scanline_bytes = bpp * self.stride();
let width_bytes = bpp * self.width();
let bytes = self.bytes()?;

Some(
bytes
.chunks_exact_mut(scanline_bytes)
.map(move |scanline| &mut scanline[..width_bytes]),
)
}
}

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);
}
}

0 comments on commit dd12f8d

Please sign in to comment.