2019-11-12 00:04:07 +00:00
|
|
|
use std::time::{Instant, Duration};
|
|
|
|
|
|
|
|
mod cpu;
|
|
|
|
mod ppu;
|
|
|
|
mod cartridge;
|
|
|
|
mod screen;
|
|
|
|
mod input;
|
|
|
|
|
|
|
|
use cpu::Cpu;
|
|
|
|
use ppu::Ppu;
|
|
|
|
use cartridge::Cartridge;
|
2019-11-12 23:19:25 +00:00
|
|
|
use screen::{init_window, draw_pixel, draw_to_window};
|
2019-11-12 00:04:07 +00:00
|
|
|
use input::poll_buttons;
|
|
|
|
|
|
|
|
use sdl2::keyboard::Keycode;
|
|
|
|
use sdl2::event::Event;
|
2019-11-12 23:19:25 +00:00
|
|
|
use sdl2::pixels::PixelFormatEnum;
|
2019-11-12 00:04:07 +00:00
|
|
|
|
|
|
|
// use cpuprofiler::PROFILER;
|
|
|
|
|
|
|
|
fn main() -> Result<(), String> {
|
2019-11-12 23:19:25 +00:00
|
|
|
// Set up screen
|
2019-11-12 00:04:07 +00:00
|
|
|
let sdl_context = sdl2::init()?;
|
|
|
|
let mut event_pump = sdl_context.event_pump()?;
|
2019-11-12 23:19:25 +00:00
|
|
|
let (mut canvas, texture_creator) = init_window(&sdl_context).expect("Could not create window");
|
|
|
|
let mut texture = texture_creator.create_texture_streaming(
|
|
|
|
PixelFormatEnum::RGB24, 256*screen::SCALE_FACTOR as u32, 240*screen::SCALE_FACTOR as u32)
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
let byte_width = 256 * 3 * screen::SCALE_FACTOR; // 256 NES pixels, 3 bytes for each pixel (RGB 24-bit), and NES-to-SDL scale factor
|
|
|
|
let byte_height = 240 * screen::SCALE_FACTOR; // NES image is 240 pixels tall, multiply by scale factor for total number of rows needed
|
|
|
|
let mut screen_buffer = vec![0; byte_width * byte_height]; // contains raw RGB data for the screen
|
|
|
|
|
|
|
|
// Initialize hardware components
|
2019-11-12 00:04:07 +00:00
|
|
|
let cart = Cartridge::new();
|
|
|
|
let ppu = Ppu::new(&cart);
|
|
|
|
let mut cpu = Cpu::new(&cart, ppu);
|
|
|
|
|
2019-11-12 23:19:25 +00:00
|
|
|
// For throttling to 60 FPS
|
2019-11-12 00:04:07 +00:00
|
|
|
let mut timer = Instant::now();
|
|
|
|
let mut fps_timer = Instant::now();
|
|
|
|
let mut fps = 0;
|
|
|
|
|
|
|
|
// PROFILER.lock().unwrap().start("./main.profile").unwrap();
|
|
|
|
'running: loop {
|
|
|
|
// perform 1 cpu instruction, getting back number of clock cycles it took
|
|
|
|
let num_cycles = cpu.step();
|
|
|
|
// maintain ratio of 3 ppu cycles for 1 cpu step
|
|
|
|
for _i in 0..num_cycles * 3 {
|
|
|
|
let (pixel, end_of_frame) = cpu.ppu.step();
|
|
|
|
match pixel {
|
2019-11-12 23:19:25 +00:00
|
|
|
Some((x, y, color)) => draw_pixel(&mut screen_buffer, x, y, color),
|
|
|
|
None => (),
|
|
|
|
};
|
2019-11-12 00:04:07 +00:00
|
|
|
if end_of_frame {
|
|
|
|
fps += 1;
|
2019-11-12 23:19:25 +00:00
|
|
|
draw_to_window(&mut texture, &mut canvas, &screen_buffer)?;
|
2019-11-12 00:04:07 +00:00
|
|
|
let now = Instant::now();
|
|
|
|
if now < timer + Duration::from_millis(1000/60) {
|
|
|
|
std::thread::sleep(timer + Duration::from_millis(1000/60) - now);
|
|
|
|
}
|
|
|
|
timer = Instant::now();
|
|
|
|
for event in event_pump.poll_iter() {
|
|
|
|
match event {
|
|
|
|
Event::Quit {..} | Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
|
|
|
|
break 'running
|
|
|
|
},
|
|
|
|
_ => (),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// handle keyboard events
|
|
|
|
match poll_buttons(&cpu.strobe, &event_pump) {
|
|
|
|
Some(button_states) => cpu.button_states = button_states,
|
|
|
|
None => (),
|
|
|
|
};
|
|
|
|
// calculate fps
|
|
|
|
let now = Instant::now();
|
|
|
|
if now > fps_timer + Duration::from_secs(1) {
|
|
|
|
println!("fps: {}", fps);
|
|
|
|
fps = 0;
|
|
|
|
fps_timer = now;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// PROFILER.lock().unwrap().stop().unwrap();
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: reset function?
|
|
|
|
// TODO: save/load functionality
|