summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Fors <peter.fors@mindkiller.com>2025-03-31 20:31:05 +0200
committerPeter Fors <peter.fors@mindkiller.com>2025-03-31 20:31:05 +0200
commita386ef64f6376b3ef8434a6cdf456495287fcbca (patch)
treee2da9f72ce0a565b4fac2fc8be19ab3497286b36
parentd5486a5af100fb37fac08b60d862ac14943853ce (diff)
currently 90% working
-rw-r--r--base/base.c4
-rwxr-xr-xbase/build.sh20
-rw-r--r--base/settings.h2
-rw-r--r--base/state.c11
-rwxr-xr-xbuild.sh1
-rw-r--r--cpu.c19
-rw-r--r--cpu_opcodes.c4
-rw-r--r--ines2.c10
-rw-r--r--mapper.c2
-rw-r--r--mapper_nrom.c23
-rw-r--r--memory.c21
-rw-r--r--mknes.c63
-rw-r--r--mknes.h178
-rw-r--r--ppu.c677
14 files changed, 566 insertions, 469 deletions
diff --git a/base/base.c b/base/base.c
index 4c6fbe3..91555ef 100644
--- a/base/base.c
+++ b/base/base.c
@@ -85,7 +85,9 @@ int main(int argc, char **argv) {
reset_profiling_data();
#endif
render_callback();
+// #ifndef PERF_TEST
apply_phosphor_decay();
+// #endif
update_keyboard_state();
update_modifier_state();
update_mouse_state();
@@ -112,7 +114,7 @@ int main(int argc, char **argv) {
mkfw_swap_buffers();
#endif
}
-
+ shutdown_callback();
#ifdef PROFILER
overlay_shutdown();
#endif
diff --git a/base/build.sh b/base/build.sh
index b415041..e237c16 100755
--- a/base/build.sh
+++ b/base/build.sh
@@ -51,27 +51,27 @@ case "$BUILD_TYPE" in
esac
# Make sure shaders are up to date
-shader2h 330 vertex_shader vertex_shader.glsl data/vertex_shader.h
-shader2h 330 fragment_shader shader.h fragment_shader.glsl data/fragment_shader.h
+#shader2h 330 vertex_shader vertex_shader.glsl data/vertex_shader.h
+#shader2h 330 fragment_shader shader.h fragment_shader.glsl data/fragment_shader.h
# Stop on first error
set -e
# Common compile commands
gcc_cmd="gcc $CFLAGS ${PROJECT_NAME}.c -o ${PROJECT_NAME} $INCLUDE_PATHS $LDFLAGS"
-mingw_cmd="x86_64-w64-mingw32-gcc $CFLAGS ${PROJECT_NAME}.c -o ${PROJECT_NAME}.exe -mwindows $INCLUDE_PATHS $LDFLAGS"
+#mingw_cmd="x86_64-w64-mingw32-gcc $CFLAGS ${PROJECT_NAME}.c -o ${PROJECT_NAME}.exe -mwindows $INCLUDE_PATHS $LDFLAGS"
# Run Linux and Windows builds in parallel
(
- ctime -begin .${PROJECT_NAME}_linux
+# ctime -begin .${PROJECT_NAME}_linux
$gcc_cmd $LINUX_INCLUDE $LINUX_LIBS
- ctime -end .${PROJECT_NAME}_linux $?
+# ctime -end .${PROJECT_NAME}_linux $?
) &
-(
- ctime -begin .${PROJECT_NAME}_windows
- $mingw_cmd $WINDOWS_INCLUDE $WINDOWS_LIBS
- ctime -end .${PROJECT_NAME}_windows $?
-) &
+#(
+# ctime -begin .${PROJECT_NAME}_windows
+# $mingw_cmd $WINDOWS_INCLUDE $WINDOWS_LIBS
+# ctime -end .${PROJECT_NAME}_windows $?
+#) &
wait
diff --git a/base/settings.h b/base/settings.h
index be448c4..c8d3c5c 100644
--- a/base/settings.h
+++ b/base/settings.h
@@ -3,7 +3,7 @@
// #define PERF_TEST
#ifndef PERF_TEST
-#define PROFILER
+// #define PROFILER
#endif
diff --git a/base/state.c b/base/state.c
index f883d64..1e9d95b 100644
--- a/base/state.c
+++ b/base/state.c
@@ -1,7 +1,7 @@
-#define BUFFER_WIDTH 2048
-#define BUFFER_HEIGHT 1024
-#define FPS 50
+#define BUFFER_WIDTH 256 // render buffer
+#define BUFFER_HEIGHT 240 // render buffer
+#define FPS 60.1
#ifdef _WIN32
#define SLEEP_MARGIN_NS 330000 // 0.33ms (Windows timing functionality is utter garbage)
#else
@@ -11,8 +11,8 @@
#define ONE_SECOND_NS 1000000000
#define FRAMETIME (ONE_SECOND_NS / FPS)
-#define SCREEN_WIDTH 360
-#define SCREEN_HEIGHT 270
+#define SCREEN_WIDTH 256 // screen size
+#define SCREEN_HEIGHT 240 // screen size
uint32_t buffer[BUFFER_WIDTH * BUFFER_HEIGHT] __attribute__((section(".bss"), aligned(4096)));
uint32_t display_buffer[SCREEN_WIDTH * SCREEN_HEIGHT] __attribute__((section(".bss"), aligned(4096)));
@@ -85,6 +85,7 @@ static struct remake_callbacks *current_part = 0;
static void render_callback();
static void audio_callback(int16_t *audio_buffer, size_t frames);
static void init_callback();
+static void shutdown_callback();
#ifndef PROFILER
#define PROFILE_NAMED(name)
diff --git a/build.sh b/build.sh
index efe3158..e05fc05 100755
--- a/build.sh
+++ b/build.sh
@@ -35,6 +35,7 @@ fi
case "$BUILD_TYPE" in
"normal")
CFLAGS+=" -g -O2 -DDEBUG_INTERNAL"
+# -fsanitize=address,undefined -fno-omit-frame-pointer"
;;
"release")
CFLAGS+=" -s -O2"
diff --git a/cpu.c b/cpu.c
index 3e731e5..b773e57 100644
--- a/cpu.c
+++ b/cpu.c
@@ -26,11 +26,7 @@ static inline void update_zn(struct cpu_state *cpu, uint8_t result) {
cpu->n = (result & 0x80) != 0;
}
-
-
-static void (*opcode_lut[256])(struct nes_state *state);
-
-uint32_t line = 1;
+static void (*opcode_lut[256*2])(struct nes_state *) __attribute__((aligned(4096)));
struct addr_result {
uint32_t addr;
@@ -84,6 +80,7 @@ static inline void check_interrupts(struct nes_state * restrict state) {
state->nmi_pending = 0;
do_nmi(state);
} else if(state->irq_pending && cpu->i == 0) {
+ state->irq_pending = 0;
do_irq(state);
}
}
@@ -93,9 +90,15 @@ static void cpu_tick(struct nes_state *state) {
check_interrupts(state);
- // printf("%5.5d %4.4x: ", line++, cpu->pc);
- uint8_t opcode = memory_read(state, cpu->pc++);
- // printf("%2.2x a:%2.2x x:%2.2x y:%2.2x p:%2.2x sp:%2.2x cycle: %d\n", opcode, cpu->a, cpu->x, cpu->y, pack_flags(cpu), cpu->sp, cycle);
+ uint8_t opcode;
+
+ // if(cpu->pc <= 0x90cc || cpu->pc >= 0x90e6) {
+ // printf("%5.5d %4.4x: ", line++, cpu->pc);
+ // opcode = memory_read(state, cpu->pc++);
+ // printf("%2.2x a:%2.2x x:%2.2x y:%2.2x p:%2.2x sp:%2.2x cycle: %ld\n", opcode, cpu->a, cpu->x, cpu->y, pack_flags(cpu), cpu->sp, state->cycle);
+ // } else {
+ opcode = memory_read(state, cpu->pc++);
+ // }
opcode_lut[opcode](state);
}
diff --git a/cpu_opcodes.c b/cpu_opcodes.c
index 2d308da..5fd5634 100644
--- a/cpu_opcodes.c
+++ b/cpu_opcodes.c
@@ -544,7 +544,9 @@ static void opcode_clc(struct nes_state * restrict state) {
static void opcode_cld(struct nes_state * restrict state) {
struct cpu_state * restrict cpu = &state->cpu;
memory_read_dummy(state, cpu->pc);
+#ifdef ENABLE_DECIMAL_MODE
cpu->d = 0;
+#endif
}
static void opcode_cli(struct nes_state * restrict state) {
@@ -568,7 +570,9 @@ static void opcode_sec(struct nes_state * restrict state) {
static void opcode_sed(struct nes_state * restrict state) {
struct cpu_state * restrict cpu = &state->cpu;
memory_read_dummy(state, cpu->pc);
+#ifdef ENABLE_DECIMAL_MODE
cpu->d = 1;
+#endif
}
static void opcode_sei(struct nes_state * restrict state) {
diff --git a/ines2.c b/ines2.c
index 450c22e..7e3c17e 100644
--- a/ines2.c
+++ b/ines2.c
@@ -27,7 +27,7 @@
#define MIRROR_FOUR_SCREEN 2
-static int load_ines2(struct nes_state *state, const char *path) {
+static int ines2_load(struct nes_state *state, char *path) {
FILE *f = fopen(path, "rb");
if(!f) {
return -1;
@@ -55,8 +55,8 @@ static int load_ines2(struct nes_state *state, const char *path) {
// Extract mapper
uint8_t mapper_low = (header[INES_FLAGS6] >> 4);
uint8_t mapper_high = (header[INES_FLAGS7] & 0xf0);
- uint8_t mapper_ext = (header[INES_PRG_CHR_MSB] & 0x0f) << 8;
- state->ines.mapper = mapper_low | mapper_high | mapper_ext;
+ uint8_t mapper_ext = (header[INES_PRG_CHR_MSB] & 0x0f);
+ state->ines.mapper = mapper_low | mapper_high | (mapper_ext << 8);
// Extract mirroring
if(header[INES_FLAGS6] & FLAG6_FOUR_SCREEN) {
@@ -76,11 +76,11 @@ static int load_ines2(struct nes_state *state, const char *path) {
}
// Read PRG
- fread(state->rom, 1, prg_size, f);
+ printf("prgsize_read: %ld\n", fread(state->rom, 1, prg_size, f));
// Read CHR if present
if(chr_size > 0) {
- fread(state->chrrom, 1, chr_size, f);
+ printf("chrsize_read: %ld\n", fread(state->chrrom, 1, chr_size, f));
}
fclose(f);
diff --git a/mapper.c b/mapper.c
index 620be7d..7e374df 100644
--- a/mapper.c
+++ b/mapper.c
@@ -4,7 +4,7 @@
// #include "mapper_mmc1.c"
// #include "mapper_uxrom.c"
-static void mapper_setup(struct state *state) {
+static void mapper_setup(struct nes_state *state) {
switch(state->ines.mapper) {
case 0:
state->mapper.read = mapper_nrom_read;
diff --git a/mapper_nrom.c b/mapper_nrom.c
index 425db31..f618211 100644
--- a/mapper_nrom.c
+++ b/mapper_nrom.c
@@ -1,27 +1,30 @@
-static void mapper_nrom_init(struct state *state) {
+static void mapper_nrom_init(struct nes_state *state) {
// Nothing to initialize for NROM
}
-static uint8_t mapper_nrom_read(struct state *state, uint16_t addr) {
+static uint8_t mapper_nrom_read(struct nes_state *state, uint32_t addr) {
uint32_t prg_size = state->ines.prg_size;
- if(state->ines.prg_size == 16384) {
- return state->rom[addr & 0x3fff];
- } else {
- return state->rom[addr - 0x8000];
- }
- return 0;
+ uint32_t mask = (state->ines.prg_size == 16384) ? 0x3fff : 0x7fff;
+ return state->rom[addr & mask];
+
+ // if(state->ines.prg_size == 16384) {
+ // return state->rom[addr & 0x3fff];
+ // } else {
+ // return state->rom[addr - 0x8000];
+ // }
+ // return 0;
}
-static void mapper_nrom_write(struct state *state, uint16_t addr, uint8_t value) {
+static void mapper_nrom_write(struct nes_state *state, uint32_t addr, uint8_t value) {
(void)state;
(void)addr;
(void)value;
}
-static void mapper_nrom_tick(struct state *state) {
+static void mapper_nrom_tick(struct nes_state *state) {
(void)state;
}
diff --git a/memory.c b/memory.c
index cb29344..d4d2aa6 100644
--- a/memory.c
+++ b/memory.c
@@ -3,10 +3,9 @@
static uint8_t memory_read(struct nes_state *restrict state, uint32_t offset) {
state->cycle++;
- for(uint32_t i = 0; i < 3; ++i) {
- ppu_tick(state);
- }
+ ppu_tick(state); ppu_tick(state); ppu_tick(state);
+ // state->ram[0x301] = 0x1;
if(offset < 0x2000) {
return state->ram[offset & 0x07ff];
} else if(offset < 0x4000) {
@@ -28,16 +27,18 @@ static uint8_t memory_read(struct nes_state *restrict state, uint32_t offset) {
static void memory_write(struct nes_state *restrict state, uint32_t offset, uint8_t value) {
state->cycle++;
- for(uint32_t i = 0; i < 3; ++i) {
- ppu_tick(state);
- }
+ ppu_tick(state); ppu_tick(state); ppu_tick(state);
+
+// if(offset == 0x0300) {
+// printf("WRITE $0300 = %02x @ PC=%04x\n", value, state->cpu.pc);
+// }
if(offset < 0x2000) {
state->ram[offset & 0x07ff] = value;
} else if(offset < 0x4000) {
switch(offset & 7) {
- case 0: state->ppu.ctrl = value; break;
- case 1: state->ppu.mask = value; break;
+ case 0: ppu_write_2000(state, value); break;
+ case 1: ppu_write_2001(state, value); break;
case 3: ppu_write_2003(state, value); break;
case 4: ppu_write_2004(state, value); break;
case 5: ppu_write_2005(state, value); break;
@@ -73,9 +74,7 @@ static uint8_t memory_read_dma(struct nes_state *restrict state, uint32_t offset
static uint8_t memory_read_dummy(struct nes_state *restrict state, uint32_t offset) {
state->cycle++;
- for(uint32_t i = 0; i < 3; ++i) {
- ppu_tick(state);
- }
+ ppu_tick(state); ppu_tick(state); ppu_tick(state);
if(offset < 0x2000) {
return 0;
diff --git a/mknes.c b/mknes.c
index 0027ce6..bd7911c 100644
--- a/mknes.c
+++ b/mknes.c
@@ -8,19 +8,78 @@
#include "memory.c"
#include "cpu.c"
#include "ines2.c"
+#include "mapper.c"
+struct nes_state nstate;
-static void render_callback() {
+static uint32_t frames;
+static void render_callback(void) {
+ clear_buffer();
+
+ while(!nstate.ppu.frame_ready) {
+ cpu_tick(&nstate);
+ }
+ nstate.ppu.frame_ready = 0;
+ frames++;
+
+ uint32_t *dst = RENDER_START(0,0);
+ uint8_t *src = nstate.ppu.pixels;
+ for(uint32_t y = 0; y < 240; ++y) {
+ for(uint32_t x = 0; x < 256; ++x) {
+ uint8_t val = *src++;
+ if(val >= 64) val = 0;
+ dst[x] = nes_palette[val];
+ }
+ dst += BUFFER_WIDTH;
+ }
+}
+
+static void shutdown_callback() {
+ printf("%d\n", frames);
}
static void audio_callback(int16_t *buffer, size_t frames) {
}
-static void init_callback() {
+#include <sys/mman.h>
+#include <unistd.h>
+
+void protect_opcode_lut(void) {
+ uintptr_t addr = (uintptr_t)opcode_lut;
+ size_t page_size = getpagesize();
+ uintptr_t page = addr & ~(page_size - 1);
+
+ if(mprotect((void*)page, page_size, PROT_READ) != 0) {
+ perror("mprotect");
+ abort();
+ }
+}
+
+static void init_callback(void) {
+ setbuf(stdout, 0);
+ init_opcode_lut();
+ init_opcode_ud_lut();
+ // protect_opcode_lut();
+ ppu_reset(&nstate);
+ // ines2_load(&nstate, "data/nrom/Super Mario Bros. (World) (HVC-SM).nes");
+ // ines2_load(&nstate, "data/nrom/10-Yard Fight (USA, Europe).nes");
+ // ines2_load(&nstate, "data/nrom/Balloon Fight (USA).nes");
+ ines2_load(&nstate, "data/nrom/Excitebike (Japan, USA).nes");
+ // ines2_load(&nstate, "data/nrom/Ice Climber (USA, Europe, Korea).nes");
+ // ines2_load(&nstate, "data/nrom/Kung Fu (Japan, USA).nes");
+ // ines2_load(&nstate, "data/nrom/Super Mario Bros. (World) (HVC-SM).nes");
+ // ines2_load(&nstate, "data/nrom/Urban Champion (World).nes");
+ // ines2_load(&nstate, "data/nrom/Wrecking Crew (World).nes");
+
+
+ mapper_setup(&nstate);
+ uint32_t lo = nstate.mapper.read(&nstate, 0xfffc);
+ uint32_t hi = nstate.mapper.read(&nstate, 0xfffd);
+ nstate.cpu.pc = (hi << 8) | lo;
}
// int main(void) {
diff --git a/mknes.h b/mknes.h
index 1f82ae1..8fc6872 100644
--- a/mknes.h
+++ b/mknes.h
@@ -1,84 +1,104 @@
-// PPUSTATUS ($2002) flags
-#define PPU_STATUS_VBLANK 0x80
-#define PPU_STATUS_SPRITE0_HIT 0x40
-#define PPU_STATUS_OVERFLOW 0x20
-
-// PPUMASK ($2001) flags
-#define PPU_MASK_SHOW_BG 0x08
-#define PPU_MASK_SHOW_SPRITES 0x10
-
-// PPUCTRL ($2000) flags
-#define PPU_CTRL_NMI_ENABLE 0x80
-#define PPU_CTRL_MASTER_SLAVE 0x40
-#define PPU_CTRL_SPRITE_HEIGHT 0x20
-#define PPU_CTRL_BG_TABLE 0x10
-#define PPU_CTRL_SPRITE_TABLE 0x08
-#define PPU_CTRL_INCREMENT 0x04
-#define PPU_CTRL_NT_SELECT_Y 0x02
-#define PPU_CTRL_NT_SELECT_X 0x01
+
+// #define PPU_CTRL_NMI_ENABLE 0x80
+// #define PPU_CTRL_MASTER_SLAVE 0x40
+// #define PPU_CTRL_SPRITE_HEIGHT 0x20
+// #define PPU_CTRL_BG_TILE_SELECT 0x10
+// #define PPU_CTRL_SPRITE_TILE_SELECT 0x08
+// #define PPU_CTRL_NT_SELECT_Y 0x04
+// #define PPU_CTRL_NT_SELECT_X 0x02
+// #define PPU_CTRL_VRAM_INCREMENT 0x01
+
+// #define PPU_MASK_SHOW_BG 0x08
+// #define PPU_MASK_SHOW_SPRITES 0x10
+
+// #define PPU_STATUS_VBLANK 0x80
+// #define PPU_STATUS_SPRITE0_HIT 0x40
+// #define PPU_STATUS_OVERFLOW 0x20
+
+#define PPU_CTRL_BG_TILE_SELECT 0x10
+#define PPU_CTRL_SPRITE_TILE_SELECT 0x08
+#define PPU_CTRL_NMI 0x80
+#define PPU_CTRL_VRAM_INCREMENT 0x04
+
+// Define constants for PPU control and mask bits
+#define PPU_CTRL_NMI 0x80
+#define PPU_CTRL_VRAM_INCREMENT 0x04
+#define PPU_CTRL_SPRITE_HEIGHT 0x20
+#define PPU_CTRL_SPRITE_TILE 0x08
+
+#define PPU_MASK_SHOW_BG 0x08
+#define PPU_MASK_SHOW_SPRITES 0x10
+
+// Define mirroring modes
+#define MIRROR_HORIZONTAL 0
+#define MIRROR_VERTICAL 1
+#define MIRROR_FOURSCREEN 2
struct nes_state;
+
+
struct ppu_state {
- uint8_t pixels[240*256];
- uint8_t sprite_pixels[256]; // Sprite pixel color indexes (BG priority resolved)
- uint8_t sprite_zero_flags[256]; // 1 if pixel came from sprite #0 and is nonzero
- // Sprite memory
- uint8_t oam[256];
- uint8_t sec_oam[32];
+ uint8_t control;
+ uint8_t mask;
+ uint8_t fine_x;
+ uint8_t coarse_x;
+ uint8_t coarse_y;
+ uint32_t nt_x_offset;
+ uint32_t nt_y_offset;
+ uint8_t fine_y;
+
+ uint8_t tmp_fine_x;
+ uint8_t tmp_coarse_x;
+ uint8_t tmp_fine_y;
+ uint8_t tmp_coarse_y;
+ uint32_t tmp_nt_x;
+ uint32_t tmp_nt_y;
+ uint32_t tmp_addr;
+
+ uint32_t vram_addr;
+ uint8_t write_latch;
+
+ uint32_t cycle;
+ uint32_t scanline;
+ uint32_t dot;
+ uint32_t frame;
+
+ uint32_t bg_shift_lo;
+ uint32_t bg_shift_hi;
+ uint32_t attr_shift_lo;
+ uint32_t attr_shift_hi;
+ uint8_t bg_attribute_latch;
+
+ uint8_t next_tile;
+ uint8_t next_attr;
+ uint8_t next_lo;
+ uint8_t next_hi;
+
+ uint8_t vram[2048];
+ uint8_t palette[32];
+ uint8_t oam[256];
uint8_t oam_addr;
- uint8_t read_buffer;
-
- uint32_t scanline; // 0–261
- uint32_t dot; // 0–340
-
- // Scroll state
- uint32_t coarse_x_offs;
- uint32_t coarse_y_offs;
- uint32_t fine_x;
- uint32_t fine_y;
- uint32_t nt_base_x;
- uint32_t nt_base_y;
-
- // Latch state for $2005/$2006
- uint8_t write_toggle;
- uint32_t temp_coarse_x_offs;
- uint32_t temp_coarse_y_offs;
- uint32_t temp_fine_x;
- uint32_t temp_fine_y;
- uint32_t temp_nt_base_x;
- uint32_t temp_nt_base_y;
-
- // Background shift registers
- uint32_t bg_tile_lsb;
- uint32_t bg_tile_msb;
- uint32_t bg_attr_lsb;
- uint32_t bg_attr_msb;
-
- // Tile fetch latches
- uint8_t nt_byte;
- uint8_t attr_byte;
- uint8_t tile_lsb;
- uint8_t tile_msb;
-
- // Control and status
- uint8_t ctrl; // $2000
- uint8_t mask; // $2001
- uint8_t status; // $2002
-
- // Flags
- uint8_t frame_even;
- uint8_t nmi_occurred;
- uint8_t nmi_output;
+ uint8_t secondary_oam[32];
+ uint32_t sprite_count;
+ uint8_t sprite_zero_hit;
+ uint8_t sprite_overflow;
+
+ uint8_t pixels[256 * 240];
+ uint8_t vblank;
+ uint8_t frame_ready;
};
+
+
+
struct cpu_state {
uint32_t pc; // Program Counter
uint8_t sp; // Stack Pointer
@@ -99,7 +119,7 @@ struct cpu_state {
struct ines_state {
- uint16_t mapper;
+ uint32_t mapper;
uint8_t mirroring; // 0 = H, 1 = V, 2 = 4-screen
uint32_t prg_size;
uint32_t chr_size;
@@ -107,8 +127,8 @@ struct ines_state {
struct mapper {
void (*init)(struct nes_state *state);
- uint8_t (*read)(struct nes_state *state, uint16_t addr);
- void (*write)(struct nes_state *state, uint16_t addr, uint8_t value);
+ uint8_t (*read)(struct nes_state *state, uint32_t addr);
+ void (*write)(struct nes_state *state, uint32_t addr, uint8_t value);
void (*tick)(struct nes_state *state);
};
@@ -134,13 +154,13 @@ struct nes_state {
uint8_t nmi_pending;
};
-static const uint32_t nes_palette_argb[64] = {
- 0xff757575, 0xff8f1b27, 0xffab0000, 0xff9f0047, 0xff77008f, 0xff1300ab, 0xff0000a7, 0xff000b7f,
- 0xff002f43, 0xff004700, 0xff005100, 0xff173f00, 0xff5f3f1b, 0xff000000, 0xff000000, 0xff000000,
- 0xffbcbcbc, 0xffef7300, 0xffef3b23, 0xfff30083, 0xffbf00bf, 0xff5b00e7, 0xff002bdb, 0xff0f4fcb,
- 0xff00738b, 0xff009700, 0xff00ab00, 0xff3b9300, 0xff8b8300, 0xff000000, 0xff000000, 0xff000000,
- 0xffffffff, 0xffffbf3f, 0xffff975f, 0xfffd8ba7, 0xffff7bff, 0xffb777ff, 0xff6377ff, 0xff3b9bff,
- 0xff3fbff3, 0xff13d383, 0xff4bdf4f, 0xff98f858, 0xffdbeb00, 0xff000000, 0xff000000, 0xff000000,
- 0xffffffff, 0xffffe7ab, 0xffffd7c7, 0xffffcbd7, 0xffffc7ff, 0xffdbc7ff, 0xffb3bfff, 0xffabdbff,
- 0xffa3e7ff, 0xff83f7c7, 0xffb3ffbf, 0xffcfffb3, 0xfff3ff9f, 0xff000000, 0xff000000, 0xff000000
+static uint32_t nes_palette[64] = {
+ 0x757575ff, 0x271a75ff, 0x3b0072ff, 0x4c0f64ff, 0x400048ff, 0x600027ff, 0x600000ff, 0x500f00ff,
+ 0x783a00ff, 0x755c00ff, 0x406c00ff, 0x504764ff, 0x005468ff, 0x000000ff, 0x000000ff, 0x000000ff,
+ 0xbfbfbfff, 0x273aa7ff, 0x5c14a7ff, 0x7514a7ff, 0x751468ff, 0x982727ff, 0xa03a00ff, 0x986c00ff,
+ 0x888800ff, 0x689800ff, 0x3aa700ff, 0x6c6c6cff, 0x007878ff, 0x000000ff, 0x000000ff, 0x000000ff,
+ 0xffffffff, 0x3ab5ffff, 0x5cb5ffff, 0x9888ffff, 0xa778ffff, 0xc87878ff, 0xf05c00ff, 0xf08800ff,
+ 0xe0a700ff, 0xb8b800ff, 0x88c800ff, 0xcccc68ff, 0x00e0d8ff, 0x000000ff, 0x000000ff, 0x000000ff,
+ 0xffffffff, 0xa7e0ffff, 0xb8d8ffff, 0xc8c8ffff, 0xd8b8ffff, 0xd8a7a7ff, 0xf0d0b8ff, 0xf0d898ff,
+ 0xf0c878ff, 0xd8d878ff, 0xb8e078ff, 0xd0e0b8ff, 0xb8f0f0ff, 0x000000ff, 0x000000ff, 0x000000ff
};
diff --git a/ppu.c b/ppu.c
index adfdc13..19c4702 100644
--- a/ppu.c
+++ b/ppu.c
@@ -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);
}
}
+