From 8e1148fd352502b117ea073e650d0a376478815b Mon Sep 17 00:00:00 2001 From: Lattice 0 Date: Thu, 9 Jun 2022 10:39:36 -0300 Subject: [PATCH] Add `SurfaceTexture` bindings (#267) Co-authored-by: Marijn Suijten --- ndk/CHANGELOG.md | 1 + ndk/src/lib.rs | 2 + ndk/src/posix.rs | 10 +++ ndk/src/surface_texture.rs | 172 +++++++++++++++++++++++++++++++++++++ 4 files changed, 185 insertions(+) create mode 100644 ndk/src/posix.rs create mode 100644 ndk/src/surface_texture.rs diff --git a/ndk/CHANGELOG.md b/ndk/CHANGELOG.md index 4bb4d463..5f4e9693 100644 --- a/ndk/CHANGELOG.md +++ b/ndk/CHANGELOG.md @@ -11,6 +11,7 @@ - native_window: Add `format()` getter and `set_buffers_geometry()` setter. (#276) - native_activity: Add `set_window_format()` setter. (#277) - native_activity: Add `set_window_flags()` to change window behavior. (#278) +- Add `SurfaceTexture` bindings. (#267) # 0.6.0 (2022-01-05) diff --git a/ndk/src/lib.rs b/ndk/src/lib.rs index 19a8bbc4..29f267ea 100644 --- a/ndk/src/lib.rs +++ b/ndk/src/lib.rs @@ -27,4 +27,6 @@ pub mod looper; pub mod media; pub mod native_activity; pub mod native_window; +pub mod posix; +pub mod surface_texture; pub mod trace; diff --git a/ndk/src/posix.rs b/ndk/src/posix.rs new file mode 100644 index 00000000..c0f81817 --- /dev/null +++ b/ndk/src/posix.rs @@ -0,0 +1,10 @@ +use thiserror::Error; + +#[derive(Debug, Error)] +pub struct PosixError(pub i32); + +impl std::fmt::Display for PosixError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "Posix Error: {}", self.0) + } +} diff --git a/ndk/src/surface_texture.rs b/ndk/src/surface_texture.rs new file mode 100644 index 00000000..2d79bd77 --- /dev/null +++ b/ndk/src/surface_texture.rs @@ -0,0 +1,172 @@ +//! Bindings for [`ASurfaceTexture`] +//! +//! See for an architectural overview of +//! [`SurfaceTexture`] internals. +//! +//! [`ASurfaceTexture`]: https://developer.android.com/ndk/reference/group/surface-texture +#![cfg(feature = "api-level-28")] + +use super::posix::PosixError; +use crate::native_window::NativeWindow; +use jni_sys::{jobject, JNIEnv}; +use std::{convert::TryInto, ptr::NonNull, time::Duration}; + +/// An opaque type to manage [`android.graphics.SurfaceTexture`] from native code +/// +/// [`android.graphics.SurfaceTexture`]: https://developer.android.com/reference/android/graphics/SurfaceTexture +#[derive(Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct SurfaceTexture { + ptr: NonNull, +} + +unsafe impl Send for SurfaceTexture {} + +impl Drop for SurfaceTexture { + fn drop(&mut self) { + unsafe { ffi::ASurfaceTexture_release(self.ptr.as_ptr()) } + } +} + +impl SurfaceTexture { + /// Assumes ownership of `ptr` + /// + /// # Safety + /// `ptr` must be a valid pointer to an Android [`ffi::ASurfaceTexture`]. + pub unsafe fn from_ptr(ptr: NonNull) -> Self { + Self { ptr } + } + + /// Get a reference to the native [`SurfaceTexture`] from the corresponding Java object. + /// + /// # Safety + /// + /// This function should be called with a healthy JVM pointer and with a non-null + /// [`android.graphics.SurfaceTexture`], which must be kept alive on the Java/Kotlin side. + /// + /// The caller must keep a reference to the Java [`android.graphics.SurfaceTexture`] during the + /// lifetime of the returned [`SurfaceTexture`]. Failing to do so could result in the + /// [`SurfaceTexture`] to stop functioning properly once the Java object gets finalized. + /// However, this will not result in program termination. + /// + /// [`android.graphics.SurfaceTexture`]: https://developer.android.com/reference/android/graphics/SurfaceTexture + pub unsafe fn from_surface_texture(env: *mut JNIEnv, surface_texture: jobject) -> Option { + let a_surface_texture_ptr = ffi::ASurfaceTexture_fromSurfaceTexture(env, surface_texture); + let s = NonNull::new(a_surface_texture_ptr)?; + Some(SurfaceTexture::from_ptr(s)) + } + + /// Returns a pointer to the native [`ffi::ASurfaceTexture`]. + pub fn ptr(&self) -> NonNull { + self.ptr + } + + /// Returns a reference to a [`NativeWindow`] (i.e. the Producer) for this [`SurfaceTexture`]. + /// + /// This is equivalent to Java's: + /// ```java + /// Surface sur = new Surface(surfaceTexture); + /// ``` + pub fn acquire_native_window(&self) -> Option { + let native_window = unsafe { ffi::ASurfaceTexture_acquireANativeWindow(self.ptr.as_ptr()) }; + let n = NonNull::new(native_window)?; + Some(unsafe { NativeWindow::from_ptr(n) }) + } + + /// Attach the [`SurfaceTexture`] to the OpenGL ES context that is current on the calling + /// thread. + /// + /// A new OpenGL ES texture object is created and populated with the [`SurfaceTexture`] image + /// frame that was current at the time of the last call to + /// [`detach_from_gl_context()`][Self::detach_from_gl_context()]. This new texture is bound to + /// the `GL_TEXTURE_EXTERNAL_OES` texture target. + /// + /// This can be used to access the [`SurfaceTexture`] image contents from multiple OpenGL ES + /// contexts. Note, however, that the image contents are only accessible from one OpenGL ES + /// context at a time. + pub fn attach_to_gl_context(&self, tex_name: u32) -> Result<(), PosixError> { + let r = unsafe { ffi::ASurfaceTexture_attachToGLContext(self.ptr.as_ptr(), tex_name) }; + if r == 0 { + Ok(()) + } else { + Err(PosixError(r)) + } + } + + /// Detach the [`SurfaceTexture`] from the OpenGL ES context that owns the OpenGL ES texture + /// object. + /// + /// This call must be made with the OpenGL ES context current on the calling thread. The OpenGL + /// ES texture object will be deleted as a result of this call. After calling this method all + /// calls to [`update_tex_image()`][Self::update_tex_image()] will fail until a successful call + /// to [`attach_to_gl_context()`][Self::attach_to_gl_context()] is made. + /// + /// This can be used to access the [`SurfaceTexture`] image contents from multiple OpenGL ES + /// contexts. Note, however, that the image contents are only accessible from one OpenGL ES + /// context at a time. + pub fn detach_from_gl_context(&self) -> Result<(), PosixError> { + let r = unsafe { ffi::ASurfaceTexture_detachFromGLContext(self.ptr.as_ptr()) }; + if r == 0 { + Ok(()) + } else { + Err(PosixError(r)) + } + } + + /// Retrieve the 4x4 texture coordinate transform matrix associated with the texture image set + /// by the most recent call to [`update_tex_image()`][Self::update_tex_image()]. + /// + /// This transform matrix maps 2D homogeneous texture coordinates of the form `(s, t, 0, 1)` + /// with `s` and `t` in the inclusive range `[0, 1]` to the texture coordinate that should be + /// used to sample that location from the texture. Sampling the texture outside of the range of + /// this transform is undefined. + /// + /// The matrix is stored in column-major order so that it may be passed directly to OpenGL ES + /// via the [`glLoadMatrixf()`] or [`glUniformMatrix4fv()`] functions. + /// + /// [`glLoadMatrixf()`]: https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glLoadMatrix.xml + /// [`gluniformmatrix4fv()`]: https://www.khronos.org/registry/OpenGL-Refpages/es3.1/html/glUniform.xhtml + pub fn transform_matrix(&self) -> [f32; 16] { + let mut r = [0f32; 16]; + unsafe { ffi::ASurfaceTexture_getTransformMatrix(self.ptr.as_ptr(), r.as_mut_ptr()) }; + r + } + + /// Retrieve the timestamp associated with the texture image set by the most recent call to + /// [`update_tex_image()`][Self::update_tex_image()]. + /// + /// This timestamp is in nanoseconds, and is normally monotonically increasing. The timestamp + /// should be unaffected by time-of-day adjustments, and for a camera should be strictly + /// monotonic but for a [`MediaPlayer`] may be reset when the position is set. The specific + /// meaning and zero point of the timestamp depends on the source providing images to the + /// [`SurfaceTexture`]. Unless otherwise specified by the image source, timestamps cannot + /// generally be compared across [`SurfaceTexture`] instances, or across multiple program + /// invocations. It is mostly useful for determining time offsets between subsequent frames. + /// + /// For EGL/Vulkan producers, this timestamp is the desired present time set with the + /// [`EGL_ANDROID_presentation_time`] or [`VK_GOOGLE_display_timing`] extensions. + /// + /// [`MediaPlayer`]: https://developer.android.com/reference/android/media/MediaPlayer + /// [`EGL_ANDROID_presentation_time`]: https://www.khronos.org/registry/EGL/extensions/ANDROID/EGL_ANDROID_presentation_time.txt + /// [`VK_GOOGLE_display_timing`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VK_GOOGLE_display_timing.html + pub fn timestamp(&self) -> Duration { + Duration::from_nanos( + unsafe { ffi::ASurfaceTexture_getTimestamp(self.ptr.as_ptr()) } + .try_into() + .unwrap(), + ) + } + + /// Update the texture image to the most recent frame from the image stream. + /// + /// This may only be called while the OpenGL ES context that owns the texture is current on the + /// calling thread. It will implicitly bind its texture to the `GL_TEXTURE_EXTERNAL_OES` + /// texture target. + pub fn update_tex_image(&self) -> Result<(), PosixError> { + let r = unsafe { ffi::ASurfaceTexture_updateTexImage(self.ptr.as_ptr()) }; + if r == 0 { + Ok(()) + } else { + Err(PosixError(r)) + } + } +}