diff --git a/src/emu.rs b/src/emu.rs index 95a1a8a..696c721 100644 --- a/src/emu.rs +++ b/src/emu.rs @@ -69,9 +69,7 @@ impl Chip8 { } pub fn load_font_set(&mut self) { - for (i, byte) in Self::FONT_SET.iter().enumerate() { - self.memory[i] = *byte; - } + self.memory[0..0x50].copy_from_slice(&Self::FONT_SET); } pub fn load_rom>(&mut self, path: P) -> Result<(), io::Error> { @@ -79,9 +77,7 @@ impl Chip8 { let mut rom_buf: Vec = vec![]; file.read_to_end(&mut rom_buf)?; - for (i, byte) in rom_buf.iter().enumerate() { - self.memory[i + 0x200] = *byte; - } + self.memory[0x200..(0x200 + rom_buf.len())].copy_from_slice(&rom_buf); Ok(()) } @@ -92,88 +88,98 @@ impl Chip8 { ((self.memory[pc] as u16) << 8) | self.memory[pc + 1] as u16 } + fn get_nibs(opcode: u16) -> (u8, u8, u8, u8) { + // Given 0xA2F0 + ( + ((opcode & 0xF000) >> 12) as u8, // 0xA + ((opcode & 0x0F00) >> 8) as u8, // 0x2 + ((opcode & 0x00F0) >> 4) as u8, // 0xF + (opcode & 0x000F) as u8, // 0x0 + ) + } + fn handle_opcode(&mut self) { - // Given: 0xA2F0 - let nib_1 = (self.opcode & 0xF000) >> 12; //0xA - let nib_2 = (self.opcode & 0x0F00) >> 8; // 0x2 - let nib_3 = (self.opcode & 0x00F0) >> 4; // 0xF - let nib_4 = self.opcode & 0x000F; // 0x0 + let (nib_1, nib_2, nib_3, nib_4) = Self::get_nibs(self.opcode); + + let nnn: u16 = self.opcode & 0x0FFF; + let kk: u8 = (self.opcode & 0x00FF) as u8; + let x = nib_2; + let y = nib_3; + let n = nib_4; self.pc += 2; - // nib_ns are u16s so we waste 4 bytes here. - match (nib_1, nib_2, nib_3, nib_4) { // CLS (0x0, 0x0, 0xE, 0x0) => self.cls(), // 00EE (0x0, 0x0, 0xE, 0xE) => self.ret(), // 1NNN - (0x1, _, _, _) => self.jmp_addr(nib_2, nib_3, nib_4), + (0x1, _, _, _) => self.jmp_addr(nnn), // 2NNN - (0x2, _, _, _) => self.call_addr(nib_2, nib_3, nib_4), + (0x2, _, _, _) => self.call_addr(nnn), // 3XKK - (0x3, _, _, _) => self.se_vx_byte(nib_2, nib_3, nib_4), + (0x3, _, _, _) => self.se_vx_byte(x, kk), // 4xkk - (0x4, _, _, _) => self.sne_vx_byte(nib_2, nib_3, nib_4), + (0x4, _, _, _) => self.sne_vx_byte(x, kk), // 5xy0 - (0x5, _, _, 0x0) => self.se_vx_vy(nib_2, nib_3), + (0x5, _, _, 0x0) => self.se_vx_vy(x, y), // 6xkk - (0x6, _, _, _) => self.ld_vx_byte(nib_2, nib_3, nib_4), + (0x6, _, _, _) => self.ld_vx_byte(x, kk), // 7xkk - (0x7, _, _, _) => self.add_vx_byte(nib_2, nib_3, nib_4), + (0x7, _, _, _) => self.add_vx_byte(x, kk), // 8xy0 - (0x8, _, _, 0x0) => self.ld_vx_vy(nib_2, nib_3), + (0x8, _, _, 0x0) => self.ld_vx_vy(x, y), // 8xy1 - (0x8, _, _, 0x1) => self.or_vx_vy(nib_2, nib_3), + (0x8, _, _, 0x1) => self.or(x, y), // 8xy2 - (0x8, _, _, 0x2) => self.and_vx_vy(nib_2, nib_3), + (0x8, _, _, 0x2) => self.and_vx_vy(x, y), // 8xy3 - (0x8, _, _, 0x3) => self.xor_vx_vy(nib_2, nib_3), + (0x8, _, _, 0x3) => self.xor(x, y), // 8xy4 - (0x8, _, _, 0x4) => self.add_vx_vy(nib_2, nib_3), + (0x8, _, _, 0x4) => self.add_vx_vy(x, y), // 8xy5 - (0x8, _, _, 0x5) => self.sub_vx_vy(nib_2, nib_3), + (0x8, _, _, 0x5) => self.sub_vx_vy(x, y), // 8xy6 - (0x8, _, _, 0x6) => self.shr_vx(nib_2), + (0x8, _, _, 0x6) => self.shr(x), // 8xy7 - (0x8, _, _, 0x7) => self.subn_vx_vy(nib_2, nib_3), + (0x8, _, _, 0x7) => self.subn_vx_vy(x, y), // 8xyE - (0x8, _, _, 0xE) => self.shl_vx(nib_2), + (0x8, _, _, 0xE) => self.shl(x), // 9xy0 - (0x9, _, _, 0x0) => self.sne_vx_vy(nib_2, nib_3), + (0x9, _, _, 0x0) => self.sne_vx_vy(x, y), // Annn - (0xA, _, _, _) => self.ld_i_addr(nib_2, nib_3, nib_4), + (0xA, _, _, _) => self.load_addr_to_i(nnn), // Bnnn - (0xB, _, _, _) => self.jmp_v0_addr(nib_2, nib_3, nib_4), + (0xB, _, _, _) => self.jmp_addr_with_offset(nnn), // Cxkk - (0xC, _, _, _) => self.rnd_vx_byte(nib_2, nib_3, nib_4), + (0xC, _, _, _) => self.rand(x, kk), //Dxyn - (0xD, _, _, _) => self.drw_vx_vy_nib(nib_2, nib_3, nib_4), + (0xD, _, _, _) => self.draw(x, y, n), // Ex9E - (0xE, _, 0x9, 0xE) => self.skp_vx(nib_2), + (0xE, _, 0x9, 0xE) => self.skip_on_press(x), // ExA1 - (0xE, _, 0xA, 0x1) => self.sknp_vx(nib_2), + (0xE, _, 0xA, 0x1) => self.skip_not_pressed(x), // Fx07 - (0xF, _, 0x0, 0x7) => self.ld_vx_dt(nib_2), + (0xF, _, 0x0, 0x7) => self.copy_delay_timer_val(x), // Fx0A - (0xF, _, 0x0, 0xA) => self.ld_vx_k(nib_2), + (0xF, _, 0x0, 0xA) => self.loop_until_key_vx(x), // Fx15 - (0xF, _, 0x1, 0x15) => self.ld_dt_vx(nib_2), + (0xF, _, 0x1, 0x15) => self.set_delay_to_vx(x), // Fx18 - (0xF, _, 0x1, 0x8) => self.ld_st_vx(nib_2), + (0xF, _, 0x1, 0x8) => self.set_sound_to_vx(x), // Fx1E - (0xF, _, 0x1, 0xE) => self.add_i_vx(nib_2), + (0xF, _, 0x1, 0xE) => self.add_vx_to_i(x), // Fx29 - (0xF, _, 0x2, 0x9) => self.ld_f_vx(nib_2), + (0xF, _, 0x2, 0x9) => self.set_i_to_hex_sprite_loc(x), // Fx33 - (0xF, _, 0x3, 0x3) => self.ld_b_vx(nib_2), + (0xF, _, 0x3, 0x3) => self.copy_digits_to_i(x), // Fx55 - (0xF, _, 0x5, 0x5) => self.ld_i_vx(nib_2), + (0xF, _, 0x5, 0x5) => self.copy_from_vx_to_memory(x), // Fx65 - (0xF, _, 0x6, 0x5) => self.ld_vx_i(nib_2), - // General Case - _ => println!("UNIMPLEMENTED OPCODE: {:#x}", self.opcode), + (0xF, _, 0x6, 0x5) => self.load_into_vx_from_i(x), + // Otherwise... + _ => panic!("UNIMPLEMENTED OPCODE: {:#x}", self.opcode), } } @@ -182,171 +188,168 @@ impl Chip8 { self.display.clear(); } - fn jmp_addr(&mut self, n_1: u16, n_2: u16, n_3: u16) { - // sets the program counter to addr (nnn) - self.pc = Self::convert_to_addr(n_1, n_2, n_3); - println!("{:#x}", self.pc); - } - fn ret(&mut self) { - // sets the program counter to the addres at the top of the stack, + // sets the program counter to the address at the top of the stack, // then subtracts one (1) from the stack pointer self.pc = self.stack[self.sp as usize]; self.sp -= 1; } - fn call_addr(&mut self, n_1: u16, n_2: u16, n_3: u16) { + fn jmp_addr(&mut self, nnn: u16) { + // sets the program counter to addr (nnn) + self.pc = nnn; + } + + fn call_addr(&mut self, nnn: u16) { // increments the stack pointer, then puts current pc on top of stack // pc is then set to addr self.sp += 1; self.stack[self.sp as usize] = self.pc; - self.pc = Self::convert_to_addr(n_1, n_2, n_3); + self.pc = nnn; } - fn se_vx_byte(&mut self, x: u16, k_1: u16, k_2: u16) { + fn se_vx_byte(&mut self, x: u8, kk: u8) { // compares Vx to kk. If they are equal, pc is incremented by 2 - if self.v[x as usize] == Self::convert_to_byte(k_1, k_2) { + if self.v[x as usize] == kk { self.pc += 2; } } - fn sne_vx_byte(&mut self, x: u16, k_1: u16, k_2: u16) { + fn sne_vx_byte(&mut self, x: u8, kk: u8) { // compares Vx to kk. If they are **not** equal, pc is incremented by 2 - if self.v[x as usize] != Self::convert_to_byte(k_1, k_2) { + if self.v[x as usize] != kk { self.pc += 2; } } - fn se_vx_vy(&mut self, x: u16, y: u16) { + fn se_vx_vy(&mut self, x: u8, y: u8) { // compares Vx to Vy. If they are equal, pc is incremented by 2 if self.v[x as usize] == self.v[y as usize] { self.pc += 2; } } - fn ld_vx_byte(&mut self, x: u16, k_1: u16, k_2: u16) { + fn ld_vx_byte(&mut self, x: u8, kk: u8) { // put value kk into Vx - self.v[x as usize] = Self::convert_to_byte(k_1, k_2); + self.v[x as usize] = kk; } - fn add_vx_byte(&mut self, x: u16, k_1: u16, k_2: u16) { + fn add_vx_byte(&mut self, x: u8, kk: u8) { // calculate Vx + kk, then store it in Vx let x = x as usize; - self.v[x] = self.v[x].wrapping_add(Self::convert_to_byte(k_1, k_2)); + self.v[x] = self.v[x] + kk; } - fn ld_vx_vy(&mut self, x: u16, y: u16) { + fn ld_vx_vy(&mut self, x: u8, y: u8) { // store Vy in Vx self.v[x as usize] = self.v[y as usize]; } - fn or_vx_vy(&mut self, x: u16, y: u16) { + fn or(&mut self, x: u8, y: u8) { // calc bitwise OR on Vx and Vy, then store in Vx let x = x as usize; self.v[x] = self.v[x] | self.v[y as usize]; } - fn and_vx_vy(&mut self, x: u16, y: u16) { + fn and_vx_vy(&mut self, x: u8, y: u8) { // calc bitwise AND on Vx and Vy, then store in Vx let x = x as usize; self.v[x] = self.v[x] & self.v[y as usize]; } - fn xor_vx_vy(&mut self, x: u16, y: u16) { + fn xor(&mut self, x: u8, y: u8) { // calc bitwise XOR on Vx and Vy, then store in Vx let x = x as usize; self.v[x] = self.v[x] ^ self.v[y as usize]; } - fn add_vx_vy(&mut self, x: u16, y: u16) { + fn add_vx_vy(&mut self, x: u8, y: u8) { // add Vx and Vy, if result is greater than 8 bits // set VF to 1, otherwise 0 // only the lowest 8 bits of result are stored in Vx let x = x as usize; let (res, did_overflow) = self.v[x].overflowing_add(self.v[y as usize]); - self.v[0xF as usize] = if did_overflow { 1 } else { 0 }; + self.v[0xF] = if did_overflow { 1 } else { 0 }; self.v[x] = res; } - fn sub_vx_vy(&mut self, x: u16, y: u16) { + fn sub_vx_vy(&mut self, x: u8, y: u8) { // subtract Vx and Vy, if Vx > Vy VF is set to 1, otherwise 0 // then set Vx to Vx - Vy let vx = self.v[x as usize]; let vy = self.v[y as usize]; - self.v[0xF as usize] = if vx > vy { 1 } else { 0 }; - self.v[x as usize] = vx.wrapping_sub(vy); + self.v[0xF] = if vx > vy { 1 } else { 0 }; + self.v[x as usize] = vx - vy; } - fn shr_vx(&mut self, x: u16) { + fn shr(&mut self, x: u8) { // if LSB is set to 1, set VF to 1, otherwise set VF to 0 // then shift Vx one to the right let x = x as usize; - self.v[0xF as usize] = if (self.v[x] & 1) == 1 { 1 } else { 0 }; - self.v[x] = self.v[x] >> 1; + self.v[0xF] = if (self.v[x] & 1) == 1 { 1 } else { 0 }; + self.v[x] >>= 1; } - fn subn_vx_vy(&mut self, x: u16, y: u16) { + fn subn_vx_vy(&mut self, x: u8, y: u8) { // subtract Vy and Vx, if Vy > Vx VF is set to 1, otherwise 0 // then set Vx = Vy - Vx let vx = self.v[x as usize]; let vy = self.v[y as usize]; - self.v[0xF as usize] = if vy > vx { 1 } else { 0 }; + self.v[0xF] = if vy > vx { 1 } else { 0 }; self.v[x as usize] = vy - vx; } - fn shl_vx(&mut self, x: u16) { + fn shl(&mut self, x: u8) { // if MSB is set to 1, set VF to 1, otherwise set VF to 0 // then shift Vx one to the left; let x = x as usize; - self.v[0xF as usize] = if (self.v[x] & 0x80) == 1 { 1 } else { 0 }; + self.v[0xF] = if (self.v[x] & 0x80) == 1 { 1 } else { 0 }; self.v[x] = self.v[x] << 1; } - fn sne_vx_vy(&mut self, x: u16, y: u16) { + fn sne_vx_vy(&mut self, x: u8, y: u8) { // if Vx != vy program counter is increased by 2 if self.v[x as usize] != self.v[y as usize] { self.pc += 2; } } - fn ld_i_addr(&mut self, n_1: u16, n_2: u16, n_3: u16) { + fn load_addr_to_i(&mut self, nnn: u16) { // set i to addr - self.i = Self::convert_to_addr(n_1, n_2, n_3); + self.i = nnn; } - fn jmp_v0_addr(&mut self, n_1: u16, n_2: u16, n_3: u16) { + fn jmp_addr_with_offset(&mut self, nnn: u16) { // set program counter to addr + V0 - self.pc = Self::convert_to_addr(n_1, n_2, n_3) + self.v[0 as usize] as u16; + self.pc = nnn + self.v[0] as u16; } - fn rnd_vx_byte(&mut self, x: u16, k_1: u16, k_2: u16) { + fn rand(&mut self, x: u8, kk: u8) { // generate a random number from 0 to 255 // AND with the value of kk, then store in Vx - - self.v[x as usize] = rand::random::() & Self::convert_to_byte(k_1, k_2); + self.v[x as usize] = rand::random::() & kk; } - fn drw_vx_vy_nib(&mut self, x: u16, y: u16, nib: u16) { + fn draw(&mut self, x: u8, y: u8, n: u8) { // read n bytes from memory starting from self.i // then display them starting at (vx, vy) - let i = self.i as usize; let disp_x = self.v[x as usize]; let disp_y = self.v[y as usize]; let mut display_buf = self.display.buf; let mut pixel: u8; - self.v[0xF as usize] = 0; + self.v[0xF] = 0; // TODO: Rewrite Your Solution to better fit // the rest of your code base. // http://www.multigesture.net/articles/how-to-write-an-emulator-chip-8-interpreter/ - for i in 0..(nib as usize) { + for i in 0..(n as usize) { // height of sprite (y-axis) pixel = self.memory[self.i as usize + i]; for j in 0..8 { @@ -355,7 +358,7 @@ impl Chip8 { let index = (disp_x as usize) + j + (((disp_y as usize) + i) * 64); if display_buf[index] == 1 { - self.v[0xF as usize] = 1; + self.v[0xF] = 1; } display_buf[index] ^= 1; @@ -364,7 +367,7 @@ impl Chip8 { } } - fn skp_vx(&mut self, x: u16) { + fn skip_on_press(&mut self, x: u8) { // if current key is the same as the on in Vx // program counter is increased by 2 @@ -375,7 +378,7 @@ impl Chip8 { } } - fn sknp_vx(&mut self, x: u16) { + fn skip_not_pressed(&mut self, x: u8) { // if current key is not the sameas the one in Vx // increment the program counter by 2 @@ -389,12 +392,12 @@ impl Chip8 { } } - fn ld_vx_dt(&mut self, x: u16) { + fn copy_delay_timer_val(&mut self, x: u8) { // set Vx to be the value of the delay timer self.v[x as usize] = self.delay.get(); } - fn ld_vx_k(&mut self, x: u16) { + fn loop_until_key_vx(&mut self, x: u8) { // wait (blocking) until a key is pressed // once pressed, store in Vx @@ -404,27 +407,27 @@ impl Chip8 { } } - fn ld_dt_vx(&mut self, x: u16) { + fn set_delay_to_vx(&mut self, x: u8) { // set delay timer to be value of Vx self.delay.set(self.v[x as usize]); } - fn ld_st_vx(&mut self, x: u16) { + fn set_sound_to_vx(&mut self, x: u8) { // set sound timer to be value of Vx self.delay.set(self.v[x as usize]); } - fn add_i_vx(&mut self, x: u16) { + fn add_vx_to_i(&mut self, x: u8) { // set I to be I + Vx self.i = self.i + self.v[x as usize] as u16; } - fn ld_f_vx(&mut self, x: u16) { + fn set_i_to_hex_sprite_loc(&mut self, x: u8) { // set I to location of hex sprite related to Vx self.i = self.v[x as usize] as u16 * 5; } - fn ld_b_vx(&mut self, x: u16) { + fn copy_digits_to_i(&mut self, x: u8) { // take hundreds digit and place it at I // take tens digit and place it at I + 1 // take ones digit and place it at I + 2 @@ -440,7 +443,7 @@ impl Chip8 { self.memory[i + 2] = ones as u8; } - fn ld_i_vx(&mut self, x: u16) { + fn copy_from_vx_to_memory(&mut self, x: u8) { // copy values v0 -> Vx to memory starting at i for n in 0..=(x as usize) { @@ -448,7 +451,7 @@ impl Chip8 { } } - fn ld_vx_i(&mut self, x: u16) { + fn load_into_vx_from_i(&mut self, x: u8) { // read what will be values of v0 -> vx from memory starting at i let x = x as usize; @@ -457,14 +460,6 @@ impl Chip8 { } } - fn convert_to_byte(k_1: u16, k_2: u16) -> u8 { - ((k_1 as u8) << 4) | (k_2 as u8) - } - - fn convert_to_addr(n_1: u16, n_2: u16, n_3: u16) -> u16 { - (n_1 << 8) | (n_2 << 4) | n_3 - } - // https://stackoverflow.com/questions/41536479/how-do-i-split-an-integer-into-individual-digits fn digits(mut num: usize) -> impl Iterator { let mut divisor = 1; @@ -484,3 +479,77 @@ impl Chip8 { }) } } + +#[cfg(test)] +mod test { + use super::Chip8; + + #[test] + fn chip8_initializes_properly() { + let chip8: Chip8 = Default::default(); + assert_eq!(chip8.pc, 0x200); + + let slice: &[u8] = &chip8.memory[0..0x50]; + let font_set: &[u8] = &Chip8::FONT_SET; + + for (i, byte) in slice.iter().enumerate() { + assert_eq!(*byte, font_set[i]); + } + } + + #[test] + fn get_opcode_works() { + let mut chip8: Chip8 = Default::default(); + let expected = 0x00E0; // Clear Screen + + chip8.memory[0x202] = 0x00; + chip8.memory[0x203] = 0xE0; + chip8.pc = 0x202; + + assert_eq!(chip8.get_opcode(), expected); + } + + #[test] + fn get_nibs_works() { + let opcode = 0xA2F0; + + let (n1, n2, n3, n4) = Chip8::get_nibs(opcode); + + assert_eq!(n1, 0xA); + assert_eq!(n2, 0x2); + assert_eq!(n3, 0xF); + assert_eq!(n4, 0x0); + } + + #[test] + fn opcode_cls_works() { + let mut chip8: Chip8 = Default::default(); + + // Display is now noise + for byte in chip8.display.buf.iter_mut() { + *byte = rand::random(); + } + chip8.cls(); + // now check that everything in the display buffer is zeroed out + for byte in chip8.display.buf.iter() { + assert_eq!(*byte, 0); + } + } + + #[test] + fn opcode_ret_works() { + let mut chip8: Chip8 = Default::default(); + + chip8.pc = 0x202; + chip8.stack[0] = 0xABF0; + chip8.stack[1] = 0x50D5; // 80 + chip8.sp = 1; + + chip8.ret(); + + // Ret should set the programcouner to the address at the top of the stack + // and then decrement the stack pointer by 1 + assert_eq!(chip8.sp, 0); + assert_eq!(chip8.pc, 0x50D5); + } +} diff --git a/src/main.rs b/src/main.rs index b52db4f..3c5fbb9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,13 +3,12 @@ // for information about how to implement a CHIP-8 Emulator use chip8::emu::Chip8; use std::path::Path; -use std::time::Duration; -use pixels::{wgpu::Surface, Error, Pixels, SurfaceTexture}; +use pixels::{wgpu::Surface, Pixels, SurfaceTexture}; use winit::dpi::LogicalSize; use winit::event::{Event, VirtualKeyCode}; use winit::event_loop::{ControlFlow, EventLoop}; -use winit::window::WindowBuilder; +use winit::window::{Window, WindowBuilder}; use winit_input_helper::WinitInputHelper; static WIDTH: u32 = 64; @@ -17,57 +16,66 @@ static HEIGHT: u32 = 32; fn main() { let event_loop = EventLoop::new(); - let mut input = WinitInputHelper::new(); - let window = { - let size = LogicalSize::new(WIDTH as f64, HEIGHT as f64); - WindowBuilder::new() - .with_title("Chip8 Emulator") - .with_inner_size(size) - .with_min_inner_size(size) - .build(&event_loop) - .unwrap() - }; + let window = init_window(&event_loop); + let mut pixels = init_pixels(&window); let mut hidpi_factor = window.scale_factor(); - - let mut pixels = { - let surface = Surface::create(&window); - let surface_texture = SurfaceTexture::new(WIDTH, HEIGHT, surface); - Pixels::new(WIDTH, HEIGHT, surface_texture).unwrap() - }; + let mut input = WinitInputHelper::new(); let mut chip8: Chip8 = Default::default(); - chip8 .load_rom(Path::new("./games/test_opcode.ch8")) .expect("Unable to load ROM"); event_loop.run(move |event, _, control_flow| { - chip8.execute_cycle(); - - draw(&chip8.display.buf, pixels.get_frame()); - pixels.render().unwrap(); - window.request_redraw(); + if let Event::RedrawRequested(_) = event { + draw(&chip8.display.buf, pixels.get_frame()); + if pixels + .render() + .map_err(|e| eprintln!("pixels.render() failed: {}", e)) + .is_err() + { + *control_flow = ControlFlow::Exit; + return; + } + } if input.update(&event) { - if let Some(factor) = input.scale_factor_changed() { - hidpi_factor = factor; - }; - if input.key_pressed(VirtualKeyCode::Escape) || input.quit() { *control_flow = ControlFlow::Exit; return; } + if let Some(factor) = input.scale_factor_changed() { + hidpi_factor = factor; + }; + if let Some(size) = input.window_resized() { pixels.resize(size.width, size.height); } - } - // std::thread::sleep(Duration::from_millis(1000)); + chip8.execute_cycle(); + window.request_redraw(); + } }); } +fn init_pixels(window: &Window) -> Pixels { + let surface = Surface::create(window); + let texture = SurfaceTexture::new(WIDTH, HEIGHT, surface); + Pixels::new(WIDTH, HEIGHT, texture).unwrap() +} + +fn init_window(event_loop: &EventLoop<()>) -> Window { + let size = LogicalSize::new(WIDTH as f64, HEIGHT as f64); + WindowBuilder::new() + .with_title("Chip8 Emulator") + .with_inner_size(size) + .with_min_inner_size(size) + .build(event_loop) + .unwrap() +} + fn draw(chip8_gfx: &[u8], frame: &mut [u8]) { for (i, pixel) in frame.chunks_exact_mut(4).enumerate() { let rgba = if chip8_gfx[i] != 0 {