pub struct Background {
bg_pattern_shift_lo: u16,
bg_pattern_shift_hi: u16,
bg_attribute_shift_lo: u16,
bg_attribute_shift_hi: u16,
nametable_latch: u8,
attribute_latch: u8,
pattern_lo_latch: u8,
pattern_hi_latch: u8,
tile_addr: u16,
}
impl Default for Background {
fn default() -> Self {
Self::new()
}
}
impl Background {
pub fn new() -> Self {
Self {
bg_pattern_shift_lo: 0,
bg_pattern_shift_hi: 0,
bg_attribute_shift_lo: 0,
bg_attribute_shift_hi: 0,
nametable_latch: 0,
attribute_latch: 0,
pattern_lo_latch: 0,
pattern_hi_latch: 0,
tile_addr: 0,
}
}
pub fn reset(&mut self) {
self.bg_pattern_shift_lo = 0;
self.bg_pattern_shift_hi = 0;
self.bg_attribute_shift_lo = 0;
self.bg_attribute_shift_hi = 0;
self.nametable_latch = 0;
self.attribute_latch = 0;
self.pattern_lo_latch = 0;
self.pattern_hi_latch = 0;
self.tile_addr = 0;
}
pub fn fetch_nametable<F>(&mut self, v: u16, pattern_table_base: u16, read_nametable: F)
where
F: Fn(u16) -> u8,
{
let addr = 0x2000 | (v & 0x0FFF);
self.nametable_latch = read_nametable(addr);
let tile_index = self.nametable_latch as u16;
let fine_y = (v >> 12) & 0x07;
self.tile_addr = pattern_table_base | (tile_index << 4) | fine_y;
}
pub fn fetch_attribute<F>(&mut self, v: u16, read_nametable: F)
where
F: Fn(u16) -> u8,
{
let addr = 0x23C0 | (v & 0x0C00) | ((v >> 4) & 0x38) | ((v >> 2) & 0x07);
self.attribute_latch = read_nametable(addr);
}
pub fn fetch_pattern_lo<F>(&mut self, read_chr: F)
where
F: Fn(u16) -> u8,
{
self.pattern_lo_latch = read_chr(self.tile_addr);
}
pub fn fetch_pattern_hi<F>(&mut self, read_chr: F)
where
F: Fn(u16) -> u8,
{
self.pattern_hi_latch = read_chr(self.tile_addr | 0x08);
}
pub fn load_shift_registers(&mut self, v: u16) {
self.bg_pattern_shift_lo =
(self.bg_pattern_shift_lo & 0xFF00) | (self.pattern_lo_latch as u16);
self.bg_pattern_shift_hi =
(self.bg_pattern_shift_hi & 0xFF00) | (self.pattern_hi_latch as u16);
let coarse_x = v & 0x1F;
let coarse_y = (v >> 5) & 0x1F;
let shift = ((coarse_y & 0x02) << 1) | (coarse_x & 0x02);
let palette = (self.attribute_latch >> shift) & 0x03;
let palette_lo_bits = if (palette & 0x01) != 0 { 0xFF } else { 0x00 };
let palette_hi_bits = if (palette & 0x02) != 0 { 0xFF } else { 0x00 };
self.bg_attribute_shift_lo =
(self.bg_attribute_shift_lo & 0xFF00) | (palette_lo_bits as u16);
self.bg_attribute_shift_hi =
(self.bg_attribute_shift_hi & 0xFF00) | (palette_hi_bits as u16);
}
pub fn shift_registers(&mut self) {
self.bg_pattern_shift_lo <<= 1;
self.bg_pattern_shift_hi <<= 1;
self.bg_attribute_shift_lo <<= 1;
self.bg_attribute_shift_hi <<= 1;
}
pub fn get_pixel(&self, fine_x: u8) -> u8 {
let bit_position = 15 - fine_x;
let pattern_lo_bit = ((self.bg_pattern_shift_lo >> bit_position) & 0x01) as u8;
let pattern_hi_bit = ((self.bg_pattern_shift_hi >> bit_position) & 0x01) as u8;
let pattern = (pattern_hi_bit << 1) | pattern_lo_bit;
if pattern == 0 {
return 0;
}
let attr_lo_bit = ((self.bg_attribute_shift_lo >> bit_position) & 0x01) as u8;
let attr_hi_bit = ((self.bg_attribute_shift_hi >> bit_position) & 0x01) as u8;
let palette = (attr_hi_bit << 1) | attr_lo_bit;
palette * 4 + pattern
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BackgroundState {
pub bg_pattern_shift_lo: u16,
pub bg_pattern_shift_hi: u16,
pub bg_attribute_shift_lo: u16,
pub bg_attribute_shift_hi: u16,
pub nametable_latch: u8,
pub attribute_latch: u8,
pub pattern_lo_latch: u8,
pub pattern_hi_latch: u8,
}
impl Background {
pub fn capture_state(&self) -> BackgroundState {
BackgroundState {
bg_pattern_shift_lo: self.bg_pattern_shift_lo,
bg_pattern_shift_hi: self.bg_pattern_shift_hi,
bg_attribute_shift_lo: self.bg_attribute_shift_lo,
bg_attribute_shift_hi: self.bg_attribute_shift_hi,
nametable_latch: self.nametable_latch,
attribute_latch: self.attribute_latch,
pattern_lo_latch: self.pattern_lo_latch,
pattern_hi_latch: self.pattern_hi_latch,
}
}
pub fn restore_state(&mut self, state: &BackgroundState) {
self.bg_pattern_shift_lo = state.bg_pattern_shift_lo;
self.bg_pattern_shift_hi = state.bg_pattern_shift_hi;
self.bg_attribute_shift_lo = state.bg_attribute_shift_lo;
self.bg_attribute_shift_hi = state.bg_attribute_shift_hi;
self.nametable_latch = state.nametable_latch;
self.attribute_latch = state.attribute_latch;
self.pattern_lo_latch = state.pattern_lo_latch;
self.pattern_hi_latch = state.pattern_hi_latch;
}
}
#[cfg(test)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BackgroundDebugState {
pub bg_pattern_shift_lo: u16,
pub bg_pattern_shift_hi: u16,
pub bg_attribute_shift_lo: u16,
pub bg_attribute_shift_hi: u16,
pub nametable_latch: u8,
pub attribute_latch: u8,
pub pattern_lo_latch: u8,
pub pattern_hi_latch: u8,
}
#[cfg(test)]
impl Background {
pub fn debug_state(&self) -> BackgroundDebugState {
BackgroundDebugState {
bg_pattern_shift_lo: self.bg_pattern_shift_lo,
bg_pattern_shift_hi: self.bg_pattern_shift_hi,
bg_attribute_shift_lo: self.bg_attribute_shift_lo,
bg_attribute_shift_hi: self.bg_attribute_shift_hi,
nametable_latch: self.nametable_latch,
attribute_latch: self.attribute_latch,
pattern_lo_latch: self.pattern_lo_latch,
pattern_hi_latch: self.pattern_hi_latch,
}
}
pub fn set_debug_state(&mut self, state: BackgroundDebugState) {
self.bg_pattern_shift_lo = state.bg_pattern_shift_lo;
self.bg_pattern_shift_hi = state.bg_pattern_shift_hi;
self.bg_attribute_shift_lo = state.bg_attribute_shift_lo;
self.bg_attribute_shift_hi = state.bg_attribute_shift_hi;
self.nametable_latch = state.nametable_latch;
self.attribute_latch = state.attribute_latch;
self.pattern_lo_latch = state.pattern_lo_latch;
self.pattern_hi_latch = state.pattern_hi_latch;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_background_new() {
let bg = Background::new();
assert_eq!(bg.get_pixel(0), 0);
}
#[test]
fn test_background_reset() {
let mut bg = Background::new();
bg.nametable_latch = 42;
bg.reset();
assert_eq!(bg.nametable_latch, 0);
}
#[test]
fn test_fetch_nametable() {
let mut bg = Background::new();
bg.fetch_nametable(0x2000, 0x0000, |_| 0x42);
assert_eq!(bg.nametable_latch, 0x42);
assert_eq!(bg.tile_addr, 0x0422);
}
#[test]
fn test_shift_registers() {
let mut bg = Background::new();
bg.bg_pattern_shift_lo = 0x8000;
bg.shift_registers();
assert_eq!(bg.bg_pattern_shift_lo, 0);
}
#[test]
fn test_load_shift_registers() {
let mut bg = Background::new();
bg.pattern_lo_latch = 0xFF;
bg.pattern_hi_latch = 0xFF;
bg.attribute_latch = 0x03;
bg.load_shift_registers(0);
assert_eq!(bg.bg_pattern_shift_lo & 0xFF, 0xFF);
assert_eq!(bg.bg_pattern_shift_hi & 0xFF, 0xFF);
}
#[test]
fn test_get_pixel_transparent() {
let bg = Background::new();
assert_eq!(bg.get_pixel(0), 0);
}
#[test]
fn test_get_pixel_with_pattern() {
let mut bg = Background::new();
bg.bg_pattern_shift_lo = 0x8000;
bg.bg_pattern_shift_hi = 0x8000;
let pixel = bg.get_pixel(0);
assert_eq!(pixel, 3); }
}