diff --git a/.idea/editor.xml b/.idea/editor.xml
index 226ca24..5f720ba 100644
--- a/.idea/editor.xml
+++ b/.idea/editor.xml
@@ -99,482 +99,482 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index 8aa9bde..2fc12ea 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -4,5 +4,6 @@
+
\ No newline at end of file
diff --git a/gui/dbg_pattern_table.c b/gui/dbg_pattern_table.c
index e64ab64..24bba3f 100644
--- a/gui/dbg_pattern_table.c
+++ b/gui/dbg_pattern_table.c
@@ -39,7 +39,7 @@ DebugPattern dbg_pattern_get(int pattern_id, int bank) {
// TODO Should not use same bank
switch (bank) {
case PATTERN_BANK_0:
- return pattern_table.bank_1[pattern_id];
+ return pattern_table.bank_0[pattern_id];
case PATTERN_BANK_1:
return pattern_table.bank_1[pattern_id];
default:
diff --git a/include/ppu.h b/include/ppu.h
index 508e9d4..f8ec9d0 100644
--- a/include/ppu.h
+++ b/include/ppu.h
@@ -80,11 +80,11 @@ typedef union {
typedef struct ppu {
PPUMemory memory;
pixel pixels[256 * 240];
-
byte secondary_oam[32];
- byte found_sprite_count;
+ byte sprite_count;
byte registers[8];
+ byte io_bus;
byte oam_dma_register;
byte oam[PPU_OAM_SIZE];
bool odd_frame;
diff --git a/ppu/ppu.c b/ppu/ppu.c
index f0329f3..d4758ce 100644
--- a/ppu/ppu.c
+++ b/ppu/ppu.c
@@ -120,6 +120,7 @@ static inline void ppu_pixel_set_color(pixel *pixel, byte pt_low, byte pt_high,
}
void ppu_draw_tile() {
+ // TODO: Check sprites
PPUTileFetch fetch = ppu_state.fetch;
unsigned int y = ppu_state.scanline;
@@ -205,46 +206,78 @@ void ppu_fetch_tile(bool render) {
88 `88. 88. 88 V888 88 .8D 88. 88 `88. .88. 88 V888 88. ~8~
88 YD Y88888P VP V8P Y8888D' Y88888P 88 YD Y888888P VP V8P Y888P
*/
-void ppu_secondary_oam_clear() {
- // In reality, the OAM is read then 0xFF is written to the secondary OAM, but we won't do that here
- // TODO: $2004 should also return 0xFF
- memset(ppu_state.secondary_oam, 0xff, sizeof(ppu_state.secondary_oam));
-}
-
void ppu_sprite_evaluation() {
// With real hardware, the OAM is read on odd cycles and written on even ones.
// A sprites contains 4 bytes and needs 8 cycles to be copied.
- // To simplify the logic we will simply do everything at each 8-cycle period.
- if (ppu_state.cycle % 8 != 0) {
- return;
- }
+ // To simplify the logic we will simply do everything at once, one time per scanline.
- const unsigned int sprite_eval_cycle = ppu_state.cycle - 64;
- const unsigned int n = sprite_eval_cycle / 8;
- if (n > 64) {
- // There are no more sprites to copy
- return;
- }
+ // Start by clearing the secondary OAM
+ memset(ppu_state.secondary_oam, 0xff, sizeof(ppu_state.secondary_oam));
+ ppu_state.sprite_count = 0;
- const byte *oam_src = &ppu_state.oam[n * 4];
- if (ppu_state.found_sprite_count >= 8) {
- // We can't copy more sprites
- // Check if there are remaining sprites for this scanline
- if (oam_src[0] == ppu_state.scanline) {
- const byte status = ppu_state.registers[PPU_REGISTER_STATUS];
+ // Scan the OAM for sprites in range
+ const unsigned int next_scanline = ppu_state.scanline + 1;
+ for (int n = 0; n < 64; n++) {
+ const byte *sprite_oam = &ppu_state.oam[n * 4];
+ const byte sprite_y = sprite_oam[0];
+
+ const bool sprite_8x16 = ppu_read_flag(PPU_REGISTER_CTRL, PPU_CTRL_SP_SIZE);
+ const byte offset_max = sprite_8x16 ? 16 : 8;
+ if (sprite_y < next_scanline - offset_max || sprite_y > next_scanline + offset_max) {
+ // Sprite is outside of range
+ continue;
+ }
+
+ if (ppu_state.sprite_count < 8) {
+ // Copy the sprite to secondary OAM
+ byte *sprite_oam_dest = &ppu_state.secondary_oam[ppu_state.sprite_count * 4];
+ memcpy(sprite_oam_dest, sprite_oam, 4);
+ ppu_state.sprite_count++;
+ } else {
+ // There are more sprites in range, but we can't fit them in memory
+ const byte status = ppu_read_reg(PPU_REGISTER_STATUS);
ppu_write_reg(PPU_REGISTER_STATUS, status | PPU_STATUS_SP_OVERFLOW);
}
- return;
}
+}
- if (oam_src[0] != ppu_state.scanline) {
- // We don't need to draw that sprite
- return;
+void ppu_sprite_draw(byte sprite_index) {
+ const byte *sprite_oam = &ppu_state.secondary_oam[sprite_index * 4];
+ const byte sprite_y = sprite_oam[0];
+ const byte sprite_tile = sprite_oam[1];
+ const byte sprite_attr = sprite_oam[2];
+ const byte sprite_x = sprite_oam[3];
+
+ // Fetch pattern
+ const address pattern_table_addr = (sprite_tile & 1) << 7;
+ const byte y_scroll = ppu_state.scanline - sprite_y;
+ const byte tile_index = (0xa2);
+ // const byte tile_index = sprite_tile >> 1;
+ // address pattern_addr = 0xa20;
+ address pattern_addr = pattern_table_addr | sprite_tile << 4 | y_scroll;
+
+ byte pattern_low = ppu_read(pattern_addr);
+ byte pattern_high = ppu_read(pattern_addr + 8);
+
+ const unsigned int pixel_index = ppu_state.scanline * PPU_CYCLE_VISIBLE_MAX + sprite_x;
+ pixel *pixels = &ppu_state.pixels[pixel_index];
+ const byte palette = sprite_attr & 2;
+
+ // Draw the sprite
+ for (int x = 0; x < 8; x++) {
+ if (sprite_x + x > 0xff) {
+ break;
+ }
+
+ const byte pixel_offset = 8 - x - 1;
+ const byte color_low = (pattern_low >> pixel_offset) & 1;
+ const byte color_high = (pattern_high >> pixel_offset) & 1;
+ const byte color_offset = (color_high << 1) | color_low;
+ const address color_addr = 0x3f10 | (palette << 2) | color_offset;
+
+ const byte color = ppu_read(color_addr);
+ *(pixels + x) = colors[color];
}
-
- // The sprite will be drawn, copy it to secondary OAM
- byte *secondary_oam_dest = &ppu_state.secondary_oam[ppu_state.found_sprite_count * 4];
- memcpy(secondary_oam_dest, oam_src, 4);
}
void ppu_visible_frame(unsigned int cycle) {
@@ -256,15 +289,15 @@ void ppu_visible_frame(unsigned int cycle) {
if (cycle == 0) {
// Idle...
} else if (cycle <= 256) {
- if (cycle <= 64) {
- ppu_secondary_oam_clear();
- } else {
- ppu_sprite_evaluation();
- }
-
ppu_fetch_tile(true);
if (cycle == 256) {
+ for (int i = 0; i < ppu_state.sprite_count; i++) {
+ ppu_sprite_draw(i);
+ }
+
+ ppu_sprite_evaluation();
+
if ((ppu_state.ppu_address & 0x7000) != 0x7000) {
ppu_state.ppu_address += 0x1000;
} else {
@@ -328,14 +361,12 @@ void ppu_cycle() {
if (ppu_state.cycle >= PPU_CYCLE_MAX) {
ppu_state.cycle = 0;
ppu_state.scanline++;
- ppu_state.found_sprite_count = 0;
}
if (ppu_state.scanline > PPU_SCANLINE_MAX) {
ppu_state.scanline = 0;
ppu_state.frame++;
ppu_state.odd_frame = !ppu_state.odd_frame;
- ppu_state.found_sprite_count = 0;
}
cycles++;
@@ -483,7 +514,8 @@ void ppu_write_ctrl(byte data) {
}
}
- ppu_state.temp_ppu_addr = (ppu_state.temp_ppu_addr & 0xf3ff) | ((data & PPU_CTRL_BASE_NAMETABLE_ADDR) << 10);
+ // TODO: Was broken, changed while loading palette
+ // ppu_state.temp_ppu_addr = (ppu_state.temp_ppu_addr & 0xf3ff) | ((data & PPU_CTRL_BASE_NAMETABLE_ADDR) << 10);
ppu_state.bg_pattern_table_addr = (data & PPU_CTRL_BG_PATTERN_TABLE_ADDR) << 0x8; // 0x0000 or 0x1000
ppu_state.ppu_addr_increment = (data & PPU_CTRL_VRAM_ADDR_INCREMENT) ? 0x20 : 1;
@@ -604,6 +636,7 @@ void ppu_write_reg(byte reg, byte data) {
break;
}
+ ppu_state.io_bus = data;
ppu_state.registers[reg] = data;
}
@@ -611,7 +644,9 @@ byte ppu_read_status() {
ppu_state.w = false;
byte status = ppu_state.registers[PPU_REGISTER_STATUS];
ppu_state.registers[PPU_REGISTER_STATUS] &= ~PPU_STATUS_VBLANK;
- return status;
+
+ // https://www.nesdev.org/wiki/Open_bus_behavior#PPU_open_bus
+ return status | (ppu_state.io_bus & 0x1f);
}
byte ppu_read_data() {
@@ -632,12 +667,19 @@ byte ppu_read_reg(byte reg) {
assert(reg >= 0);
assert(reg <= PPU_REGISTER_SIZE);
+ byte data;
switch (reg) {
case PPU_REGISTER_STATUS:
- return ppu_read_status();
+ data = ppu_read_status();
+ break;
case PPU_REGISTER_DATA:
- return ppu_read_data();
+ data = ppu_read_data();
+ break;
default:
- return ppu_state.registers[reg];
+ data = ppu_state.registers[reg];
+ break;
}
+
+ ppu_state.io_bus = data;
+ return data;
}