From 66460ee5fe0c0de79aeff884fa10019afb01a0a9 Mon Sep 17 00:00:00 2001 From: Nathaniel Walizer Date: Tue, 3 Dec 2024 18:35:24 -0800 Subject: [PATCH] Add horizontal/vertical VRAM mirroring - Ready to add support for mappers - Block-line background rendering presents certain artifacts in some vertical-scrolling games like 1942 --- Makefile | 2 +- src/ines.c | 20 ++++++++--- src/ines.h | 2 +- src/nes.c | 6 ++-- src/nes.h | 10 ++++-- src/nese.c | 7 ++-- src/ppu.c | 24 ++++++------- src/ppu.h | 9 +++-- src/sdl_render.c | 3 +- src/vram.c | 92 ++++++++++++++++++++++++++++++++++++++++++++++++ src/vram.h | 48 +++++++++++++++++++++++++ 11 files changed, 193 insertions(+), 30 deletions(-) create mode 100644 src/vram.c create mode 100644 src/vram.h diff --git a/Makefile b/Makefile index d0d5735..c557550 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ BINDIR = bin TARGET_1 = nese LDLIBS_1 = -lSDL2 -SRC_SRCS_1 = nese.c ines.c nes.c ppu.c input.c +SRC_SRCS_1 = nese.c ines.c nes.c ppu.c input.c vram.c SRC_SRCS_1 += sdl_render.c sdl_input.c EXT_SRCS_1 = e6502/e6502.c diff --git a/src/ines.c b/src/ines.c index 7c9d963..80bfac9 100644 --- a/src/ines.c +++ b/src/ines.c @@ -38,7 +38,7 @@ int ines_check(ines_Header* ret, FILE* file) { return status; } -int ines_load(nes_cart_rom* cart_rom, FILE* file) { +int ines_load(nes_cart* cart, FILE* file) { int status = 0; ines_Header hdr = {0}; @@ -56,14 +56,14 @@ int ines_load(nes_cart_rom* cart_rom, FILE* file) { prg_size, nes_mem_rom_size); status = -1; - } else if (1 != fread(cart_rom->prg, prg_size, 1, file)) { + } else if (1 != fread(cart->rom.prg, prg_size, 1, file)) { INES_ERR("Failed to read program ROM"); status = -1; } else if (1U == hdr.prg_size_lsb) { // If there's only one PRG_ROM chunk, duplicate it - memcpy(cart_rom->prg + ines_prg_rom_chunk, - cart_rom, ines_prg_rom_chunk); + memcpy(cart->rom.prg + ines_prg_rom_chunk, + cart->rom.prg, ines_prg_rom_chunk); } } @@ -74,11 +74,21 @@ int ines_load(nes_cart_rom* cart_rom, FILE* file) { chr_size, nes_ppu_mem_size); status = -1; - } else if (1 != fread(cart_rom->chr, chr_size, 1, file)) { + } else if (1 != fread(cart->rom.chr, chr_size, 1, file)) { INES_ERR("Failed to read sprite ROM"); status = -1; } } + if (0 == status) { + if (hdr.flags_6 & ines_Flag_Horizontal) { + cart->flags |= Cart_Flag_Horizontal; + } else { + cart->flags &= ~Cart_Flag_Horizontal; + } + cart->mapper = (hdr.flags_6 & ines_Mapper_Nibble_Lo) >> 4 | + (hdr.flags_7 & ines_Mapper_Nibble_Hi); + } + return status; } diff --git a/src/ines.h b/src/ines.h index 46a52f3..91bad63 100644 --- a/src/ines.h +++ b/src/ines.h @@ -47,7 +47,7 @@ typedef struct { int ines_check(ines_Header*, FILE*); -int ines_load(nes_cart_rom*, FILE*); +int ines_load(nes_cart*, FILE*); #endif // INES_H_ diff --git a/src/nes.c b/src/nes.c index 9ca833d..081028a 100644 --- a/src/nes.c +++ b/src/nes.c @@ -69,10 +69,12 @@ void nes_mem_write(nes* sys, uint16_t addr, uint8_t val) { } } -void nes_init(nes* sys) { +int nes_init(nes* sys) { e6502_init(&sys->cpu, (e6502_Read*)nes_mem_read, (e6502_Write*)nes_mem_write, sys); - nes_ppu_init(&sys->ppu, sys->cart.rom.chr); + return nes_ppu_init(&sys->ppu, sys->cart.rom.chr, + (sys->cart.flags & Cart_Flag_Horizontal), + sys->cart.mapper); } void nes_reset(nes* sys) { diff --git a/src/nes.h b/src/nes.h index b4d175f..e1b1e2a 100644 --- a/src/nes.h +++ b/src/nes.h @@ -43,10 +43,16 @@ typedef struct { uint8_t chr[nes_ppu_mem_size]; } nes_cart_rom; +typedef enum { + Cart_Flag_Vertical = 0b0, + Cart_Flag_Horizontal = 0b1, +} nes_Cart_Flags; + typedef struct { nes_cart_rom rom; uint8_t wram[nes_mem_wram_size]; - // TODO: Mapper support + nes_Cart_Flags flags; + int mapper; } nes_cart; typedef struct { @@ -62,7 +68,7 @@ uint8_t nes_mem_read(nes*, uint16_t addr); void nes_mem_write(nes*, uint16_t addr, uint8_t); -void nes_init(nes*); +int nes_init(nes*); void nes_reset(nes*); diff --git a/src/nese.c b/src/nese.c index 2c96124..ab5ec60 100644 --- a/src/nese.c +++ b/src/nese.c @@ -66,7 +66,7 @@ int main(int argc, char* argv[]) { int n_loops = (argc > 1) ? atoi(argv[1]) : 0; if (n_loops <= 0) n_loops = INT_MAX; - status = ines_load(&sys.cart.rom, stdin); + status = ines_load(&sys.cart, stdin); nes_Renderer* rend = &sdl_renderer; if (status == 0) { @@ -79,7 +79,10 @@ int main(int argc, char* argv[]) { } if (status == 0) { - nes_init(&sys); + status = nes_init(&sys); + } + + if (status == 0) { nes_reset(&sys); nes_render(rend, &sys.ppu); diff --git a/src/ppu.c b/src/ppu.c index 40cb626..e4e294c 100644 --- a/src/ppu.c +++ b/src/ppu.c @@ -45,8 +45,10 @@ uint8_t nes_ppu_read(nes_ppu* ppu, uint16_t addr) { } else if (ppu->addr < nes_ppu_mem_vram_start + nes_ppu_mem_vram_size) { // printf("PPU: VRAM READ %04x > %02x\n", ppu->addr, val); - ppu->data = ppu->vram[ppu->addr - - nes_ppu_mem_vram_start]; + ppu->data = nes_vram_read( + ppu->vram_map, + ppu->addr - nes_ppu_mem_vram_start + ); } else if (ppu->addr < nes_ppu_mem_pal_start) { // printf("PPU: BLANK READ %04x > %02x\n", ppu->addr, val); ppu->data = ppu->chr_mem[ppu->addr]; @@ -152,7 +154,7 @@ void nes_ppu_write(nes_ppu* ppu, uint16_t addr, uint8_t val) { } */ // printf("PPU: VRAM %04x < %02x\n", vram_addr, val); - ppu->vram[vram_addr] = val; + nes_vram_write(ppu->vram_map, vram_addr, val); } ppu->addr += (ppu->control & ppu_Control_VRAM_Inc) ? @@ -172,12 +174,17 @@ void nes_ppu_reset(nes_ppu* ppu) { ppu->cycle = 0; } -void nes_ppu_init(nes_ppu* ppu, uint8_t* chr_mem) { +int nes_ppu_init(nes_ppu* ppu, uint8_t* chr_mem, + int horizontal, int mapper) { ppu->chr_mem = chr_mem; ppu->status = 0; ppu->oam_addr = 0; ppu->addr = 0; + ppu->vram_map = ( horizontal ? + &vram_horizontal : + &vram_vertical ); nes_ppu_reset(ppu); + return nes_vram_init(ppu->vram_map); } int nes_ppu_run(nes_ppu* ppu, int cycles) { @@ -224,12 +231,3 @@ int nes_ppu_run(nes_ppu* ppu, int cycles) { return result; } - -/* -int nes_ppu_cycles_til_vblank(nes_ppu* ppu) { - int cycles_til_vblank = nes_ppu_active_cycles - ( - ppu->cycle + (ppu->scanline * nes_ppu_dots)); - return (cycles_til_vblank > 0 ? cycles_til_vblank : - nes_ppu_vblank_cycles + cycles_til_vblank); -} -*/ diff --git a/src/ppu.h b/src/ppu.h index fc18574..002d30b 100644 --- a/src/ppu.h +++ b/src/ppu.h @@ -3,6 +3,8 @@ #include +#include "vram.h" + #define nes_ppu_dots (341U) #define nes_ppu_prerender (1U) @@ -79,8 +81,8 @@ typedef struct { // Memory uint8_t* chr_mem; uint8_t oam[nes_ppu_oam_size]; - uint8_t vram[nes_ppu_mem_vram_size]; uint8_t palette[nes_ppu_mem_pal_size]; + nes_vram_map* vram_map; // Timing int frame; @@ -108,7 +110,8 @@ typedef struct { uint8_t nes_ppu_read(nes_ppu* ppu, uint16_t addr); void nes_ppu_write(nes_ppu* ppu, uint16_t addr, uint8_t val); void nes_ppu_reset(nes_ppu* ppu); -void nes_ppu_init(nes_ppu* ppu, uint8_t* chr_mem); +int nes_ppu_init(nes_ppu* ppu, uint8_t* chr_mem, + int horizonal, int mapper); typedef enum { ppu_Result_Halt = -1, @@ -119,6 +122,6 @@ typedef enum { } nes_ppu_Result; nes_ppu_Result nes_ppu_run(nes_ppu* ppu, int cycles); -//int nes_ppu_cycles_til_vblank(nes_ppu* ppu); + #endif // ENES_PPU_H_ diff --git a/src/sdl_render.c b/src/sdl_render.c index dd8fbb9..1a39eaf 100644 --- a/src/sdl_render.c +++ b/src/sdl_render.c @@ -197,7 +197,8 @@ static void render_background_area(const nes_ppu* ppu, int page, void* buffer, int pitch, int xs, int ys, int w, int h) { int bank = (ppu->control & ppu_Control_Back_Bank) ? 0x100 : 0; - const uint8_t* index_line = &ppu->vram[page * 0x400U]; + const uint8_t* index_line = nes_vram_page(ppu->vram_map, + page); const uint8_t* attrs = index_line + 960U; index_line += xs + (ys * nes_ppu_blocks_w); uint8_t* dst_line = (uint8_t*)buffer; diff --git a/src/vram.c b/src/vram.c new file mode 100644 index 0000000..1a93714 --- /dev/null +++ b/src/vram.c @@ -0,0 +1,92 @@ +#include + +#include "vram.h" + + +typedef enum { + Mirror_Method_Vertical, + Mirror_Method_Horizontal, +} vram_Mirror_Method; + +typedef struct { + vram_Mirror_Method method; + uint8_t vram[nes_vram_page_size * 2]; +} vram_mirror; + + +static vram_mirror* vram_mirror_create(vram_Mirror_Method method) { + vram_mirror* mirror = calloc(1, sizeof(vram_mirror)); + if (NULL != mirror) mirror->method = method; + return mirror; +} + +static void vram_mirror_destroy(vram_mirror* mirror) { + free(mirror); +} + +static inline uint8_t* vram_mirror_page(vram_mirror* mirror, + int page) { + if (mirror->method == Mirror_Method_Vertical) { + page >>= 1; + } + return &mirror->vram[(page & 1) ? nes_vram_page_size : 0]; +} + +static inline uint8_t* vram_mirror_map(vram_mirror* mirror, + uint16_t addr) { + uint8_t* page = vram_mirror_page(mirror, (addr >> 10)); + return &page[addr & nes_vram_addr_mask]; +} + +static int vram_map_mirror_init_method(nes_vram_map* map, + vram_Mirror_Method method) { + map->data = vram_mirror_create(method); + return (NULL == map->data ? -1 : 0); +} + +static int vram_map_mirror_init_horz(nes_vram_map* map) { + return vram_map_mirror_init_method( + map, Mirror_Method_Horizontal + ); +} + +static int vram_map_mirror_init_vert(nes_vram_map* map) { + return vram_map_mirror_init_method( + map, Mirror_Method_Vertical + ); +} + +static void vram_map_mirror_done(nes_vram_map* map) { + vram_mirror_destroy((vram_mirror*)map->data); +} + +static uint8_t vram_map_mirror_read(nes_vram_map* map, + uint16_t addr) { + return *(vram_mirror_map((vram_mirror*)map->data, addr)); +} + +static void vram_map_mirror_write(nes_vram_map* map, + uint16_t addr, + uint8_t val) { + *(vram_mirror_map((vram_mirror*)map->data, addr)) = val; +} + +static uint8_t* vram_map_mirror_page(nes_vram_map* map, int page) { + return vram_mirror_page((vram_mirror*)map->data, page); +} + +nes_vram_map vram_horizontal = { + .init = vram_map_mirror_init_horz, + .done = vram_map_mirror_done, + .read = vram_map_mirror_read, + .write = vram_map_mirror_write, + .page = vram_map_mirror_page, +}; + +nes_vram_map vram_vertical = { + .init = vram_map_mirror_init_vert, + .done = vram_map_mirror_done, + .read = vram_map_mirror_read, + .write = vram_map_mirror_write, + .page = vram_map_mirror_page, +}; diff --git a/src/vram.h b/src/vram.h new file mode 100644 index 0000000..33c5464 --- /dev/null +++ b/src/vram.h @@ -0,0 +1,48 @@ +#ifndef NES_VRAM_ +#define NES_VRAM_ + +#include + + +#define nes_vram_page_size (0x400U) +#define nes_vram_page_mask (0xC00U) +#define nes_vram_addr_mask (0x3FFU) + + +typedef struct nes_vram_map_t { + int (*init)(struct nes_vram_map_t*); + void (*done)(struct nes_vram_map_t*); + uint8_t* (*page)(struct nes_vram_map_t*, int page); + uint8_t (*read)(struct nes_vram_map_t*, uint16_t addr); + void (*write)(struct nes_vram_map_t*, uint16_t addr, uint8_t val); + void* data; +} nes_vram_map; + +static inline int nes_vram_init(nes_vram_map* map) { + return map->init(map); +} + +static inline void nes_vram_done(nes_vram_map* map) { + map->done(map); +} + +static inline uint8_t* nes_vram_page(nes_vram_map* map, int page) { + return map->page(map, page); +} + +static inline uint8_t nes_vram_read(nes_vram_map* map, + uint16_t addr) { + return map->read(map, addr); +} + +static inline void nes_vram_write(nes_vram_map* map, + uint16_t addr, uint8_t val) { + map->write(map, addr, val); +} + + +extern nes_vram_map vram_horizontal; +extern nes_vram_map vram_vertical; + + +#endif // NES_VRAM_