diff --git a/examples/sdl/Cargo.toml b/examples/sdl/Cargo.toml index 6a029adafa561be3e8c6030827a2f0e05e01886f..203f0936cfaf7a2d99af449a7926c8b7a245e240 100644 --- a/examples/sdl/Cargo.toml +++ b/examples/sdl/Cargo.toml @@ -9,7 +9,6 @@ edition = "2018" [dependencies.boytacean] path = "../.." -features = ["debug"] [dependencies.sdl2] version = "0.35" diff --git a/examples/sdl/src/main.rs b/examples/sdl/src/main.rs index 2fbc7a1cc0c6e9174d6b7e562b2de88ff4bd891c..f7a1ec4043fab4d37071f131b5581940c16c07e4 100644 --- a/examples/sdl/src/main.rs +++ b/examples/sdl/src/main.rs @@ -1,111 +1,167 @@ pub mod data; +pub mod util; use boytacean::{ gb::GameBoy, pad::PadKey, ppu::{DISPLAY_HEIGHT, DISPLAY_WIDTH}, }; -use sdl2::{ - event::Event, keyboard::Keycode, pixels::PixelFormatEnum, rwops::RWops, surface::Surface, - sys::image, video::Window, AudioSubsystem, EventPump, TimerSubsystem, VideoSubsystem, -}; +use sdl2::{event::Event, keyboard::Keycode, pixels::PixelFormatEnum}; +use util::Graphics; + +use crate::util::surface_from_bytes; + +const VISUAL_HZ: u32 = 60; +const SCREEN_SCALE: f32 = 2.0; /// The base title to be used in the window. static TITLE: &'static str = "Boytacean"; -pub struct Graphics { - window: Window, - video_subsystem: VideoSubsystem, - timer_subsystem: TimerSubsystem, - audio_subsystem: AudioSubsystem, - event_pump: EventPump, -} - -fn start_sdl() -> Graphics { - // initializes the SDL sub-system, making it ready to be - // used for display of graphics and audio - let sdl = sdl2::init().unwrap(); - let video_subsystem = sdl.video().unwrap(); - let timer_subsystem = sdl.timer().unwrap(); - let audio_subsystem = sdl.audio().unwrap(); - let event_pump = sdl.event_pump().unwrap(); - - // initialized the fonts context to be used - // in the loading of fonts - let ttf_context = sdl2::ttf::init().unwrap(); - - // creates the system window that is going to be used to - // show the emulator and sets it to the central are o screen - let window = video_subsystem - .window( - TITLE, - 2 as u32 * DISPLAY_WIDTH as u32, //@todo check screen scale - 2 as u32 * DISPLAY_HEIGHT as u32, //@todo check screen scale - ) - .resizable() - .position_centered() - .opengl() - .build() - .unwrap(); - - Graphics { - window: window, - video_subsystem: video_subsystem, - timer_subsystem: timer_subsystem, - audio_subsystem: audio_subsystem, - event_pump: event_pump, - } +pub struct Emulator { + system: GameBoy, + graphics: Graphics, + visual_frequency: u32, + next_tick_time: f32, + next_tick_time_i: u32, } -pub fn surface_from_bytes(bytes: &[u8]) -> Surface { - unsafe { - let rw_ops = RWops::from_bytes(bytes).unwrap(); - let raw_surface = image::IMG_Load_RW(rw_ops.raw(), 0); - Surface::from_ll(raw_surface) +impl Emulator { + pub fn new(system: GameBoy, screen_scale: f32) -> Self { + Self { + system: system, + graphics: Graphics::new( + TITLE, + DISPLAY_WIDTH as u32, + DISPLAY_HEIGHT as u32, + screen_scale, + ), + visual_frequency: VISUAL_HZ, + next_tick_time: 0.0, + next_tick_time_i: 0, + } } -} -fn main() { - let mut graphics = start_sdl(); - - // updates the icon of the window to reflect the image - // and style of the emulator - let surface = surface_from_bytes(&data::ICON); - graphics.window.set_icon(&surface); + pub fn run(&mut self) { + // updates the icon of the window to reflect the image + // and style of the emulator + let surface = surface_from_bytes(&data::ICON); + self.graphics.window_mut().set_icon(&surface); + + // creates an accelerated canvas to be used in the drawing + // then clears it and presents it + self.graphics.canvas.present(); + + // creates a texture creator for the current canvas, required + // for the creation of dynamic and static textures + let texture_creator = self.graphics.canvas.texture_creator(); + + // creates the texture streaming that is going to be used + // as the target for the pixel buffer + let mut texture = texture_creator + .create_texture_streaming( + PixelFormatEnum::RGB24, + DISPLAY_WIDTH as u32, + DISPLAY_HEIGHT as u32, + ) + .unwrap(); - let mut canvas = graphics.window.into_canvas().accelerated().build().unwrap(); - canvas.clear(); - canvas.present(); + // allocates space for the loop ticks counter to be used in each + // iteration cycle + let mut counter = 0u32; + + // the main loop to execute the multiple machine clocks + 'main: loop { + // increments the counter that will keep track + // on the number of visual ticks since beginning + counter = counter.wrapping_add(1); + + // obtains an event from the SDL sub-system to be + // processed under the current emulation context + while let Some(event) = self.graphics.event_pump.poll_event() { + match event { + Event::Quit { .. } => break 'main, + + Event::KeyDown { + keycode: Some(keycode), + .. + } => match key_to_pad(keycode) { + Some(key) => self.system.key_press(key), + None => (), + }, + + Event::KeyUp { + keycode: Some(keycode), + .. + } => match key_to_pad(keycode) { + Some(key) => self.system.key_lift(key), + None => (), + }, + + _ => (), + } + } - let texture_creator = canvas.texture_creator(); + let current_time = self.graphics.timer_subsystem.ticks(); + + let mut counter_ticks = 0u32; + + if current_time >= self.next_tick_time_i { + loop { + // limits the number of ticks to the typical number + // of ticks required to do a complete PPU draw + if counter_ticks >= 70224 { + break; + } + + // runs the Game Boy clock, this operations should + // include the advance of both the CPU and the PPU + counter_ticks += self.system.clock() as u32; + } + + // obtains the frame buffer of the Game Boy PPU and uses it + // to update the stream texture, copying it then to the canvas + let frame_buffer = self.system.frame_buffer().as_ref(); + texture + .update(None, frame_buffer, DISPLAY_WIDTH as usize * 3) + .unwrap(); + self.graphics.canvas.copy(&texture, None, None).unwrap(); + + // presents the canvas effectively updating the screen + // information presented to the user + self.graphics.canvas.present(); + + // updates the next update time reference to the current + // time so that it can be used from game loop control + self.next_tick_time += 1000.0 / self.visual_frequency as f32; + self.next_tick_time_i = self.next_tick_time.ceil() as u32; + } - // creates the texture streaming that is going to be used - // as the target for the pixel buffer - let mut texture = texture_creator - .create_texture_streaming( - PixelFormatEnum::RGB24, - DISPLAY_WIDTH as u32, - DISPLAY_HEIGHT as u32, - ) - .unwrap(); + let current_time = self.graphics.timer_subsystem.ticks(); + let pending_time = self.next_tick_time_i.saturating_sub(current_time); + self.graphics.timer_subsystem.delay(pending_time); + } + } +} +fn main() { // creates a new Game Boy instance and loads both the boot ROM // and the initial game ROM to "start the engine" let mut game_boy = GameBoy::new(); game_boy.load_boot_default(); - //game_boy.load_rom_file("../../res/roms.prop/tetris.gb"); - //game_boy.load_rom_file("../../res/roms.prop/dr_mario.gb"); - //game_boy.load_rom_file("../../res/roms.prop/alleyway.gb"); - //game_boy.load_rom_file("../../res/roms.prop/super_mario.gb"); + //let rom = game_boy.load_rom_file("../../res/roms.prop/tetris.gb"); + //let rom = game_boy.load_rom_file("../../res/roms.prop/dr_mario.gb"); + //let rom = game_boy.load_rom_file("../../res/roms.prop/alleyway.gb"); + let rom = game_boy.load_rom_file("../../res/roms.prop/super_mario.gb"); //let rom = game_boy.load_rom_file("../../res/roms.prop/super_mario_2.gb"); - //game_boy.load_rom_file("../../res/roms/firstwhite.gb"); - //game_boy.load_rom_file("../../res/roms/opus5.gb"); + //let rom = game_boy.load_rom_file("../../res/roms/firstwhite.gb"); + //let rom = game_boy.load_rom_file("../../res/roms/opus5.gb"); //let rom = game_boy.load_rom_file("../../res/roms/paradius/cpu/cpu_instrs.gb"); // CRASHED //let rom = game_boy.load_rom_file("../../res/roms/paradius/interrupt_time/interrupt_time.gb"); // FAILED - let rom = game_boy.load_rom_file("../../res/roms/paradius/instr_timing/instr_timing.gb"); // FAILED + //let rom = game_boy.load_rom_file("../../res/roms/paradius/instr_timing/instr_timing.gb"); // FAILED + //let rom = game_boy.load_rom_file("../../res/roms/paradius/mem_timing/mem_timing.gb"); // NO FINISH //let rom = game_boy.load_rom_file("../../res/roms/paradius/cpu/01-special.gb"); // PASSED //let rom = game_boy.load_rom_file("../../res/roms/paradius/cpu/02-interrupts.gb"); // PASSED //let rom = game_boy.load_rom_file("../../res/roms/paradius/cpu/03-op sp,hl.gb"); // PASSED @@ -115,73 +171,12 @@ fn main() { //let rom = game_boy.load_rom_file("../../res/roms/paradius/cpu/07-jr,jp,call,ret,rst.gb"); // PASSED //let rom = game_boy.load_rom_file("../../res/roms/paradius/cpu/08-misc instrs.gb"); // PASSED //let rom = game_boy.load_rom_file("../../res/roms/paradius/cpu/09-op r,r.gb"); // PASSED - //let rom = game_boy.load_rom_file("../../res/roms/paradius/cpu/10-bit ops.gb"); // - //let rom = game_boy.load_rom_file("../../res/roms/paradius/cpu/11-op a,(hl).gb"); //let rom PASSED - + //let rom = game_boy.load_rom_file("../../res/roms/paradius/cpu/10-bit ops.gb"); // PASSED + //let rom = game_boy.load_rom_file("../../res/roms/paradius/cpu/11-op a,(hl).gb"); // PASSED println!("==== Cartridge ====\n{}\n===================", rom); - let mut counter = 0u32; - - 'main: loop { - // increments the counter that will keep track - // on the number of visual ticks since beginning - counter = counter.wrapping_add(1); - - // obtains an event from the SDL sub-system to be - // processed under the current emulation context - while let Some(event) = graphics.event_pump.poll_event() { - match event { - Event::Quit { .. } => break 'main, - - Event::KeyDown { - keycode: Some(keycode), - .. - } => match key_to_pad(keycode) { - Some(key) => game_boy.key_press(key), - None => (), - }, - - Event::KeyUp { - keycode: Some(keycode), - .. - } => match key_to_pad(keycode) { - Some(key) => game_boy.key_lift(key), - None => (), - }, - - _ => (), - } - } - - let mut counter_ticks = 0u32; - - loop { - // limits the number of ticks to the typical number - // of ticks required to do a complete PPU draw - if counter_ticks >= 70224 { - break; - } - - // runs the Game Boy clock, this operations should - // include the advance of both the CPU and the PPU - counter_ticks += game_boy.clock() as u32; - } - - // obtains the frame buffer of the Game Boy PPU and uses it - // to update the stream texture, copying it then to the canvas - let frame_buffer = game_boy.frame_buffer().as_ref(); - texture - .update(None, frame_buffer, DISPLAY_WIDTH as usize * 3) - .unwrap(); - canvas.copy(&texture, None, None).unwrap(); - - // presents the canvas effectively updating the screen - // information presented to the user - canvas.present(); - - // @todo this must be improved with proper timestamps - graphics.timer_subsystem.delay(17); - } + let mut emulator = Emulator::new(game_boy, SCREEN_SCALE); + emulator.run(); } fn key_to_pad(keycode: Keycode) -> Option<PadKey> { diff --git a/examples/sdl/src/util.rs b/examples/sdl/src/util.rs new file mode 100644 index 0000000000000000000000000000000000000000..2f613a9def6b695b28513f082ddb3c58318333ad --- /dev/null +++ b/examples/sdl/src/util.rs @@ -0,0 +1,72 @@ +use sdl2::{ + render::Canvas, rwops::RWops, surface::Surface, sys::image, ttf::Sdl2TtfContext, video::Window, + AudioSubsystem, EventPump, TimerSubsystem, VideoSubsystem, +}; + +pub struct Graphics { + pub canvas: Canvas<Window>, + pub video_subsystem: VideoSubsystem, + pub timer_subsystem: TimerSubsystem, + pub audio_subsystem: AudioSubsystem, + pub event_pump: EventPump, + pub ttf_context: Sdl2TtfContext, +} + +impl Graphics { + /// Start the SDL sub-system and all of its structure and returns + /// a structure with all the needed stuff to handle SDL graphics + /// and sound. + pub fn new(title: &str, width: u32, height: u32, scale: f32) -> Self { + // initializes the SDL sub-system, making it ready to be + // used for display of graphics and audio + let sdl = sdl2::init().unwrap(); + let video_subsystem = sdl.video().unwrap(); + let timer_subsystem = sdl.timer().unwrap(); + let audio_subsystem = sdl.audio().unwrap(); + let event_pump = sdl.event_pump().unwrap(); + + // initialized the fonts context to be used + // in the loading of fonts + let ttf_context = sdl2::ttf::init().unwrap(); + + // creates the system window that is going to be used to + // show the emulator and sets it to the central are o screen + let window = video_subsystem + .window(title, scale as u32 * width, scale as u32 * height) + .resizable() + .position_centered() + .opengl() + .build() + .unwrap(); + + // creates an accelerated canvas to be used in the drawing + // then clears it so that is can be presented empty initially + let mut canvas = window.into_canvas().accelerated().build().unwrap(); + canvas.clear(); + + Self { + canvas: canvas, + video_subsystem: video_subsystem, + timer_subsystem: timer_subsystem, + audio_subsystem: audio_subsystem, + event_pump: event_pump, + ttf_context: ttf_context, + } + } + + pub fn window(&self) -> &Window { + self.canvas.window() + } + + pub fn window_mut(&mut self) -> &mut Window { + self.canvas.window_mut() + } +} + +pub fn surface_from_bytes(bytes: &[u8]) -> Surface { + unsafe { + let rw_ops = RWops::from_bytes(bytes).unwrap(); + let raw_surface = image::IMG_Load_RW(rw_ops.raw(), 0); + Surface::from_ll(raw_surface) + } +} diff --git a/res/roms/paradius/mem_timing/mem_timing.gb b/res/roms/paradius/mem_timing/mem_timing.gb new file mode 100644 index 0000000000000000000000000000000000000000..78766b579f74433332c546c9cf06f8b972a4c2d8 Binary files /dev/null and b/res/roms/paradius/mem_timing/mem_timing.gb differ diff --git a/src/rom.rs b/src/rom.rs index 4eef24f6cd6395633b6e3126b5ed9d395c84d815..86c89671ef7239668a184b87a5a19aa4794b83c0 100644 --- a/src/rom.rs +++ b/src/rom.rs @@ -349,10 +349,12 @@ pub static MBC1: Mbc = Mbc { rom.set_rom_bank(rom_bank); } 0x4000 | 0x5000 => { - println!("SETTING UPPER BITS {}", value); + let ram_bank = value & 0x03; + rom.set_ram_bank(ram_bank); } + // ROM mode selection 0x6000 | 0x7000 => { - println!("SETTING MODE {}", value); + debugln!("SETTING MODE {}", value); } _ => panic!("Writing to unknown Cartridge ROM location 0x{:04x}", addr), }