diff --git a/.idea/editor.xml b/.idea/editor.xml
index 66c8679..226ca24 100644
--- a/.idea/editor.xml
+++ b/.idea/editor.xml
@@ -98,5 +98,483 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/include/ppu.h b/include/ppu.h
index 852dcd3..508e9d4 100644
--- a/include/ppu.h
+++ b/include/ppu.h
@@ -81,6 +81,9 @@ typedef struct ppu {
PPUMemory memory;
pixel pixels[256 * 240];
+ byte secondary_oam[32];
+ byte found_sprite_count;
+
byte registers[8];
byte oam_dma_register;
byte oam[PPU_OAM_SIZE];
diff --git a/ppu/ppu.c b/ppu/ppu.c
index 08143a6..f0329f3 100644
--- a/ppu/ppu.c
+++ b/ppu/ppu.c
@@ -53,6 +53,7 @@ void ppu_status_set(byte mask, bool enabled) {
}
int hits = 0;
+
void ppu_trigger_vbl_nmi() {
if (!ppu_read_flag(PPU_REGISTER_CTRL, PPU_CTRL_GEN_VBLANK_NMI)) {
// VBlank NMI generation is disabled
@@ -71,7 +72,6 @@ void ppu_trigger_vbl_nmi() {
88 .88. 88booo. 88.
YP Y888888P Y88888P Y88888P
-
d8888b. d88888b d8b db d8888b. d88888b d8888b. d888888b d8b db d888b
88 `8D 88' 888o 88 88 `8D 88' 88 `8D `88' 888o 88 88' Y8b
88oobY' 88ooooo 88V8o 88 88 88 88ooooo 88oobY' 88 88V8o 88 88
@@ -109,7 +109,8 @@ static inline void ppu_pixel_set_color(pixel *pixel, byte pt_low, byte pt_high,
byte color_offset = (color_high << 1) | color_low;
address color_addr = 0x3f00 + color_offset;
- if (color_offset != 0) { // The first color of a palette (0) is always the universal color
+ if (color_offset != 0) {
+ // The first color of a palette (0) is always the universal color
color_addr += ppu_pixel_get_palette(attribute) * 4;
}
@@ -125,7 +126,7 @@ void ppu_draw_tile() {
unsigned int x = ppu_state.cycle;
unsigned int pixel_index = (y * PPU_CYCLE_VISIBLE_MAX + x) % (240 * 256);
- assert(pixel_index < 240 * 256); // If this goes over, the PPU registers will be overridden
+ assert(pixel_index < 240 * 256); // If this goes over, the PPU registers will be overridden
pixel *pixel = &ppu_state.pixels[pixel_index];
ppu_pixel_set_color(pixel, fetch.pattern_table_tile_low, fetch.pattern_table_tile_high, fetch.attribute_table);
@@ -189,6 +190,63 @@ void ppu_fetch_tile(bool render) {
}
}
+/*
+ .d8888. d8888b. d8888b. d888888b d888888b d88888b
+ 88' YP 88 `8D 88 `8D `88' `~~88~~' 88'
+ `8bo. 88oodD' 88oobY' 88 88 88ooooo
+ `Y8b. 88~~~ 88`8b 88 88 88~~~~~
+ db 8D 88 88 `88. .88. 88 88.
+ `8888Y' 88 88 YD Y888888P YP Y88888P
+
+ d8888b. d88888b d8b db d8888b. d88888b d8888b. d888888b d8b db d888b
+ 88 `8D 88' 888o 88 88 `8D 88' 88 `8D `88' 888o 88 88' Y8b
+ 88oobY' 88ooooo 88V8o 88 88 88 88ooooo 88oobY' 88 88V8o 88 88
+ 88`8b 88~~~~~ 88 V8o88 88 88 88~~~~~ 88`8b 88 88 V8o88 88 ooo
+ 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;
+ }
+
+ 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;
+ }
+
+ 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];
+ 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;
+ }
+
+ // 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) {
if (!ppu_read_flag(PPU_REGISTER_MASK, PPU_MASK_SHOW_BG)) {
// Background rendering is off
@@ -198,6 +256,12 @@ 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) {
@@ -248,10 +312,12 @@ void ppu_post_render(unsigned int x, unsigned int y) {
}
int cycles = 0;
+
void ppu_cycle() {
if (ppu_state.scanline < PPU_SCANLINE_VISIBLE_MAX) {
ppu_visible_frame(ppu_state.cycle);
- } else if (ppu_state.scanline >= PPU_SCANLINE_POST_RENDER_MIN && ppu_state.scanline <= PPU_SCANLINE_POST_RENDER_MAX) {
+ } else if (ppu_state.scanline >= PPU_SCANLINE_POST_RENDER_MIN && ppu_state.scanline <=
+ PPU_SCANLINE_POST_RENDER_MAX) {
ppu_post_render(ppu_state.cycle, ppu_state.scanline);
} else if (ppu_state.scanline == PPU_SCANLINE_PRE_RENDER) {
ppu_pre_render(ppu_state.cycle);
@@ -262,12 +328,14 @@ 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++;
@@ -354,7 +422,7 @@ byte ppu_read(address addr) {
return ppu_state.memory.palette[relative_addr];
}
-// assert(false);
+ // assert(false);
return 0;
}
@@ -416,7 +484,7 @@ void ppu_write_ctrl(byte data) {
}
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.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;
@@ -572,4 +640,4 @@ byte ppu_read_reg(byte reg) {
default:
return ppu_state.registers[reg];
}
-}
\ No newline at end of file
+}