From 0370da8538dbf67f37bc5e8f6863ff466512100b Mon Sep 17 00:00:00 2001 From: Nathaniel Walizer Date: Thu, 13 Mar 2025 01:45:31 -0700 Subject: [PATCH] PPU rendering stub; interrupt and memory bugfixes --- Makefile | 2 + src/f6502.c | 9 ++- src/linux/port.c | 140 ++++++++++++++++++++++++++++++++++++++++++++--- src/nes.c | 47 +++++++++------- src/nes.h | 2 +- src/nese.c | 4 +- src/nese.h | 2 +- src/port.h | 6 +- src/ppu.c | 54 ++++++++++++++++++ src/ppu.h | 3 + 10 files changed, 235 insertions(+), 34 deletions(-) diff --git a/Makefile b/Makefile index b7be8d7..bcaf68f 100644 --- a/Makefile +++ b/Makefile @@ -45,6 +45,8 @@ all: $(BINDIR)/nese $(BINDIR)/test: CFLAGS += -DF6502_FLAT -DF6502_TEST -DF6502_TRACE $(BINDIR)/nese: CFLAGS += $(foreach debug,$(NESE_DEBUG), -DDEBUG_$(debug)) +$(BINDIR)/nese: CFLAGS += $(shell sdl2-config --cflags) +$(BINDIR)/nese: LDFLAGS += $(shell sdl2-config --libs) $(BINDIR)/nese: $(NESE_OBJS) @mkdir -p $(@D) $(LD) $^ $(LDFLAGS) -o $@ diff --git a/src/f6502.c b/src/f6502.c index 2a23007..f504d0b 100644 --- a/src/f6502.c +++ b/src/f6502.c @@ -15,6 +15,9 @@ (type *)((char *)__mptr - offsetof(type,member));}) #endif +#define DEBUG "Core" +#include "log.h" + // TOTO: Temp Hack volatile uint8_t memval; @@ -174,6 +177,7 @@ static inline bool f6502_write(nes_Memory* mem, // Keep ctrl & t nametable in sync mem->ppu.ctrl &= ~ppu_Control_Nametable_Mask; mem->ppu.ctrl |= ((val >> 10) & ppu_Control_Nametable_Mask); + LOGI("PPU: ADDR %04X", mem->ppu.addr); } else { mem->ppu.t &= 0x00FFU; mem->ppu.t |= (uint16_t)(val & 0x3FU) << 8; @@ -183,8 +187,9 @@ static inline bool f6502_write(nes_Memory* mem, case ppu_reg_data: // Disallow CHR ROM writes + addr = mem->ppu.addr & 0x3FFFU; if (addr >= NES_PPU_CHR_SIZE || mem->ppu.chr_ram) { - addr = mem->ppu.addr; + LOGI("PPU W %04X < %02X", addr, val); mem->ppu.bank[addr >> 10][addr & 0x3FFU] = val; if (addr >= NES_PPU_PAL_START) { addr = 0x300U | (addr & 0x1FU); @@ -197,6 +202,8 @@ static inline bool f6502_write(nes_Memory* mem, } } } + } else { + LOGE("PPU W %04X < %02X : INVALID", addr, val); } mem->ppu.addr += mem->ppu.addr_inc; break; diff --git a/src/linux/port.c b/src/linux/port.c index 79c0da1..6485d6c 100644 --- a/src/linux/port.c +++ b/src/linux/port.c @@ -4,6 +4,8 @@ #include #include +#include + #include "nese.h" #include "port.h" @@ -25,7 +27,63 @@ int nese_unmap_file(void* addr, int size) { return munmap(addr, size); } -int nese_frame_ready() { + +typedef struct { + nes* sys; + SDL_Window* window; + SDL_Renderer* renderer; + SDL_Texture* texture; + SDL_Surface* target; +} platform_data; + +// TODO: Return an action enum? +static int process_events(nes* sys) { + int status = 0; + + SDL_Event event = {0}; + while (0 == status && 0 != SDL_PollEvent(&event)) { + if (SDL_QUIT == event.type) { + status = -1; + } + + // TODO: Update gamepad + // TODO: Menu actions, &c. + } + + return status; +} + +static SDL_Color nes_palette[64] = { + {0x80,0x80,0x80}, {0x00,0x00,0xBB}, {0x37,0x00,0xBF}, {0x84,0x00,0xA6}, + {0xBB,0x00,0x6A}, {0xB7,0x00,0x1E}, {0xB3,0x00,0x00}, {0x91,0x26,0x00}, + {0x7B,0x2B,0x00}, {0x00,0x3E,0x00}, {0x00,0x48,0x0D}, {0x00,0x3C,0x22}, + {0x00,0x2F,0x66}, {0x00,0x00,0x00}, {0x05,0x05,0x05}, {0x05,0x05,0x05}, + + {0xC8,0xC8,0xC8}, {0x00,0x59,0xFF}, {0x44,0x3C,0xFF}, {0xB7,0x33,0xCC}, + {0xFF,0x33,0xAA}, {0xFF,0x37,0x5E}, {0xFF,0x37,0x1A}, {0xD5,0x4B,0x00}, + {0xC4,0x62,0x00}, {0x3C,0x7B,0x00}, {0x1E,0x84,0x15}, {0x00,0x95,0x66}, + {0x00,0x84,0xC4}, {0x11,0x11,0x11}, {0x09,0x09,0x09}, {0x09,0x09,0x09}, + + {0xFF,0xFF,0xFF}, {0x00,0x95,0xFF}, {0x6F,0x84,0xFF}, {0xD5,0x6F,0xFF}, + {0xFF,0x77,0xCC}, {0xFF,0x6F,0x99}, {0xFF,0x7B,0x59}, {0xFF,0x91,0x5F}, + {0xFF,0xA2,0x33}, {0xA6,0xBF,0x00}, {0x51,0xD9,0x6A}, {0x4D,0xD5,0xAE}, + {0x00,0xD9,0xFF}, {0x66,0x66,0x66}, {0x0D,0x0D,0x0D}, {0x0D,0x0D,0x0D}, + + {0xFF,0xFF,0xFF}, {0x84,0xBF,0xFF}, {0xBB,0xBB,0xFF}, {0xD0,0xBB,0xFF}, + {0xFF,0xBF,0xEA}, {0xFF,0xBF,0xCC}, {0xFF,0xC4,0xB7}, {0xFF,0xCC,0xAE}, + {0xFF,0xD9,0xA2}, {0xCC,0xE1,0x99}, {0xAE,0xEE,0xB7}, {0xAA,0xF7,0xEE}, + {0xB3,0xEE,0xFF}, {0xDD,0xDD,0xDD}, {0x11,0x11,0x11}, {0x11,0x11,0x11} +}; + +int nese_line_ready(void* plat_data, uint8_t* buffer, int line) { + // TODO: Render line to target? + (void)nes_palette; + return 0; +} + +int nese_frame_ready(void* plat_data) { + int status = 0; + platform_data* plat = (platform_data*)plat_data; /* static int frame = 0; static int ignore = 1; @@ -46,27 +104,95 @@ int nese_frame_ready() { // TODO: Render frame // TODO: Time sync - // TODO: Handle quit signal + + status = process_events(plat->sys); + // TODO: Perform menu actions - return 0; + + return status; } -int nese_update_input(nes_Input* input) { +int nese_update_input(void* plat_data, nes_Input* input) { // TODO: Populate the gamepad states return 0; } +static int plat_init(platform_data* plat) { + int status = SDL_Init( + SDL_INIT_EVENTS | + SDL_INIT_VIDEO + ); + + if (0 == status) { + plat->window = SDL_CreateWindow( + "NESe", + SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, + nes_ppu_render_w * 8, + nes_ppu_render_h * 7, + 0 + ); + if (NULL == plat->window) { + LOGE("SDL: Failed to create window"); + status = -1; + } + } + + if (0 == status) { + plat->renderer = SDL_CreateRenderer( + plat->window, -1, + SDL_RENDERER_ACCELERATED | + SDL_RENDERER_PRESENTVSYNC + ); + if (NULL == plat->renderer) { + LOGE("SDL: Failed to create renderer"); + status = -1; + } + } + + if (0 == status) { + plat->texture = SDL_CreateTexture( + plat->renderer, SDL_PIXELFORMAT_RGB888, + SDL_TEXTUREACCESS_STREAMING, + nes_ppu_render_w, nes_ppu_render_h + ); + if (NULL == plat->texture) { + LOGE("SDL: Failed to create target"); + status = -1; + } else { + SDL_LockTextureToSurface(plat->texture, NULL, + &plat->target); +// SDL_FillRect(data->target, NULL, color_background); + } + } + + return status; +} + +static void plat_done(platform_data* plat) { + if (NULL != plat->texture) SDL_DestroyTexture(plat->texture); + if (NULL != plat->renderer) SDL_DestroyRenderer(plat->renderer); + if (NULL != plat->window) SDL_DestroyWindow(plat->window); + SDL_Quit(); +} + +// This is too big for the stack. static nes sys = {0}; int main(int argc, char* argv[]) { - int status = 0; + // This should be tiny enough to keep on the stack. + platform_data plat = { + .sys = &sys, + }; + int status = plat_init(&plat); if (argc <= 1) { LOGE("No ROM file provided."); status = -1; - } else { - status = nese_start(&sys, argv[1]); + } else if (0 == status) { + status = nese_start(&sys, argv[1], &plat); } + plat_done(&plat); return status; } diff --git a/src/nes.c b/src/nes.c index 801c6e0..70114b6 100644 --- a/src/nes.c +++ b/src/nes.c @@ -3,6 +3,9 @@ #include "nes.h" #include "port.h" +#define DEBUG "NES" +#include "log.h" + void nes_init(nes* sys) { f6502_init(&sys->core); @@ -20,11 +23,12 @@ void nes_done(nes* sys) { // TODO: deallocate RAM, etc. } -static int nes_vsync(nes* sys) { +static int nes_vsync(nes* sys, void* plat) { int status = 0; sys->core.memory.ppu.status |= ppu_Status_VBlank; if (sys->core.memory.ppu.ctrl & ppu_Control_VBlank) { + LOGI("VBlank NMI"); f6502_set_NMI(&sys->core, 1); } @@ -34,30 +38,33 @@ static int nes_vsync(nes* sys) { nes_Memory* mem = &sys->core.memory; if (0 == status && NULL != mem->mapper.vsync) { mem->mapper.vsync(&mem->mapper); - status = nese_update_input(&sys->input); + status = nese_update_input(plat, &sys->input); } return status; } -static int nes_hsync(nes* sys) { +static int nes_hsync(nes* sys, void* plat) { int status = 0; // TODO: APU sync - // TODO: PPU Update H Bytes & Select Nametable - // TODO: PPU draw line if visible - // TODO: PPU update regs - - // TODO: This belongs in nes_ppu_drawline() -/* - if ( sys->ppu.scanline >= nes_ppu_visible_line && - sys->ppu.scanline < nes_ppu_postrender_line) { - // TODO: Pass scanline buffer - nese_line_ready(NULL, sys->ppu.scanline - - nes_ppu_visible_line); + if (sys->ppu.scanline < nes_ppu_postrender_line) { + if (sys->ppu.scanline < nes_ppu_visible_line) { + if ( sys->core.memory.ppu.mask & + (ppu_Mask_Sprite | ppu_Mask_Back)) { + sys->core.memory.ppu.addr = + sys->core.memory.ppu.t; + } + } else { + nes_ppu_render_line(&sys->ppu, + &sys->core.memory.ppu); + nese_line_ready( + plat, sys->ppu.line_data, + sys->ppu.scanline - nes_ppu_visible_line + ); + } } -*/ sys->ppu.scanline++; if (nes_ppu_frame_end_line == sys->ppu.scanline) { @@ -73,22 +80,23 @@ static int nes_hsync(nes* sys) { break; case nes_ppu_postrender_line: - status = nese_frame_ready(); + status = nese_frame_ready(plat); break; case nes_ppu_vblank_line: - status = nes_vsync(sys); + status = nes_vsync(sys, plat); break; } return status; } -int nes_loop(nes* sys) { +int nes_loop(nes* sys, void* plat) { int status = 0; int dot = 0; while (0 == status) { + // TODO: Move to inline PPU function int dots_remaining = nes_ppu_scanline_dots - dot; if ( sys->ppu.hit_line == sys->ppu.scanline && (sys->core.memory.ppu.mask & (ppu_Mask_Sprite | ppu_Mask_Back)) && @@ -101,6 +109,7 @@ int nes_loop(nes* sys) { dots_remaining = hit_dot - dot; } } + int cpu_cycles = (dots_remaining + 2) / 3; dot += 3 * f6502_step(&sys->core, cpu_cycles); @@ -111,7 +120,7 @@ int nes_loop(nes* sys) { if (NULL != mem->mapper.hsync) { mem->mapper.hsync(&mem->mapper); } - status = nes_hsync(sys); + status = nes_hsync(sys, plat); } } diff --git a/src/nes.h b/src/nes.h index 6887c7d..3de9957 100644 --- a/src/nes.h +++ b/src/nes.h @@ -19,7 +19,7 @@ typedef struct { void nes_init(nes*); void nes_reset(nes*); void nes_done(nes*); -int nes_loop(nes*); +int nes_loop(nes*, void*); #endif // NES_H_ diff --git a/src/nese.c b/src/nese.c index d1a8a2a..64ee31c 100644 --- a/src/nese.c +++ b/src/nese.c @@ -14,7 +14,7 @@ static int nese_file_size(FILE* file) { } -int nese_start(nes* sys, const char* filename) { +int nese_start(nes* sys, const char* filename, void* plat) { int status = 0; void* cart_data = NULL; int filesize = -1; @@ -41,7 +41,7 @@ int nese_start(nes* sys, const char* filename) { nes_reset(sys); - nes_loop(sys); + nes_loop(sys, plat); if (cart_data) nese_unmap_file(cart_data, filesize); diff --git a/src/nese.h b/src/nese.h index 3851867..a738f8b 100644 --- a/src/nese.h +++ b/src/nese.h @@ -4,7 +4,7 @@ #include "nes.h" -int nese_start(nes* sys, const char* filename); +int nese_start(nes* sys, const char* filename, void*); #endif // NESE_H_ diff --git a/src/port.h b/src/port.h index 0bfb2e3..61a59ff 100644 --- a/src/port.h +++ b/src/port.h @@ -9,9 +9,9 @@ void* nese_map_file(FILE* file, int size); int nese_unmap_file(void* addr, int size); -int nese_line_ready(uint8_t* buffer, int line); -int nese_frame_ready(); -int nese_update_input(nes_Input*); +int nese_line_ready(void*, uint8_t* buffer, int line); +int nese_frame_ready(void*); +int nese_update_input(void*, nes_Input*); #endif // NESE_PORT_H_ diff --git a/src/ppu.c b/src/ppu.c index 5de224b..fec41ff 100644 --- a/src/ppu.c +++ b/src/ppu.c @@ -1,5 +1,10 @@ +#include + #include "ppu.h" +#define DEBUG "PPU" +#include "log.h" + static const uint8_t mirror_schemes[][4] = { [nes_Mirror_Horizontal] = {0, 0, 1, 1}, @@ -50,3 +55,52 @@ void nes_ppu_find_hit_line(nes_PPU* ppu, nes_PPU_Memory* mem) { } } } + + +void nes_ppu_render_line(nes_PPU* ppu, nes_PPU_Memory* mem) { + uint8_t* ptr = ppu->line_data; + + if (!(mem->mask & ppu_Mask_Back)) { + memset(ptr, 0, nes_ppu_render_w); + } else { +/* + int bank = nes_PPU_Nametable_Bank_Index + + ((mem->addr >> 10) & 3); + + int y_coarse = (mem->addr >> 5) & 0x1F; + int y_fine = mem->addr >> 12; + int x_coarse = mem->addr & 0x1FU; + + const int bank_off = !!(mem->ctrl & ppu_Control_Back_Bank) << 2; + + const uint8_t* nametable = mem->bank[bank] + (y_coarse * 32) + x_coarse; + const uint8_t* attrs = mem->bank[bank] + 0x3C0U + ((y_coarse >> 2) << 3); + + ptr += 8 - mem->x; +*/ + + + // TODO: Draw Background + } + + // TODO: Draw Sprites + + if (mem->mask & (ppu_Mask_Sprite | ppu_Mask_Back)) { + uint16_t mask = 0b10000011111; + mem->addr = (mem->addr & ~mask) | (mem->t & mask); + + int y_scroll = (mem->addr >> 12) | + ((mem->addr >> 2) & 0xF8U); + if (nes_ppu_render_h - 1 == y_scroll) { + y_scroll = 0; + mem->addr ^= 0x800; + } else if (0xFFU == y_scroll) { + y_scroll = 0; + } else { + ++y_scroll; + } + mem->addr = (mem->addr & ~0b111001111100000) | + ((y_scroll & 7) << 12) | + ((y_scroll & 0xF8U) << 2); + } +} diff --git a/src/ppu.h b/src/ppu.h index 88270f6..9bcd554 100644 --- a/src/ppu.h +++ b/src/ppu.h @@ -130,9 +130,12 @@ void nes_ppu_set_mirroring(nes_PPU_Memory* mem, typedef struct { int scanline; int hit_line; + uint8_t line_data[nes_ppu_render_w]; } nes_PPU; void nes_ppu_find_hit_line(nes_PPU*, nes_PPU_Memory*); +void nes_ppu_render_line(nes_PPU*, nes_PPU_Memory*); + #endif // NESE_PPU_H_