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
This commit is contained in:
parent
c68b9502fe
commit
c5b9c68d5e
|
@ -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"
|
||||
|
|
|
@ -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<RefCell<Hardware>>,
|
||||
gba: GameBoyAdvance,
|
||||
}
|
||||
|
||||
static mut JVM_PTR: Option<Arc<Mutex<*mut JavaVM>>> = None;
|
||||
static mut EMULATOR: Option<Arc<Mutex<Emulator>>> = 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<Emulator> = None;
|
||||
static mut ROM: Option<Cartridge> = 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");
|
||||
|
||||
info!("Loaded ROM file {:?}", gamepak.header);
|
||||
ROM = Some(gamepak);
|
||||
debug!("trying to load {}", rom_path);
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
#[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");
|
||||
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);
|
||||
|
||||
let hw = Hardware { key_state: 0xffff };
|
||||
let hw = Rc::new(RefCell::new(hw));
|
||||
|
||||
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)
|
||||
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;
|
||||
}
|
||||
error!("emulator is not initalized");
|
||||
} 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;
|
||||
} else {
|
||||
return -1;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<A: ToSocketAddrs + fmt::Display>(
|
||||
target: &mut GameBoyAdvance,
|
||||
|
|
Reference in a new issue