| ... | ... |
@@ -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 |
+} |