- 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 | TARGET_1 = nese | ||||
| LDLIBS_1 = -lSDL2 | 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 | SRC_SRCS_1 += sdl_render.c sdl_input.c | ||||
| EXT_SRCS_1 = e6502/e6502.c | EXT_SRCS_1 = e6502/e6502.c | ||||
| @@ -38,7 +38,7 @@ int ines_check(ines_Header* ret, FILE* file) { | |||||
| return status; | return status; | ||||
| } | } | ||||
| int ines_load(nes_cart_rom* cart_rom, FILE* file) { | |||||
| int ines_load(nes_cart* cart, FILE* file) { | |||||
| int status = 0; | int status = 0; | ||||
| ines_Header hdr = {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); | prg_size, nes_mem_rom_size); | ||||
| status = -1; | 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"); | INES_ERR("Failed to read program ROM"); | ||||
| status = -1; | status = -1; | ||||
| } else if (1U == hdr.prg_size_lsb) { | } else if (1U == hdr.prg_size_lsb) { | ||||
| // If there's only one PRG_ROM chunk, duplicate it | // 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); | chr_size, nes_ppu_mem_size); | ||||
| status = -1; | 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"); | INES_ERR("Failed to read sprite ROM"); | ||||
| status = -1; | 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; | return status; | ||||
| } | } | ||||
| @@ -47,7 +47,7 @@ typedef struct { | |||||
| int ines_check(ines_Header*, FILE*); | int ines_check(ines_Header*, FILE*); | ||||
| int ines_load(nes_cart_rom*, FILE*); | |||||
| int ines_load(nes_cart*, FILE*); | |||||
| #endif // INES_H_ | #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_init(&sys->cpu, (e6502_Read*)nes_mem_read, | ||||
| (e6502_Write*)nes_mem_write, sys); | (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) { | void nes_reset(nes* sys) { | ||||
| @@ -43,10 +43,16 @@ typedef struct { | |||||
| uint8_t chr[nes_ppu_mem_size]; | uint8_t chr[nes_ppu_mem_size]; | ||||
| } nes_cart_rom; | } nes_cart_rom; | ||||
| typedef enum { | |||||
| Cart_Flag_Vertical = 0b0, | |||||
| Cart_Flag_Horizontal = 0b1, | |||||
| } nes_Cart_Flags; | |||||
| typedef struct { | typedef struct { | ||||
| nes_cart_rom rom; | nes_cart_rom rom; | ||||
| uint8_t wram[nes_mem_wram_size]; | uint8_t wram[nes_mem_wram_size]; | ||||
| // TODO: Mapper support | |||||
| nes_Cart_Flags flags; | |||||
| int mapper; | |||||
| } nes_cart; | } nes_cart; | ||||
| typedef struct { | 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_mem_write(nes*, uint16_t addr, uint8_t); | ||||
| void nes_init(nes*); | |||||
| int nes_init(nes*); | |||||
| void nes_reset(nes*); | void nes_reset(nes*); | ||||
| @@ -66,7 +66,7 @@ int main(int argc, char* argv[]) { | |||||
| int n_loops = (argc > 1) ? atoi(argv[1]) : 0; | int n_loops = (argc > 1) ? atoi(argv[1]) : 0; | ||||
| if (n_loops <= 0) n_loops = INT_MAX; | 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; | nes_Renderer* rend = &sdl_renderer; | ||||
| if (status == 0) { | if (status == 0) { | ||||
| @@ -79,7 +79,10 @@ int main(int argc, char* argv[]) { | |||||
| } | } | ||||
| if (status == 0) { | if (status == 0) { | ||||
| nes_init(&sys); | |||||
| status = nes_init(&sys); | |||||
| } | |||||
| if (status == 0) { | |||||
| nes_reset(&sys); | nes_reset(&sys); | ||||
| nes_render(rend, &sys.ppu); | 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 + | } else if (ppu->addr < nes_ppu_mem_vram_start + | ||||
| nes_ppu_mem_vram_size) { | nes_ppu_mem_vram_size) { | ||||
| // printf("PPU: VRAM READ %04x > %02x\n", ppu->addr, val); | // 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) { | } else if (ppu->addr < nes_ppu_mem_pal_start) { | ||||
| // printf("PPU: BLANK READ %04x > %02x\n", ppu->addr, val); | // printf("PPU: BLANK READ %04x > %02x\n", ppu->addr, val); | ||||
| ppu->data = ppu->chr_mem[ppu->addr]; | 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); | // 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) ? | ppu->addr += (ppu->control & ppu_Control_VRAM_Inc) ? | ||||
| @@ -172,12 +174,17 @@ void nes_ppu_reset(nes_ppu* ppu) { | |||||
| ppu->cycle = 0; | 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->chr_mem = chr_mem; | ||||
| ppu->status = 0; | ppu->status = 0; | ||||
| ppu->oam_addr = 0; | ppu->oam_addr = 0; | ||||
| ppu->addr = 0; | ppu->addr = 0; | ||||
| ppu->vram_map = ( horizontal ? | |||||
| &vram_horizontal : | |||||
| &vram_vertical ); | |||||
| nes_ppu_reset(ppu); | nes_ppu_reset(ppu); | ||||
| return nes_vram_init(ppu->vram_map); | |||||
| } | } | ||||
| int nes_ppu_run(nes_ppu* ppu, int cycles) { | int nes_ppu_run(nes_ppu* ppu, int cycles) { | ||||
| @@ -224,12 +231,3 @@ int nes_ppu_run(nes_ppu* ppu, int cycles) { | |||||
| return result; | 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 <stdint.h> | ||||
| #include "vram.h" | |||||
| #define nes_ppu_dots (341U) | #define nes_ppu_dots (341U) | ||||
| #define nes_ppu_prerender (1U) | #define nes_ppu_prerender (1U) | ||||
| @@ -79,8 +81,8 @@ typedef struct { | |||||
| // Memory | // Memory | ||||
| uint8_t* chr_mem; | uint8_t* chr_mem; | ||||
| uint8_t oam[nes_ppu_oam_size]; | uint8_t oam[nes_ppu_oam_size]; | ||||
| uint8_t vram[nes_ppu_mem_vram_size]; | |||||
| uint8_t palette[nes_ppu_mem_pal_size]; | uint8_t palette[nes_ppu_mem_pal_size]; | ||||
| nes_vram_map* vram_map; | |||||
| // Timing | // Timing | ||||
| int frame; | int frame; | ||||
| @@ -108,7 +110,8 @@ typedef struct { | |||||
| uint8_t nes_ppu_read(nes_ppu* ppu, uint16_t addr); | 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_write(nes_ppu* ppu, uint16_t addr, uint8_t val); | ||||
| void nes_ppu_reset(nes_ppu* ppu); | 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 { | typedef enum { | ||||
| ppu_Result_Halt = -1, | ppu_Result_Halt = -1, | ||||
| @@ -119,6 +122,6 @@ typedef enum { | |||||
| } nes_ppu_Result; | } nes_ppu_Result; | ||||
| nes_ppu_Result nes_ppu_run(nes_ppu* ppu, int cycles); | nes_ppu_Result nes_ppu_run(nes_ppu* ppu, int cycles); | ||||
| //int nes_ppu_cycles_til_vblank(nes_ppu* ppu); | |||||
| #endif // ENES_PPU_H_ | #endif // ENES_PPU_H_ | ||||
| @@ -197,7 +197,8 @@ static void render_background_area(const nes_ppu* ppu, int page, | |||||
| void* buffer, int pitch, | void* buffer, int pitch, | ||||
| int xs, int ys, int w, int h) { | int xs, int ys, int w, int h) { | ||||
| int bank = (ppu->control & ppu_Control_Back_Bank) ? 0x100 : 0; | 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; | const uint8_t* attrs = index_line + 960U; | ||||
| index_line += xs + (ys * nes_ppu_blocks_w); | index_line += xs + (ys * nes_ppu_blocks_w); | ||||
| uint8_t* dst_line = (uint8_t*)buffer; | 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_ | |||||