Refactor "backend" concept
See plat/sdl2 Former-commit-id: 5f7c9625467003d5b73307959095eb5365db523e
This commit is contained in:
parent
326bb06e82
commit
04d2edfc01
|
@ -25,6 +25,10 @@ zip = "0.5.3"
|
||||||
ctrlc = "3.1.3"
|
ctrlc = "3.1.3"
|
||||||
cpal="0.10.0"
|
cpal="0.10.0"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "rba-sdl2"
|
||||||
|
path = "src/plat/sdl2/main.rs"
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
opt-level = 0
|
opt-level = 0
|
||||||
debug = true
|
debug = true
|
|
@ -1,79 +0,0 @@
|
||||||
use std::ffi::OsStr;
|
|
||||||
use std::time;
|
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate clap;
|
|
||||||
|
|
||||||
use clap::{App, ArgMatches};
|
|
||||||
|
|
||||||
extern crate rustboyadvance_ng;
|
|
||||||
|
|
||||||
use rustboyadvance_ng::backend::*;
|
|
||||||
use rustboyadvance_ng::core::arm7tdmi::Core;
|
|
||||||
use rustboyadvance_ng::core::cartridge::Cartridge;
|
|
||||||
use rustboyadvance_ng::core::{GBAError, GBAResult, GameBoyAdvance};
|
|
||||||
use rustboyadvance_ng::debugger::Debugger;
|
|
||||||
use rustboyadvance_ng::util::read_bin_file;
|
|
||||||
|
|
||||||
fn run_emulator(matches: &ArgMatches) -> GBAResult<()> {
|
|
||||||
let skip_bios = matches.occurrences_of("skip_bios") != 0;
|
|
||||||
let no_framerate_limit = matches.occurrences_of("no_framerate_limit") != 0;
|
|
||||||
let debug = matches.occurrences_of("debug") != 0;
|
|
||||||
|
|
||||||
let backend: Box<EmulatorBackend> = match matches.value_of("backend") {
|
|
||||||
Some("sdl2") => Box::new(Sdl2Backend::new()),
|
|
||||||
Some("minifb") => Box::new(MinifbBackend::new()),
|
|
||||||
// None => DummyBackend::new(),
|
|
||||||
None => Box::new(DummyBackend::new()),
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let bios_bin = read_bin_file(matches.value_of("bios").unwrap_or_default())?;
|
|
||||||
|
|
||||||
let cart = Cartridge::from_path(matches.value_of("game_rom").unwrap())?;
|
|
||||||
println!("loaded rom: {:#?}", cart.header);
|
|
||||||
|
|
||||||
let mut core = Core::new();
|
|
||||||
if skip_bios {
|
|
||||||
core.skip_bios();
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut gba = GameBoyAdvance::new(core, bios_bin, cart, backend);
|
|
||||||
|
|
||||||
if debug {
|
|
||||||
gba.cpu.set_verbose(true);
|
|
||||||
let mut debugger = Debugger::new(gba);
|
|
||||||
println!("starting debugger...");
|
|
||||||
debugger.repl(matches.value_of("script_file"))?;
|
|
||||||
println!("ending debugger...");
|
|
||||||
} else {
|
|
||||||
let frame_time = time::Duration::new(0, 1_000_000_000u32 / 60);
|
|
||||||
loop {
|
|
||||||
let start_time = time::Instant::now();
|
|
||||||
gba.frame();
|
|
||||||
if !no_framerate_limit {
|
|
||||||
let time_passed = start_time.elapsed();
|
|
||||||
let delay = frame_time.checked_sub(time_passed);
|
|
||||||
match delay {
|
|
||||||
None => {}
|
|
||||||
Some(delay) => {
|
|
||||||
::std::thread::sleep(delay);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let yaml = load_yaml!("cli.yml");
|
|
||||||
let matches = App::from_yaml(yaml).get_matches();
|
|
||||||
|
|
||||||
let result = run_emulator(&matches);
|
|
||||||
|
|
||||||
if let Err(err) = result {
|
|
||||||
println!("Got an error: {:?}", err);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use std::ffi::OsStr;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
@ -8,7 +9,7 @@ use zip::ZipArchive;
|
||||||
|
|
||||||
use super::super::util::read_bin_file;
|
use super::super::util::read_bin_file;
|
||||||
use super::arm7tdmi::{bus::Bus, Addr};
|
use super::arm7tdmi::{bus::Bus, Addr};
|
||||||
use super::GBAResult;
|
use super::{GBAError, GBAResult};
|
||||||
|
|
||||||
/// From GBATEK
|
/// From GBATEK
|
||||||
///
|
///
|
||||||
|
@ -75,30 +76,40 @@ pub struct Cartridge {
|
||||||
bytes: Box<[u8]>,
|
bytes: Box<[u8]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_rom(path: &str) -> GBAResult<Vec<u8>> {
|
fn load_rom(path: &Path) -> GBAResult<Vec<u8>> {
|
||||||
if path.ends_with(".zip") {
|
match path.extension() {
|
||||||
let zipfile = File::open(path)?;
|
Some(extension) => match extension.to_str() {
|
||||||
let mut archive = ZipArchive::new(zipfile)?;
|
Some("zip") => {
|
||||||
for i in 0..archive.len() {
|
let zipfile = File::open(path)?;
|
||||||
let mut file = archive.by_index(i)?;
|
let mut archive = ZipArchive::new(zipfile)?;
|
||||||
if file.name().ends_with(".gba") {
|
for i in 0..archive.len() {
|
||||||
let mut buf = Vec::new();
|
let mut file = archive.by_index(i)?;
|
||||||
file.read_to_end(&mut buf)?;
|
if file.name().ends_with(".gba") {
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
file.read_to_end(&mut buf)?;
|
||||||
|
return Ok(buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic!("no .gba file contained in the zip file");
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let buf = read_bin_file(path)?;
|
||||||
return Ok(buf);
|
return Ok(buf);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
let buf = read_bin_file(path)?;
|
||||||
|
return Ok(buf);
|
||||||
}
|
}
|
||||||
panic!("no .gba file contained in the zip file");
|
|
||||||
} else {
|
|
||||||
let buf = read_bin_file(path)?;
|
|
||||||
Ok(buf)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cartridge {
|
impl Cartridge {
|
||||||
const MIN_SIZE: usize = 4 * 1024 * 1024;
|
const MIN_SIZE: usize = 4 * 1024 * 1024;
|
||||||
|
|
||||||
pub fn from_path(rom_path: &str) -> GBAResult<Cartridge> {
|
pub fn from_path(rom_path: &Path) -> GBAResult<Cartridge> {
|
||||||
let mut rom_bin = load_rom(rom_path)?;
|
let mut rom_bin = load_rom(rom_path)?;
|
||||||
|
println!("loaded {} bytes", rom_bin.len());
|
||||||
|
|
||||||
if rom_bin.len() < Cartridge::MIN_SIZE {
|
if rom_bin.len() < Cartridge::MIN_SIZE {
|
||||||
rom_bin.resize_with(Cartridge::MIN_SIZE, Default::default);
|
rom_bin.resize_with(Cartridge::MIN_SIZE, Default::default);
|
||||||
|
|
|
@ -7,45 +7,34 @@ use super::iodev::*;
|
||||||
use super::sysbus::SysBus;
|
use super::sysbus::SysBus;
|
||||||
|
|
||||||
use super::SyncedIoDevice;
|
use super::SyncedIoDevice;
|
||||||
use crate::backend::*;
|
|
||||||
|
use super::super::{AudioInterface, InputInterface, VideoInterface};
|
||||||
|
|
||||||
pub struct GameBoyAdvance {
|
pub struct GameBoyAdvance {
|
||||||
backend: Box<dyn EmulatorBackend>,
|
|
||||||
pub cpu: Core,
|
pub cpu: Core,
|
||||||
pub sysbus: Box<SysBus>,
|
pub sysbus: Box<SysBus>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GameBoyAdvance {
|
impl GameBoyAdvance {
|
||||||
pub fn new(
|
pub fn new(cpu: Core, bios_rom: Vec<u8>, gamepak: Cartridge) -> GameBoyAdvance {
|
||||||
cpu: Core,
|
|
||||||
bios_rom: Vec<u8>,
|
|
||||||
gamepak: Cartridge,
|
|
||||||
// TODO rename this "graphics backend"
|
|
||||||
// TODO add sound backend
|
|
||||||
backend: Box<dyn EmulatorBackend>,
|
|
||||||
) -> GameBoyAdvance {
|
|
||||||
let io = IoDevices::new();
|
let io = IoDevices::new();
|
||||||
GameBoyAdvance {
|
GameBoyAdvance {
|
||||||
backend: backend,
|
|
||||||
cpu: cpu,
|
cpu: cpu,
|
||||||
sysbus: Box::new(SysBus::new(io, bios_rom, gamepak)),
|
sysbus: Box::new(SysBus::new(io, bios_rom, gamepak)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn frame(&mut self) {
|
pub fn frame(&mut self, video: &mut VideoInterface, input: &mut InputInterface) {
|
||||||
self.update_key_state();
|
self.sysbus.io.keyinput = input.poll();
|
||||||
while self.sysbus.io.gpu.state != GpuState::VBlank {
|
while self.sysbus.io.gpu.state != GpuState::VBlank {
|
||||||
self.step_new();
|
self.step_new();
|
||||||
}
|
}
|
||||||
|
video.render(self.sysbus.io.gpu.get_framebuffer());
|
||||||
while self.sysbus.io.gpu.state == GpuState::VBlank {
|
while self.sysbus.io.gpu.state == GpuState::VBlank {
|
||||||
self.step_new();
|
self.step_new();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_key_state(&mut self) {
|
|
||||||
self.sysbus.io.keyinput = self.backend.get_key_state();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_breakpoint(&mut self, addr: u32) -> Option<usize> {
|
pub fn add_breakpoint(&mut self, addr: u32) -> Option<usize> {
|
||||||
if !self.cpu.breakpoints.contains(&addr) {
|
if !self.cpu.breakpoints.contains(&addr) {
|
||||||
let new_index = self.cpu.breakpoints.len();
|
let new_index = self.cpu.breakpoints.len();
|
||||||
|
@ -100,7 +89,6 @@ impl GameBoyAdvance {
|
||||||
if let Some(new_gpu_state) = io.gpu.step(cycles, &mut self.sysbus, &mut irqs) {
|
if let Some(new_gpu_state) = io.gpu.step(cycles, &mut self.sysbus, &mut irqs) {
|
||||||
match new_gpu_state {
|
match new_gpu_state {
|
||||||
GpuState::VBlank => {
|
GpuState::VBlank => {
|
||||||
self.backend.render(io.gpu.get_framebuffer());
|
|
||||||
io.dmac.notify_vblank();
|
io.dmac.notify_vblank();
|
||||||
}
|
}
|
||||||
GpuState::HBlank => io.dmac.notify_hblank(),
|
GpuState::HBlank => io.dmac.notify_hblank(),
|
||||||
|
|
|
@ -102,31 +102,31 @@ impl Debugger {
|
||||||
}
|
}
|
||||||
println!("{}\n", self.gba.cpu);
|
println!("{}\n", self.gba.cpu);
|
||||||
}
|
}
|
||||||
Continue => {
|
// Continue => {
|
||||||
self.ctrlc_flag.store(true, Ordering::SeqCst);
|
// self.ctrlc_flag.store(true, Ordering::SeqCst);
|
||||||
while self.ctrlc_flag.load(Ordering::SeqCst) {
|
// while self.ctrlc_flag.load(Ordering::SeqCst) {
|
||||||
let start_time = time::Instant::now();
|
// let start_time = time::Instant::now();
|
||||||
self.gba.update_key_state();
|
// self.gba.update_key_state();
|
||||||
match self.gba.check_breakpoint() {
|
// match self.gba.check_breakpoint() {
|
||||||
Some(addr) => {
|
// Some(addr) => {
|
||||||
println!("Breakpoint reached! @{:x}", addr);
|
// println!("Breakpoint reached! @{:x}", addr);
|
||||||
break;
|
// break;
|
||||||
}
|
// }
|
||||||
_ => {
|
// _ => {
|
||||||
self.gba.step_new();
|
// self.gba.step_new();
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
Frame(count) => {
|
// Frame(count) => {
|
||||||
use super::time::PreciseTime;
|
// use super::time::PreciseTime;
|
||||||
let start = PreciseTime::now();
|
// let start = PreciseTime::now();
|
||||||
for _ in 0..count {
|
// for _ in 0..count {
|
||||||
self.gba.frame();
|
// self.gba.frame();
|
||||||
}
|
// }
|
||||||
let end = PreciseTime::now();
|
// let end = PreciseTime::now();
|
||||||
println!("that took {} seconds", start.to(end));
|
// println!("that took {} seconds", start.to(end));
|
||||||
}
|
// }
|
||||||
HexDump(addr, nbytes) => {
|
HexDump(addr, nbytes) => {
|
||||||
let bytes = self.gba.sysbus.get_bytes(addr..addr + nbytes);
|
let bytes = self.gba.sysbus.get_bytes(addr..addr + nbytes);
|
||||||
hexdump::hexdump(&bytes);
|
hexdump::hexdump(&bytes);
|
||||||
|
@ -206,6 +206,7 @@ impl Debugger {
|
||||||
self.gba.sysbus.io.timers.trace = !self.gba.sysbus.io.timers.trace;
|
self.gba.sysbus.io.timers.trace = !self.gba.sysbus.io.timers.trace;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_ => println!("Not Implemented",),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
22
src/lib.rs
22
src/lib.rs
|
@ -25,7 +25,27 @@ extern crate zip;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod util;
|
pub mod util;
|
||||||
pub mod backend;
|
|
||||||
pub mod core;
|
pub mod core;
|
||||||
pub mod debugger;
|
pub mod debugger;
|
||||||
pub mod disass;
|
pub mod disass;
|
||||||
|
|
||||||
|
pub trait VideoInterface {
|
||||||
|
fn render(&mut self, buffer: &[u32]);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait AudioInterface {
|
||||||
|
fn get_sample_rate(&self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait InputInterface {
|
||||||
|
fn poll(&mut self) -> u16;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod prelude {
|
||||||
|
pub use super::core::arm7tdmi;
|
||||||
|
pub use super::core::cartridge::Cartridge;
|
||||||
|
pub use super::core::{GBAError, GBAResult, GameBoyAdvance};
|
||||||
|
pub use super::debugger::Debugger;
|
||||||
|
pub use super::util::read_bin_file;
|
||||||
|
pub use super::{AudioInterface, InputInterface, VideoInterface};
|
||||||
|
}
|
||||||
|
|
1
src/plat/sdl2/audio.rs
Normal file
1
src/plat/sdl2/audio.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
const SAMPLE_RATE: u32 = 44100;
|
|
@ -1,6 +1,6 @@
|
||||||
name: rustboyadvance-ng
|
name: rba-sdl2
|
||||||
author: Michel Heily <michelheily@gmail.com>
|
author: Michel Heily <michelheily@gmail.com>
|
||||||
about: Game boy advance emulator and debugger
|
about: RustBoyAdvance SDL2 port
|
||||||
args:
|
args:
|
||||||
- bios:
|
- bios:
|
||||||
help: Sets the bios file to use
|
help: Sets the bios file to use
|
||||||
|
@ -19,12 +19,6 @@ args:
|
||||||
- no_framerate_limit:
|
- no_framerate_limit:
|
||||||
long: no-framerate-limit
|
long: no-framerate-limit
|
||||||
help: Run without frame limiter
|
help: Run without frame limiter
|
||||||
- backend:
|
|
||||||
long: backend
|
|
||||||
takes_value: true
|
|
||||||
possible_values: [ sdl2, minifb ]
|
|
||||||
default_value: minifb
|
|
||||||
required: false
|
|
||||||
- debug:
|
- debug:
|
||||||
long: debug
|
long: debug
|
||||||
help: Start with the debugger attached
|
help: Start with the debugger attached
|
64
src/plat/sdl2/keyboard.rs
Normal file
64
src/plat/sdl2/keyboard.rs
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
use sdl2::keyboard::Keycode;
|
||||||
|
use sdl2::{event::Event, EventPump};
|
||||||
|
|
||||||
|
use rustboyadvance_ng::core::keypad as gba_keypad;
|
||||||
|
use rustboyadvance_ng::InputInterface;
|
||||||
|
|
||||||
|
extern crate bit;
|
||||||
|
use bit::BitIndex;
|
||||||
|
|
||||||
|
pub struct PlatformSdl2_Keyboard {
|
||||||
|
event_pump: EventPump,
|
||||||
|
keyinput: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InputInterface for PlatformSdl2_Keyboard {
|
||||||
|
fn poll(&mut self) -> u16 {
|
||||||
|
for event in self.event_pump.poll_iter() {
|
||||||
|
match event {
|
||||||
|
Event::KeyDown {
|
||||||
|
keycode: Some(keycode),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
if let Some(key) = keycode_to_keypad(keycode) {
|
||||||
|
self.keyinput.set_bit(key as usize, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::KeyUp {
|
||||||
|
keycode: Some(keycode),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
if let Some(key) = keycode_to_keypad(keycode) {
|
||||||
|
self.keyinput.set_bit(key as usize, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::Quit { .. } => panic!("quit!"),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.keyinput
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn keycode_to_keypad(keycode: Keycode) -> Option<gba_keypad::Keys> {
|
||||||
|
match keycode {
|
||||||
|
Keycode::Up => Some(gba_keypad::Keys::Up),
|
||||||
|
Keycode::Down => Some(gba_keypad::Keys::Down),
|
||||||
|
Keycode::Left => Some(gba_keypad::Keys::Left),
|
||||||
|
Keycode::Right => Some(gba_keypad::Keys::Right),
|
||||||
|
Keycode::Z => Some(gba_keypad::Keys::ButtonB),
|
||||||
|
Keycode::X => Some(gba_keypad::Keys::ButtonA),
|
||||||
|
Keycode::Return => Some(gba_keypad::Keys::Start),
|
||||||
|
Keycode::Space => Some(gba_keypad::Keys::Select),
|
||||||
|
Keycode::A => Some(gba_keypad::Keys::ButtonL),
|
||||||
|
Keycode::S => Some(gba_keypad::Keys::ButtonR),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_keyboard(sdl: &sdl2::Sdl) -> PlatformSdl2_Keyboard {
|
||||||
|
PlatformSdl2_Keyboard {
|
||||||
|
event_pump: sdl.event_pump().unwrap(),
|
||||||
|
keyinput: gba_keypad::KEYINPUT_ALL_RELEASED,
|
||||||
|
}
|
||||||
|
}
|
78
src/plat/sdl2/main.rs
Normal file
78
src/plat/sdl2/main.rs
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
extern crate sdl2;
|
||||||
|
use sdl2::Sdl;
|
||||||
|
|
||||||
|
use std::path::Path;
|
||||||
|
use std::time;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate clap;
|
||||||
|
|
||||||
|
mod audio;
|
||||||
|
mod keyboard;
|
||||||
|
mod video;
|
||||||
|
|
||||||
|
use keyboard::{create_keyboard, PlatformSdl2_Keyboard};
|
||||||
|
use video::{create_video_interface, PlatformSdl2_VideoInterface};
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate rustboyadvance_ng;
|
||||||
|
use rustboyadvance_ng::prelude::*;
|
||||||
|
use rustboyadvance_ng::util::FpsCounter;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let yaml = load_yaml!("cli.yml");
|
||||||
|
let matches = clap::App::from_yaml(yaml).get_matches();
|
||||||
|
|
||||||
|
let skip_bios = matches.occurrences_of("skip_bios") != 0;
|
||||||
|
let no_framerate_limit = matches.occurrences_of("no_framerate_limit") != 0;
|
||||||
|
let debug = matches.occurrences_of("debug") != 0;
|
||||||
|
|
||||||
|
let bios_path = Path::new(matches.value_of("bios").unwrap_or_default());
|
||||||
|
let rom_path = Path::new(matches.value_of("game_rom").unwrap());
|
||||||
|
let rom_name = rom_path.file_name().unwrap().to_str().unwrap();
|
||||||
|
|
||||||
|
let bios_bin = read_bin_file(bios_path).unwrap();
|
||||||
|
let cart = Cartridge::from_path(rom_path).unwrap();
|
||||||
|
let mut cpu = arm7tdmi::Core::new();
|
||||||
|
if skip_bios {
|
||||||
|
cpu.skip_bios();
|
||||||
|
}
|
||||||
|
let cpu = cpu;
|
||||||
|
|
||||||
|
let sdl_context = sdl2::init().unwrap();
|
||||||
|
let mut video = create_video_interface(&sdl_context);
|
||||||
|
let mut keyboard = create_keyboard(&sdl_context);
|
||||||
|
|
||||||
|
let mut fps_counter = FpsCounter::default();
|
||||||
|
let mut gba = GameBoyAdvance::new(cpu, bios_bin, cart);
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
gba.cpu.set_verbose(true);
|
||||||
|
let mut debugger = Debugger::new(gba);
|
||||||
|
println!("starting debugger...");
|
||||||
|
debugger.repl(matches.value_of("script_file")).unwrap();
|
||||||
|
println!("ending debugger...");
|
||||||
|
} else {
|
||||||
|
let frame_time = time::Duration::new(0, 1_000_000_000u32 / 60);
|
||||||
|
loop {
|
||||||
|
let start_time = time::Instant::now();
|
||||||
|
|
||||||
|
gba.frame(&mut video, &mut keyboard);
|
||||||
|
if let Some(fps) = fps_counter.tick() {
|
||||||
|
let title = format!("{} ({} fps)", rom_name, fps);
|
||||||
|
video.set_window_title(&title);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !no_framerate_limit {
|
||||||
|
let time_passed = start_time.elapsed();
|
||||||
|
let delay = frame_time.checked_sub(time_passed);
|
||||||
|
match delay {
|
||||||
|
None => {}
|
||||||
|
Some(delay) => {
|
||||||
|
::std::thread::sleep(delay);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
77
src/plat/sdl2/video.rs
Normal file
77
src/plat/sdl2/video.rs
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
use sdl2::pixels::{Color, PixelFormatEnum};
|
||||||
|
use sdl2::rect::{Point, Rect};
|
||||||
|
use sdl2::render::{TextureCreator, WindowCanvas};
|
||||||
|
use sdl2::video::WindowContext;
|
||||||
|
use sdl2::Sdl;
|
||||||
|
|
||||||
|
use rustboyadvance_ng::core::gpu::{DISPLAY_HEIGHT, DISPLAY_WIDTH};
|
||||||
|
use rustboyadvance_ng::util::FpsCounter;
|
||||||
|
use rustboyadvance_ng::VideoInterface;
|
||||||
|
|
||||||
|
const SCREEN_WIDTH: u32 = DISPLAY_WIDTH as u32;
|
||||||
|
const SCREEN_HEIGHT: u32 = DISPLAY_HEIGHT as u32;
|
||||||
|
const SCALE: u32 = 3; // TODO control via CLI & support window resize
|
||||||
|
|
||||||
|
pub struct PlatformSdl2_VideoInterface {
|
||||||
|
tc: TextureCreator<WindowContext>,
|
||||||
|
canvas: WindowCanvas,
|
||||||
|
fps_counter: FpsCounter,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PlatformSdl2_VideoInterface {
|
||||||
|
pub fn set_window_title(&mut self, title: &str) {
|
||||||
|
self.canvas.window_mut().set_title(&title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VideoInterface for PlatformSdl2_VideoInterface {
|
||||||
|
fn render(&mut self, buffer: &[u32]) {
|
||||||
|
let mut texture = self
|
||||||
|
.tc
|
||||||
|
.create_texture_target(PixelFormatEnum::RGB24, SCREEN_WIDTH, SCREEN_HEIGHT)
|
||||||
|
.unwrap();
|
||||||
|
self.canvas
|
||||||
|
.with_texture_canvas(&mut texture, |texture_canvas| {
|
||||||
|
for y in 0i32..(SCREEN_HEIGHT as i32) {
|
||||||
|
for x in 0i32..(SCREEN_WIDTH as i32) {
|
||||||
|
let c = buffer[index2d!(x, y, SCREEN_WIDTH as i32) as usize];
|
||||||
|
let color = Color::RGB((c >> 16) as u8, (c >> 8) as u8, c as u8);
|
||||||
|
texture_canvas.set_draw_color(color);
|
||||||
|
let _ = texture_canvas.draw_point(Point::from((x, y)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
self.canvas
|
||||||
|
.copy(
|
||||||
|
&texture,
|
||||||
|
None,
|
||||||
|
Some(Rect::new(0, 0, SCREEN_WIDTH * SCALE, SCREEN_HEIGHT * SCALE)),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
self.canvas.present();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_video_interface(sdl: &Sdl) -> PlatformSdl2_VideoInterface {
|
||||||
|
let video_subsystem = sdl.video().unwrap();
|
||||||
|
let window = video_subsystem
|
||||||
|
.window(
|
||||||
|
"RustBoyAdvance",
|
||||||
|
SCREEN_WIDTH * SCALE,
|
||||||
|
SCREEN_HEIGHT * SCALE,
|
||||||
|
)
|
||||||
|
.opengl()
|
||||||
|
.position_centered()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
let mut canvas = window.into_canvas().accelerated().build().unwrap();
|
||||||
|
canvas.set_draw_color(Color::RGB(0, 0, 0));
|
||||||
|
canvas.clear();
|
||||||
|
let tc = canvas.texture_creator();
|
||||||
|
PlatformSdl2_VideoInterface {
|
||||||
|
tc: tc,
|
||||||
|
canvas: canvas,
|
||||||
|
fps_counter: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
32
src/util.rs
32
src/util.rs
|
@ -1,14 +1,44 @@
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::time;
|
||||||
|
|
||||||
pub fn read_bin_file(filename: &str) -> io::Result<Vec<u8>> {
|
pub fn read_bin_file(filename: &Path) -> io::Result<Vec<u8>> {
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
let mut file = File::open(filename)?;
|
let mut file = File::open(filename)?;
|
||||||
file.read_to_end(&mut buf)?;
|
file.read_to_end(&mut buf)?;
|
||||||
Ok(buf)
|
Ok(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct FpsCounter {
|
||||||
|
count: u32,
|
||||||
|
timer: time::Instant,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for FpsCounter {
|
||||||
|
fn default() -> FpsCounter {
|
||||||
|
FpsCounter {
|
||||||
|
count: 0,
|
||||||
|
timer: time::Instant::now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FpsCounter {
|
||||||
|
pub fn tick(&mut self) -> Option<u32> {
|
||||||
|
self.count += 1;
|
||||||
|
if self.timer.elapsed() >= time::Duration::from_secs(1) {
|
||||||
|
let fps = self.count;
|
||||||
|
self.timer = time::Instant::now();
|
||||||
|
self.count = 0;
|
||||||
|
Some(fps)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! index2d {
|
macro_rules! index2d {
|
||||||
($x:expr, $y:expr, $w:expr) => {
|
($x:expr, $y:expr, $w:expr) => {
|
||||||
|
|
Reference in a new issue