Newer
Older
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 Emulator {
system: GameBoy,
graphics: Graphics,
visual_frequency: u32,
next_tick_time: f32,
next_tick_time_i: u32,
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,
}
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();
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// 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 => (),
},
_ => (),
}
}
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
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;
}
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);
}
}
}
// 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();
//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");
//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/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
//let rom = game_boy.load_rom_file("../../res/roms/paradius/cpu/04-op r,imm.gb"); // PASSED
//let rom = game_boy.load_rom_file("../../res/roms/paradius/cpu/05-op rp.gb"); // PASSED
//let rom = game_boy.load_rom_file("../../res/roms/paradius/cpu/06-ld r,r.gb"); // PASSED
//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"); // 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 emulator = Emulator::new(game_boy, SCREEN_SCALE);
emulator.run();
fn key_to_pad(keycode: Keycode) -> Option<PadKey> {
Keycode::Up => Some(PadKey::Up),
Keycode::Down => Some(PadKey::Down),
Keycode::Left => Some(PadKey::Left),
Keycode::Right => Some(PadKey::Right),
Keycode::Return => Some(PadKey::Start),
Keycode::Return2 => Some(PadKey::Start),
Keycode::Space => Some(PadKey::Select),
Keycode::A => Some(PadKey::A),
Keycode::S => Some(PadKey::B),
_ => None,