- Ready to add support for mappers - Block-line background rendering presents certain artifacts in some vertical-scrolling games like 1942master
| @@ -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 | |||
| @@ -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; | |||
| } | |||
| @@ -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_ | |||
| @@ -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) { | |||
| @@ -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*); | |||
| @@ -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); | |||
| @@ -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); | |||
| } | |||
| */ | |||
| @@ -3,6 +3,8 @@ | |||
| #include <stdint.h> | |||
| #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_ | |||
| @@ -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; | |||
| @@ -0,0 +1,92 @@ | |||
| #include <stdlib.h> | |||
| #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, | |||
| }; | |||
| @@ -0,0 +1,48 @@ | |||
| #ifndef NES_VRAM_ | |||
| #define NES_VRAM_ | |||
| #include <stdint.h> | |||
| #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_ | |||