Browse code

Add uniform

Robert Cranston authored on 21/05/2021 00:46:36
Showing 3 changed files

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