diff options
| author | 3gg <3gg@shellblade.net> | 2026-05-02 14:59:18 -0700 |
|---|---|---|
| committer | 3gg <3gg@shellblade.net> | 2026-05-02 14:59:18 -0700 |
| commit | 456d7f883ca34ec83692ff3879ca5f5717e82b33 (patch) | |
| tree | 9bc29d8aa999d98d220419bbf87a02e9639afc64 | |
| parent | 5c7204d7b334855b18761ba95f61fea0aefddd52 (diff) | |
| -rw-r--r-- | app/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | app/demo/main.c | 11 | ||||
| -rw-r--r-- | app/include/gfx/app.h | 54 | ||||
| -rw-r--r-- | app/src/app.c | 46 |
4 files changed, 51 insertions, 61 deletions
diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 021a77d..b88219a 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt | |||
| @@ -12,6 +12,7 @@ target_link_libraries(gfx-app PUBLIC | |||
| 12 | glad | 12 | glad |
| 13 | glfw | 13 | glfw |
| 14 | log | 14 | log |
| 15 | simloop | ||
| 15 | timer) | 16 | timer) |
| 16 | 17 | ||
| 17 | add_subdirectory(demo) | 18 | add_subdirectory(demo) |
diff --git a/app/demo/main.c b/app/demo/main.c index 0aa4e34..2efca7c 100644 --- a/app/demo/main.c +++ b/app/demo/main.c | |||
| @@ -2,10 +2,11 @@ | |||
| 2 | 2 | ||
| 3 | #include <assert.h> | 3 | #include <assert.h> |
| 4 | 4 | ||
| 5 | const int WIDTH = 960; | 5 | const int WIDTH = 960; |
| 6 | const int HEIGHT = 600; | 6 | const int HEIGHT = 600; |
| 7 | const int MAX_FPS = 60; | 7 | const int UPDATE_FPS = 60; |
| 8 | const char* TITLE = "iso3d"; | 8 | const int MAX_FPS = 60; |
| 9 | const char* TITLE = "iso3d"; | ||
| 9 | 10 | ||
| 10 | typedef struct GfxAppState { | 11 | typedef struct GfxAppState { |
| 11 | int unused; | 12 | int unused; |
| @@ -47,4 +48,4 @@ void Resize(GfxApp* app, GfxAppState* state, int width, int height) { | |||
| 47 | (void)height; | 48 | (void)height; |
| 48 | } | 49 | } |
| 49 | 50 | ||
| 50 | GFX_APP_MAIN(WIDTH, HEIGHT, MAX_FPS, TITLE) | 51 | GFX_APP_MAIN(WIDTH, HEIGHT, UPDATE_FPS, MAX_FPS, TITLE) |
diff --git a/app/include/gfx/app.h b/app/include/gfx/app.h index 3017707..42fccec 100644 --- a/app/include/gfx/app.h +++ b/app/include/gfx/app.h | |||
| @@ -5,13 +5,13 @@ typedef struct GfxAppState GfxAppState; | |||
| 5 | 5 | ||
| 6 | /// Application settings. | 6 | /// Application settings. |
| 7 | typedef struct GfxAppDesc { | 7 | typedef struct GfxAppDesc { |
| 8 | int argc; // Number of application arguments. | 8 | int argc; // Number of application arguments. |
| 9 | const char** argv; // Application arguments. | 9 | const char** argv; // Application arguments. |
| 10 | int width; // Window width. | 10 | int width; // Window width. |
| 11 | int height; // Window height. | 11 | int height; // Window height. |
| 12 | int max_fps; // Desired maximum display framerate. 0 to disable. | 12 | int update_fps; // Desired frame update frequency. |
| 13 | double update_delta_time; // Desired delta time between frame updates. | 13 | int max_render_fps; // Desired maximum render frame rate. 0 to disable. |
| 14 | const char* title; // Window title. | 14 | const char* title; // Window title. |
| 15 | GfxAppState* app_state; | 15 | GfxAppState* app_state; |
| 16 | } GfxAppDesc; | 16 | } GfxAppDesc; |
| 17 | 17 | ||
| @@ -88,24 +88,24 @@ bool gfx_app_is_key_pressed(GfxApp*, Key); | |||
| 88 | 88 | ||
| 89 | /// Define a main function that initializes and puts the application in a loop. | 89 | /// Define a main function that initializes and puts the application in a loop. |
| 90 | /// See also: gfx_app_run(). | 90 | /// See also: gfx_app_run(). |
| 91 | #define GFX_APP_MAIN(WIDTH, HEIGHT, MAX_FPS, TITLE) \ | 91 | #define GFX_APP_MAIN(WIDTH, HEIGHT, UPDATE_FPS, MAX_RENDER_FPS, TITLE) \ |
| 92 | int main(int argc, const char** argv) { \ | 92 | int main(int argc, const char** argv) { \ |
| 93 | GfxAppState app_state = {0}; \ | 93 | GfxAppState app_state = {0}; \ |
| 94 | gfx_app_run( \ | 94 | gfx_app_run( \ |
| 95 | &(GfxAppDesc){ \ | 95 | &(GfxAppDesc){ \ |
| 96 | .argc = argc, \ | 96 | .argc = argc, \ |
| 97 | .argv = argv, \ | 97 | .argv = argv, \ |
| 98 | .width = WIDTH, \ | 98 | .width = WIDTH, \ |
| 99 | .height = HEIGHT, \ | 99 | .height = HEIGHT, \ |
| 100 | .max_fps = MAX_FPS, \ | 100 | .update_fps = UPDATE_FPS, \ |
| 101 | .update_delta_time = MAX_FPS > 0 ? 1.0 / (double)MAX_FPS : 0.0, \ | 101 | .max_render_fps = MAX_RENDER_FPS, \ |
| 102 | .title = TITLE, \ | 102 | .title = TITLE, \ |
| 103 | .app_state = &app_state, \ | 103 | .app_state = &app_state, \ |
| 104 | }, \ | 104 | }, \ |
| 105 | &(GfxAppCallbacks){.init = Init, \ | 105 | &(GfxAppCallbacks){.init = Init, \ |
| 106 | .shutdown = Shutdown, \ | 106 | .shutdown = Shutdown, \ |
| 107 | .update = Update, \ | 107 | .update = Update, \ |
| 108 | .render = Render, \ | 108 | .render = Render, \ |
| 109 | .resize = Resize}); \ | 109 | .resize = Resize}); \ |
| 110 | return 0; \ | 110 | return 0; \ |
| 111 | } | 111 | } |
diff --git a/app/src/app.c b/app/src/app.c index 8262378..c056ddd 100644 --- a/app/src/app.c +++ b/app/src/app.c | |||
| @@ -4,6 +4,7 @@ | |||
| 4 | 4 | ||
| 5 | #include <GLFW/glfw3.h> | 5 | #include <GLFW/glfw3.h> |
| 6 | #include <log/log.h> | 6 | #include <log/log.h> |
| 7 | #include <simloop.h> | ||
| 7 | #include <timer.h> | 8 | #include <timer.h> |
| 8 | 9 | ||
| 9 | #include <assert.h> | 10 | #include <assert.h> |
| @@ -13,9 +14,8 @@ | |||
| 13 | typedef struct GfxApp { | 14 | typedef struct GfxApp { |
| 14 | GfxAppState* app_state; | 15 | GfxAppState* app_state; |
| 15 | GfxAppCallbacks callbacks; | 16 | GfxAppCallbacks callbacks; |
| 16 | int max_fps; | ||
| 17 | double update_delta_time; | ||
| 18 | GLFWwindow* window; | 17 | GLFWwindow* window; |
| 18 | Simloop simloop; | ||
| 19 | } GfxApp; | 19 | } GfxApp; |
| 20 | 20 | ||
| 21 | /// Storing the application state in a global variable so that we can call the | 21 | /// Storing the application state in a global variable so that we can call the |
| @@ -32,17 +32,9 @@ static void loop(GfxApp* app) { | |||
| 32 | assert(app); | 32 | assert(app); |
| 33 | assert(app->window); | 33 | assert(app->window); |
| 34 | 34 | ||
| 35 | const time_delta min_frame_time = | 35 | time_delta time = 0; |
| 36 | app->max_fps > 0 ? time_delta_from_sec(1.0 / (double)(app->max_fps)) : 0; | 36 | Timer timer = timer_make(); |
| 37 | const time_delta update_dt = time_delta_from_sec(app->update_delta_time); | 37 | SimloopOut simout = {}; |
| 38 | time_delta time = 0; | ||
| 39 | time_delta time_budget = 0; | ||
| 40 | Timer timer = timer_make(); | ||
| 41 | |||
| 42 | // Warm up the update to initialize the application's state. | ||
| 43 | (*app->callbacks.update)( | ||
| 44 | app, app->app_state, time_delta_to_sec(time), | ||
| 45 | time_delta_to_sec(update_dt)); | ||
| 46 | 38 | ||
| 47 | // Warm up the rendering before entering the main loop. A renderer can | 39 | // Warm up the rendering before entering the main loop. A renderer can |
| 48 | // compile shaders and do other initialization the first time it renders a | 40 | // compile shaders and do other initialization the first time it renders a |
| @@ -53,25 +45,21 @@ static void loop(GfxApp* app) { | |||
| 53 | timer_start(&timer); | 45 | timer_start(&timer); |
| 54 | while (!glfwWindowShouldClose(app->window)) { | 46 | while (!glfwWindowShouldClose(app->window)) { |
| 55 | timer_tick(&timer); | 47 | timer_tick(&timer); |
| 56 | time_budget += timer.delta_time; | 48 | simloop_update(&app->simloop, timer.delta_time, &simout); |
| 49 | |||
| 50 | glfwPollEvents(); | ||
| 57 | 51 | ||
| 58 | while (time_budget >= update_dt) { | 52 | if (simout.should_update) { |
| 59 | (*app->callbacks.update)( | 53 | (*app->callbacks.update)( |
| 60 | app, app->app_state, time_delta_to_sec(time), | 54 | app, app->app_state, time_delta_to_sec(time), |
| 61 | time_delta_to_sec(update_dt)); | 55 | time_delta_to_sec(simout.update_dt)); |
| 62 | |||
| 63 | time += update_dt; | ||
| 64 | time_budget -= update_dt; | ||
| 65 | } | 56 | } |
| 66 | 57 | ||
| 67 | (*app->callbacks.render)(app, app->app_state); | 58 | (*app->callbacks.render)(app, app->app_state); |
| 68 | glfwSwapBuffers(app->window); | 59 | glfwSwapBuffers(app->window); |
| 69 | glfwPollEvents(); | ||
| 70 | 60 | ||
| 71 | const time_point frame_end = time_now(); | 61 | if (simout.throttle > 0) { |
| 72 | const time_delta frame_time = time_diff(timer.last_tick, frame_end); | 62 | time_sleep(simout.throttle); |
| 73 | if ((min_frame_time > 0) && (frame_time < min_frame_time)) { | ||
| 74 | time_sleep(min_frame_time - frame_time); | ||
| 75 | } | 63 | } |
| 76 | } | 64 | } |
| 77 | } | 65 | } |
| @@ -82,11 +70,9 @@ bool gfx_app_run(const GfxAppDesc* desc, const GfxAppCallbacks* callbacks) { | |||
| 82 | 70 | ||
| 83 | bool success = false; | 71 | bool success = false; |
| 84 | 72 | ||
| 85 | g_gfx_app.app_state = desc->app_state; | 73 | g_gfx_app.app_state = desc->app_state; |
| 86 | g_gfx_app.callbacks = *callbacks; | 74 | g_gfx_app.callbacks = *callbacks; |
| 87 | g_gfx_app.max_fps = desc->max_fps; | 75 | g_gfx_app.window = nullptr; |
| 88 | g_gfx_app.update_delta_time = desc->update_delta_time; | ||
| 89 | g_gfx_app.window = nullptr; | ||
| 90 | 76 | ||
| 91 | if (!glfwInit()) { | 77 | if (!glfwInit()) { |
| 92 | LOGE("glfwInit() failed"); | 78 | LOGE("glfwInit() failed"); |
| @@ -138,6 +124,8 @@ bool gfx_app_run(const GfxAppDesc* desc, const GfxAppCallbacks* callbacks) { | |||
| 138 | // Set GLFW callbacks now that the application has been initialized. | 124 | // Set GLFW callbacks now that the application has been initialized. |
| 139 | glfwSetWindowSizeCallback(g_gfx_app.window, on_resize); | 125 | glfwSetWindowSizeCallback(g_gfx_app.window, on_resize); |
| 140 | 126 | ||
| 127 | g_gfx_app.simloop = simloop_make(&(SimloopArgs){ | ||
| 128 | .update_fps = desc->update_fps, .max_render_fps = desc->max_render_fps}); | ||
| 141 | loop(&g_gfx_app); | 129 | loop(&g_gfx_app); |
| 142 | 130 | ||
| 143 | (*g_gfx_app.callbacks.shutdown)(&g_gfx_app, g_gfx_app.app_state); | 131 | (*g_gfx_app.callbacks.shutdown)(&g_gfx_app, g_gfx_app.app_state); |
