From c5b9c68d5ef2bc31f9a9d6c6aeeda4fe840ca16b Mon Sep 17 00:00:00 2001 From: Michel Heily Date: Sun, 23 Feb 2020 23:05:09 +0200 Subject: [PATCH] feat/jni: Cleanup & Improve jni bindings - Wrap static globals with a mutex. - Implement InputInterface Testing this on a standalone Java Console Application (without rendering) shows sufficiant performanse for now (AVG fps of 180 in the bios) Former-commit-id: cfbb9abb7e91b04258c41fc20e8a22c16d797386 --- rustboyadvance-jni/Cargo.toml | 7 +- rustboyadvance-jni/src/lib.rs | 219 +++++++++++++++++++++------------- src/core/gba.rs | 6 + src/core/gpu/mod.rs | 4 + src/util.rs | 2 +- 5 files changed, 155 insertions(+), 83 deletions(-) diff --git a/rustboyadvance-jni/Cargo.toml b/rustboyadvance-jni/Cargo.toml index 9671000..4b64410 100644 --- a/rustboyadvance-jni/Cargo.toml +++ b/rustboyadvance-jni/Cargo.toml @@ -12,5 +12,10 @@ crate-type = ["staticlib", "cdylib"] [dependencies] rustboyadvance-ng = {path = "../"} jni = { version = "0.14", default-features = false } -log = "0.4.8" +log = {version = "0.4.8", features = ["release_max_level_info", "max_level_debug"]} + +[target.'cfg(target_os="android")'.dependencies] +android_log = "0.1.3" + +[target.'cfg(not(target_os="android"))'.dependencies] env_logger = "0.7.1" diff --git a/rustboyadvance-jni/src/lib.rs b/rustboyadvance-jni/src/lib.rs index 7ee397d..b8d8e1b 100644 --- a/rustboyadvance-jni/src/lib.rs +++ b/rustboyadvance-jni/src/lib.rs @@ -1,159 +1,216 @@ /// JNI Bindings to rustboyadvance -/// For use with the following java class +/// For use with the following example java class /// /// package com.mrmichel.rustboyadvance; //// -/// public class EmulatorInterface { +/// public class EmulatorBindings { /// -/// public static native int loadRom(String romPath); -/// -/// public static native int openEmulator(String biosPath); +/// public static native int openEmulator(String biosPath, String romPath, boolean skipBiosAnimation); /// /// public static native void closeEmulator(); /// /// public static native int runFrame(int[] frame_buffer); /// +/// public static native int log(); +/// /// static { /// System.loadLibrary("rustboyadvance_jni"); /// } /// } +/// use std::cell::RefCell; use std::os::raw::c_void; use std::rc::Rc; +use std::sync::{Arc, Mutex}; + +use jni; + +use jni::objects::{JClass, JString}; +use jni::sys::{jboolean, jint, jintArray, JNI_VERSION_1_6}; +use jni::{JNIEnv, JavaVM}; #[macro_use] extern crate log; +#[cfg(target_os = "android")] +use android_log; +#[cfg(not(target_os = "android"))] use env_logger; use rustboyadvance_ng::prelude::*; struct Hardware { - frame_buffer: [u32; DISPLAY_WIDTH * DISPLAY_HEIGHT], -} - -impl Hardware { - fn new() -> Hardware { - Hardware { - frame_buffer: [0; DISPLAY_WIDTH * DISPLAY_HEIGHT], - } - } -} - -impl VideoInterface for Hardware { - fn render(&mut self, buffer: &[u32]) { - self.frame_buffer[..].clone_from_slice(buffer); - } + // frame_buffer: [u32; DISPLAY_WIDTH * DISPLAY_HEIGHT], + key_state: u16, } +impl VideoInterface for Hardware {} impl AudioInterface for Hardware {} -impl InputInterface for Hardware {} +impl InputInterface for Hardware { + fn poll(&mut self) -> u16 { + self.key_state + } +} struct Emulator { hwif: Rc>, gba: GameBoyAdvance, } +static mut JVM_PTR: Option>> = None; +static mut EMULATOR: Option>> = None; +static mut DID_LOAD: bool = false; + +macro_rules! get_static_global { + ($GLBL:ident: &mut $v:ident => $ok:block else $err:block) => { + if let Some(lock) = &mut $GLBL { + let mut $v = lock.lock().unwrap(); + + $ok + } else { + error!("{} not initialized", stringify!($GLBL)); + $err + } + }; + ($GLBL:ident: &$v:ident => $ok:block else $err:block) => { + if let Some(lock) = &mut $GLBL { + let $v = lock.lock().unwrap(); + + $ok + } else { + error!("{} not initialized", stringify!($GLBL)); + $err + } + }; +} + #[allow(non_snake_case)] -pub mod android { +pub mod bindings { use super::*; use std::path::Path; - use jni; - - use jni::objects::{JClass, JString}; - use jni::sys::{jint, jintArray, JNI_VERSION_1_6}; - use jni::{JNIEnv, JavaVM}; - - static mut EMULATOR: Option = None; - static mut ROM: Option = None; - #[no_mangle] - pub unsafe extern "C" fn JNI_OnLoad(_vm: *mut JavaVM, _reserved: *mut c_void) -> jint { + pub unsafe extern "C" fn JNI_OnLoad(vm: *mut JavaVM, _reserved: *mut c_void) -> jint { + if DID_LOAD { + return JNI_VERSION_1_6; + } + #[cfg(target_os = "android")] + android_log::init("EmulatorBindings").unwrap(); + #[cfg(not(target_os = "android"))] env_logger::init(); - debug!("library loaded!"); + + debug!("library loaded and logger initialized!"); + debug!("JVM: {:?}", vm); + + // save JVM_PTR + JVM_PTR = Some(Arc::new(Mutex::new(vm))); + + DID_LOAD = true; JNI_VERSION_1_6 } #[no_mangle] - pub unsafe extern "C" fn Java_com_mrmichel_rustboyadvance_EmulatorInterface_loadRom( + pub unsafe extern "C" fn Java_com_mrmichel_rustboyadvance_EmulatorBindings_openEmulator( env: JNIEnv, _: JClass, + bios_path: JString, rom_path: JString, + skip_bios: jboolean, ) -> jint { - if EMULATOR.is_some() { - error!("can't load rom while emulator is running"); - return -1; - } + let bios_path: String = env + .get_string(bios_path) + .expect("invalid bios path object") + .into(); + + let bios_rom = read_bin_file(&Path::new(&bios_path)).expect("failed to load bios file"); let rom_path: String = env .get_string(rom_path) .expect("invalid rom path object") .into(); - let gamepak = GamepakBuilder::new() - .file(&Path::new(&rom_path)) - .build() - .expect("failed to load rom"); + + debug!("trying to load {}", rom_path); + + let gamepak = match GamepakBuilder::new().file(&Path::new(&rom_path)).build() { + Ok(gamepak) => gamepak, + Err(err) => { + error!("failed to load rom, error: {:?}", err); + return -1; + } + }; info!("Loaded ROM file {:?}", gamepak.header); - ROM = Some(gamepak); - 0 - } + let hw = Hardware { key_state: 0xffff }; + let hw = Rc::new(RefCell::new(hw)); - #[no_mangle] - pub unsafe extern "C" fn Java_com_mrmichel_rustboyadvance_EmulatorInterface_openEmulator( - env: JNIEnv, - _: JClass, - bios_path: JString, - ) -> jint { - if let Some(cartridge) = ROM.clone() { - let bios_path: String = env - .get_string(bios_path) - .expect("invalid bios path object") - .into(); - let hw = Rc::new(RefCell::new(Hardware::new())); - - let bios_rom = read_bin_file(&Path::new(&bios_path)).expect("failed to load bios file"); - - EMULATOR = Some(Emulator { - hwif: hw.clone(), - gba: GameBoyAdvance::new(bios_rom, cartridge, hw.clone(), hw.clone(), hw.clone()), - }); - - return 0; - } else { - error!("please call loadRom first"); - return -1; + let mut gba = GameBoyAdvance::new(bios_rom, gamepak, hw.clone(), hw.clone(), hw.clone()); + if skip_bios != 0 { + gba.skip_bios(); } + + EMULATOR = Some(Arc::new(Mutex::new(Emulator { + hwif: hw.clone(), + gba, + }))); + + return 0; } #[no_mangle] - pub unsafe extern "C" fn Java_com_mrmichel_rustboyadvance_EmulatorInterface_closeEmulator( - env: JNIEnv, + pub unsafe extern "C" fn Java_com_mrmichel_rustboyadvance_EmulatorBindings_closeEmulator( + _env: JNIEnv, _: JClass, ) { EMULATOR = None; } #[no_mangle] - pub unsafe extern "C" fn Java_com_mrmichel_rustboyadvance_EmulatorInterface_runFrame( + pub unsafe extern "C" fn Java_com_mrmichel_rustboyadvance_EmulatorBindings_runFrame( env: JNIEnv, _: JClass, frame_buffer: jintArray, ) -> jint { - if let Some(emu) = &mut EMULATOR { - emu.gba.frame(); - let our_buffer = - std::mem::transmute::<&[u32], &[i32]>(&emu.hwif.borrow().frame_buffer as &[u32]); - env.set_int_array_region(frame_buffer, 0, our_buffer) - .expect("failed to copy frame buffer to java"); + get_static_global!(EMULATOR: &mut e => { + e.gba.frame(); + // let our_buffer = std::mem::transmute::<&[u32], &[i32]>(&e.hwif.borrow().frame_buffer as &[u32]); + env.set_int_array_region(frame_buffer, 0, std::mem::transmute::<&[u32], &[i32]>(&e.gba.get_frame_buffer() as &[u32])) + .expect("failed to copy frame buffer to java"); + return 0; + } else { + return -1; + } + ); + } + + #[no_mangle] + pub unsafe extern "C" fn Java_com_mrmichel_rustboyadvance_EmulatorBindings_setKeyState( + env: JNIEnv, + _: JClass, + key_state: jint, + ) -> jint { + get_static_global!(EMULATOR: &mut e => { + e.hwif.borrow_mut().key_state = key_state as u16; + return 0; + } else { + return -1; + } + ); + } + + #[no_mangle] + pub unsafe extern "C" fn Java_com_mrmichel_rustboyadvance_EmulatorBindings_log( + _env: JNIEnv, + _: JClass, + ) -> jint { + get_static_global!(EMULATOR: &e => { + info!("CPU LOG: {:#x?}", e.gba.cpu); return 0; - } - error!("emulator is not initalized"); - return -1; + } else { + return -1; + }); } } diff --git a/src/core/gba.rs b/src/core/gba.rs index 97cf2d7..fb4c91d 100644 --- a/src/core/gba.rs +++ b/src/core/gba.rs @@ -179,6 +179,12 @@ impl GameBoyAdvance { self.cycles_to_next_event = cycles_to_next_event; io.intc.request_irqs(irqs); } + + /// Query the emulator for the recently drawn framebuffer. + /// for use with implementations where the VideoInterface is not a viable option. + pub fn get_frame_buffer(&self) -> &[u32] { + self.sysbus.io.gpu.get_frame_buffer() + } } #[cfg(test)] diff --git a/src/core/gpu/mod.rs b/src/core/gpu/mod.rs index e391172..2d9c4bb 100644 --- a/src/core/gpu/mod.rs +++ b/src/core/gpu/mod.rs @@ -343,6 +343,10 @@ impl Gpu { } } + pub fn get_frame_buffer(&self) -> &[u32] { + &self.frame_buffer + } + pub fn on_state_completed( &mut self, completed: GpuState, diff --git a/src/util.rs b/src/util.rs index 8b0de94..aef7346 100644 --- a/src/util.rs +++ b/src/util.rs @@ -10,9 +10,9 @@ use gdbstub; #[cfg(feature = "gdb")] use gdbstub::GdbStub; use std::fmt; -use std::net::ToSocketAddrs; #[cfg(feature = "gdb")] use std::net::TcpListener; +use std::net::ToSocketAddrs; pub fn spawn_and_run_gdb_server( target: &mut GameBoyAdvance,