use crate::{
event::{EventHandler, TouchPhase},
native::egl::{self, LibEgl},
native::NativeDisplay,
GraphicsContext,
};
use std::{cell::RefCell, sync::mpsc, thread};
pub use crate::gl::{self, *};
pub use ndk_sys;
pub mod ndk_utils;
#[no_mangle]
pub unsafe extern "C" fn JNI_OnLoad(
vm: *mut ndk_sys::JavaVM,
_: std::ffi::c_void,
) -> ndk_sys::jint {
VM = vm as *mut _ as _;
ndk_sys::JNI_VERSION_1_6 as _
}
extern "C" {
fn quad_main();
}
#[derive(Debug)]
enum Message {
SurfaceChanged {
window: *mut ndk_sys::ANativeWindow,
width: i32,
height: i32,
},
SurfaceCreated {
window: *mut ndk_sys::ANativeWindow,
},
SurfaceDestroyed,
Touch {
phase: TouchPhase,
touch_id: u64,
x: f32,
y: f32,
},
Character {
character: u32,
},
Pause,
Resume,
Destroy,
}
unsafe impl Send for Message {}
thread_local! {
static MESSAGES_TX: RefCell<Option<mpsc::Sender<Message>>> = RefCell::new(None);
}
fn send_message(message: Message) {
MESSAGES_TX.with(|tx| {
let mut tx = tx.borrow_mut();
tx.as_mut().unwrap().send(message).unwrap();
})
}
static mut ACTIVITY: ndk_sys::jobject = std::ptr::null_mut();
static mut VM: *mut ndk_sys::JavaVM = std::ptr::null_mut();
struct AndroidDisplay {
screen_width: f32,
screen_height: f32,
fullscreen: bool,
}
impl NativeDisplay for AndroidDisplay {
fn screen_size(&self) -> (f32, f32) {
(self.screen_width as _, self.screen_height as _)
}
fn dpi_scale(&self) -> f32 {
1.
}
fn high_dpi(&self) -> bool {
true
}
fn order_quit(&mut self) {}
fn request_quit(&mut self) {}
fn cancel_quit(&mut self) {}
fn set_cursor_grab(&mut self, _grab: bool) {}
fn show_mouse(&mut self, _shown: bool) {}
fn set_mouse_cursor(&mut self, _cursor: crate::CursorIcon) {}
fn set_window_size(&mut self, _new_width: u32, _new_height: u32) {}
fn set_fullscreen(&mut self, fullscreen: bool) {
unsafe {
let env = attach_jni_env();
set_full_screen(env, fullscreen);
}
self.fullscreen = fullscreen;
}
fn clipboard_get(&mut self) -> Option<String> {
None
}
fn clipboard_set(&mut self, _data: &str) {}
fn show_keyboard(&mut self, show: bool) {
unsafe {
let env = attach_jni_env();
ndk_utils::call_void_method!(env, ACTIVITY, "showKeyboard", "(Z)V", show as i32);
}
}
fn as_any(&mut self) -> &mut dyn std::any::Any {
self
}
}
pub unsafe fn console_debug(msg: *const ::std::os::raw::c_char) {
ndk_sys::__android_log_write(
ndk_sys::android_LogPriority_ANDROID_LOG_DEBUG as _,
b"SAPP\0".as_ptr() as _,
msg,
);
}
pub unsafe fn console_info(msg: *const ::std::os::raw::c_char) {
ndk_sys::__android_log_write(
ndk_sys::android_LogPriority_ANDROID_LOG_INFO as _,
b"SAPP\0".as_ptr() as _,
msg,
);
}
pub unsafe fn console_warn(msg: *const ::std::os::raw::c_char) {
ndk_sys::__android_log_write(
ndk_sys::android_LogPriority_ANDROID_LOG_WARN as _,
b"SAPP\0".as_ptr() as _,
msg,
);
}
pub unsafe fn console_error(msg: *const ::std::os::raw::c_char) {
ndk_sys::__android_log_write(
ndk_sys::android_LogPriority_ANDROID_LOG_ERROR as _,
b"SAPP\0".as_ptr() as _,
msg,
);
}
struct MainThreadState {
libegl: LibEgl,
context: GraphicsContext,
egl_display: egl::EGLDisplay,
egl_config: egl::EGLConfig,
egl_context: egl::EGLContext,
surface: egl::EGLSurface,
display: AndroidDisplay,
window: *mut ndk_sys::ANativeWindow,
event_handler: Box<dyn EventHandler>,
quit: bool,
}
impl MainThreadState {
unsafe fn destroy_surface(&mut self) {
(self.libegl.eglMakeCurrent.unwrap())(
self.egl_display,
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
);
(self.libegl.eglDestroySurface.unwrap())(self.egl_display, self.surface);
self.surface = std::ptr::null_mut();
}
unsafe fn update_surface(&mut self, window: *mut ndk_sys::ANativeWindow) {
if !self.window.is_null() {
ndk_sys::ANativeWindow_release(self.window);
}
self.window = window;
if self.surface.is_null() == false {
self.destroy_surface();
}
self.surface = (self.libegl.eglCreateWindowSurface.unwrap())(
self.egl_display,
self.egl_config,
window as _,
std::ptr::null_mut(),
);
assert!(!self.surface.is_null());
let res = (self.libegl.eglMakeCurrent.unwrap())(
self.egl_display,
self.surface,
self.surface,
self.egl_context,
);
assert!(res != 0);
}
fn process_message(&mut self, msg: Message) {
match msg {
Message::SurfaceCreated { window } => unsafe {
self.update_surface(window);
},
Message::SurfaceDestroyed => unsafe {
self.destroy_surface();
},
Message::SurfaceChanged {
window,
width,
height,
} => {
unsafe {
self.update_surface(window);
}
self.display.screen_width = width as _;
self.display.screen_height = height as _;
self.event_handler.resize_event(
self.context.with_display(&mut self.display),
width as _,
height as _,
);
}
Message::Touch {
phase,
touch_id,
x,
y,
} => {
self.event_handler.touch_event(
self.context.with_display(&mut self.display),
phase,
touch_id,
x,
y,
);
}
Message::Character { character } => {
if let Some(character) = char::from_u32(character) {
self.event_handler.char_event(
self.context.with_display(&mut self.display),
character,
Default::default(),
false,
);
}
}
Message::Pause => self
.event_handler
.window_minimized_event(self.context.with_display(&mut self.display)),
Message::Resume => {
if self.display.fullscreen {
unsafe {
let env = attach_jni_env();
set_full_screen(env, true);
}
}
self.event_handler
.window_restored_event(self.context.with_display(&mut self.display))
}
Message::Destroy => {
self.quit = true;
}
}
}
fn frame(&mut self) {
self.event_handler
.update(self.context.with_display(&mut self.display));
if self.surface.is_null() == false {
self.event_handler
.draw(self.context.with_display(&mut self.display));
unsafe {
(self.libegl.eglSwapBuffers.unwrap())(self.egl_display, self.surface);
}
}
}
}
pub unsafe fn attach_jni_env() -> *mut ndk_sys::JNIEnv {
let mut env: *mut ndk_sys::JNIEnv = std::ptr::null_mut();
let attach_current_thread = (**VM).AttachCurrentThread.unwrap();
let res = attach_current_thread(VM, &mut env, std::ptr::null_mut());
assert!(res == 0);
env
}
pub unsafe fn run<F>(conf: crate::conf::Conf, f: F)
where
F: 'static + FnOnce(&mut crate::Context) -> Box<dyn EventHandler>,
{
{
use std::ffi::CString;
use std::panic;
panic::set_hook(Box::new(|info| {
let msg = CString::new(format!("{:?}", info)).unwrap_or_else(|_| {
CString::new(format!("MALFORMED ERROR MESSAGE {:?}", info.location())).unwrap()
});
console_error(msg.as_ptr());
}));
}
if conf.fullscreen {
let env = attach_jni_env();
set_full_screen(env, true);
}
struct SendHack<F>(F);
unsafe impl<F> Send for SendHack<F> {}
let f = SendHack(f);
let (tx, rx) = mpsc::channel();
MESSAGES_TX.with(move |messages_tx| *messages_tx.borrow_mut() = Some(tx));
thread::spawn(move || {
let mut libegl = LibEgl::try_load().expect("Cant load LibEGL");
let (window, screen_width, screen_height) = 'a: loop {
match rx.try_recv() {
Ok(Message::SurfaceChanged {
window,
width,
height,
}) => {
break 'a (window, width as f32, height as f32);
}
_ => {}
}
};
let (egl_context, egl_config, egl_display) = crate::native::egl::create_egl_context(
&mut libegl,
std::ptr::null_mut(),
conf.platform.framebuffer_alpha,
)
.expect("Cant create EGL context");
assert!(!egl_display.is_null());
assert!(!egl_config.is_null());
crate::native::gl::load_gl_funcs(|proc| {
let name = std::ffi::CString::new(proc).unwrap();
libegl.eglGetProcAddress.expect("non-null function pointer")(name.as_ptr() as _)
});
let surface = (libegl.eglCreateWindowSurface.unwrap())(
egl_display,
egl_config,
window as _,
std::ptr::null_mut(),
);
if (libegl.eglMakeCurrent.unwrap())(egl_display, surface, surface, egl_context) == 0 {
panic!();
}
let mut context = GraphicsContext::new();
context.features.instancing = !gl::is_gl2();
let mut display = AndroidDisplay {
screen_width,
screen_height,
fullscreen: conf.fullscreen,
};
let event_handler = f.0(context.with_display(&mut display));
let mut s = MainThreadState {
libegl,
egl_display,
egl_config,
egl_context,
surface,
context,
display,
window,
event_handler,
quit: false,
};
while !s.quit {
while let Ok(msg) = rx.try_recv() {
s.process_message(msg);
}
s.frame();
thread::yield_now();
}
(s.libegl.eglMakeCurrent.unwrap())(
s.egl_display,
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
);
(s.libegl.eglDestroySurface.unwrap())(s.egl_display, s.surface);
(s.libegl.eglDestroyContext.unwrap())(s.egl_display, s.egl_context);
(s.libegl.eglTerminate.unwrap())(s.egl_display);
});
}
#[no_mangle]
extern "C" fn jni_on_load(vm: *mut std::ffi::c_void) {
unsafe {
VM = vm as _;
}
}
unsafe fn create_native_window(surface: ndk_sys::jobject) -> *mut ndk_sys::ANativeWindow {
let env = attach_jni_env();
ndk_sys::ANativeWindow_fromSurface(env, surface)
}
#[no_mangle]
pub unsafe extern "C" fn Java_quad_1native_QuadNative_activityOnCreate(
_: *mut ndk_sys::JNIEnv,
_: ndk_sys::jobject,
activity: ndk_sys::jobject,
) {
let env = attach_jni_env();
ACTIVITY = (**env).NewGlobalRef.unwrap()(env, activity);
quad_main();
}
#[no_mangle]
unsafe extern "C" fn Java_quad_1native_QuadNative_activityOnResume(
_: *mut ndk_sys::JNIEnv,
_: ndk_sys::jobject,
) {
send_message(Message::Resume);
}
#[no_mangle]
unsafe extern "C" fn Java_quad_1native_QuadNative_activityOnPause(
_: *mut ndk_sys::JNIEnv,
_: ndk_sys::jobject,
) {
send_message(Message::Pause);
}
#[no_mangle]
unsafe extern "C" fn Java_quad_1native_QuadNative_activityOnDestroy(
_: *mut ndk_sys::JNIEnv,
_: ndk_sys::jobject,
) {
send_message(Message::Destroy);
}
#[no_mangle]
extern "C" fn Java_quad_1native_QuadNative_surfaceOnSurfaceCreated(
_: *mut ndk_sys::JNIEnv,
_: ndk_sys::jobject,
surface: ndk_sys::jobject,
) {
let window = unsafe { create_native_window(surface) };
send_message(Message::SurfaceCreated { window });
}
#[no_mangle]
extern "C" fn Java_quad_1native_QuadNative_surfaceOnSurfaceDestroyed(
_: *mut ndk_sys::JNIEnv,
_: ndk_sys::jobject,
) {
send_message(Message::SurfaceDestroyed);
}
#[no_mangle]
extern "C" fn Java_quad_1native_QuadNative_surfaceOnSurfaceChanged(
_: *mut ndk_sys::JNIEnv,
_: ndk_sys::jobject,
surface: ndk_sys::jobject,
width: ndk_sys::jint,
height: ndk_sys::jint,
) {
let window = unsafe { create_native_window(surface) };
send_message(Message::SurfaceChanged {
window,
width: width as _,
height: height as _,
});
}
#[no_mangle]
extern "C" fn Java_quad_1native_QuadNative_surfaceOnTouch(
_: *mut ndk_sys::JNIEnv,
_: ndk_sys::jobject,
touch_id: ndk_sys::jint,
action: ndk_sys::jint,
x: ndk_sys::jfloat,
y: ndk_sys::jfloat,
) {
let phase = match action {
0 => TouchPhase::Moved,
1 => TouchPhase::Ended,
2 => TouchPhase::Started,
3 => TouchPhase::Cancelled,
x => panic!("Unsupported touch phase: {}", x),
};
send_message(Message::Touch {
phase,
touch_id: touch_id as _,
x: x as f32,
y: y as f32,
});
}
#[no_mangle]
extern "C" fn Java_quad_1native_QuadNative_surfaceOnKeyDown(
_: *mut ndk_sys::JNIEnv,
_: ndk_sys::jobject,
keycode: ndk_sys::jint,
) {
}
#[no_mangle]
extern "C" fn Java_quad_1native_QuadNative_surfaceOnKeyUp(
_: *mut ndk_sys::JNIEnv,
_: ndk_sys::jobject,
_keycode: ndk_sys::jint,
) {
}
#[no_mangle]
extern "C" fn Java_quad_1native_QuadNative_surfaceOnCharacter(
_: *mut ndk_sys::JNIEnv,
_: ndk_sys::jobject,
character: ndk_sys::jint,
) {
send_message(Message::Character {
character: character as u32,
});
}
unsafe fn set_full_screen(env: *mut ndk_sys::JNIEnv, fullscreen: bool) {
ndk_utils::call_void_method!(env, ACTIVITY, "setFullScreen", "(Z)V", fullscreen as i32);
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct android_asset {
pub content: *mut ::std::os::raw::c_char,
pub content_length: ::std::os::raw::c_int,
}
extern "C" {
pub fn AAssetManager_fromJava(
env: *mut ndk_sys::JNIEnv,
assetManager: ndk_sys::jobject,
) -> *mut ndk_sys::AAssetManager;
}
pub(crate) unsafe fn load_asset(filepath: *const ::std::os::raw::c_char, out: *mut android_asset) {
let env = attach_jni_env();
let get_method_id = (**env).GetMethodID.unwrap();
let get_object_class = (**env).GetObjectClass.unwrap();
let call_object_method = (**env).CallObjectMethod.unwrap();
let mid = (get_method_id)(
env,
get_object_class(env, ACTIVITY),
b"getAssets\0".as_ptr() as _,
b"()Landroid/content/res/AssetManager;\0".as_ptr() as _,
);
let asset_manager = (call_object_method)(env, ACTIVITY, mid);
let mgr = AAssetManager_fromJava(env, asset_manager);
let asset = ndk_sys::AAssetManager_open(mgr, filepath, ndk_sys::AASSET_MODE_BUFFER as _);
if asset.is_null() {
return;
}
let length = ndk_sys::AAsset_getLength64(asset);
let buffer = libc::malloc(length as _);
if ndk_sys::AAsset_read(asset, buffer, length as _) > 0 {
ndk_sys::AAsset_close(asset);
(*out).content_length = length as _;
(*out).content = buffer as _;
}
}