#define GL_SILENCE_DEPRECATION #define GLFW_INCLUDE_NONE #include #include #include #include #include #include #include #define BUFFER_WIDTH 256 #define BUFFER_HEIGHT 240 #define WINDOW_WIDTH 320 * 3 #define WINDOW_HEIGHT 240 * 3 #define LIKELY(x) __builtin_expect(!!(x), 1) #define UNLIKELY(x) __builtin_expect(!!(x), 0) #ifndef restrict # if defined(__cplusplus) # define restrict __restrict # endif #endif struct main_state { int32_t filter_override; float filter_frequency; uint32_t screen_width; uint32_t screen_height; uint32_t texture; uint32_t render_width; uint32_t render_height; uint32_t shader_program; float contrast; float saturation; float brightness; float tone_data[4]; int32_t uniform_resolution; int32_t uniform_src_image_size; int32_t uniform_brightness; int32_t uniform_tone; int32_t uniform_crt_emulation; int32_t uniform_sampler_location; uint32_t vao; uint32_t vbo; uint32_t ebo; struct { int32_t x; int32_t y; int32_t w; int32_t h; } viewport; uint8_t fullscreen; uint8_t toggle_crt_emulation; }; struct main_state state; uint32_t buffer[BUFFER_WIDTH * BUFFER_HEIGHT] __attribute__((section(".bss"), aligned(4096))); uint32_t display_buffer[BUFFER_WIDTH * BUFFER_HEIGHT] __attribute__((section(".bss"), aligned(4096))); void audio_callback(int16_t *data, size_t frames) { } #define FRAME_INTERVAL_NS (1000000000ULL / 60.0988) static GLFWwindow *window; #define DEBUG_PRINT printf #include "timer.c" #include "audio.c" #include "opengl_loader.c" #include "opengl.c" #include "render.c" #include "callbacks.c" // NES core #include "mapper.h" #include "mknes.h" // #include "apu.c" #include "ppu.c" #include "memory.c" #include "cpu.c" #include "ines2.c" #include "mapper.c" struct nes_state nstate; static uint32_t frames; int main(int argc, char **argv) { #ifdef _WIN32 timeBeginPeriod(1); #endif state.toggle_crt_emulation = 1; setbuf(stdout, 0); init_opcode_lut(); init_opcode_ud_lut(); // protect_opcode_lut(); ppu_reset(&nstate); // ines2_load(&nstate, "data/nrom/10-Yard Fight (USA, Europe).nes"); // ines2_load(&nstate, "data/nrom/Balloon Fight (USA).nes"); // ines2_load(&nstate, "data/nrom/Excitebike (Japan, USA).nes"); // ines2_load(&nstate, "data/nrom/Ice Climber (USA, Europe, Korea).nes"); // ines2_load(&nstate, "data/nrom/Kung Fu (Japan, USA).nes"); // ines2_load(&nstate, "data/nrom/Super Mario Bros. (World) (HVC-SM).nes"); // ines2_load(&nstate, "data/nrom/Urban Champion (World).nes"); // ines2_load(&nstate, "data/nrom/Wrecking Crew (World).nes"); // ines2_load(&nstate, "data/nrom/scanline.nes"); // ines2_load(&nstate, "data/nrom/Sayoonara!.NES"); // ines2_load(&nstate, "data/nrom/raster_demos/RasterChromaLuma.NES"); // ines2_load(&nstate, "data/nrom/raster_demos/RasterTest1.NES"); // ines2_load(&nstate, "data/nrom/raster_demos/RasterTest2.NES"); // ines2_load(&nstate, "data/nrom/raster_demos/RasterTest3.NES"); // ines2_load(&nstate, "data/nrom/raster_demos/RasterTest3a.NES"); // ines2_load(&nstate, "data/nrom/raster_demos/RasterTest3b.NES"); // ines2_load(&nstate, "data/nrom/raster_demos/RasterTest3c.NES"); // ines2_load(&nstate, "data/nrom/raster_demos/RasterTest3d.NES"); // ines2_load(&nstate, "data/nrom/raster_demos/RasterTest3e.NES"); // ines2_load(&nstate, "data/nrom/NEStress.NES"); // ines2_load(&nstate, "data/tv.nes"); ines2_load(&nstate, "data/Super Mario Bros. (World) (HVC-SM).zip"); // ines2_load(&nstate, "data/Super Mario Bros. + Duck Hunt (USA).zip"); mapper_setup(&nstate); uint32_t lo = nstate.mapper.prg_read(&nstate, 0xfffc); uint32_t hi = nstate.mapper.prg_read(&nstate, 0xfffd); nstate.cpu.pc = (hi << 8) | lo; struct timer_handle *timer = timer_new(FRAME_INTERVAL_NS); if(!timer) { fprintf(stderr, "Failed to create timer\n"); return 1; } if(glfwInit()) { glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_COMPAT_PROFILE); window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "NES Emulator", 0, 0); if(window) { glfwSetWindowAspectRatio(window, 320, 240); glfwSetWindowSizeLimits(window, 320*3, 240*3, GLFW_DONT_CARE, GLFW_DONT_CARE); glfwMakeContextCurrent(window); opengl_setup(); glfwSetKeyCallback(window, key_callback); glfwSetFramebufferSizeCallback(window, framebuffer_callback); glfwSwapInterval(0); framebuffer_callback(window, WINDOW_WIDTH, WINDOW_HEIGHT); for(int jid = GLFW_JOYSTICK_1; jid <= GLFW_JOYSTICK_LAST; jid++) { if(glfwJoystickPresent(jid)) { const char *name = glfwGetJoystickName(jid); printf("Joystick %d detected: %s\n", jid, name); break; } } set_decay(20); timer_start(timer); while(!glfwWindowShouldClose(window)) { timer_wait(timer); glfwPollEvents(); // // while(!nstate.ppu.frame_ready) { // PROFILE_NAMED("nes emulator"); cpu_tick(&nstate); } nstate.ppu.frame_ready = 0; frames++; uint32_t * restrict dst = buffer; uint8_t * restrict src = nstate.ppu.pixels; for(uint32_t y = 0; y < 240; ++y) { for(uint32_t x = 0; x < 256; ++x) { uint8_t val = *src++; if(val >= 64) val = 0; dst[x] = nes_palette[val]; } dst += BUFFER_WIDTH; } apply_phosphor_decay(); render_frame(); glfwSwapBuffers(window); } printf("total frames: %6.6d total cycles: %ld\n", frames, nstate.cycles); glfwDestroyWindow(window); } else { fprintf(stderr, "Failed to create window\n"); } glfwTerminate(); } else { fprintf(stderr, "Failed to initialize GLFW\n"); } timer_destroy(timer); #ifdef _WIN32 timeEndPeriod(1); #endif return 0; }