Browse code

Add GLBackendGLFW

Robert Cranston authored on 12/10/2021 04:08:43
Showing 8 changed files

... ...
@@ -17,6 +17,16 @@ add_library(${PROJECT_NAME})
17 17
 set(GLBACKEND_DEPENDENCIES)
18 18
 set(GLBACKEND_DEFINITIONS)
19 19
 
20
+## glfw
21
+find_package(glfw3)
22
+if(glfw3_FOUND)
23
+    list(APPEND GLBACKEND_DEPENDENCIES glfw)
24
+    list(APPEND GLBACKEND_DEFINITIONS  GLBACKEND_GLFW)
25
+endif()
26
+
27
+## Cache
28
+set(GLBACKEND_DEFINITIONS "${GLBACKEND_DEFINITIONS}" CACHE INTERNAL "")
29
+
20 30
 ## Common
21 31
 include(common.cmake)
22 32
 common(
... ...
@@ -4,14 +4,16 @@ A [C++11][]/[OpenGL][] \>=[1.0][] [backend][] library.
4 4
 
5 5
 Currently supported backends:
6 6
 
7
-| Name | Category | Define | Include | Class |
8
-| --   | --       | --     | --      | --    |
7
+| Name     | Category        | Define           | Include              | Class         |
8
+| --       | --              | --               | --                   | --            |
9
+| [GLFW][] | Windowing/input | `GLBACKEND_GLFW` | `glbackend_glfw.hpp` | `BackendGLFW` |
9 10
 
10 11
 [`glbackend`]: https://git.rcrnstn.net/rcrnstn/glbackend
11 12
 [C++11]: https://en.wikipedia.org/wiki/C++11
12 13
 [OpenGL]: https://en.wikipedia.org/wiki/OpenGL
13 14
 [1.0]: https://en.wikipedia.org/wiki/OpenGL#Version_history
14 15
 [backend]: https://www.khronos.org/opengl/wiki/Related_toolkits_and_APIs#OpenGL_initialization
16
+[GLFW]: https://www.glfw.org
15 17
 
16 18
 ## Usage
17 19
 
... ...
@@ -330,10 +332,13 @@ System dependencies that need to be installed:
330 332
 -   Public (interface):
331 333
     -   [OpenGL][] (e.g. [`libgl1-mesa-dev`][]).
332 334
     -   [OpenGL Extension Wrangler (GLEW)][] (e.g. [`libglew-dev`][]).
335
+-   Private (tests):
336
+    -   [GLFW][] (e.g. [`libglfw3-dev`][]).
333 337
 
334 338
 [OpenGL Extension Wrangler (GLEW)]: http://glew.sourceforge.net
335 339
 [`libgl1-mesa-dev`]: https://packages.debian.org/search?keywords=libgl1-mesa-dev
336 340
 [`libglew-dev`]: https://packages.debian.org/search?keywords=libglew-dev
341
+[`libglfw3-dev`]: https://packages.debian.org/search?keywords=libglfw3-dev
337 342
 
338 343
 ## Build system
339 344
 
340 345
new file mode 100644
341 346
Binary files /dev/null and b/assets/tests/GLBackendGLFW.tga differ
... ...
@@ -2,7 +2,10 @@
2 2
 #define GLBACKEND_DEFAULT_HPP_
3 3
 
4 4
 
5
-#if 1
5
+#if defined(GLBACKEND_GLFW)
6
+    #include "glbackend_glfw.hpp" // IWYU pragma: export
7
+    using GLBackendDefault = GLBackendGLFW;
8
+#else
6 9
     #error "No GLBackendDefault configured"
7 10
 #endif
8 11
 
9 12
new file mode 100644
... ...
@@ -0,0 +1,182 @@
1
+/// Guards
2
+
3
+#ifdef  GLBACKEND_GLFW
4
+#ifndef GLBACKEND_GLFW_HPP_
5
+#define GLBACKEND_GLFW_HPP_
6
+
7
+
8
+/// Includes
9
+
10
+#include <array>
11
+#include <string>
12
+
13
+#include <glbackend.hpp>
14
+
15
+#include <GLFW/glfw3.h>
16
+
17
+
18
+/// Class definition
19
+
20
+class GLBackendGLFW final : public GLBackend
21
+{
22
+
23
+public:
24
+
25
+    //// Special member functions
26
+
27
+    explicit GLBackendGLFW(
28
+        std::string const & title,
29
+        std::array<int, 2>  size        = {0, 0},
30
+        std::array<int, 2>  version     = {0, 0},
31
+        int                 samples     = 0,
32
+        bool                fullscreen  = false,
33
+        bool                transparent = false
34
+    );
35
+    ~GLBackendGLFW();
36
+    GLBackendGLFW(GLBackendGLFW &&) noexcept;
37
+    GLBackendGLFW(GLBackendGLFW const &) = delete;
38
+    GLBackendGLFW & operator=(GLBackendGLFW &&) = delete;
39
+    GLBackendGLFW & operator=(GLBackendGLFW const &) = delete;
40
+
41
+    //// Context
42
+
43
+    void current() override;
44
+
45
+    //// Render loop
46
+
47
+    void swap() override;
48
+
49
+    void events() override;
50
+
51
+    bool running() const override;
52
+    bool running(bool running) override;
53
+
54
+    float time() const override;
55
+    float time(float time) override;
56
+
57
+    //// Input and output
58
+
59
+    void lock(bool lock) override;
60
+
61
+    bool key   (std::string const & key)    const override;
62
+    bool button(int                 button) const override;
63
+
64
+    //// Debug
65
+
66
+    std::string debug_info() const override;
67
+
68
+protected:
69
+
70
+    //// Special member functions
71
+
72
+    void destroy_();
73
+
74
+    //// Debug
75
+
76
+    void static debug_glfw_error_callback_(
77
+        int          error,
78
+        char const * message
79
+    );
80
+
81
+protected:
82
+
83
+    //// Context
84
+
85
+    int static thread_local init_count_;
86
+
87
+    GLFWwindow * window_;
88
+
89
+};
90
+
91
+
92
+/// Inline definitions
93
+
94
+inline void GLBackendGLFW::current()
95
+{
96
+    glfwMakeContextCurrent(window_);
97
+}
98
+
99
+inline void GLBackendGLFW::events()
100
+{
101
+    glfwPollEvents();
102
+}
103
+
104
+inline void GLBackendGLFW::swap()
105
+{
106
+    glfwSwapBuffers(window_);
107
+}
108
+
109
+inline bool GLBackendGLFW::running(bool running)
110
+{
111
+    glfwSetWindowShouldClose(window_, !running);
112
+    return running;
113
+}
114
+
115
+inline bool GLBackendGLFW::running() const
116
+{
117
+    return !glfwWindowShouldClose(window_);
118
+}
119
+
120
+inline float GLBackendGLFW::time() const
121
+{
122
+    return (float)glfwGetTime();
123
+}
124
+
125
+inline float GLBackendGLFW::time(float time)
126
+{
127
+    glfwSetTime((double)time);
128
+    return time;
129
+}
130
+
131
+inline void GLBackendGLFW::lock(bool lock)
132
+{
133
+    glfwSetInputMode(
134
+        window_, GLFW_CURSOR, lock ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL
135
+    );
136
+}
137
+
138
+inline bool GLBackendGLFW::key(std::string const & key) const
139
+{
140
+    if (key == "Left")
141
+        return glfwGetKey(window_, GLFW_KEY_LEFT) == GLFW_PRESS;
142
+    else if (key == "Right")
143
+        return glfwGetKey(window_, GLFW_KEY_RIGHT) == GLFW_PRESS;
144
+    else if (key == "Up")
145
+        return glfwGetKey(window_, GLFW_KEY_UP) == GLFW_PRESS;
146
+    else if (key == "Down")
147
+        return glfwGetKey(window_, GLFW_KEY_DOWN) == GLFW_PRESS;
148
+    else if (key == "Enter")
149
+        return glfwGetKey(window_, GLFW_KEY_ENTER) == GLFW_PRESS;
150
+    else if (key == "Escape")
151
+        return glfwGetKey(window_, GLFW_KEY_ESCAPE) == GLFW_PRESS;
152
+    else if (key == "Tab")
153
+        return glfwGetKey(window_, GLFW_KEY_TAB) == GLFW_PRESS;
154
+    else if (key == "Backspace")
155
+        return glfwGetKey(window_, GLFW_KEY_BACKSPACE) == GLFW_PRESS;
156
+    else if (key == "Control")
157
+        return
158
+            glfwGetKey(window_, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS ||
159
+            glfwGetKey(window_, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS;
160
+    else if (key == "Shift")
161
+        return
162
+            glfwGetKey(window_, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS ||
163
+            glfwGetKey(window_, GLFW_KEY_RIGHT_SHIFT) == GLFW_PRESS;
164
+    else if (key == "Alt")
165
+        return
166
+            glfwGetKey(window_, GLFW_KEY_LEFT_ALT) == GLFW_PRESS ||
167
+            glfwGetKey(window_, GLFW_KEY_RIGHT_ALT) == GLFW_PRESS;
168
+    else if (key.length() == 1)
169
+        return glfwGetKey(window_, key[0]) == GLFW_PRESS;
170
+    return false;
171
+}
172
+
173
+inline bool GLBackendGLFW::button(int button) const
174
+{
175
+    return glfwGetMouseButton(window_, button - 1) == GLFW_PRESS;
176
+}
177
+
178
+
179
+/// Guards
180
+
181
+#endif
182
+#endif
0 183
new file mode 100644
... ...
@@ -0,0 +1,380 @@
1
+/// Guards
2
+
3
+
4
+#ifdef GLBACKEND_GLFW
5
+
6
+
7
+/// Includes
8
+
9
+
10
+#include <glbackend_glfw.hpp>
11
+
12
+#include <array>
13
+#include <functional>
14
+#include <limits>
15
+#include <sstream>
16
+#include <string>
17
+#include <type_traits>
18
+#include <utility>
19
+
20
+#include <glbase.hpp>
21
+#include <glbackend.hpp>
22
+
23
+#include <GLFW/glfw3.h>
24
+
25
+// NOLINTNEXTLINE
26
+#define STR_EXCEPTION GLBase::Exception
27
+#include <str.hpp>
28
+
29
+
30
+/// Special member functions
31
+
32
+
33
+GLBackendGLFW::GLBackendGLFW(
34
+    std::string const & title,
35
+    std::array<int, 2>  size,
36
+    std::array<int, 2>  version,
37
+    int                 samples,
38
+    bool                fullscreen,
39
+    bool                transparent
40
+)
41
+:
42
+    GLBackend(),
43
+    window_{nullptr}
44
+{
45
+    //// Backend errors
46
+    glfwSetErrorCallback(debug_glfw_error_callback_);
47
+    auto const glfw_get_error_description = []()
48
+    {
49
+        char const * description{};
50
+        glfwGetError(&description);
51
+        return description;
52
+    };
53
+
54
+    //// Backend init
55
+    if (!glfwInit())
56
+        STR_THROW(
57
+            "Failed to initialize GLFW" << ":\n" <<
58
+            glfw_get_error_description()
59
+        );
60
+    ++init_count_;
61
+
62
+    //// Window options
63
+    glfwDefaultWindowHints();
64
+    glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
65
+    GLFWmonitor * monitor = nullptr;
66
+    if (fullscreen)
67
+        monitor = glfwGetPrimaryMonitor();
68
+    if (transparent)
69
+    {
70
+        glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE);
71
+        glfwWindowHint(GLFW_DECORATED, GLFW_FALSE);
72
+        glfwWindowHint(GLFW_FLOATING, GLFW_TRUE);
73
+    }
74
+    if (size[0] == 0 || size[1] == 0)
75
+    {
76
+        auto const * mode = glfwGetVideoMode(glfwGetPrimaryMonitor());
77
+        size[0] = mode->width;
78
+        size[1] = mode->height;
79
+    }
80
+    size_ = size;
81
+
82
+    //// Context options
83
+    glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_TRUE);
84
+    glfwWindowHint(GLFW_RED_BITS,      8); // NOLINT
85
+    glfwWindowHint(GLFW_GREEN_BITS,    8); // NOLINT
86
+    glfwWindowHint(GLFW_BLUE_BITS,     8); // NOLINT
87
+    glfwWindowHint(GLFW_ALPHA_BITS,    8); // NOLINT
88
+    glfwWindowHint(GLFW_DEPTH_BITS,   24); // NOLINT
89
+    glfwWindowHint(GLFW_STENCIL_BITS,  8); // NOLINT
90
+    if (samples)
91
+        glfwWindowHint(GLFW_SAMPLES, samples);
92
+    if (version[0])
93
+        glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, version[0]);
94
+    if (version[1])
95
+        glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, version[1]);
96
+    if ((version[0] == 3 && version[1] >= 2) || version[0] >= 4) // NOLINT
97
+    {
98
+        glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
99
+        glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
100
+    }
101
+    if (debug() >= 1)
102
+        glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE);
103
+    else if (version[0] >= 2) // NOLINT
104
+        glfwWindowHint(GLFW_CONTEXT_NO_ERROR, GLFW_TRUE);
105
+
106
+    try
107
+    {
108
+        //// Window
109
+        window_ = glfwCreateWindow(
110
+            size[0],
111
+            size[1],
112
+            title.c_str(),
113
+            monitor,
114
+            nullptr
115
+        );
116
+        if (!window_)
117
+            STR_THROW(
118
+                "Failed to create GLFW window" << ":\n" <<
119
+                glfw_get_error_description()
120
+            );
121
+
122
+        //// Context
123
+        glfwMakeContextCurrent(window_);
124
+        if (debug() == 0)
125
+            glfwSwapInterval(1);
126
+        init_();
127
+
128
+        //// Lock
129
+        if (fullscreen)
130
+            lock(true);
131
+    }
132
+    catch (...)
133
+    {
134
+        destroy_();
135
+        throw;
136
+    }
137
+
138
+    //// Callbacks
139
+    // NOLINTNEXTLINE
140
+    #define GLBACKEND_GLFW_SELF_(WINDOW) \
141
+        (*static_cast<GLBackendGLFW *>(glfwGetWindowUserPointer(WINDOW)))
142
+    glfwSetWindowUserPointer(window_, this);
143
+    glfwSetKeyCallback(
144
+        window_,
145
+        [](
146
+            GLFWwindow * window,
147
+            int key,
148
+            int scancode,
149
+            int action,
150
+            int mods
151
+        )
152
+        {
153
+            (void)scancode;
154
+            (void)mods;
155
+            if (action != GLFW_PRESS)
156
+                return;
157
+            auto & self = GLBACKEND_GLFW_SELF_(window);
158
+            if (self.callback_key_)
159
+            {
160
+                if(key == GLFW_KEY_LEFT)
161
+                    self.callback_key_("Left");
162
+                else if (key == GLFW_KEY_RIGHT)
163
+                    self.callback_key_("Right");
164
+                else if (key == GLFW_KEY_UP)
165
+                    self.callback_key_("Up");
166
+                else if (key == GLFW_KEY_DOWN)
167
+                    self.callback_key_("Down");
168
+                else if (key == GLFW_KEY_ENTER)
169
+                    self.callback_key_("Enter");
170
+                else if (key == GLFW_KEY_ESCAPE)
171
+                    self.callback_key_("Escape");
172
+                else if (key == GLFW_KEY_TAB)
173
+                    self.callback_key_("Tab");
174
+                else if (key == GLFW_KEY_BACKSPACE)
175
+                    self.callback_key_("Backspace");
176
+                else if (
177
+                    key == GLFW_KEY_RIGHT_CONTROL ||
178
+                    key == GLFW_KEY_LEFT_CONTROL
179
+                )
180
+                    self.callback_key_("Control");
181
+                else if (
182
+                    key == GLFW_KEY_RIGHT_SHIFT ||
183
+                    key == GLFW_KEY_LEFT_SHIFT
184
+                )
185
+                    self.callback_key_("Shift");
186
+                else if (
187
+                    key == GLFW_KEY_RIGHT_ALT ||
188
+                    key == GLFW_KEY_LEFT_ALT
189
+                )
190
+                    self.callback_key_("Alt");
191
+                else if (
192
+                    0 <= key && key <= std::numeric_limits<char>::max()
193
+                )
194
+                    self.callback_key_(std::string(1, (char)key));
195
+            }
196
+        }
197
+    );
198
+    glfwSetMouseButtonCallback(
199
+        window_,
200
+        [](
201
+            GLFWwindow * window,
202
+            int button,
203
+            int action,
204
+            int mods
205
+        )
206
+        {
207
+            (void)mods;
208
+            if (action != GLFW_PRESS)
209
+                return;
210
+            auto & self = GLBACKEND_GLFW_SELF_(window);
211
+            if (self.callback_button_)
212
+                self.callback_button_(button);
213
+        }
214
+    );
215
+    glfwSetScrollCallback(
216
+        window_,
217
+        [](
218
+            GLFWwindow * window,
219
+            double scroll_x,
220
+            double scroll_y
221
+        )
222
+        {
223
+            auto & self = GLBACKEND_GLFW_SELF_(window);
224
+            self.scroll_ = {
225
+                (float)scroll_x,
226
+                (float)scroll_y,
227
+            };
228
+            if (self.callback_scroll_)
229
+                self.callback_scroll_(self.scroll_);
230
+        }
231
+    );
232
+    glfwSetCursorPosCallback(
233
+        window_,
234
+        [](
235
+            GLFWwindow * window,
236
+            double x,
237
+            double y
238
+        )
239
+        {
240
+            auto & self = GLBACKEND_GLFW_SELF_(window);
241
+            self.move_ = {
242
+                (float)x - self.position_[0],
243
+                (float)y - self.position_[1],
244
+            };
245
+            self.position_ = {
246
+                (float)x,
247
+                (float)y,
248
+            };
249
+            if (self.callback_position_)
250
+                self.callback_position_(self.position_);
251
+            if (self.callback_move_)
252
+                self.callback_move_(self.move_);
253
+        }
254
+    );
255
+    glfwSetFramebufferSizeCallback(
256
+        window_,
257
+        [](
258
+            GLFWwindow * window,
259
+            int width,
260
+            int height
261
+        )
262
+        {
263
+            auto & self = GLBACKEND_GLFW_SELF_(window);
264
+            self.size_ = {
265
+                width,
266
+                height,
267
+            };
268
+            if (self.callback_size_)
269
+                self.callback_size_(self.size_);
270
+        }
271
+    );
272
+}
273
+
274
+
275
+GLBackendGLFW::GLBackendGLFW(GLBackendGLFW && other) noexcept
276
+:
277
+    GLBackend(std::move(other)),
278
+    window_{other.window_}
279
+{
280
+    other.window_ = nullptr;
281
+}
282
+
283
+
284
+GLBackendGLFW::~GLBackendGLFW()
285
+{
286
+    destroy_();
287
+}
288
+
289
+
290
+void GLBackendGLFW::destroy_()
291
+{
292
+    if (window_)
293
+    {
294
+        lock(false);
295
+        glfwDestroyWindow(window_);
296
+    }
297
+    if (--init_count_ == 0)
298
+        glfwTerminate();
299
+}
300
+
301
+
302
+GLBASE_GLOBAL(GLBackendGLFW::init_count_, {0})
303
+
304
+
305
+/// Debug
306
+
307
+
308
+std::string GLBackendGLFW::debug_info() const
309
+{
310
+    auto ostream = std::ostringstream{};
311
+
312
+    auto version_major = int{};
313
+    auto version_minor = int{};
314
+    auto version_rev   = int{};
315
+    glfwGetVersion(
316
+        &version_major,
317
+        &version_minor,
318
+        &version_rev
319
+    );
320
+    debug_info_(ostream, "GLFW", {
321
+        {
322
+            "VERSION_COMPILED",
323
+                glfwGetVersionString()
324
+        },
325
+        {
326
+            "VERSION_LINKED",
327
+                std::to_string(version_major) + "." +
328
+                std::to_string(version_minor) + "." +
329
+                std::to_string(version_rev),
330
+        },
331
+    });
332
+
333
+    ostream << GLBackend::debug_info();
334
+
335
+    return ostream.str();
336
+}
337
+
338
+
339
+void GLBackendGLFW::debug_glfw_error_callback_(
340
+    int          error,
341
+    char const * message
342
+)
343
+{
344
+    auto ostream = std::ostringstream{};
345
+    ostream << std::hex << std::showbase;
346
+
347
+    // https://www.glfw.org/docs/3.3/group__errors.html
348
+    ostream << "GLFW error ";
349
+    // NOLINTNEXTLINE
350
+    #define GLBACKEND_GLFW_CALLBACK_CASE_(VALUE) \
351
+        case GLFW_ ## VALUE: \
352
+            ostream << #VALUE; \
353
+            break;
354
+    switch(error)
355
+    {
356
+        GLBACKEND_GLFW_CALLBACK_CASE_(NOT_INITIALIZED)
357
+        GLBACKEND_GLFW_CALLBACK_CASE_(NO_CURRENT_CONTEXT)
358
+        GLBACKEND_GLFW_CALLBACK_CASE_(INVALID_ENUM)
359
+        GLBACKEND_GLFW_CALLBACK_CASE_(INVALID_VALUE)
360
+        GLBACKEND_GLFW_CALLBACK_CASE_(OUT_OF_MEMORY)
361
+        GLBACKEND_GLFW_CALLBACK_CASE_(API_UNAVAILABLE)
362
+        GLBACKEND_GLFW_CALLBACK_CASE_(VERSION_UNAVAILABLE)
363
+        GLBACKEND_GLFW_CALLBACK_CASE_(PLATFORM_ERROR)
364
+        GLBACKEND_GLFW_CALLBACK_CASE_(FORMAT_UNAVAILABLE)
365
+        GLBACKEND_GLFW_CALLBACK_CASE_(NO_WINDOW_CONTEXT)
366
+        default:
367
+            ostream << error;
368
+    }
369
+    ostream << ":\n";
370
+
371
+    ostream << message;
372
+
373
+    debug_callback()(ostream.str());
374
+}
375
+
376
+
377
+/// Guards
378
+
379
+
380
+#endif
0 381
new file mode 100644
... ...
@@ -0,0 +1,29 @@
1
+#ifdef GLBACKEND_GLFW
2
+
3
+
4
+#include <GLFW/glfw3.h>
5
+
6
+
7
+constexpr auto width = 640;
8
+constexpr auto height = 480;
9
+
10
+
11
+int main()
12
+{
13
+    if (!glfwInit())
14
+        return 1;
15
+    auto * window = glfwCreateWindow(
16
+        width, height,
17
+        "baseline_glfw",
18
+        nullptr, nullptr
19
+    );
20
+    if (!window)
21
+        return 1;
22
+    glfwDestroyWindow(window);
23
+    glfwTerminate();
24
+}
25
+
26
+
27
+#else
28
+int main() {}
29
+#endif
0 30
new file mode 100644
... ...
@@ -0,0 +1,17 @@
1
+#ifdef GLBACKEND_GLFW
2
+
3
+
4
+#include <glbackend_glfw.hpp>
5
+
6
+#include "common/run.hpp"
7
+
8
+
9
+int main()
10
+{
11
+    GLBACKEND_TESTS_COMMON_RUN(GLBackendGLFW)
12
+}
13
+
14
+
15
+#else
16
+int main() {}
17
+#endif