use alloc::vec;
use alloc::vec::Vec;
use super::Ppu;
use crate::{
common::{ResetKind, NesRegion, Regional, Reset},
mapper::{Mapped, MappedRead, MappedWrite, Mapper, MemMap},
mem::{Access, Mem},
ppu::Mirroring,
};
use serde::{Deserialize, Serialize};
pub trait PpuAddr {
fn is_attr(&self) -> bool;
fn is_palette(&self) -> bool;
}
impl PpuAddr for u16 {
#[inline]
fn is_attr(&self) -> bool {
(*self & 0x03FF) >= 0x03C0
}
#[inline]
fn is_palette(&self) -> bool {
*self >= 0x3F00
}
}
#[derive(Clone, Serialize, Deserialize)]
#[must_use]
pub struct PpuBus {
mapper: Mapper,
mirror_shift: usize,
ciram: Vec<u8>, palette: [u8; Self::PALETTE_SIZE],
chr_rom: Vec<u8>,
chr_ram: Vec<u8>,
exram: Vec<u8>,
open_bus: u8,
}
impl Default for PpuBus {
fn default() -> Self {
Self::new()
}
}
impl PpuBus {
const VRAM_SIZE: usize = 0x0800; const PALETTE_SIZE: usize = 32;
pub fn new() -> Self {
Self {
mapper: Mapper::none(),
mirror_shift: Mirroring::default() as usize,
ciram: vec![0x00; Self::VRAM_SIZE],
palette: [0x00; Self::PALETTE_SIZE],
chr_rom: vec![],
chr_ram: vec![],
exram: vec![],
open_bus: 0x00,
}
}
#[inline]
pub fn mirroring(&self) -> Mirroring {
self.mapper.mirroring()
}
#[inline]
pub fn update_mirroring(&mut self) {
self.mirror_shift = self.mirroring() as usize;
}
#[inline]
pub fn load_chr_rom(&mut self, chr_rom: Vec<u8>) {
self.chr_rom = chr_rom;
}
#[inline]
pub fn load_chr_ram(&mut self, chr_ram: Vec<u8>) {
self.chr_ram = chr_ram;
}
#[inline]
pub fn load_ex_ram(&mut self, ex_ram: Vec<u8>) {
self.exram = ex_ram;
}
#[inline]
pub fn load_mapper(&mut self, mapper: Mapper) {
self.mapper = mapper;
}
#[inline]
pub const fn mapper(&self) -> &Mapper {
&self.mapper
}
#[inline]
pub fn mapper_mut(&mut self) -> &mut Mapper {
&mut self.mapper
}
#[inline]
const fn ciram_mirror(&self, addr: usize) -> usize {
let nametable = (addr >> self.mirror_shift) & (Ppu::NT_SIZE as usize);
(nametable) | (!nametable & addr & 0x03FF)
}
#[inline]
const fn palette_mirror(&self, addr: usize) -> usize {
let addr = addr & 0x001F;
if addr >= 16 && addr.trailing_zeros() >= 2 {
addr - 16
} else {
addr
}
}
}
impl Mem for PpuBus {
fn read(&mut self, addr: u16, _access: Access) -> u8 {
let val = match addr {
0x0000..=0x1FFF => {
let addr = if let MappedRead::Chr(addr) = self.mapper.map_read(addr) {
addr
} else {
addr.into()
};
if self.chr_rom.is_empty() {
self.chr_ram[addr]
} else {
self.chr_rom[addr]
}
}
0x2000..=0x3EFF => match self.mapper.map_read(addr) {
MappedRead::CIRam(addr) => self.ciram[addr & 0x07FF],
MappedRead::ExRam(addr) => self.exram[addr & 0x03FF],
MappedRead::Data(data) => data,
_ => {
if self.mirroring() == Mirroring::FourScreen {
0x00
} else {
self.ciram[self.ciram_mirror(addr as usize)]
}
}
},
0x3F00..=0x3FFF => self.palette[self.palette_mirror(addr as usize)],
_ => {
log::error!("unexpected PPU memory access at ${:04X}", addr);
0x00
}
};
self.open_bus = val;
val
}
fn peek(&self, addr: u16, _access: Access) -> u8 {
match addr {
0x2000..=0x3EFF => match self.mapper.map_peek(addr) {
MappedRead::CIRam(addr) => self.ciram[addr & 0x07FF],
MappedRead::ExRam(addr) => self.exram[addr & 0x03FF],
MappedRead::Data(data) => data,
_ => {
if self.mirroring() == Mirroring::FourScreen {
0x00
} else {
self.ciram[self.ciram_mirror(addr as usize)]
}
}
},
0x0000..=0x1FFF => {
let addr = if let MappedRead::Chr(addr) = self.mapper.map_peek(addr) {
addr
} else {
addr.into()
};
if !self.chr_ram.is_empty() {
self.chr_ram[addr]
} else {
self.chr_rom[addr]
}
}
0x3F00..=0x3FFF => self.palette[self.palette_mirror(addr as usize)],
_ => {
log::error!("unexpected PPU memory access at ${:04X}", addr);
0x00
}
}
}
fn write(&mut self, addr: u16, val: u8, _access: Access) {
match addr {
0x2000..=0x3EFF => match self.mapper.map_write(addr, val) {
MappedWrite::CIRam(addr, val) => self.ciram[addr] = val,
MappedWrite::ExRam(addr, val) => self.exram[addr] = val,
_ => {
if self.mirroring() != Mirroring::FourScreen {
let addr = self.ciram_mirror(addr as usize);
self.ciram[addr] = val;
}
}
},
0x0000..=0x1FFF => {
if !self.chr_ram.is_empty() {
if let MappedWrite::Chr(addr, val) = self.mapper.map_write(addr, val) {
self.chr_ram[addr] = val;
}
}
}
0x3F00..=0x3FFF => {
self.palette[self.palette_mirror(addr as usize)] = val;
}
_ => log::error!("unexpected PPU memory access at ${:04X}", addr),
}
self.mapper.ppu_bus_write(addr, val);
self.open_bus = val;
}
}
impl Regional for PpuBus {
#[inline]
fn region(&self) -> NesRegion {
self.mapper.region()
}
fn set_region(&mut self, region: NesRegion) {
self.mapper.set_region(region);
}
}
impl Reset for PpuBus {
fn reset(&mut self, kind: ResetKind) {
self.open_bus = 0x00;
self.mapper.reset(kind);
}
}
impl core::fmt::Debug for PpuBus {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("PpuBus")
.field("mapper", &self.mapper)
.field("ciram_len", &self.ciram.len())
.field("palette_len", &self.palette.len())
.field("chr_rom_len", &self.chr_rom.len())
.field("chr_ram_len", &self.chr_ram.len())
.field("ex_ram_len", &self.exram.len())
.field("open_bus", &self.open_bus)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ciram_mirror_horizontal() {
let mut ppu_bus = PpuBus::new();
ppu_bus.mirror_shift = Mirroring::Horizontal as usize;
assert_eq!(ppu_bus.ciram_mirror(0x2000), 0x0000);
assert_eq!(ppu_bus.ciram_mirror(0x2005), 0x0005);
assert_eq!(ppu_bus.ciram_mirror(0x23FF), 0x03FF);
assert_eq!(ppu_bus.ciram_mirror(0x2400), 0x0000);
assert_eq!(ppu_bus.ciram_mirror(0x2405), 0x0005);
assert_eq!(ppu_bus.ciram_mirror(0x27FF), 0x03FF);
assert_eq!(ppu_bus.ciram_mirror(0x2800), 0x0400);
assert_eq!(ppu_bus.ciram_mirror(0x2805), 0x0405);
assert_eq!(ppu_bus.ciram_mirror(0x2BFF), 0x07FF);
assert_eq!(ppu_bus.ciram_mirror(0x2C00), 0x0400);
assert_eq!(ppu_bus.ciram_mirror(0x2C05), 0x0405);
assert_eq!(ppu_bus.ciram_mirror(0x2FFF), 0x07FF);
}
#[test]
fn ciram_mirror_vertical() {
let mut ppu_bus = PpuBus::new();
ppu_bus.mirror_shift = Mirroring::Vertical as usize;
assert_eq!(ppu_bus.ciram_mirror(0x2000), 0x0000);
assert_eq!(ppu_bus.ciram_mirror(0x2005), 0x0005);
assert_eq!(ppu_bus.ciram_mirror(0x23FF), 0x03FF);
assert_eq!(ppu_bus.ciram_mirror(0x2800), 0x0000);
assert_eq!(ppu_bus.ciram_mirror(0x2805), 0x0005);
assert_eq!(ppu_bus.ciram_mirror(0x2BFF), 0x03FF);
assert_eq!(ppu_bus.ciram_mirror(0x2400), 0x0400);
assert_eq!(ppu_bus.ciram_mirror(0x2405), 0x0405);
assert_eq!(ppu_bus.ciram_mirror(0x27FF), 0x07FF);
assert_eq!(ppu_bus.ciram_mirror(0x2C00), 0x0400);
assert_eq!(ppu_bus.ciram_mirror(0x2C05), 0x0405);
assert_eq!(ppu_bus.ciram_mirror(0x2FFF), 0x07FF);
}
#[test]
fn ciram_mirror_single_screen_a() {
let mut ppu_bus = PpuBus::new();
ppu_bus.mirror_shift = Mirroring::SingleScreenA as usize;
assert_eq!(ppu_bus.ciram_mirror(0x2000), 0x0000);
assert_eq!(ppu_bus.ciram_mirror(0x2005), 0x0005);
assert_eq!(ppu_bus.ciram_mirror(0x23FF), 0x03FF);
assert_eq!(ppu_bus.ciram_mirror(0x2800), 0x0000);
assert_eq!(ppu_bus.ciram_mirror(0x2805), 0x0005);
assert_eq!(ppu_bus.ciram_mirror(0x2BFF), 0x03FF);
assert_eq!(ppu_bus.ciram_mirror(0x2400), 0x0000);
assert_eq!(ppu_bus.ciram_mirror(0x2405), 0x0005);
assert_eq!(ppu_bus.ciram_mirror(0x27FF), 0x03FF);
assert_eq!(ppu_bus.ciram_mirror(0x2C00), 0x0000);
assert_eq!(ppu_bus.ciram_mirror(0x2C05), 0x0005);
assert_eq!(ppu_bus.ciram_mirror(0x2FFF), 0x03FF);
}
#[test]
fn ciram_mirror_single_screen_b() {
let mut ppu_bus = PpuBus::new();
ppu_bus.mirror_shift = Mirroring::SingleScreenB as usize;
assert_eq!(ppu_bus.ciram_mirror(0x2000), 0x0400);
assert_eq!(ppu_bus.ciram_mirror(0x2005), 0x0405);
assert_eq!(ppu_bus.ciram_mirror(0x23FF), 0x07FF);
assert_eq!(ppu_bus.ciram_mirror(0x2800), 0x0400);
assert_eq!(ppu_bus.ciram_mirror(0x2805), 0x0405);
assert_eq!(ppu_bus.ciram_mirror(0x2BFF), 0x07FF);
assert_eq!(ppu_bus.ciram_mirror(0x2400), 0x0400);
assert_eq!(ppu_bus.ciram_mirror(0x2405), 0x0405);
assert_eq!(ppu_bus.ciram_mirror(0x27FF), 0x07FF);
assert_eq!(ppu_bus.ciram_mirror(0x2C00), 0x0400);
assert_eq!(ppu_bus.ciram_mirror(0x2C05), 0x0405);
assert_eq!(ppu_bus.ciram_mirror(0x2FFF), 0x07FF);
}
}