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