use crate::ppu::registers::ppu_control::PpuControlFlags;
use crate::ppu::registers::ppu_mask::PpuMaskFlags;
use crate::ppu::registers::ppu_status::PpuStatus;
use crate::ppu::registers::vram_addr::{
combine_coarse_fine_into_coord, separate_coarse_fine_from_coord,
};
use crate::ppu::registers::Registers;
use crate::ppu::Register;
use ringbuffer::RingBuffer;
impl Default for Registers {
fn default() -> Self {
let mut def = Self {
ppu_ctrl: Default::default(),
ppu_mask: Default::default(),
ppu_status: Default::default(),
oam_addr: 0,
oam_data: 0,
data: 0,
v: 0,
t: 0,
x: 0,
show_bg_buffer: Default::default(),
show_sprites_buffer: Default::default(),
write_second: false,
};
for _ in 0..def.show_bg_buffer.capacity() {
def.show_bg_buffer.push(false);
}
for _ in 0..def.show_sprites_buffer.capacity() {
def.show_sprites_buffer.push(false);
}
def
}
}
impl Registers {
pub fn reset(&mut self) {
*self = Default::default();
}
pub fn next_write_is_second_byte(&self) -> bool {
self.write_second
}
pub fn write_register(&mut self, value: u8, selected_reg: Register) {
match selected_reg {
Register::PpuControl => {
self.ppu_ctrl = PpuControlFlags::from_bits_truncate(value);
let mut t_components = self.t_addr_components();
t_components.nametable_index = value & 0b11;
self.t = t_components.build_vram_address();
}
Register::PpuMask => {
self.ppu_mask = PpuMaskFlags::from_bits_truncate(value);
}
Register::PpuStatus => {}
Register::OamAddr => self.oam_addr = value,
Register::OamData => {
self.oam_data = value;
self.oam_addr = self.oam_addr.wrapping_add(1);
}
Register::PpuScroll => {
let (coarse, fine) = separate_coarse_fine_from_coord(value);
let mut t_addr_components = self.t_addr_components();
if !self.write_second {
t_addr_components.coarse_x = coarse;
self.x = fine;
} else {
t_addr_components.coarse_y = coarse;
t_addr_components.fine_y = fine;
}
self.t = t_addr_components.build_vram_address();
self.write_second = !self.write_second;
}
Register::PpuAddr => {
if !self.write_second {
let high_byte = ((value & 0x3F) as u16) << 8;
let low_byte = self.t & 0xFF;
self.t = high_byte | low_byte;
} else {
let high_byte = self.t & 0xFF00;
let low_byte = value as u16;
self.t = high_byte | low_byte;
self.v = self.t;
}
self.write_second = !self.write_second;
}
Register::Data => {
self.data = value;
self.update_addr_after_data_access();
}
}
}
pub fn read_register(&mut self, selected_reg: Register) -> u8 {
match selected_reg {
Register::PpuControl => 0,
Register::PpuMask => 0,
Register::PpuStatus => {
let value = self.ppu_status.bits();
self.reset_addr_latch();
self.ppu_status.remove(PpuStatus::VBLANK_STARTED);
value
}
Register::OamAddr => 0,
Register::OamData => self.oam_data,
Register::PpuScroll => 0,
Register::PpuAddr => 0,
Register::Data => {
self.update_addr_after_data_access();
0 }
}
}
pub fn scroll(&self) -> (u8, u8) {
let vram_addr_components = self.t_addr_components();
let x = combine_coarse_fine_into_coord(vram_addr_components.coarse_x, self.x);
let y = combine_coarse_fine_into_coord(
vram_addr_components.coarse_y,
vram_addr_components.fine_y,
);
(x, y)
}
pub fn oam_addr(&self) -> u8 {
self.oam_addr
}
#[allow(dead_code)]
pub fn set_oam_addr(&mut self, addr: u8) {
self.oam_addr = addr;
}
pub fn vram_addr(&self) -> u16 {
self.v
}
pub fn set_data(&mut self, data: u8) {
self.data = data;
}
fn reset_addr_latch(&mut self) {
self.write_second = false;
}
fn update_addr_after_data_access(&mut self) {
let increment = if self.ppu_ctrl.contains(PpuControlFlags::INC_MODE) {
32
} else {
1
};
self.v = self.v.wrapping_add(increment);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn increment_vram_addresses_read_mode0() {
let mut expected_addr = 0u16;
let mut regs = Registers::default();
assert_eq!(regs.v, expected_addr);
for _ in 0..u16::MAX {
regs.read_register(Register::Data);
expected_addr = expected_addr.wrapping_add(1);
assert_eq!(regs.v, expected_addr);
}
}
#[test]
fn increment_vram_addresses_read_mode1() {
let mut expected_addr = 0u16;
let mut regs = Registers::default();
regs.ppu_ctrl.insert(PpuControlFlags::INC_MODE);
assert_eq!(regs.v, expected_addr);
for _ in 0..u16::MAX {
regs.read_register(Register::Data);
expected_addr = expected_addr.wrapping_add(32);
assert_eq!(regs.v, expected_addr);
}
}
#[test]
fn increment_vram_addresses_write_mode0() {
let mut expected_addr = 0u16;
let mut regs = Registers::default();
assert_eq!(regs.v, expected_addr);
for _ in 0..u16::MAX {
regs.write_register(0, Register::Data);
expected_addr = expected_addr.wrapping_add(1);
assert_eq!(regs.v, expected_addr);
}
}
#[test]
fn increment_vram_addresses_write_mode1() {
let mut expected_addr = 0u16;
let mut regs = Registers::default();
regs.ppu_ctrl.insert(PpuControlFlags::INC_MODE);
assert_eq!(regs.v, expected_addr);
for _ in 0..u16::MAX {
regs.write_register(0, Register::Data);
expected_addr = expected_addr.wrapping_add(32);
assert_eq!(regs.v, expected_addr);
}
}
fn ppu_addr_high(regs: &Registers) -> u8 {
(regs.t >> 8) as u8
}
fn ppu_addr_low(regs: &Registers) -> u8 {
regs.t as u8
}
#[test]
fn shared_latch_between_ppuaddr_and_ppuscroll() {
let mut regs = Registers::default();
regs.write_register(0xAB, Register::PpuAddr);
assert_eq!(ppu_addr_high(®s), 0xAB & 0x3F);
regs.write_register(0xCD, Register::PpuScroll);
assert_eq!(regs.scroll().1, 0xCD);
regs.write_register(0xEF, Register::PpuScroll);
assert_eq!(regs.scroll().0, 0xEF);
regs.write_register(0x12, Register::PpuAddr);
assert_eq!(ppu_addr_low(®s), 0x12);
let _ = regs.read_register(Register::PpuStatus);
regs.write_register(0x34, Register::PpuScroll);
assert_eq!(regs.scroll().0, 0x34);
let _ = regs.read_register(Register::PpuStatus);
regs.write_register(0x36, Register::PpuScroll);
assert_eq!(regs.scroll().0, 0x36);
}
#[test]
fn reset_addr_latch() {
let mut regs = Registers::default();
regs.write_register(0xCA, Register::PpuAddr);
regs.write_register(0xFE, Register::PpuAddr);
assert_eq!(regs.t, 0xCAFE & 0x3FFF);
regs.write_register(0xFE, Register::PpuAddr);
regs.write_register(0xCA, Register::PpuAddr);
assert_eq!(regs.t, 0xFECA & 0x3FFF);
regs.write_register(0xFE, Register::PpuAddr);
regs.reset_addr_latch();
regs.write_register(0xCA, Register::PpuAddr);
assert_eq!(regs.t, 0xCACA & 0x3FFF);
}
#[test]
fn reset_addr_latch_scroll() {
let mut regs = Registers::default();
regs.write_register(0xCA, Register::PpuScroll);
regs.write_register(0xFE, Register::PpuScroll);
assert_eq!(regs.scroll().0, 0xCA);
assert_eq!(regs.scroll().1, 0xFE);
regs.write_register(0xFE, Register::PpuScroll);
regs.write_register(0xCA, Register::PpuScroll);
assert_eq!(regs.scroll().0, 0xFE);
assert_eq!(regs.scroll().1, 0xCA);
regs.write_register(0xFE, Register::PpuScroll);
regs.reset_addr_latch();
regs.write_register(0xCA, Register::PpuScroll);
assert_eq!(regs.scroll().0, 0xCA);
assert_eq!(regs.scroll().1, 0xCA);
}
#[test]
fn increment_oam_address_write() {
let mut regs = Registers::default();
let mut expected_addr = 0u8;
assert_eq!(regs.oam_addr, expected_addr);
for _ in 0..u8::MAX {
regs.write_register(0, Register::OamData);
expected_addr = expected_addr.wrapping_add(1);
assert_eq!(regs.oam_addr, expected_addr);
}
}
#[test]
fn dont_increment_oam_address_read() {
let mut regs = Registers::default();
let mut expected_addr = 0u8;
assert_eq!(regs.oam_addr, expected_addr);
regs.read_register(Register::OamData);
assert_eq!(regs.oam_addr, expected_addr);
expected_addr = 0xCA;
regs.write_register(expected_addr, Register::OamAddr);
assert_eq!(regs.oam_addr, expected_addr);
regs.read_register(Register::OamData);
assert_eq!(regs.oam_addr, expected_addr);
}
}