1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
|
#include <gfx/gfx_app.h>
#include <GLFW/glfw3.h>
#include <log/log.h>
#include <timer.h>
#include <assert.h>
#include <stdlib.h>
/// Application state.
typedef struct GfxApp {
void* 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 double min_frame_time =
app->max_fps > 0 ? 1.0 / (double)(app->max_fps) : 0.0;
const double update_dt = app->update_delta_time;
double time = 0.0;
double time_budget = 0.0;
Timer timer = timer_make();
// 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 += time_delta_to_sec(timer.delta_time);
while (time_budget >= update_dt) {
(*app->callbacks.update)(app->app_state, time, 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.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.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);
g_gfx_app.window =
glfwCreateWindow(desc->width, desc->height, "space", 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)(desc, &g_gfx_app.app_state)) {
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;
}
|