#[cfg(feature = "show_name_table_change")]
use crate::ppu::palette::{Color, EmphasisFlags};
use crate::ppu::ppu_render_pixel::BgTileToShow;
use crate::ppu::prepare_scanline::*;
use crate::ppu::registers::Registers;
use crate::ppu::scanline_pos::PpuState;
use crate::ppu::sprite::SpriteData;
use crate::ppu::vram_read_buffer::VramReadBuffer;
#[cfg(feature = "show_name_table_change")]
use crate::ppu::ScanlineEvent;
use crate::ppu::{
FrameEvent, Ppu, PpuCartMemorySpace, SpriteDataArray, VramReadEvent, SCREEN_WIDTH, TILE_DIM,
};
const NAME_TABLE_ROW_COUNT: u16 = 30;
const NAME_TABLE_COL_COUNT: u16 = 32;
fn must_increment_vram_addr_horizontal(pos_at_scanline: u16) -> bool {
if pos_at_scanline % 8 == 0 {
if ((pos_at_scanline > 0) && (pos_at_scanline <= 256))
|| (pos_at_scanline == 328)
|| (pos_at_scanline == 336)
{
return true;
}
}
false
}
fn get_row_addr_for_sprite(sprite: &SpriteData, screen_y: u8, regs: &Registers) -> u16 {
let sprite_base_addr = sprite_base_address_in_pattern_table(sprite.obj_attributes(), ®s);
let local_y = screen_y.wrapping_sub(sprite.obj_attributes().y()).wrapping_sub(1);
let vertical_flip = sprite.obj_attributes().vertical_flip_flag();
set_fine_y_offset_to_pattern_table_addr(
sprite_base_addr,
local_y,
vertical_flip,
regs.large_sprites_flag(),
)
}
fn update_sprite_color_rows(
sprite: &mut SpriteData,
screen_y: u8,
regs: &Registers,
cart_memory: &mut impl PpuCartMemorySpace,
) {
let row_addr = get_row_addr_for_sprite(sprite, screen_y, ®s);
let color_rows = get_color_index_row_bytes_from_pattern_table(row_addr, cart_memory);
sprite.set_row_color_index_bytes(color_rows);
}
fn update_sprites_color_rows(
screen_y: u8,
sprites_for_scanline: &mut SpriteDataArray,
regs: &Registers,
cart_memory: &mut impl PpuCartMemorySpace,
) {
for sprite in sprites_for_scanline {
update_sprite_color_rows(sprite, screen_y, regs, cart_memory);
}
}
fn find_current_bg_tile(vram_read_buffer: &VramReadBuffer) -> BgTileToShow {
let coords_in_table = vram_read_buffer.front_coords_in_name_table();
let row_colors = (
vram_read_buffer.front_bg_high_data(),
vram_read_buffer.front_bg_low_data(),
);
let attribute_byte = vram_read_buffer.front_attr_data();
let palette_index = palette_index_in_name_table3(coords_in_table, attribute_byte);
BgTileToShow::new(row_colors, palette_index)
}
fn show_sprites(screen_coords: (u8, u8), regs: &Registers) -> bool {
let left_most_tile = screen_coords.0 < TILE_DIM;
regs.show_sprites_flag() && (!left_most_tile || regs.show_sprites_leftmost_flag())
}
impl Ppu {
pub fn nmi_signal(&self) -> bool {
self.nmi_signal
}
#[inline]
fn run_vram_read_event(&mut self, cart_memory: &mut impl PpuCartMemorySpace) {
let event = self.scanline_pos.current_vram_read_event();
match event {
VramReadEvent::None => {
}
VramReadEvent::TileAddrSet => {
let vram_addr_components = &self.regs.v_addr_components();
let tile_address_in_name_table = vram_addr_components.build_nametable_address();
self.vram_read_buffer
.set_tile_addr(tile_address_in_name_table);
let coords_in_table = (
vram_addr_components.coarse_x as u16 % NAME_TABLE_COL_COUNT,
vram_addr_components.coarse_y as u16 % NAME_TABLE_ROW_COUNT,
);
self.vram_read_buffer
.set_coords_in_name_table(coords_in_table);
self.vram_read_buffer
.set_second_bg_table_selected(self.regs.bg_second_table_selected());
}
VramReadEvent::TileDataRead => {
let tile_index = cart_memory.read(self.vram_read_buffer.back_tile_addr());
self.vram_read_buffer.set_tile_data(tile_index);
}
VramReadEvent::AttrAddrSet => {
let attr_address = self.regs.v_addr_components().build_attribute_address();
self.vram_read_buffer.set_attr_addr(attr_address);
}
VramReadEvent::AttrDataRead => {
let attribute = cart_memory.read(self.vram_read_buffer.back_attr_addr());
self.vram_read_buffer.set_attr_data(attribute);
}
VramReadEvent::BackgroundLowAddrSet => {
let tile_index = self.vram_read_buffer.back_tile_data();
let second_bg_table_selected =
self.vram_read_buffer.back_bg_second_table_selected();
let tile_base_addr =
bg_base_address_in_pattern_table(tile_index, second_bg_table_selected);
let row_addr = set_fine_y_offset_to_pattern_table_addr(
tile_base_addr,
self.regs.v_addr_components().fine_y,
false,
false,
);
let row_addr = row_addr & 0x1FF7;
self.vram_read_buffer.set_bg_low_addr(row_addr);
}
VramReadEvent::BackgroundLowDataRead => {
let bg_low_byte = cart_memory.read(self.vram_read_buffer.back_bg_low_addr());
self.vram_read_buffer.set_bg_low_data(bg_low_byte);
}
VramReadEvent::BackgroundHighAddrSet => {
let tile_index = self.vram_read_buffer.back_tile_data();
let second_bg_table_selected =
self.vram_read_buffer.back_bg_second_table_selected();
let tile_base_addr =
bg_base_address_in_pattern_table(tile_index, second_bg_table_selected);
let row_addr = set_fine_y_offset_to_pattern_table_addr(
tile_base_addr,
self.regs.v_addr_components().fine_y,
false,
false,
);
let row_addr = (row_addr & 0x1FF7) | 0b_1000;
self.vram_read_buffer.set_bg_high_addr(row_addr);
}
VramReadEvent::BackgroundHighDataRead => {
let bg_high_byte = cart_memory.read(self.vram_read_buffer.back_bg_high_addr());
self.vram_read_buffer.set_bg_high_data(bg_high_byte);
}
VramReadEvent::SpriteAddrSet {
sprite_index,
is_high_byte,
} => {
if self.cycle_accurate_sprites_enabled {
let next_y = if self.scanline_pos.current_scanline() < 261 {
self.scanline_pos.current_scanline() as u8 + 1
} else {
(self.scanline_pos.current_scanline() - 261) as u8 + 1
};
let row_addr = if let Some(sprite) =
self.sprites_for_next_scanline.get(sprite_index as usize)
{
let row_addr = get_row_addr_for_sprite(sprite, next_y, &self.regs);
if is_high_byte {
row_addr | 0b_1000
} else {
row_addr
}
} else {
0x1FFF
};
self.sprite_addr = row_addr;
}
}
VramReadEvent::SpriteDataRead {
sprite_index,
is_high_byte,
} => {
if self.cycle_accurate_sprites_enabled {
let row_byte = cart_memory.read(self.sprite_addr);
if let Some(sprite) = self
.sprites_for_next_scanline
.get_mut(sprite_index as usize)
{
sprite.set_row_color_index_byte(row_byte, is_high_byte);
}
}
}
}
}
#[inline]
pub fn run_cycle(&mut self, cart_memory: &mut impl PpuCartMemorySpace) -> FrameEvent {
let current_state = self.scanline_pos.current_state();
#[cfg(test)]
{
let x = self.scanline_pos.pos_at_scanline();
let y = self.scanline_pos.current_scanline();
if x == 0 {
let pixels_drawn = self.screen_colors.len();
println!("starting {current_state:?} {y}: {pixels_drawn} pixels drawn");
}
}
match current_state {
PpuState::PreRenderScanline => self.run_pre_render_cycle(cart_memory),
PpuState::Rendering => self.run_render_cycle(cart_memory),
PpuState::PostRenderScanline => {
debug_assert_eq!(self.screen_colors.len(), self.screen_colors.capacity())
}
PpuState::VBlank => self.run_vblank_cycle(),
}
if current_state != PpuState::VBlank {
match self.scanline_pos.pos_at_scanline() {
256 => self.regs.vram_addr_increment_vertical(),
257 => self.regs.vram_addr_update_horizontal_bits(),
_ => (),
}
if must_increment_vram_addr_horizontal(self.scanline_pos.pos_at_scanline()) {
self.regs.vram_addr_increment_horizontal();
}
}
self.nmi_signal = self.regs.vblank_started() && self.regs.nmi_enabled();
let frame_event = self.scanline_pos.current_frame_event();
#[cfg(test)]
{
self.cycles_since_reset += 1;
}
#[cfg(test)]
if (frame_event != FrameEvent::None) && (frame_event != FrameEvent::EndOfScanline) {
println!("{frame_event:?}\t\t {} cycles", self.cycles_since_reset);
}
self.scanline_pos.advance_cycle();
self.regs.update_render_toggle_buffers();
#[cfg(feature = "show_name_table_change")]
{
self.last_event = ScanlineEvent::None;
}
frame_event
}
fn run_pre_render_cycle(&mut self, cart_memory: &mut impl PpuCartMemorySpace) {
if self.scanline_pos.pos_at_scanline() == 1 {
self.regs.frame_begin_clear_flags();
}
self.run_vram_read_event(cart_memory);
match self.scanline_pos.pos_at_scanline() {
280..=304 => {
self.regs.vram_addr_update_vertical_bits();
}
_ => (),
}
}
#[inline]
fn run_render_cycle(&mut self, cart_memory: &mut impl PpuCartMemorySpace) {
let x = self.scanline_pos.pos_at_scanline();
let y = self.scanline_pos.current_scanline() as u8;
if x == 0 {
if y == 0 {
self.screen_colors.clear();
}
self.prepare_scanline(cart_memory);
}
if x < SCREEN_WIDTH {
#[cfg(feature = "show_name_table_change")]
if self.last_event == ScanlineEvent::ShowBg {
self.screen_colors
.push(Color::new(0x19, EmphasisFlags::empty()));
return;
} else if self.last_event == ScanlineEvent::DontShowBg {
self.screen_colors
.push(Color::new(0x16, EmphasisFlags::empty()));
return;
}
if self.sprites_start_col.len() > 0 {
let start_col = unsafe { self.sprites_start_col.get_unchecked(0) };
if x == start_col.x as u16 {
self.sprite_zero_pos = u8::MAX;
self.sprites_start_col.remove(0);
self.sprites_at_current_pixel.clear();
for sprite in self.sprites_for_scanline.iter() {
if sprite.obj_attributes().x() as u16 <= x {
if (sprite.obj_attributes().x() as u16 + 8) > x {
if sprite.oam_index() == 0 {
self.sprite_zero_pos =
self.sprites_at_current_pixel.len() as u8;
}
self.sprites_at_current_pixel.push(*sprite);
}
}
}
}
}
let x = x as u8;
let screen_coords = (x, y);
self.update_bg_tile(screen_coords);
let show_sprites = show_sprites(screen_coords, &self.regs);
let color = self.calculate_pixel_color_index((x, y), self.bg_tile, show_sprites);
self.screen_colors.push(color);
}
self.run_vram_read_event(cart_memory);
}
#[inline]
fn update_bg_tile(&mut self, screen_coords: (u8, u8)) {
let left_most_tile = screen_coords.0 < TILE_DIM;
let scroll = self.regs.scroll();
let scrolled_coords = (
screen_coords.0.wrapping_add(scroll.0),
screen_coords.1.wrapping_add(scroll.1),
);
if self.regs.show_bg_flag() && (!left_most_tile || self.regs.show_bg_leftmost_flag()) {
if self.bg_tile.is_none() || ((scrolled_coords.0 % TILE_DIM) == 0) {
self.bg_tile = Some(find_current_bg_tile(&self.vram_read_buffer));
}
} else {
self.bg_tile = None;
}
if (scrolled_coords.0 % TILE_DIM) == (TILE_DIM - 1) {
self.vram_read_buffer.pop_front();
}
}
fn run_vblank_cycle(&mut self) {
if self.scanline_pos.first_vblank_scanline() {
if self.scanline_pos.pos_at_scanline() == 0 {
self.regs.start_vblank();
}
}
}
fn prepare_scanline(&mut self, cart_memory: &mut impl PpuCartMemorySpace) {
self.bg_tile = None;
let y = self.scanline_pos.current_scanline() as u8;
self.sprites_at_current_pixel.clear();
if true && self.regs.show_sprites_flag() {
let (sprites, y) = if self.cycle_accurate_sprites_enabled {
self.sprites_for_scanline = self.sprites_for_next_scanline.clone();
(&mut self.sprites_for_next_scanline, y + 1)
} else {
(&mut self.sprites_for_scanline, y)
};
let large_sprites = self.regs.large_sprites_flag();
let overflow = prepare_sprites_for_scanline(y, large_sprites, &self.oam, sprites);
if overflow {
self.regs.set_sprite_overflow();
}
if !self.cycle_accurate_sprites_enabled {
update_sprites_color_rows(y, sprites, &self.regs, cart_memory);
}
sort_sprites_by_col(&self.sprites_for_scanline, &mut self.sprites_start_col);
} else {
self.sprites_for_scanline.clear();
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::ppu::oam;
use crate::ppu::sprite::SpriteData;
use crate::ppu::Register;
struct MockPpuMem;
impl PpuCartMemorySpace for MockPpuMem {
fn read(&mut self, addr: u16) -> u8 {
match addr {
0b_0001_1010_0101_0010 => 0xCC,
0b_0001_1010_0101_1010 => 0xAA,
0x2C21 => 0b_1010_0101,
0x2FC0 => 0b_0000_0010,
_ => 0xFF,
}
}
fn write(&mut self, _data: u8, _addr: u16) {
unreachable!()
}
}
#[test]
fn sprite_color_rows() {
let mut mem = MockPpuMem;
let mut regs = Registers::default();
regs.write_register(0b_0001_1011, Register::PpuControl);
let mut sprites = SpriteDataArray::new();
let sprite_data =
SpriteData::with_oam_index(oam::Entry::with_data(1, 165, Default::default(), 5), 0);
sprite_data.obj_attributes().set_palette(4);
sprites.push(sprite_data);
update_sprites_color_rows(4, &mut sprites, ®s, &mut mem);
assert_eq!(sprites[0].row_color_index_bytes(), Some((0xAA, 0xCC)));
}
}