use super::java::{raw_env, JavaObject, JavaRef};
use crate::log::LogPriority;
use std::ffi::{c_char, c_int, c_long, c_void, CString};
use std::ptr::{self, NonNull};
use std::sync::OnceLock;
#[repr(C)]
struct ANativeWindow {
_private: [u8; 0],
}
#[repr(C)]
struct AChoreographer {
_private: [u8; 0],
}
#[repr(C)]
struct ALooper {
_private: [u8; 0],
}
#[repr(C)]
struct ARect {
left: i32,
top: i32,
right: i32,
bottom: i32,
}
#[repr(C)]
struct ANativeWindowBuffer {
width: i32,
height: i32,
stride: i32,
format: i32,
bits: *mut c_void,
reserved: [u32; 6],
}
type FrameCallback = unsafe extern "C" fn(frame_time_nanos: c_long, data: *mut c_void);
type FrameCallback64 = unsafe extern "C" fn(frame_time_nanos: i64, data: *mut c_void);
type PostFrameCallback64 =
unsafe extern "C" fn(choreographer: *mut AChoreographer, callback: FrameCallback64, data: *mut c_void);
#[link(name = "android")]
extern "C" {
fn ANativeWindow_fromSurface(env: *mut c_void, surface: *mut c_void) -> *mut ANativeWindow;
fn ANativeWindow_acquire(window: *mut ANativeWindow);
fn ANativeWindow_release(window: *mut ANativeWindow);
fn ANativeWindow_getWidth(window: *mut ANativeWindow) -> i32;
fn ANativeWindow_getHeight(window: *mut ANativeWindow) -> i32;
fn ANativeWindow_lock(
window: *mut ANativeWindow,
out_buffer: *mut ANativeWindowBuffer,
in_out_dirty_bounds: *mut ARect,
) -> i32;
fn ANativeWindow_unlockAndPost(window: *mut ANativeWindow) -> i32;
fn AChoreographer_getInstance() -> *mut AChoreographer;
fn AChoreographer_postFrameCallback(choreographer: *mut AChoreographer, callback: FrameCallback, data: *mut c_void);
fn ALooper_prepare(opts: c_int) -> *mut ALooper;
fn ALooper_pollOnce(
timeout_millis: c_int,
out_fd: *mut c_int,
out_events: *mut c_int,
out_data: *mut *mut c_void,
) -> c_int;
}
#[link(name = "log")]
extern "C" {
fn __android_log_write(prio: c_int, tag: *const c_char, text: *const c_char) -> c_int;
}
pub(crate) struct NativeWindow {
window: NonNull<ANativeWindow>,
}
unsafe impl Send for NativeWindow {}
unsafe impl Sync for NativeWindow {}
impl NativeWindow {
pub fn from_surface(surface: &JavaObject) -> Option<NativeWindow> {
let window = unsafe { ANativeWindow_fromSurface(raw_env(), surface.raw().cast()) };
NonNull::new(window).map(|window| NativeWindow { window })
}
pub fn handle(&self) -> isize {
self.window.as_ptr() as isize
}
pub fn width(&self) -> i32 {
unsafe { ANativeWindow_getWidth(self.window.as_ptr()) }
}
pub fn height(&self) -> i32 {
unsafe { ANativeWindow_getHeight(self.window.as_ptr()) }
}
pub fn lock(&self) -> Option<LockedBuffer> {
let mut buffer =
ANativeWindowBuffer { width: 0, height: 0, stride: 0, format: 0, bits: ptr::null_mut(), reserved: [0; 6] };
let mut bounds = ARect { left: 0, top: 0, right: self.width(), bottom: self.height() };
let status = unsafe { ANativeWindow_lock(self.window.as_ptr(), &mut buffer, &mut bounds) };
if status != 0 || buffer.bits.is_null() {
return None;
}
Some(LockedBuffer {
window: self.clone(),
width: buffer.width,
height: buffer.height,
stride: buffer.stride,
format: buffer.format,
bits: buffer.bits.cast(),
})
}
}
impl Clone for NativeWindow {
fn clone(&self) -> Self {
unsafe { ANativeWindow_acquire(self.window.as_ptr()) };
NativeWindow { window: self.window }
}
}
impl Drop for NativeWindow {
fn drop(&mut self) {
unsafe { ANativeWindow_release(self.window.as_ptr()) };
}
}
pub(crate) struct LockedBuffer {
window: NativeWindow,
pub width: i32,
pub height: i32,
pub stride: i32,
pub format: i32,
bits: *mut u8,
}
impl LockedBuffer {
pub fn bits(&self) -> *mut u8 {
self.bits
}
}
impl Drop for LockedBuffer {
fn drop(&mut self) {
unsafe { ANativeWindow_unlockAndPost(self.window.window.as_ptr()) };
}
}
pub(crate) trait IFrameCallback: Sync + 'static {
fn do_frame(&'static self, frame_time_nanos: i64);
}
#[derive(Clone, Copy)]
pub(crate) struct Choreographer {
choreographer: NonNull<AChoreographer>,
}
unsafe impl Send for Choreographer {}
unsafe impl Sync for Choreographer {}
fn post_frame_callback64() -> Option<PostFrameCallback64> {
static ENTRY: OnceLock<Option<PostFrameCallback64>> = OnceLock::new();
*ENTRY.get_or_init(|| {
let address = unsafe {
let library = libc::dlopen(c"libandroid.so".as_ptr(), libc::RTLD_NOW);
if library.is_null() {
return None;
}
libc::dlsym(library, c"AChoreographer_postFrameCallback64".as_ptr())
};
(!address.is_null()).then(|| unsafe { std::mem::transmute::<*mut c_void, PostFrameCallback64>(address) })
})
}
impl Choreographer {
pub fn instance() -> Option<Choreographer> {
NonNull::new(unsafe { AChoreographer_getInstance() }).map(|choreographer| Choreographer { choreographer })
}
pub fn post_frame_callback<T: IFrameCallback>(&self, target: &'static T) {
unsafe extern "C" fn callback64<T: IFrameCallback>(frame_time_nanos: i64, data: *mut c_void) {
let target: &'static T = unsafe { &*data.cast::<T>() };
run_callback(|| target.do_frame(frame_time_nanos));
}
unsafe extern "C" fn callback<T: IFrameCallback>(frame_time_nanos: c_long, data: *mut c_void) {
let target: &'static T = unsafe { &*data.cast::<T>() };
run_callback(|| target.do_frame(frame_time_nanos as i64));
}
let data = ptr::from_ref(target).cast_mut().cast::<c_void>();
unsafe {
match post_frame_callback64() {
Some(post) => post(self.choreographer.as_ptr(), callback64::<T>, data),
None => AChoreographer_postFrameCallback(self.choreographer.as_ptr(), callback::<T>, data),
}
}
}
}
fn run_callback(body: impl FnOnce()) {
if let Err(panic) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(body)) {
let message = crate::interop::panic_message(&*panic);
log_write(LogPriority::Fatal, crate::log::TAG, &format!("panic in a frame callback: {message}"));
std::process::abort();
}
}
pub(crate) fn looper_prepare() {
unsafe { ALooper_prepare(0) };
}
pub(crate) fn looper_loop() -> ! {
loop {
unsafe { ALooper_pollOnce(-1, ptr::null_mut(), ptr::null_mut(), ptr::null_mut()) };
}
}
pub(crate) fn log_write(priority: LogPriority, tag: &str, message: &str) {
let tag = CString::new(tag.replace('\0', " ")).expect("the tag has no NUL");
let message = CString::new(message.replace('\0', " ")).expect("the message has no NUL");
unsafe { __android_log_write(priority as c_int, tag.as_ptr(), message.as_ptr()) };
}