Browse code

Add create

Robert Cranston authored on 16/05/2020 11:49:30
Showing 9 changed files

... ...
@@ -8,6 +8,122 @@ A [C++11][] [OpenGL][] \>=[2.0][] [shader][] library.
8 8
 [2.0]: https://en.wikipedia.org/wiki/OpenGL#Version_history
9 9
 [shader]: https://www.khronos.org/opengl/wiki/Shader
10 10
 
11
+## Usage
12
+
13
+Overview of usage:
14
+
15
+```cpp
16
+#include <GL/glew.h>
17
+
18
+#include <glshader.hpp>
19
+
20
+
21
+int main()
22
+{
23
+    // Global settings.
24
+    Shader::root("assets/shaders");
25
+
26
+    // Create.
27
+    auto player = Shader({
28
+        "player.vert",
29
+        "player.frag", "lighting.frag",
30
+    });
31
+}
32
+```
33
+
34
+### Errors
35
+
36
+Errors are handled by throwing [`std::runtime_error`][] with a [`what()`][]
37
+that returns helpful context, including operating system messages and the
38
+OpenGL info log where appropriate.
39
+
40
+If OpenGL \>=4.3 or the extension [`GL_KHR_debug`][] is available,
41
+[`glObjectLabel`][] is used to improve debug messages provided to any callback
42
+registered with [`glDebugMessageCallback`][].
43
+
44
+Se [`tests/glshader.cpp`][] for an overview of some of the handled errors.
45
+(Note that the tests are written for compatibility and convenience and do not
46
+necessarily reflect current best practices.)
47
+
48
+[`std::runtime_error`]: https://en.cppreference.com/w/cpp/error/runtime_error
49
+[`what()`]: https://en.cppreference.com/w/cpp/error/exception/what
50
+[`GL_KHR_debug`]: https://www.khronos.org/registry/OpenGL/extensions/KHR/KHR_debug.txt
51
+[`glObjectLabel`]: https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glObjectLabel.xhtml
52
+[`glDebugMessageCallback`]: https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glDebugMessageCallback.xhtml
53
+[`tests/glshader.cpp`]: tests/glshader.cpp
54
+
55
+### Lifetime
56
+
57
+No default constructor is defined. This, together with a throwing constructor,
58
+means that a (non-moved-from) `Shader` object always corresponds to a valid
59
+OpenGL shader object.
60
+
61
+At destruction, the underlying OpenGL shader program is deleted, but not
62
+explicitly unbound. Note that the behavior of [deletion unbinding][] depends on
63
+the OpenGL version. It is recommended to always consider the underlying OpenGL
64
+shader program to be invalid after destruction.
65
+
66
+Copying (creating a new identical underlying OpenGL shader object) or move
67
+assigning (deleting the underlying OpenGL shader object of the moved-into
68
+`Shader` object) would probably be a (performance) bug and is disabled. I.e.
69
+`Shader` is a move(-constructible)-only type.
70
+
71
+[deletion unbinding]: https://www.khronos.org/opengl/wiki/OpenGL_Object#Deletion_unbinding
72
+
73
+### Loading files
74
+
75
+An arbitrary number of files can be loaded by giving them as arguments to the
76
+`Shader(Shader::Paths const & paths)` constructor.
77
+
78
+`Shader::Paths` is an alias for `std::set<std::string>`. `std::set` is used
79
+over e.g. `std::vector` to facilitate deduplicating shaders based on paths,
80
+even when the paths are given in different orders. `std::set` is used over
81
+`std::unordered_set` to retain the same ordering in error messages as in the
82
+creation for easy identification.
83
+
84
+The [shader stage][] is automatically detected by the file extension using the
85
+same rules as the [OpenGL / OpenGL ES Reference Compiler][]:
86
+
87
+-   `.vert`: vertex
88
+-   `.tesc`: tessellation control
89
+-   `.tese`: tessellation evaluation
90
+-   `.geom`: geometry
91
+-   `.frag`: fragment
92
+-   `.comp`: compute
93
+
94
+If multiple files are provided for the same stage they are compiled separately
95
+and linked into the final shader program.
96
+
97
+The value passed to `Shader::root(std::string const & root)` is prepended, with
98
+a separating `"/"` if non-empty, to all paths (defaults to `""`).
99
+
100
+[shader stage]: https://www.khronos.org/opengl/wiki/Shader#Stages
101
+[OpenGL / OpenGL ES Reference Compiler]: https://www.khronos.org/opengles/sdk/tools/Reference-Compiler/
102
+
103
+## Dependencies
104
+
105
+Public (interface):
106
+
107
+-   [OpenGL][], system (e.g. [`libgl1-mesa-dev`][]).
108
+-   [OpenGL Extension Wrangler (GLEW)][], system (e.g. [`libglew-dev`][]).
109
+
110
+Private (build):
111
+
112
+-   [`str`][], downloaded as part of the CMake configure step.
113
+
114
+Private (tests):
115
+
116
+-   [GLFW][], system (e.g. [`libglfw3-dev`][]).
117
+-   [`gltest`][], downloaded as part of the CMake configure step.
118
+
119
+[OpenGL Extension Wrangler (GLEW)]: http://glew.sourceforge.net
120
+[GLFW]: https://www.glfw.org
121
+[`libgl1-mesa-dev`]: https://packages.debian.org/search?keywords=libgl1-mesa-dev
122
+[`libglew-dev`]: https://packages.debian.org/search?keywords=libglew-dev
123
+[`libglfw3-dev`]: https://packages.debian.org/search?keywords=libglfw3-dev
124
+[`str`]: https://git.rcrnstn.net/rcrnstn/str
125
+[`gltest`]: https://git.rcrnstn.net/rcrnstn/gltest
126
+
11 127
 ## Build system
12 128
 
13 129
 This project supports [CMake][] and uses [`cmake-common`][]. There are several
14 130
new file mode 100644
... ...
@@ -0,0 +1,7 @@
1
+#version 110
2
+
3
+
4
+void main()
5
+{
6
+    gl_FragColor = vec4(1.0, 0.5, 0.25, 1.0);
7
+}
0 8
new file mode 100644
... ...
@@ -0,0 +1,7 @@
1
+#version 110
2
+
3
+
4
+void main()
5
+{
6
+    gl_Position = gl_Vertex;
7
+}
0 8
new file mode 100644
... ...
@@ -0,0 +1,7 @@
1
+#version 110
2
+
3
+
4
+int main()
5
+{
6
+    return 0;
7
+}
0 8
new file mode 100644
... ...
@@ -0,0 +1,6 @@
1
+#version 110
2
+
3
+
4
+void main()
5
+{
6
+}
0 7
new file mode 100644
1 8
Binary files /dev/null and b/assets/tests/frame.data differ
... ...
@@ -0,0 +1,51 @@
1
+#ifndef GLSHADER_SHADER_HPP_
2
+#define GLSHADER_SHADER_HPP_
3
+
4
+
5
+#include <set>
6
+#include <string>
7
+
8
+#include <GL/glew.h>
9
+
10
+
11
+class Shader
12
+{
13
+public:
14
+
15
+    using Paths = std::set<std::string>;
16
+
17
+    explicit Shader(Paths const & paths);
18
+    virtual ~Shader();
19
+    Shader(Shader &&) noexcept;
20
+    Shader(Shader const &) = delete;
21
+    Shader & operator=(Shader &&) = delete;
22
+    Shader & operator=(Shader const &) = delete;
23
+
24
+    static void root(std::string const & root);
25
+
26
+    GLuint program() const;
27
+
28
+protected:
29
+
30
+    GLuint             program_;
31
+    std::string        program_name_;
32
+    std::string static root_;
33
+};
34
+
35
+
36
+// Inline definitions.
37
+
38
+#define GLSHADER_SET_(TYPE, NAME) \
39
+    inline void Shader::NAME(TYPE const & NAME) \
40
+    { \
41
+        NAME##_ = NAME; \
42
+    }
43
+GLSHADER_SET_(std::string, root)
44
+
45
+inline GLuint Shader::program() const
46
+{
47
+    return program_;
48
+}
49
+
50
+
51
+#endif // GLSHADER_SHADER_HPP_
... ...
@@ -0,0 +1,218 @@
1
+#include <glshader.hpp>
2
+
3
+#include <algorithm>
4
+#include <array>
5
+#include <cerrno>
6
+#include <cstring>
7
+#include <fstream>
8
+#include <memory>
9
+#include <stdexcept>
10
+#include <string>
11
+#include <utility>
12
+#include <vector>
13
+
14
+#include <GL/glew.h>
15
+
16
+#include <str.hpp>
17
+
18
+
19
+// NOLINTNEXTLINE
20
+#define GLSHADER_INIT_(NAME, INIT) decltype(NAME) NAME INIT;
21
+GLSHADER_INIT_(Shader::root_, {})
22
+
23
+
24
+template<typename Type>
25
+static Type get_integer_(GLenum name, bool supported = true)
26
+{
27
+    auto data = GLint{};
28
+    if (supported)
29
+        glGetIntegerv(name, &data);
30
+    return (Type)data;
31
+}
32
+
33
+
34
+static void info_log_action_(
35
+    std::string const & error,
36
+    void (action)(GLuint object),
37
+    GLuint object,
38
+    GLenum status_enum,
39
+    void (GLAPIENTRY * getObjectiv)(
40
+        GLuint object, GLenum pname, GLint * params
41
+    ),
42
+    void (GLAPIENTRY * getObjectInfoLog)(
43
+        GLuint object, GLsizei max_length, GLsizei * length, GLchar * info_log
44
+    )
45
+)
46
+{
47
+    // Perform action.
48
+    action(object);
49
+
50
+    // Check status.
51
+    auto status = GLint{};
52
+    getObjectiv(object, status_enum, &status);
53
+    if (status)
54
+        return;
55
+
56
+    // Get info log length.
57
+    auto info_log_length = GLint{};
58
+    getObjectiv(object, GL_INFO_LOG_LENGTH, &info_log_length);
59
+
60
+    // Get info log content.
61
+    // NOLINTNEXTLINE
62
+    auto info_log = std::unique_ptr<GLchar[]>(new GLchar[info_log_length]);
63
+    if (info_log_length)
64
+        getObjectInfoLog(object, info_log_length, nullptr, &info_log[0]);
65
+
66
+    // Throw.
67
+    throw std::runtime_error{STR(
68
+        error << (!info_log_length ? "." : STR(":\n" << &info_log[0]))
69
+    )};
70
+}
71
+
72
+
73
+Shader::Shader(Paths const & paths)
74
+:
75
+    program_{0},
76
+    program_name_{STR(
77
+        "shader program " << STR_JOIN(", ", it, "'" << it << "'", paths)
78
+    )}
79
+{
80
+    // Get label limits.
81
+    static auto const max_label_length = get_integer_<GLsizei>(
82
+        GL_MAX_LABEL_LENGTH, GLEW_VERSION_4_3 || GLEW_KHR_debug
83
+    );
84
+
85
+    try
86
+    {
87
+        // Create program.
88
+        program_ = glCreateProgram();
89
+        if (!program_)
90
+            throw std::runtime_error{STR(
91
+                "Failed to create " << program_name_ << "."
92
+            )};
93
+
94
+        // Label program.
95
+        if (GLEW_VERSION_4_3 || GLEW_KHR_debug)
96
+            glObjectLabel(
97
+                GL_PROGRAM,
98
+                program_,
99
+                std::min(max_label_length, (GLsizei)program_name_.length()),
100
+                program_name_.c_str()
101
+            );
102
+
103
+        // Process shader paths.
104
+        auto shaders = std::vector<GLuint>{};
105
+        shaders.reserve(paths.size());
106
+        for (auto const & path : paths)
107
+        {
108
+            // Set shader name.
109
+            auto const shader_name = STR(
110
+                "shader '" << path << "' of " << program_name_
111
+            );
112
+
113
+            // Infer shader type from path extension.
114
+            auto const type_error = STR(
115
+                "Failed to infer type of " << shader_name
116
+            );
117
+            auto const type_pos = path.rfind('.');
118
+            if (type_pos == path.npos)
119
+                throw std::runtime_error{STR(
120
+                    type_error << "; " <<
121
+                    "no file extension."
122
+                )};
123
+            auto const type_name = path.substr(type_pos + 1);
124
+            auto const type =
125
+                type_name == "vert" ? GL_VERTEX_SHADER :
126
+                type_name == "tesc" ? GL_TESS_CONTROL_SHADER :
127
+                type_name == "tese" ? GL_TESS_EVALUATION_SHADER :
128
+                type_name == "geom" ? GL_GEOMETRY_SHADER :
129
+                type_name == "frag" ? GL_FRAGMENT_SHADER :
130
+                type_name == "comp" ? GL_COMPUTE_SHADER :
131
+                GLenum{0};
132
+            if (!type)
133
+                throw std::runtime_error{STR(
134
+                    type_error << "; " <<
135
+                    "unknown file extension '" << type_name << "'."
136
+                )};
137
+
138
+            // Create, attach, and flag shader for deletion when detached.
139
+            auto const shader = glCreateShader(type);
140
+            if (!shader)
141
+                throw std::runtime_error{STR(
142
+                    "Failed to create " << type_name << " shader for " <<
143
+                    shader_name << "."
144
+                )};
145
+            shaders.push_back(shader);
146
+            glAttachShader(program_, shader);
147
+            glDeleteShader(shader);
148
+
149
+            // Label shader.
150
+            if (GLEW_VERSION_4_3 || GLEW_KHR_debug)
151
+                glObjectLabel(
152
+                    GL_SHADER,
153
+                    shader,
154
+                    std::min(max_label_length, (GLsizei)shader_name.length()),
155
+                    shader_name.c_str()
156
+                );
157
+
158
+            // Set shader source.
159
+            auto const source_error = STR("Failed to source " << shader_name);
160
+            auto path_full = path;
161
+            if (!root_.empty())
162
+                path_full = STR(root_ << "/" << path_full);
163
+            auto source_istream = std::ifstream{path_full};
164
+            if (!source_istream)
165
+                throw std::runtime_error{STR(
166
+                    source_error << "; " <<
167
+                    "could not open file '" << path_full << "':\n" <<
168
+                    std::strerror(errno)
169
+                )};
170
+            auto const source = STR(source_istream.rdbuf());
171
+            auto const sources = std::array<char const *, 1>{{
172
+                source.c_str()
173
+            }};
174
+            glShaderSource(shader, sources.size(), &sources[0], nullptr);
175
+
176
+            // Compile shader.
177
+            info_log_action_(
178
+                STR("Failed to compile " << shader_name),
179
+                glCompileShader, shader,
180
+                GL_COMPILE_STATUS, glGetShaderiv, glGetShaderInfoLog
181
+            );
182
+        }
183
+
184
+        // Link program.
185
+        info_log_action_(
186
+            STR("Failed to link " << program_name_),
187
+            glLinkProgram, program_,
188
+            GL_LINK_STATUS, glGetProgramiv, glGetProgramInfoLog
189
+        );
190
+
191
+        // Detach shaders.
192
+        for (auto const & shader : shaders)
193
+            glDetachShader(program_, shader);
194
+    }
195
+    catch (...)
196
+    {
197
+        // Delete program (and detach and delete shaders).
198
+        if (program_)
199
+            glDeleteProgram(program_);
200
+        throw;
201
+    }
202
+}
203
+
204
+
205
+Shader::Shader(Shader && other) noexcept
206
+:
207
+    program_     {other.program_},
208
+    program_name_{std::move(other.program_name_)}
209
+{
210
+    other.program_ = 0;
211
+}
212
+
213
+
214
+Shader::~Shader()
215
+{
216
+    if (program_)
217
+        glDeleteProgram(program_);
218
+}
... ...
@@ -0,0 +1,56 @@
1
+#include <string>
2
+
3
+#include <GL/glew.h>
4
+
5
+#include <glshader.hpp>
6
+#include <gltest.hpp>
7
+
8
+
9
+GLTEST(2, 0, 640, 480, glshader)
10
+{
11
+    gltest_root("assets/tests");
12
+    Shader::root("assets/shaders");
13
+
14
+    // Create.
15
+    GLTEST_EXPECT_EXCEPTION(false,
16
+        Shader({"tests/create_noextension"}),
17
+        "Failed to infer type of shader 'tests/create_noextension' of shader program 'tests/create_noextension'; "
18
+        "no file extension."
19
+    )
20
+    GLTEST_EXPECT_EXCEPTION(false,
21
+        Shader({"tests/create.unknownextension"}),
22
+        "Failed to infer type of shader 'tests/create.unknownextension' of shader program 'tests/create.unknownextension'; "
23
+        "unknown file extension 'unknownextension'."
24
+    )
25
+    GLTEST_EXPECT_EXCEPTION(true,
26
+        Shader({"tests/create_nonexistent.vert"}),
27
+        "Failed to source shader 'tests/create_nonexistent.vert' of shader program 'tests/create_nonexistent.vert'; "
28
+        "could not open file 'assets/shaders/tests/create_nonexistent.vert':\n"
29
+        "No such file or directory"
30
+    )
31
+    GLTEST_EXPECT_EXCEPTION(true,
32
+        Shader({"tests/create_bad_compile.vert"}),
33
+        "Failed to compile shader 'tests/create_bad_compile.vert' of shader program 'tests/create_bad_compile.vert':\n"
34
+        "0:4(5): error: main() must return void\n"
35
+    )
36
+    GLTEST_EXPECT_EXCEPTION(true,
37
+        Shader({"tests/create_bad_link.vert"}),
38
+        "Failed to link shader program 'tests/create_bad_link.vert':\n"
39
+        "error: vertex shader does not write to `gl_Position'. \n"
40
+    )
41
+
42
+    auto all = Shader({
43
+        "tests/all.vert",
44
+        "tests/all.frag",
45
+    });
46
+
47
+    constexpr auto size = 0.5F;
48
+    glBegin(GL_TRIANGLE_STRIP);
49
+    glVertex3f(-size, -size, 0.0F);
50
+    glVertex3f(-size, +size, 0.0F);
51
+    glVertex3f(+size, -size, 0.0F);
52
+    glVertex3f(+size, +size, 0.0F);
53
+    glEnd();
54
+
55
+    GLTEST_EXPECT_FRAME("frame.data")
56
+}