summaryrefslogtreecommitdiff
path: root/ppu.c
diff options
context:
space:
mode:
authorPeter Fors <peter.fors@mindkiller.com>2025-04-01 00:06:02 +0200
committerPeter Fors <peter.fors@mindkiller.com>2025-04-01 00:06:02 +0200
commit59b092914fcf3c33a783a2813ebe268586609ff9 (patch)
treea18b4236e82525e01d2e3bce0cdc464abfeb9cf9 /ppu.c
parenta386ef64f6376b3ef8434a6cdf456495287fcbca (diff)
before add scroll_latch
Diffstat (limited to 'ppu.c')
-rw-r--r--ppu.c241
1 files changed, 128 insertions, 113 deletions
diff --git a/ppu.c b/ppu.c
index 19c4702..f25348d 100644
--- a/ppu.c
+++ b/ppu.c
@@ -9,6 +9,35 @@ static void ppu_reset(struct nes_state *state) {
}
+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;
@@ -17,19 +46,11 @@ static uint8_t ppu_read_mem(struct nes_state *state, uint32_t addr) {
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];
- }
+ uint32_t mirrored = nt_mirror(state->ines.mirroring, nt_index);
+ return ppu->vram[mirrored];
} else {
- addr &= 0x1f;
- if((addr & 0x13) == 0x10) {
- addr &= ~0x10;
- }
- return ppu->palette[addr];
+ uint32_t mirrored = palette_mirror(addr);
+ return ppu->palette[mirrored];
}
}
@@ -37,34 +58,18 @@ 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 >= 0x3f00 && addr < 0x3f20) {
- printf("PPU write: %04x = %02x\n", addr, value);
-}
if(addr < 0x2000) {
- // Ignore write to CHR ROM
- return;
+ return; // CHR ROM is read-only
} 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;
- }
+ uint32_t mirrored = nt_mirror(state->ines.mirroring, addr - 0x2000);
+ ppu->vram[mirrored] = value;
} else {
- addr &= 0x1f;
- if((addr & 0x13) == 0x10) {
- addr &= ~0x10;
- }
- ppu->palette[addr] = value;
- if(addr == 0) {
- printf("Write to universal BG color: %02x\n", value);
- }
+ ppu->palette[palette_mirror(addr)] = value;
}
}
+
static void ppu_write_2000(struct nes_state *state, uint8_t value) {
struct ppu_state *ppu = &state->ppu;
@@ -93,6 +98,8 @@ static void ppu_write_2004(struct nes_state *state, uint8_t 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);
@@ -108,24 +115,17 @@ 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");
+
+ 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;
-
- uint32_t addr = ppu->vram_addr;
-
- 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;
}
}
@@ -135,7 +135,10 @@ static void ppu_write_2007(struct nes_state *state, uint8_t 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) {
@@ -156,76 +159,69 @@ static uint8_t ppu_read_2004(struct nes_state *state) {
static uint8_t ppu_read_2007(struct nes_state *state) {
struct ppu_state *ppu = &state->ppu;
- printf("ppu_read_2007\n");
+ // 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->coarse_x++;
- if(ppu->coarse_x == 32) {
- ppu->coarse_x = 0;
- ppu->nt_x_offset ^= 0x400;
+ 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->fine_y++;
- if(ppu->fine_y == 8) {
- ppu->fine_y = 0;
- ppu->coarse_y++;
+ ppu->render_fine_y++;
+ if(ppu->render_fine_y == 8) {
+ ppu->render_fine_y = 0;
+ ppu->render_coarse_y++;
- if(ppu->coarse_y == 30) {
- ppu->coarse_y = 0;
- ppu->nt_y_offset ^= 0x800;
+ 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->nt_x_offset | ppu->nt_y_offset | (ppu->coarse_y << 5) | ppu->coarse_x;
-
- 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;
+ 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) {
- 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);
+ 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);
- uint8_t val = ppu_read_mem(state, addr);
- // printf("ATTR[%04x] = %02x (X:%u Y:%u)\n", addr, val, x, y);
- return val;
-}
+ 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 << 4) + ppu->fine_y;
+ 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 << 4) + ppu->fine_y + 8;
-
+ uint32_t addr = base + tile * 16 + ppu->render_fine_y + 8;
return ppu_read_mem(state, addr);
}
@@ -267,50 +263,67 @@ static void ppu_render_pixel(struct nes_state *state) {
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];
+
+ 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) {
+static void ppu_evaluate_sprites(struct nes_state *state, uint32_t target_scanline) {
struct ppu_state *ppu = &state->ppu;
- uint8_t count = 0;
- uint8_t sprite_height = (ppu->control & 0x20) ? 16 : 8;
+ ppu->sprite_count = 0;
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 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++;
+ 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(count) printf("sprite_count: %d\n", count);
- ppu->sprite_count = count;
}
-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;
+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;
- base = bank | ((tile & 0xfe) << 4);
+ addr = bank | ((tile << 4) + (y_offset & 0x07));
} else {
- base = ((state->ppu.control & 0x08) ? 0x1000 : 0x0000) | (tile << 4);
+ 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;
- uint32_t addr = base + y_offset;
+ 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);
}
@@ -333,11 +346,15 @@ static void ppu_render_sprites(struct nes_state *state) {
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 height = (ppu->control & 0x20) ? 16 : 8;
uint8_t offset_y = y - sy;
if(attr & 0x80) {
@@ -349,8 +366,8 @@ static void ppu_render_sprites(struct nes_state *state) {
shift = x - sx;
}
- 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);
+ 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;
@@ -375,21 +392,17 @@ static void ppu_render_sprites(struct nes_state *state) {
static void ppu_copy_horizontal_scroll(struct nes_state *state) {
struct ppu_state *ppu = &state->ppu;
-
uint32_t addr = ppu->vram_addr;
-
- ppu->coarse_x = (addr >> 0) & 0x1f;
- ppu->nt_x_offset = (addr & 0x400) ? 0x400 : 0;
+ 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->coarse_y = (addr >> 5) & 0x1f;
- ppu->nt_y_offset = (addr & 0x800) ? 0x800 : 0;
- ppu->fine_y = (addr >> 12) & 0x07;
+ ppu->render_coarse_y = (addr >> 5) & 0x1f;
+ ppu->render_fine_y = (addr >> 12) & 0x07;
+ ppu->render_nt_y = (addr & 0x800) ? 0x800 : 0;
}
__attribute__((flatten))
@@ -399,7 +412,7 @@ static void ppu_tick(struct nes_state *state) {
uint32_t scanline = ppu->scanline;
uint32_t dot = ppu->dot;
- if(scanline < 240) {
+ if(scanline < 240 || scanline == 261) {
if((dot >= 1 && dot <= 256) || (dot >= 321 && dot <= 336)) {
ppu_shift_background(state);
@@ -418,7 +431,7 @@ static void ppu_tick(struct nes_state *state) {
break;
case 7: {
uint8_t attr = ppu->next_attr;
- uint8_t shift = ((ppu->coarse_y >> 2) & 2) | ((ppu->coarse_x >> 1) & 1);
+ 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;
@@ -429,7 +442,7 @@ static void ppu_tick(struct nes_state *state) {
}
}
- if(dot >= 1 && dot <= 256) {
+ if(scanline < 240 && dot >= 1 && dot <= 256) {
ppu_render_pixel(state);
ppu_render_sprites(state);
}
@@ -439,11 +452,13 @@ static void ppu_tick(struct nes_state *state) {
}
if(dot == 257) {
- ppu_evaluate_sprites(state);
ppu_copy_horizontal_scroll(state);
+ if(scanline < 240) {
+ ppu_evaluate_sprites(state, scanline + 1);
+ }
}
- if(scanline == 261 && dot >= 280 && dot <= 304) {
+ if(scanline == 261 && dot >= 280) { // && dot <= 304) {
ppu_copy_vertical_scroll(state);
}
}