Browse code

Add load support

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

... ...
@@ -13,8 +13,8 @@ LDLIBS    += -lglfw -lGLEW -lGL
13 13
 CXXFLAGS += -g
14 14
 
15 15
 ## Files
16
-SRCS =
17
-HDRS =
16
+SRCS = shader.cpp
17
+HDRS = shader.hpp
18 18
 
19 19
 ## Standard targets
20 20
 
... ...
@@ -2,8 +2,26 @@
2 2
 
3 3
 A C++11 helper for [OpenGL][] >=2.0 [shaders][Shader].
4 4
 
5
+`glshader` can load an arbitrary number of [shaders][Shader] with the type
6
+autodetected by the file extension (using the same rules as the [OpenGL /
7
+OpenGL ES Reference Compiler][]). For example:
8
+
9
+```cpp
10
+Shader shader({
11
+    "test.vert",
12
+    "test.frag",
13
+});
14
+shader.use();
15
+```
16
+
17
+Errors are handled by throwing [`std::runtime_error`][] with a [`what()`][]
18
+that returns helpful context as well as the OpenGL info log where appropriate.
19
+See the [tests](#tests) source for an overview of (some of) the generated error
20
+messages.
21
+
5 22
 [OpenGL]: https://www.opengl.org
6 23
 [Shader]: https://www.khronos.org/opengl/wiki/Shader
24
+[OpenGL / OpenGL ES Reference Compiler]: https://www.khronos.org/opengles/sdk/tools/Reference-Compiler/
7 25
 [`std::runtime_error`]: https://en.cppreference.com/w/cpp/error/runtime_error
8 26
 [`what()`]: https://en.cppreference.com/w/cpp/error/exception/what
9 27
 
10 28
new file mode 100644
... ...
@@ -0,0 +1,261 @@
1
+#include "shader.hpp"
2
+
3
+#include <vector>
4
+#include <string>
5
+#include <fstream>
6
+#include <stdexcept>
7
+#include <iostream>
8
+
9
+#include <GL/glew.h>
10
+
11
+#include "str.hpp"
12
+
13
+
14
+static void checked_action(
15
+    void (glAction)(
16
+        GLuint object
17
+    ),
18
+    GLuint object,
19
+    GLenum status_enum,
20
+    void (glGetObjectiv)(
21
+        GLuint object, GLenum pname, GLint *params
22
+    ),
23
+    void (glGetObjectInfoLog)(
24
+        GLuint object, GLsizei maxLength, GLsizei *length, GLchar *infoLog
25
+    ),
26
+    std::string const & what
27
+)
28
+{
29
+    // Perform action.
30
+    glAction(object);
31
+
32
+    // Check status.
33
+    GLint status;
34
+    glGetObjectiv(object, status_enum, &status);
35
+    if (status)
36
+        return;
37
+
38
+    // Get info log length.
39
+    GLint info_log_length;
40
+    glGetObjectiv(object, GL_INFO_LOG_LENGTH, &info_log_length);
41
+
42
+    // Get info log content.
43
+    std::string info_log;
44
+    if (info_log_length) {
45
+        std::vector<char> info_log_buf(info_log_length);
46
+        glGetObjectInfoLog(object, info_log_length, nullptr, &info_log_buf[0]);
47
+        info_log = STR("\n" << &info_log[0]);
48
+    }
49
+
50
+    // Throw.
51
+    throw std::runtime_error{STR(what << info_log)};
52
+}
53
+
54
+
55
+static GLenum shader_type(std::string type_str)
56
+{
57
+    return
58
+        type_str == "vert" ? GL_VERTEX_SHADER :
59
+        type_str == "tesc" ? GL_TESS_CONTROL_SHADER :
60
+        type_str == "tese" ? GL_TESS_EVALUATION_SHADER :
61
+        type_str == "geom" ? GL_GEOMETRY_SHADER :
62
+        type_str == "frag" ? GL_FRAGMENT_SHADER :
63
+        type_str == "comp" ? GL_COMPUTE_SHADER :
64
+        0;
65
+}
66
+
67
+
68
+Shader::Shader(std::vector<std::string> paths, std::string name)
69
+:
70
+    program_{0},
71
+    paths_{std::move(paths)},
72
+    name_{!name.empty()
73
+        ? std::move(name)
74
+        : STR_JOIN(", ", "'" << it << "'", paths_)
75
+    }
76
+{
77
+    new_();
78
+}
79
+
80
+
81
+Shader::Shader(Shader const & other)
82
+:
83
+    program_{0},
84
+    paths_  {other.paths_},
85
+    name_   {other.name_}
86
+{
87
+    new_();
88
+}
89
+
90
+
91
+Shader & Shader::operator=(Shader const & other)
92
+{
93
+    delete_();
94
+    program_ = 0;
95
+    paths_   = other.paths_;
96
+    name_    = other.name_;
97
+    new_();
98
+    return *this;
99
+}
100
+
101
+
102
+Shader::Shader(Shader && other)
103
+:
104
+    program_{std::move(other.program_)},
105
+    paths_  {std::move(other.paths_)},
106
+    name_   {std::move(other.name_)}
107
+{
108
+    other.program_ = 0;
109
+}
110
+
111
+
112
+Shader & Shader::operator=(Shader && other)
113
+{
114
+    delete_();
115
+    program_ = std::move(other.program_);
116
+    paths_   = std::move(other.paths_);
117
+    name_    = std::move(other.name_);
118
+    other.program_ = 0;
119
+    return *this;
120
+}
121
+
122
+
123
+Shader::~Shader()
124
+{
125
+    delete_();
126
+}
127
+
128
+
129
+Shader & Shader::use()
130
+{
131
+    glUseProgram(program_);
132
+    return *this;
133
+}
134
+
135
+
136
+Shader & Shader::validate()
137
+{
138
+    // Validate shader and check for errors.
139
+    checked_action(
140
+        glValidateProgram, program_,
141
+        GL_VALIDATE_STATUS, glGetProgramiv, glGetProgramInfoLog,
142
+        STR("Failed to validate shader program " << name_ << ".")
143
+    );
144
+    return *this;
145
+}
146
+
147
+
148
+void Shader::new_()
149
+{
150
+    try
151
+    {
152
+        // Create program.
153
+        program_ = glCreateProgram();
154
+        if (!program_)
155
+            throw std::runtime_error{STR(
156
+                "Failed to create shader program for " << name_ << "."
157
+            )};
158
+
159
+        // Label program.
160
+        glObjectLabel(GL_PROGRAM, program_, -1, name_.c_str());
161
+
162
+        // Process shader paths.
163
+        std::vector<GLuint> shaders;
164
+        for (auto const & path : paths_)
165
+        {
166
+            // Infer shader type from path extension.
167
+            // https://www.khronos.org/opengles/sdk/tools/Reference-Compiler/
168
+            auto pos = path.rfind(".");
169
+            if (pos == path.npos)
170
+                throw std::runtime_error{STR(
171
+                    "Failed to infer shader type of '" << path <<
172
+                    "' of shader program " << name_ << "; no file extension."
173
+                )};
174
+            auto type_str = path.substr(pos + 1);
175
+            auto type = shader_type(type_str);
176
+            if (!type)
177
+                throw std::runtime_error{STR(
178
+                    "Failed to infer shader type of '" << path <<
179
+                    "' of shader program " << name_ <<
180
+                    "; unknown file extension '" << type_str << "'."
181
+                )};
182
+
183
+            // Create, attach, and flag shader for deletion when detached.
184
+            auto shader = glCreateShader(type);
185
+            if (!shader)
186
+                throw std::runtime_error{STR(
187
+                    "Failed to create " << type_str << " shader for " << path
188
+                    << " of shader program " << name_ << "."
189
+                )};
190
+            glAttachShader(program_, shader);
191
+            glDeleteShader(shader);
192
+            shaders.push_back(shader);
193
+
194
+            // Label shader.
195
+            glObjectLabel(GL_SHADER, shader, -1, path.c_str());
196
+
197
+            // Set shader source.
198
+            std::ifstream source_file(path);
199
+            if (!source_file)
200
+                throw std::runtime_error{STR(
201
+                    "Failed to open " << type_str << " shader '" << path <<
202
+                    "' of shader program " << name_ << "."
203
+                )};
204
+            auto source_str = STR(source_file.rdbuf());
205
+            std::vector<char const *> sources = { source_str.c_str() };
206
+            glShaderSource(shader, sources.size(), &sources[0], nullptr);
207
+
208
+            // Compile shader and check for errors.
209
+            checked_action(
210
+                glCompileShader, shader,
211
+                GL_COMPILE_STATUS, glGetShaderiv, glGetShaderInfoLog,
212
+                STR(
213
+                    "Failed to compile " << type_str << " shader '" << path <<
214
+                    "' of shader program " << name_ << "."
215
+                )
216
+            );
217
+        }
218
+
219
+        // Link program and check for errors.
220
+        checked_action(
221
+            glLinkProgram, program_,
222
+            GL_LINK_STATUS, glGetProgramiv, glGetProgramInfoLog,
223
+            STR("Failed to link shader program " << name_ << ".")
224
+        );
225
+
226
+        // Detach shaders.
227
+        for (auto shader : shaders)
228
+            glDetachShader(program_, shader);
229
+    }
230
+    catch (...)
231
+    {
232
+        delete_();
233
+        throw;
234
+    }
235
+}
236
+
237
+
238
+void Shader::delete_()
239
+{
240
+    // Delete program (and detach and delete shaders).
241
+    glDeleteProgram(program_);
242
+}
243
+
244
+
245
+void Shader::ensure_current_(
246
+    std::string const & operation,
247
+    std::string const & name
248
+)
249
+{
250
+    GLuint current_program;
251
+    glGetIntegerv(GL_CURRENT_PROGRAM, (GLint *)&current_program);
252
+    if (current_program != program_) {
253
+        auto action = name.empty()
254
+            ? operation
255
+            : STR(operation << " '" << name << "'");
256
+        throw std::runtime_error{STR(
257
+            "Failed to " << action << " of shader program " << name_ <<
258
+            "; program is not current."
259
+        )};
260
+    }
261
+}
0 262
new file mode 100644
... ...
@@ -0,0 +1,47 @@
1
+#ifndef GLSHADER_SHADER_HPP
2
+#define GLSHADER_SHADER_HPP
3
+
4
+
5
+#include <vector>
6
+#include <string>
7
+
8
+#include <GL/glew.h>
9
+
10
+
11
+class Shader {
12
+public:
13
+
14
+    Shader(std::vector<std::string> paths, std::string name = "");
15
+    Shader(Shader &&);
16
+    Shader(Shader const &);
17
+    Shader & operator=(Shader &&);
18
+    Shader & operator=(Shader const &);
19
+    ~Shader();
20
+
21
+    Shader & use();
22
+    Shader & validate();
23
+
24
+private:
25
+
26
+    void new_();
27
+    void delete_();
28
+    void ensure_current_(
29
+        std::string const & operation,
30
+        std::string const & name = ""
31
+    );
32
+
33
+    GLuint program_;
34
+    std::vector<std::string> paths_;
35
+    std::string name_;
36
+};
37
+
38
+
39
+#ifdef NDEBUG
40
+    #define GLSHADER_ENSURE_CURRENT(OPERATION, NAME)
41
+#else
42
+    #define GLSHADER_ENSURE_CURRENT(OPERATION, NAME) \
43
+        ensure_current_(OPERATION, NAME)
44
+#endif
45
+
46
+
47
+#endif // GLSHADER_SHADER_HPP
0 48
new file mode 100644
... ...
@@ -0,0 +1,5 @@
1
+#version 110
2
+
3
+
4
+int main() {
5
+}
0 6
new file mode 100644
... ...
@@ -0,0 +1,5 @@
1
+#version 110
2
+
3
+
4
+void main() {
5
+}
0 6
new file mode 100644
... ...
@@ -0,0 +1,6 @@
1
+#version 110
2
+
3
+
4
+void main() {
5
+    gl_FragColor = vec4(1, 0, 0, 1);
6
+}
0 7
new file mode 100644
... ...
@@ -0,0 +1,6 @@
1
+#version 110
2
+
3
+
4
+void main() {
5
+    gl_Position = gl_Vertex;
6
+}
... ...
@@ -9,6 +9,7 @@
9 9
 #include <GLFW/glfw3.h>
10 10
 
11 11
 #include "str.hpp"
12
+#include "shader.hpp"
12 13
 
13 14
 
14 15
 static auto debugMessageDisable = false;
... ...
@@ -40,6 +41,47 @@ static auto debugMessageDisable = false;
40 41
 
41 42
 
42 43
 void test() {
44
+    EXPECT_EXCEPTION(
45
+        Shader({ "bad_noextension" }),
46
+        "Failed to infer shader type of 'bad_noextension' of shader program 'bad_noextension'; no file extension."
47
+    )
48
+    EXPECT_EXCEPTION(
49
+        Shader({ "bad.unknownextension" }),
50
+        "Failed to infer shader type of 'bad.unknownextension' of shader program 'bad.unknownextension'; unknown file extension 'unknownextension'."
51
+    )
52
+    EXPECT_EXCEPTION(
53
+        Shader({ "bad_nonexistent.vert" }),
54
+        "Failed to open vert shader 'bad_nonexistent.vert' of shader program 'bad_nonexistent.vert'."
55
+    )
56
+    EXPECT_EXCEPTION(
57
+        Shader({ "bad_compile.vert" }),
58
+        "Failed to compile vert shader 'bad_compile.vert' of shader program 'bad_compile.vert'."
59
+        // "0:4(5): error: main() must return void"
60
+        // "0:4(1): error: function `main' has non-void return type int, but no return statement"
61
+    )
62
+    EXPECT_EXCEPTION(
63
+        Shader({ "bad_link.vert", "good.frag" }),
64
+        "Failed to link shader program 'bad_link.vert', 'good.frag'."
65
+        // "error: vertex shader does not write to `gl_Position'. "
66
+    )
67
+    {
68
+        Shader shader{std::move(Shader({
69
+            "good.vert",
70
+            "good.frag",
71
+        }))};
72
+        shader.use();
73
+        shader = Shader({
74
+            "good.vert",
75
+            "good.frag",
76
+        });
77
+        shader.use();
78
+    }
79
+    Shader shader({
80
+        "good.vert",
81
+        "good.frag",
82
+    });
83
+    shader.use();
84
+    shader.validate();
43 85
 }
44 86
 
45 87