#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 double min_frame_time = app->max_fps > 0 ? 1.0 / (double)(app->max_fps) : 0.0; const double update_dt = app->update_delta_time; double time = 0.0; double time_budget = 0.0; Timer timer = timer_make(); // Warm up the update to initialize the application's state. (*app->callbacks.update)(app->app_state, time, 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 += time_delta_to_sec(timer.delta_time); while (time_budget >= update_dt) { (*app->callbacks.update)(app->app_state, time, 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.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); g_gfx_app.window = glfwCreateWindow( desc->width, desc->height, "Gfx Application", 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; }