diff --git a/snes-core/src/ppu/interface.rs b/snes-core/src/ppu/interface.rs index d7920b2..4b7656b 100644 --- a/snes-core/src/ppu/interface.rs +++ b/snes-core/src/ppu/interface.rs @@ -1,4 +1,4 @@ -use super::registers::{PPURegisters, MAX_TV_HEIGHT, MAX_TV_WIDTH}; +use super::registers::{PPURegisters, Background, MAX_TV_HEIGHT, MAX_TV_WIDTH}; const FRAMEBUFFER_SIZE: usize = MAX_TV_HEIGHT * MAX_TV_WIDTH * 4; @@ -12,13 +12,24 @@ pub struct PPU { impl PPU { pub fn new() -> Self { Self { - framebuffer: vec![0xFF; FRAMEBUFFER_SIZE], + framebuffer: Self::initialize_tv_framebuffer(), registers: PPURegisters::new(), was_vblank_nmi_set: false, is_irq_set: false, } } + fn initialize_tv_framebuffer() -> Vec { + let mut fb = vec![0x00; FRAMEBUFFER_SIZE]; + // 0x00 also makes the alpha channel transparent, we need to make it opaque + let mut i = 3; // start at the first alpha channel + while i < FRAMEBUFFER_SIZE { + fb[i] = 0xFF; + i += 4; + } + fb + } + pub fn tick(&mut self, cpu_cycles: usize) { for _ in 0..(cpu_cycles * 2) { self.dot_cycle(); @@ -56,10 +67,102 @@ impl PPU { } fn compute_pixel(&self) -> (u8, u8, u8) { - if self.registers.is_hblanking() || self.registers.is_hblanking() { - return (0x00, 0x00, 0x00) + // Objectives: + // 1. first try to render one of the backgrounds. Background 0 for now. + // 2. render the rest of the backgrounds + // 3. render sprites + // 4. figure out the priorities of each background + self.compute_background_pixel(Background::Bg1) + // (0xFF, 0x00, 0xFF) + } + + // TODO: wrte tests for this function + fn compute_background_pixel(&self, background: Background) -> (u8, u8, u8) { + // 0. detect video mode? + // 1. get base tileset vram address + // 2. get base charset vram address + // 3. know the height and width of background + // 4. calculate which is the current tile: (x / tile width) + ((y / tile height) * background width) + // 4.1: TODO: consider scroll values of the background for this calculation (x += x scroll, y += y scroll) + // 5. get the tile information from vram + // 6. get character index for the tile + // 7. calculate the vram address of the character + // TODO: consider that each tile can be mirrored either vertically or horizontally. Keep this in mind when fetching the character information from vram + // 8. look up color palette + // ---- + // possible optimizations: + // - Fetch all of the necessary data before starting to render the scanline + let tileset_vram_base_address = self.registers.get_bg_tile_base_address(background) as usize; + let charset_vram_base_address = self.registers.get_bg_char_base_address(background) as usize; + let (bg_size_width, _) = self.registers.get_bg_size(background).to_usize(); + let (tile_size_width, tile_size_height) = self.registers.get_bg_tile_size(background).to_usize(); + let vram = self.registers.vram(); + let x = (self.registers.h_count as usize) - 22; // H count ranges from 0 to 339. Pixels become visible at 22. + let y = (self.registers.v_count as usize) - 1; // V count ranges from 0 to 261 (depending on the region). Pixels become visible at 1. + + let current_tile = (x / tile_size_width) + ((y / tile_size_height) * bg_size_width); + let tile_byte = vram[tileset_vram_base_address + current_tile]; + let char_index = tile_byte & 0b11_11111111; + let current_char_column = x.rem_euclid(tile_size_width); + let current_char_row = y.rem_euclid(tile_size_height); + + let effective_vram_address = + charset_vram_base_address + + ((char_index as usize) * tile_size_width) + + current_char_row; + + let vram_word = vram[effective_vram_address]; + let lsb_bitplane= vram_word as u8; + let msb_bitplane= (vram_word >> 8) as u8; + + let pixels = Self::mix_pixel_bitplanes(lsb_bitplane, msb_bitplane); + let effective_pixel = pixels[current_char_column]; + + return match effective_pixel { + 0b00 => (0xFF, 0xFF, 0xFF), + 0b01 => (0xAC, 0xAC, 0xAC), + 0b10 => (0x56, 0x56, 0x56), + 0b11 => (0x00, 0x00, 0x00), + _ => unreachable!(), } - (0xFF, 0x00, 0xFF) + } + + // TODO: write tests + fn mix_pixel_bitplanes(lsb_bitplane: u8, msb_bitplane: u8) -> [u8; 8] { + [ + ( + (lsb_bitplane >> 7) | + ((msb_bitplane >> 7) << 1) + ), + ( + ((lsb_bitplane >> 6) & 1) | + (((msb_bitplane >> 6) & 1) << 1) + ), + ( + ((lsb_bitplane >> 5) & 1) | + (((msb_bitplane >> 5) & 1) << 1) + ), + ( + ((lsb_bitplane >> 4) & 1) | + (((msb_bitplane >> 4) & 1) << 1) + ), + ( + ((lsb_bitplane >> 3) & 1) | + (((msb_bitplane >> 3) & 1) << 1) + ), + ( + ((lsb_bitplane >> 2) & 1) | + (((msb_bitplane >> 2) & 1) << 1) + ), + ( + ((lsb_bitplane >> 1) & 1) | + (((msb_bitplane >> 1) & 1) << 1) + ), + ( + (lsb_bitplane & 1) | + ((msb_bitplane & 1) << 1) + ), + ] } fn put_pixel(&mut self, pixel: (u8, u8, u8)) {