#include #include #include #include #include #include /// Application state. typedef struct GfxApp { void* app_state; GfxAppCallbacks callbacks; int max_fps; double update_delta_time; GLFWwindow* window; } GfxApp; /// Storing the application state in a global variable so that we can call the /// application's callbacks from GLFW callbacks. static GfxApp g_gfx_app; /// Called by GLFW when the window is resized. static void on_resize(GLFWwindow* window, int width, int height) { (*g_gfx_app.callbacks.resize)(g_gfx_app.app_state, width, height); } /// Run the application's main loop. static void loop(GfxApp* app) { assert(app); assert(app->window); const time_delta min_frame_time = app->max_fps > 0 ? sec_to_time_delta(1.0 / (double)(app->max_fps)) : 0; const time_delta update_dt = sec_to_time_delta(app->update_delta_time); time_delta time = 0; time_delta time_budget = 0; Timer timer = timer_make(); // Warm up the update to initialize the application's state. (*app->callbacks.update)( app->app_state, time_delta_to_sec(time), time_delta_to_sec(update_dt)); // Warm up the rendering before entering the main loop. A renderer can // compile shaders and do other initialization the first time it renders a // scene. (*app->callbacks.render)(app->app_state); glfwSwapBuffers(app->window); timer_start(&timer); while (!glfwWindowShouldClose(app->window)) { timer_tick(&timer); time_budget += timer.delta_time; while (time_budget >= update_dt) { (*app->callbacks.update)( app->app_state, time_delta_to_sec(time), time_delta_to_sec(update_dt)); time += update_dt; time_budget -= update_dt; } (*app->callbacks.render)(app->app_state); glfwSwapBuffers(app->window); glfwPollEvents(); const time_point frame_end = time_now(); const time_delta frame_time = time_diff(timer.last_tick, frame_end); if ((min_frame_time > 0) && (frame_time < min_frame_time)) { time_sleep(min_frame_time - frame_time); } } } bool gfx_app_run(const GfxAppDesc* desc, const GfxAppCallbacks* callbacks) { assert(desc); assert(callbacks); bool success = false; g_gfx_app.callbacks = *callbacks; g_gfx_app.max_fps = desc->max_fps; g_gfx_app.update_delta_time = desc->update_delta_time; g_gfx_app.window = 0; if (!glfwInit()) { LOGE("glfwInit() failed"); return false; } const int major = 4; const int minor = 4; glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, major); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, minor); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); // TODO: Test antialiasing later on. // glfwWindowHint(GLFW_SAMPLES, 4); const char* title = desc->title ? desc->title : "Gfx Application"; g_gfx_app.window = glfwCreateWindow(desc->width, desc->height, title, NULL, NULL); if (!g_gfx_app.window) { LOGE("glfwCreateWindow() failed"); goto cleanup; } glfwMakeContextCurrent(g_gfx_app.window); // Initialize the application's state before setting any callbacks. if (!(*g_gfx_app.callbacks.init)(desc, &g_gfx_app.app_state)) { LOGE("Failed to initialize application"); goto cleanup; } // Trigger an initial resize for convenience. (*g_gfx_app.callbacks.resize)(g_gfx_app.app_state, desc->width, desc->height); // Set GLFW callbacks now that the application has been initialized. glfwSetWindowSizeCallback(g_gfx_app.window, on_resize); loop(&g_gfx_app); (*g_gfx_app.callbacks.shutdown)(g_gfx_app.app_state); success = true; cleanup: if (g_gfx_app.window) { glfwDestroyWindow(g_gfx_app.window); } glfwTerminate(); return success; } void gfx_app_get_mouse_position(double* x, double* y) { glfwGetCursorPos(g_gfx_app.window, x, y); }