... | ... |
@@ -137,6 +137,9 @@ Additional issues that are not typically detected by [`glValidateProgram`][] |
137 | 137 |
are checked for: |
138 | 138 |
|
139 | 139 |
- Shader program not [current](#use). |
140 |
+- Uniforms not set with a call to [`uniform`](#uniforms), uniform blocks not |
|
141 |
+ set with a call to `uniform` or `uniform_buffer`. (OpenGL defaults them to |
|
142 |
+ zero unless initialized in the shader.) |
|
140 | 143 |
|
141 | 144 |
If the macro [`NDEBUG`][] is defined, the check is optimised out. Therefore, it |
142 | 145 |
is recommended to always call `validate()` before issuing draw calls. |
... | ... |
@@ -148,7 +151,7 @@ is recommended to always call `validate()` before issuing draw calls. |
148 | 151 |
### Use |
149 | 152 |
|
150 | 153 |
The shader needs to be [use][]d by calling `use()` before performing certain |
151 |
-other actions on it. |
|
154 |
+other actions on it, such as calling [`uniform`](#uniforms). |
|
152 | 155 |
|
153 | 156 |
If the macro [`NDEBUG`][] is not defined, an error is thrown if this is not the |
154 | 157 |
case. |
... | ... |
@@ -243,6 +246,29 @@ up once at application startup. |
243 | 246 |
[`glDrawBuffers`]: https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glDrawBuffers.xhtml |
244 | 247 |
[program interface query]: https://www.khronos.org/opengl/wiki/Program_Introspection#Interface_query |
245 | 248 |
|
249 |
+### Uniforms |
|
250 |
+ |
|
251 |
+[Uniform][]s are set with the template specialized `uniform(std::string const & |
|
252 |
+name, T const & value, bool required = true)`. If `required` is `true` an error |
|
253 |
+is thrown if the shader does not contain an active uniform with the given |
|
254 |
+`name`. If the macro [`NDEBUG`][] is not defined an error is thrown if the |
|
255 |
+value is of the wrong type. [`use`](#use) must be called before calling |
|
256 |
+`uniform`. |
|
257 |
+ |
|
258 |
+[uniform]: https://www.khronos.org/opengl/wiki/Uniform |
|
259 |
+ |
|
260 |
+#### User-defined uniforms |
|
261 |
+ |
|
262 |
+Support for e.g. third party mathematics libraries can be added by providing |
|
263 |
+the relevant template specializations of `uniform`. The macro |
|
264 |
+`GLSHADER_UNIFORM(TYPE, CODE)` may be used to ease this task. `TYPE` is the |
|
265 |
+type for which to provide the specialization and `CODE` is code that may use |
|
266 |
+the supplied variables `TYPE const & value` and `GLint const & location`, |
|
267 |
+probably as parameters to `glUniform*`. `GLSHADER_UNIFORM_DELETE(TYPE)` can be |
|
268 |
+used to [`delete`][] the specialization for a given type. |
|
269 |
+ |
|
270 |
+[`delete`]: https://en.cppreference.com/w/cpp/language/function#Deleted_functions |
|
271 |
+ |
|
246 | 272 |
## Dependencies |
247 | 273 |
|
248 | 274 |
Public (interface): |
... | ... |
@@ -5,6 +5,7 @@ |
5 | 5 |
#include <map> |
6 | 6 |
#include <set> |
7 | 7 |
#include <string> |
8 |
+#include <unordered_map> |
|
8 | 9 |
|
9 | 10 |
#include <GL/glew.h> |
10 | 11 |
|
... | ... |
@@ -35,19 +36,47 @@ public: |
35 | 36 |
Shader & validate(); |
36 | 37 |
Shader & use(); |
37 | 38 |
|
39 |
+ template<typename Value> |
|
40 |
+ Shader & uniform( |
|
41 |
+ std::string const & name, |
|
42 |
+ Value const & value, |
|
43 |
+ bool required = true |
|
44 |
+ ) = delete; |
|
45 |
+ |
|
38 | 46 |
protected: |
39 | 47 |
|
48 |
+ struct Uniform |
|
49 |
+ { |
|
50 |
+ GLint location; |
|
51 |
+ bool set; |
|
52 |
+ }; |
|
53 |
+ |
|
40 | 54 |
void validate_() const; |
41 | 55 |
void current_( |
42 | 56 |
std::string const & error |
43 | 57 |
) const; |
44 | 58 |
|
45 |
- GLuint program_; |
|
46 |
- std::string program_name_; |
|
47 |
- std::string static root_; |
|
48 |
- Defines static defines_; |
|
49 |
- Locations static verts_; |
|
50 |
- Locations static frags_; |
|
59 |
+ static void error_( |
|
60 |
+ std::string const & error, |
|
61 |
+ std::string const & error_hint = {} |
|
62 |
+ ); |
|
63 |
+ |
|
64 |
+ Uniform * uniform_( |
|
65 |
+ std::string const & error, |
|
66 |
+ std::string const & name, |
|
67 |
+ bool required |
|
68 |
+ ); |
|
69 |
+ |
|
70 |
+ template<typename Value> |
|
71 |
+ using ByName = std::unordered_map<std::string, Value>; |
|
72 |
+ |
|
73 |
+ GLuint program_; |
|
74 |
+ std::string program_name_; |
|
75 |
+ std::string static root_; |
|
76 |
+ Defines static defines_; |
|
77 |
+ Locations static verts_; |
|
78 |
+ Locations static frags_; |
|
79 |
+ ByName<Uniform> uniforms_; |
|
51 | 80 |
}; |
52 | 81 |
|
53 | 82 |
|
... | ... |
@@ -55,8 +84,12 @@ protected: |
55 | 84 |
|
56 | 85 |
#ifndef NDEBUG |
57 | 86 |
#define GLSHADER_DEBUG_(...) __VA_ARGS__ |
87 |
+ #define GLSHADER_DEBUG_ERROR_(...) \ |
|
88 |
+ auto error = std::string{} + __VA_ARGS__; |
|
58 | 89 |
#else |
59 | 90 |
#define GLSHADER_DEBUG_(...) |
91 |
+ #define GLSHADER_DEBUG_ERROR_(...) \ |
|
92 |
+ auto error = std::string{}; |
|
60 | 93 |
#endif // NDEBUG |
61 | 94 |
|
62 | 95 |
|
... | ... |
@@ -90,4 +123,36 @@ inline Shader & Shader::use() |
90 | 123 |
} |
91 | 124 |
|
92 | 125 |
|
126 |
+// Uniform template specializations. |
|
127 |
+ |
|
128 |
+#define GLSHADER_UNIFORM_SIGNATURE_(TYPE) \ |
|
129 |
+ template<> \ |
|
130 |
+ inline Shader & Shader::uniform( \ |
|
131 |
+ std::string const & name, \ |
|
132 |
+ TYPE const & value, \ |
|
133 |
+ bool required \ |
|
134 |
+ ) |
|
135 |
+ |
|
136 |
+#define GLSHADER_UNIFORM_DELETE(TYPE) \ |
|
137 |
+ GLSHADER_UNIFORM_SIGNATURE_(TYPE) = delete; |
|
138 |
+ |
|
139 |
+#define GLSHADER_UNIFORM(TYPE, CODE) \ |
|
140 |
+ GLSHADER_UNIFORM_SIGNATURE_(TYPE) \ |
|
141 |
+ { \ |
|
142 |
+ GLSHADER_DEBUG_ERROR_( \ |
|
143 |
+ "Failed to set uniform '" + name + "' of " + program_name_ \ |
|
144 |
+ ) \ |
|
145 |
+ GLSHADER_DEBUG_(current_(error);) \ |
|
146 |
+ if (auto * uniform = uniform_(error, name, required)) \ |
|
147 |
+ { \ |
|
148 |
+ GLint const & location = uniform->location; \ |
|
149 |
+ GLSHADER_DEBUG_(error_(error, "unprocessed previous error");) \ |
|
150 |
+ CODE \ |
|
151 |
+ GLSHADER_DEBUG_(error_(error, "wrong type?");) \ |
|
152 |
+ GLSHADER_DEBUG_(uniform->set = true;) \ |
|
153 |
+ } \ |
|
154 |
+ return *this; \ |
|
155 |
+ } |
|
156 |
+ |
|
157 |
+ |
|
93 | 158 |
#endif // GLSHADER_SHADER_HPP_ |
... | ... |
@@ -5,6 +5,7 @@ |
5 | 5 |
#include <cerrno> |
6 | 6 |
#include <cstring> |
7 | 7 |
#include <fstream> |
8 |
+#include <ios> |
|
8 | 9 |
#include <list> |
9 | 10 |
#include <memory> |
10 | 11 |
#include <regex> |
... | ... |
@@ -44,6 +45,28 @@ static Type get_integer_(GLenum name, bool supported = true) |
44 | 45 |
} |
45 | 46 |
|
46 | 47 |
|
48 |
+void Shader::error_( |
|
49 |
+ std::string const & error, |
|
50 |
+ std::string const & error_hint |
|
51 |
+) |
|
52 |
+{ |
|
53 |
+ auto gl_error = glGetError(); |
|
54 |
+ if (gl_error != GL_NO_ERROR) |
|
55 |
+ throw std::runtime_error{STR( |
|
56 |
+ error << "; " << |
|
57 |
+ "got error " << ( |
|
58 |
+ STR_COND(gl_error, GL_INVALID_ENUM) |
|
59 |
+ STR_COND(gl_error, GL_INVALID_VALUE) |
|
60 |
+ STR_COND(gl_error, GL_INVALID_OPERATION) |
|
61 |
+ STR_COND(gl_error, GL_OUT_OF_MEMORY) |
|
62 |
+ STR(std::hex << std::showbase << gl_error) |
|
63 |
+ ) << |
|
64 |
+ (error_hint.empty() ? "" : STR(" (" << error_hint << ")")) << |
|
65 |
+ "." |
|
66 |
+ )}; |
|
67 |
+} |
|
68 |
+ |
|
69 |
+ |
|
47 | 70 |
static void info_log_action_( |
48 | 71 |
std::string const & error, |
49 | 72 |
void (action)(GLuint object), |
... | ... |
@@ -378,7 +401,8 @@ Shader::Shader(Paths const & paths) |
378 | 401 |
program_{0}, |
379 | 402 |
program_name_{STR( |
380 | 403 |
"shader program " << STR_JOIN(", ", it, "'" << it << "'", paths) |
381 |
- )} |
|
404 |
+ )}, |
|
405 |
+ uniforms_{} |
|
382 | 406 |
{ |
383 | 407 |
// Get label limits. |
384 | 408 |
static auto const max_label_length = get_integer_<GLsizei>( |
... | ... |
@@ -518,6 +542,24 @@ Shader::Shader(Paths const & paths) |
518 | 542 |
)}; |
519 | 543 |
} |
520 | 544 |
); |
545 |
+ |
|
546 |
+ // Initialize uniforms. |
|
547 |
+ for_variable_( |
|
548 |
+ program_, |
|
549 |
+ GL_ACTIVE_UNIFORMS, GL_ACTIVE_UNIFORM_MAX_LENGTH, |
|
550 |
+ [&](GLuint index, GLsizei max_length, GLchar * name) |
|
551 |
+ { |
|
552 |
+ GLint size{}; |
|
553 |
+ GLenum type{}; |
|
554 |
+ glGetActiveUniform( |
|
555 |
+ program_, index, max_length, |
|
556 |
+ nullptr, &size, &type, name |
|
557 |
+ ); |
|
558 |
+ auto location = glGetUniformLocation(program_, name); |
|
559 |
+ if (location != -1) |
|
560 |
+ uniforms_.emplace(name, Uniform{location, false}); |
|
561 |
+ } |
|
562 |
+ ); |
|
521 | 563 |
} |
522 | 564 |
catch (...) |
523 | 565 |
{ |
... | ... |
@@ -532,7 +574,8 @@ Shader::Shader(Paths const & paths) |
532 | 574 |
Shader::Shader(Shader && other) noexcept |
533 | 575 |
: |
534 | 576 |
program_ {other.program_}, |
535 |
- program_name_{std::move(other.program_name_)} |
|
577 |
+ program_name_{std::move(other.program_name_)}, |
|
578 |
+ uniforms_ {std::move(other.uniforms_)} |
|
536 | 579 |
{ |
537 | 580 |
other.program_ = 0; |
538 | 581 |
} |
... | ... |
@@ -545,6 +588,31 @@ Shader::~Shader() |
545 | 588 |
} |
546 | 589 |
|
547 | 590 |
|
591 |
+template<typename Uniforms, typename Set> |
|
592 |
+static void uniforms_validate_( |
|
593 |
+ std::string const & error, |
|
594 |
+ std::string const & uniform_type, |
|
595 |
+ Uniforms uniforms, |
|
596 |
+ Set set |
|
597 |
+) |
|
598 |
+{ |
|
599 |
+ // Find. |
|
600 |
+ auto it = std::find_if(uniforms.begin(), uniforms.end(), |
|
601 |
+ [&](typename Uniforms::value_type const & it) |
|
602 |
+ { |
|
603 |
+ return !set(it.second); |
|
604 |
+ } |
|
605 |
+ ); |
|
606 |
+ |
|
607 |
+ // Error if not found. |
|
608 |
+ if (it != uniforms.end()) |
|
609 |
+ throw std::runtime_error{STR( |
|
610 |
+ error << "; " << |
|
611 |
+ uniform_type << " '" << it->first << "' not set." |
|
612 |
+ )}; |
|
613 |
+} |
|
614 |
+ |
|
615 |
+ |
|
548 | 616 |
void Shader::validate_() const |
549 | 617 |
{ |
550 | 618 |
// Set error. |
... | ... |
@@ -559,6 +627,14 @@ void Shader::validate_() const |
559 | 627 |
|
560 | 628 |
// Assert current. |
561 | 629 |
current_(validate_error); |
630 |
+ |
|
631 |
+ // Validate uniforms. |
|
632 |
+ uniforms_validate_(validate_error, "uniform", uniforms_, |
|
633 |
+ [](Uniform const & uniform) |
|
634 |
+ { |
|
635 |
+ return uniform.set; |
|
636 |
+ } |
|
637 |
+ ); |
|
562 | 638 |
} |
563 | 639 |
|
564 | 640 |
|
... | ... |
@@ -571,3 +647,29 @@ void Shader::current_(std::string const & error) const |
571 | 647 |
"shader program not current." |
572 | 648 |
)}; |
573 | 649 |
} |
650 |
+ |
|
651 |
+ |
|
652 |
+Shader::Uniform * Shader::uniform_( |
|
653 |
+ std::string const & error, |
|
654 |
+ std::string const & name, |
|
655 |
+ bool required |
|
656 |
+) |
|
657 |
+{ |
|
658 |
+ // Return if found. |
|
659 |
+ auto it = uniforms_.find(name); |
|
660 |
+ if (it != uniforms_.end()) |
|
661 |
+ { |
|
662 |
+ auto & uniform = it->second; |
|
663 |
+ return &uniform; |
|
664 |
+ } |
|
665 |
+ |
|
666 |
+ // Error if required. |
|
667 |
+ if (required) |
|
668 |
+ throw std::runtime_error{STR( |
|
669 |
+ error << "; " << |
|
670 |
+ "uniform required but not found." |
|
671 |
+ )}; |
|
672 |
+ |
|
673 |
+ // Return. |
|
674 |
+ return nullptr; |
|
675 |
+} |