From 2b723440cbd42b7745d05fe2957bf86d8e12e12a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jo=C3=A3o=20Magalh=C3=A3es?= <joamag@gmail.com>
Date: Sun, 10 Jul 2022 11:41:57 +0100
Subject: [PATCH] feat: greatly simplified SDL emulator structure

---
 examples/sdl/Cargo.toml                    |   1 -
 examples/sdl/src/main.rs                   | 283 ++++++++++-----------
 examples/sdl/src/util.rs                   |  72 ++++++
 res/roms/paradius/mem_timing/mem_timing.gb | Bin 0 -> 65536 bytes
 src/rom.rs                                 |   6 +-
 5 files changed, 215 insertions(+), 147 deletions(-)
 create mode 100644 examples/sdl/src/util.rs
 create mode 100644 res/roms/paradius/mem_timing/mem_timing.gb

diff --git a/examples/sdl/Cargo.toml b/examples/sdl/Cargo.toml
index 6a029ada..203f0936 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 2fbc7a1c..f7a1ec40 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 00000000..2f613a9d
--- /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
GIT binary patch
literal 65536
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
Q0;3@?8UmvsFa$#Y0RA)CQvd(}

literal 0
HcmV?d00001

diff --git a/src/rom.rs b/src/rom.rs
index 4eef24f6..86c89671 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),
         }
-- 
GitLab