Get rid of the VideoInterface trait
Former-commit-id: 30a14ff0609b385b80b4c17a8f70e89fce816509 Former-commit-id: 519aeea19b3faa37b732463ecfdddb9730322021
This commit is contained in:
parent
0f64f07133
commit
4db32b1af2
|
@ -69,13 +69,16 @@ impl DebugRead for SimpleMemory {
|
||||||
|
|
||||||
impl MemoryGdbInterface for SimpleMemory {
|
impl MemoryGdbInterface for SimpleMemory {
|
||||||
fn memory_map_xml(&self, offset: u64, length: usize, buf: &mut [u8]) -> usize {
|
fn memory_map_xml(&self, offset: u64, length: usize, buf: &mut [u8]) -> usize {
|
||||||
let memory_map = format!(r#"<?xml version="1.0"?>
|
let memory_map = format!(
|
||||||
|
r#"<?xml version="1.0"?>
|
||||||
<!DOCTYPE memory-map
|
<!DOCTYPE memory-map
|
||||||
PUBLIC "+//IDN gnu.org//DTD GDB Memory Map V1.0//EN"
|
PUBLIC "+//IDN gnu.org//DTD GDB Memory Map V1.0//EN"
|
||||||
"http://sourceware.org/gdb/gdb-memory-map.dtd">
|
"http://sourceware.org/gdb/gdb-memory-map.dtd">
|
||||||
<memory-map>
|
<memory-map>
|
||||||
<memory type="ram" start="0x0" length="{}"/>
|
<memory type="ram" start="0x0" length="{}"/>
|
||||||
</memory-map>"#, self.data.len());
|
</memory-map>"#,
|
||||||
|
self.data.len()
|
||||||
|
);
|
||||||
copy_range_to_buf(memory_map.trim().as_bytes(), offset, length, buf)
|
copy_range_to_buf(memory_map.trim().as_bytes(), offset, length, buf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,5 +2,5 @@
|
||||||
|
|
||||||
rustboyadvance-core crate provides a simple yet effective performance benchmark.
|
rustboyadvance-core crate provides a simple yet effective performance benchmark.
|
||||||
|
|
||||||
to run it use `cargo bench --manifest-path ./core/Cargo.toml --features no_video_interface` from the repository root
|
to run it use `cargo bench --manifest-path ./core/Cargo.toml` from the repository root
|
||||||
|
|
||||||
|
|
|
@ -54,6 +54,4 @@ harness = false
|
||||||
default = []
|
default = []
|
||||||
elf_support = []
|
elf_support = []
|
||||||
debugger = ["nom", "rustyline", "fuzzy-matcher", "elf_support"]
|
debugger = ["nom", "rustyline", "fuzzy-matcher", "elf_support"]
|
||||||
gdb = ["gdbstub"]
|
gdb = ["gdbstub"]
|
||||||
# For use for ports where VideoInterface is not needed like wasm & jni
|
|
||||||
no_video_interface = []
|
|
|
@ -11,7 +11,6 @@ use rustboyadvance_utils::elf::{load_elf, GoblinError};
|
||||||
use rustboyadvance_utils::read_bin_file;
|
use rustboyadvance_utils::read_bin_file;
|
||||||
use zip::ZipArchive;
|
use zip::ZipArchive;
|
||||||
|
|
||||||
|
|
||||||
pub enum LoadRom {
|
pub enum LoadRom {
|
||||||
#[cfg(feature = "elf_support")]
|
#[cfg(feature = "elf_support")]
|
||||||
Elf {
|
Elf {
|
||||||
|
|
|
@ -16,8 +16,6 @@ use super::sysbus::SysBus;
|
||||||
use super::timer::Timers;
|
use super::timer::Timers;
|
||||||
|
|
||||||
use super::AudioInterface;
|
use super::AudioInterface;
|
||||||
#[cfg(not(feature = "no_video_interface"))]
|
|
||||||
use super::VideoInterface;
|
|
||||||
|
|
||||||
use arm7tdmi::{self, Arm7tdmiCore};
|
use arm7tdmi::{self, Arm7tdmiCore};
|
||||||
use rustboyadvance_utils::Shared;
|
use rustboyadvance_utils::Shared;
|
||||||
|
@ -28,8 +26,6 @@ pub struct GameBoyAdvance {
|
||||||
pub io_devs: Shared<IoDevices>,
|
pub io_devs: Shared<IoDevices>,
|
||||||
pub scheduler: SharedScheduler,
|
pub scheduler: SharedScheduler,
|
||||||
interrupt_flags: SharedInterruptFlags,
|
interrupt_flags: SharedInterruptFlags,
|
||||||
#[cfg(not(feature = "no_video_interface"))]
|
|
||||||
pub video_device: Rc<RefCell<dyn VideoInterface>>,
|
|
||||||
pub audio_device: Rc<RefCell<dyn AudioInterface>>,
|
pub audio_device: Rc<RefCell<dyn AudioInterface>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +63,6 @@ impl GameBoyAdvance {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
bios_rom: Box<[u8]>,
|
bios_rom: Box<[u8]>,
|
||||||
gamepak: Cartridge,
|
gamepak: Cartridge,
|
||||||
#[cfg(not(feature = "no_video_interface"))] video_device: Rc<RefCell<dyn VideoInterface>>,
|
|
||||||
audio_device: Rc<RefCell<dyn AudioInterface>>,
|
audio_device: Rc<RefCell<dyn AudioInterface>>,
|
||||||
) -> GameBoyAdvance {
|
) -> GameBoyAdvance {
|
||||||
// Warn the user if the bios is not the real one
|
// Warn the user if the bios is not the real one
|
||||||
|
@ -108,8 +103,6 @@ impl GameBoyAdvance {
|
||||||
cpu,
|
cpu,
|
||||||
sysbus,
|
sysbus,
|
||||||
io_devs,
|
io_devs,
|
||||||
#[cfg(not(feature = "no_video_interface"))]
|
|
||||||
video_device,
|
|
||||||
audio_device,
|
audio_device,
|
||||||
scheduler,
|
scheduler,
|
||||||
interrupt_flags,
|
interrupt_flags,
|
||||||
|
@ -124,7 +117,6 @@ impl GameBoyAdvance {
|
||||||
savestate: &[u8],
|
savestate: &[u8],
|
||||||
bios: Box<[u8]>,
|
bios: Box<[u8]>,
|
||||||
rom: Box<[u8]>,
|
rom: Box<[u8]>,
|
||||||
#[cfg(not(feature = "no_video_interface"))] video_device: Rc<RefCell<dyn VideoInterface>>,
|
|
||||||
audio_device: Rc<RefCell<dyn AudioInterface>>,
|
audio_device: Rc<RefCell<dyn AudioInterface>>,
|
||||||
) -> bincode::Result<GameBoyAdvance> {
|
) -> bincode::Result<GameBoyAdvance> {
|
||||||
let decoded: Box<SaveState> = bincode::deserialize_from(savestate)?;
|
let decoded: Box<SaveState> = bincode::deserialize_from(savestate)?;
|
||||||
|
@ -156,8 +148,6 @@ impl GameBoyAdvance {
|
||||||
sysbus,
|
sysbus,
|
||||||
io_devs,
|
io_devs,
|
||||||
interrupt_flags: interrupts,
|
interrupt_flags: interrupts,
|
||||||
#[cfg(not(feature = "no_video_interface"))]
|
|
||||||
video_device,
|
|
||||||
audio_device,
|
audio_device,
|
||||||
scheduler,
|
scheduler,
|
||||||
})
|
})
|
||||||
|
@ -308,12 +298,7 @@ impl GameBoyAdvance {
|
||||||
let apu = &mut io.sound;
|
let apu = &mut io.sound;
|
||||||
Some(timers.handle_overflow_event(channel_id, event_time, apu, dmac))
|
Some(timers.handle_overflow_event(channel_id, event_time, apu, dmac))
|
||||||
}
|
}
|
||||||
EventType::Gpu(gpu_event) => Some(io.gpu.on_event(
|
EventType::Gpu(gpu_event) => Some(io.gpu.on_event(gpu_event, &mut *self.sysbus)),
|
||||||
gpu_event,
|
|
||||||
&mut *self.sysbus,
|
|
||||||
#[cfg(not(feature = "no_video_interface"))]
|
|
||||||
&self.video_device,
|
|
||||||
)),
|
|
||||||
EventType::Apu(event) => Some(io.sound.on_event(event, &self.audio_device)),
|
EventType::Apu(event) => Some(io.sound.on_event(event, &self.audio_device)),
|
||||||
};
|
};
|
||||||
if let Some((new_event, when)) = new_event {
|
if let Some((new_event, when)) = new_event {
|
||||||
|
@ -380,8 +365,6 @@ impl GameBoyAdvance {
|
||||||
breakpoint
|
breakpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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] {
|
pub fn get_frame_buffer(&self) -> &[u32] {
|
||||||
self.sysbus.io.gpu.get_frame_buffer()
|
self.sysbus.io.gpu.get_frame_buffer()
|
||||||
}
|
}
|
||||||
|
@ -401,17 +384,15 @@ mod tests {
|
||||||
use crate::cartridge::GamepakBuilder;
|
use crate::cartridge::GamepakBuilder;
|
||||||
use arm7tdmi::memory::BusIO;
|
use arm7tdmi::memory::BusIO;
|
||||||
|
|
||||||
struct DummyInterface {}
|
struct DummyAudio {}
|
||||||
|
|
||||||
impl DummyInterface {
|
impl DummyAudio {
|
||||||
fn new() -> DummyInterface {
|
fn new() -> DummyAudio {
|
||||||
DummyInterface {}
|
DummyAudio {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_video_interface"))]
|
impl AudioInterface for DummyAudio {}
|
||||||
impl VideoInterface for DummyInterface {}
|
|
||||||
impl AudioInterface for DummyInterface {}
|
|
||||||
|
|
||||||
fn make_mock_gba(rom: &[u8]) -> GameBoyAdvance {
|
fn make_mock_gba(rom: &[u8]) -> GameBoyAdvance {
|
||||||
let bios = vec![0; 0x4000].into_boxed_slice();
|
let bios = vec![0; 0x4000].into_boxed_slice();
|
||||||
|
@ -421,14 +402,8 @@ mod tests {
|
||||||
.without_backup_to_file()
|
.without_backup_to_file()
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let dummy = Rc::new(RefCell::new(DummyInterface::new()));
|
let dummy = Rc::new(RefCell::new(DummyAudio::new()));
|
||||||
let mut gba = GameBoyAdvance::new(
|
let mut gba = GameBoyAdvance::new(bios, cartridge, dummy.clone());
|
||||||
bios,
|
|
||||||
cartridge,
|
|
||||||
#[cfg(not(feature = "no_video_interface"))]
|
|
||||||
dummy.clone(),
|
|
||||||
dummy.clone(),
|
|
||||||
);
|
|
||||||
gba.skip_bios();
|
gba.skip_bios();
|
||||||
|
|
||||||
gba
|
gba
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
#[cfg(not(feature = "no_video_interface"))]
|
|
||||||
use std::cell::RefCell;
|
|
||||||
#[cfg(not(feature = "no_video_interface"))]
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use num::FromPrimitive;
|
use num::FromPrimitive;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
@ -13,8 +8,6 @@ use super::dma::{DmaNotifer, TIMING_HBLANK, TIMING_VBLANK};
|
||||||
use super::interrupt::{self, Interrupt, InterruptConnect, SharedInterruptFlags};
|
use super::interrupt::{self, Interrupt, InterruptConnect, SharedInterruptFlags};
|
||||||
use super::sched::{EventType, FutureEvent, GpuEvent, Scheduler};
|
use super::sched::{EventType, FutureEvent, GpuEvent, Scheduler};
|
||||||
pub use super::sysbus::consts::*;
|
pub use super::sysbus::consts::*;
|
||||||
#[cfg(not(feature = "no_video_interface"))]
|
|
||||||
use super::VideoInterface;
|
|
||||||
|
|
||||||
mod render;
|
mod render;
|
||||||
|
|
||||||
|
@ -107,9 +100,6 @@ impl Default for ObjBufferEntry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_video_interface"))]
|
|
||||||
type VideoDeviceRcRefCell = Rc<RefCell<dyn VideoInterface>>;
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, DebugStub)]
|
#[derive(Serialize, Deserialize, Clone, DebugStub)]
|
||||||
pub struct Gpu {
|
pub struct Gpu {
|
||||||
interrupt_flags: SharedInterruptFlags,
|
interrupt_flags: SharedInterruptFlags,
|
||||||
|
@ -369,11 +359,7 @@ impl Gpu {
|
||||||
(GpuEvent::HBlank, CYCLES_HBLANK)
|
(GpuEvent::HBlank, CYCLES_HBLANK)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_hblank_end<D: DmaNotifer>(
|
fn handle_hblank_end<D: DmaNotifer>(&mut self, dma_notifier: &mut D) -> FutureGpuEvent {
|
||||||
&mut self,
|
|
||||||
dma_notifier: &mut D,
|
|
||||||
#[cfg(not(feature = "no_video_interface"))] video_device: &VideoDeviceRcRefCell,
|
|
||||||
) -> FutureGpuEvent {
|
|
||||||
self.update_vcount(self.vcount + 1);
|
self.update_vcount(self.vcount + 1);
|
||||||
|
|
||||||
if self.vcount < DISPLAY_HEIGHT {
|
if self.vcount < DISPLAY_HEIGHT {
|
||||||
|
@ -401,9 +387,6 @@ impl Gpu {
|
||||||
|
|
||||||
dma_notifier.notify(TIMING_VBLANK);
|
dma_notifier.notify(TIMING_VBLANK);
|
||||||
|
|
||||||
#[cfg(not(feature = "no_video_interface"))]
|
|
||||||
video_device.borrow_mut().render(&self.frame_buffer);
|
|
||||||
|
|
||||||
self.obj_buffer_reset();
|
self.obj_buffer_reset();
|
||||||
|
|
||||||
(GpuEvent::VBlankHDraw, CYCLES_HDRAW)
|
(GpuEvent::VBlankHDraw, CYCLES_HDRAW)
|
||||||
|
@ -432,22 +415,13 @@ impl Gpu {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_event<D>(
|
pub fn on_event<D>(&mut self, event: GpuEvent, dma_notifier: &mut D) -> FutureEvent
|
||||||
&mut self,
|
|
||||||
event: GpuEvent,
|
|
||||||
dma_notifier: &mut D,
|
|
||||||
#[cfg(not(feature = "no_video_interface"))] video_device: &VideoDeviceRcRefCell,
|
|
||||||
) -> FutureEvent
|
|
||||||
where
|
where
|
||||||
D: DmaNotifer,
|
D: DmaNotifer,
|
||||||
{
|
{
|
||||||
let (event, when) = match event {
|
let (event, when) = match event {
|
||||||
GpuEvent::HDraw => self.handle_hdraw_end(dma_notifier),
|
GpuEvent::HDraw => self.handle_hdraw_end(dma_notifier),
|
||||||
GpuEvent::HBlank => self.handle_hblank_end(
|
GpuEvent::HBlank => self.handle_hblank_end(dma_notifier),
|
||||||
dma_notifier,
|
|
||||||
#[cfg(not(feature = "no_video_interface"))]
|
|
||||||
video_device,
|
|
||||||
),
|
|
||||||
GpuEvent::VBlankHDraw => self.handle_vblank_hdraw_end(),
|
GpuEvent::VBlankHDraw => self.handle_vblank_hdraw_end(),
|
||||||
GpuEvent::VBlankHBlank => self.handle_vblank_hblank_end(),
|
GpuEvent::VBlankHBlank => self.handle_vblank_hblank_end(),
|
||||||
};
|
};
|
||||||
|
@ -549,26 +523,10 @@ mod tests {
|
||||||
fn notify(&mut self, _timing: u16) {}
|
fn notify(&mut self, _timing: u16) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct TestVideoInterface {
|
|
||||||
frame_counter: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_video_interface"))]
|
|
||||||
impl VideoInterface for TestVideoInterface {
|
|
||||||
fn render(&mut self, _buffer: &[u32]) {
|
|
||||||
self.frame_counter += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_gpu_state_machine() {
|
fn test_gpu_state_machine() {
|
||||||
let mut sched = Scheduler::new();
|
let mut sched = Scheduler::new();
|
||||||
let mut gpu = Gpu::new(&mut sched, Rc::new(Cell::new(Default::default())));
|
let mut gpu = Gpu::new(&mut sched, Rc::new(Cell::new(Default::default())));
|
||||||
#[cfg(not(feature = "no_video_interface"))]
|
|
||||||
let video = Rc::new(RefCell::new(TestVideoInterface::default()));
|
|
||||||
#[cfg(not(feature = "no_video_interface"))]
|
|
||||||
let video_clone: VideoDeviceRcRefCell = video.clone();
|
|
||||||
let mut dma_notifier = NopDmaNotifer;
|
let mut dma_notifier = NopDmaNotifer;
|
||||||
|
|
||||||
gpu.dispstat.vcount_setting = 0;
|
gpu.dispstat.vcount_setting = 0;
|
||||||
|
@ -580,12 +538,7 @@ mod tests {
|
||||||
let (event, event_time) = sched.pop_pending_event().unwrap();
|
let (event, event_time) = sched.pop_pending_event().unwrap();
|
||||||
assert_eq!(event_time, sched.timestamp());
|
assert_eq!(event_time, sched.timestamp());
|
||||||
let next_event = match event {
|
let next_event = match event {
|
||||||
EventType::Gpu(event) => gpu.on_event(
|
EventType::Gpu(event) => gpu.on_event(event, &mut dma_notifier),
|
||||||
event,
|
|
||||||
&mut dma_notifier,
|
|
||||||
#[cfg(not(feature = "no_video_interface"))]
|
|
||||||
&video_clone,
|
|
||||||
),
|
|
||||||
_ => panic!("Found unexpected event in queue!"),
|
_ => panic!("Found unexpected event in queue!"),
|
||||||
};
|
};
|
||||||
sched.schedule(next_event);
|
sched.schedule(next_event);
|
||||||
|
@ -594,8 +547,6 @@ mod tests {
|
||||||
|
|
||||||
for line in 0..160 {
|
for line in 0..160 {
|
||||||
println!("line = {}", line);
|
println!("line = {}", line);
|
||||||
#[cfg(not(feature = "no_video_interface"))]
|
|
||||||
assert_eq!(video.borrow().frame_counter, 0);
|
|
||||||
assert_eq!(gpu.vcount, line);
|
assert_eq!(gpu.vcount, line);
|
||||||
assert_eq!(sched.peek_next(), Some(EventType::Gpu(GpuEvent::HDraw)));
|
assert_eq!(sched.peek_next(), Some(EventType::Gpu(GpuEvent::HDraw)));
|
||||||
assert_eq!(gpu.dispstat.hblank_flag, false);
|
assert_eq!(gpu.dispstat.hblank_flag, false);
|
||||||
|
@ -613,9 +564,6 @@ mod tests {
|
||||||
assert_eq!(gpu.interrupt_flags.get().LCD_VCounterMatch(), false);
|
assert_eq!(gpu.interrupt_flags.get().LCD_VCounterMatch(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_video_interface"))]
|
|
||||||
assert_eq!(video.borrow().frame_counter, 1);
|
|
||||||
|
|
||||||
for line in 0..68 {
|
for line in 0..68 {
|
||||||
println!("line = {}", 160 + line);
|
println!("line = {}", 160 + line);
|
||||||
assert_eq!(gpu.dispstat.hblank_flag, false);
|
assert_eq!(gpu.dispstat.hblank_flag, false);
|
||||||
|
@ -638,8 +586,6 @@ mod tests {
|
||||||
update!(CYCLES_HBLANK);
|
update!(CYCLES_HBLANK);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_video_interface"))]
|
|
||||||
assert_eq!(video.borrow().frame_counter, 1);
|
|
||||||
assert_eq!(sched.timestamp(), CYCLES_FULL_REFRESH);
|
assert_eq!(sched.timestamp(), CYCLES_FULL_REFRESH);
|
||||||
|
|
||||||
assert_eq!(gpu.interrupt_flags.get().LCD_VCounterMatch(), true);
|
assert_eq!(gpu.interrupt_flags.get().LCD_VCounterMatch(), true);
|
||||||
|
|
|
@ -55,12 +55,6 @@ pub(crate) mod overrides;
|
||||||
#[cfg(feature = "debugger")]
|
#[cfg(feature = "debugger")]
|
||||||
pub mod debugger;
|
pub mod debugger;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_video_interface"))]
|
|
||||||
pub trait VideoInterface {
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
fn render(&mut self, buffer: &[u32]) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type StereoSample<T> = [T; 2];
|
pub type StereoSample<T> = [T; 2];
|
||||||
|
|
||||||
pub trait AudioInterface {
|
pub trait AudioInterface {
|
||||||
|
@ -129,8 +123,6 @@ pub mod prelude {
|
||||||
pub use super::debugger::Debugger;
|
pub use super::debugger::Debugger;
|
||||||
pub use super::gpu::{DISPLAY_HEIGHT, DISPLAY_WIDTH};
|
pub use super::gpu::{DISPLAY_HEIGHT, DISPLAY_WIDTH};
|
||||||
pub use super::Bus;
|
pub use super::Bus;
|
||||||
#[cfg(not(feature = "no_video_interface"))]
|
|
||||||
pub use super::VideoInterface;
|
|
||||||
pub use super::{AudioInterface, StereoSample};
|
pub use super::{AudioInterface, StereoSample};
|
||||||
pub use super::{GBAError, GBAResult, GameBoyAdvance};
|
pub use super::{GBAError, GBAResult, GameBoyAdvance};
|
||||||
pub use rustboyadvance_utils::{read_bin_file, write_bin_file};
|
pub use rustboyadvance_utils::{read_bin_file, write_bin_file};
|
||||||
|
|
|
@ -6,16 +6,15 @@ use std::rc::Rc;
|
||||||
use rustboyadvance_core::prelude::*;
|
use rustboyadvance_core::prelude::*;
|
||||||
use rustboyadvance_core::util::FpsCounter;
|
use rustboyadvance_core::util::FpsCounter;
|
||||||
|
|
||||||
struct BenchmarkHardware {}
|
struct DummyAudio {}
|
||||||
|
|
||||||
impl BenchmarkHardware {
|
impl DummyAudio {
|
||||||
fn new() -> BenchmarkHardware {
|
fn new() -> DummyAudio {
|
||||||
BenchmarkHardware {}
|
DummyAudio {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VideoInterface for BenchmarkHardware {}
|
impl AudioInterface for DummyAudio {}
|
||||||
impl AudioInterface for BenchmarkHardware {}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
if env::args().count() < 3 {
|
if env::args().count() < 3 {
|
||||||
|
@ -36,14 +35,9 @@ fn main() {
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let dummy = Rc::new(RefCell::new(BenchmarkHardware::new()));
|
let dummy = Rc::new(RefCell::new(DummyAudio::new()));
|
||||||
|
|
||||||
let mut gba = GameBoyAdvance::new(
|
let mut gba = GameBoyAdvance::new(bios.into_boxed_slice(), gamepak, dummy.clone());
|
||||||
bios.into_boxed_slice(),
|
|
||||||
gamepak,
|
|
||||||
dummy.clone(),
|
|
||||||
dummy.clone(),
|
|
||||||
);
|
|
||||||
gba.skip_bios();
|
gba.skip_bios();
|
||||||
|
|
||||||
let mut fps_counter = FpsCounter::default();
|
let mut fps_counter = FpsCounter::default();
|
||||||
|
|
|
@ -10,8 +10,8 @@ publish = false
|
||||||
crate-type = ["staticlib", "cdylib"]
|
crate-type = ["staticlib", "cdylib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rustboyadvance-core = {path = "../../core/", features = ["no_video_interface"]}
|
rustboyadvance-core = { path = "../../core/" }
|
||||||
rustboyadvance-utils = {path = "../../utils/" }
|
rustboyadvance-utils = { path = "../../utils/" }
|
||||||
jni = "0.17.0"
|
jni = "0.17.0"
|
||||||
log = {version = "0.4.8", features = ["release_max_level_info", "max_level_debug"]}
|
log = {version = "0.4.8", features = ["release_max_level_info", "max_level_debug"]}
|
||||||
|
|
||||||
|
|
|
@ -15,12 +15,12 @@ use jni::JNIEnv;
|
||||||
|
|
||||||
use crate::audio::{self, connector::AudioJNIConnector, thread::AudioThreadCommand};
|
use crate::audio::{self, connector::AudioJNIConnector, thread::AudioThreadCommand};
|
||||||
|
|
||||||
struct Hardware {
|
struct AudioDevice {
|
||||||
sample_rate: i32,
|
sample_rate: i32,
|
||||||
audio_producer: Option<Producer<i16>>,
|
audio_producer: Option<Producer<i16>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AudioInterface for Hardware {
|
impl AudioInterface for AudioDevice {
|
||||||
fn push_sample(&mut self, samples: &[i16]) {
|
fn push_sample(&mut self, samples: &[i16]) {
|
||||||
if let Some(prod) = &mut self.audio_producer {
|
if let Some(prod) = &mut self.audio_producer {
|
||||||
for s in samples.iter() {
|
for s in samples.iter() {
|
||||||
|
@ -146,7 +146,7 @@ impl Default for EmulationState {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct EmulatorContext {
|
pub struct EmulatorContext {
|
||||||
hwif: Rc<RefCell<Hardware>>,
|
audio_device: Rc<RefCell<AudioDevice>>,
|
||||||
renderer: Renderer,
|
renderer: Renderer,
|
||||||
audio_player_ref: GlobalRef,
|
audio_player_ref: GlobalRef,
|
||||||
keypad: Keypad,
|
keypad: Keypad,
|
||||||
|
@ -188,11 +188,11 @@ impl EmulatorContext {
|
||||||
let renderer = Renderer::new(env, renderer_obj)?;
|
let renderer = Renderer::new(env, renderer_obj)?;
|
||||||
|
|
||||||
info!("Creating GBA Instance");
|
info!("Creating GBA Instance");
|
||||||
let hw = Rc::new(RefCell::new(Hardware {
|
let audio = Rc::new(RefCell::new(AudioDevice {
|
||||||
sample_rate: audio::util::get_sample_rate(env, audio_player),
|
sample_rate: audio::util::get_sample_rate(env, audio_player),
|
||||||
audio_producer: None,
|
audio_producer: None,
|
||||||
}));
|
}));
|
||||||
let mut gba = GameBoyAdvance::new(bios, gamepak, hw.clone());
|
let mut gba = GameBoyAdvance::new(bios, gamepak, audio.clone());
|
||||||
if skip_bios != 0 {
|
if skip_bios != 0 {
|
||||||
info!("skipping bios");
|
info!("skipping bios");
|
||||||
gba.skip_bios();
|
gba.skip_bios();
|
||||||
|
@ -209,7 +209,7 @@ impl EmulatorContext {
|
||||||
renderer,
|
renderer,
|
||||||
audio_player_ref,
|
audio_player_ref,
|
||||||
emustate: Mutex::new(EmulationState::default()),
|
emustate: Mutex::new(EmulationState::default()),
|
||||||
hwif: hw.clone(),
|
audio_device: audio.clone(),
|
||||||
};
|
};
|
||||||
Ok(context)
|
Ok(context)
|
||||||
}
|
}
|
||||||
|
@ -237,17 +237,18 @@ impl EmulatorContext {
|
||||||
|
|
||||||
let renderer = Renderer::new(env, renderer_obj)?;
|
let renderer = Renderer::new(env, renderer_obj)?;
|
||||||
|
|
||||||
let hw = Rc::new(RefCell::new(Hardware {
|
let audio = Rc::new(RefCell::new(AudioDevice {
|
||||||
sample_rate: audio::util::get_sample_rate(env, audio_player),
|
sample_rate: audio::util::get_sample_rate(env, audio_player),
|
||||||
audio_producer: None,
|
audio_producer: None,
|
||||||
}));
|
}));
|
||||||
let gba = GameBoyAdvance::from_saved_state(&savestate, bios, rom, hw.clone())
|
let gba = GameBoyAdvance::from_saved_state(&savestate, bios, rom, audio.clone()).map_err(
|
||||||
.map_err(|e| {
|
|e| {
|
||||||
format!(
|
format!(
|
||||||
"failed to create GameBoyAdvance from saved savestate, error {:?}",
|
"failed to create GameBoyAdvance from saved savestate, error {:?}",
|
||||||
e
|
e
|
||||||
)
|
)
|
||||||
})?;
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
let keypad = Keypad::new(env, keypad_obj);
|
let keypad = Keypad::new(env, keypad_obj);
|
||||||
|
|
||||||
|
@ -258,7 +259,7 @@ impl EmulatorContext {
|
||||||
renderer,
|
renderer,
|
||||||
audio_player_ref,
|
audio_player_ref,
|
||||||
emustate: Mutex::new(EmulationState::default()),
|
emustate: Mutex::new(EmulationState::default()),
|
||||||
hwif: hw.clone(),
|
audio_device: audio.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -288,7 +289,7 @@ impl EmulatorContext {
|
||||||
let (prod, cons) = AudioRingBuffer::new_with_capacity(audio_connector.sample_count).split();
|
let (prod, cons) = AudioRingBuffer::new_with_capacity(audio_connector.sample_count).split();
|
||||||
|
|
||||||
// Store the ringbuffer producer in the emulator
|
// Store the ringbuffer producer in the emulator
|
||||||
self.hwif.borrow_mut().audio_producer = Some(prod);
|
self.audio_device.borrow_mut().audio_producer = Some(prod);
|
||||||
|
|
||||||
// Spawn the audio worker thread, give it the audio connector, jvm and ringbuffer consumer
|
// Spawn the audio worker thread, give it the audio connector, jvm and ringbuffer consumer
|
||||||
let (audio_thread_handle, audio_thread_tx) =
|
let (audio_thread_handle, audio_thread_tx) =
|
||||||
|
@ -354,7 +355,7 @@ impl EmulatorContext {
|
||||||
|
|
||||||
audio_connector.pause(env);
|
audio_connector.pause(env);
|
||||||
|
|
||||||
self.hwif.borrow_mut().audio_producer = None;
|
self.audio_device.borrow_mut().audio_producer = None;
|
||||||
|
|
||||||
*self.emustate.lock().unwrap() = EmulationState::Stopped;
|
*self.emustate.lock().unwrap() = EmulationState::Stopped;
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ crate-type = ["cdylib"]
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rustboyadvance-core = { path = "../../core/", features = ["no_video_interface"] }
|
rustboyadvance-core = { path = "../../core/" }
|
||||||
rustboyadvance-utils = { path = "../../utils" }
|
rustboyadvance-utils = { path = "../../utils" }
|
||||||
log = "0.4.8"
|
log = "0.4.8"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
|
|
|
@ -49,7 +49,7 @@ struct GbaButton(_GbaButton);
|
||||||
impl Deref for GbaButton {
|
impl Deref for GbaButton {
|
||||||
type Target = _GbaButton;
|
type Target = _GbaButton;
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
return &self.0
|
return &self.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
use sdl2;
|
|
||||||
use sdl2::controller::Button;
|
use sdl2::controller::Button;
|
||||||
use sdl2::event::{Event, WindowEvent};
|
use sdl2::event::{Event, WindowEvent};
|
||||||
use sdl2::image::{InitFlag, LoadSurface, LoadTexture};
|
use sdl2::image::{InitFlag, LoadSurface, LoadTexture};
|
||||||
|
@ -7,6 +6,7 @@ use sdl2::pixels::Color;
|
||||||
use sdl2::rect::Rect;
|
use sdl2::rect::Rect;
|
||||||
use sdl2::render::WindowCanvas;
|
use sdl2::render::WindowCanvas;
|
||||||
use sdl2::surface::Surface;
|
use sdl2::surface::Surface;
|
||||||
|
use sdl2::{self};
|
||||||
|
|
||||||
use sdl2::EventPump;
|
use sdl2::EventPump;
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ mod input;
|
||||||
mod video;
|
mod video;
|
||||||
|
|
||||||
use audio::{create_audio_player, create_dummy_player};
|
use audio::{create_audio_player, create_dummy_player};
|
||||||
use video::{create_video_interface, SCREEN_HEIGHT, SCREEN_WIDTH};
|
use video::{SCREEN_HEIGHT, SCREEN_WIDTH};
|
||||||
|
|
||||||
use rustboyadvance_core::cartridge::BackupType;
|
use rustboyadvance_core::cartridge::BackupType;
|
||||||
use rustboyadvance_core::prelude::*;
|
use rustboyadvance_core::prelude::*;
|
||||||
|
@ -182,7 +182,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let video = Rc::new(RefCell::new(create_video_interface(canvas)));
|
let mut renderer = video::Renderer::from_canvas(canvas);
|
||||||
let audio: Rc<RefCell<dyn AudioInterface>> = if silent {
|
let audio: Rc<RefCell<dyn AudioInterface>> = if silent {
|
||||||
Rc::new(RefCell::new(create_dummy_player()))
|
Rc::new(RefCell::new(create_dummy_player()))
|
||||||
} else {
|
} else {
|
||||||
|
@ -205,12 +205,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
|
||||||
let gamepak = builder.build()?;
|
let gamepak = builder.build()?;
|
||||||
|
|
||||||
let mut gba = GameBoyAdvance::new(
|
let mut gba = GameBoyAdvance::new(bios_bin.clone(), gamepak, audio.clone());
|
||||||
bios_bin.clone(),
|
|
||||||
gamepak,
|
|
||||||
video.clone(),
|
|
||||||
audio.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if skip_bios {
|
if skip_bios {
|
||||||
gba.skip_bios();
|
gba.skip_bios();
|
||||||
|
@ -331,12 +326,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let bios_bin = read_bin_file(bios_path).unwrap();
|
let bios_bin = read_bin_file(bios_path).unwrap();
|
||||||
|
|
||||||
// create a new emulator - TODO, export to a function
|
// create a new emulator - TODO, export to a function
|
||||||
gba = GameBoyAdvance::new(
|
gba = GameBoyAdvance::new(bios_bin.into_boxed_slice(), gamepak, audio.clone());
|
||||||
bios_bin.into_boxed_slice(),
|
|
||||||
gamepak,
|
|
||||||
video.clone(),
|
|
||||||
audio.clone(),
|
|
||||||
);
|
|
||||||
gba.skip_bios();
|
gba.skip_bios();
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
@ -344,10 +334,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
gba.frame();
|
gba.frame();
|
||||||
|
renderer.render(gba.get_frame_buffer());
|
||||||
|
|
||||||
if let Some(fps) = fps_counter.tick() {
|
if let Some(fps) = fps_counter.tick() {
|
||||||
let title = format!("{} ({} fps)", rom_name, fps);
|
let title = format!("{} ({} fps)", rom_name, fps);
|
||||||
video.borrow_mut().set_window_title(&title);
|
renderer.set_window_title(&title);
|
||||||
}
|
}
|
||||||
|
|
||||||
if frame_limiter {
|
if frame_limiter {
|
||||||
|
|
|
@ -4,25 +4,37 @@ use sdl2::render::{Texture, TextureCreator, WindowCanvas};
|
||||||
use sdl2::video::WindowContext;
|
use sdl2::video::WindowContext;
|
||||||
|
|
||||||
use rustboyadvance_core::gpu::{DISPLAY_HEIGHT, DISPLAY_WIDTH};
|
use rustboyadvance_core::gpu::{DISPLAY_HEIGHT, DISPLAY_WIDTH};
|
||||||
use rustboyadvance_core::VideoInterface;
|
|
||||||
|
|
||||||
pub const SCREEN_WIDTH: u32 = DISPLAY_WIDTH as u32;
|
pub const SCREEN_WIDTH: u32 = DISPLAY_WIDTH as u32;
|
||||||
pub const SCREEN_HEIGHT: u32 = DISPLAY_HEIGHT as u32;
|
pub const SCREEN_HEIGHT: u32 = DISPLAY_HEIGHT as u32;
|
||||||
|
|
||||||
pub struct Sdl2Video<'a> {
|
pub struct Renderer<'a> {
|
||||||
_tc: TextureCreator<WindowContext>, // only kept alive because of the texture
|
_tc: TextureCreator<WindowContext>, // only kept alive because of the texture
|
||||||
texture: Texture<'a>, // TODO - what happens if _tc is destroyed first ?
|
texture: Texture<'a>, // TODO - what happens if _tc is destroyed first ?
|
||||||
canvas: WindowCanvas,
|
canvas: WindowCanvas,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Sdl2Video<'a> {
|
impl<'a> Renderer<'a> {
|
||||||
|
pub fn from_canvas(canvas: WindowCanvas) -> Renderer<'a> {
|
||||||
|
let mut tc = canvas.texture_creator();
|
||||||
|
let texture = unsafe {
|
||||||
|
let tc_ptr = &mut tc as *mut TextureCreator<WindowContext>;
|
||||||
|
(*tc_ptr)
|
||||||
|
.create_texture_streaming(PixelFormatEnum::BGRA32, SCREEN_WIDTH, SCREEN_HEIGHT)
|
||||||
|
.unwrap()
|
||||||
|
};
|
||||||
|
Renderer {
|
||||||
|
_tc: tc,
|
||||||
|
texture,
|
||||||
|
canvas,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_window_title(&mut self, title: &str) {
|
pub fn set_window_title(&mut self, title: &str) {
|
||||||
self.canvas.window_mut().set_title(&title).unwrap();
|
self.canvas.window_mut().set_title(&title).unwrap();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> VideoInterface for Sdl2Video<'a> {
|
pub fn render(&mut self, buffer: &[u32]) {
|
||||||
fn render(&mut self, buffer: &[u32]) {
|
|
||||||
self.texture
|
self.texture
|
||||||
.update(
|
.update(
|
||||||
None,
|
None,
|
||||||
|
@ -42,18 +54,3 @@ impl<'a> VideoInterface for Sdl2Video<'a> {
|
||||||
self.canvas.present();
|
self.canvas.present();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_video_interface<'a>(canvas: WindowCanvas) -> Sdl2Video<'a> {
|
|
||||||
let mut tc = canvas.texture_creator();
|
|
||||||
let texture = unsafe {
|
|
||||||
let tc_ptr = &mut tc as *mut TextureCreator<WindowContext>;
|
|
||||||
(*tc_ptr)
|
|
||||||
.create_texture_streaming(PixelFormatEnum::BGRA32, SCREEN_WIDTH, SCREEN_HEIGHT)
|
|
||||||
.unwrap()
|
|
||||||
};
|
|
||||||
Sdl2Video {
|
|
||||||
_tc: tc,
|
|
||||||
texture: texture,
|
|
||||||
canvas: canvas,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -19,10 +19,21 @@ use bit::BitIndex;
|
||||||
pub struct Emulator {
|
pub struct Emulator {
|
||||||
gba: GameBoyAdvance,
|
gba: GameBoyAdvance,
|
||||||
interface: Rc<RefCell<Interface>>,
|
interface: Rc<RefCell<Interface>>,
|
||||||
|
frame: Option<Box<[u8]>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn translate_frame_to_u8(input_fb: &[u32], out_fb: &mut [u8]) {
|
||||||
|
// TODO optimize
|
||||||
|
for i in 0..input_fb.len() {
|
||||||
|
let color = input_fb[i];
|
||||||
|
out_fb[4 * i + 0] = ((color >> 16) & 0xff) as u8;
|
||||||
|
out_fb[4 * i + 1] = ((color >> 8) & 0xff) as u8;
|
||||||
|
out_fb[4 * i + 2] = (color & 0xff) as u8;
|
||||||
|
out_fb[4 * i + 3] = 255;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Interface {
|
struct Interface {
|
||||||
frame: Vec<u8>,
|
|
||||||
sample_rate: i32,
|
sample_rate: i32,
|
||||||
audio_ctx: AudioContext,
|
audio_ctx: AudioContext,
|
||||||
audio_ring_buffer: AudioRingBuffer,
|
audio_ring_buffer: AudioRingBuffer,
|
||||||
|
@ -37,7 +48,6 @@ impl Drop for Interface {
|
||||||
impl Interface {
|
impl Interface {
|
||||||
fn new(audio_ctx: AudioContext) -> Result<Interface, JsValue> {
|
fn new(audio_ctx: AudioContext) -> Result<Interface, JsValue> {
|
||||||
Ok(Interface {
|
Ok(Interface {
|
||||||
frame: vec![0; 240 * 160 * 4],
|
|
||||||
sample_rate: audio_ctx.sample_rate() as i32,
|
sample_rate: audio_ctx.sample_rate() as i32,
|
||||||
audio_ctx: audio_ctx,
|
audio_ctx: audio_ctx,
|
||||||
audio_ring_buffer: Default::default(),
|
audio_ring_buffer: Default::default(),
|
||||||
|
@ -45,19 +55,6 @@ impl Interface {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VideoInterface for Interface {
|
|
||||||
fn render(&mut self, buffer: &[u32]) {
|
|
||||||
// TODO optimize
|
|
||||||
for i in 0..buffer.len() {
|
|
||||||
let color = buffer[i];
|
|
||||||
self.frame[4 * i + 0] = ((color >> 16) & 0xff) as u8;
|
|
||||||
self.frame[4 * i + 1] = ((color >> 8) & 0xff) as u8;
|
|
||||||
self.frame[4 * i + 2] = (color & 0xff) as u8;
|
|
||||||
self.frame[4 * i + 3] = 255;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn convert_sample(s: i16) -> f32 {
|
fn convert_sample(s: i16) -> f32 {
|
||||||
(s as f32) / 32767_f32
|
(s as f32) / 32767_f32
|
||||||
}
|
}
|
||||||
|
@ -88,15 +85,13 @@ impl Emulator {
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let gba = GameBoyAdvance::new(
|
let gba = GameBoyAdvance::new(bios.to_vec().into_boxed_slice(), gamepak, interface.clone());
|
||||||
bios.to_vec().into_boxed_slice(),
|
|
||||||
gamepak,
|
|
||||||
interface.clone(),
|
|
||||||
interface.clone(),
|
|
||||||
interface.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(Emulator { gba, interface })
|
Ok(Emulator {
|
||||||
|
gba,
|
||||||
|
interface,
|
||||||
|
frame: Some(vec![0; 240 * 160 * 4].into_boxed_slice()),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn skip_bios(&mut self) {
|
pub fn skip_bios(&mut self) {
|
||||||
|
@ -105,13 +100,11 @@ impl Emulator {
|
||||||
|
|
||||||
pub fn run_frame(&mut self, ctx: &CanvasRenderingContext2d) -> Result<(), JsValue> {
|
pub fn run_frame(&mut self, ctx: &CanvasRenderingContext2d) -> Result<(), JsValue> {
|
||||||
self.gba.frame();
|
self.gba.frame();
|
||||||
let mut frame_buffer = &mut self.interface.borrow_mut().frame;
|
let mut frame = self.frame.take().unwrap();
|
||||||
let data = web_sys::ImageData::new_with_u8_clamped_array_and_sh(
|
translate_frame_to_u8(self.gba.get_frame_buffer(), &mut frame);
|
||||||
Clamped(&mut frame_buffer),
|
let data =
|
||||||
240,
|
web_sys::ImageData::new_with_u8_clamped_array_and_sh(Clamped(&mut frame), 240, 160)?;
|
||||||
160,
|
self.frame.replace(frame);
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
ctx.put_image_data(&data, 0.0, 0.0)
|
ctx.put_image_data(&data, 0.0, 0.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,14 +127,14 @@ impl Emulator {
|
||||||
pub fn key_down(&mut self, event_key: &str) {
|
pub fn key_down(&mut self, event_key: &str) {
|
||||||
debug!("Key down: {}", event_key);
|
debug!("Key down: {}", event_key);
|
||||||
if let Some(key) = Emulator::map_key(event_key) {
|
if let Some(key) = Emulator::map_key(event_key) {
|
||||||
self.gba.get_key_state().set_bit(key as usize, false);
|
self.gba.get_key_state_mut().set_bit(key as usize, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn key_up(&mut self, event_key: &str) {
|
pub fn key_up(&mut self, event_key: &str) {
|
||||||
debug!("Key up: {}", event_key);
|
debug!("Key up: {}", event_key);
|
||||||
if let Some(key) = Emulator::map_key(event_key) {
|
if let Some(key) = Emulator::map_key(event_key) {
|
||||||
self.gba.get_key_state().set_bit(key as usize, true);
|
self.gba.get_key_state_mut().set_bit(key as usize, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Reference in a new issue