diff --git a/Cargo.toml b/Cargo.toml index 5240c15..152f784 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,6 @@ edition = "2018" [dependencies] sdl2 = "0.32" cpuprofiler = "0.0.3" + +[profile.release] +debug = true diff --git a/src/main.rs b/src/main.rs index 86cd52b..27f5c26 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,24 +9,33 @@ mod input; use cpu::Cpu; use ppu::Ppu; use cartridge::Cartridge; -use screen::Screen; +use screen::{init_window, draw_pixel, draw_to_window}; use input::poll_buttons; use sdl2::keyboard::Keycode; use sdl2::event::Event; +use sdl2::pixels::PixelFormatEnum; // use cpuprofiler::PROFILER; fn main() -> Result<(), String> { + // Set up screen let sdl_context = sdl2::init()?; let mut event_pump = sdl_context.event_pump()?; - let mut screen = Screen::new(&sdl_context)?; + 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 let cart = Cartridge::new(); - let ppu = Ppu::new(&cart); let mut cpu = Cpu::new(&cart, ppu); + // For throttling to 60 FPS let mut timer = Instant::now(); let mut fps_timer = Instant::now(); let mut fps = 0; @@ -39,12 +48,12 @@ fn main() -> Result<(), String> { for _i in 0..num_cycles * 3 { let (pixel, end_of_frame) = cpu.ppu.step(); match pixel { - Some((x, y, color)) => screen.draw_pixel(x, y, color), - None => Ok(()), - }?; + Some((x, y, color)) => draw_pixel(&mut screen_buffer, x, y, color), + None => (), + }; if end_of_frame { fps += 1; - screen.canvas.present(); + draw_to_window(&mut texture, &mut canvas, &screen_buffer)?; let now = Instant::now(); if now < timer + Duration::from_millis(1000/60) { std::thread::sleep(timer + Duration::from_millis(1000/60) - now); diff --git a/src/screen/mod.rs b/src/screen/mod.rs index c765270..06c7c2c 100644 --- a/src/screen/mod.rs +++ b/src/screen/mod.rs @@ -2,38 +2,50 @@ extern crate sdl2; use sdl2::Sdl; use sdl2::pixels::Color; -use sdl2::rect::Rect; -use sdl2::render::Canvas; -use sdl2::video::Window; +use sdl2::render::{Canvas, Texture, TextureCreator}; +use sdl2::video::{Window, WindowContext}; -const SCALE_FACTOR: usize = 4; +pub const SCALE_FACTOR: usize = 3; +const BYTES_IN_COL: usize = SCALE_FACTOR * 3; // 3 bytes per pixel in RGB24. This represents a thick, SCALE_FACTOR-pixel-wide column. +const BYTES_IN_ROW: usize = BYTES_IN_COL * 256; // 256 = screen width in NES pixels. This represents a thin, one-SDL-pixel-tall row. type RGBColor = (u8, u8, u8); -pub struct Screen { - pub canvas: Canvas, +pub fn init_window(context: &Sdl) -> Result<(Canvas, TextureCreator), String> { + let video_subsystem = context.video()?; + let window = video_subsystem.window("NESTUR", (256 * SCALE_FACTOR) as u32, (240 * SCALE_FACTOR) as u32) + .position_centered() + .opengl() + .build() + .map_err(|e| e.to_string())?; + let mut canvas = window.into_canvas().build().map_err(|e| e.to_string())?; + let texture_creator = canvas.texture_creator(); + canvas.set_draw_color(Color::RGB(0, 0, 0)); + canvas.clear(); + canvas.present(); + Ok((canvas, texture_creator)) } -impl Screen { - pub fn new(context: &Sdl) -> Result { - let video_subsystem = context.video()?; - let window = video_subsystem.window("NESTUR", (256 * SCALE_FACTOR) as u32, (240 * SCALE_FACTOR) as u32) - .position_centered() - .opengl() - .build() - .map_err(|e| e.to_string())?; - let mut canvas = window.into_canvas().build().map_err(|e| e.to_string())?; - canvas.set_draw_color(Color::RGB(0, 0, 0)); - canvas.clear(); - canvas.present(); - Ok(Screen{canvas}) +pub fn draw_pixel(buffer: &mut Vec, x: usize, y: usize, color: RGBColor) { + let (r, g, b) = color; + let nes_y_offset = y * BYTES_IN_ROW * SCALE_FACTOR; // find offset for thick, SCALE_FACTOR-pixel tall row + for sdl_row_num in 0..SCALE_FACTOR { // looping over one-pixel tall rows up to SCALE_FACTOR + let row_offset = nes_y_offset + (sdl_row_num * BYTES_IN_ROW); // row_offset is the offset within buffer of the thin row we're on + let nes_x_offset = x * BYTES_IN_COL; // find horizontal offset within row (in byte terms) of NES x-coordinate + for sdl_col_num in 0..SCALE_FACTOR { // for pixels up to SCALE_FACTOR, moving horizontally + let col_offset = nes_x_offset + (sdl_col_num * 3); // skip 3 bytes at a time, R/G/B for each pixel + let offset = row_offset + col_offset; + buffer[offset + 0] = r; + buffer[offset + 1] = g; + buffer[offset + 2] = b; + } } +} - pub fn draw_pixel(&mut self, x: usize, y: usize, color: RGBColor) -> Result<(), String> { - let (r, g, b) = color; - self.canvas.set_draw_color(Color::RGB(r, g, b)); - self.canvas.fill_rect(Rect::new((x * SCALE_FACTOR) as i32, (y * SCALE_FACTOR) as i32, - SCALE_FACTOR as u32, SCALE_FACTOR as u32))?; - Ok(()) - } -} \ No newline at end of file +pub fn draw_to_window(texture: &mut Texture, canvas: &mut Canvas, buffer: &Vec) -> Result<(), String> { + texture.update(None, buffer, 256*3*SCALE_FACTOR) + .map_err(|e| e.to_string())?; + canvas.copy(&texture, None, None)?; + canvas.present(); + Ok(()) +}