Browse code

Add GLBackend, GLBackendDefault

Robert Cranston authored on 12/10/2021 00:02:06
Showing 6 changed files

... ...
@@ -13,8 +13,25 @@ project(glbackend
13 13
 ## Main target
14 14
 add_library(${PROJECT_NAME})
15 15
 
16
+## Variables
17
+set(GLBACKEND_DEPENDENCIES)
18
+set(GLBACKEND_DEFINITIONS)
19
+
16 20
 ## Common
17 21
 include(common.cmake)
18 22
 common(
19 23
     CXX_STANDARD 11
24
+    FETCHCONTENT
25
+        https://git.rcrnstn.net/rcrnstn/glbase
26
+        https://git.rcrnstn.net/rcrnstn/cxx-str
27
+    DEPENDENCIES_PRIVATE
28
+        glbase
29
+        cxx-str
30
+        ${GLBACKEND_DEPENDENCIES}
31
+    DEPENDENCIES_TESTS
32
+        glbase
33
+        cxx-str
34
+        ${GLBACKEND_DEPENDENCIES}
35
+    DEFINITIONS
36
+        ${GLBACKEND_DEFINITIONS}
20 37
 )
... ...
@@ -2,12 +2,327 @@
2 2
 
3 3
 A [C++11][]/[OpenGL][] \>=[1.0][] [backend][] library.
4 4
 
5
+Currently supported backends:
6
+
7
+| Name | Category | Define | Include | Class |
8
+| --   | --       | --     | --      | --    |
9
+
5 10
 [`glbackend`]: https://git.rcrnstn.net/rcrnstn/glbackend
6 11
 [C++11]: https://en.wikipedia.org/wiki/C++11
7 12
 [OpenGL]: https://en.wikipedia.org/wiki/OpenGL
8 13
 [1.0]: https://en.wikipedia.org/wiki/OpenGL#Version_history
9 14
 [backend]: https://www.khronos.org/opengl/wiki/Related_toolkits_and_APIs#OpenGL_initialization
10 15
 
16
+## Usage
17
+
18
+### Overview
19
+
20
+```cpp
21
+#include <glbackend_default.hpp>
22
+
23
+
24
+constexpr auto size = GLBackend::Size{640, 480};
25
+
26
+
27
+int main()
28
+{
29
+    // Create.
30
+    auto backend = GLBackendDefault("Default", size);
31
+
32
+    // Render loop.
33
+    backend.callback_render([&]()
34
+    {
35
+        glClearColor(1.0F, 0.0F, 0.0F, 0.5F);
36
+        glClear(GL_COLOR_BUFFER_BIT);
37
+        if (backend.key("Enter"))
38
+            backend.tga_write("screenshot.tga");
39
+        if (backend.key("Escape"))
40
+            backend.running(false);
41
+    });
42
+    backend.run();
43
+}
44
+```
45
+
46
+### Global settings
47
+
48
+#### Errors
49
+
50
+After backend construction (see [Lifetime](#lifetime)) errors are non-fatal and
51
+are reported to the output configured with
52
+
53
+```
54
+using OutputCallback = std::function<void(std::string const & message)>;
55
+
56
+static std::ostream * output_stream(std::ostream * output_stream);
57
+static OutputCallback output_callback(OutputCallback output_callback);
58
+```
59
+
60
+(defaults to `std::cerr` and empty, respectively). The old values are returned.
61
+
62
+If the backend is constructed with `debug = true` (see [Lifetime](#lifetime))
63
+and OpenGL \>=4.3 or the extension [`GL_KHR_debug`][] is available, OpenGL
64
+debug messages with a severity other than `GL_DEBUG_SEVERITY_NOTIFICATION` are
65
+also reported using the same output. The messages received from the OpenGL
66
+implementation are modified slightly to remove trailing whitespace and double
67
+quotes surrounding file names in lines starting with *"path":line*.
68
+
69
+[`GL_KHR_debug`]: https://www.khronos.org/registry/OpenGL/extensions/KHR/KHR_debug.txt
70
+
71
+#### File system
72
+
73
+If the value passed to
74
+
75
+```
76
+static std::string directory(std::string directory);
77
+```
78
+
79
+is non-empty it is prepended, with a separating `"/"`, to all relative paths
80
+(defined as paths not starting with `"/"`) given as arguments to other
81
+functions. The old value is returned.
82
+
83
+### Lifetime
84
+
85
+Constructors [`throw`][] [`std::runtime_error`][] on failure, with a `what()`
86
+describing the error.
87
+
88
+To use a backend, instantiate the appropriate `Backend`-derived class. Several
89
+objects can be instantiated, using the same or different underlying backends.
90
+
91
+All the provided `Backend`-derived classes' constructors take the following
92
+arguments, in this order:
93
+
94
+-   Window-related:
95
+    -   `std::string const & title`: The window title.
96
+    -   `int width`: The window width.
97
+    -   `int height`: The window height.
98
+    -   `bool fullscreen = false`: Whether or not to create a fullscreen
99
+        window.
100
+    -   `bool transparent = false`: Whether or not to use the alpha channel of
101
+        the default framebuffer to blend with the background. If this is
102
+        enabled, window decorations are disabled and the window is kept on top
103
+        of other windows.
104
+-   [Context][]-related:
105
+    -   `bool debug = false`: If `false`, creates a [no error][] context. If
106
+        `true`, creates a [debug context][], and enables [debug output][].
107
+    -   `int samples = -1`: The number of samples used in
108
+        [mutlisample][multisampling] anti-aliasing (MSAA) of the default
109
+        framebuffer (`-1` lets the implementation decide).
110
+    -   `int version_major = -1`: The major version with which the OpenGL
111
+        [context][] must be compatible (`-1` lets the implementation decide).
112
+    -   `int version_minor = -1`: The minor version with which the OpenGL
113
+        [context][] must be compatible (`-1` lets the implementation decide).
114
+
115
+The relevant underlying backend is automatically initialized at instantiation.
116
+If no other code has initialized that backend, it is guaranteed to be
117
+terminated when the last instance is destroyed. It is backend-specific if the
118
+backend is terminated even if other code has initialized it.
119
+
120
+The provided `Backend`s create double buffered, 32 bit color (including alpha),
121
+24 bit depth, 8 bit stencil OpenGL [context][]s. If the requested version is
122
+\>=3.2 a [forward compatible][] core context is created.
123
+
124
+The `Backend`s are move(-constructible)-only types.
125
+
126
+[`throw`]: https://en.cppreference.com/w/cpp/language/throw
127
+[`std::runtime_error`]: https://en.cppreference.com/w/cpp/error/runtime_error
128
+[context]: https://www.khronos.org/opengl/wiki/OpenGL_Context
129
+[debug context]: https://www.khronos.org/opengl/wiki/Debug_Context
130
+[debug output]: https://www.khronos.org/opengl/wiki/Debug_Output
131
+[no error]: https://www.khronos.org/opengl/wiki/OpenGL_Context#No_error_contexts
132
+[multisampling]: https://www.khronos.org/opengl/wiki/Multisampling
133
+[forward compatible]: https://www.khronos.org/opengl/wiki/OpenGL_Context#Forward_compatibility
134
+
135
+### Info
136
+
137
+```
138
+std::string info() const;
139
+```
140
+
141
+returns information about the underlying backend.
142
+
143
+### Context
144
+
145
+Each `Backend` instance has its own OpenGL [context][], which is made current
146
+at instantiation. If several `Backend`s are used,
147
+
148
+```
149
+void current();
150
+```
151
+
152
+can be called to make the context of a specific one current.
153
+
154
+### Render loop
155
+
156
+```
157
+void events();
158
+```
159
+
160
+handles events from the underlying backend.
161
+
162
+```
163
+void swap();
164
+```
165
+
166
+swaps the OpenGL default framebuffer.
167
+
168
+```
169
+bool running() const;
170
+bool running(bool running);
171
+```
172
+
173
+queries and sets, respectively, the window "running" state.
174
+
175
+```
176
+float time() const;
177
+float time(float time);
178
+```
179
+
180
+queries and sets, respectively, the current time, in seconds.
181
+
182
+```
183
+using Update = std::function<void(float t, float dt, bool final)>;
184
+using Render = std::function<void()>;
185
+
186
+Backend & update(Update const & update);
187
+Backend & render(Render const & render);
188
+```
189
+
190
+sets the `update` and `render` callbacks, respectively, used by
191
+
192
+```
193
+void run(float dt_max = 0.0F);
194
+```
195
+
196
+which is a convenience function that uses the member functions and callbacks
197
+described in this section to implement a render loop. `t` is the current time,
198
+which is set to `0.0F` at the start of `run`. If `dt_max` is `0.0F`, the
199
+`update` callback is called once for every frame, with `dt` holding the elapsed
200
+time. Otherwise, the `update` callback is called as many times as necessary to
201
+advance through the elapsed time while ensuring `dt` is never larger than
202
+`dt_max`. `final` is `true` if this is the final update of the frame. `render`
203
+is called at the end of each frame. Either of the callbacks can be unset.
204
+
205
+### Input and output
206
+
207
+```
208
+void lock(bool lock);
209
+```
210
+
211
+controls mouse locking. If `true` is passed the mouse is locked, which means it
212
+is hidden and prevented from leaving the window. Passing `false` restores the
213
+normal behavior.
214
+
215
+The input (keyboard and mouse) and output (framebuffer) state can be accessed
216
+in two different ways: registering callbacks and / or polling. The same
217
+function name is used for both methods (with function overloading). These are
218
+described below.
219
+
220
+#### Keyboard
221
+
222
+The
223
+
224
+```
225
+key
226
+```
227
+
228
+*callback / poll* takes as argument a `std::string const & key` and *is called
229
+when / returns `true` if* that key is pressed. Valid arguments are
230
+backend-specific but guaranteed to support:
231
+
232
+-   `"A"` through `"Z"`
233
+-   `"0"` through `"9"`
234
+-   `"Left"`, `"Right"`, `"Up"`, `"Down"`
235
+-   `"Enter"`, `"Escape"`, `"Tab"`, `"Backspace"`
236
+-   `"Control"`, `"Shift"`, `"Alt"`
237
+
238
+#### Mouse
239
+
240
+The
241
+
242
+```
243
+button
244
+```
245
+
246
+*callback / poll* takes as argument an `int button` and *is called when /
247
+returns `true` if* that button is pressed. Valid arguments are backend-specific
248
+but guaranteed to support `1`, `2`, and `3`.
249
+
250
+The
251
+
252
+```
253
+scroll
254
+```
255
+
256
+*callback / poll* takes as *argument / returns* `std::array<float, 2> scroll`.
257
+
258
+The
259
+
260
+```
261
+position
262
+```
263
+
264
+*callback / poll* takes as *argument / returns* `std::array<float, 2>
265
+position`.
266
+
267
+The
268
+
269
+```
270
+move
271
+```
272
+
273
+*callback / poll* takes as *argument / returns* `std::array<float, 2> move`.
274
+
275
+#### Framebuffer
276
+
277
+The
278
+
279
+```
280
+size
281
+```
282
+
283
+*callback / poll* takes as *argument / returns* `std::array<int, 2> size`.
284
+
285
+### Persisting frames
286
+
287
+There is basic support for writing frames to disk, useful for testing or
288
+screenshot functionality.
289
+
290
+Writing is done with
291
+
292
+```
293
+bool frame_tga_write(
294
+    std::string const & path
295
+) const;
296
+```
297
+
298
+As the name suggests, the frame is written as an uncompressed [BGRA][]
299
+[Truevision TGA][] image, selected because it is widely supported and has a
300
+trivial header and data layout. The function returns `true` on success, on
301
+failure an [error](#errors) is emitted and `false` is returned.
302
+
303
+```
304
+bool frame_tga_compare(
305
+    std::string const & path,
306
+    bool write_on_failed_read = false
307
+) const;
308
+```
309
+
310
+can be used to compare the current frame to one written previously. Note that a
311
+general TGA reader is not used; the comparison is done byte for byte, header
312
+included. If the given `path` cannot be read an [error](#errors) is emitted and
313
+if `write_on_failed_read` is `true` the current frame is instead written to
314
+`path`.
315
+
316
+`".tga"` is automatically added to the end of `path`. In addition, `directory`
317
+is applied if appropriate, see [File system](#file-system).
318
+
319
+No function is provided to read the frame without interacting with the file
320
+system. If this is required, simply use [`glReadPixels`][].
321
+
322
+[BGRA]: https://en.wikipedia.org/wiki/RGBA_color_model
323
+[Truevision TGA]: https://en.wikipedia.org/wiki/Truevision_TGA
324
+[`glReadPixels`]: https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glReadPixels.xhtml
325
+
11 326
 ## Dependencies
12 327
 
13 328
 System dependencies that need to be installed:
... ...
@@ -1,4 +1,4 @@
1
-/// Include guard
1
+/// Guards
2 2
 
3 3
 #ifndef GLBACKEND_HPP_
4 4
 #define GLBACKEND_HPP_
... ...
@@ -158,6 +158,6 @@ protected:
158 158
 };
159 159
 
160 160
 
161
-/// Include guard
161
+/// Guards
162 162
 
163 163
 #endif
164 164
new file mode 100644
... ...
@@ -0,0 +1,10 @@
1
+#ifndef GLBACKEND_DEFAULT_HPP_
2
+#define GLBACKEND_DEFAULT_HPP_
3
+
4
+
5
+#if 1
6
+    #error "No GLBackendDefault configured"
7
+#endif
8
+
9
+
10
+#endif
... ...
@@ -0,0 +1,308 @@
1
+/// Includes
2
+
3
+
4
+#include <glbackend.hpp>
5
+
6
+#include <algorithm>
7
+#include <cstddef>
8
+#include <exception>
9
+#include <iomanip>
10
+#include <regex>
11
+#include <sstream>
12
+#include <string>
13
+
14
+#include <glbase.hpp>
15
+
16
+// NOLINTNEXTLINE
17
+#define STR_EXCEPTION GLBase::Exception
18
+#include <str.hpp>
19
+
20
+
21
+/// Special member functions
22
+
23
+
24
+GLBackend::GLBackend()
25
+:
26
+    callback_update_{},
27
+    callback_render_{},
28
+    scroll_{},
29
+    position_{},
30
+    move_{},
31
+    size_{},
32
+    callback_key_{},
33
+    callback_button_{},
34
+    callback_scroll_{},
35
+    callback_position_{},
36
+    callback_move_{},
37
+    callback_size_{}
38
+{
39
+}
40
+
41
+
42
+void GLBackend::init_()
43
+{
44
+    #ifdef __GLEW_H__
45
+    if (auto error = glewInit())
46
+        STR_THROW(
47
+            "Failed to initialize GLEW:" << "\n" <<
48
+            glewGetErrorString(error)
49
+        );
50
+    #endif
51
+
52
+    if (debug() >= 1)
53
+    {
54
+        if (supported({4, 3}, "GL_KHR_debug"))
55
+        {
56
+            glEnable(GL_DEBUG_OUTPUT);
57
+            glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
58
+            glDebugMessageControl(
59
+                GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE,
60
+                0, nullptr,
61
+                GL_TRUE
62
+            );
63
+            glDebugMessageControl(
64
+                GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_NOTIFICATION,
65
+                0, nullptr,
66
+                GL_FALSE
67
+            );
68
+            glDebugMessageCallback(debug_gl_message_callback_, nullptr);
69
+        }
70
+    }
71
+}
72
+
73
+
74
+/// Render loop
75
+
76
+
77
+void GLBackend::run(float dt_fixed)
78
+{
79
+    auto t = time(0);
80
+    running(true);
81
+    while (events(), running())
82
+    {
83
+        if (callback_update_)
84
+        {
85
+            auto t_next = time();
86
+            auto dt = t_next - t;
87
+            if (dt_fixed != 0.0F)
88
+                dt = dt_fixed;
89
+            while (t + dt <= t_next)
90
+            {
91
+                callback_update_(t, dt, !(t + 2 * dt <= t_next));
92
+                t += dt;
93
+            }
94
+        }
95
+        if (callback_render_)
96
+            callback_render_();
97
+        swap();
98
+    }
99
+}
100
+
101
+
102
+/// Path
103
+
104
+
105
+GLBASE_GLOBAL(GLBackend::prefix_, {})
106
+
107
+
108
+/// TGA
109
+
110
+
111
+void GLBackend::tga_write(
112
+    Path const & path
113
+) const
114
+{
115
+    tga_().write(path_prefix_(path, prefix()));
116
+}
117
+
118
+
119
+bool GLBackend::tga_compare(
120
+    Path const & path,
121
+    bool         write_on_failed_read
122
+) const
123
+{
124
+    auto path_prefix = path_prefix_(path, prefix());
125
+    auto tga = tga_();
126
+    try
127
+    {
128
+        auto tga_read = TGA_::read(path_prefix);
129
+        return tga_read.data() == tga.data();
130
+    }
131
+    catch (std::exception const & exception)
132
+    {
133
+        if (!write_on_failed_read)
134
+            throw;
135
+        if (debug() >= 1)
136
+            debug_callback()(exception.what());
137
+        debug_callback()(STR("Writing TGA \"" << path_prefix << "\"."));
138
+        tga.write(path_prefix);
139
+        return false;
140
+    }
141
+}
142
+
143
+
144
+GLBackend::TGA_ GLBackend::tga_() const
145
+{
146
+    glFlush();
147
+    auto size = this->size();
148
+    auto data = std::vector<GLubyte>((4 * (size_t)size[0] * (size_t)size[1]));
149
+    glPixelStorei(GL_PACK_ALIGNMENT, 4);
150
+    glReadPixels(
151
+        0, 0,
152
+        size[0], size[1],
153
+        GL_BGRA, GL_UNSIGNED_BYTE,
154
+        data.data()
155
+    );
156
+    return TGA_(size, std::move(data));
157
+}
158
+
159
+
160
+/// Debug
161
+
162
+
163
+std::string GLBackend::debug_info() const
164
+{
165
+    auto ostream = std::ostringstream{};
166
+
167
+    #ifdef __GLEW_H__
168
+    // NOLINTNEXTLINE
169
+    #define GLBACKEND_INFO_GLEW_(NAME) \
170
+        {#NAME, (char const *)glewGetString(GLEW_##NAME)}
171
+    debug_info_(ostream, "GLEW", {
172
+        GLBACKEND_INFO_GLEW_(VERSION),
173
+    });
174
+    #endif
175
+
176
+    // NOLINTNEXTLINE
177
+    #define GLBACKEND_INFO_GL_(NAME) \
178
+        {#NAME, (char const *)glGetString(GL_##NAME)}
179
+    // NOLINTNEXTLINE
180
+    #define GLBACKEND_INFO_GL_FLAG_(NAME) \
181
+        { \
182
+            #NAME, \
183
+            (flags & (GLuint)GL_CONTEXT_FLAG_##NAME##_BIT) \
184
+                ? "TRUE" \
185
+                : "FALSE" \
186
+        }
187
+    // NOLINTNEXTLINE
188
+    #define GLBACKEND_INFO_GL_INTEGER_(NAME) \
189
+        {#NAME, std::to_string(integer(GL_##NAME)) }
190
+    auto const flags = (GLuint)integer(GL_CONTEXT_FLAGS);
191
+    debug_info_(ostream, "OpenGL", {
192
+        GLBACKEND_INFO_GL_(VENDOR),
193
+        GLBACKEND_INFO_GL_(RENDERER),
194
+        GLBACKEND_INFO_GL_(VERSION),
195
+        GLBACKEND_INFO_GL_(SHADING_LANGUAGE_VERSION),
196
+        GLBACKEND_INFO_GL_FLAG_(FORWARD_COMPATIBLE),
197
+        GLBACKEND_INFO_GL_FLAG_(DEBUG),
198
+        GLBACKEND_INFO_GL_FLAG_(ROBUST_ACCESS),
199
+        GLBACKEND_INFO_GL_FLAG_(NO_ERROR),
200
+        GLBACKEND_INFO_GL_INTEGER_(SAMPLE_BUFFERS),
201
+        GLBACKEND_INFO_GL_INTEGER_(SAMPLES),
202
+    });
203
+
204
+    return ostream.str();
205
+}
206
+
207
+
208
+void GLBackend::debug_info_(
209
+    std::ostream                                           & ostream,
210
+    std::string                                      const & category,
211
+    std::vector<std::pair<std::string, std::string>> const & values,
212
+    char                                                     fill
213
+)
214
+{
215
+    auto length_max = int{0};
216
+    for (auto const & value : values)
217
+        length_max = std::max(length_max, (int)value.first.length());
218
+    ostream << category << "\n";
219
+    for (auto const & value : values)
220
+        ostream
221
+            << "  "
222
+            << std::left << std::setw(length_max + 1 + 2) << std::setfill(fill)
223
+            << (value.first + " ")
224
+            << (" " + value.second)
225
+            << "\n";
226
+}
227
+
228
+
229
+void GLAPIENTRY GLBackend::debug_gl_message_callback_(
230
+    GLenum          source,
231
+    GLenum          type,
232
+    GLuint          id,
233
+    GLenum          severity,
234
+    GLsizei         length,
235
+    GLchar  const * message,
236
+    void    const * user_param
237
+)
238
+{
239
+    (void)length;
240
+    (void)user_param;
241
+
242
+    auto ostream = std::ostringstream{};
243
+    ostream << std::hex << std::showbase;
244
+
245
+    // https://www.khronos.org/opengl/wiki/Debug_Output#Message_Components
246
+    ostream << "GL debug message ";
247
+    // NOLINTNEXTLINE
248
+    #define GLBACKEND_CALLBACK_CASE_(CATEGORY, VALUE) \
249
+        case GL_DEBUG_##CATEGORY##_##VALUE: \
250
+            ostream << #VALUE; \
251
+            break;
252
+    switch(source)
253
+    {
254
+        GLBACKEND_CALLBACK_CASE_(SOURCE, API)
255
+        GLBACKEND_CALLBACK_CASE_(SOURCE, WINDOW_SYSTEM)
256
+        GLBACKEND_CALLBACK_CASE_(SOURCE, SHADER_COMPILER)
257
+        GLBACKEND_CALLBACK_CASE_(SOURCE, THIRD_PARTY)
258
+        GLBACKEND_CALLBACK_CASE_(SOURCE, APPLICATION)
259
+        GLBACKEND_CALLBACK_CASE_(SOURCE, OTHER)
260
+        default:
261
+            ostream << source;
262
+    }
263
+    ostream << " ";
264
+    switch(type)
265
+    {
266
+        GLBACKEND_CALLBACK_CASE_(TYPE, ERROR)
267
+        GLBACKEND_CALLBACK_CASE_(TYPE, DEPRECATED_BEHAVIOR)
268
+        GLBACKEND_CALLBACK_CASE_(TYPE, UNDEFINED_BEHAVIOR)
269
+        GLBACKEND_CALLBACK_CASE_(TYPE, PORTABILITY)
270
+        GLBACKEND_CALLBACK_CASE_(TYPE, PERFORMANCE)
271
+        GLBACKEND_CALLBACK_CASE_(TYPE, MARKER)
272
+        GLBACKEND_CALLBACK_CASE_(TYPE, PUSH_GROUP)
273
+        GLBACKEND_CALLBACK_CASE_(TYPE, POP_GROUP)
274
+        GLBACKEND_CALLBACK_CASE_(TYPE, OTHER)
275
+        default:
276
+            ostream << type;
277
+    }
278
+    ostream << " ";
279
+    switch(severity)
280
+    {
281
+        GLBACKEND_CALLBACK_CASE_(SEVERITY, HIGH)
282
+        GLBACKEND_CALLBACK_CASE_(SEVERITY, MEDIUM)
283
+        GLBACKEND_CALLBACK_CASE_(SEVERITY, LOW)
284
+        GLBACKEND_CALLBACK_CASE_(SEVERITY, NOTIFICATION)
285
+        default:
286
+            ostream << severity;
287
+    }
288
+    ostream << " ";
289
+    ostream << id;
290
+    ostream << ":\n";
291
+
292
+    ostream << debug_gl_message_(message);
293
+
294
+    debug_callback()(ostream.str());
295
+}
296
+
297
+
298
+std::string GLBackend::debug_gl_message_(std::string message)
299
+{
300
+    message.erase(message.find_last_not_of(" \n") + 1);
301
+
302
+    auto static const file_line = std::regex{R""(^"([^"]+)"(:[0-9]+.*))""};
303
+    auto match = std::smatch{};
304
+    if (std::regex_match(message, match, file_line))
305
+        message = match.str(1) + match.str(2);
306
+
307
+    return message;
308
+}
0 309
new file mode 100644
... ...
@@ -0,0 +1,45 @@
1
+#ifndef GLBACKEND_TESTS_COMMON_RUN_HPP_
2
+#define GLBACKEND_TESTS_COMMON_RUN_HPP_
3
+
4
+
5
+#include <iostream>
6
+#include <string>
7
+
8
+#include <glbase.hpp>
9
+#include <glbackend.hpp>
10
+
11
+// NOLINTNEXTLINE
12
+#define STR_EXCEPTION GLBase::Exception
13
+#include <str.hpp>
14
+
15
+
16
+
17
+#define GLBACKEND_TESTS_COMMON_RUN(GLBACKEND) \
18
+    run<GLBACKEND>(#GLBACKEND);
19
+
20
+
21
+constexpr auto size = std::array<int, 2>{640, 480};
22
+
23
+
24
+template<typename GLBackend>
25
+inline void run(std::string const & name)
26
+{
27
+    GLBackend::prefix("assets/tests");
28
+
29
+    auto backend = GLBackend(name, size);
30
+
31
+    std::cout << backend.debug_info() << std::endl;
32
+
33
+    backend.callback_render([&]()
34
+    {
35
+        glClearColor(1.0F, 0.0F, 0.0F, 0.5F);
36
+        glClear(GL_COLOR_BUFFER_BIT);
37
+        if (!backend.tga_compare(name + ".tga", true))
38
+            STR_THROW("Frame did not match expected frame.");
39
+        backend.running(false);
40
+    });
41
+    backend.run();
42
+}
43
+
44
+
45
+#endif