summaryrefslogtreecommitdiff
path: root/gfx-app/src/gfx_app.c
blob: 31f8448bb9c66a77171f640bebf9d82b41fccaf9 (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
#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 update to initialize the application's state.
  (*app->callbacks.update)(app->app_state, time, 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 += 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, "Gfx Application", 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;
}