-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathapplication.cpp
More file actions
462 lines (389 loc) · 17.4 KB
/
Copy pathapplication.cpp
File metadata and controls
462 lines (389 loc) · 17.4 KB
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
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
#include "application.hpp"
#include <flags/flags.h>
#include <ctime>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <queue>
#include <sstream>
#include <string>
#include <tuple>
// Include the Dear ImGui implementation headers
#define IMGUI_IMPL_OPENGL_LOADER_GLAD2
#include <imgui_impl/imgui_impl_glfw.h>
#include <imgui_impl/imgui_impl_opengl3.h>
#if !defined(NDEBUG)
// If NDEBUG (no debug) is not defined, enable OpenGL debug messages
#define ENABLE_OPENGL_DEBUG_MESSAGES
#endif
#include "texture/screenshot.hpp"
std::string default_screenshot_filepath() {
std::stringstream stream;
auto time = std::time(nullptr);
struct tm localtime;
// Linux compatiblity
#ifdef _WIN32
localtime_s(&localtime, &time);
#else
localtime_r(&time, &localtime);
#endif
stream << "screenshots/screenshot-" << std::put_time(&localtime, "%Y-%m-%d-%H-%M-%S") << ".png";
return stream.str();
}
// This function will be used to log errors thrown by GLFW
void glfw_error_callback(int error, const char* description) {
std::cerr << "GLFW Error: " << error << ": " << description << std::endl;
}
// This function will be used to log OpenGL debug messages
void GLAPIENTRY opengl_callback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length,
const GLchar* message, const void* userParam) {
std::string _source;
std::string _type;
std::string _severity;
// What is the source of the message
switch (source) {
case GL_DEBUG_SOURCE_API:
_source = "API";
break;
case GL_DEBUG_SOURCE_WINDOW_SYSTEM:
_source = "WINDOW SYSTEM";
break;
case GL_DEBUG_SOURCE_SHADER_COMPILER:
_source = "SHADER COMPILER";
break;
case GL_DEBUG_SOURCE_THIRD_PARTY:
_source = "THIRD PARTY";
break;
case GL_DEBUG_SOURCE_APPLICATION:
_source = "APPLICATION";
break;
case GL_DEBUG_SOURCE_OTHER:
default:
_source = "UNKNOWN";
break;
}
// What is the type of the message (error, warning, etc).
switch (type) {
case GL_DEBUG_TYPE_ERROR:
_type = "ERROR";
break;
case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR:
_type = "DEPRECATED BEHAVIOR";
break;
case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:
_type = "UDEFINED BEHAVIOR";
break;
case GL_DEBUG_TYPE_PORTABILITY:
_type = "PORTABILITY";
break;
case GL_DEBUG_TYPE_PERFORMANCE:
_type = "PERFORMANCE";
break;
case GL_DEBUG_TYPE_OTHER:
_type = "OTHER";
break;
case GL_DEBUG_TYPE_MARKER:
_type = "MARKER";
break;
default:
_type = "UNKNOWN";
break;
}
// How severe is the message
switch (severity) {
case GL_DEBUG_SEVERITY_HIGH:
_severity = "HIGH";
break;
case GL_DEBUG_SEVERITY_MEDIUM:
_severity = "MEDIUM";
break;
case GL_DEBUG_SEVERITY_LOW:
_severity = "LOW";
break;
case GL_DEBUG_SEVERITY_NOTIFICATION:
_severity = "NOTIFICATION";
break;
default:
_severity = "UNKNOWN";
break;
}
std::cout << "OpenGL Debug Message " << id << " (type: " << _type << ") of " << _severity << " raised from "
<< _source << ": " << message << std::endl;
}
void our::Application::configureOpenGL() {
// Request that OpenGL is 3.3
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
// Only enable core functionalities (disable features from older OpenGL versions that were removed in 3.3)
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// Enable forward compatibility with newer OpenGL versions by removing deprecated functionalities
// This is necessary for some platforms
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
// Set Number of sample used in MSAA (0 = Disabled)
glfwWindowHint(GLFW_SAMPLES, 0);
// Enable Double Buffering
glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_TRUE);
// Set the bit-depths of the frame buffer
glfwWindowHint(GLFW_RED_BITS, 8);
glfwWindowHint(GLFW_GREEN_BITS, 8);
glfwWindowHint(GLFW_BLUE_BITS, 8);
glfwWindowHint(GLFW_ALPHA_BITS, 8);
// Set Bits for Depth Buffer
glfwWindowHint(GLFW_DEPTH_BITS, 24);
// Set Bits for Stencil Buffer
glfwWindowHint(GLFW_STENCIL_BITS, 8);
// Set the refresh rate of the window (GLFW_DONT_CARE = Run as fast as possible)
glfwWindowHint(GLFW_REFRESH_RATE, GLFW_DONT_CARE);
}
our::WindowConfiguration our::Application::getWindowConfiguration() {
auto window_config = app_config["window"];
std::string title = window_config["title"].get<std::string>();
int width = window_config["size"]["width"].get<int>();
int height = window_config["size"]["height"].get<int>();
bool isFullScreen = window_config["fullscreen"].get<bool>();
return {title, {width, height}, isFullScreen};
}
// This is the main class function that run the whole application (Initialize, Game loop, House cleaning).
// run_for_frames decides how many frames should be run before the application automatically closes.
// if run_for_frames == 0, the application runs indefinitely till manually closed.
int our::Application::run(int run_for_frames) {
// Set the function to call when an error occurs.
glfwSetErrorCallback(glfw_error_callback);
// Initialize GLFW and exit if it failed
if (!glfwInit()) {
std::cerr << "Failed to Initialize GLFW" << std::endl;
return -1;
}
configureOpenGL(); // This function sets OpenGL window hints.
auto win_config = getWindowConfiguration(); // Returns the WindowConfiguration current struct instance.
// Create the window. When fullscreen is requested, prefer a borderless fullscreen window
// instead of exclusive monitor fullscreen so leaving the game doesn't disturb other desktop windows
GLFWmonitor* primaryMonitor = glfwGetPrimaryMonitor();
GLFWmonitor* monitor = nullptr;
int windowWidth = win_config.size.x;
int windowHeight = win_config.size.y;
int windowPosX = GLFW_DONT_CARE;
int windowPosY = GLFW_DONT_CARE;
if (win_config.isFullscreen && primaryMonitor) {
if (const GLFWvidmode* videoMode = glfwGetVideoMode(primaryMonitor)) {
windowWidth = videoMode->width;
windowHeight = videoMode->height;
}
glfwGetMonitorPos(primaryMonitor, &windowPosX, &windowPosY);
glfwWindowHint(GLFW_DECORATED, GLFW_FALSE);
glfwWindowHint(GLFW_AUTO_ICONIFY, GLFW_FALSE);
glfwWindowHint(GLFW_FLOATING, GLFW_TRUE);
}
// The last parameter "share" can be used to share the resources (OpenGL objects) between multiple windows.
window = glfwCreateWindow(windowWidth, windowHeight, win_config.title.c_str(), monitor, nullptr);
if (!window) {
std::cerr << "Failed to Create Window" << std::endl;
glfwTerminate();
return -1;
}
if (win_config.isFullscreen && primaryMonitor) {
glfwSetWindowAttrib(window, GLFW_DECORATED, GLFW_FALSE);
glfwSetWindowAttrib(window, GLFW_FLOATING, GLFW_TRUE); // so taskbar doesn't cover the window
glfwSetWindowPos(window, windowPosX, windowPosY);
glfwSetWindowSize(window, windowWidth, windowHeight);
}
glfwMakeContextCurrent(
window); // Tell GLFW to make the context of our window the main context on the current thread.
gladLoadGL(glfwGetProcAddress); // Load the OpenGL functions from the driver
glfwSwapInterval(0); // Disable V-Sync
// Print information about the OpenGL context
std::cout << "VENDOR : " << glGetString(GL_VENDOR) << std::endl;
std::cout << "RENDERER : " << glGetString(GL_RENDERER) << std::endl;
std::cout << "VERSION : " << glGetString(GL_VERSION) << std::endl;
std::cout << "GLSL VERSION : " << glGetString(GL_SHADING_LANGUAGE_VERSION) << std::endl;
#if defined(ENABLE_OPENGL_DEBUG_MESSAGES)
// if we have OpenGL debug messages enabled, set the message callback
glDebugMessageCallback(opengl_callback, nullptr);
// Then enable debug output
glEnable(GL_DEBUG_OUTPUT);
// Then make the output synchronized to the OpenGL commands.
// This will make sure that OpenGL and the main thread are synchronized such that message callback is called as soon
// as the command causing it is called. This is useful for debugging but slows down the code execution.
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
// Disable ALL messages with severity of "notification"
glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_NOTIFICATION, 0, nullptr, GL_FALSE);
#endif
setupCallbacks();
keyboard.enable(window);
mouse.enable(window);
// Start the ImGui context and set dark style (just my preference :D)
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
ImGui::StyleColorsDark();
// Initialize ImGui for GLFW and OpenGL
ImGui_ImplGlfw_InitForOpenGL(window, true);
ImGui_ImplOpenGL3_Init("#version 330 core");
audioSystem.initialize();
// This part of the code extracts the list of requested screenshots and puts them into a priority queue
using ScreenshotRequest = std::pair<int, std::string>;
std::priority_queue<ScreenshotRequest, std::vector<ScreenshotRequest>, std::greater<ScreenshotRequest>>
requested_screenshots;
if (auto& screenshots = app_config["screenshots"]; screenshots.is_object()) {
auto base_path = std::filesystem::path(screenshots.value("directory", "screenshots"));
if (auto& requests = screenshots["requests"]; requests.is_array()) {
for (auto& item : requests) {
auto path = base_path / item.value("file", "");
int frame = item.value("frame", 0);
requested_screenshots.push({frame, path.string()});
}
}
}
// If a scene change was requested, apply it
if (nextState) {
currentState = nextState;
nextState = nullptr;
}
// Call onInitialize if the scene needs to do some custom initialization (such as file loading, object creation,
// etc).
if (currentState) currentState->onInitialize();
// The time at which the last frame started. But there was no frames yet, so we'll just pick the current time.
double last_frame_time = glfwGetTime();
int current_frame = 0;
// Game loop
while (!glfwWindowShouldClose(window)) {
if (run_for_frames != 0 && current_frame >= run_for_frames) break;
glfwPollEvents(); // Read all the user events and call relevant callbacks.
// Start a new ImGui frame
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
if (currentState) currentState->onImmediateGui(); // Call to run any required Immediate GUI.
// If ImGui is using the mouse or keyboard, then we don't want the captured events to affect our keyboard and
// mouse objects. For example, if you're focusing on an input and writing "W", the keyboard object shouldn't
// record this event.
keyboard.setEnabled(!io.WantCaptureKeyboard, window);
mouse.setEnabled(!io.WantCaptureMouse, window);
// Render the ImGui commands we called (this doesn't actually draw to the screen yet.
ImGui::Render();
// Just in case ImGui changed the OpenGL viewport (the portion of the window to which we render the geometry),
// we set it back to cover the whole window
auto frame_buffer_size = getFrameBufferSize();
glViewport(0, 0, frame_buffer_size.x, frame_buffer_size.y);
// Get the current time (the time at which we are starting the current frame).
double current_frame_time = glfwGetTime();
// Call onDraw, in which we will draw the current frame, and send to it the time difference between the last and
// current frame
if (currentState) currentState->onDraw(current_frame_time - last_frame_time);
last_frame_time =
current_frame_time; // Then update the last frame start time (this frame is now the last frame)
#if defined(ENABLE_OPENGL_DEBUG_MESSAGES)
// Since ImGui causes many messages to be thrown, we are temporarily disabling the debug messages till we render
// the ImGui
glDisable(GL_DEBUG_OUTPUT);
glDisable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
#endif
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); // Render the ImGui to the framebuffer
#if defined(ENABLE_OPENGL_DEBUG_MESSAGES)
// Re-enable the debug messages
glEnable(GL_DEBUG_OUTPUT);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
#endif
// If F12 is pressed, take a screenshot
if (keyboard.justPressed(GLFW_KEY_F12)) {
glViewport(0, 0, frame_buffer_size.x, frame_buffer_size.y);
std::string path = default_screenshot_filepath();
if (our::screenshot_png(path)) {
std::cout << "Screenshot saved to: " << path << std::endl;
} else {
std::cerr << "Failed to save a Screenshot" << std::endl;
}
}
// There are any requested screenshots, take them
while (requested_screenshots.size()) {
if (const auto& request = requested_screenshots.top(); request.first == current_frame) {
if (our::screenshot_png(request.second)) {
std::cout << "Screenshot saved to: " << request.second << std::endl;
} else {
std::cerr << "Failed to save a screenshot to: " << request.second << std::endl;
}
requested_screenshots.pop();
} else
break;
}
// Swap the frame buffers
glfwSwapBuffers(window);
// Update the keyboard and mouse data
keyboard.update();
mouse.update();
// If a scene change was requested, apply it
while (nextState) {
// If a scene was already running, destroy it (not delete since we can go back to it later)
if (currentState) currentState->onDestroy();
// Switch scenes
currentState = nextState;
nextState = nullptr;
// Initialize the new scene
currentState->onInitialize();
}
++current_frame;
}
// Call for cleaning up
if (currentState) currentState->onDestroy();
audioSystem.destroy();
// Shutdown ImGui & destroy the context
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();
// Destroy the window
glfwDestroyWindow(window);
// And finally terminate GLFW
glfwTerminate();
return 0; // Good bye
}
// Sets-up the window callback functions from GLFW to our (Mouse/Keyboard) classes.
void our::Application::setupCallbacks() {
// We use GLFW to store a pointer to "this" window instance.
glfwSetWindowUserPointer(window, this);
// The pointer is then retrieved in the callback function.
// The second parameter to "glfwSet---Callback" is a function pointer.
// It is replaced by an inline function -lambda expression- as it is not needed to create
// a seperate function for it.
// In the inline function we retrieve the window instance and use it to set our (Mouse/Keyboard) classes values.
// Keyboard callbacks
glfwSetKeyCallback(window, [](GLFWwindow* window, int key, int scancode, int action, int mods) {
auto* app = static_cast<Application*>(glfwGetWindowUserPointer(window));
if (app) {
app->getKeyboard().keyEvent(key, scancode, action, mods);
if (app->currentState) app->currentState->onKeyEvent(key, scancode, action, mods);
}
});
// mouse position callbacks
glfwSetCursorPosCallback(window, [](GLFWwindow* window, double x_position, double y_position) {
auto* app = static_cast<Application*>(glfwGetWindowUserPointer(window));
if (app) {
app->getMouse().CursorMoveEvent(x_position, y_position);
if (app->currentState) app->currentState->onCursorMoveEvent(x_position, y_position);
}
});
// mouse position callbacks
glfwSetCursorEnterCallback(window, [](GLFWwindow* window, int entered) {
auto* app = static_cast<Application*>(glfwGetWindowUserPointer(window));
if (app) {
if (app->currentState) app->currentState->onCursorEnterEvent(entered);
}
});
// mouse button position callbacks
glfwSetMouseButtonCallback(window, [](GLFWwindow* window, int button, int action, int mods) {
auto* app = static_cast<Application*>(glfwGetWindowUserPointer(window));
if (app) {
app->getMouse().MouseButtonEvent(button, action, mods);
if (app->currentState) app->currentState->onMouseButtonEvent(button, action, mods);
}
});
// mouse scroll callbacks
glfwSetScrollCallback(window, [](GLFWwindow* window, double x_offset, double y_offset) {
auto* app = static_cast<Application*>(glfwGetWindowUserPointer(window));
if (app) {
app->getMouse().ScrollEvent(x_offset, y_offset);
if (app->currentState) app->currentState->onScrollEvent(x_offset, y_offset);
}
});
}