summaryrefslogtreecommitdiff
path: root/gfx-app/src/gfx_app.c
blob: a93756c8331b441fed671444ef520876ea5cf41c (plain)
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
#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 {
  GfxAppState*    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 time_delta min_frame_time =
      app->max_fps > 0 ? sec_to_time_delta(1.0 / (double)(app->max_fps)) : 0;
  const time_delta update_dt   = sec_to_time_delta(app->update_delta_time);
  time_delta       time        = 0;
  time_delta       time_budget = 0;
  Timer            timer       = timer_make();

  // Warm up the update to initialize the application's state.
  (*app->callbacks.update)(
      app->app_state, time_delta_to_sec(time), time_delta_to_sec(update_dt));

  // 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 += timer.delta_time;

    while (time_budget >= update_dt) {
      (*app->callbacks.update)(
          app->app_state, time_delta_to_sec(time),
          time_delta_to_sec(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) && (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.app_state         = desc->app_state;
  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);

  const char* title = desc->title ? desc->title : "Gfx Application";

  g_gfx_app.window =
      glfwCreateWindow(desc->width, desc->height, title, 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)(
          g_gfx_app.app_state, desc->argc, desc->argv)) {
    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;
}

void gfx_app_get_mouse_position(double* x, double* y) {
  glfwGetCursorPos(g_gfx_app.window, x, y);
}

static int to_glfw_key(Key key);

bool gfx_is_key_pressed(Key key) {
  return glfwGetKey(g_gfx_app.window, to_glfw_key(key)) == GLFW_PRESS;
}

static int to_glfw_key(Key key) {
  switch (key) {
  case KeyA:
    return GLFW_KEY_A;
  case KeyB:
    return GLFW_KEY_B;
  case KeyC:
    return GLFW_KEY_C;
  case KeyD:
    return GLFW_KEY_D;
  case KeyE:
    return GLFW_KEY_E;
  case KeyF:
    return GLFW_KEY_F;
  case KeyG:
    return GLFW_KEY_G;
  case KeyH:
    return GLFW_KEY_H;
  case KeyI:
    return GLFW_KEY_I;
  case KeyJ:
    return GLFW_KEY_J;
  case KeyK:
    return GLFW_KEY_K;
  case KeyL:
    return GLFW_KEY_L;
  case KeyM:
    return GLFW_KEY_M;
  case KeyN:
    return GLFW_KEY_N;
  case KeyO:
    return GLFW_KEY_O;
  case KeyP:
    return GLFW_KEY_P;
  case KeyQ:
    return GLFW_KEY_Q;
  case KeyR:
    return GLFW_KEY_R;
  case KeyS:
    return GLFW_KEY_S;
  case KeyT:
    return GLFW_KEY_T;
  case KeyU:
    return GLFW_KEY_U;
  case KeyV:
    return GLFW_KEY_V;
  case KeyW:
    return GLFW_KEY_W;
  case KeyX:
    return GLFW_KEY_X;
  case KeyY:
    return GLFW_KEY_Y;
  case KeyZ:
    return GLFW_KEY_Z;
  }
}