Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ndk/native_window: Add lock() to blit raw pixel data #404

Merged
merged 1 commit into from
Jun 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it would be possible to have a function that returns a mutable slice instead of a pointer?

Copy link
Member Author

@MarijnS95 MarijnS95 Jun 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had considered that, and especially in the context of softbuffer it is UB to cast a pointer to a safe slice, since these APIs are mostly for writing to memory.

It'll have to be &mut [MaybeUninit<u8>].

Copy link
Member Author

@MarijnS95 MarijnS95 Jun 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@notgull done this now. Perhaps it is also interesting to add API's that give access to individual scanlines, by offsetting the pointer by stride() * num_rows * bpp and giving the user only a slice of length width() * bpp? And then wrap that in an iterator again so that it becomes very natural to update pixels?

That won't help for softbuffer though which currently assumes contiguous memory.

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