summaryrefslogtreecommitdiff
path: root/app/src/app.c
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/app.c')
-rw-r--r--app/src/app.c205
1 files changed, 205 insertions, 0 deletions
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 @@
1#include <gfx/app.h>
2
3#include <GLFW/glfw3.h>
4#include <log/log.h>
5#include <timer.h>
6
7#include <assert.h>
8#include <stdlib.h>
9
10/// Application state.
11typedef struct GfxApp {
12 GfxAppState* app_state;
13 GfxAppCallbacks callbacks;
14 int max_fps;
15 double update_delta_time;
16 GLFWwindow* window;
17} GfxApp;
18
19/// Storing the application state in a global variable so that we can call the
20/// application's callbacks from GLFW callbacks.
21static GfxApp g_gfx_app;
22
23/// Called by GLFW when the window is resized.
24static void on_resize(GLFWwindow* window, int width, int height) {
25 (*g_gfx_app.callbacks.resize)(g_gfx_app.app_state, width, height);
26}
27
28/// Run the application's main loop.
29static void loop(GfxApp* app) {
30 assert(app);
31 assert(app->window);
32
33 const time_delta min_frame_time =
34 app->max_fps > 0 ? sec_to_time_delta(1.0 / (double)(app->max_fps)) : 0;
35 const time_delta update_dt = sec_to_time_delta(app->update_delta_time);
36 time_delta time = 0;
37 time_delta time_budget = 0;
38 Timer timer = timer_make();
39
40 // Warm up the update to initialize the application's state.
41 (*app->callbacks.update)(
42 app->app_state, time_delta_to_sec(time), time_delta_to_sec(update_dt));
43
44 // Warm up the rendering before entering the main loop. A renderer can
45 // compile shaders and do other initialization the first time it renders a
46 // scene.
47 (*app->callbacks.render)(app->app_state);
48 glfwSwapBuffers(app->window);
49
50 timer_start(&timer);
51 while (!glfwWindowShouldClose(app->window)) {
52 timer_tick(&timer);
53 time_budget += timer.delta_time;
54
55 while (time_budget >= update_dt) {
56 (*app->callbacks.update)(
57 app->app_state, time_delta_to_sec(time),
58 time_delta_to_sec(update_dt));
59
60 time += update_dt;
61 time_budget -= update_dt;
62 }
63
64 (*app->callbacks.render)(app->app_state);
65 glfwSwapBuffers(app->window);
66 glfwPollEvents();
67
68 const time_point frame_end = time_now();
69 const time_delta frame_time = time_diff(timer.last_tick, frame_end);
70 if ((min_frame_time > 0) && (frame_time < min_frame_time)) {
71 time_sleep(min_frame_time - frame_time);
72 }
73 }
74}
75
76bool gfx_app_run(const GfxAppDesc* desc, const GfxAppCallbacks* callbacks) {
77 assert(desc);
78 assert(callbacks);
79
80 bool success = false;
81
82 g_gfx_app.app_state = desc->app_state;
83 g_gfx_app.callbacks = *callbacks;
84 g_gfx_app.max_fps = desc->max_fps;
85 g_gfx_app.update_delta_time = desc->update_delta_time;
86 g_gfx_app.window = 0;
87
88 if (!glfwInit()) {
89 LOGE("glfwInit() failed");
90 return false;
91 }
92
93 const int major = 4;
94 const int minor = 4;
95 glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API);
96 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, major);
97 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, minor);
98 glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
99 glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
100 // TODO: Test antialiasing later on.
101 // glfwWindowHint(GLFW_SAMPLES, 4);
102
103 const char* title = desc->title ? desc->title : "Gfx Application";
104
105 g_gfx_app.window =
106 glfwCreateWindow(desc->width, desc->height, title, NULL, NULL);
107 if (!g_gfx_app.window) {
108 LOGE("glfwCreateWindow() failed");
109 goto cleanup;
110 }
111 glfwMakeContextCurrent(g_gfx_app.window);
112
113 // Initialize the application's state before setting any callbacks.
114 if (!(*g_gfx_app.callbacks.init)(
115 g_gfx_app.app_state, desc->argc, desc->argv)) {
116 LOGE("Failed to initialize application");
117 goto cleanup;
118 }
119
120 // Trigger an initial resize for convenience.
121 (*g_gfx_app.callbacks.resize)(g_gfx_app.app_state, desc->width, desc->height);
122
123 // Set GLFW callbacks now that the application has been initialized.
124 glfwSetWindowSizeCallback(g_gfx_app.window, on_resize);
125
126 loop(&g_gfx_app);
127
128 (*g_gfx_app.callbacks.shutdown)(g_gfx_app.app_state);
129
130 success = true;
131
132cleanup:
133 if (g_gfx_app.window) {
134 glfwDestroyWindow(g_gfx_app.window);
135 }
136 glfwTerminate();
137 return success;
138}
139
140void gfx_app_get_mouse_position(double* x, double* y) {
141 glfwGetCursorPos(g_gfx_app.window, x, y);
142}
143
144static int to_glfw_key(Key key);
145
146bool gfx_is_key_pressed(Key key) {
147 return glfwGetKey(g_gfx_app.window, to_glfw_key(key)) == GLFW_PRESS;
148}
149
150static int to_glfw_key(Key key) {
151 switch (key) {
152 case KeyA:
153 return GLFW_KEY_A;
154 case KeyB:
155 return GLFW_KEY_B;
156 case KeyC:
157 return GLFW_KEY_C;
158 case KeyD:
159 return GLFW_KEY_D;
160 case KeyE:
161 return GLFW_KEY_E;
162 case KeyF:
163 return GLFW_KEY_F;
164 case KeyG:
165 return GLFW_KEY_G;
166 case KeyH:
167 return GLFW_KEY_H;
168 case KeyI:
169 return GLFW_KEY_I;
170 case KeyJ:
171 return GLFW_KEY_J;
172 case KeyK:
173 return GLFW_KEY_K;
174 case KeyL:
175 return GLFW_KEY_L;
176 case KeyM:
177 return GLFW_KEY_M;
178 case KeyN:
179 return GLFW_KEY_N;
180 case KeyO:
181 return GLFW_KEY_O;
182 case KeyP:
183 return GLFW_KEY_P;
184 case KeyQ:
185 return GLFW_KEY_Q;
186 case KeyR:
187 return GLFW_KEY_R;
188 case KeyS:
189 return GLFW_KEY_S;
190 case KeyT:
191 return GLFW_KEY_T;
192 case KeyU:
193 return GLFW_KEY_U;
194 case KeyV:
195 return GLFW_KEY_V;
196 case KeyW:
197 return GLFW_KEY_W;
198 case KeyX:
199 return GLFW_KEY_X;
200 case KeyY:
201 return GLFW_KEY_Y;
202 case KeyZ:
203 return GLFW_KEY_Z;
204 }
205}