From a386ef64f6376b3ef8434a6cdf456495287fcbca Mon Sep 17 00:00:00 2001 From: Peter Fors Date: Mon, 31 Mar 2025 20:31:05 +0200 Subject: currently 90% working --- base/base.c | 4 +- base/build.sh | 20 +- base/settings.h | 2 +- base/state.c | 11 +- build.sh | 1 + cpu.c | 19 +- cpu_opcodes.c | 4 + ines2.c | 10 +- mapper.c | 2 +- mapper_nrom.c | 23 +- memory.c | 21 +- mknes.c | 63 +++++- mknes.h | 178 ++++++++------- ppu.c | 677 ++++++++++++++++++++++++++++---------------------------- 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 +#include + +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); } } + -- cgit v1.2.3