Window.cpp
1// Copyright 2019 Arthur Sonzogni. All rights reserved.
2// Use of this source code is governed by the MIT license that can be found in
3// the LICENSE file.
4
5#include <algorithm>
6#include <chrono>
7#include <cmath>
8#include <glm/gtc/matrix_transform.hpp>
9#include <iostream>
10#include <smk/Color.hpp>
11#include <smk/Drawable.hpp>
12#include <smk/Input.hpp>
13#include <smk/InputImpl.hpp>
14#include <smk/OpenGL.hpp>
15#include <smk/View.hpp>
16#include <smk/Window.hpp>
17#include <thread>
18#include <vector>
19
20#ifdef __EMSCRIPTEN__
21 #include <emscripten.h>
22 #include <emscripten/html5.h>
23#endif
24
25namespace smk {
26
27bool g_khr_parallel_shader = false; // NOLINT
28
29namespace {
30
31int g_next_id = 0; // NOLINT
32std::map<int, Window*> window_by_id; // NOLINT
33std::map<GLFWwindow*, Window*> window_by_glfw_window; // NOLINT
34
35void GLFWScrollCallback(GLFWwindow* glfw_window,
36 double xoffset,
37 double yoffset) {
38 Window* window = window_by_glfw_window[glfw_window];
39 if (!window) {
40 return;
41 }
42
43 dynamic_cast<InputImpl*>(&(window->input()))
44 ->OnScrollEvent({xoffset, yoffset});
45}
46
47void GLFWErrorCallback(int error, const char* description) {
48 std::cerr << "GFLW error n°" << error << std::endl;
49 std::cerr << "~~~" << std::endl;
50 std::cerr << description << std::endl;
51 std::cerr << "~~~" << std::endl;
52 fprintf(stderr, "Error: %s\n", description); // NOLINT
53}
54
55#ifdef __EMSCRIPTEN__
56
57EM_BOOL OnTouchEvent(int eventType,
58 const EmscriptenTouchEvent* keyEvent,
59 void* userData) {
60 int g_next_id = (int)(userData);
61 Window* window = window_by_id[g_next_id];
62 if (!window) {
63 return false;
64 }
65 static_cast<InputImpl*>(&(window->input()))
66 ->OnTouchEvent(eventType, keyEvent);
67 return true;
68}
69
70std::function<void(void)> main_loop;
71void MainLoop() {
72 return main_loop();
73}
74
75EM_JS(void, MakeCanvasSelectable, (int window_id), {
76 if (!Module) {
77 return;
78 }
79
80 if (!Module['canvas']) {
81 return;
82 }
83
84 Module['canvas'].setAttribute("smk", window_id);
85});
86
87#endif
88
89#if !defined NDEBUG && !defined __EMSCRIPTEN__ && __linux__
90void OpenGLDebugMessageCallback(GLenum /*source*/,
91 GLenum type,
92 GLuint /*g_next_id*/,
93 GLenum /*severity*/,
94 GLsizei length,
95 const GLchar* message,
96 const void* /*userParam*/) {
97 if (type == GL_DEBUG_TYPE_OTHER) {
98 return;
99 }
100 std::cerr << "SMK > OpenGL error: " << std::string(message, length)
101 << std::endl;
102}
103#endif
104
105void GLFWCharCallback(GLFWwindow* glfw_window, unsigned int codepoint) {
106 Window* window = window_by_glfw_window[glfw_window];
107 if (!window) {
108 return;
109 }
110 dynamic_cast<InputImpl*>(&(window->input()))
111 ->OnCharacterTyped((wchar_t)codepoint);
112}
113
114} // namespace
115
116/// @brief A null window.
118 id_ = ++g_next_id;
119 window_by_id[id_] = this;
120}
121
122/// @brief The window construtor.
123/// @param width The desired width of the window.
124/// @param height The desired height of the window.
125/// @param title The window's title.
126Window::Window(int width, int height, const std::string& title) {
127 input_ = std::make_unique<InputImpl>();
128 id_ = ++g_next_id;
129 window_by_id[id_] = this;
130 width_ = width;
131 height_ = height;
132
133 glfwSetErrorCallback(GLFWErrorCallback);
134 // initialize the GLFW library
135 if (!glfwInit()) {
136 throw std::runtime_error("Couldn't init GLFW");
137 }
138
139 // setting the opengl version
140#ifdef __EMSCRIPTEN__
141 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
142 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
143#else
144 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
145 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
146 glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
147 glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
148#endif
149
150 glfwWindowHint(GLFW_RESIZABLE, GL_TRUE);
151 glfwWindowHint(GLFW_SAMPLES, 4);
152
153 // create the window_
154 window_ = glfwCreateWindow(width_, height_, title.c_str(), nullptr, nullptr);
155 if (!window_) {
156 glfwTerminate();
157 throw std::runtime_error("Couldn't create a window_");
158 }
159 window_by_glfw_window[window_] = this;
160
161 glfwMakeContextCurrent(window_);
162
163#ifndef __EMSCRIPTEN__
164 glewExperimental = GL_TRUE;
165 GLenum err = glewInit();
166
167 if (err != GLEW_OK) {
168 glfwTerminate();
169 std::string error = (const char*)glewGetErrorString(err); // NOLINT
170 throw std::runtime_error("Could initialize GLEW, error = " + error);
171 }
172#endif
173
174#if !defined NDEBUG && !defined __EMSCRIPTEN__ && __linux__
175 glEnable(GL_DEBUG_OUTPUT);
176 glDebugMessageCallback(OpenGLDebugMessageCallback, 0);
177#endif
178
179 // get version info
180 const GLubyte* renderer = glGetString(GL_RENDERER);
181 const GLubyte* version = glGetString(GL_VERSION);
182 std::cout << "Renderer: " << renderer << std::endl;
183 std::cout << "OpenGL version supported " << version << std::endl;
184
185 // Alpha transparency.
186 glEnable(GL_BLEND);
187 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
188
189 InitRenderTarget();
190
191#ifndef __EMSCRIPTEN__
192 if (GLEW_KHR_parallel_shader_compile) {
193 glMaxShaderCompilerThreadsKHR(4);
194 g_khr_parallel_shader = true;
195 }
196#else
197 g_khr_parallel_shader = emscripten_webgl_enable_extension(
198 emscripten_webgl_get_current_context(), "KHR_parallel_shader_compile");
199
200 MakeCanvasSelectable(id_);
201
202 module_canvas_selector_ = "[smk='" + std::to_string(id_) + "']";
203
204 emscripten_set_touchstart_callback(module_canvas_selector_.c_str(),
205 (void*)id_, true, OnTouchEvent);
206 emscripten_set_touchend_callback(module_canvas_selector_.c_str(), (void*)id_,
207 true, OnTouchEvent);
208 emscripten_set_touchmove_callback(module_canvas_selector_.c_str(), (void*)id_,
209 true, OnTouchEvent);
210 emscripten_set_touchcancel_callback(module_canvas_selector_.c_str(),
211 (void*)id_, true, OnTouchEvent);
212#endif
213 glfwSetScrollCallback(window_, GLFWScrollCallback);
214 glfwSetCharCallback(window_, GLFWCharCallback);
215}
216
217Window::Window(Window&& window) noexcept {
218 operator=(std::move(window));
219}
220
221Window& Window::operator=(Window&& other) noexcept {
222 if (&other == this) {
223 return *this;
224 }
225 std::swap(window_, other.window_);
226 std::swap(time_, other.time_);
227 std::swap(time_last_sleep_, other.time_last_sleep_);
228 std::swap(input_, other.input_);
229 std::swap(id_, other.id_);
230 std::swap(module_canvas_selector_, other.module_canvas_selector_);
231 RenderTarget::operator=(std::move(other));
232 window_by_id[id_] = this;
233 window_by_glfw_window[window_] = this;
234 return *this;
235}
236
237/// @brief Handle all the new input events. This update the input() object.
239 glfwPollEvents();
240 input_->Update(window_);
241}
242
243/// @brief Present what has been draw to the screen.
245 // Swap Front and Back buffers (double buffering)
246 glfwSwapBuffers(window_);
247
248 // Detect window_ related changes
249 UpdateDimensions();
250
251 time_ = static_cast<float>(glfwGetTime());
252}
253
254Window::~Window() {
255 window_by_id.erase(id_);
256 // glfwTerminate(); // Needed? What about multiple windows?
257}
258
259/// The window handle.
260GLFWwindow* Window::window() const {
261 return window_;
262}
263
264/// The last time Window::PoolEvent() was called.
265float Window::time() const {
266 return time_;
267}
268
269/// Return an object for querying the input state.
271 return *(input_.get());
272}
273
274/// @brief Helper function. Execute the main loop of the application.
275/// On web based application: registers the loop in 'requestAnimationFrame'.
276/// On desktop based applications: This is a simple 'while' loop. The loop stops
277/// when the user presses the 'escape' button or when the |loop| function
278/// returns 'false'.
279/// @param loop The function to be called for each new frame.
280void Window::ExecuteMainLoopUntil(const std::function<bool(void)>& loop) {
281#ifdef __EMSCRIPTEN__
282 main_loop = [my_loop = loop] { (void)my_loop(); };
283 emscripten_set_main_loop(&MainLoop, 0, 1);
284#else
285 while (loop()) {
286 LimitFrameRate(60.F); // NOLINT
287 }
288#endif
289}
290
291/// @brief Helper function. Execute the main loop of the application.
292/// On web based application: registers the loop in 'requestAnimationFrame'.
293/// On desktop based applications: This is a simple 'while' loop. The loop stops
294/// when the user presses the 'escape' button or when the |loop| function
295/// returns 'false'.
296/// @param loop The function to be called for each new frame.
297void Window::ExecuteMainLoop(const std::function<void(void)>& loop) {
298#ifdef __EMSCRIPTEN__
299 main_loop = loop;
300 emscripten_set_main_loop(&MainLoop, 0, 1);
301#else
302 while (!input().IsKeyPressed(GLFW_KEY_ESCAPE) && !ShouldClose()) {
303 loop();
304 LimitFrameRate(60.F); // NOLINT
305 };
306#endif
307}
308
309void Window::UpdateDimensions() {
310 int width = width_;
311 int height = height_;
312#ifdef __EMSCRIPTEN__
313 emscripten_get_canvas_element_size(module_canvas_selector_.c_str(), &width_,
314 &height_);
315#else
316 glfwGetWindowSize(window_, &width_, &height_);
317#endif
318
319 if (width != width_ || height != height_) {
320 glViewport(0, 0, width_, height_);
321 SetView(view_);
322 }
323}
324
325/// If needed, insert pause in the execution to maintain a given framerate.
326/// @param fps the desired frame rate.
327void Window::LimitFrameRate(float fps) {
328 const float delta = static_cast<float>(glfwGetTime()) - time_last_sleep_;
329 const float target = 1.f / fps;
330 float sleep_duration = target - delta;
331 sleep_duration = std::min(target, sleep_duration);
332 if (sleep_duration > 0.f) {
333 std::this_thread::sleep_for(
334 std::chrono::microseconds(int(sleep_duration * 1'000'000))); // NOLINT
335 }
336 time_last_sleep_ = static_cast<float>(glfwGetTime());
337}
338
339/// Returns true when the user wants to close the window.
341 return glfwWindowShouldClose(window_);
342}
343
344} // namespace smk
A class for querying input state (keyboard, mouse, touch). The Input class is tied to a smk::Window....
Definition: Input.hpp:29
RenderTarget & operator=(RenderTarget &&other) noexcept
Move operator.
void SetView(const View &view)
Set the View to use.
int height() const
the height of the surface.
int width() const
the width of the surface.
A window. You can draw objects on the window.
Definition: Window.hpp:31
void ExecuteMainLoop(const std::function< void(void)> &loop)
Helper function. Execute the main loop of the application. On web based application: registers the lo...
Definition: Window.cpp:297
void PoolEvents()
Handle all the new input events. This update the input() object.
Definition: Window.cpp:238
bool ShouldClose()
Returns true when the user wants to close the window.
Definition: Window.cpp:340
void Display()
Present what has been draw to the screen.
Definition: Window.cpp:244
void ExecuteMainLoopUntil(const std::function< bool(void)> &loop)
Helper function. Execute the main loop of the application. On web based application: registers the lo...
Definition: Window.cpp:280
float time() const
The last time Window::PoolEvent() was called.
Definition: Window.cpp:265
Window()
A null window.
Definition: Window.cpp:117
GLFWwindow * window() const
The window handle.
Definition: Window.cpp:260
Input & input()
Return an object for querying the input state.
Definition: Window.cpp:270
void LimitFrameRate(float fps)
Definition: Window.cpp:327