#include #include "ppu.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) { val = ppu->oam[ppu->oam_addr]; } else if (ppu_reg_data == addr) { val = ppu->data; if (ppu->addr < nes_ppu_mem_vram_start) { ppu->data = ppu->chr_mem[ppu->addr]; } else if (ppu->addr < nes_ppu_mem_vram_start + nes_ppu_mem_vram_size) { ppu->data = ppu->vram[ppu->addr - nes_ppu_mem_vram_start]; } else if (ppu->addr < nes_ppu_mem_pal_start) { ppu->data = ppu->chr_mem[ppu->addr]; } else if (ppu->addr < nes_ppu_mem_size) { uint8_t pal_addr = (ppu->addr - nes_ppu_mem_pal_start) & (nes_ppu_mem_pal_size - 1); ppu->data = ppu->palette[pal_addr]; } ppu->addr += (ppu->status & ppu_Control_VRAM_Inc) ? 32 : 1; } // fprintf(stdout, "PPU: <-R $%04x %02x\n", addr, val); return val; } void nes_ppu_write(nes_ppu* ppu, uint16_t addr, uint8_t val) { // fprintf(stdout, "PPU: W-> $%04x %02x\n", addr, val); if (ppu_reg_ctrl == addr) { ppu->control = val; // TODO: Trigger NMI if it's enabled during VBlank? } else if (oam_reg_addr == addr) { ppu->oam_addr = val; } else if (oam_reg_data == addr) { ppu->oam[ppu->oam_addr++] = val; } else if (ppu_reg_mask == addr) { ppu->mask = val; } else if (ppu_reg_scroll == addr) { if (ppu->latch) { ppu->scroll &= 0xFF00U; ppu->scroll |= val; } else { ppu->scroll &= 0x00FFU; ppu->scroll |= (uint16_t)val << 8; } ppu->latch = !ppu->latch; } else if (ppu_reg_addr == addr) { if (ppu->latch) { ppu->addr &= 0x3F00U; ppu->addr |= val; // printf("PPU: VRAM ADDR %04x\n", ppu->addr); } else { ppu->addr &= 0x00FFU; ppu->addr |= (uint16_t)val << 8; } ppu->latch = !ppu->latch; } else if (ppu_reg_data == addr) { if (ppu->addr >= nes_ppu_mem_size) { printf("!!! PPU: MEM OOB: %04x", 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); // fprintf(stderr, "PPU PAL %02x < %02x\n", pal_addr, val); ppu->palette[pal_addr] = 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", vram_addr); vram_addr &= (nes_ppu_mem_vram_size - 1); } ppu->vram[vram_addr] = val; } ppu->addr += (ppu->status & ppu_Control_VRAM_Inc) ? 32 : 1; } } void nes_ppu_reset(nes_ppu* ppu) { ppu->control = 0; ppu->mask = 0; ppu->latch = 0; ppu->scroll = 0; ppu->data = 0; ppu->frame = 0; ppu->scanline = 0; ppu->cycle = 0; } void nes_ppu_init(nes_ppu* ppu, uint8_t* chr_mem) { ppu->chr_mem = chr_mem; ppu->status = 0; ppu->oam_addr = 0; ppu->addr = 0; nes_ppu_reset(ppu); } int nes_ppu_run(nes_ppu* ppu, int cycles) { int vblank = 0; ppu->cycle += cycles; if ( 0 != ppu->hit_line && ppu->scanline > ppu->hit_line && ppu->cycle > ppu->hit_dot) { ppu->status |= ppu_Status_Hit; } 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 ppu->cycle++; } ppu->scanline++; if (ppu->scanline >= nes_ppu_prerender + nes_ppu_height + nes_ppu_postrender + nes_ppu_vblank) { ppu->status &= ~(ppu_Status_VBlank | ppu_Status_Hit); ppu->hit_line = 0; ppu->hit_dot = 0; ppu->scanline = 0; ppu->frame++; // TODO: Render callback if vblank was previously set } else if (ppu->scanline >= nes_ppu_prerender + nes_ppu_height + nes_ppu_postrender) { ppu->status |= ppu_Status_VBlank; if (ppu->control & ppu_Control_VBlank) { vblank = 1; } } } return vblank; } int nes_ppu_cycles_til_vblank(nes_ppu* ppu) { int cycles_til_vblank = nes_ppu_active_cycles - ( ppu->cycle + (ppu->scanline * nes_ppu_dots)); return (cycles_til_vblank > 0 ? cycles_til_vblank : nes_ppu_vblank_cycles + cycles_til_vblank); }