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)
+
+
+
+
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),
+ }
+ }
+ }
+ }
}