Get rid of the VideoInterface trait

Former-commit-id: 30a14ff0609b385b80b4c17a8f70e89fce816509
Former-commit-id: 519aeea19b3faa37b732463ecfdddb9730322021
This commit is contained in:
Michel Heily 2022-09-12 01:13:01 +03:00
parent 0f64f07133
commit 4db32b1af2
15 changed files with 98 additions and 209 deletions

View file

@ -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)
} }
} }

View file

@ -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

View file

@ -55,5 +55,3 @@ 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 = []

View file

@ -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 {

View file

@ -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

View file

@ -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);

View file

@ -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};

View 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();

View file

@ -10,7 +10,7 @@ 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"]}

View file

@ -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;

View file

@ -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"

View file

@ -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;
} }
} }

View file

@ -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 {

View file

@ -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 set_window_title(&mut self, title: &str) { pub fn from_canvas(canvas: WindowCanvas) -> Renderer<'a> {
self.canvas.window_mut().set_title(&title).unwrap(); 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,
} }
} }
impl<'a> VideoInterface for Sdl2Video<'a> { pub fn set_window_title(&mut self, title: &str) {
fn render(&mut self, buffer: &[u32]) { self.canvas.window_mut().set_title(&title).unwrap();
}
pub 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,
}
}

View file

@ -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);
} }
} }