summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--audio.c2
-rwxr-xr-xbuild.sh7
-rw-r--r--callbacks.c25
-rw-r--r--cpu.c7
-rw-r--r--cpu_opcodes.c1
-rw-r--r--dump_mappers.c112
-rw-r--r--mapper.c24
-rw-r--r--mapper.h11
-rw-r--r--mapper_0003.c25
-rw-r--r--mapper_0003.h4
-rw-r--r--mapper_0007.c44
-rw-r--r--mapper_0007.h7
-rw-r--r--mapper_000b.c27
-rw-r--r--mapper_000b.h6
-rw-r--r--mapper_2002.c28
-rw-r--r--mapper_2002.h7
-rw-r--r--memory.c21
-rw-r--r--mknes.c139
-rw-r--r--mknes.h7
-rw-r--r--ppu.c347
20 files changed, 627 insertions, 224 deletions
diff --git a/audio.c b/audio.c
index 4311d2f..587065c 100644
--- a/audio.c
+++ b/audio.c
@@ -199,7 +199,7 @@ int audio_initialize(void) {
params[0] = fmt_param;
params[1] = buf_param;
- int res = pw_stream_connect(pa_stream, PW_DIRECTION_OUTPUT, PW_ID_ANY, (pw_stream_flags)(PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_RT_PROCESS | PW_STREAM_FLAG_MAP_BUFFERS), params, 2);
+ int res = pw_stream_connect(pa_stream, PW_DIRECTION_OUTPUT, PW_ID_ANY, (PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_RT_PROCESS | PW_STREAM_FLAG_MAP_BUFFERS), params, 2);
pw_thread_loop_unlock(pa_thread_loop);
return 0;
diff --git a/build.sh b/build.sh
index 845da73..b548675 100755
--- a/build.sh
+++ b/build.sh
@@ -4,11 +4,12 @@
PROJECT_NAME="mknes" # Change this for each new project
# Base configuration common to all builds
-CFLAGS="-std=gnu++23 "
+CFLAGS="-std=gnu11 "
CFLAGS+="-mavx2 -mbmi2 -mtune=native -mfunction-return=keep -mindirect-branch=keep "
CFLAGS+="-fwrapv -ffast-math -fno-trapping-math -fwhole-program "
CFLAGS+="-fno-stack-protector -fno-PIE -no-pie -fno-strict-aliasing -ffunction-sections -fdata-sections "
-CFLAGS+="-fno-exceptions -fno-rtti -fno-use-cxa-atexit -fno-non-call-exceptions -fno-unwind-tables -fno-asynchronous-unwind-tables "
+# CFLAGS+="-fno-exceptions -fno-rtti -fno-use-cxa-atexit "
+CFLAGS+="-fno-non-call-exceptions -fno-unwind-tables -fno-asynchronous-unwind-tables "
CFLAGS+="-Wall -Wextra "
CFLAGS+="-Wno-unused-parameter -Wno-sign-compare -Wno-trigraphs -Wno-maybe-uninitialized "
CFLAGS+="-Wno-unused-variable -Wno-unused-const-variable -Wno-unused-function -Wno-write-strings -Wno-missing-field-initializers "
@@ -61,7 +62,7 @@ set -e
# Build Linux version
(
# ../bin/ctime -begin .${PROJECT_NAME}_linux
- g++ $CFLAGS ${PROJECT_NAME}.c -o ${PROJECT_NAME} $INCLUDE_PATHS $LINUX_INCLUDE $LDFLAGS $LINUX_LIBS
+ gcc $CFLAGS ${PROJECT_NAME}.c -o ${PROJECT_NAME} $INCLUDE_PATHS $LINUX_INCLUDE $LDFLAGS $LINUX_LIBS
# ../bin/ctime -end .${PROJECT_NAME}_linux $?
) &
diff --git a/callbacks.c b/callbacks.c
index 806fe86..a610a43 100644
--- a/callbacks.c
+++ b/callbacks.c
@@ -43,6 +43,7 @@ static void toggle_fullscreen(bool enable) {
}
static void key_callback(GLFWwindow *window, int key, int scancode, int action, int mods) {
+ struct nes_state *nes_state = (struct nes_state*)glfwGetWindowUserPointer(window);
if(key == GLFW_KEY_ESCAPE) {
if(action == GLFW_PRESS) {
@@ -50,6 +51,30 @@ static void key_callback(GLFWwindow *window, int key, int scancode, int action,
}
}
+ if(action == GLFW_PRESS) {
+ switch(key) {
+ case GLFW_KEY_X: nes_state->input[0] |= (1 << 0); break; // A
+ case GLFW_KEY_Z: nes_state->input[0] |= (1 << 1); break; // B
+ case GLFW_KEY_SPACE: nes_state->input[0] |= (1 << 2); break; // Select
+ case GLFW_KEY_ENTER: nes_state->input[0] |= (1 << 3); break; // Start
+ case GLFW_KEY_UP: nes_state->input[0] |= (1 << 4); break;
+ case GLFW_KEY_DOWN: nes_state->input[0] |= (1 << 5); break;
+ case GLFW_KEY_LEFT: nes_state->input[0] |= (1 << 6); break;
+ case GLFW_KEY_RIGHT: nes_state->input[0] |= (1 << 7); break;
+ }
+ } else if(action == GLFW_RELEASE) {
+ switch(key) {
+ case GLFW_KEY_X: nes_state->input[0] &= ~(1 << 0); break;
+ case GLFW_KEY_Z: nes_state->input[0] &= ~(1 << 1); break;
+ case GLFW_KEY_SPACE: nes_state->input[0] &= ~(1 << 2); break;
+ case GLFW_KEY_ENTER: nes_state->input[0] &= ~(1 << 3); break;
+ case GLFW_KEY_UP: nes_state->input[0] &= ~(1 << 4); break;
+ case GLFW_KEY_DOWN: nes_state->input[0] &= ~(1 << 5); break;
+ case GLFW_KEY_LEFT: nes_state->input[0] &= ~(1 << 6); break;
+ case GLFW_KEY_RIGHT: nes_state->input[0] &= ~(1 << 7); break;
+ }
+ }
+
if(action == GLFW_RELEASE) {
switch(key) {
case GLFW_KEY_F12: {
diff --git a/cpu.c b/cpu.c
index 263ee6e..34d5bed 100644
--- a/cpu.c
+++ b/cpu.c
@@ -20,7 +20,6 @@ static inline void unpack_flags(struct cpu_state *cpu, uint8_t value) {
cpu->c = value & 1;
}
-
static inline void update_zn(struct cpu_state *cpu, uint8_t result) {
cpu->z = (result == 0);
cpu->n = (result & 0x80) != 0;
@@ -35,6 +34,7 @@ struct addr_result {
#include "cpu_opcodes.c"
#include "cpu_opcodes_ud.c"
+__attribute__((hot))
static inline void do_nmi(struct nes_state * restrict state) {
struct cpu_state * restrict cpu = &state->cpu;
@@ -54,6 +54,7 @@ static inline void do_nmi(struct nes_state * restrict state) {
cpu->i = 1;
}
+__attribute__((hot))
static inline void do_irq(struct nes_state * restrict state) {
struct cpu_state * restrict cpu = &state->cpu;
@@ -73,6 +74,7 @@ static inline void do_irq(struct nes_state * restrict state) {
cpu->i = 1;
}
+__attribute__((hot))
static inline void check_interrupts(struct nes_state * restrict state) {
struct cpu_state * restrict cpu = &state->cpu;
@@ -85,6 +87,7 @@ static inline void check_interrupts(struct nes_state * restrict state) {
}
}
+__attribute__((hot))
static void cpu_tick(struct nes_state *state) {
struct cpu_state * restrict cpu = &state->cpu;
@@ -92,7 +95,7 @@ static void cpu_tick(struct nes_state *state) {
uint8_t opcode;
- // printf("%5.5d %4.4x: ", line++, cpu->pc);
+ // printf("%4.4x: ", 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->cycles);
opcode_lut[opcode](state);
diff --git a/cpu_opcodes.c b/cpu_opcodes.c
index 5fd5634..b879015 100644
--- a/cpu_opcodes.c
+++ b/cpu_opcodes.c
@@ -583,7 +583,6 @@ static void opcode_sei(struct nes_state * restrict state) {
// CMP
-
static inline void cmp(struct cpu_state * restrict cpu, uint8_t value) {
uint8_t result = cpu->a - value;
cpu->c = (cpu->a >= value);
diff --git a/dump_mappers.c b/dump_mappers.c
new file mode 100644
index 0000000..42034de
--- /dev/null
+++ b/dump_mappers.c
@@ -0,0 +1,112 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <dirent.h>
+#include <zlib.h>
+#include <minizip/unzip.h>
+#include <sys/stat.h>
+
+#define INES_HEADER_SIZE 16
+
+static void process_nes_file(const char *path) {
+ FILE *f = fopen(path, "rb");
+ if(!f) return;
+
+ uint8_t header[INES_HEADER_SIZE];
+ if(fread(header, 1, INES_HEADER_SIZE, f) != INES_HEADER_SIZE) {
+ fclose(f);
+ return;
+ }
+ fclose(f);
+
+ if(memcmp(header, "NES\x1A", 4) != 0) return;
+
+ uint8_t mapper_low = (header[6] >> 4);
+ uint8_t mapper_high = (header[7] & 0xF0);
+ uint8_t mapper_ext = header[8] & 0x0F;
+ uint8_t submapper = header[8] >> 4;
+
+ uint32_t mapper = (submapper << 12) | (mapper_ext << 8) | mapper_high | mapper_low;
+ printf("0x%04x %s\n", mapper, path);
+}
+
+static void process_zip_file(const char *path) {
+ unzFile zip = unzOpen(path);
+ if(!zip) return;
+
+ if(unzGoToFirstFile(zip) != UNZ_OK) {
+ unzClose(zip);
+ return;
+ }
+
+ char filename[256];
+ unz_file_info info;
+ if(unzGetCurrentFileInfo(zip, &info, filename, sizeof(filename), 0, 0, 0, 0) != UNZ_OK) {
+ unzClose(zip);
+ return;
+ }
+
+ if(strstr(filename, ".nes") == 0) {
+ unzClose(zip);
+ return;
+ }
+
+ if(unzOpenCurrentFile(zip) != UNZ_OK) {
+ unzClose(zip);
+ return;
+ }
+
+ uint8_t header[INES_HEADER_SIZE];
+ if(unzReadCurrentFile(zip, header, INES_HEADER_SIZE) != INES_HEADER_SIZE) {
+ unzCloseCurrentFile(zip);
+ unzClose(zip);
+ return;
+ }
+ unzCloseCurrentFile(zip);
+ unzClose(zip);
+
+ if(memcmp(header, "NES\x1A", 4) != 0) return;
+
+ uint8_t mapper_low = (header[6] >> 4);
+ uint8_t mapper_high = (header[7] & 0xF0);
+ uint8_t mapper_ext = header[8] & 0x0F;
+ uint8_t submapper = header[8] >> 4;
+
+ uint32_t mapper = (submapper << 12) | (mapper_ext << 8) | mapper_high | mapper_low;
+ printf("0x%04x %s\n", mapper, path);
+}
+
+static void scan_directory(const char *path) {
+ DIR *dir = opendir(path);
+ if(!dir) return;
+
+ struct dirent *entry;
+ while((entry = readdir(dir))) {
+ if(entry->d_name[0] == '.') continue;
+
+ char fullpath[1024];
+ snprintf(fullpath, sizeof(fullpath), "%s/%s", path, entry->d_name);
+
+ struct stat st;
+ if(stat(fullpath, &st) < 0) continue;
+
+ if(S_ISDIR(st.st_mode)) {
+ scan_directory(fullpath);
+ } else if(strstr(fullpath, ".nes")) {
+ process_nes_file(fullpath);
+ } else if(strstr(fullpath, ".zip")) {
+ process_zip_file(fullpath);
+ }
+ }
+ closedir(dir);
+}
+
+int main(int argc, char **argv) {
+ if(argc < 2) {
+ fprintf(stderr, "usage: %s <romdir>\n", argv[0]);
+ return 1;
+ }
+ scan_directory(argv[1]);
+ return 0;
+}
diff --git a/mapper.c b/mapper.c
index 09d9a62..821224d 100644
--- a/mapper.c
+++ b/mapper.c
@@ -1,7 +1,11 @@
#include "mapper_0000.c"
+#include "mapper_0003.c"
+#include "mapper_0007.c"
+#include "mapper_000b.c"
#include "mapper_0042.c"
+#include "mapper_2002.c"
static uint8_t mapper_default_ciram_read(struct nes_state *state, uint32_t addr) {
@@ -32,23 +36,29 @@ static void mapper_default_tick(struct nes_state *state) { // No IRQ or timing l
* NOTE(peter): Mapper 0 always has to be first!
*/
static struct mapper_entry mapper_table[] = {
-/* Mapper: 0 */ { 0x00, mapper_0000_prg_read, mapper_0000_prg_write, mapper_0000_chr_read, mapper_0000_chr_write, mapper_default_ciram_read, mapper_default_ciram_write, mapper_default_tick, mapper_0000_init },
-/* Mapper: 66 */ { 0x42, mapper_0042_prg_read, mapper_0042_prg_write, mapper_0042_chr_read, mapper_0042_chr_write, mapper_default_ciram_read, mapper_default_ciram_write, mapper_default_tick, mapper_0042_init },
+/* Mapper: 0 */ { 0x00, mapper_0000_prg_read, mapper_0000_prg_write, mapper_0000_chr_read, mapper_0000_chr_write, mapper_default_ciram_read, mapper_default_ciram_write, mapper_default_tick, mapper_0000_init },
+/* Mapper: 3 */ { 0x03, mapper_0003_prg_read, mapper_0003_prg_write, mapper_0003_chr_read, mapper_0003_chr_write, mapper_default_ciram_read, mapper_default_ciram_write, mapper_default_tick, mapper_0003_init },
+/* Mapper: 7 */ { 0x07, mapper_0007_prg_read, mapper_0007_prg_write, mapper_0007_chr_read, mapper_0007_chr_write, mapper_0007_ciram_read, mapper_0007_ciram_write, mapper_default_tick, mapper_0007_init },
+/* Mapper: b */ { 0x0b, mapper_000b_prg_read, mapper_000b_prg_write, mapper_000b_chr_read, mapper_000b_chr_write, mapper_default_ciram_read, mapper_default_ciram_write, mapper_default_tick, mapper_000b_init },
+/* Mapper: 66 */ { 0x42, mapper_0042_prg_read, mapper_0042_prg_write, mapper_0042_chr_read, mapper_0042_chr_write, mapper_default_ciram_read, mapper_default_ciram_write, mapper_default_tick, mapper_0042_init },
+/* Mapper: 2002 */ { 0x2002, mapper_2002_prg_read, mapper_2002_prg_write, mapper_2002_chr_read, mapper_2002_chr_write, mapper_default_ciram_read, mapper_default_ciram_write, mapper_default_tick, mapper_2002_init },
};
-
static void mapper_setup(struct nes_state *state) {
- uint32_t mapper = state->ines.mapper;
+ uint32_t mapper = (state->ines.submapper << 12) | state->ines.mapper;
for(uint32_t i = 0; i < sizeof(mapper_table)/sizeof(mapper_table[0]); i++) {
if(mapper_table[i].id == mapper) {
- state->mapper = mapper_table[i];
+ printf("Mapper %4.4x\n", mapper);
+ memcpy(&state->mapper, &mapper_table[i], sizeof(struct mapper_entry)); // NOTE(peter): BECAUSE GCC IS BROKEN
+ // state->mapper = mapper_table[i];
state->mapper.init(state);
return;
}
}
- printf("Unsupported mapper %d, falling back to NROM\n", mapper);
- state->mapper = mapper_table[0];
+ printf("Unsupported mapper %4.4x, falling back to NROM\n", mapper);
+ memcpy(&state->mapper, &mapper_table[0], sizeof(struct mapper_entry)); // NOTE(peter): BECAUSE GCC IS BROKEN
+ // state->mapper = mapper_table[0];
state->mapper.init(state);
}
diff --git a/mapper.h b/mapper.h
index e7e1437..808bc4e 100644
--- a/mapper.h
+++ b/mapper.h
@@ -1,9 +1,14 @@
+#include "mapper_0003.h"
+#include "mapper_0007.h"
+#include "mapper_000b.h"
#include "mapper_0042.h"
+#include "mapper_2002.h"
+struct nes_state;
struct mapper_entry {
- int id;
+ uint64_t id;
uint8_t (*prg_read)(struct nes_state *state, uint32_t addr);
void (*prg_write)(struct nes_state *state, uint32_t addr, uint8_t value);
uint8_t (*chr_read)(struct nes_state *state, uint32_t addr);
@@ -15,6 +20,10 @@ struct mapper_entry {
};
union mapper_data {
+ struct mapper_0003 m0003;
+ struct mapper_0007 m0007;
+ struct mapper_000b m000b;
struct mapper_0042 m0042;
+ struct mapper_2002 m2002;
};
diff --git a/mapper_0003.c b/mapper_0003.c
new file mode 100644
index 0000000..743df51
--- /dev/null
+++ b/mapper_0003.c
@@ -0,0 +1,25 @@
+
+
+static void mapper_0003_init(struct nes_state *state) {
+ state->map.m0003.chr_ptr = state->chr_rom;
+}
+
+static uint8_t mapper_0003_prg_read(struct nes_state *state, uint32_t addr) {
+ if(addr >= 0x8000) {
+ return state->prg_rom[addr - 0x8000];
+ }
+ return 0;
+}
+
+static void mapper_0003_prg_write(struct nes_state *state, uint32_t addr, uint8_t value) {
+ if(addr >= 0x8000) {
+ state->map.m0003.chr_ptr = state->chr_rom + (value & 3) * 0x2000;
+ }
+}
+
+static uint8_t mapper_0003_chr_read(struct nes_state *state, uint32_t addr) {
+ return state->map.m0003.chr_ptr[addr];
+}
+
+static void mapper_0003_chr_write(struct nes_state *state, uint32_t addr, uint8_t value) {
+}
diff --git a/mapper_0003.h b/mapper_0003.h
new file mode 100644
index 0000000..c562519
--- /dev/null
+++ b/mapper_0003.h
@@ -0,0 +1,4 @@
+
+struct mapper_0003 {
+ uint8_t *chr_ptr;
+};
diff --git a/mapper_0007.c b/mapper_0007.c
new file mode 100644
index 0000000..c1ad7fa
--- /dev/null
+++ b/mapper_0007.c
@@ -0,0 +1,44 @@
+static void mapper_0007_init(struct nes_state *state) {
+ state->map.m0007.prg_ptr = state->prg_rom;
+ state->map.m0007.mirroring = 0;
+}
+
+static uint8_t mapper_0007_prg_read(struct nes_state *state, uint32_t addr) {
+ if(addr >= 0x8000) {
+ return state->map.m0007.prg_ptr[addr - 0x8000];
+ }
+ return 0;
+}
+
+static void mapper_0007_prg_write(struct nes_state *state, uint32_t addr, uint8_t value) {
+ if(addr >= 0x8000) {
+ state->map.m0007.prg_ptr = state->prg_rom + ((value & 0x07) * 0x8000);
+ state->map.m0007.mirroring = (value & 0x10) ? 1 : 0;
+ }
+}
+
+static uint8_t mapper_0007_chr_read(struct nes_state *state, uint32_t addr) {
+ return state->chr_ram[addr];
+}
+
+static void mapper_0007_chr_write(struct nes_state *state, uint32_t addr, uint8_t value) {
+ state->chr_ram[addr] = value;
+}
+
+static uint8_t mapper_0007_ciram_read(struct nes_state *state, uint32_t addr) {
+ if(state->map.m0007.mirroring == 0) {
+ addr = (addr & 0x800) | (addr & 0x3ff);
+ } else {
+ addr = addr & 0x7ff;
+ }
+ return state->ciram[addr];
+}
+
+static void mapper_0007_ciram_write(struct nes_state *state, uint32_t addr, uint8_t value) {
+ if(state->map.m0007.mirroring == 0) {
+ addr = (addr & 0x800) | (addr & 0x3ff);
+ } else {
+ addr = addr & 0x7ff;
+ }
+ state->ciram[addr] = value;
+}
diff --git a/mapper_0007.h b/mapper_0007.h
new file mode 100644
index 0000000..632b57c
--- /dev/null
+++ b/mapper_0007.h
@@ -0,0 +1,7 @@
+
+
+struct mapper_0007 {
+ uint8_t *prg_ptr;
+ uint8_t mirroring;
+};
+
diff --git a/mapper_000b.c b/mapper_000b.c
new file mode 100644
index 0000000..9252060
--- /dev/null
+++ b/mapper_000b.c
@@ -0,0 +1,27 @@
+
+
+static void mapper_000b_init(struct nes_state *state) {
+ state->map.m000b.prg_ptr = state->prg_rom;
+ state->map.m000b.chr_ptr = state->chr_rom;
+}
+
+static uint8_t mapper_000b_prg_read(struct nes_state *state, uint32_t addr) {
+ if(addr >= 0x8000) {
+ return state->map.m000b.prg_ptr[addr - 0x8000];
+ }
+ return 0;
+}
+
+static void mapper_000b_prg_write(struct nes_state *state, uint32_t addr, uint8_t value) {
+ if(addr >= 0x8000) {
+ state->map.m000b.prg_ptr = state->prg_rom + ((value >> 4) & 7) * 0x8000;
+ state->map.m000b.chr_ptr = state->chr_rom + (value & 0x0F) * 0x2000;
+ }
+}
+
+static uint8_t mapper_000b_chr_read(struct nes_state *state, uint32_t addr) {
+ return state->map.m000b.chr_ptr[addr];
+}
+
+static void mapper_000b_chr_write(struct nes_state *state, uint32_t addr, uint8_t value) {
+}
diff --git a/mapper_000b.h b/mapper_000b.h
new file mode 100644
index 0000000..2c3f15a
--- /dev/null
+++ b/mapper_000b.h
@@ -0,0 +1,6 @@
+
+
+struct mapper_000b {
+ uint8_t *prg_ptr;
+ uint8_t *chr_ptr;
+};
diff --git a/mapper_2002.c b/mapper_2002.c
new file mode 100644
index 0000000..700a24b
--- /dev/null
+++ b/mapper_2002.c
@@ -0,0 +1,28 @@
+
+static void mapper_2002_init(struct nes_state *state) {
+ state->map.m2002.prg_bank0 = state->prg_rom; // default to bank 0
+ state->map.m2002.prg_bank1 = state->prg_rom + state->ines.prg_size - 0x4000;
+}
+
+static uint8_t mapper_2002_prg_read(struct nes_state *state, uint32_t addr) {
+ if(addr >= 0x8000 && addr < 0xc000) {
+ return state->map.m2002.prg_bank0[addr & 0x3fff];
+ } else if(addr >= 0xc000) {
+ return state->map.m2002.prg_bank1[addr & 0x3fff];
+ }
+ return 0;
+}
+
+static void mapper_2002_prg_write(struct nes_state *state, uint32_t addr, uint8_t value) {
+ if(addr >= 0x8000) {
+ state->map.m2002.prg_bank0 = state->prg_rom + ((value & 0x0f) * 0x4000);
+ }
+}
+
+static uint8_t mapper_2002_chr_read(struct nes_state *state, uint32_t addr) {
+ return state->chr_ram[addr];
+}
+
+static void mapper_2002_chr_write(struct nes_state *state, uint32_t addr, uint8_t value) {
+ state->chr_ram[addr] = value;
+}
diff --git a/mapper_2002.h b/mapper_2002.h
new file mode 100644
index 0000000..6c648b5
--- /dev/null
+++ b/mapper_2002.h
@@ -0,0 +1,7 @@
+
+
+
+struct mapper_2002 {
+ uint8_t *prg_bank0; // $8000–BFFF (switchable)
+ uint8_t *prg_bank1; // $C000–FFFF (fixed to last 16KB)
+};
diff --git a/memory.c b/memory.c
index 28c51ad..a2e094f 100644
--- a/memory.c
+++ b/memory.c
@@ -10,9 +10,11 @@ static uint8_t memory_read(struct nes_state *restrict state, uint32_t offset) {
return state->ram[offset & 0x07ff];
} else if(offset < 0x4000) {
return ppu_read(state, offset);
- // } else if(offset < 0x4020) {
- // // TODO: APU and I/O reads
- // return 0;
+ } else if(offset == 0x4016 || offset == 0x4017) {
+ uint32_t index = offset & 1;
+ uint8_t value = (state->input_latch[index] >> state->input_bit[index]) & 1;
+ state->input_bit[index]++;
+ return value | 0x40; // Bit 6 open bus high, bit 7 low
} else if(LIKELY(offset >= 0x6000)) {
return state->mapper.prg_read(state, offset);
}
@@ -30,8 +32,17 @@ static void memory_write(struct nes_state *restrict state, uint32_t offset, uint
ppu_write(state, offset, value);
} else if(offset == 0x4014) {
ppu_dma_4014(state, value);
- // } else if(offset < 0x4020) {
- // // TODO: APU and I/O writes
+ } else if(offset == 0x4016) {
+ uint8_t prev = state->input_strobe;
+ state->input_strobe = value & 1;
+
+ if(prev == 1 && (value & 1) == 0) {
+ // Latch current inputs
+ state->input_latch[0] = state->input[0];
+ state->input_latch[1] = state->input[1];
+ state->input_bit[0] = 0;
+ state->input_bit[1] = 0;
+ }
} else if(offset >= 0x6000) {
state->mapper.prg_write(state, offset, value);
}
diff --git a/mknes.c b/mknes.c
index 25e920f..7417bed 100644
--- a/mknes.c
+++ b/mknes.c
@@ -67,10 +67,7 @@ struct main_state state;
uint32_t buffer[BUFFER_WIDTH * BUFFER_HEIGHT] __attribute__((section(".bss"), aligned(4096)));
uint32_t display_buffer[BUFFER_WIDTH * BUFFER_HEIGHT] __attribute__((section(".bss"), aligned(4096)));
-void audio_callback(int16_t *data, size_t frames) {
-
-}
-
+static void audio_callback(int16_t *data, size_t frames) { }
#define FRAME_INTERVAL_NS (1000000000ULL / 60.0988)
@@ -84,7 +81,6 @@ static GLFWwindow *window;
#include "opengl.c"
#include "render.c"
-#include "callbacks.c"
// NES core
#include "mapper.h"
@@ -96,6 +92,7 @@ static GLFWwindow *window;
#include "ines2.c"
#include "mapper.c"
+#include "callbacks.c"
struct nes_state nstate;
static uint32_t frames;
@@ -103,40 +100,29 @@ static uint32_t frames;
#define PRG_ROM_SIZE (512 * 1024)
#define CHR_ROM_SIZE (512 * 1024)
-#define PIXELS_SIZE (256 * 240)
+#define PIXELS_SIZE (256 * 240)
#define RAM_SIZE 0x800
#define SRAM_SIZE 0x2000
-#define CIRAM_SIZE 0x1000
+#define CIRAM_SIZE 0x1000
+#define CHR_RAM_SIZE 0x4000
static struct nes_state *allocate_nes_state(void) {
struct nes_state *state = (struct nes_state*)calloc(1, sizeof(struct nes_state));
if(!state) return 0;
- size_t total_size = (PRG_ROM_SIZE + CHR_ROM_SIZE + PIXELS_SIZE + RAM_SIZE + SRAM_SIZE + CIRAM_SIZE + 4095) & ~0xfff;
+ size_t total_size = (PRG_ROM_SIZE + CHR_ROM_SIZE + PIXELS_SIZE + RAM_SIZE + SRAM_SIZE + CIRAM_SIZE + CHR_RAM_SIZE+ 4095) & ~0xfff;
uint8_t *m = (uint8_t*)aligned_alloc(4096, total_size);
memset(m, 0, total_size);
size_t offset = 0;
-
- state->prg_rom = m + offset;
- offset += PRG_ROM_SIZE;
-
- state->chr_rom = m + offset;
- offset += CHR_ROM_SIZE;
-
- state->pixels = m + offset;
- offset += PIXELS_SIZE;
-
- state->ram = m + offset;
- offset += RAM_SIZE;
-
- state->sram = m + offset;
- offset += SRAM_SIZE;
-
- state->ciram = m + offset;
- offset += CIRAM_SIZE;
-
+ state->prg_rom = m + offset; offset += PRG_ROM_SIZE;
+ state->chr_rom = m + offset; offset += CHR_ROM_SIZE;
+ state->pixels = m + offset; offset += PIXELS_SIZE;
+ state->ram = m + offset; offset += RAM_SIZE;
+ state->sram = m + offset; offset += SRAM_SIZE;
+ state->ciram = m + offset; offset += CIRAM_SIZE;
+ state->chr_ram = m + offset; offset += CHR_RAM_SIZE;
return state;
}
@@ -154,29 +140,46 @@ int main(int argc, char **argv) {
init_opcode_ud_lut();
// protect_opcode_lut();
ppu_reset(nstate);
- // 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");
- // ines2_load(nstate, "data/nrom/scanline.nes");
- // ines2_load(nstate, "data/nrom/Sayoonara!.NES");
- // ines2_load(nstate, "data/nrom/raster_demos/RasterChromaLuma.NES");
- // ines2_load(nstate, "data/nrom/raster_demos/RasterTest1.NES");
- // ines2_load(nstate, "data/nrom/raster_demos/RasterTest2.NES");
- // ines2_load(nstate, "data/nrom/raster_demos/RasterTest3.NES");
- // ines2_load(nstate, "data/nrom/raster_demos/RasterTest3a.NES");
- // ines2_load(nstate, "data/nrom/raster_demos/RasterTest3b.NES");
- // ines2_load(nstate, "data/nrom/raster_demos/RasterTest3c.NES");
- // ines2_load(nstate, "data/nrom/raster_demos/RasterTest3d.NES");
- // ines2_load(nstate, "data/nrom/raster_demos/RasterTest3e.NES");
- // ines2_load(nstate, "data/nrom/NEStress.NES");
+ // ines2_load(nstate, "data/0000/10-Yard Fight (USA, Europe).nes");
+ // ines2_load(nstate, "data/0000/Balloon Fight (USA).nes");
+ // ines2_load(nstate, "data/0000/Excitebike (Japan, USA).nes");
+ // ines2_load(nstate, "data/0000/Ice Climber (USA, Europe, Korea).nes");
+ // ines2_load(nstate, "data/0000/Kung Fu (Japan, USA).nes");
+ ines2_load(nstate, "data/0000/Super Mario Bros. (World) (HVC-SM).nes");
+ // ines2_load(nstate, "data/0000/Urban Champion (World).nes");
+ // ines2_load(nstate, "data/0000/Wrecking Crew (World).nes");
+ // ines2_load(nstate, "data/0000/scanline.nes");
+ // ines2_load(nstate, "data/0000/Sayoonara!.NES");
+ // ines2_load(nstate, "data/0000/raster_demos/RasterChromaLuma.NES");
+ // ines2_load(nstate, "data/0000/raster_demos/RasterTest1.NES");
+ // ines2_load(nstate, "data/0000/raster_demos/RasterTest2.NES");
+ // ines2_load(nstate, "data/0000/raster_demos/RasterTest3.NES");
+ // ines2_load(nstate, "data/0000/raster_demos/RasterTest3a.NES");
+ // ines2_load(nstate, "data/0000/raster_demos/RasterTest3b.NES");
+ // ines2_load(nstate, "data/0000/raster_demos/RasterTest3c.NES");
+ // ines2_load(nstate, "data/0000/raster_demos/RasterTest3d.NES");
+ // ines2_load(nstate, "data/0000/raster_demos/RasterTest3e.NES");
+ // ines2_load(nstate, "data/0000/NEStress.NES");
+ // ines2_load(nstate, "data/0000/Super Mario Bros. (World) (HVC-SM).zip");
+ // ines2_load(nstate, "data/0042/Super Mario Bros. + Duck Hunt (USA).zip");
+ // ines2_load(nstate, "data/0000/Xevious - The Avenger (USA).zip");
// ines2_load(nstate, "data/tv.nes");
- // ines2_load(nstate, "data/Super Mario Bros. (World) (HVC-SM).zip");
- // ines2_load(nstate, "data/Super Mario Bros. + Duck Hunt (USA).zip");
+
+ // ines2_load(nstate, "data/0003/Flipull - An Exciting Cube Game (Japan) (En).zip");
+ // ines2_load(nstate, "data/0003/Friday the 13th (USA).zip");
+ // ines2_load(nstate, "data/0003/Ghostbusters (Japan).zip");
+
+ // ines2_load(nstate, "data/0007/Battletoads (USA).zip");
+ // ines2_load(nstate, "data/0007/Beetlejuice (USA).zip");
+ // ines2_load(nstate, "data/0007/Cabal (USA).zip");
+
+ // ines2_load(nstate, "data/000b/Baby Boomer (USA) (Unl).zip");
+ // ines2_load(nstate, "data/000b/Captain Comic - The Adventure (USA) (Unl).zip");
+ // ines2_load(nstate, "data/000b/King Neptune's Adventure (USA) (Unl).zip");
+
+ // ines2_load(nstate, "data/2002/Attack Animal Gakuen (Japan).zip");
+ // ines2_load(nstate, "data/2002/Ballblazer (Japan).zip");
+ // ines2_load(nstate, "data/2002/Best of the Best - Championship Karate (USA).zip");
mapper_setup(nstate);
uint32_t lo = nstate->mapper.prg_read(nstate, 0xfffc);
@@ -197,6 +200,7 @@ int main(int argc, char **argv) {
window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "NES Emulator", 0, 0);
if(window) {
+ glfwSetWindowUserPointer(window, (void*)nstate);
glfwSetWindowAspectRatio(window, 320, 240);
glfwSetWindowSizeLimits(window, 320*3, 240*3, GLFW_DONT_CARE, GLFW_DONT_CARE);
@@ -208,6 +212,7 @@ int main(int argc, char **argv) {
framebuffer_callback(window, WINDOW_WIDTH, WINDOW_HEIGHT);
+
for(int jid = GLFW_JOYSTICK_1; jid <= GLFW_JOYSTICK_LAST; jid++) {
if(glfwJoystickPresent(jid)) {
const char *name = glfwGetJoystickName(jid);
@@ -216,14 +221,14 @@ int main(int argc, char **argv) {
}
}
- set_decay(40);
+ set_decay(10);
timer_start(timer);
- while(!glfwWindowShouldClose(window)) {
- // for(uint32_t i = 0; i < 0x5000; ++ i) {
- timer_wait(timer);
- glfwPollEvents();
+ // while(!glfwWindowShouldClose(window)) {
+ for(uint32_t i = 0; i < 0x5000; ++ i) {
+ // timer_wait(timer);
+ // glfwPollEvents();
// //
while(!nstate->ppu.frame_ready) {
@@ -233,19 +238,19 @@ int main(int argc, char **argv) {
nstate->ppu.frame_ready = 0;
frames++;
- uint32_t * restrict dst = buffer;
- uint8_t * restrict src = nstate->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;
- }
- apply_phosphor_decay();
- render_frame();
- glfwSwapBuffers(window);
+ // uint32_t * restrict dst = buffer;
+ // uint8_t * restrict src = nstate->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;
+ // }
+ // apply_phosphor_decay();
+ // render_frame();
+ // glfwSwapBuffers(window);
}
printf("total frames: %6.6d total cycles: %ld\n", frames, nstate->cycles);
glfwDestroyWindow(window);
diff --git a/mknes.h b/mknes.h
index c1784f6..a296fe4 100644
--- a/mknes.h
+++ b/mknes.h
@@ -97,15 +97,22 @@ struct nes_state {
struct cpu_state cpu;
uint8_t irq_pending;
uint8_t nmi_pending;
+ uint8_t input[2]; // Controller 1 & 2
+ uint8_t input_latch[2]; // Latched inputs after strobe
+ uint8_t input_strobe; // Control bit (0 or 1)
+ uint8_t input_bit[2]; // Current bit position being shifted out
struct ppu_state ppu;
+
struct mapper_entry mapper;
union mapper_data map;
+
uint8_t *pixels;
uint8_t *ram;
uint8_t *sram;
uint8_t *ciram;
uint8_t *prg_rom;
uint8_t *chr_rom;
+ uint8_t *chr_ram;
};
diff --git a/ppu.c b/ppu.c
index db78b91..da360e0 100644
--- a/ppu.c
+++ b/ppu.c
@@ -124,8 +124,8 @@ static inline uint8_t ppu_read(struct nes_state *state, uint32_t offset) {
return result;
}
-__attribute__((hot))
-static void ppu_evaluate_sprites(struct nes_state *state) {
+__attribute__((always_inline, hot))
+static inline 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;
@@ -158,8 +158,8 @@ static void ppu_evaluate_sprites(struct nes_state *state) {
ppu->sprite_count = n;
}
-__attribute__((hot))
-static void ppu_fetch_sprite_patterns(struct nes_state *state) {
+__attribute__((always_inline, hot))
+static inline void ppu_fetch_sprite_patterns(struct nes_state *state) {
struct ppu_state *ppu = &state->ppu;
uint32_t addr;
uint32_t bank;
@@ -201,16 +201,14 @@ static void ppu_fetch_sprite_patterns(struct nes_state *state) {
}
}
-__attribute__((hot))
-static void ppu_render_pixel(struct nes_state *state) {
+__attribute__((always_inline, hot))
+static inline void ppu_render_pixel(struct nes_state *state) {
struct ppu_state *ppu = &state->ppu;
uint32_t x = ppu->dot - 1;
uint32_t y = ppu->scanline;
- // Fine X shift mask
- // static const uint16_t fine_shift[8] = { 0x8000, 0x4000, 0x2000, 0x1000, 0x0800, 0x0400, 0x0200, 0x0100 };
- uint16_t bit = 0x8000 >> ppu->fine_x;//fine_shift[ppu->fine_x];
+ uint16_t bit = 0x8000 >> ppu->fine_x;
uint8_t bg_pixel = 0;
uint8_t bg_palette = 0;
@@ -219,7 +217,6 @@ static void ppu_render_pixel(struct nes_state *state) {
uint8_t sp_prio = 0;
uint8_t sp_zero = 0;
-#if 1 // TODO(peter): Decide what I prefer, masking away unlikely path, or LIKELY hint to the compiler
uint8_t bg_mask = (ppu->reg_mask & 0x08) ? 0xff : 0x00;
uint8_t sp_mask = (ppu->reg_mask & 0x10) ? 0xff : 0x00;
@@ -247,36 +244,7 @@ static void ppu_render_pixel(struct nes_state *state) {
sp_zero = (ppu->sprite_indexes[i] == 0);
break;
}
-#else
- // Background fetch
- if(LIKELY(ppu->reg_mask & 0x08)) {
- uint8_t p0 = !!(ppu->bg_shift_pattern_low & bit);
- uint8_t p1 = !!(ppu->bg_shift_pattern_high & bit);
- bg_pixel = (p1 << 1) | p0;
-
- uint8_t a0 = !!(ppu->bg_shift_attrib_low & bit);
- uint8_t a1 = !!(ppu->bg_shift_attrib_high & bit);
- bg_palette = (a1 << 1) | a0;
- }
-
- // Sprite fetch
- if(LIKELY(ppu->reg_mask & 0x10)) {
- for(uint8_t i = 0; i < ppu->sprite_count; i++) {
- if(ppu->sprite_positions[i]) continue;
-
- uint8_t lo = ppu->sprite_shift_lo[i];
- uint8_t hi = ppu->sprite_shift_hi[i];
- sp_pixel = ((hi & 0x80) >> 6) | ((lo & 0x80) >> 7);
-
- if(!sp_pixel) continue;
- sp_palette = ppu->secondary_oam[i * 4 + 2] & 3;
- sp_prio = ppu->sprite_priorities[i];
- sp_zero = (ppu->sprite_indexes[i] == 0);
- break;
- }
- }
-#endif
// Final pixel composition
uint8_t palette_index = 0;
uint8_t bg_index = (bg_palette << 2) + bg_pixel;
@@ -309,117 +277,222 @@ static void ppu_tick(struct nes_state *state) {
for(uint32_t ppu_loops = 0; ppu_loops < 3; ++ppu_loops) {
if(LIKELY(rendering)) {
+ switch(scanline) {
+ case 0 ... 239: {
+ switch(dot) {
+ case 1:
+ __attribute__((fallthrough));
+
+ case 2 ... 256: // fallthrough: this is 1->256
+ ppu_render_pixel(state);
+
+ if(UNLIKELY(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);
+ }
+ }
+ __attribute__((fallthrough));
+
+ case 321 ... 336: { // fallthrough: the code below has to run 1->256 + 321->336
+ // Rendering and tile fetch goes here
+ if(ppu->reg_mask & 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;
+ }
+ }
+ }
- if(ppu->even_frame && dot == 0) {
- // call mapper_tick here.
- ppu->dot++;
- }
+ ppu->bg_shift_pattern_low <<= 1;
+ ppu->bg_shift_pattern_high <<= 1;
+ ppu->bg_shift_attrib_low <<= 1;
+ ppu->bg_shift_attrib_high <<= 1;
+
+ switch(dot % 8) {
+ case 1: {
+ uint32_t nt_addr = 0x2000 | (ppu->vram_addr & 0x0fff);
+ ppu->bg_next_tile_id = state->mapper.ciram_read(state, nt_addr);
+ break;
+ }
+ case 3: {
+ uint32_t attr_addr = 0x23c0 | (ppu->vram_addr & 0x0c00) | ((ppu->vram_addr >> 4) & 0x38) | ((ppu->vram_addr >> 2) & 0x07);
+ uint8_t attr = state->mapper.ciram_read(state, attr_addr & 0x0fff);
+ uint8_t shift = ((ppu->vram_addr >> 4) & 4) | (ppu->vram_addr & 2);
+ ppu->bg_next_tile_attrib = (attr >> shift) & 3;
+ break;
+ }
+ 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->mapper.chr_read(state, addr_lsb);
+ break;
+ }
+ case 7: {
+ 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->mapper.chr_read(state, 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++;
+ }
+
+ break;
+ }
+ }
+ } break;
- if(scanline < 240 && dot >= 1 && dot <= 256) {
- ppu_render_pixel(state);
- }
+ case 257: { // Sprite evaluation and horizontal vram transfer
+ ppu->vram_addr = (ppu->vram_addr & ~0x041f) | (ppu->temp_addr & 0x041f);
+ ppu_evaluate_sprites(state);
+ } break;
- if(scanline < 240 || scanline == 261) {
- if((dot >= 1 && dot <= 256) || (dot >= 321 && dot <= 336)) {
+ case 340: { // sprite fetch pattern
+ ppu_fetch_sprite_patterns(state);
+ } break;
+ }
- if(ppu->reg_mask & 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;
+ } break;
+
+ case 261: {
+ switch(dot) {
+ case 1:
+ // Clear vblank, sprite 0 hit, etc.
+ __attribute__((fallthrough));
+
+ case 2 ... 256:
+ if(UNLIKELY(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);
+ }
}
- }
- }
- ppu->bg_shift_pattern_low <<= 1;
- ppu->bg_shift_pattern_high <<= 1;
- ppu->bg_shift_attrib_low <<= 1;
- ppu->bg_shift_attrib_high <<= 1;
-
- switch(dot % 8) {
- case 1: {
- uint32_t nt_addr = 0x2000 | (ppu->vram_addr & 0x0fff);
- ppu->bg_next_tile_id = state->mapper.ciram_read(state, nt_addr);
- break;
- }
- case 3: {
- uint32_t attr_addr = 0x23c0 | (ppu->vram_addr & 0x0c00) | ((ppu->vram_addr >> 4) & 0x38) | ((ppu->vram_addr >> 2) & 0x07);
- uint8_t attr = state->mapper.ciram_read(state, attr_addr & 0x0fff);
- uint8_t shift = ((ppu->vram_addr >> 4) & 4) | (ppu->vram_addr & 2);
- ppu->bg_next_tile_attrib = (attr >> shift) & 3;
- break;
- }
- 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->mapper.chr_read(state, addr_lsb);
- break;
- }
- case 7: {
- 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->mapper.chr_read(state, 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++;
+ __attribute__((fallthrough));
+ case 321 ... 336: { // Rendering and tile fetch
+ if(ppu->reg_mask & 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;
+ }
+ }
}
- break;
- }
- }
- }
+ ppu->bg_shift_pattern_low <<= 1;
+ ppu->bg_shift_pattern_high <<= 1;
+ ppu->bg_shift_attrib_low <<= 1;
+ ppu->bg_shift_attrib_high <<= 1;
+
+ switch(dot % 8) {
+ case 1: {
+ uint32_t nt_addr = 0x2000 | (ppu->vram_addr & 0x0fff);
+ ppu->bg_next_tile_id = state->mapper.ciram_read(state, nt_addr);
+ break;
+ }
+ case 3: {
+ uint32_t attr_addr = 0x23c0 | (ppu->vram_addr & 0x0c00) | ((ppu->vram_addr >> 4) & 0x38) | ((ppu->vram_addr >> 2) & 0x07);
+ uint8_t attr = state->mapper.ciram_read(state, attr_addr & 0x0fff);
+ uint8_t shift = ((ppu->vram_addr >> 4) & 4) | (ppu->vram_addr & 2);
+ ppu->bg_next_tile_attrib = (attr >> shift) & 3;
+ break;
+ }
+ 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->mapper.chr_read(state, addr_lsb);
+ break;
+ }
+ case 7: {
+ 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->mapper.chr_read(state, 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++;
+ }
+
+ break;
+ }
+ }
+ } break;
- 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);
- }
- }
+ case 257: { // Sprite evaluation and horizontal vram transfer
+ ppu->vram_addr = (ppu->vram_addr & ~0x041f) | (ppu->temp_addr & 0x041f);
+ ppu_evaluate_sprites(state);
- if(dot == 257) {
- ppu->vram_addr = (ppu->vram_addr & ~0x041f) | (ppu->temp_addr & 0x041f);
- }
+ } break;
- if(UNLIKELY(scanline == 261) && dot >= 280 && dot <= 304) {
- ppu->vram_addr = (ppu->vram_addr & ~0x7be0) | (ppu->temp_addr & 0x7be0);
- }
+ case 280 ... 304: { // Vertical vram transfer
+ ppu->vram_addr = (ppu->vram_addr & ~0x7be0) | (ppu->temp_addr & 0x7be0);
+ } break;
- if(dot == 257 && LIKELY(scanline < 240)) {
- ppu_evaluate_sprites(state);
- }
+ case 340: { // Sprite pattern fetch
+ ppu_fetch_sprite_patterns(state);
+ } break;
- if(dot == 340 && (LIKELY(scanline < 240) || UNLIKELY(scanline == 261))) {
- ppu_fetch_sprite_patterns(state);
- }
+ }
+
+ } break;
}
}