#include #include #include #include #include #include #include #define STB_IMAGE_IMPLEMENTATION #include #if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) /* include winapi */ #include #elif defined(__APPLE__) /* mac includes */ #elif defined(__linux) || defined(__linux__) || defined(linux) #include #include #endif #include #include #include #include #include #include #include #include #include #include #define DEFAULT_NUM_PROCS 8 Instance* GLOBAL_PLATFORM = NULL; input_callback_t* callbacks[128]; usize callbacks_len; i32 nproc(void) { return get_nprocs(); } void delay( uint64_t us ) { #if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) Sleep( ms ); #else struct timespec ts = { .tv_sec = (i64)us / 1000000, .tv_nsec = ((i64)us * 1000) % 1000000000 }; struct timespec rem = {0,0}; int err = 0; while((err = clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, &rem))) { switch (err) { case EINTR: WARN("clock_nanosleep didn't sleep full duration: interrupted."); break; case EINVAL: WARN("clock_nanosleep didn't sleep full duration: invalid argument."); break; case ENOTSUP: WARN("clock_nanosleep didn't sleep full duration: unsupported clock."); break; default: WARN("clock_nanosleep didn't sleep full duration!"); break; } ts = rem; rem = (struct timespec){0,0}; } #endif } i32 cmp_int(const void* a, const void* b) { const i32* x = a; const i32* y = b; return *x - *y; } /* Creates the window, initializes IO, Rendering, Fonts and engine-specific * resources. */ Instance* engine_init(const char* windowtitle, i32 windowWidth, i32 windowHeight, const u32 flags, const usize initial_memory) { INFO("Engine version " ENGINE_VERSION); #if defined(__linux) || defined(__linux__) || defined(linux) { pid_t pid = getpid(); INFO("Starting with pid %lu", pid); } #endif ivec2 windowsize = {windowWidth, windowHeight}; Instance* p = (Instance*)calloc(1, sizeof(Instance)); Window* w = (Window*)calloc(1, sizeof(Window)); /* initialize resources */ Resources* resources = calloc(1, sizeof(Resources)); // Until we've gotten rid of this global, it is important to set it before // `window_new`. GLOBAL_PLATFORM = p; w = Window_new(&Platform_GLFW, windowtitle, WINDOW_FRAMEWORK_GLFW, WINDOW_RENDERER_OPENGL, (ivec2){windowsize[0], windowsize[1]}, flags); p->window = w; p->quit = false; p->resources = resources; p->frame = 0; #ifdef _DEBUG engine_fps_max(p, 30); #else engine_fps_max(p, 300); #endif p->mem = memory_new(initial_memory); INFO("Available cores: %d", nproc()); #ifdef DAW_BUILD_HOTRELOAD #define State(name) \ if (!State_reload(STATE_##name, p->window->bindings, p->window->bindings_len)) { \ ERROR("Failed to reload shared object file for state %s", #name); \ }; #include #undef State #endif return p; } i32 engine_run(Instance* p, StateType initial_state, void* state_arg) { Shader default_quad_shader = init_default_shader(); RenderObject default_quad_renderobject; if (p == NULL) { ERROR("Platform is uninitialized.\n"); INFO("initialize with `engine_init`"); return -1; } memory* mem = p->mem; StateType state = initial_state; Window* w = p->window; { u64 state_init_time = get_time(); State_init(state, p->window, mem, state_arg); r_reset_pipeline(w); // TODO: Both the shader & renderobject should only be created here if it is // overwritten in Instance->Window. // TODO: Add default quad & renderobject to Instance->Window. Should allow // for customization. default_quad_renderobject = init_default_renderobject(w, default_quad_shader); INFO("Initializing state \"%s\" took %.1fns", StateTypeStr[state], (get_time() - state_init_time)); } u64 frame_end = get_time(); // Update ticks u64 ticks = 0; StateType (*update_func)(Window *restrict, void*, f64) = State_updateFunc(state); u64 last_fps_measurement = frame_end; u64 last_fps_ticks = 0; /* The target frametime measured in μs */ const u32 fps_cap = p->fps_target > 0 ? 1000000 / p->fps_target : 0; u64 measurement_total[10]; u64 measurement_rendering[10]; u64 measurement_idle[10]; memset(measurement_total, 0, sizeof(u64) * 10); memset(measurement_rendering, 0, sizeof(u64) * 10); memset(measurement_idle, 0, sizeof(u64) * 10); /* Main loop */ do { /* frame_start is μs since engine was initialized */ const u64 frame_start = get_time(); /* dt measured in μs */ const u64 dt = frame_start - frame_end; measurement_total[ticks%10] = dt; /* Delta is relative to FPS cap */ //const f64 delta = (f64)dt / (f64)fps_cap; /* Events */ Platform_GLFW.window_poll(); i_flush_bindings(dt, callbacks_len, callbacks, mem->data); /* Update */ StateType next_state; next_state = update_func(p->window, (void*)(mem->data), dt); if (next_state != STATE_null) { if (next_state == STATE_quit) break; window_reset_drawing(); void* retval = State_free(state, mem); memory_clear(mem); i_ctx_reset(); // Reset camera to default camera free(w->render_targets); state = next_state; update_func = State_updateFunc(state); { u64 state_init_time = get_time(); State_init(state, p->window, mem, retval); r_reset_pipeline(w); default_quad_renderobject = init_default_renderobject(w, default_quad_shader); INFO("Initializing state \"%s\" took %.1fns", StateTypeStr[state], (get_time() - state_init_time)); } } else { /* Render */ const u64 rendertime_begin = get_time(); render_begin(p->window); render_present(p->window, &default_quad_renderobject); measurement_rendering[ticks%10] = get_time() - rendertime_begin; /* Regulate FPS */ frame_end = get_time(); const i64 fps_diff = fps_cap - (i64)(frame_end - frame_start); if (fps_diff > 0) { const u64 idletime_begin = get_time(); delay((u64)fps_diff); measurement_idle[ticks%10] = get_time() - idletime_begin; } frame_end = frame_start; /* Print stats */ const u64 dt_measurement = frame_end - last_fps_measurement; /* only make measurements once a second */ if (dt_measurement > 1000000) { double avg_total = 0; double avg_rendering = 0; double avg_idle = 0; for (int i = 0; i < 10; i++) { avg_total += measurement_total[i]; avg_rendering += measurement_rendering[i]; avg_idle += measurement_idle[i]; } avg_total /= 10.0; avg_rendering /= 10.0; avg_idle /= 10.0; printf(" FPS: %.1f" "\tticks: %lu" "\tdt: %.3fms" "\trendertime: %.3fms" "\tidle: %.3fms" "\n" , ((f64)(ticks - last_fps_ticks)) / ((f64)dt_measurement / 1000000.0) , ticks , (double)avg_total / 1000.0 , (double)avg_rendering / 1000.0 , (double)avg_idle / 1000.0 ); last_fps_measurement = frame_end; last_fps_ticks = ticks; } } ticks++; } while( !Platform_GLFW.window_should_close(p->window) && state != STATE_quit ); return 0; } void engine_stop(Instance* p) { if (p == NULL) return; //{ /* Deallocate resources */ // struct Resources* r = (struct Resources*)p->data; // if (r != NULL) { // /* Destroy textures */ // for (usize i = 0; i < r->textures_len; i++) { // if (r->textures[i] != NULL) { // r->textures[i] = NULL; // } // } // free(r->textures); // /* Destroy Fonts */ // } //} Platform_GLFW.window_destroy(p->window); free(p->resources); } /* Set the maximum framerate */ void engine_fps_max(Instance* p, u16 cap) { LOG("Setting max fps to %llu", cap); p->fps_target = cap; } usize f_get_sz(FILE* f) { if (f == NULL) { ERROR("File was null!"); return 0; } const isize pos = ftell(f); if (pos == -1) { ERROR("Failed to determine file size (%d): %s", errno, strerror(errno)); return 0; } const i32 err = fseek(f, 0, SEEK_END); if (err != 0) { if (err == ESPIPE) { ERROR("File is a pipe"); } else { ERROR("Failed to determine file size!"); } return 0; } const isize size = ftell(f); if (size == -1) { ERROR("Failed to determine file size (%d): %s", errno, strerror(errno)); return 0; } // Reset the position to the position prior to calling f_get_sz fseek(f, pos, SEEK_SET); return (usize)size; }