summaryrefslogtreecommitdiff
path: root/ppu.c
diff options
context:
space:
mode:
Diffstat (limited to 'ppu.c')
-rw-r--r--ppu.c653
1 files changed, 303 insertions, 350 deletions
diff --git a/ppu.c b/ppu.c
index fbe3670..1c34381 100644
--- a/ppu.c
+++ b/ppu.c
@@ -1,115 +1,81 @@
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 void ppu_sprite_shift(struct nes_state *state) {
+ struct ppu_state *ppu = &state->ppu;
-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;
+ if(!(ppu->reg_mask & 0x10)) {
+ return;
}
-}
-static uint32_t palette_mirror(uint32_t addr) {
- addr &= 0x1f;
- if((addr & 0x13) == 0x10) {
- addr &= ~0x10;
+ for(uint32_t i = 0; i < ppu->sprite_count; i++) {
+ if(ppu->sprite_positions[i] > 0) {
+ ppu->sprite_positions[i]--;
+ } else {
+ ppu->sprite_shift_lo[i] <<= 1;
+ ppu->sprite_shift_hi[i] <<= 1;
+ }
}
- return addr;
}
-static uint8_t ppu_read_mem(struct nes_state *state, uint32_t addr) {
+
+static void ppu_reset(struct nes_state *state) {
struct ppu_state *ppu = &state->ppu;
- addr &= 0x3fff;
+ memset(ppu, 0, sizeof(struct ppu_state));
- 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;
+static uint32_t ppu_resolve_ciram(struct nes_state *state, uint32_t addr) {
+ // Apply mirroring logic to address in $2000–$2FFF range
+ addr &= 0x0fff;
- 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;
+ switch(state->ines.mirroring) {
+ case MIRROR_VERTICAL:
+ return (addr & 0x0800) | (addr & 0x03ff); // $2000/$2800 → $0000, $2400/$2C00 → $0400
+ case MIRROR_HORIZONTAL:
+ return ((addr & 0x0400) >> 1) | (addr & 0x03ff); // $2000/$2400 → $0000, $2800/$2C00 → $0400
+ default:
+ return addr & 0x07ff; // For now, 4-screen = direct
}
}
+static uint8_t ppu_ciram_read(struct nes_state *state, uint32_t addr) {
+ return state->ppu.ciram[ppu_resolve_ciram(state, addr)];
+}
+
+static void ppu_ciram_write(struct nes_state *state, uint32_t addr, uint8_t value) {
+ state->ppu.ciram[ppu_resolve_ciram(state, 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);
+ ppu->reg_ctrl = value;
+ ppu->temp_addr = (ppu->temp_addr & 0xf3ff) | ((value & 0x03) << 10);
}
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");
+ state->ppu.reg_mask = value;
}
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->temp_addr = (ppu->temp_addr & ~0x001f) | (value >> 3);
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->temp_addr = (ppu->temp_addr & ~0x73e0) | ((value & 0x07) << 12) | ((value & 0xf8) << 2);
ppu->write_latch = 0;
}
}
@@ -117,392 +83,379 @@ static void ppu_write_2005(struct nes_state *state, uint8_t value) {
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->temp_addr = (ppu->temp_addr & 0x00ff) | ((value & 0x3f) << 8);
ppu->write_latch = 1;
} else {
- ppu->vram_addr |= value;
+ ppu->temp_addr = (ppu->temp_addr & 0xff00) | value;
+ ppu->vram_addr = ppu->temp_addr;
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);
+ uint32_t addr = ppu->vram_addr & 0x3fff;
- 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);
+ if(addr < 0x2000) {
+ // CHR-RAM, skip
+ } else if(addr < 0x3f00) {
+ uint32_t mirrored_addr = addr & 0x0fff;
+ ppu->ciram[mirrored_addr & 0x7ff] = value;
+ } else if(addr < 0x4000) {
+ uint32_t pal_addr = addr & 0x1f;
+ if((pal_addr & 0x13) == 0x10) {
+ pal_addr &= ~0x10;
+ }
+ ppu->palette[pal_addr] = value;
+ }
- ppu->vram_addr += (ppu->control & 0x04) ? 32 : 1;
- ppu->vram_addr &= 0x3fff;
+ ppu->vram_addr += (ppu->reg_ctrl & 0x04) ? 32 : 1;
}
static uint8_t ppu_read_2002(struct nes_state *state) {
struct ppu_state *ppu = &state->ppu;
+ uint8_t result = ppu->reg_status;
- 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->reg_status &= ~0x80;
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);
+ uint32_t addr = ppu->vram_addr & 0x3fff;
+ uint8_t result = 0;
- 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;
+ // Read from CHR-RAM (CHR-ROM in PPU)
+ if(addr < 0x2000) {
+ result = ppu->vram_read_buffer;
+ ppu->vram_read_buffer = state->chrrom[addr];
+ } else if(addr < 0x3f00) {
+ // Read from CIRAM (internal VRAM)
+ uint32_t mirrored_addr = addr & 0x0fff;
+ if(state->ines.mirroring == 2) {
+ mirrored_addr |= (addr & 0x0800) ? 0x400 : 0x000; // Handle 4-screen mirroring
+ }
+ result = ppu->ciram[mirrored_addr & 0x7ff];
+ } else if(addr < 0x4000) {
+ // Read from palette
+ uint32_t pal_addr = addr & 0x1f;
+ if((pal_addr & 0x13) == 0x10) {
+ pal_addr &= ~0x10; // Skip over the unused area of the palette
}
+ result = ppu->palette[pal_addr];
}
-}
-
-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);
+ // Update VRAM address based on control register
+ ppu->vram_addr += (ppu->reg_ctrl & 0x04) ? 32 : 1;
+ return result;
}
-static uint8_t ppu_fetch_attribute(struct nes_state *state) {
+static void ppu_evaluate_sprites(struct nes_state *state) {
struct ppu_state *ppu = &state->ppu;
+ uint8_t sprite_height = (ppu->reg_ctrl & 0x20) ? 16 : 8;
+ uint8_t n = 0;
- 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);
-}
+ for(uint8_t i = 0; i < 64; i++) {
+ uint8_t y = ppu->oam[i * 4 + 0];
+ int row = (int)ppu->scanline - y;
+
+ if(row >= 0 && row < sprite_height) {
+ if(n < 8) {
+ uint8_t *src = ppu->oam + i * 4;
+ uint8_t *dst = ppu->secondary_oam + n * 4;
+ dst[0] = src[0];
+ dst[1] = src[1];
+ dst[2] = src[2];
+ dst[3] = src[3];
+ ppu->sprite_indexes[n] = i;
+ if(i == 0) {
+ ppu->sprite_zero_hit_possible = 1;
+ }
+ n++;
+ } else {
+ ppu->reg_status |= 0x20;
+ break;
+ }
+ }
+ }
-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);
+ ppu->sprite_count = n;
}
-static uint8_t ppu_fetch_pattern_hi(struct nes_state *state, uint8_t tile) {
+static void ppu_fetch_sprite_patterns(struct nes_state *state) {
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);
-}
+ for(uint8_t i = 0; i < ppu->sprite_count; i++) {
+ uint8_t *s = ppu->secondary_oam + i * 4;
+ uint8_t y = s[0], tile = s[1], attr = s[2], x = s[3];
+ uint8_t row = ppu->scanline - y;
+ uint8_t height = (ppu->reg_ctrl & 0x20) ? 16 : 8;
-static void ppu_load_background_shifters(struct nes_state *state, uint8_t pattern_lo, uint8_t pattern_hi) {
- struct ppu_state *ppu = &state->ppu;
+ if(attr & 0x80) {
+ row = height - 1 - row;
+ }
- ppu->bg_shift_lo = (ppu->bg_shift_lo & 0xff00) | pattern_lo;
- ppu->bg_shift_hi = (ppu->bg_shift_hi & 0xff00) | pattern_hi;
+ uint32_t addr;
+ if(height == 16) {
+ uint32_t bank = (tile & 1) ? 0x1000 : 0x0000;
+ tile &= 0xfe;
+ if(row >= 8) {
+ tile++;
+ row -= 8;
+ }
+ addr = bank + tile * 16 + row;
+ } else {
+ uint32_t bank = (ppu->reg_ctrl & 0x08) ? 0x1000 : 0x0000;
+ addr = bank + tile * 16 + row;
+ }
- 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);
-}
+ uint8_t lsb = state->chrrom[addr];
+ uint8_t msb = state->chrrom[addr + 8];
-static void ppu_shift_background(struct nes_state *state) {
- struct ppu_state *ppu = &state->ppu;
+ if(attr & 0x40) {
+ lsb = ((lsb * 0x0202020202ULL & 0x010884422010ULL) % 1023) & 0xff;
+ msb = ((msb * 0x0202020202ULL & 0x010884422010ULL) % 1023) & 0xff;
+ }
- ppu->bg_shift_lo <<= 1;
- ppu->bg_shift_hi <<= 1;
- ppu->attr_shift_lo <<= 1;
- ppu->attr_shift_hi <<= 1;
+ ppu->sprite_shift_lo[i] = lsb;
+ ppu->sprite_shift_hi[i] = msb;
+ ppu->sprite_positions[i] = x;
+ ppu->sprite_priorities[i] = (attr >> 5) & 1;
+ }
}
static void ppu_render_pixel(struct nes_state *state) {
- struct ppu_state *ppu = &state->ppu;
+ uint8_t bg_pixel = 0;
+ uint8_t bg_palette = 0;
+ uint8_t sp_pixel = 0;
+ uint8_t sp_palette = 0;
+ uint8_t sp_prio = 0;
+ uint8_t sp_zero = 0;
+ uint8_t final_color = 0;
- if(!(ppu->mask & 0x08)) {
- return;
- }
+ struct ppu_state *ppu = &state->ppu;
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];
+ if(x >= 256 || y >= 240) {
+ return;
}
- ppu->pixels[y * 256 + x] = color_index;
-}
+ uint32_t bit = 0x8000 >> ppu->fine_x;
-static void ppu_evaluate_sprites(struct nes_state *state, uint32_t target_scanline) {
- struct ppu_state *ppu = &state->ppu;
+ if(ppu->reg_mask & 0x08) {
+ uint8_t p0 = (ppu->bg_shift_pattern_low & bit) ? 1 : 0;
+ uint8_t p1 = (ppu->bg_shift_pattern_high & bit) ? 1 : 0;
+ bg_pixel = (p1 << 1) | p0;
- ppu->sprite_count = 0;
+ uint8_t a0 = (ppu->bg_shift_attrib_low & bit) ? 1 : 0;
+ uint8_t a1 = (ppu->bg_shift_attrib_high & bit) ? 1 : 0;
+ bg_palette = (a1 << 1) | a0;
+ }
- 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;
+ if(ppu->reg_mask & 0x10) {
+ for(uint8_t i = 0; i < ppu->sprite_count; i++) {
+ if(ppu->sprite_positions[i] == 0) {
+ uint8_t p0 = (ppu->sprite_shift_lo[i] & 0x80) ? 1 : 0;
+ uint8_t p1 = (ppu->sprite_shift_hi[i] & 0x80) ? 1 : 0;
+ sp_pixel = (p1 << 1) | p0;
+
+ if(sp_pixel) {
+ sp_palette = ppu->secondary_oam[i * 4 + 2] & 3;
+ sp_prio = ppu->sprite_priorities[i];
+ sp_zero = (ppu->sprite_indexes[i] == 0);
+ 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);
+ if(bg_pixel == 0 && sp_pixel == 0) {
+ final_color = ppu->palette[0];
+ } else if(bg_pixel == 0 && sp_pixel != 0) {
+ final_color = ppu->palette[0x10 | (sp_palette << 2) | sp_pixel];
+ } else if(bg_pixel != 0 && sp_pixel == 0) {
+ final_color = ppu->palette[(bg_palette << 2) | bg_pixel];
} 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;
+ if(sp_zero && ppu->sprite_zero_hit_possible && x < 255) {
+ ppu->reg_status |= 0x40;
}
-
- 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;
+ if(sp_prio == 0) {
+ final_color = ppu->palette[0x10 | (sp_palette << 2) | sp_pixel];
+ } else {
+ final_color = ppu->palette[(bg_palette << 2) | bg_pixel];
}
}
+ assert(y*256+x <= 256*240);
+ ppu->pixels[y * 256 + x] = final_color;
}
-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;
+ uint32_t scanline = ppu->scanline;
- if(scanline < 240 || scanline == 261) {
- if((dot >= 1 && dot <= 256) || (dot >= 321 && dot <= 336)) {
- ppu_shift_background(state);
+ uint8_t rendering = (ppu->reg_mask & 0x18) != 0;
- switch((dot - 1) % 8) {
- case 0:
- ppu->next_tile = ppu_fetch_name_table(state);
- break;
- case 2:
- ppu->next_attr = ppu_fetch_attribute(state);
+ if(rendering && scanline < 240 && dot >= 1 && dot <= 256) {
+ ppu_render_pixel(state);
+ }
+
+ if(rendering && ((dot >= 2 && dot <= 257) || (dot >= 322 && dot <= 337))) {
+ ppu_sprite_shift(state);
+ ppu->bg_shift_pattern_low <<= 1;
+ ppu->bg_shift_pattern_high <<= 1;
+ ppu->bg_shift_attrib_low <<= 1;
+ ppu->bg_shift_attrib_high <<= 1;
+ }
+
+ if(scanline < 240 || scanline == 261) {
+ if(rendering && ((dot >= 1 && dot <= 256) || (dot >= 321 && dot <= 336))) {
+ switch(dot % 8) {
+ case 1: {
+ uint32_t nt_addr = 0x2000 | (ppu->vram_addr & 0x0fff);
+ ppu->bg_next_tile_id = ppu->ciram[nt_addr & 0x07ff];
break;
- case 4:
- ppu->next_lo = ppu_fetch_pattern_lo(state, ppu->next_tile);
+ }
+ case 3: {
+ uint32_t attr_addr = 0x23c0 | (ppu->vram_addr & 0x0c00) | ((ppu->vram_addr >> 4) & 0x38) | ((ppu->vram_addr >> 2) & 0x07);
+ uint8_t attr = ppu->ciram[attr_addr & 0x07ff];
+ uint8_t shift = ((ppu->vram_addr >> 4) & 4) | (ppu->vram_addr & 2);
+ ppu->bg_next_tile_attrib = (attr >> shift) & 3;
break;
- case 6:
- ppu->next_hi = ppu_fetch_pattern_hi(state, ppu->next_tile);
+ }
+ case 5: {
+ uint32_t base = (ppu->reg_ctrl & 0x10) ? 0x1000 : 0x0000;
+ uint32_t tile = ppu->bg_next_tile_id;
+ uint32_t fine_y = (ppu->vram_addr >> 12) & 7;
+ uint32_t addr_lsb = (base + tile * 16 + fine_y) & 0x1fff;
+ ppu->bg_next_tile_lsb = state->chrrom[addr_lsb];
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);
+ uint32_t base = (ppu->reg_ctrl & 0x10) ? 0x1000 : 0x0000;
+ uint32_t tile = ppu->bg_next_tile_id;
+ uint32_t fine_y = (ppu->vram_addr >> 12) & 7;
+ uint32_t addr_msb = (base + tile * 16 + fine_y + 8) & 0x1fff;
+ ppu->bg_next_tile_msb = state->chrrom[addr_msb];
break;
}
+ case 0: {
+ ppu->bg_shift_pattern_low = (ppu->bg_shift_pattern_low & 0xff00) | ppu->bg_next_tile_lsb;
+ ppu->bg_shift_pattern_high = (ppu->bg_shift_pattern_high & 0xff00) | ppu->bg_next_tile_msb;
+
+ uint8_t a = ppu->bg_next_tile_attrib;
+ ppu->bg_shift_attrib_low = (ppu->bg_shift_attrib_low & 0xff00) | ((a & 1) ? 0xff : 0x00);
+ ppu->bg_shift_attrib_high = (ppu->bg_shift_attrib_high & 0xff00) | ((a & 2) ? 0xff : 0x00);
+
+ if((ppu->vram_addr & 0x001f) == 31) {
+ ppu->vram_addr &= ~0x001f;
+ ppu->vram_addr ^= 0x0400;
+ } else {
+ ppu->vram_addr++;
}
- if(dot % 8 == 0) {
- ppu_inc_x(state);
+
+ break;
+ }
}
}
- if(scanline < 240 && dot >= 1 && dot <= 256) {
- ppu_render_pixel(state);
- ppu_render_sprites(state);
- }
+ if(rendering) {
+ if(dot == 256) {
+ if((ppu->vram_addr & 0x7000) != 0x7000) {
+ ppu->vram_addr += 0x1000;
+ } else {
+ ppu->vram_addr &= ~0x7000;
+ uint32_t y = (ppu->vram_addr & 0x03e0) >> 5;
+ if(y == 29) {
+ y = 0;
+ ppu->vram_addr ^= 0x0800;
+ } else if(y == 31) {
+ y = 0;
+ } else {
+ y++;
+ }
+ ppu->vram_addr = (ppu->vram_addr & ~0x03e0) | (y << 5);
+ }
+ }
- if(dot == 256) {
- ppu_inc_y(state);
- }
+ if(dot == 257) {
+ ppu->vram_addr = (ppu->vram_addr & ~0x041f) | (ppu->temp_addr & 0x041f);
+ }
- 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->vram_addr = (ppu->vram_addr & ~0x7be0) | (ppu->temp_addr & 0x7be0);
}
- }
- if(scanline == 261 && dot >= 280) { // && dot <= 304) {
- ppu_copy_vertical_scroll(state);
+ if(dot == 257 && scanline < 240) {
+ ppu_evaluate_sprites(state);
+ }
+
+ if(dot == 340 && scanline < 240) {
+ ppu_fetch_sprite_patterns(state);
+ }
}
}
if(scanline == 241 && dot == 1) {
- ppu->vblank = 0x80;
- state->nmi_pending = 1;
- ppu->frame_ready = 1;
+ ppu->reg_status |= 0x80;
+ if(ppu->reg_ctrl & 0x80) {
+ state->nmi_pending = 1;
+ }
}
if(scanline == 261 && dot == 1) {
- ppu->vblank = 0;
- ppu->sprite_overflow = 0;
- ppu->sprite_zero_hit = 0;
+ ppu->reg_status &= ~0x80;
+ ppu->reg_status &= ~0x40;
+ ppu->sprite_zero_hit_possible = 0;
}
- ppu->dot++;
- if(ppu->dot == 341) {
- ppu->dot = 0;
- ppu->scanline++;
- if(ppu->scanline == 262) {
- ppu->scanline = 0;
+ dot++;
+ if(dot > 340) {
+ dot = 0;
+ scanline++;
+ if(scanline > 261) {
+ scanline = 0;
+ ppu->frame_ready = 1;
}
}
+
+ ppu->dot = dot;
+ ppu->scanline = scanline;
}
static void ppu_dma_4014(struct nes_state *state, uint8_t page) {
- uint32_t base = (uint32_t)page << 8;
- uint32_t i;
+ uint32_t base = page << 8;
- uint32_t idle = (state->cycle & 1) ? 2 : 1;
- while(idle--) {
- ppu_tick(state); ppu_tick(state); ppu_tick(state);
+ // Add 1 or 2 idle cycles depending on current CPU cycle
+ uint8_t idle_cycles = (state->cycle & 1) ? 1 : 2;
+ for(uint8_t i = 0; i < idle_cycles; i++) {
state->cycle++;
+ ppu_tick(state); ppu_tick(state); ppu_tick(state);
}
- for(i = 0; i < 256; i++) {
- ppu_tick(state); ppu_tick(state); ppu_tick(state);
+ for(uint32_t i = 0; i < 256; i++) {
+ uint32_t addr = base + i;
+
+ // First CPU cycle (read, ticks only)
state->cycle++;
+ ppu_tick(state); ppu_tick(state); ppu_tick(state);
- uint8_t val = memory_read_dma(state, base + i);
+ // Perform read
+ uint8_t value = memory_read_dma(state, addr);
- ppu_tick(state); ppu_tick(state); ppu_tick(state);
- state->cycle++;
+ // Second CPU cycle (write)
+ ppu_write_2004(state, value);
- ppu_write_2004(state, val);
+ state->cycle++;
+ ppu_tick(state); ppu_tick(state); ppu_tick(state);
}
} \ No newline at end of file