summaryrefslogtreecommitdiff
path: root/gfx-app
diff options
context:
space:
mode:
Diffstat (limited to 'gfx-app')
-rw-r--r--gfx-app/CMakeLists.txt14
-rw-r--r--gfx-app/README.md3
-rw-r--r--gfx-app/include/gfx/gfx_app.h23
-rw-r--r--gfx-app/src/gfx_app.c126
4 files changed, 166 insertions, 0 deletions
diff --git a/gfx-app/CMakeLists.txt b/gfx-app/CMakeLists.txt
new file mode 100644
index 0000000..d9cb80f
--- /dev/null
+++ b/gfx-app/CMakeLists.txt
@@ -0,0 +1,14 @@
1cmake_minimum_required(VERSION 3.0)
2
3project(gfx-app)
4
5add_library(gfx-app
6 src/gfx_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/gfx-app/README.md b/gfx-app/README.md
new file mode 100644
index 0000000..b21d1aa
--- /dev/null
+++ b/gfx-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/gfx-app/include/gfx/gfx_app.h b/gfx-app/include/gfx/gfx_app.h
new file mode 100644
index 0000000..bdb3550
--- /dev/null
+++ b/gfx-app/include/gfx/gfx_app.h
@@ -0,0 +1,23 @@
1#pragma once
2
3#include <stdbool.h>
4
5typedef struct GfxAppDesc {
6 int argc; // Number of application arguments.
7 const char** argv; // Application arguments.
8 int width; // Window width.
9 int height; // Window height.
10 int max_fps; // Desired maximum framerate. 0 to disable.
11 double update_delta_time; // Desired delta time between frame updates.
12} GfxAppDesc;
13
14typedef struct GfxAppCallbacks {
15 bool (*init)(const GfxAppDesc*, void** app_state);
16 void (*update)(void* app_state, double t, double dt);
17 void (*render)(void* app_state);
18 void (*resize)(void* app_state, int width, int height);
19 void (*shutdown)(void* app_state);
20} GfxAppCallbacks;
21
22/// Create a window with an OpenGL context and run the main loop.
23bool gfx_app_run(const GfxAppDesc*, const GfxAppCallbacks*);
diff --git a/gfx-app/src/gfx_app.c b/gfx-app/src/gfx_app.c
new file mode 100644
index 0000000..f3f0473
--- /dev/null
+++ b/gfx-app/src/gfx_app.c
@@ -0,0 +1,126 @@
1#include <gfx/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 void* 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 double min_frame_time =
34 app->max_fps > 0 ? 1.0 / (double)(app->max_fps) : 0.0;
35 const double update_dt = app->update_delta_time;
36 double time = 0.0;
37 double time_budget = 0.0;
38 Timer timer = timer_make();
39
40 // Warm up the rendering before entering the main loop. A renderer can compile
41 // shaders and do other initialization the first time it renders a scene.
42 (*app->callbacks.render)(app->app_state);
43 glfwSwapBuffers(app->window);
44
45 timer_start(&timer);
46 while (!glfwWindowShouldClose(app->window)) {
47 timer_tick(&timer);
48 time_budget += time_delta_to_sec(timer.delta_time);
49
50 while (time_budget >= update_dt) {
51 (*app->callbacks.update)(app->app_state, time, update_dt);
52 time += update_dt;
53 time_budget -= update_dt;
54 }
55
56 (*app->callbacks.render)(app->app_state);
57 glfwSwapBuffers(app->window);
58 glfwPollEvents();
59
60 const time_point frame_end = time_now();
61 const time_delta frame_time = time_diff(timer.last_tick, frame_end);
62 if (min_frame_time > 0.0 && frame_time < min_frame_time) {
63 time_sleep(min_frame_time - frame_time);
64 }
65 }
66}
67
68bool gfx_app_run(const GfxAppDesc* desc, const GfxAppCallbacks* callbacks) {
69 assert(desc);
70 assert(callbacks);
71
72 bool success = false;
73
74 g_gfx_app.callbacks = *callbacks;
75 g_gfx_app.max_fps = desc->max_fps;
76 g_gfx_app.update_delta_time = desc->update_delta_time;
77 g_gfx_app.window = 0;
78
79 if (!glfwInit()) {
80 LOGE("glfwInit() failed");
81 return false;
82 }
83
84 const int major = 4;
85 const int minor = 4;
86 glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API);
87 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, major);
88 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, minor);
89 glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
90 glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
91 // TODO: Test antialiasing later on.
92 // glfwWindowHint(GLFW_SAMPLES, 4);
93
94 g_gfx_app.window =
95 glfwCreateWindow(desc->width, desc->height, "space", NULL, NULL);
96 if (!g_gfx_app.window) {
97 LOGE("glfwCreateWindow() failed");
98 goto cleanup;
99 }
100 glfwMakeContextCurrent(g_gfx_app.window);
101
102 // Initialize the application's state before setting any callbacks.
103 if (!(*g_gfx_app.callbacks.init)(desc, &g_gfx_app.app_state)) {
104 LOGE("Failed to initialize application");
105 goto cleanup;
106 }
107
108 // Trigger an initial resize for convenience.
109 (*g_gfx_app.callbacks.resize)(g_gfx_app.app_state, desc->width, desc->height);
110
111 // Set GLFW callbacks now that the application has been initialized.
112 glfwSetWindowSizeCallback(g_gfx_app.window, on_resize);
113
114 loop(&g_gfx_app);
115
116 (*g_gfx_app.callbacks.shutdown)(g_gfx_app.app_state);
117
118 success = true;
119
120cleanup:
121 if (g_gfx_app.window) {
122 glfwDestroyWindow(g_gfx_app.window);
123 }
124 glfwTerminate();
125 return success;
126}