#include #include #include #include #include #include #include #include /// Application state. typedef struct GfxApp { GfxAppState* app_state; GfxAppCallbacks callbacks; GLFWwindow* window; Simloop simloop; } 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, g_gfx_app.app_state, width, height); } /// Run the application's main loop. static void loop(GfxApp* app) { assert(app); assert(app->window); time_delta time = 0; Timer timer = timer_make(); SimloopOut simout = {}; // 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->app_state); glfwSwapBuffers(app->window); timer_start(&timer); while (!glfwWindowShouldClose(app->window)) { timer_tick(&timer); simloop_update(&app->simloop, timer.delta_time, &simout); glfwPollEvents(); if (simout.should_update) { (*app->callbacks.update)( app, app->app_state, time_delta_to_sec(time), time_delta_to_sec(simout.update_dt)); } (*app->callbacks.render)(app, app->app_state); glfwSwapBuffers(app->window); if (simout.throttle > 0) { time_sleep(simout.throttle); } } } bool gfx_app_run(const GfxAppDesc* desc, const GfxAppCallbacks* callbacks) { assert(desc); assert(callbacks); bool success = false; g_gfx_app.app_state = desc->app_state; g_gfx_app.callbacks = *callbacks; g_gfx_app.window = nullptr; 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; } int width, height; glfwGetFramebufferSize(g_gfx_app.window, &width, &height); glfwMakeContextCurrent(g_gfx_app.window); // Request adaptive sync if supported. glfwSwapInterval(-1); // Load GL before calling the application init callback. // Use the GLFW loader. See: https://github.com/apitrace/apitrace/issues/954 if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { LOGE("Failed loading glad"); goto cleanup; } // Initialize the application's state before setting any callbacks. if (!(*g_gfx_app.callbacks.init)( &g_gfx_app, g_gfx_app.app_state, desc->argc, desc->argv)) { LOGE("Failed to initialize application"); goto cleanup; } // Trigger an initial resize for convenience. (*g_gfx_app.callbacks.resize)(&g_gfx_app, g_gfx_app.app_state, width, height); // Set GLFW callbacks now that the application has been initialized. glfwSetWindowSizeCallback(g_gfx_app.window, on_resize); g_gfx_app.simloop = simloop_make(&(SimloopArgs){ .update_fps = desc->update_fps, .max_render_fps = desc->max_render_fps}); loop(&g_gfx_app); (*g_gfx_app.callbacks.shutdown)(&g_gfx_app, 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(GfxApp* app, double* x, double* y) { glfwGetCursorPos(app->window, x, y); } static int to_glfw_mouse_button(MouseButton button); bool gfx_app_is_mouse_button_pressed(GfxApp* app, MouseButton button) { return glfwGetMouseButton(app->window, to_glfw_mouse_button(button)) == GLFW_PRESS; } static int to_glfw_key(Key key); bool gfx_app_is_key_pressed(GfxApp* app, Key key) { return glfwGetKey(app->window, to_glfw_key(key)) == GLFW_PRESS; } static int to_glfw_mouse_button(MouseButton button) { switch (button) { case LMB: return GLFW_MOUSE_BUTTON_LEFT; case RMB: return GLFW_MOUSE_BUTTON_RIGHT; case MMB: return GLFW_MOUSE_BUTTON_MIDDLE; } } static int to_glfw_key(Key key) { switch (key) { case KeyA: return GLFW_KEY_A; case KeyB: return GLFW_KEY_B; case KeyC: return GLFW_KEY_C; case KeyD: return GLFW_KEY_D; case KeyE: return GLFW_KEY_E; case KeyF: return GLFW_KEY_F; case KeyG: return GLFW_KEY_G; case KeyH: return GLFW_KEY_H; case KeyI: return GLFW_KEY_I; case KeyJ: return GLFW_KEY_J; case KeyK: return GLFW_KEY_K; case KeyL: return GLFW_KEY_L; case KeyM: return GLFW_KEY_M; case KeyN: return GLFW_KEY_N; case KeyO: return GLFW_KEY_O; case KeyP: return GLFW_KEY_P; case KeyQ: return GLFW_KEY_Q; case KeyR: return GLFW_KEY_R; case KeyS: return GLFW_KEY_S; case KeyT: return GLFW_KEY_T; case KeyU: return GLFW_KEY_U; case KeyV: return GLFW_KEY_V; case KeyW: return GLFW_KEY_W; case KeyX: return GLFW_KEY_X; case KeyY: return GLFW_KEY_Y; case KeyZ: return GLFW_KEY_Z; case KeyLShift: return GLFW_KEY_LEFT_SHIFT; } }