Browse code

Add uniform block / buffer

Robert Cranston authored on 17/05/2020 15:29:35
Showing 7 changed files

... ...
@@ -13,6 +13,8 @@ A [C++11][] [OpenGL][] \>=[2.0][] [shader][] library.
13 13
 Overview of usage:
14 14
 
15 15
 ```cpp
16
+#include <array>
17
+
16 18
 #include <GL/glew.h>
17 19
 #include <glm/glm.hpp>
18 20
 
... ...
@@ -32,6 +34,11 @@ int main()
32 34
     // Local data.
33 35
     auto light_count = 3;
34 36
     auto color = glm::vec4{1, 0, 0, 1};
37
+    struct
38
+    {
39
+        std::array<float, 4> value1;
40
+        std::array<float, 4> value2;
41
+    } values = {};
35 42
 
36 43
     // Global settings.
37 44
     Shader::root("assets/shaders");
... ...
@@ -57,7 +64,9 @@ int main()
57 64
         .use()
58 65
         .uniform("light_count", light_count)
59 66
         .uniform("color", color)
67
+        .uniform("values", values)
60 68
         .validate();
69
+    Shader::uniform_buffer("values", values);
61 70
 }
62 71
 ```
63 72
 
... ...
@@ -294,6 +303,22 @@ the supplied variables `TYPE const & value` and `GLint const & location`,
294 303
 probably as parameters to `glUniform*`. `GLSHADER_UNIFORM_DELETE(TYPE)` can be
295 304
 used to [`delete`][] the specialization for a given type.
296 305
 
306
+#### Uniform blocks / buffers
307
+
308
+If no specialization is provided, [uniform block][]s are assumed. This requires
309
+OpenGL \>=3.1 or the [`ARB_uniform_buffer_object`][] extension, an error is
310
+thrown if it is not available. Shared, by name, [Uniform Buffer Object
311
+(UBO)][]s are automatically allocated. Beware of alignment, it is recommended
312
+to use the `std140` [memory layout][] and to [avoid `vec3`/`mat3`][].
313
+
314
+Bare arrays are not supported, wrap them in uniform blocks.
315
+
316
+[Uniform block]: https://www.khronos.org/opengl/wiki/Interface_Block_(GLSL)#Uniform_blocks
317
+[`ARB_uniform_buffer_object`]: https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_uniform_buffer_object.txt
318
+[Uniform Buffer Object (UBO)]: https://www.khronos.org/opengl/wiki/Uniform_Buffer_Object
319
+[memory layout]: https://www.khronos.org/opengl/wiki/Interface_Block_(GLSL)#Memory_layout
320
+[avoid `vec3`/`mat3`]: https://stackoverflow.com/questions/38172696/should-i-ever-use-a-vec3-inside-of-a-uniform-buffer-or-shader-storage-buffer-object
321
+
297 322
 ## Dependencies
298 323
 
299 324
 Public (interface):
... ...
@@ -2,6 +2,7 @@
2 2
 
3 3
 
4 4
 #extension GL_ARB_shading_language_include : require
5
+#extension GL_ARB_uniform_buffer_object : require
5 6
 
6 7
 
7 8
 #include "all_green.h"
... ...
@@ -9,6 +10,10 @@
9 10
 
10 11
 uniform float blue;
11 12
 uniform vec4 color;
13
+layout(std140) uniform small
14
+{
15
+    vec4 value1;
16
+};
12 17
 
13 18
 
14 19
 void main()
15 20
new file mode 100644
... ...
@@ -0,0 +1,17 @@
1
+#version 110
2
+
3
+
4
+#extension GL_ARB_uniform_buffer_object : require
5
+
6
+
7
+layout(std140) uniform small
8
+{
9
+    vec4 value1;
10
+    vec4 value2;
11
+};
12
+
13
+
14
+void main()
15
+{
16
+    gl_Position = gl_Vertex;
17
+}
0 18
new file mode 100644
... ...
@@ -0,0 +1,16 @@
1
+#version 110
2
+
3
+
4
+#extension GL_ARB_uniform_buffer_object : require
5
+
6
+
7
+layout(std140) uniform small
8
+{
9
+    vec4 value1;
10
+};
11
+
12
+
13
+void main()
14
+{
15
+    gl_Position = gl_Vertex;
16
+}
... ...
@@ -41,6 +41,25 @@ public:
41 41
         std::string const & name,
42 42
         Value       const & value,
43 43
         bool                required = true
44
+    );
45
+    template<typename Value>
46
+    Shader & uniform(
47
+        std::string const & name,
48
+        Value             * value,
49
+        bool                required = true
50
+    ) = delete;
51
+
52
+    template<typename Value>
53
+    static void uniform_buffer(
54
+        std::string const & name,
55
+        Value       const & value,
56
+        bool                required = true
57
+    );
58
+    template<typename Value>
59
+    static void uniform_buffer(
60
+        std::string const & name,
61
+        Value             * value,
62
+        bool                required = true
44 63
     ) = delete;
45 64
 
46 65
 protected:
... ...
@@ -51,6 +70,19 @@ protected:
51 70
         bool set;
52 71
     };
53 72
 
73
+    struct UniformBuffer
74
+    {
75
+        GLuint buffer;
76
+        GLsizeiptr size;
77
+        GLuint binding;
78
+        bool set;
79
+    };
80
+
81
+    struct UniformBlock
82
+    {
83
+        UniformBuffer & buffer;
84
+    };
85
+
54 86
     void validate_() const;
55 87
     void current_(
56 88
         std::string const & error
... ...
@@ -66,17 +98,31 @@ protected:
66 98
         std::string const & name,
67 99
         bool                required
68 100
     );
101
+    UniformBlock * uniform_block_(
102
+        std::string const & error,
103
+        std::string const & name,
104
+        bool                required,
105
+        GLsizeiptr          size
106
+    );
107
+    static UniformBuffer * uniform_buffer_(
108
+        std::string const & error,
109
+        std::string const & name,
110
+        bool                required,
111
+        GLsizeiptr          size
112
+    );
69 113
 
70 114
     template<typename Value>
71 115
     using ByName = std::unordered_map<std::string, Value>;
72 116
 
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_;
117
+    GLuint                       program_;
118
+    std::string                  program_name_;
119
+    std::string           static root_;
120
+    Defines               static defines_;
121
+    Locations             static verts_;
122
+    Locations             static frags_;
123
+    ByName<Uniform>              uniforms_;
124
+    ByName<UniformBlock>         uniform_blocks_;
125
+    ByName<UniformBuffer> static uniform_buffers_;
80 126
 };
81 127
 
82 128
 
... ...
@@ -123,6 +169,49 @@ inline Shader & Shader::use()
123 169
 }
124 170
 
125 171
 
172
+// Uniform template definitions.
173
+
174
+#define GLSHADER_UNIFORM_BUFFER_(BLOCK_OR_BUFFER, BUFFER, SET) \
175
+    if (auto block_or_buffer = BLOCK_OR_BUFFER( \
176
+        error, name, required, sizeof(value) \
177
+    )) \
178
+    { \
179
+        glBindBuffer(GL_UNIFORM_BUFFER, block_or_buffer->BUFFER); \
180
+        GLSHADER_DEBUG_(error_(error, "unprocessed previous error");) \
181
+        glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(value), &value); \
182
+        GLSHADER_DEBUG_(error_(error);) \
183
+        GLSHADER_DEBUG_(block_or_buffer->SET = true;) \
184
+    }
185
+
186
+template<typename Value>
187
+inline void Shader::uniform_buffer(
188
+    std::string const & name,
189
+    Value       const & value,
190
+    bool                required
191
+)
192
+{
193
+    GLSHADER_DEBUG_ERROR_(
194
+        "Failed to set uniform buffer '" + name + "'"
195
+    )
196
+    GLSHADER_UNIFORM_BUFFER_(uniform_buffer_, buffer, set)
197
+}
198
+
199
+template<typename Value>
200
+inline Shader & Shader::uniform(
201
+    std::string const & name,
202
+    Value       const & value,
203
+    bool                required
204
+)
205
+{
206
+    GLSHADER_DEBUG_ERROR_(
207
+        "Failed to set uniform block '" + name + "' of " + program_name_
208
+    )
209
+    GLSHADER_DEBUG_(current_(error);)
210
+    GLSHADER_UNIFORM_BUFFER_(uniform_block_, buffer.buffer, buffer.set)
211
+    return *this;
212
+}
213
+
214
+
126 215
 // Uniform template specializations.
127 216
 
128 217
 #define GLSHADER_UNIFORM_SIGNATURE_(TYPE) \
... ...
@@ -33,6 +33,7 @@ GLSHADER_INIT_(Shader::root_, {})
33 33
 GLSHADER_INIT_(Shader::defines_, {})
34 34
 GLSHADER_INIT_(Shader::verts_, {})
35 35
 GLSHADER_INIT_(Shader::frags_, {})
36
+GLSHADER_INIT_(Shader::uniform_buffers_, {})
36 37
 
37 38
 
38 39
 template<typename Type>
... ...
@@ -402,7 +403,8 @@ Shader::Shader(Paths const & paths)
402 403
     program_name_{STR(
403 404
         "shader program " << STR_JOIN(", ", it, "'" << it << "'", paths)
404 405
     )},
405
-    uniforms_{}
406
+    uniforms_{},
407
+    uniform_blocks_{}
406 408
 {
407 409
     // Get label limits.
408 410
     static auto const max_label_length = get_integer_<GLsizei>(
... ...
@@ -560,6 +562,39 @@ Shader::Shader(Paths const & paths)
560 562
                     uniforms_.emplace(name, Uniform{location, false});
561 563
             }
562 564
         );
565
+
566
+        // Initialize uniform blocks.
567
+        for_variable_(
568
+            program_,
569
+            GL_ACTIVE_UNIFORM_BLOCKS, GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH,
570
+            [&](GLuint index, GLsizei max_length, GLchar * name)
571
+            {
572
+                if (!(GLEW_VERSION_3_1 || GLEW_ARB_uniform_buffer_object))
573
+                    throw std::runtime_error{STR(
574
+                        "Failed to initialize uniform block; " <<
575
+                        "ARB_uniform_buffer_object not available."
576
+                    )};
577
+                glGetActiveUniformBlockName(
578
+                    program_, index, max_length,
579
+                    nullptr, name
580
+                );
581
+                auto error = STR(
582
+                    "Failed to initialize uniform block '" << name << "' of "
583
+                    << program_name_
584
+                );
585
+                auto size = GLsizei{};
586
+                glGetActiveUniformBlockiv(
587
+                    program_, index, GL_UNIFORM_BLOCK_DATA_SIZE, (GLint *)&size
588
+                );
589
+                auto & uniform_buffer = *uniform_buffer_(
590
+                    error, name, false, size
591
+                );
592
+                glUniformBlockBinding(program_, index, uniform_buffer.binding);
593
+                uniform_blocks_.emplace(
594
+                    name, UniformBlock{uniform_buffer}
595
+                );
596
+            }
597
+        );
563 598
     }
564 599
     catch (...)
565 600
     {
... ...
@@ -573,9 +608,10 @@ Shader::Shader(Paths const & paths)
573 608
 
574 609
 Shader::Shader(Shader && other) noexcept
575 610
 :
576
-    program_     {other.program_},
577
-    program_name_{std::move(other.program_name_)},
578
-    uniforms_    {std::move(other.uniforms_)}
611
+    program_       {other.program_},
612
+    program_name_  {std::move(other.program_name_)},
613
+    uniforms_      {std::move(other.uniforms_)},
614
+    uniform_blocks_{std::move(other.uniform_blocks_)}
579 615
 {
580 616
     other.program_ = 0;
581 617
 }
... ...
@@ -635,6 +671,14 @@ void Shader::validate_() const
635 671
             return uniform.set;
636 672
         }
637 673
     );
674
+
675
+    // Validate uniform blocks.
676
+    uniforms_validate_(validate_error, "uniform block", uniform_blocks_,
677
+        [](UniformBlock const & uniform_block)
678
+        {
679
+            return uniform_block.buffer.set;
680
+        }
681
+    );
638 682
 }
639 683
 
640 684
 
... ...
@@ -665,11 +709,160 @@ Shader::Uniform * Shader::uniform_(
665 709
 
666 710
     // Error if required.
667 711
     if (required)
712
+    {
713
+        auto error_hint = std::string{};
714
+        if (uniform_blocks_.find(name) != uniform_blocks_.end())
715
+            error_hint = " (did you mean the uniform block?)";
716
+        throw std::runtime_error{STR(
717
+            error << "; " <<
718
+            "uniform required but not found" << error_hint << "."
719
+        )};
720
+    }
721
+
722
+    // Return.
723
+    return nullptr;
724
+}
725
+
726
+
727
+Shader::UniformBlock * Shader::uniform_block_(
728
+    std::string const & error,
729
+    std::string const & name,
730
+    bool required,
731
+    GLsizeiptr size
732
+)
733
+{
734
+    // Return if found.
735
+    auto it = uniform_blocks_.find(name);
736
+    if (it != uniform_blocks_.end())
737
+    {
738
+        auto & uniform_block = it->second;
739
+        if (size != uniform_block.buffer.size)
740
+            throw std::runtime_error{STR(
741
+                error << "; " <<
742
+                "expected size " << uniform_block.buffer.size << " but got " <<
743
+                size << "."
744
+            )};
745
+        return &uniform_block;
746
+    }
747
+
748
+    // Error if required.
749
+    if (required)
750
+    {
751
+        auto error_hint = std::string{};
752
+        if (uniforms_.find(name) != uniforms_.end())
753
+            error_hint = " (did you mean the uniform?)";
668 754
         throw std::runtime_error{STR(
669 755
             error << "; " <<
670
-            "uniform required but not found."
756
+            "uniform block required but not found" << error_hint << "."
671 757
         )};
758
+    }
672 759
 
673 760
     // Return.
674 761
     return nullptr;
675 762
 }
763
+
764
+
765
+Shader::UniformBuffer * Shader::uniform_buffer_(
766
+    std::string const & error,
767
+    std::string const & name,
768
+    bool required,
769
+    GLsizeiptr size
770
+)
771
+{
772
+    // Get uniform block / buffer limits.
773
+    static auto const max_uniform_block_size = get_integer_<GLuint>(
774
+        GL_MAX_UNIFORM_BLOCK_SIZE
775
+    );
776
+    static auto const max_uniform_buffer_bindings = get_integer_<GLuint>(
777
+        GL_MAX_UNIFORM_BUFFER_BINDINGS
778
+    );
779
+
780
+    // Define next binding.
781
+    static auto next_uniform_buffer_binding_ = GLuint{0};
782
+
783
+    // Return if found.
784
+    auto it = uniform_buffers_.find(name);
785
+    if (it != uniform_buffers_.end())
786
+    {
787
+        auto & uniform_buffer = it->second;
788
+        if (size != uniform_buffer.size)
789
+            throw std::runtime_error{STR(
790
+                error << "; " <<
791
+                "expected size " << uniform_buffer.size << " but got " << size
792
+                << "."
793
+            )};
794
+        return &uniform_buffer;
795
+    }
796
+
797
+    // Error if required.
798
+    if (required)
799
+        throw std::runtime_error{STR(
800
+            error << "; " <<
801
+            "uniform buffer required but not found."
802
+        )};
803
+
804
+    // Set create error.
805
+    auto create_error = STR(
806
+        error << ":\n" <<
807
+        "Failed to create uniform buffer '" << name << "'"
808
+    );
809
+
810
+    // Check availability.
811
+    if (!(GLEW_VERSION_3_1 || GLEW_ARB_uniform_buffer_object))
812
+        throw std::runtime_error{STR(
813
+            create_error << "; " <<
814
+            "ARB_uniform_buffer_object not available."
815
+        )};
816
+
817
+    // Create storage.
818
+    auto emplace = uniform_buffers_.emplace(name, UniformBuffer{});
819
+    if (!emplace.second)
820
+        throw std::runtime_error{STR(
821
+            create_error << "; " <<
822
+            "already exists."
823
+        )};
824
+    auto & uniform_buffer = emplace.first->second;
825
+
826
+    // Check for errors.
827
+    if (size > max_uniform_block_size)
828
+        throw std::runtime_error{STR(
829
+            create_error << "; " <<
830
+            "buffer has size " << size << " but max size is " <<
831
+            max_uniform_block_size << "."
832
+        )};
833
+    if (next_uniform_buffer_binding_ >= max_uniform_buffer_bindings)
834
+        throw std::runtime_error{STR(
835
+            create_error << "; " <<
836
+            "buffer would have binding " << next_uniform_buffer_binding_ <<
837
+            " but max bindings is " << max_uniform_buffer_bindings << "."
838
+        )};
839
+
840
+    // Generate and bind.
841
+    glGenBuffers(1, &uniform_buffer.buffer);
842
+    glBindBuffer(GL_UNIFORM_BUFFER, uniform_buffer.buffer),
843
+
844
+    // Allocate size.
845
+    uniform_buffer.size = size;
846
+    error_(create_error, "unprocessed previous error");
847
+    glBufferData(
848
+        GL_UNIFORM_BUFFER,
849
+        uniform_buffer.size,
850
+        nullptr,
851
+        GL_DYNAMIC_DRAW
852
+    );
853
+    error_(create_error);
854
+
855
+    // Allocate binding and bind.
856
+    uniform_buffer.binding = next_uniform_buffer_binding_++;
857
+    glBindBufferBase(
858
+        GL_UNIFORM_BUFFER,
859
+        uniform_buffer.binding,
860
+        uniform_buffer.buffer
861
+    );
862
+
863
+    // Mark as unset.
864
+    uniform_buffer.set = false;
865
+
866
+    // Return
867
+    return &uniform_buffer;
868
+}
... ...
@@ -1,3 +1,4 @@
1
+#include <array>
1 2
 #include <string>
2 3
 
3 4
 #include <GL/glew.h>
... ...
@@ -166,6 +167,47 @@ GLTEST(2, 0, 640, 480, glshader)
166 167
     // Uniform OpenGL Mathematics (GLM).
167 168
     glm::vec4 color{1.0F};
168 169
 
170
+    // Uniform buffer.
171
+    struct
172
+    {
173
+        std::array<float, 4> value1;
174
+    } small = {};
175
+    struct
176
+    {
177
+        std::array<float, 4> value1;
178
+        std::array<float, 4> value2;
179
+    } big = {};
180
+    GLTEST_EXPECT_EXCEPTION(false,
181
+        Shader({"tests/uniform_buffer_small.vert"}).use().validate(),
182
+        "Failed to validate shader program 'tests/uniform_buffer_small.vert'; "
183
+        "uniform block 'small' not set."
184
+    )
185
+    GLTEST_EXPECT_EXCEPTION(false,
186
+        Shader::uniform_buffer("small", big),
187
+        "Failed to set uniform buffer 'small'; "
188
+        "expected size 16 but got 32."
189
+    )
190
+    GLTEST_EXPECT_EXCEPTION(false,
191
+        Shader({"tests/uniform_buffer_small.vert"}).use().uniform("small", big),
192
+        "Failed to set uniform block 'small' of shader program 'tests/uniform_buffer_small.vert'; "
193
+        "expected size 16 but got 32."
194
+    )
195
+    GLTEST_EXPECT_EXCEPTION(false,
196
+        Shader({"tests/uniform_buffer_bad_size.vert"}),
197
+        "Failed to initialize uniform block 'small' of shader program 'tests/uniform_buffer_bad_size.vert'; "
198
+        "expected size 16 but got 32."
199
+    )
200
+    GLTEST_EXPECT_EXCEPTION(false,
201
+        Shader({"tests/uniform_buffer_small.vert"}).use().uniform("small", 1.0F),
202
+        "Failed to set uniform 'small' of shader program 'tests/uniform_buffer_small.vert'; "
203
+        "uniform required but not found (did you mean the uniform block?)."
204
+    )
205
+    GLTEST_EXPECT_EXCEPTION(false,
206
+        Shader({"tests/uniform_blue.frag"}).use().uniform("blue", small),
207
+        "Failed to set uniform block 'blue' of shader program 'tests/uniform_blue.frag'; "
208
+        "uniform block required but not found (did you mean the uniform?)."
209
+    )
210
+
169 211
     auto all = Shader({
170 212
         "tests/all.vert",
171 213
         "tests/all.frag",
... ...
@@ -175,7 +217,9 @@ GLTEST(2, 0, 640, 480, glshader)
175 217
         .use()
176 218
         .uniform("blue", blue)
177 219
         .uniform("color", color)
220
+        .uniform("small", small)
178 221
         .validate();
222
+    Shader::uniform_buffer("small", small);
179 223
 
180 224
     constexpr auto size = 0.5F;
181 225
     glBegin(GL_TRIANGLE_STRIP);