static uint8_t memory_read_dma(struct nes_state *state, uint32_t offset); static void ppu_reset(struct nes_state *state) { struct ppu_state *ppu = &state->ppu; __builtin_memset(ppu, 0, sizeof(struct ppu_state)); ppu->scanline = 261; } static uint32_t nt_mirror(uint8_t mode, uint32_t addr) { addr &= 0x0fff; uint32_t table = addr / 0x400; uint32_t offset = addr & 0x03ff; switch(mode) { case 0: // Horizontal (A A B B) if(table == 0 || table == 1) return offset; return offset + 0x400; case 1: // Vertical (A B A B) return (table & 1) * 0x400 + offset; case 2: // 1-screen lower return offset; case 3: // 1-screen upper return offset + 0x400; case 4: // 4-screen (unsupported in base PPU logic) default: return addr; } } static uint32_t palette_mirror(uint32_t addr) { addr &= 0x1f; if((addr & 0x13) == 0x10) { addr &= ~0x10; } return addr; } static uint8_t ppu_read_mem(struct nes_state *state, uint32_t addr) { struct ppu_state *ppu = &state->ppu; addr &= 0x3fff; if(addr < 0x2000) { return state->chrrom[addr]; } else if(addr < 0x3f00) { uint32_t nt_index = addr - 0x2000; uint32_t mirrored = nt_mirror(state->ines.mirroring, nt_index); return ppu->vram[mirrored]; } else { uint32_t mirrored = palette_mirror(addr); return ppu->palette[mirrored]; } } static void ppu_write_mem(struct nes_state *state, uint32_t addr, uint8_t value) { struct ppu_state *ppu = &state->ppu; addr &= 0x3fff; if(addr < 0x2000) { return; // CHR ROM is read-only } else if(addr < 0x3f00) { uint32_t mirrored = nt_mirror(state->ines.mirroring, addr - 0x2000); ppu->vram[mirrored] = value; } else { ppu->palette[palette_mirror(addr)] = value; } } static void ppu_write_2000(struct nes_state *state, uint8_t value) { struct ppu_state *ppu = &state->ppu; ppu->control = value; ppu->nt_x_offset = (value & 0x01) ? 0x400 : 0; ppu->nt_y_offset = (value & 0x02) ? 0x800 : 0; // printf("write 0x2000 %d\n", value); } static void ppu_write_2001(struct nes_state *state, uint8_t value) { state->ppu.mask = value; // printf("PPU $2001 write: %02x (BG: %s, SPR: %s)\n", value, (value & 0x08) ? "on" : "off", (value & 0x10) ? "on" : "off"); } static void ppu_write_2003(struct nes_state *state, uint8_t value) { state->ppu.oam_addr = value; // printf("write 0x2003 %d\n", value); } static void ppu_write_2004(struct nes_state *state, uint8_t value) { state->ppu.oam[state->ppu.oam_addr] = value; state->ppu.oam_addr++; // printf("write 0x2004 %d\n", value); } static void ppu_write_2005(struct nes_state *state, uint8_t value) { struct ppu_state *ppu = &state->ppu; printf("2005 write at sc=%u dot=%u: %02x\n", ppu->scanline, ppu->dot, value); if(ppu->write_latch == 0) { ppu->fine_x = value & 0x07; ppu->vram_addr = (ppu->vram_addr & ~0x001f) | ((value >> 3) & 0x1f); ppu->write_latch = 1; } else { uint32_t coarse_y = (value >> 3) & 0x1f; uint32_t fine_y = value & 0x07; uint32_t nt_y = (value & 0x80) ? 0x800 : 0; // Bits 5–9: coarse Y, bit 11: nt_y, bits 12–14: fine Y ppu->vram_addr = (ppu->vram_addr & ~0x7be0) | (coarse_y << 5) | nt_y | (fine_y << 12); ppu->write_latch = 0; } } static void ppu_write_2006(struct nes_state *state, uint8_t value) { struct ppu_state *ppu = &state->ppu; printf("2006 write at sc=%u dot=%u: %02x\n", ppu->scanline, ppu->dot, value); if(ppu->write_latch == 0) { ppu->vram_addr = ((uint32_t)(value & 0x3f)) << 8; ppu->write_latch = 1; } else { ppu->vram_addr |= value; ppu->write_latch = 0; } } static void ppu_write_2007(struct nes_state *state, uint8_t value) { struct ppu_state *ppu = &state->ppu; // printf("PPU $2007 write: addr=%04x val=%02x\n", ppu->vram_addr, value); ppu_write_mem(state, ppu->vram_addr, value); // printf("w%04x:%02x cx=%u cy=%u fx=%u ntx=%x nty=%x ", ppu->vram_addr, value, ppu->coarse_x, ppu->coarse_y, ppu->fine_x, ppu->nt_x_offset, ppu->nt_y_offset); ppu->vram_addr += (ppu->control & 0x04) ? 32 : 1; ppu->vram_addr &= 0x3fff; } static uint8_t ppu_read_2002(struct nes_state *state) { struct ppu_state *ppu = &state->ppu; uint8_t result = ppu->vblank | ppu->sprite_zero_hit | ppu->sprite_overflow; // printf("PPU $2002 read at PC=%04x: result=%02x (vblank=%02x)\n", state->cpu.pc, result, ppu->vblank); ppu->vblank = 0; ppu->write_latch = 0; return result; } static uint8_t ppu_read_2004(struct nes_state *state) { printf("ppu_read_2004\n"); return state->ppu.oam[state->ppu.oam_addr]; } static uint8_t ppu_read_2007(struct nes_state *state) { struct ppu_state *ppu = &state->ppu; // printf("ppu_read_2007\n"); uint8_t value = ppu_read_mem(state, ppu->vram_addr); ppu->vram_addr += (ppu->control & 0x04) ? 32 : 1; ppu->vram_addr &= 0x3fff; return value; } static void ppu_inc_x(struct nes_state *state) { struct ppu_state *ppu = &state->ppu; ppu->render_coarse_x++; if(ppu->render_coarse_x == 32) { ppu->render_coarse_x = 0; ppu->render_nt_x ^= 0x400; } } static void ppu_inc_y(struct nes_state *state) { struct ppu_state *ppu = &state->ppu; ppu->render_fine_y++; if(ppu->render_fine_y == 8) { ppu->render_fine_y = 0; ppu->render_coarse_y++; if(ppu->render_coarse_y == 30) { ppu->render_coarse_y = 0; ppu->render_nt_y ^= 0x800; } } } static uint8_t ppu_fetch_name_table(struct nes_state *state) { struct ppu_state *ppu = &state->ppu; uint32_t addr = 0x2000 | ppu->render_nt_x | ppu->render_nt_y | (ppu->render_coarse_y << 5) | ppu->render_coarse_x; return ppu_read_mem(state, addr); } static uint8_t ppu_fetch_attribute(struct nes_state *state) { struct ppu_state *ppu = &state->ppu; uint32_t x = ppu->render_coarse_x; uint32_t y = ppu->render_coarse_y; uint32_t nt = (ppu->render_nt_y ? 0x800 : 0) | (ppu->render_nt_x ? 0x400 : 0); uint32_t addr = 0x23c0 | (nt & 0x0c00) | ((y >> 2) << 3) | (x >> 2); return ppu_read_mem(state, addr); } static uint8_t ppu_fetch_pattern_lo(struct nes_state *state, uint8_t tile) { struct ppu_state *ppu = &state->ppu; uint32_t base = (ppu->control & 0x10) ? 0x1000 : 0x0000; uint32_t addr = base + tile * 16 + ppu->render_fine_y; return ppu_read_mem(state, addr); } static uint8_t ppu_fetch_pattern_hi(struct nes_state *state, uint8_t tile) { struct ppu_state *ppu = &state->ppu; uint32_t base = (ppu->control & 0x10) ? 0x1000 : 0x0000; uint32_t addr = base + tile * 16 + ppu->render_fine_y + 8; return ppu_read_mem(state, addr); } static void ppu_load_background_shifters(struct nes_state *state, uint8_t pattern_lo, uint8_t pattern_hi) { struct ppu_state *ppu = &state->ppu; ppu->bg_shift_lo = (ppu->bg_shift_lo & 0xff00) | pattern_lo; ppu->bg_shift_hi = (ppu->bg_shift_hi & 0xff00) | pattern_hi; uint8_t pal = ppu->bg_attribute_latch & 3; ppu->attr_shift_lo = (ppu->attr_shift_lo & 0xff00) | ((pal & 1) ? 0xff : 0x00); ppu->attr_shift_hi = (ppu->attr_shift_hi & 0xff00) | ((pal & 2) ? 0xff : 0x00); } static void ppu_shift_background(struct nes_state *state) { struct ppu_state *ppu = &state->ppu; ppu->bg_shift_lo <<= 1; ppu->bg_shift_hi <<= 1; ppu->attr_shift_lo <<= 1; ppu->attr_shift_hi <<= 1; } static void ppu_render_pixel(struct nes_state *state) { struct ppu_state *ppu = &state->ppu; if(!(ppu->mask & 0x08)) { return; } uint32_t x = ppu->dot - 1; uint32_t y = ppu->scanline; uint8_t shift = 15 - ppu->fine_x; uint8_t pattern = ((ppu->bg_shift_hi >> shift) & 1) << 1; pattern |= (ppu->bg_shift_lo >> shift) & 1; uint8_t attrib = ((ppu->attr_shift_hi >> shift) & 1) << 1; attrib |= (ppu->attr_shift_lo >> shift) & 1; uint8_t index = (attrib << 2) | pattern; uint8_t color_index; if(index == 0) { color_index = ppu->palette[0]; } else { color_index = ppu->palette[index & 0x1f]; } ppu->pixels[y * 256 + x] = color_index; } static void ppu_evaluate_sprites(struct nes_state *state, uint32_t target_scanline) { struct ppu_state *ppu = &state->ppu; ppu->sprite_count = 0; for(uint32_t i = 0; i < 64; i++) { uint8_t y = ppu->oam[i * 4 + 0]; uint8_t height = (ppu->control & 0x20) ? 16 : 8; if(target_scanline >= y && target_scanline < y + height) { if(ppu->sprite_count < 8) { uint8_t *dest = &ppu->secondary_oam[ppu->sprite_count * 4]; dest[0] = y; dest[1] = ppu->oam[i * 4 + 1]; dest[2] = ppu->oam[i * 4 + 2]; dest[3] = ppu->oam[i * 4 + 3]; ppu->sprite_count++; } else { ppu->sprite_overflow = 0x20; break; } } } } static uint8_t ppu_fetch_sprite_pattern_lo(struct nes_state *state, uint8_t tile, uint8_t y_offset, uint8_t attr, uint8_t height) { uint32_t addr; if(height == 16) { uint32_t bank = (tile & 1) ? 0x1000 : 0x0000; tile &= 0xfe; addr = bank | ((tile << 4) + (y_offset & 0x07)); } else { uint32_t base = (state->ppu.control & 0x08) ? 0x1000 : 0x0000; addr = base | ((tile << 4) + (y_offset & 0x07)); } return ppu_read_mem(state, addr); } static uint8_t ppu_fetch_sprite_pattern_hi(struct nes_state *state, uint8_t tile, uint8_t y_offset, uint8_t attr, uint8_t height) { uint32_t addr; if(height == 16) { uint32_t bank = (tile & 1) ? 0x1000 : 0x0000; tile &= 0xfe; addr = bank | ((tile << 4) + (y_offset & 0x07) + 8); } else { uint32_t base = (state->ppu.control & 0x08) ? 0x1000 : 0x0000; addr = base | ((tile << 4) + (y_offset & 0x07) + 8); } return ppu_read_mem(state, addr); } static void ppu_render_sprites(struct nes_state *state) { struct ppu_state *ppu = &state->ppu; if(!(ppu->mask & 0x10)) { return; } uint32_t x = ppu->dot - 1; uint32_t y = ppu->scanline; uint8_t bg_pixel = ppu->pixels[y * 256 + x] & 0x3f; for(uint32_t i = 0; i < ppu->sprite_count; i++) { uint8_t *spr = &ppu->secondary_oam[i * 4]; uint8_t sy = spr[0]; uint8_t tile = spr[1]; uint8_t attr = spr[2]; uint8_t sx = spr[3]; uint8_t height = (ppu->control & 0x20) ? 16 : 8; if(y < sy || y >= sy + height) { continue; } if(x < sx || x >= sx + 8) { continue; } uint8_t offset_y = y - sy; if(attr & 0x80) { offset_y = height - 1 - offset_y; } uint8_t shift = 7 - (x - sx); if(attr & 0x40) { shift = x - sx; } uint8_t pattern_lo = ppu_fetch_sprite_pattern_lo(state, tile, offset_y, attr, height); uint8_t pattern_hi = ppu_fetch_sprite_pattern_hi(state, tile, offset_y, attr, height); uint8_t lo_bit = (pattern_lo >> shift) & 1; uint8_t hi_bit = (pattern_hi >> shift) & 1; uint8_t spr_pixel = (hi_bit << 1) | lo_bit; if(spr_pixel == 0) { continue; } uint8_t palette = (attr & 3) << 2; uint8_t color_index = ppu_read_mem(state, 0x3f10 + palette + spr_pixel); if(i == 0 && bg_pixel && spr_pixel) { ppu->sprite_zero_hit = 0x40; } if(!(attr & 0x20) || bg_pixel == 0) { ppu->pixels[y * 256 + x] = color_index; } } } static void ppu_copy_horizontal_scroll(struct nes_state *state) { struct ppu_state *ppu = &state->ppu; uint32_t addr = ppu->vram_addr; ppu->render_coarse_x = (addr >> 0) & 0x1f; ppu->render_nt_x = (addr & 0x400) ? 0x400 : 0; } static void ppu_copy_vertical_scroll(struct nes_state *state) { struct ppu_state *ppu = &state->ppu; uint32_t addr = ppu->vram_addr; ppu->render_coarse_y = (addr >> 5) & 0x1f; ppu->render_fine_y = (addr >> 12) & 0x07; ppu->render_nt_y = (addr & 0x800) ? 0x800 : 0; } __attribute__((flatten)) static void ppu_tick(struct nes_state *state) { struct ppu_state *ppu = &state->ppu; uint32_t scanline = ppu->scanline; uint32_t dot = ppu->dot; if(scanline < 240 || scanline == 261) { if((dot >= 1 && dot <= 256) || (dot >= 321 && dot <= 336)) { ppu_shift_background(state); switch((dot - 1) % 8) { case 0: ppu->next_tile = ppu_fetch_name_table(state); break; case 2: ppu->next_attr = ppu_fetch_attribute(state); break; case 4: ppu->next_lo = ppu_fetch_pattern_lo(state, ppu->next_tile); break; case 6: ppu->next_hi = ppu_fetch_pattern_hi(state, ppu->next_tile); break; case 7: { uint8_t attr = ppu->next_attr; uint8_t shift = ((ppu->render_coarse_y >> 2) & 2) | ((ppu->render_coarse_x >> 1) & 1); ppu->bg_attribute_latch = (attr >> (shift * 2)) & 3; ppu_load_background_shifters(state, ppu->next_lo, ppu->next_hi); break; } } if(dot % 8 == 0) { ppu_inc_x(state); } } if(scanline < 240 && dot >= 1 && dot <= 256) { ppu_render_pixel(state); ppu_render_sprites(state); } if(dot == 256) { ppu_inc_y(state); } if(dot == 257) { ppu_copy_horizontal_scroll(state); if(scanline < 240) { ppu_evaluate_sprites(state, scanline + 1); } } if(scanline == 261 && dot >= 280) { // && dot <= 304) { ppu_copy_vertical_scroll(state); } } if(scanline == 241 && dot == 1) { ppu->vblank = 0x80; state->nmi_pending = 1; ppu->frame_ready = 1; } if(scanline == 261 && dot == 1) { ppu->vblank = 0; ppu->sprite_overflow = 0; ppu->sprite_zero_hit = 0; } ppu->dot++; if(ppu->dot == 341) { ppu->dot = 0; ppu->scanline++; if(ppu->scanline == 262) { ppu->scanline = 0; } } } static void ppu_dma_4014(struct nes_state *state, uint8_t page) { uint32_t base = (uint32_t)page << 8; uint32_t i; uint32_t idle = (state->cycle & 1) ? 2 : 1; while(idle--) { ppu_tick(state); ppu_tick(state); ppu_tick(state); state->cycle++; } for(i = 0; i < 256; i++) { ppu_tick(state); ppu_tick(state); ppu_tick(state); state->cycle++; uint8_t val = memory_read_dma(state, base + i); ppu_tick(state); ppu_tick(state); ppu_tick(state); state->cycle++; ppu_write_2004(state, val); } }