From f9e38bf24e1874d773083d06ca443cce9bd92d56 Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Sat, 9 Mar 2024 08:55:46 -0800 Subject: Rename gfx_app -> app. --- app/src/app.c | 205 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 app/src/app.c (limited to 'app/src/app.c') diff --git a/app/src/app.c b/app/src/app.c new file mode 100644 index 0000000..b6d10ca --- /dev/null +++ b/app/src/app.c @@ -0,0 +1,205 @@ +#include + +#include +#include +#include + +#include +#include + +/// Application state. +typedef struct GfxApp { + GfxAppState* 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.app_state = desc->app_state; + 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)( + 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.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); +} + +static int to_glfw_key(Key key); + +bool gfx_is_key_pressed(Key key) { + return glfwGetKey(g_gfx_app.window, to_glfw_key(key)) == GLFW_PRESS; +} + +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; + } +} -- cgit v1.2.3