summaryrefslogtreecommitdiff
path: root/gfx-app/src/gfx_app.c
blob: f3f04735f81758250054104dbf7e2a0712089107 (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
#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;
}