#include #include "ppu.h" #include "mapper.h" #include "cart.h" // TODO: Retain open bus bits? #define ppu_reg_ctrl (0x2000U) #define ppu_reg_mask (0x2001U) #define ppu_reg_status (0x2002U) #define oam_reg_addr (0x2003U) #define oam_reg_data (0x2004U) #define ppu_reg_scroll (0x2005U) #define ppu_reg_addr (0x2006U) #define ppu_reg_data (0x2007U) #define oam_reg_dma (0x4014U) uint8_t nes_ppu_read(nes_ppu* ppu, uint16_t addr) { uint8_t val = 0; if (ppu_reg_status == addr) { val = ppu->status; ppu->latch = 0; } else if (oam_reg_data == addr) { OAM_LOG("PPU: OAM READ %02x > %02x\n", ppu->oam_addr, val); val = ((uint8_t*)ppu->oam)[ppu->oam_addr]; } else if (ppu_reg_data == addr) { if (ppu->addr >= nes_ppu_mem_pal_start) { PPU_LOG("PPU: PAL READ %04x > %02x\n", ppu->addr, val); uint8_t pal_addr = (ppu->addr - nes_ppu_mem_pal_start) & (nes_ppu_mem_pal_size - 1); val = ppu->palette[pal_addr]; } else { val = ppu->data; if (ppu->addr < nes_ppu_mem_vram_start) { PPU_LOG("PPU: CHR MEM READ %04x > %02x\n", ppu->addr, val); ppu->data = nes_chr_read(ppu->mapper, ppu->map_data, ppu->addr); } else if (ppu->addr < nes_ppu_mem_vram_start + nes_ppu_mem_vram_size) { VRAM_LOG("PPU: VRAM READ %04x > %02x\n", ppu->addr, val); ppu->data = nes_vram_read( ppu->mapper, ppu->map_data, ppu->addr - nes_ppu_mem_vram_start ); } else if (ppu->addr < nes_ppu_mem_pal_start) { PPU_LOG("PPU: BLANK READ %04x > %02x\n", ppu->addr, val); /* ppu->data = *(nes_map_chr_addr(ppu->mapper, ppu->addr)); */ } } ppu->addr += (ppu->control & ppu_Control_VRAM_Inc) ? 32 : 1; } PPU_LOG("PPU: <-R $%04x %02x\n", addr, val); return val; } // Copy t to v (decoded into scroll_x, control) static inline void nes_ppu_internal_copy_x(nes_ppu* ppu) { ppu->control &= ~(1U); ppu->control |= ((ppu->t >> 10) & 1U); ppu->scroll_x = ((ppu->t & 0b11111U) << 3) | (ppu->x & 0b111U); } // Copy t to v (decoded into scroll_y, control) static inline void nes_ppu_internal_copy_y(nes_ppu* ppu) { ppu->control &= ~(0b10U); ppu->control |= ((ppu->t >> 10) & 0b10U); ppu->scroll_y = ((ppu->t & 0x03E0U) >> 2) | ((ppu->t & 0x7000U) >> 12); } void nes_ppu_write(nes_ppu* ppu, uint16_t addr, uint8_t val) { PPU_LOG("PPU: W-> $%04x %02x\n", addr, val); if (ppu_reg_ctrl == addr) { PPU_LOG("PPU: CTRL %02x\n", val); ppu->control &= ppu_Control_Nametable_Mask; ppu->control |= (val & ~ppu_Control_Nametable_Mask); ppu->t &= ~(0xC00U); ppu->t |= (uint16_t)(val & ppu_Control_Nametable_Mask) << 10; } else if (oam_reg_addr == addr) { OAM_LOG("PPU: OAM ADDR %02x\n", val); ppu->oam_addr = val; } else if (oam_reg_data == addr) { OAM_LOG("PPU: OAM %02x < %02x\n", ppu->oam_addr, val); ((uint8_t*)ppu->oam)[ppu->oam_addr++] = val; } else if (ppu_reg_mask == addr) { PPU_LOG("PPU: Mask %02x\n", val); ppu->mask = val; } else if (ppu_reg_scroll == addr) { if (ppu->latch) { PPU_LOG("PPU: Scroll Y %02x\n", val); ppu->t &= 0b0000110000011111U; ppu->t |= (uint16_t)(val & 0b00000111U) << 12; ppu->t |= (uint16_t)(val & 0b11111000U) << 2; } else { PPU_LOG("PPU: Scroll X %02x\n", val); ppu->t &= ~(0b11111U); ppu->t |= (val & 0b11111000U) >> 3; ppu->x = (val & 0b111U); } ppu->latch = !ppu->latch; } else if (ppu_reg_addr == addr) { VRAM_LOG("PPU: ADDR %02x\n", val); if (ppu->latch) { ppu->t &= 0xFF00U; ppu->t |= val; ppu->addr = (ppu->t & 0x3FFFU); VRAM_LOG("PPU: VRAM ADDR %04x\n", ppu->addr); nes_ppu_internal_copy_x(ppu); nes_ppu_internal_copy_y(ppu); PPU_LOG("PPU: Scroll N, X, Y = %d, %d, %d\n", ppu->control & ppu_Control_Nametable_Mask, ppu->scroll_x, ppu->scroll_y); } else { ppu->t &= 0x00FFU; ppu->t |= (uint16_t)(0x3FU & val) << 8; } ppu->latch = !ppu->latch; } else if (ppu_reg_data == addr) { if (ppu->addr >= nes_ppu_mem_size) { printf("!!! PPU: MEM OOB: %04x\n", ppu->addr); } else if (ppu->addr >= nes_ppu_mem_pal_start) { uint8_t pal_addr = (ppu->addr - nes_ppu_mem_pal_start) & (nes_ppu_mem_pal_size - 1); PPU_LOG("PPU: PAL %02x < %02x\n", pal_addr, val); ppu->palette[pal_addr] = val; if ((pal_addr & 0b11) == 0) { ppu->palette[pal_addr & 0xF] = val; } } else if (ppu->addr >= nes_ppu_mem_vram_start) { uint16_t vram_addr = ppu->addr - nes_ppu_mem_vram_start; if (vram_addr >= nes_ppu_mem_vram_size) { printf("!!! PPU: VRAM OOB: %04x\n", vram_addr); vram_addr &= (nes_ppu_mem_vram_size - 1); } #ifdef DEBUG_VRAM { int page = vram_addr >> 10; int loc = vram_addr & 0x3FFU; if (loc < 960) { int y = loc / 32; int x = loc % 32; printf("PPU: VRAM Page %d @ %2d,%2d : %02x\n", page, y, x, val); } else { printf("PPU: VRAM Attr %2d : %02x\n", loc - 960, val); } } #endif // DEBUG_VRAM // printf("PPU: VRAM %04x < %02x\n", vram_addr, val); nes_vram_write(ppu->mapper, ppu->map_data, vram_addr, val); } else { // PPU_LOG("PPU: CHR MEM WRITE %04x > %02x\n", ppu->addr, val); nes_chr_write(ppu->mapper, ppu->map_data, ppu->addr, val); } ppu->addr += (ppu->control & ppu_Control_VRAM_Inc) ? 32 : 1; } } void nes_ppu_reset(nes_ppu* ppu) { ppu->control = 0; ppu->mask = 0; ppu->latch = 0; ppu->scroll_x = 0; ppu->scroll_y = 0; ppu->data = 0; ppu->frame = 0; ppu->scanline = 0; ppu->cycle = 0; } int nes_ppu_init(nes_ppu* ppu, const nes_cart* cart) { ppu->mapper = cart->mapper; ppu->map_data = cart->map_data; ppu->status = 0; ppu->oam_addr = 0; ppu->addr = 0; ppu->t = 0; nes_ppu_reset(ppu); return 0; } nes_ppu_Result nes_ppu_run(nes_ppu* ppu, int cycles) { nes_ppu_Result result = ppu_Result_Running; int next_cycle = ppu->cycle + cycles; if ( ppu->scanline < nes_ppu_render && ppu->cycle < 257 && next_cycle >= 257) { nes_ppu_internal_copy_x(ppu); if (ppu->scanline == 0) { nes_ppu_internal_copy_y(ppu); } } if ( NULL != ppu->mapper->scanline && ppu->scanline < nes_ppu_render && (ppu->mask & (ppu_Mask_Back | ppu_Mask_Sprite)) && ppu->cycle < 260 && next_cycle >= 260) { ppu->mapper->scanline(ppu->map_data); } ppu->cycle = next_cycle; while (ppu->cycle >= nes_ppu_dots) { ppu->cycle -= nes_ppu_dots; if ( ppu->scanline < nes_ppu_prerender && (ppu->frame & 1)) { // Prerender line is one dot shorter in odd frames // Fake it by incrementing the cycle in this case // TODO: Only if actually rendering ppu->cycle++; } ppu->scanline++; if (ppu->scanline >= nes_ppu_frame) { ppu->status &= ~(ppu_Status_VBlank | ppu_Status_Hit); ppu->hit_line = 0; ppu->hit_dot = 0; ppu->scanline = 0; ppu->frame++; result = ppu_Result_VBlank_Off; } else if (ppu->scanline >= nes_ppu_active) { ppu->status |= ppu_Status_VBlank; if (ppu->control & ppu_Control_VBlank) { result = ppu_Result_VBlank_On; } } else { if ( ppu->scanline > nes_ppu_prerender && ppu->scanline < nes_ppu_render) { ppu->scroll_y++; if (ppu->scroll_y >= nes_ppu_render_h) { ppu->scroll_y -= nes_ppu_render_h; ppu->control ^= 0b10; } } result = ppu_Result_Ready; } } if ( ppu->hit_line > 0 && ppu->scanline > ppu->hit_line && ppu->cycle >= ppu->hit_dot) { if (!(ppu->status & ppu_Status_Hit)) { PPU_LOG("PPU: Hit @ %d, %d\n", ppu->hit_line + 1, ppu->hit_dot); PPU_LOG("(Currently @ %d, %d)\n", ppu->scanline, ppu->cycle); } ppu->status |= ppu_Status_Hit; } return result; }