diff options
| author | Peter Fors <peter.fors@mindkiller.com> | 2025-03-31 20:31:05 +0200 |
|---|---|---|
| committer | Peter Fors <peter.fors@mindkiller.com> | 2025-03-31 20:31:05 +0200 |
| commit | a386ef64f6376b3ef8434a6cdf456495287fcbca (patch) | |
| tree | e2da9f72ce0a565b4fac2fc8be19ab3497286b36 /ppu.c | |
| parent | d5486a5af100fb37fac08b60d862ac14943853ce (diff) | |
currently 90% working
Diffstat (limited to 'ppu.c')
| -rw-r--r-- | ppu.c | 677 |
1 files changed, 341 insertions, 336 deletions
@@ -1,490 +1,495 @@ static uint8_t memory_read_dma(struct nes_state *state, uint32_t offset); -static void ppu_reset_scroll_latch(struct nes_state *state) { - state->ppu.write_toggle = 0; -} - -static uint32_t compute_nt_address(struct nes_state *state) { +static void ppu_reset(struct nes_state *state) { struct ppu_state *ppu = &state->ppu; - return 0x2000 | ppu->nt_base_y | ppu->nt_base_x | ppu->coarse_y_offs | ppu->coarse_x_offs; -} + __builtin_memset(ppu, 0, sizeof(struct ppu_state)); + ppu->scanline = 261; -static uint32_t compute_at_address(struct nes_state *state) { - struct ppu_state *ppu = &state->ppu; - uint32_t nt_offset = ppu->nt_base_y | ppu->nt_base_x; - uint32_t attr_x = (ppu->coarse_x_offs >> 2) & 0x07; - uint32_t attr_y = (ppu->coarse_y_offs >> 2) & 0x38; - return 0x23c0 | nt_offset | attr_y | attr_x; } -static void increment_x(struct nes_state *state) { +static uint8_t ppu_read_mem(struct nes_state *state, uint32_t addr) { struct ppu_state *ppu = &state->ppu; + addr &= 0x3fff; - if(ppu->coarse_x_offs == 31 << 0) { - ppu->coarse_x_offs = 0; - ppu->nt_base_x ^= 0x400; + if(addr < 0x2000) { + return state->chrrom[addr]; + } else if(addr < 0x3f00) { + uint32_t nt_index = addr - 0x2000; + uint32_t page = nt_index / 0x400; + uint32_t offset = nt_index & 0x03ff; + if(page == 0 || page == 2) { + return ppu->vram[offset]; + } else { + return ppu->vram[offset + 0x400]; + } } else { - ppu->coarse_x_offs += 1 << 0; + addr &= 0x1f; + if((addr & 0x13) == 0x10) { + addr &= ~0x10; + } + return ppu->palette[addr]; } } -static void increment_y(struct nes_state *state) { +static void ppu_write_mem(struct nes_state *state, uint32_t addr, uint8_t value) { struct ppu_state *ppu = &state->ppu; - if(ppu->fine_y < 7) { - ppu->fine_y++; - return; - } - - ppu->fine_y = 0; - - uint32_t coarse_y = ppu->coarse_y_offs >> 5; + addr &= 0x3fff; +if(addr >= 0x3f00 && addr < 0x3f20) { + printf("PPU write: %04x = %02x\n", addr, value); +} - if(coarse_y == 29) { - ppu->coarse_y_offs = 0; - ppu->nt_base_y ^= 0x800; - } else if(coarse_y == 31) { - ppu->coarse_y_offs = 0; - // Do not toggle nt_base_y — stays the same + if(addr < 0x2000) { + // Ignore write to CHR ROM + return; + } else if(addr < 0x3f00) { + uint32_t nt_index = addr - 0x2000; + uint32_t page = nt_index / 0x400; + uint32_t offset = nt_index & 0x03ff; + if(page == 0 || page == 2) { + ppu->vram[offset] = value; + } else { + ppu->vram[offset + 0x400] = value; + } } else { - ppu->coarse_y_offs += 1 << 5; + addr &= 0x1f; + if((addr & 0x13) == 0x10) { + addr &= ~0x10; + } + ppu->palette[addr] = value; + if(addr == 0) { + printf("Write to universal BG color: %02x\n", value); + } } } +static void ppu_write_2000(struct nes_state *state, uint8_t value) { + struct ppu_state *ppu = &state->ppu; -static void ppu_vram_write(struct nes_state *state, uint32_t addr, uint8_t value) { - // PPU address bus is 14 bits, wraps every 0x4000 - addr &= 0x3fff; + 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); +} - if(addr < 0x2000) { - // Pattern table (CHR ROM is read-only, ignore write) - return; - } +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"); +} - if(addr < 0x3f00) { - // Nametable RAM (CIRAM), mirrored every 0x1000 - uint32_t nt_index = addr & 0x0fff; - state->ciram[nt_index] = value; - return; - } +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); +} - if(addr < 0x4000) { - // Palette RAM (0x3F00–0x3FFF), mirrored every 32 bytes - addr = 0x3f00 + (addr & 0x1f); +static void ppu_write_2005(struct nes_state *state, uint8_t value) { + struct ppu_state *ppu = &state->ppu; - // Palette mirroring: $3F10/$3F14/$3F18/$3F1C mirror $3F00/$3F04/$3F08/$3F0C - if((addr & 0x13) == 0x10) { - addr &= ~0x10; - } + 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; - state->palette[addr & 0x1f] = value; + // 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 uint8_t ppu_vram_read(struct nes_state *state, uint32_t addr) { - // PPU address bus is 14 bits, wraps every 0x4000 - addr &= 0x3fff; - if(addr < 0x2000) { - // Pattern table (CHR ROM) - return state->chrrom[addr]; - } +static void ppu_write_2006(struct nes_state *state, uint8_t value) { + struct ppu_state *ppu = &state->ppu; +// printf("2006"); + 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; + + uint32_t addr = ppu->vram_addr; - if(addr < 0x3f00) { - // Nametables (0x2000–0x2FFF mirrored every 0x1000) - uint32_t nt_index = addr & 0x0fff; - return state->ciram[nt_index]; + ppu->coarse_x = (addr >> 0) & 0x1f; + ppu->coarse_y = (addr >> 5) & 0x1f; + ppu->nt_x_offset = (addr & 0x400) ? 0x400 : 0; + ppu->nt_y_offset = (addr & 0x800) ? 0x800 : 0; + ppu->fine_y = (addr >> 12) & 0x07; } +} - if(addr < 0x4000) { - // Palette RAM (0x3F00–0x3FFF, mirrored every 32 bytes) - addr = 0x3f00 + (addr & 0x1f); +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); - // Palette mirroring: $3F10/$3F14/$3F18/$3F1C mirror $3F00/$3F04/$3F08/$3F0C - if((addr & 0x13) == 0x10) { - addr &= ~0x10; - } + ppu_write_mem(state, ppu->vram_addr, value); - return state->palette[addr & 0x1f]; - } - - return 0; + ppu->vram_addr += (ppu->control & 0x04) ? 32 : 1; } static uint8_t ppu_read_2002(struct nes_state *state) { struct ppu_state *ppu = &state->ppu; - uint8_t result = ppu->status; - ppu->status &= ~PPU_STATUS_VBLANK; - ppu->write_toggle = 0; + 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 void ppu_write_2003(struct nes_state *state, uint8_t value) { - state->ppu.oam_addr = value; -} - static uint8_t ppu_read_2004(struct nes_state *state) { + printf("ppu_read_2004\n"); return state->ppu.oam[state->ppu.oam_addr]; } -static void ppu_write_2004(struct nes_state *state, uint8_t value) { - state->ppu.oam[state->ppu.oam_addr] = value; - state->ppu.oam_addr++; -} - - -static void ppu_write_2005(struct nes_state *state, uint8_t value) -{ +static uint8_t ppu_read_2007(struct nes_state *state) { struct ppu_state *ppu = &state->ppu; + printf("ppu_read_2007\n"); - if(ppu->write_toggle == 0) { - ppu->temp_fine_x = value & 7; - ppu->temp_coarse_x_offs = (value >> 3) & 0x1f; - ppu->temp_coarse_x_offs <<= 0; + uint8_t value = ppu_read_mem(state, ppu->vram_addr); - ppu->write_toggle = 1; - } else { - ppu->temp_fine_y = value & 7; - uint8_t coarse_y = (value >> 3) & 0x1f; - ppu->temp_coarse_y_offs = coarse_y << 5; + ppu->vram_addr += (ppu->control & 0x04) ? 32 : 1; + return value; +} - if(coarse_y >= 30) { - // vertical nametable select - ppu->temp_nt_base_y = 0x800; - } else { - ppu->temp_nt_base_y = 0x000; - } +static void ppu_inc_x(struct nes_state *state) { + struct ppu_state *ppu = &state->ppu; - ppu->write_toggle = 0; + ppu->coarse_x++; + if(ppu->coarse_x == 32) { + ppu->coarse_x = 0; + ppu->nt_x_offset ^= 0x400; } } -static void ppu_write_2006(struct nes_state *state, uint8_t value) -{ +static void ppu_inc_y(struct nes_state *state) { struct ppu_state *ppu = &state->ppu; - if(ppu->write_toggle == 0) { - // High byte: bits 14–8 of VRAM address - ppu->temp_nt_base_y = (value & 0x0c) << 8; - ppu->temp_nt_base_x = (value & 0x01) << 10; - ppu->temp_coarse_y_offs = (value & 0x03) << 3; - ppu->temp_coarse_y_offs <<= 5; + ppu->fine_y++; + if(ppu->fine_y == 8) { + ppu->fine_y = 0; + ppu->coarse_y++; - ppu->write_toggle = 1; - } else { - // Low byte: bits 7–0 of VRAM address - ppu->temp_coarse_x_offs = (value & 0x1f) << 0; - ppu->temp_fine_y = (value >> 2) & 7; - - // On second write, transfer temp -> current scroll state - ppu->coarse_x_offs = ppu->temp_coarse_x_offs; - ppu->coarse_y_offs = ppu->temp_coarse_y_offs; - ppu->fine_x = ppu->temp_fine_x; - ppu->fine_y = ppu->temp_fine_y; - ppu->nt_base_x = ppu->temp_nt_base_x; - ppu->nt_base_y = ppu->temp_nt_base_y; - - ppu->write_toggle = 0; + if(ppu->coarse_y == 30) { + ppu->coarse_y = 0; + ppu->nt_y_offset ^= 0x800; + } } } -static uint8_t ppu_read_2007(struct nes_state *state) { +static uint8_t ppu_fetch_name_table(struct nes_state *state) { struct ppu_state *ppu = &state->ppu; + uint32_t addr = 0x2000 | ppu->nt_x_offset | ppu->nt_y_offset | (ppu->coarse_y << 5) | ppu->coarse_x; - uint32_t addr = compute_nt_address(state); - if(ppu->fine_y) { - addr += ppu->fine_y; - } + uint8_t tile = ppu_read_mem(state, addr); + // printf("NT[%04x] = %02x (X:%u Y:%u)\n", addr, tile, ppu->coarse_x, ppu->coarse_y); + // if(ppu->nt_x_offset != 0 || ppu->nt_y_offset != 0) + // printf("NT[%04x] -> tile %02x (X:%d Y:%d ntx:%x nty:%x)\n", addr, tile, ppu->coarse_x, ppu->coarse_y, ppu->nt_x_offset, ppu->nt_y_offset); + return tile; +} - uint8_t result; - if(addr < 0x3f00) { - // Return buffered value, load new one - result = ppu->read_buffer; - ppu->read_buffer = ppu_vram_read(state, addr); - } else { - // Palette reads bypass buffer - result = ppu_vram_read(state, addr); - ppu->read_buffer = ppu_vram_read(state, addr & 0x2fff); // mirror to pattern/nametable area - } +static uint8_t ppu_fetch_attribute(struct nes_state *state) { + uint32_t x = state->ppu.coarse_x; + uint32_t y = state->ppu.coarse_y; + uint32_t nt = (state->ppu.nt_y_offset ? 0x800 : 0) | (state->ppu.nt_x_offset ? 0x400 : 0); + uint32_t addr = 0x23c0 | (nt & 0x0c00) | ((y >> 2) << 3) | (x >> 2); + uint8_t val = ppu_read_mem(state, addr); + // printf("ATTR[%04x] = %02x (X:%u Y:%u)\n", addr, val, x, y); + return val; +} - // Auto-increment address - if(ppu->ctrl & PPU_CTRL_INCREMENT) { - increment_y(state); - } else { - increment_x(state); - } - return result; +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 << 4) + ppu->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; -static void ppu_write_2007(struct nes_state *state, uint8_t value) { + uint32_t base = (ppu->control & 0x10) ? 0x1000 : 0x0000; + uint32_t addr = base + (tile << 4) + ppu->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; - uint32_t addr = compute_nt_address(state); - if(ppu->fine_y) - addr += ppu->fine_y; + 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); +} - ppu_vram_write(state, addr, value); +static void ppu_shift_background(struct nes_state *state) { + struct ppu_state *ppu = &state->ppu; - if(ppu->ctrl & PPU_CTRL_INCREMENT) { - increment_y(state); - } else { - increment_x(state); - } + ppu->bg_shift_lo <<= 1; + ppu->bg_shift_hi <<= 1; + ppu->attr_shift_lo <<= 1; + ppu->attr_shift_hi <<= 1; } -static uint8_t ppu_pattern_read(struct nes_state *state, uint8_t tile_index, uint8_t fine_y, uint8_t plane) { +static void ppu_render_pixel(struct nes_state *state) { struct ppu_state *ppu = &state->ppu; - uint32_t base = (ppu->ctrl & PPU_CTRL_BG_TABLE) ? 0x1000 : 0x0000; - uint32_t addr = base + (tile_index << 4) + fine_y; - if(plane) { - addr += 8; + if(!(ppu->mask & 0x08)) { + return; } - return ppu_vram_read(state, addr); + 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 = (index == 0) ? ppu->palette[0] : ppu->palette[index]; + + ppu->pixels[y * 256 + x] = color_index; } -static void ppu_eval_sprites(struct nes_state *state) { +static void ppu_evaluate_sprites(struct nes_state *state) { struct ppu_state *ppu = &state->ppu; - uint8_t scanline = ppu->scanline; - uint8_t height = (ppu->ctrl & PPU_CTRL_SPRITE_HEIGHT) ? 16 : 8; - uint8_t count = 0; + uint8_t sprite_height = (ppu->control & 0x20) ? 16 : 8; - for(uint8_t i = 0; i < 64; i++) { - uint8_t *sprite = &ppu->oam[i * 4]; - uint8_t sprite_y = sprite[0]; - - if(scanline >= sprite_y && scanline < sprite_y + height) { + for(uint32_t i = 0; i < 64; i++) { + uint8_t y = ppu->oam[i * 4]; + if(ppu->scanline >= y && ppu->scanline < y + sprite_height) { if(count < 8) { - uint8_t *dest = &ppu->sec_oam[count * 4]; - dest[0] = sprite[0]; - dest[1] = sprite[1]; - dest[2] = sprite[2]; - dest[3] = sprite[3]; + uint8_t j = count * 4; + uint32_t base = i * 4; + ppu->secondary_oam[j + 0] = ppu->oam[base + 0]; + ppu->secondary_oam[j + 1] = ppu->oam[base + 1]; + ppu->secondary_oam[j + 2] = ppu->oam[base + 2]; + ppu->secondary_oam[j + 3] = ppu->oam[base + 3]; count++; } else { - ppu->status |= PPU_STATUS_OVERFLOW; + ppu->sprite_overflow = 0x20; break; } } } +// if(count) printf("sprite_count: %d\n", count); + ppu->sprite_count = count; } -static void ppu_clear_sec_oam(struct nes_state *state) { - struct ppu_state *ppu = &state->ppu; - for(int i=0; i<32; i++) { - ppu->sec_oam[i] = 0xff; +static uint8_t ppu_fetch_sprite_tile(struct nes_state *state, uint8_t tile, uint8_t y_offset, uint8_t attr, uint8_t height) { + uint32_t base; + + if(height == 16) { + uint32_t bank = (tile & 1) ? 0x1000 : 0x0000; + tile &= 0xfe; + base = bank | ((tile & 0xfe) << 4); + } else { + base = ((state->ppu.control & 0x08) ? 0x1000 : 0x0000) | (tile << 4); } + + uint32_t addr = base + y_offset; + return ppu_read_mem(state, addr); } -static void ppu_render_sprites(struct nes_state *state) -{ +static void ppu_render_sprites(struct nes_state *state) { struct ppu_state *ppu = &state->ppu; - // Clear sprite pixel buffer - for(int x=0;x<256;x++) { - ppu->sprite_pixels[x] = 0; - ppu->sprite_zero_flags[x] = 0; + if(!(ppu->mask & 0x10)) { + return; } - for(int i=0;i<8;i++) { - uint8_t *sprite = &ppu->sec_oam[i * 4]; - uint8_t y = sprite[0]; - uint8_t tile = sprite[1]; - uint8_t attr = sprite[2]; - uint8_t x = sprite[3]; + uint32_t x = ppu->dot - 1; + uint32_t y = ppu->scanline; + uint8_t bg_pixel = ppu->pixels[y * 256 + x] & 0x3f; - uint8_t scanline = ppu->scanline; - uint8_t height = (ppu->ctrl & PPU_CTRL_SPRITE_HEIGHT) ? 16 : 8; - int sprite_y = scanline - y; + for(uint32_t i = 0; i < ppu->sprite_count; i++) { + uint8_t *spr = &ppu->secondary_oam[i * 4]; - if(attr & 0x80) { - // Vertical flip - sprite_y = height - 1 - sprite_y; + uint8_t sy = spr[0]; + uint8_t tile = spr[1]; + uint8_t attr = spr[2]; + uint8_t sx = spr[3]; + + if(x < sx || x >= sx + 8) { + continue; } - uint32_t base = (ppu->ctrl & PPU_CTRL_SPRITE_TABLE) ? 0x1000 : 0x0000; - uint32_t addr = base + tile * 16 + sprite_y; + uint8_t height = (ppu->control & 0x20) ? 16 : 8; + uint8_t offset_y = y - sy; - uint8_t lo = ppu_vram_read(state, addr); - uint8_t hi = ppu_vram_read(state, addr + 8); + if(attr & 0x80) { + offset_y = height - 1 - offset_y; + } - for(int j=0;j<8;j++) { - int sx = x + (attr & 0x40 ? j : (7 - j)); // horizontal flip - if(sx >= 256) - continue; + uint8_t shift = 7 - (x - sx); + if(attr & 0x40) { + shift = x - sx; + } - uint8_t bit = 1 << j; - uint8_t p0 = (lo & (1 << (7 - j))) ? 1 : 0; - uint8_t p1 = (hi & (1 << (7 - j))) ? 1 : 0; - uint8_t color = (attr & 0x03) << 2 | (p1 << 1) | p0; + uint8_t pattern_lo = ppu_fetch_sprite_tile(state, tile, offset_y, attr, height); + uint8_t pattern_hi = ppu_fetch_sprite_tile(state, tile, offset_y + 8, attr, height); - if((p0 | p1) == 0) - continue; + 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(ppu->sprite_pixels[sx] == 0) { - ppu->sprite_pixels[sx] = color | 0x10; - if(i == 0) - ppu->sprite_zero_flags[sx] = 1; - } + if(spr_pixel == 0) { + continue; } - } -} + uint8_t palette = (attr & 3) << 2; + uint8_t color_index = ppu_read_mem(state, 0x3f10 + palette + spr_pixel); -static void ppu_tick(struct nes_state *state) { - struct ppu_state *ppu = &state->ppu; - - if(ppu->scanline == 241 && ppu->dot == 1) { - ppu->status |= PPU_STATUS_VBLANK; - ppu->nmi_occurred = 1; - } + if(i == 0 && bg_pixel && spr_pixel) { + ppu->sprite_zero_hit = 0x40; + } - if(ppu->scanline == 261 && ppu->dot == 1) { - ppu->status &= ~PPU_STATUS_VBLANK; - ppu->status &= ~PPU_STATUS_SPRITE0_HIT; - ppu->status &= ~PPU_STATUS_OVERFLOW; - ppu->nmi_occurred = 0; + if(!(attr & 0x20) || bg_pixel == 0) { + ppu->pixels[y * 256 + x] = color_index; + } } +} - if((ppu->scanline < 240 || ppu->scanline == 261) && (ppu->dot >= 1 && ppu->dot <= 256)) { - uint32_t cycle = (ppu->dot - 1) % 8; - - switch(cycle) { - case 0: { - ppu->bg_tile_lsb = (ppu->bg_tile_lsb & 0xffff0000) | ppu->tile_lsb; - ppu->bg_tile_msb = (ppu->bg_tile_msb & 0xffff0000) | ppu->tile_msb; - ppu->bg_attr_lsb = (ppu->attr_byte & 1) ? 0xff : 0x00; - ppu->bg_attr_msb = (ppu->attr_byte & 2) ? 0xff : 0x00; - } break; - - case 1: { - ppu->nt_byte = ppu_vram_read(state, compute_nt_address(state)); - } break; - - case 3: { - ppu->attr_byte = ppu_vram_read(state, compute_at_address(state)); - } break; - - case 5: { - ppu->tile_lsb = ppu_pattern_read(state, ppu->nt_byte, ppu->fine_y, 0); - } break; +static void ppu_copy_horizontal_scroll(struct nes_state *state) { + struct ppu_state *ppu = &state->ppu; - case 7: { - ppu->tile_msb = ppu_pattern_read(state, ppu->nt_byte, ppu->fine_y, 1); - } break; - } + uint32_t addr = ppu->vram_addr; - ppu->bg_tile_lsb <<= 1; - ppu->bg_tile_msb <<= 1; - ppu->bg_attr_lsb <<= 1; - ppu->bg_attr_msb <<= 1; + ppu->coarse_x = (addr >> 0) & 0x1f; + ppu->nt_x_offset = (addr & 0x400) ? 0x400 : 0; +} - if(ppu->scanline < 240) { - uint8_t p0 = (ppu->bg_tile_lsb & 0x8000) ? 1 : 0; - uint8_t p1 = (ppu->bg_tile_msb & 0x8000) ? 1 : 0; - uint8_t a0 = (ppu->bg_attr_lsb & 0x80) ? 1 : 0; - uint8_t a1 = (ppu->bg_attr_msb & 0x80) ? 1 : 0; - uint8_t color = (a1 << 3) | (a0 << 2) | (p1 << 1) | p0; +static void ppu_copy_vertical_scroll(struct nes_state *state) { + struct ppu_state *ppu = &state->ppu; - uint32_t index = ppu->scanline * 256 + (ppu->dot - 1); + uint32_t addr = ppu->vram_addr; - uint8_t bg_color = color; - uint8_t sprite_color = ppu->sprite_pixels[ppu->dot - 1]; + ppu->coarse_y = (addr >> 5) & 0x1f; + ppu->nt_y_offset = (addr & 0x800) ? 0x800 : 0; + ppu->fine_y = (addr >> 12) & 0x07; +} - uint8_t final_color = bg_color; +__attribute__((flatten)) +static void ppu_tick(struct nes_state *state) { + struct ppu_state *ppu = &state->ppu; - if(sprite_color && ((bg_color & 3) == 0 || !(ppu->mask & 0x04))) { - // Sprite above BG, or BG is transparent - final_color = sprite_color; + uint32_t scanline = ppu->scanline; + uint32_t dot = ppu->dot; + + if(scanline < 240) { + 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->coarse_y >> 2) & 2) | ((ppu->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(ppu->sprite_zero_flags[ppu->dot - 1] && - (bg_color & 3) && (sprite_color & 0x1f) && - (ppu->mask & 0x18)) { - ppu->status |= PPU_STATUS_SPRITE0_HIT; + if(dot % 8 == 0) { + ppu_inc_x(state); } + } - ppu->pixels[index] = final_color; + if(dot >= 1 && dot <= 256) { + ppu_render_pixel(state); + ppu_render_sprites(state); + } + if(dot == 256) { + ppu_inc_y(state); } - } - if(ppu->scanline < 240 && ppu->dot == 257) { - ppu_render_sprites(state); - } + if(dot == 257) { + ppu_evaluate_sprites(state); + ppu_copy_horizontal_scroll(state); + } - if(ppu->scanline < 240 || ppu->scanline == 261) { - if(ppu->dot == 256) { - increment_x(state); - increment_y(state); - } else if(ppu->dot == 328) { - increment_x(state); + if(scanline == 261 && dot >= 280 && dot <= 304) { + ppu_copy_vertical_scroll(state); } } - ppu->dot++; + if(scanline == 241 && dot == 1) { + ppu->vblank = 0x80; + state->nmi_pending = 1; + ppu->frame_ready = 1; + } - if(ppu->scanline == 261 && ppu->dot == 339 && ppu->frame_even) { - if(ppu->mask & (PPU_MASK_SHOW_BG | PPU_MASK_SHOW_SPRITES)) { - ppu->dot++; - } + if(scanline == 261 && dot == 1) { + ppu->vblank = 0; + ppu->sprite_overflow = 0; + ppu->sprite_zero_hit = 0; } - if(ppu->dot > 340) { + ppu->dot++; + if(ppu->dot == 341) { ppu->dot = 0; ppu->scanline++; - - if(ppu->scanline > 261) { + if(ppu->scanline == 262) { ppu->scanline = 0; - ppu->frame_even ^= 1; - } - } - - // Clear secondary OAM at start of scanline - if(ppu->scanline < 240 || ppu->scanline == 261) { - if(ppu->dot == 1) { - ppu_clear_sec_oam(state); } } - - // Perform sprite evaluation - if(ppu->scanline < 240 && ppu->dot == 65) { - ppu_eval_sprites(state); - } } - static void ppu_dma_4014(struct nes_state *state, uint8_t page) { - uint32_t base = page << 8; - - for(int i = 0; i < 256; i++) { - uint8_t value = memory_read_dma(state, base + i); - state->ppu.oam[i] = value; + uint32_t base = (uint32_t)page << 8; + uint32_t i; - ppu_tick(state); - ppu_tick(state); - ppu_tick(state); - // apu_tick(state); + uint32_t idle = (state->cycle & 1) ? 2 : 1; + while(idle--) { + ppu_tick(state); ppu_tick(state); ppu_tick(state); state->cycle++; } - // DMA takes 513 cycles on even CPU cycles, 514 on odd - int extra = (state->cycle & 1) ? 514 : 513; - for(int i = 0; i < extra; i++) { - ppu_tick(state); - ppu_tick(state); - ppu_tick(state); - // apu_tick(state); + 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); } } + |
