mod frame;
mod registers;
use core::panic;
use registers::data::Data;
use registers::write_toggle::WriteToggle;
use registers::{Register16, Register8};
use frame::Frame;
use registers::address::Address;
use registers::control::Control;
use registers::mask::Mask;
use registers::status::Status;
use registers::oam_address::OAMAddress;
#[rustfmt::skip]
pub static SYSTEM_PALETTE: [(u8,u8,u8); 64] = [
(0x80, 0x80, 0x80), (0x00, 0x3D, 0xA6), (0x00, 0x12, 0xB0), (0x44, 0x00, 0x96),
(0xA1, 0x00, 0x5E), (0xC7, 0x00, 0x28), (0xBA, 0x06, 0x00), (0x8C, 0x17, 0x00),
(0x5C, 0x2F, 0x00), (0x10, 0x45, 0x00), (0x05, 0x4A, 0x00), (0x00, 0x47, 0x2E),
(0x00, 0x41, 0x66), (0x00, 0x00, 0x00), (0x05, 0x05, 0x05), (0x05, 0x05, 0x05),
(0xC7, 0xC7, 0xC7), (0x00, 0x77, 0xFF), (0x21, 0x55, 0xFF), (0x82, 0x37, 0xFA),
(0xEB, 0x2F, 0xB5), (0xFF, 0x29, 0x50), (0xFF, 0x22, 0x00), (0xD6, 0x32, 0x00),
(0xC4, 0x62, 0x00), (0x35, 0x80, 0x00), (0x05, 0x8F, 0x00), (0x00, 0x8A, 0x55),
(0x00, 0x99, 0xCC), (0x21, 0x21, 0x21), (0x09, 0x09, 0x09), (0x09, 0x09, 0x09),
(0xFF, 0xFF, 0xFF), (0x0F, 0xD7, 0xFF), (0x69, 0xA2, 0xFF), (0xD4, 0x80, 0xFF),
(0xFF, 0x45, 0xF3), (0xFF, 0x61, 0x8B), (0xFF, 0x88, 0x33), (0xFF, 0x9C, 0x12),
(0xFA, 0xBC, 0x20), (0x9F, 0xE3, 0x0E), (0x2B, 0xF0, 0x35), (0x0C, 0xF0, 0xA4),
(0x05, 0xFB, 0xFF), (0x5E, 0x5E, 0x5E), (0x0D, 0x0D, 0x0D), (0x0D, 0x0D, 0x0D),
(0xFF, 0xFF, 0xFF), (0xA6, 0xFC, 0xFF), (0xB3, 0xEC, 0xFF), (0xDA, 0xAB, 0xEB),
(0xFF, 0xA8, 0xF9), (0xFF, 0xAB, 0xB3), (0xFF, 0xD2, 0xB0), (0xFF, 0xEF, 0xA6),
(0xFF, 0xF7, 0x9C), (0xD7, 0xE8, 0x95), (0xA6, 0xED, 0xAF), (0xA2, 0xF2, 0xDA),
(0x99, 0xFF, 0xFC), (0xDD, 0xDD, 0xDD), (0x11, 0x11, 0x11), (0x11, 0x11, 0x11)
];
pub struct PPU {
control: Control,
mask: Mask,
status: Status,
oamaddr: OAMAddress,
oamdata: u16,
ppuscroll: u16,
address: Address,
data: Data,
pub frame: Frame,
pub vram: [u8; 2048],
oam_ram: [u8; 256],
palette_ram: [u8; 32],
w: WriteToggle,
pub cycles: u16,
pub scanline: u16,
pub nmi_triggered: bool,
chr: Vec<u8>,
}
impl PPU {
pub fn new(chr: Vec<u8>) -> Self {
PPU {
control: Control::new(0b0000_0000),
mask: Mask::new(0b0000_0000),
status: Status::new(0b1010_0000),
oamaddr: OAMAddress::new(0b0000_0000),
oamdata: 0b0000_0000,
ppuscroll: 0b0000_0000,
address: Address::new(0x0000),
data: Data::new(0b0000_0000),
frame: Frame::new(),
vram: [0; 2048],
oam_ram: [0; 256],
palette_ram: [0; 32],
w: WriteToggle::FirstWrite,
cycles: 0,
scanline: 0,
nmi_triggered: false,
chr: chr,
}
}
pub fn write_control(&mut self, data: u8) {
let current_nmi_enable = self.control.nmi_enable();
self.control.write_u8(data);
let next_nmi_enable = self.control.nmi_enable();
if !current_nmi_enable && next_nmi_enable && self.status.is_in_v_blank() {
self.nmi_triggered = true;
}
}
pub fn read_status(&mut self) -> u8 {
let result = self.status.read_u8();
self.status.clear_v_blank();
return result;
}
pub fn write_oam_address(&mut self, data: u8) {
self.oamaddr.write_u8(data);
}
pub fn write_mask(&mut self, data: u8) {
self.mask.write_u8(data)
}
pub fn write_address(&mut self, data: u8) {
match self.w {
WriteToggle::FirstWrite => {
self.address.write_u16((data as u16) << 8);
}
WriteToggle::SecondWrite => {
self.address
.write_u16((self.address.read_u16() & 0xFF00) | (data as u16));
}
}
self.w.toggle();
}
pub fn read_data(&mut self) -> u8 {
let result = self.data.read_u8();
self.data
.write_u8(self.mem_read_u8(self.address.read_u16()));
self.increment_address();
return result;
}
fn mem_read_u8(&self, address: u16) -> u8 {
match address {
0x0000..=0x1FFF => self.chr[address as usize],
0x2000..=0x2FFF => self.vram[(address & 0b10011111111111 - 0x2000) as usize],
0x3000..=0x3EFF => panic!("Can't access address {}", address),
0x3F00..=0x3FFF => {
let mirror_down_address = address & 0b11111100011111;
self.palette_ram[(mirror_down_address - 0x3f00) as usize]
}
_ => panic!("Address {} is out of bounds", address),
}
}
pub fn write_data(&mut self, data: u8) {
self.mem_write_u8(self.address.read_u16(), data);
self.increment_address();
}
pub fn dma_write(&mut self, data: &[u8]) {
assert!(data.len() == 256);
self.oam_ram.clone_from_slice(data);
}
fn mem_write_u8(&mut self, address: u16, data: u8) {
match address {
0x000..=0x1FFF => panic!("CHR Rom is read-only."),
0x2000..=0x2FFF => self.vram[(address & 0b10011111111111 - 0x2000) as usize] = data,
0x3000..=0x3EFF => panic!("Can't access address {}", address),
0x3F00..=0x3FFF => {
let mirror_down_address = address & 0b11111100011111;
self.palette_ram[(mirror_down_address - 0x3f00) as usize] = data;
}
_ => panic!("Address {} is out of bounds", address),
}
}
fn increment_address(&mut self) {
self.address.write_u16(
self.address
.read_u16()
.wrapping_add(self.control.vram_increment()),
);
}
pub fn tick(&mut self) {
match self.cycles {
0 => (),
1..=256 => {
}
257..=340 => (),
_ => {
self.cycles = self.cycles - 341;
self.scanline += 1;
if self.scanline == 240 {
self.render_frame();
}
if self.scanline == 241 {
self.status.set_v_blank();
if self.control.nmi_enable() {
self.nmi_triggered = true;
}
}
if self.scanline == 261 {
{
self.scanline = 0;
}
}
}
}
self.cycles += 1;
}
fn render_frame(&mut self) {
self.render_background();
self.render_sprites();
}
fn render_background(&mut self) {
let bank = (self.control.background_pattern_table_address() as u16) * 0x1000;
assert!(bank == 0 || bank == 0x1000);
for x in 0..256 {
for y in 0..240 {
self.render_pixel(x, y, bank);
}
}
}
fn render_pixel(&mut self, x: u16, y: u16, bank: u16) {
let nametable_x = x / 8;
let nametable_y = y / 8;
let nametable_base = self.control.nametable_base();
let nametable_index = nametable_base + nametable_x + nametable_y * 32;
assert!(nametable_index - nametable_base < 0x400);
let nametable_byte = self.mem_read_u8(nametable_index) as u16;
let tile: &[u8] = &self.chr
[(bank + nametable_byte * 16) as usize..=(bank + nametable_byte * 16 + 15) as usize];
let shift = vec![7, 6, 5, 4, 3, 2, 1, 0];
let upper = tile[(y % 8) as usize] >> (shift[(x % 8) as usize]);
let lower = tile[((y % 8) + 8) as usize] >> (shift[(x % 8) as usize]);
let palette_index = (1 & lower) << 1 | (1 & upper);
let background_palette = self.background_palette(x, y);
let rgb = SYSTEM_PALETTE[background_palette[palette_index as usize] as usize];
self.frame.set_pixel(x as usize, y as usize, rgb);
}
fn background_palette(&self, x: u16, y: u16) -> [u8; 4] {
const ATTRIBUTE_TABLE_OFFSET: u16 = 0x3C0;
let nametable_base = self.control.nametable_base();
let block_x = x / 32;
let block_y = y / 32;
assert!(block_x < 8);
assert!(block_y < 8);
let attribute_table_index = block_y * 8 + block_x;
assert!(attribute_table_index < 64);
let ram_addr = nametable_base + ATTRIBUTE_TABLE_OFFSET + attribute_table_index;
let attribute_table_byte: u8 = self.mem_read_u8(ram_addr);
let quadrant: (u16, u16) = ((x % 32) / 16, (y % 32) / 16);
let palette_base = match quadrant {
(0, 0) => attribute_table_byte & 0b11,
(1, 0) => attribute_table_byte >> 2 & 0b11,
(0, 1) => attribute_table_byte >> 4 & 0b11,
(1, 1) => attribute_table_byte >> 6 & 0b11,
_ => panic!("Impossible!"),
};
let palette_start = (palette_base as usize) * 4;
let result = [
self.palette_ram[palette_start],
self.palette_ram[(palette_start + 1) as usize],
self.palette_ram[(palette_start + 2) as usize],
self.palette_ram[(palette_start + 3) as usize],
];
return result;
}
fn render_sprites(&mut self) {
for i in (0..self.oam_ram.len()).step_by(4).rev() {
let tile_idx = self.oam_ram[i + 1] as u16;
let tile_x = self.oam_ram[i + 3] as usize;
let tile_y = self.oam_ram[i] as usize;
let flip_vertical = if self.oam_ram[i + 2] >> 7 & 1 == 1 {
true
} else {
false
};
let flip_horizontal = if self.oam_ram[i + 2] >> 6 & 1 == 1 {
true
} else {
false
};
let palette_ram_index = self.oam_ram[i + 2] & 0b11;
let sprite_palette = self.sprite_palette(palette_ram_index);
let bank: u16 = self.control.sprite_pattern_table_address();
assert!(bank == 0 || bank == 0x1000);
let tile =
&self.chr[(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, 1 => SYSTEM_PALETTE[sprite_palette[1] as usize],
2 => SYSTEM_PALETTE[sprite_palette[2] as usize],
3 => SYSTEM_PALETTE[sprite_palette[3] as usize],
_ => panic!("can't be"),
};
match (flip_horizontal, flip_vertical) {
(false, false) => self.frame.set_pixel(tile_x + x, tile_y + y, rgb),
(true, false) => self.frame.set_pixel(tile_x + 7 - x, tile_y + y, rgb),
(false, true) => self.frame.set_pixel(tile_x + x, tile_y + 7 - y, rgb),
(true, true) => self.frame.set_pixel(tile_x + 7 - x, tile_y + 7 - y, rgb),
}
}
}
}
}
fn sprite_palette(&self, palette_ram_index: u8) -> [u8; 4] {
let start = 0x11 + (palette_ram_index * 4) as usize;
return [
0,
self.palette_ram[start],
self.palette_ram[start + 1],
self.palette_ram[start + 2],
];
}
}
#[cfg(test)]
mod test_ppu {
use super::*;
use crate::ppu::registers::Register8;
#[test]
fn test_power_up_state() {
let ppu = PPU::new(vec![]);
assert_eq!(0b0000_0000, ppu.control.read_u8());
assert_eq!(0b0000_0000, ppu.mask.read_u8());
assert_eq!(0b1010_0000, ppu.status.read_u8());
assert_eq!(0b0000_0000, ppu.ppuscroll);
assert_eq!(0b0000_0000, ppu.oamaddr.read_u8());
assert_eq!(0x0000, ppu.address.read_u16());
assert_eq!(0b0000_0000, ppu.data.read_u8());
assert_eq!([0; 2048], ppu.vram)
}
}
#[test]
fn test_address_register_first_write() {
let mut ppu = PPU::new(vec![]);
ppu.write_address(0xAA);
assert_eq!(0xAA00, ppu.address.read_u16());
assert_eq!(WriteToggle::SecondWrite, ppu.w);
}
#[test]
fn test_address_register_second_write() {
let mut ppu = PPU::new(vec![]);
ppu.w = WriteToggle::SecondWrite;
ppu.write_address(0xAA);
assert_eq!(0x00AA, ppu.address.read_u16());
assert_eq!(WriteToggle::FirstWrite, ppu.w);
}