From 03b943eecab89ebc036552978a3c68154a6d6eca Mon Sep 17 00:00:00 2001 From: Ivsucram Date: Fri, 30 Jul 2021 15:02:08 +0800 Subject: [PATCH] Chip-8: From Rect to Textures The main contribution of this commit is changing the display rendering method. Before, I was asking the canvas to render rectangles, which represented scaled pixesl. Now I am using a texture, that is automatically stretched to the whole canvas by SDL. The display coding is running faster, but there is still some ways to improve rendering. The main one is to change the cpu display buffer from a multi-dimensional array of booleans (indincating pixels toggling) to a single dimension pixel-color array. In the pixel-color array, 4u8 samples represent a single pixel on screen (R: u8, G: u8, B: u8, A:u8). This refactor will force the CPU to control have knowledge of which color will be printed on the display, letting the display being agnostic to implementation details. This refactor will be added as an issue to the github project kanban and further analyzed. Besides that, other changes to the codebase were mainly in terms of code organization. --- src/chip_8/chip_8.rs | 33 ++++++++++++--------- src/chip_8/cpu/clock.rs | 20 +++++-------- src/chip_8/cpu/cpu.rs | 29 +++++++++---------- src/chip_8/cpu/keypad.rs | 6 ++-- src/chip_8/cpu/registers.rs | 10 +++---- src/chip_8/display.rs | 58 ++++++++++++++++++------------------- 6 files changed, 77 insertions(+), 79 deletions(-) diff --git a/src/chip_8/chip_8.rs b/src/chip_8/chip_8.rs index 14718c5..24dd71b 100644 --- a/src/chip_8/chip_8.rs +++ b/src/chip_8/chip_8.rs @@ -1,43 +1,48 @@ use super::cpu::cpu::CPU; use super::display::Display; +use sdl2::event::Event; +use sdl2::keyboard::Keycode; use std::io::Read; pub fn run() { let mut cpu = CPU::new(); - load_rom("src/chip_8/roms/CAVE.ch8", &mut cpu); + load_rom( + "src/chip_8/roms/Trip8 Demo (2008) [Revival Studios].ch8", + &mut cpu, + ); let sdl_context = sdl2::init().unwrap(); let mut display = Display::init(&sdl_context, cpu.get_display_scale()); - let mut event_listener = sdl_context.event_pump().unwrap(); + let mut event_pump = sdl_context.event_pump().unwrap(); 'runner: loop { - for event in event_listener.poll_iter() { + for event in event_pump.poll_iter() { match event { - sdl2::event::Event::Quit { .. } => break 'runner, - sdl2::event::Event::KeyDown { - keycode: Some(sdl2::keyboard::Keycode::Escape), + Event::Quit { .. } => break 'runner, + Event::KeyDown { + keycode: Some(Keycode::Escape), .. } => break 'runner, - sdl2::event::Event::KeyDown { - keycode: Some(sdl2::keyboard::Keycode::RightBracket), + Event::KeyDown { + keycode: Some(Keycode::RightBracket), .. } => { cpu.increase_clock(true); } - sdl2::event::Event::KeyDown { - keycode: Some(sdl2::keyboard::Keycode::LeftBracket), + Event::KeyDown { + keycode: Some(Keycode::LeftBracket), .. } => { cpu.decrease_clock(true); } - sdl2::event::Event::KeyDown { - keycode: Some(sdl2::keyboard::Keycode::Backspace), + Event::KeyDown { + keycode: Some(Keycode::Backspace), .. } => { cpu.reset_rom(); } - sdl2::event::Event::KeyDown { + Event::KeyDown { keycode: Some(keycode), .. } => { @@ -45,7 +50,7 @@ pub fn run() { cpu.press_key(key_index); } } - sdl2::event::Event::KeyUp { + Event::KeyUp { keycode: Some(keycode), .. } => { diff --git a/src/chip_8/cpu/clock.rs b/src/chip_8/cpu/clock.rs index 5cc3d5d..4cedc19 100644 --- a/src/chip_8/cpu/clock.rs +++ b/src/chip_8/cpu/clock.rs @@ -16,22 +16,16 @@ impl Clock { } pub fn tick(&mut self) -> bool { - let mut res: bool = false; - match self.elapsed.elapsed() { - Ok(elapsed) => { - if elapsed.as_secs_f64() >= self.clock_hz_as_secs_f64() { - if self.tick > 0 { - self.tick -= 1; - } - self.reset_elapsed(); - res = true; + if let Ok(elapsed) = self.elapsed.elapsed() { + if elapsed.as_secs_f64() >= self.clock_hz_as_secs_f64() { + if self.tick > 0 { + self.tick -= 1; } - } - Err(e) => { - println!("Error: {:?}", e); + self.reset_elapsed(); + return true; } } - res + false } fn reset_elapsed(&mut self) { diff --git a/src/chip_8/cpu/cpu.rs b/src/chip_8/cpu/cpu.rs index 695e259..2e4a5a2 100644 --- a/src/chip_8/cpu/cpu.rs +++ b/src/chip_8/cpu/cpu.rs @@ -10,16 +10,16 @@ use sdl2::keyboard::Keycode; use rand::Rng; pub struct CPU { - stack: Vec, // Function Stack - dt: Clock, // Delay Timer - st: Clock, // Sound Timer - clock: Clock, // CPU Clock - regs: Registers, // Registers - ram: RAM, // RAM - keypad: Keypad, // Keypad - db: DisplayBuffer, // Display Buffer - op: OpCodes, // Operation Code, - pub should_redraw: bool // Boolean indicating Display Buffer update + stack: Vec, // Function Stack + dt: Clock, // Delay Timer + st: Clock, // Sound Timer + clock: Clock, // CPU Clock + regs: Registers, // Registers + ram: RAM, // RAM + keypad: Keypad, // Keypad + db: DisplayBuffer, // Display Buffer + op: OpCodes, // Operation Code, + pub should_redraw: bool, // Boolean indicating Display Buffer update } impl CPU { @@ -37,7 +37,7 @@ impl CPU { keypad: Keypad::new(), db: DisplayBuffer::new(10), op: OpCodes::new(0000), - should_redraw: false + should_redraw: false, } } @@ -189,7 +189,7 @@ impl CPU { pub fn set_delay_timer(&mut self, tick: u8) { self.dt.tick = tick; - } + } pub fn set_sound_timer(&mut self, tick: u8) { self.st.tick = tick; @@ -343,8 +343,7 @@ fn op_bnnn(cpu: &mut CPU) { fn op_cxnn(cpu: &mut CPU) { let mut rng = rand::thread_rng(); - cpu.regs - .set(cpu.op.x, rng.gen_range(0x0..0xFF) & cpu.op.nn); + cpu.regs.set(cpu.op.x, rng.gen_range(0x0..0xFF) & cpu.op.nn); } fn op_dxyn(cpu: &mut CPU) { @@ -438,4 +437,4 @@ fn op_fx65(cpu: &mut CPU) { for regs in 0x0..(cpu.op.x + 1) { cpu.regs.set(regs, cpu.ram.read8(i + regs)); } -} \ No newline at end of file +} diff --git a/src/chip_8/cpu/keypad.rs b/src/chip_8/cpu/keypad.rs index a1ae97c..b826bc8 100644 --- a/src/chip_8/cpu/keypad.rs +++ b/src/chip_8/cpu/keypad.rs @@ -35,10 +35,10 @@ impl Keypad { } pub fn compute_keycode(&self, keycode: Keycode) -> Option { - match self.keys.get(&keycode) { - Some(chip8_key) => Some(*chip8_key), - None => None, + if let Some(&chip8_key) = self.keys.get(&keycode) { + return Some(chip8_key); } + None } pub fn get_status(&mut self, pos: usize) -> bool { diff --git a/src/chip_8/cpu/registers.rs b/src/chip_8/cpu/registers.rs index ec3d57e..08eb7cf 100644 --- a/src/chip_8/cpu/registers.rs +++ b/src/chip_8/cpu/registers.rs @@ -16,9 +16,9 @@ pub struct Registers { x_c: u8, x_d: u8, x_e: u8, - x_f: u8, // Flag register - pub pc: usize, // Program Counter - pub i: usize // Index register + x_f: u8, // Flag register + pub pc: usize, // Program Counter + pub i: usize, // Index register } impl Registers { @@ -64,7 +64,7 @@ impl Registers { 0xD => self.x_d, 0xE => self.x_e, 0xF => self.x_f, - _ => 0, + _ => panic!("Trying to access non-existing register"), } } @@ -87,7 +87,7 @@ impl Registers { 0xD => self.x_d = value, 0xE => self.x_e = value, 0xF => self.x_f = value, - _ => {} + _ => panic!("Trying to access non-existing register"), } } diff --git a/src/chip_8/display.rs b/src/chip_8/display.rs index 1583be1..a4635b0 100644 --- a/src/chip_8/display.rs +++ b/src/chip_8/display.rs @@ -1,56 +1,56 @@ -use sdl2::render::Canvas; use sdl2::pixels::Color; -use sdl2::rect::Rect; +use sdl2::pixels::PixelFormatEnum; +use sdl2::render::Canvas; +use sdl2::video::Window; const WIDTH: usize = 64; const HEIGHT: usize = 32; +const PITCH_BYTES: usize = std::mem::size_of::(); // 4 bytes: R G B A, from colors +const PITCH: usize = WIDTH * PITCH_BYTES; pub struct Display { - canvas: Canvas, - scale: u32, - off_color: Color, - on_color: Color, + canvas: Canvas, + off_color: [u8; PITCH_BYTES], + on_color: [u8; PITCH_BYTES], } impl Display { pub fn init(sdl_context: &sdl2::Sdl, scale: u32) -> Display { - let video_subsystem = sdl_context.video().unwrap(); - - let window = video_subsystem + let canvas = sdl_context.video().unwrap() .window("Chip-8", WIDTH as u32 * scale, HEIGHT as u32 * scale) - .position_centered() - .build() - .unwrap(); + .resizable().position_centered().build().unwrap() + .into_canvas().build().unwrap(); - let canvas = window.into_canvas().build().unwrap(); + let off_color = Color::RGBA(0, 0, 0, 255); + let on_color = Color::RGBA(255, 255, 255, 255); Display { canvas: canvas, - scale: scale, - off_color: Color::RGB(0, 0, 0), - on_color: Color::RGB(255, 255, 255), + off_color: [off_color.r, off_color.g, off_color.b, off_color.a], + on_color: [on_color.r, on_color.g, on_color.b, on_color.a], } } pub fn draw(self: &mut Display, buffer: &[[bool; HEIGHT]; WIDTH]) { - self.canvas.set_draw_color(self.off_color); - self.canvas.clear(); + self.apply_texture(buffer); + self.canvas.present(); + } - self.canvas.set_draw_color(self.on_color); + fn apply_texture(&mut self, buffer: &[[bool; HEIGHT]; WIDTH]) { + let mut framebuffer: [u8; HEIGHT * PITCH] = [0; HEIGHT * PITCH]; for (x, col) in buffer.iter().enumerate() { for (y, pixel) in col.iter().enumerate() { - if *pixel { - let x = (x as u32 * self.scale) as i32; - let y = (y as u32 * self.scale) as i32; - let width = self.scale; - let height = self.scale; - self.canvas - .fill_rect(Rect::new(x, y, width, height)) - .expect("Failed to draw pixel"); - } + let offset = x * PITCH_BYTES + y * PITCH; + let color: &[u8; PITCH_BYTES] = if *pixel { &self.on_color } else { &self.off_color }; + for i in offset..(offset + PITCH_BYTES) { framebuffer[i] = color[i - offset]; } } } - self.canvas.present(); + let texture_creator = self.canvas.texture_creator(); + let mut texture = texture_creator + .create_texture_streaming(PixelFormatEnum::RGBA32, WIDTH as u32, HEIGHT as u32) + .unwrap(); + texture.update(None, &framebuffer, PITCH).unwrap(); + self.canvas.copy(&texture, None, None).unwrap(); } }