summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/CMakeLists.txt14
-rw-r--r--app/README.md3
-rw-r--r--app/include/gfx/app.h95
-rw-r--r--app/src/app.c205
4 files changed, 317 insertions, 0 deletions
diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt
new file mode 100644
index 0000000..7e60351
--- /dev/null
+++ b/app/CMakeLists.txt
@@ -0,0 +1,14 @@
1cmake_minimum_required(VERSION 3.0)
2
3project(gfx-app)
4
5add_library(gfx-app
6 src/app.c)
7
8target_include_directories(gfx-app PUBLIC
9 include/)
10
11target_link_libraries(gfx-app PUBLIC
12 glfw
13 log
14 timer)
diff --git a/app/README.md b/app/README.md
new file mode 100644
index 0000000..b21d1aa
--- /dev/null
+++ b/app/README.md
@@ -0,0 +1,3 @@
1# Gfx App
2
3A small library to more conveniently create a window and run a game loop.
diff --git a/app/include/gfx/app.h b/app/include/gfx/app.h
new file mode 100644
index 0000000..ffff4bc
--- /dev/null
+++ b/app/include/gfx/app.h
@@ -0,0 +1,95 @@
1#pragma once
2
3#include <stdbool.h>
4
5typedef struct GfxAppState GfxAppState;
6
7/// Application settings.
8typedef struct GfxAppDesc {
9 int argc; // Number of application arguments.
10 const char** argv; // Application arguments.
11 int width; // Window width.
12 int height; // Window height.
13 int max_fps; // Desired maximum display framerate. 0 to disable.
14 double update_delta_time; // Desired delta time between frame updates.
15 const char* title; // Window title.
16 GfxAppState* app_state;
17} GfxAppDesc;
18
19typedef bool (*GfxAppInit)(GfxAppState*, int argc, const char** argv);
20typedef void (*GfxAppShutdown)(GfxAppState*);
21typedef void (*GfxAppUpdate)(GfxAppState*, double t, double dt);
22typedef void (*GfxAppRender)(GfxAppState*);
23typedef void (*GfxAppResize)(GfxAppState*, int width, int height);
24
25/// Application callback functions.
26typedef struct GfxAppCallbacks {
27 GfxAppInit init;
28 GfxAppShutdown shutdown;
29 GfxAppUpdate update;
30 GfxAppRender render;
31 GfxAppResize resize;
32} GfxAppCallbacks;
33
34typedef enum Key {
35 KeyA = 'a',
36 KeyB,
37 KeyC,
38 KeyD,
39 KeyE,
40 KeyF,
41 KeyG,
42 KeyH,
43 KeyI,
44 KeyJ,
45 KeyK,
46 KeyL,
47 KeyM,
48 KeyN,
49 KeyO,
50 KeyP,
51 KeyQ,
52 KeyR,
53 KeyS,
54 KeyT,
55 KeyU,
56 KeyV,
57 KeyW,
58 KeyX,
59 KeyY,
60 KeyZ,
61} Key;
62
63/// Create a window with an OpenGL context and run the main loop.
64bool gfx_app_run(const GfxAppDesc*, const GfxAppCallbacks*);
65
66/// Get the mouse coordinates relative to the app's window.
67void gfx_app_get_mouse_position(double* x, double* y);
68
69/// Return true if the given key is pressed.
70bool gfx_is_key_pressed(Key);
71
72/// Define a main function that initializes and puts the application in a loop.
73/// See also: gfx_app_run().
74#define GFX_APP_MAIN(WIDTH, HEIGHT, MAX_FPS, TITLE) \
75 int main(int argc, const char** argv) { \
76 GfxAppState app_state = {0}; \
77 gfx_app_run( \
78 &(GfxAppDesc){ \
79 .argc = argc, \
80 .argv = argv, \
81 .width = WIDTH, \
82 .height = HEIGHT, \
83 .max_fps = MAX_FPS, \
84 .update_delta_time = MAX_FPS > 0 ? 1.0 / (double)MAX_FPS : 0.0, \
85 .title = TITLE, \
86 .app_state = &app_state, \
87 }, \
88 &(GfxAppCallbacks){ \
89 .init = (GfxAppInit)app_init, \
90 .update = (GfxAppUpdate)app_update, \
91 .render = (GfxAppRender)app_render, \
92 .resize = (GfxAppResize)app_resize, \
93 .shutdown = (GfxAppShutdown)app_end}); \
94 return 0; \
95 }
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}