diff --git a/README.md b/README.md index 091d4a3..83dc3bc 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,25 @@ -# Nes Emulator +# NEXie(A basic NES Emulator!) This project was made in Rust and emulates the Nintendo Entertainment System including the CPU(MOS Technology 6502) and PPU. -At the moment, the MOS 6502 CPU and most of the PPU is complete. Background and Sprite data can be loaded and displayed, however no input nor scrolling is implemented(yet). +The CPU has been tested with `nestest` by kevtris and compared against the Nintendulator log(Look in trace.rs to see how the trace is created). + +At the moment, the Ricoh 2A03 CPU(based off the MOS 6502 CPU) and most of the PPU is complete. Input and vertical scrolling is complete in this version(tested with Pac-Man and Ice Climbers). Horizontal scrolling and APU(Audio Processing Unit) still needs to be implemented. ## Running the Emulator -To run, look inside `main.rs` and enter the path of the `.nes` file to run. Then, run `cargo run` to run the application. -## Pictures +To run, look inside `main.rs` and enter the path of the `.nes` file to run. Then, run `cargo run` to run the emulator! + +## Input +| Controller Input | Keyboard Input | +|------------------|-----------------| +| Left | Left Arrow Key | +| Right | Right Arrow Key | +| Up | Up Arrow Key | +| Down | Down Arrow Key | +| A | z | +| B | x | +| Start | Enter | +| Select | Space | +## Pictures/Demos(Tested with Ubuntu 24.04 in WSL) +

+ pacman + ice climbers +

diff --git a/pictures/ic.gif b/pictures/ic.gif new file mode 100644 index 0000000..f74f10c Binary files /dev/null and b/pictures/ic.gif differ diff --git a/pictures/pacman.gif b/pictures/pacman.gif new file mode 100644 index 0000000..d6e6cea Binary files /dev/null and b/pictures/pacman.gif differ diff --git a/src/bus.rs b/src/bus.rs index 9a5aca5..79634e2 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -11,11 +11,14 @@ pub struct Bus<'call> { pub ppu: PPU, pub cycles: usize, // Contains total amount of cpu cycles gameloop_callback: Box, // Box, pointer to heap ddata is managed by the box - controller1: Controller + controller1: Controller, } -impl <'a>Bus<'a> { - pub fn new<'call, F>(rom: Rom, gameloop_callback: F) -> Bus<'call> where F: FnMut(&PPU, &mut Controller) + 'call,{ +impl<'a> Bus<'a> { + pub fn new<'call, F>(rom: Rom, gameloop_callback: F) -> Bus<'call> + where + F: FnMut(&PPU, &mut Controller) + 'call, + { let ppu = PPU::new(rom.chr_rom, rom.screen_mirroring); Bus { cpu_vram: [0; 2048], @@ -23,7 +26,7 @@ impl <'a>Bus<'a> { ppu: ppu, cycles: 7, // Starting with 7 clock cycles gameloop_callback: Box::from(gameloop_callback), - controller1: Controller::new() + controller1: Controller::new(), } } fn read_prg_rom(&self, mut addr: u16) -> u8 { @@ -49,7 +52,6 @@ impl <'a>Bus<'a> { pub fn poll_nmi_status(&mut self) -> Option { self.ppu.nmi_interrupt.take() } - } const RAM: u16 = 0x0000; @@ -74,9 +76,7 @@ impl Mem for Bus<'_> { } 0x2002 => self.ppu.read_status(), 0x2004 => self.ppu.read_oam_data(), - 0x2007 => { - self.ppu.read_data() - }, + 0x2007 => self.ppu.read_data(), 0x2008..=PPU_REGISTERS_MIRRORS_END => { // Mirroring down to 0x2000 to 0x2007 let mirror_down_addr = addr & 0x2007; @@ -87,11 +87,9 @@ impl Mem for Bus<'_> { 0 } - 0x4016 =>{ - self.controller1.read() - } + 0x4016 => self.controller1.read(), - 0x4017 =>{ + 0x4017 => { // Controller 2 0 } @@ -144,7 +142,7 @@ impl Mem for Bus<'_> { } 0x4000..=0x4013 | 0x4015 => { - //ignore APU + //ignore APU } 0x4016 => { diff --git a/src/controller.rs b/src/controller.rs index d80509c..6657f05 100644 --- a/src/controller.rs +++ b/src/controller.rs @@ -3,7 +3,7 @@ use bitflags::bitflags; -bitflags!{ +bitflags! { #[derive(Copy, Clone, Debug)] pub struct ControllerButton: u8{ const RIGHT = 0b1000_0000; @@ -17,45 +17,41 @@ bitflags!{ } } -pub struct Controller{ +pub struct Controller { strobe: bool, // Determines if we are writing input or leaving the read button_index: u8, - button_status: ControllerButton + button_status: ControllerButton, } -impl Controller{ +impl Controller { pub fn new() -> Self { - Controller{ + Controller { strobe: false, button_index: 0, button_status: ControllerButton::from_bits_truncate(0b0000_0000), } } - pub fn write(&mut self, data: u8){ - println!("writing controller!"); + pub fn write(&mut self, data: u8) { self.strobe = data & 1 == 1; - if self.strobe{ - println!("resetting strobe!"); + if self.strobe { // Starts at the first index self.button_index = 0 } } - pub fn read(&mut self) -> u8{ - println!("reading controller!"); - if self.button_index > 7{ + pub fn read(&mut self) -> u8 { + if self.button_index > 7 { return 1; // Indicates that there isn't anything left ot read } let response = (self.button_status.bits() & (1 << self.button_index)) >> self.button_index; - if !self.strobe && self.button_index <= 7{ + if !self.strobe && self.button_index <= 7 { self.button_index += 1; // Increments the index for the next read } - println!("Response is {:x}", response); response } - pub fn set_button_pressed_status(&mut self, button: ControllerButton, input: bool){ + pub fn set_button_pressed_status(&mut self, button: ControllerButton, input: bool) { println!("Set status to {:?}", button); self.button_status.set(button, input); } diff --git a/src/cpu.rs b/src/cpu.rs index 0177d17..c3302f5 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -1,11 +1,11 @@ -use crate::{bus::Bus}; +use crate::bus::Bus; use log::trace; use bitflags::bitflags; use core::panic; -use log::{debug}; -use std::{fmt}; +use log::debug; +use std::fmt; type Byte = u8; @@ -198,10 +198,9 @@ impl<'a> CPU<'a> { } } - // If nmi interrupt is encountered - - fn interrupt_nmi(&mut self){ + + fn interrupt_nmi(&mut self) { self.stack_push_u16(self.pc); // Push PC and Status flag on stack let mut flag = self.flags.clone(); flag.set(CpuFlags::BREAK, false); @@ -223,12 +222,12 @@ impl<'a> CPU<'a> { F: FnMut(&mut CPU), { loop { - if let Some(_nmi) = self.bus.poll_nmi_status(){ + if let Some(_nmi) = self.bus.poll_nmi_status() { println!("nmi triggered!"); self.interrupt_nmi(); } - if self.halted{ + if self.halted { println!("Got EOF signal! Exiting program..."); break; } @@ -1098,23 +1097,15 @@ impl<'a> CPU<'a> { let addr = self.get_operand_address(&mode); // Memory location of the value to extract self.g1_cycles(&mode, addr, aaa == 4); // Adds cycles based on addressing mode, if aaa is 4, we're dealing with STA match aaa { - 0 => - self.ora(addr), - 1 => - self.and(addr), - 2 => - self.eor(addr), - 3 => - self.adc(addr), - 4 => - self.sta(addr), - - 5 => - self.lda(addr), - 6 => - self.cmp(addr), - 7 => - self.sbc(addr), + 0 => self.ora(addr), + 1 => self.and(addr), + 2 => self.eor(addr), + 3 => self.adc(addr), + 4 => self.sta(addr), + + 5 => self.lda(addr), + 6 => self.cmp(addr), + 7 => self.sbc(addr), _ => unimplemented!("aaa"), }; debug!("g1 the flags are {:#X}", self.flags.bits()); @@ -1376,12 +1367,14 @@ impl<'a> CPU<'a> { self.pc = self.pc.wrapping_add(jump as u16); // NOTE For cycles, we add an additional 1 to emulate for last pc at the end of run(this does not edit the current pc value) let new_page = self.pc.wrapping_add(1) >> 8; - debug!("branch: old_page is {:2X}, new_page is {:2X}", old_page, new_page); + debug!( + "branch: old_page is {:2X}, new_page is {:2X}", + old_page, new_page + ); if old_page != new_page { self.add_cycles(1); } - //println!("Finished branch, pc is now on {:#x}", self.pc); } diff --git a/src/frame.rs b/src/frame.rs index c399841..60d5fb8 100644 --- a/src/frame.rs +++ b/src/frame.rs @@ -1,23 +1,23 @@ pub struct Frame { - pub data: Vec, + pub data: Vec, } impl Frame { - const WIDTH: usize = 256; - const HIGHT: usize = 240; + const WIDTH: usize = 256; + const HIGHT: usize = 240; - pub fn new() -> Self { - Frame { - data: vec![0; (Frame::WIDTH) * (Frame::HIGHT) * 3], - } - } + pub fn new() -> Self { + Frame { + data: vec![0; (Frame::WIDTH) * (Frame::HIGHT) * 3], + } + } - pub fn set_pixel(&mut self, x: usize, y: usize, rgb: (u8, u8, u8)) { - let base = y * 3 * Frame::WIDTH + x * 3; - if base + 2 < self.data.len() { - self.data[base] = rgb.0; - self.data[base + 1] = rgb.1; - self.data[base + 2] = rgb.2; - } - } + pub fn set_pixel(&mut self, x: usize, y: usize, rgb: (u8, u8, u8)) { + let base = y * 3 * Frame::WIDTH + x * 3; + if base + 2 < self.data.len() { + self.data[base] = rgb.0; + self.data[base + 1] = rgb.1; + self.data[base + 2] = rgb.2; + } + } } diff --git a/src/lib.rs b/src/lib.rs index 1b16c1e..17a8c8d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,22 +1,22 @@ pub mod bus; +pub mod controller; pub mod cpu; +pub mod frame; pub mod op; +pub mod palette; pub mod ppu; pub mod ppu_reg; +pub mod render; pub mod rom; pub mod trace; -pub mod frame; -pub mod palette; -pub mod render; -pub mod controller; use cpu::*; +use frame::Frame; +use ppu::PPU; use rom::Rom; use sdl2::{event::Event, keyboard::Keycode, pixels::PixelFormatEnum}; use trace::trace; -use frame::Frame; -use ppu::PPU; use crate::bus::Bus; #[macro_export] @@ -29,7 +29,6 @@ macro_rules! print_title { }; } - // Used with NES test, removing for now due to polishing state of ppu and controllers // pub fn run_nestest_and_capture() -> Vec { // let sdl_context = sdl2::init().unwrap(); diff --git a/src/main.rs b/src/main.rs index e0777c0..810dfaa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,11 @@ -use std::collections::HashMap; use nes::controller::{self, ControllerButton}; use nes::cpu::*; +use std::collections::HashMap; use nes::frame::Frame; +use nes::ppu::PPU; use nes::render; use nes::rom::Rom; -use nes::ppu::PPU; use sdl2::event::Event; use sdl2::keyboard::Keycode; use sdl2::pixels::PixelFormatEnum; @@ -52,42 +52,45 @@ fn main() { input_map.insert(Keycode::Space, ControllerButton::SELECT); // Game loading and CPU setup - let game_bytes = std::fs::read("roms/pacman.nes").unwrap(); + let game_bytes = std::fs::read("PATH GOES HERE").unwrap(); let rom = Rom::new(&game_bytes).unwrap(); let mut frame = Frame::new(); - - let bus = Bus::new(rom, move |ppu:&PPU, controller: &mut controller::Controller|{ - render::render(ppu, &mut frame); - texture.update(None, &frame.data, 256 * 3).unwrap(); - canvas.copy(&texture, None, None).unwrap(); + let bus = Bus::new( + rom, + move |ppu: &PPU, controller: &mut controller::Controller| { + render::render(ppu, &mut frame); + texture.update(None, &frame.data, 256 * 3).unwrap(); + + canvas.copy(&texture, None, None).unwrap(); - canvas.present(); - for event in event_pump.poll_iter() { - match event { - Event::Quit { .. } - | Event::KeyDown { - keycode: Some(Keycode::Escape), - .. - } => std::process::exit(0), - Event::KeyDown { keycode, .. } => { - if let Some(key) = input_map.get(&keycode.unwrap_or(Keycode::Ampersand)) { - println!("Pressed button!"); - controller.set_button_pressed_status(*key, true); - } - } - Event::KeyUp { keycode, .. } => { - if let Some(key) = input_map.get(&keycode.unwrap_or(Keycode::Ampersand)) { - println!("Released button!"); - controller.set_button_pressed_status(*key, false); - } - } + canvas.present(); + for event in event_pump.poll_iter() { + match event { + Event::Quit { .. } + | Event::KeyDown { + keycode: Some(Keycode::Escape), + .. + } => std::process::exit(0), + Event::KeyDown { keycode, .. } => { + if let Some(key) = input_map.get(&keycode.unwrap_or(Keycode::Ampersand)) { + // println!("Pressed button!"); + controller.set_button_pressed_status(*key, true); + } + } + Event::KeyUp { keycode, .. } => { + if let Some(key) = input_map.get(&keycode.unwrap_or(Keycode::Ampersand)) { + // println!("Released button!"); + controller.set_button_pressed_status(*key, false); + } + } - _ => { /* do nothing */ } - } - } - }); + _ => { /* do nothing */ } + } + } + }, + ); let mut cpu = CPU::new(bus); cpu.reset(); // let mut screen_state = [0 as u8; 32 * 3 * 32]; @@ -96,4 +99,4 @@ fn main() { // println!("{}", trace(cpu)); // }); cpu.run(); - } +} diff --git a/src/ppu.rs b/src/ppu.rs index 706fc21..01842b4 100644 --- a/src/ppu.rs +++ b/src/ppu.rs @@ -22,7 +22,7 @@ pub struct PPU { pub scanline: u16, // Which scanline should be drawn pub cycles: usize, // Location of current cycle - pub nmi_interrupt: Option + pub nmi_interrupt: Option, } impl PPU { @@ -46,20 +46,28 @@ impl PPU { mirroring: mirroring, scanline: 0, cycles: 21, // PPU starts with 3 times the cycles of CPU(which is 7) - nmi_interrupt: None + nmi_interrupt: None, } } + fn is_sprite_zero_hit(&self, cycle: usize) -> bool { + // + let y = self.oam_data[0] as usize; + let x = self.oam_data[3] as usize; + (y == self.scanline as usize) && x <= cycle && self.mask.show_sprites() + } + pub fn tick(&mut self, cycles: u8) -> bool { self.cycles += cycles as usize; - // Scanline lasts for 341 PPU cycles if self.cycles >= 341 { - self.cycles -= 341; + if self.is_sprite_zero_hit(self.cycles) { + self.status.set_sprite_zero_hit(true); + } + + self.cycles = self.cycles - 341; self.scanline += 1; - // Scanline is on vBlank line if self.scanline == 241 { - // Enabling causes NMI interrupt to be called at start of vblank self.status.set_vblank_status(true); self.status.set_sprite_zero_hit(false); if self.ctrl.generate_vblank_nmi() { @@ -75,7 +83,7 @@ impl PPU { return true; } } - return false; // Full render is not finished + return false; } // 0x2000 write, PPUCTRL(Flags) @@ -136,7 +144,10 @@ impl PPU { let addr = self.addr.get(); println!("Writing to address {:x} with value {:x}", addr, val); match addr { - 0..=0x1fff => println!("Attempt to write to character rom space, writing to {:4X}!", addr), + 0..=0x1fff => println!( + "Attempt to write to character rom space, writing to {:4X}!", + addr + ), 0x2000..=0x2FFF => { // Name tables self.vram[self.mirror_vram_addr(addr) as usize] = val; @@ -147,18 +158,16 @@ impl PPU { let add_mirror = addr - 0x10; self.palette_table[(add_mirror - 0x3f00) as usize] = val; } - 0x3F00..=0x3FFF => - { + 0x3F00..=0x3FFF => { println!("Writing to palette table {:x}", addr); self.palette_table[(addr - 0x3f00) as usize] = val - }, + } _ => panic!("Unknown write access to mirrored space {}", addr), } self.increment_vram_addr(); } - fn increment_vram_addr(&mut self) { println!("incremented addr!"); self.addr.increment(self.ctrl.vram_addr_increment()); @@ -186,7 +195,7 @@ impl PPU { // Unused "0x3000 to 0x3eff is not expected to be used, the requested address is {}", addr - ), //Addresses $3F10/$3F14/$3F18/$3F1C are mirrors of $3F00/$3F04/$3F08/$3F0C + ), //Addresses $3F10/$3F14/$3F18/$3F1C are mirrors of $3F00/$3F04/$3F08/$3F0C 0x3f10 | 0x3f14 | 0x3f18 | 0x3f1c => { let add_mirror = addr - 0x10; self.palette_table[(add_mirror - 0x3f00) as usize] diff --git a/src/ppu_reg/addrreg.rs b/src/ppu_reg/addrreg.rs index 199f931..901c4df 100644 --- a/src/ppu_reg/addrreg.rs +++ b/src/ppu_reg/addrreg.rs @@ -31,7 +31,8 @@ impl AddrRegister { self.value.1 = data; } - if self.get() > 0x3fff { //mirror down addr above 0x3fff + if self.get() > 0x3fff { + //mirror down addr above 0x3fff self.set(self.get() & 0b11111111111111); //mirror down addr above 0x3fff } self.hi_ptr = !self.hi_ptr; diff --git a/src/ppu_reg/controlreg.rs b/src/ppu_reg/controlreg.rs index 98a6d5d..ddda846 100644 --- a/src/ppu_reg/controlreg.rs +++ b/src/ppu_reg/controlreg.rs @@ -43,11 +43,10 @@ impl ControlRegister { } } - pub fn bknd_pattern_addr(&self) -> u16{ - if self.contains(ControlRegister::BACKGROUND_PATTERN_ADDR){ + pub fn bknd_pattern_addr(&self) -> u16 { + if self.contains(ControlRegister::BACKGROUND_PATTERN_ADDR) { 0x1000 - } - else{ + } else { 0 } } @@ -56,7 +55,17 @@ impl ControlRegister { *self = ControlRegister::from_bits_truncate(data); } - + pub fn nametable_addr(&self) -> u16 { + let mut addr = 0x2000; + if self.contains(ControlRegister::NAMETABLE1) { + addr |= 0x400; + } + if self.contains(ControlRegister::NAMETABLE2) { + addr |= 0x800; + } + addr + } + pub fn sprt_pattern_addr(&self) -> u16 { if !self.contains(ControlRegister::SPRITE_PATTERN_ADDR) { 0 // Sprite table 0 @@ -67,6 +76,5 @@ impl ControlRegister { pub fn generate_vblank_nmi(&self) -> bool { self.contains(ControlRegister::GENERATE_NMI) - } - + } } diff --git a/src/ppu_reg/maskreg.rs b/src/ppu_reg/maskreg.rs index 37af0bc..fb37405 100644 --- a/src/ppu_reg/maskreg.rs +++ b/src/ppu_reg/maskreg.rs @@ -33,4 +33,8 @@ impl MaskRegister { pub fn update(&mut self, data: u8) { *self = MaskRegister::from_bits_truncate(data); } + + pub fn show_sprites(&self) -> bool { + self.contains(MaskRegister::SPRITE) + } } diff --git a/src/ppu_reg/statusreg.rs b/src/ppu_reg/statusreg.rs index bd8dc94..1bd16e1 100644 --- a/src/ppu_reg/statusreg.rs +++ b/src/ppu_reg/statusreg.rs @@ -38,13 +38,13 @@ impl StatusRegister { } else { self.remove(StatusRegister::VBLANK); } - } + } pub fn set_sprite_zero_hit(&mut self, status: bool) { self.set(StatusRegister::SPRITE_ZERO_HIT, status); } - pub fn is_in_vblank(&mut self) -> bool{ + pub fn is_in_vblank(&mut self) -> bool { self.contains(StatusRegister::VBLANK) } diff --git a/src/render.rs b/src/render.rs index d3ea954..c16cc3d 100644 --- a/src/render.rs +++ b/src/render.rs @@ -1,25 +1,100 @@ use core::panic; -use crate::ppu::PPU; use crate::frame::Frame; use crate::palette; +use crate::ppu::PPU; +use crate::rom::Mirroring; + +// This file is responsible for rendering the background and sprites + +struct Rect { + x1: usize, + y1: usize, + x2: usize, + y2: usize, +} + +impl Rect { + fn new(x1: usize, y1: usize, x2: usize, y2: usize) -> Self { + Rect { x1, y1, x2, y2 } + } +} + +fn render_name_table( + ppu: &PPU, + frame: &mut Frame, + name_table: &[u8], + view_port: Rect, + shift_x: isize, + shift_y: isize, +) { + let bank = ppu.ctrl.bknd_pattern_addr(); + + let attribute_table = &name_table[0x3c0..0x400]; + + for i in 0..0x3c0 { + let tile_column = i % 32; + let tile_row = i / 32; + let tile_idx = name_table[i] as u16; + let tile = + &ppu.chr_rom[(bank + tile_idx * 16) as usize..=(bank + tile_idx * 16 + 15) as usize]; + let palette = bg_palette(ppu, attribute_table, tile_column, tile_row); + + for y in 0..=7 { + let mut upper = tile[y]; + let mut lower = tile[y + 8]; + + for x in (0..=7).rev() { + let value = (1 & lower) << 1 | (1 & upper); + upper = upper >> 1; + lower = lower >> 1; + let rgb = match value { + 0 => palette::SYSTEM_PALLETE[ppu.palette_table[0] as usize], + 1 => palette::SYSTEM_PALLETE[palette[1] as usize], + 2 => palette::SYSTEM_PALLETE[palette[2] as usize], + 3 => palette::SYSTEM_PALLETE[palette[3] as usize], + _ => panic!("can't be"), + }; + let pixel_x = tile_column * 8 + x; + let pixel_y = tile_row * 8 + y; + + if pixel_x >= view_port.x1 + && pixel_x < view_port.x2 + && pixel_y >= view_port.y1 + && pixel_y < view_port.y2 + { + frame.set_pixel( + (shift_x + pixel_x as isize) as usize, + (shift_y + pixel_y as isize) as usize, + rgb, + ); + } + } + } + } +} // Renders the palette for a background tile -fn bg_palette(ppu: &PPU, tile_col: usize, tile_row: usize) -> [u8;4]{ - let attr_table_idx = tile_row / 4 * 8 + tile_col / 4; // 8 columns in attribute table to get index - let attr_byte = ppu.vram[0x3C0 + attr_table_idx]; // Using nametable 0 +fn bg_palette(ppu: &PPU, attribute_table: &[u8], tile_col: usize, tile_row: usize) -> [u8; 4] { + let attr_table_idx = tile_row / 4 * 8 + tile_col / 4; // 8 columns in attribute table to get index + let attr_byte = attribute_table[attr_table_idx]; // Using nametable specified by attribute table // Extracting pallet index quadrant in 4 x 4 tile - let pallet_idx = match (tile_col % 4 / 2, tile_row % 4 / 2){ + let pallet_idx = match (tile_col % 4 / 2, tile_row % 4 / 2) { (0, 0) => attr_byte & 0b11, (1, 0) => (attr_byte >> 2) & 0b11, (0, 1) => (attr_byte >> 4) & 0b11, (1, 1) => (attr_byte >> 6) & 0b11, - _ => panic!("bg_palette, shouldn't reach here!") + _ => panic!("bg_palette, shouldn't reach here!"), }; let pallete_start: usize = 1 + (pallet_idx as usize) * 4; // Take index and determine palette for the quadrant - // 4 palletes extracted with 1st one being universal - [ppu.palette_table[0], ppu.palette_table[pallete_start], ppu.palette_table[pallete_start+1], ppu.palette_table[pallete_start+2]] + // 4 palletes extracted with 1st one being universal + [ + ppu.palette_table[0], + ppu.palette_table[pallete_start], + ppu.palette_table[pallete_start + 1], + ppu.palette_table[pallete_start + 2], + ] } // Renders the sprites for a given pallete index @@ -34,83 +109,91 @@ fn sprite_palette(ppu: &PPU, pallete_idx: u8) -> [u8; 4] { } pub fn render(ppu: &PPU, frame: &mut Frame) { - let bank = ppu.ctrl.bknd_pattern_addr(); - - - // Background - for i in 0..0x03c0 { // just for now, lets use the first nametable - let tile = ppu.vram[i] as u16; - let tile_col = i % 32; - let tile_row = i / 32; - let tile = &ppu.chr_rom[(bank + tile * 16) as usize..=(bank + tile * 16 + 15) as usize]; - let palette = bg_palette(ppu, tile_col, tile_row); - - for y in 0..=7 { - let mut upper = tile[y]; - let mut lower = tile[y + 8]; - - for x in (0..=7).rev() { - let value = (1 & lower) << 1 | (1 & upper); - upper = upper >> 1; - lower = lower >> 1; - let rgb = match value { - 0 => palette::SYSTEM_PALLETE[ppu.palette_table[0] as usize], - 1 => palette::SYSTEM_PALLETE[palette[1] as usize], - 2 => palette::SYSTEM_PALLETE[palette[2] as usize], - 3 => palette::SYSTEM_PALLETE[palette[3] as usize], - _ => panic!("can't be"), - }; - frame.set_pixel(tile_col * 8 + x, tile_row * 8 + y, rgb) - } + // Rendering Background + let scroll_x = (ppu.scroll.scroll_x) as usize; + let scroll_y = (ppu.scroll.scroll_y) as usize; + + let (main_nametable, second_nametable) = match (&ppu.mirroring, ppu.ctrl.nametable_addr()) { + (Mirroring::VERTICAL, 0x2000) | (Mirroring::VERTICAL, 0x2800) | (Mirroring::HORIZONTAL, 0x2000) | (Mirroring::HORIZONTAL, 0x2400) => { + (&ppu.vram[0..0x400], &ppu.vram[0x400..0x800]) } - } + (Mirroring::VERTICAL, 0x2400) | (Mirroring::VERTICAL, 0x2C00) | (Mirroring::HORIZONTAL, 0x2800) | (Mirroring::HORIZONTAL, 0x2C00) => { + ( &ppu.vram[0x400..0x800], &ppu.vram[0..0x400]) + } + (_,_) => { + panic!("Not supported mirroring type {:?}", ppu.mirroring); + } + }; - // Sprites - // Iterates through the OAM to list up to 64 bytes + render_name_table( + ppu, + frame, + main_nametable, + Rect::new(scroll_x, scroll_y, 256, 240), + -(scroll_x as isize), + -(scroll_y as isize), + ); + + if scroll_x > 0 { + render_name_table(ppu, frame, + second_nametable, + Rect::new(0, 0, scroll_x, 240), + (256 - scroll_x) as isize, 0 + ); + } else if scroll_y > 0 { + render_name_table(ppu, frame, + second_nametable, + Rect::new(0, 0, 256, scroll_y), + 0, (240 - scroll_y) as isize + ); + } + + // Sprites + // Iterates through the OAM to list up to 64 bytes for i in (0..ppu.oam_data.len()).step_by(4).rev() { - let tile_idx = ppu.oam_data[i + 1] as u16; // Byte 1 - let tile_x = ppu.oam_data[i + 3] as usize; // Byte 3 - let tile_y = ppu.oam_data[i] as usize; //Byte 0 - - let flip_vertical = if ppu.oam_data[i + 2] >> 7 & 1 == 1 { - true - } else { - false - }; - let flip_horizontal = if ppu.oam_data[i + 2] >> 6 & 1 == 1 { - true - } else { - false - }; - let pallette_idx = ppu.oam_data[i + 2] & 0b11; - let sprite_palette = sprite_palette(ppu, pallette_idx); - - let bank: u16 = ppu.ctrl.sprt_pattern_addr(); - - let tile = &ppu.chr_rom[(bank + tile_idx * 16) as usize..=(bank + tile_idx * 16 + 15) as usize]; - - - for y in 0..=7 { - let mut upper = tile[y]; - let mut lower = tile[y + 8]; - 'ololo: for x in (0..=7).rev() { - let value = (1 & lower) << 1 | (1 & upper); - upper = upper >> 1; - lower = lower >> 1; - let rgb = match value { - 0 => continue 'ololo, // skip coloring the pixel - 1 => palette::SYSTEM_PALLETE[sprite_palette[1] as usize], - 2 => palette::SYSTEM_PALLETE[sprite_palette[2] as usize], - 3 => palette::SYSTEM_PALLETE[sprite_palette[3] as usize], - _ => panic!("can't be"), - }; - match (flip_horizontal, flip_vertical) { - (false, false) => frame.set_pixel(tile_x + x, tile_y + y, rgb), - (true, false) => frame.set_pixel(tile_x + 7 - x, tile_y + y, rgb), - (false, true) => frame.set_pixel(tile_x + x, tile_y + 7 - y, rgb), - (true, true) => frame.set_pixel(tile_x + 7 - x, tile_y + 7 - y, rgb), - } - } - } - } + let tile_idx = ppu.oam_data[i + 1] as u16; // Byte 1 + let tile_x = ppu.oam_data[i + 3] as usize; // Byte 3 + let tile_y = ppu.oam_data[i] as usize; //Byte 0 + + let flip_vertical = if ppu.oam_data[i + 2] >> 7 & 1 == 1 { + true + } else { + false + }; + let flip_horizontal = if ppu.oam_data[i + 2] >> 6 & 1 == 1 { + true + } else { + false + }; + let pallette_idx = ppu.oam_data[i + 2] & 0b11; + let sprite_palette = sprite_palette(ppu, pallette_idx); + + let bank: u16 = ppu.ctrl.sprt_pattern_addr(); + + let tile = + &ppu.chr_rom[(bank + tile_idx * 16) as usize..=(bank + tile_idx * 16 + 15) as usize]; + + for y in 0..=7 { + let mut upper = tile[y]; + let mut lower = tile[y + 8]; + 'ololo: for x in (0..=7).rev() { + let value = (1 & lower) << 1 | (1 & upper); + upper = upper >> 1; + lower = lower >> 1; + let rgb = match value { + 0 => continue 'ololo, // skip coloring the pixel + 1 => palette::SYSTEM_PALLETE[sprite_palette[1] as usize], + 2 => palette::SYSTEM_PALLETE[sprite_palette[2] as usize], + 3 => palette::SYSTEM_PALLETE[sprite_palette[3] as usize], + _ => panic!("can't be"), + }; + match (flip_horizontal, flip_vertical) { + (false, false) => frame.set_pixel(tile_x + x, tile_y + y, rgb), + (true, false) => frame.set_pixel(tile_x + 7 - x, tile_y + y, rgb), + (false, true) => frame.set_pixel(tile_x + x, tile_y + 7 - y, rgb), + (true, true) => frame.set_pixel(tile_x + 7 - x, tile_y + 7 - y, rgb), + } + } + } + } }