| @@ -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 $@ | |||
| @@ -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; | |||
| @@ -4,6 +4,8 @@ | |||
| #include <time.h> | |||
| #include <sys/mman.h> | |||
| #include <SDL.h> | |||
| #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; | |||
| } | |||
| @@ -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); | |||
| } | |||
| } | |||
| @@ -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_ | |||
| @@ -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); | |||
| @@ -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_ | |||
| @@ -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_ | |||
| @@ -1,5 +1,10 @@ | |||
| #include <string.h> | |||
| #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); | |||
| } | |||
| } | |||
| @@ -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_ | |||