Browse code

Add uniform block / buffer support

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

... ...
@@ -5,9 +5,14 @@ A C++11 helper for [OpenGL][] >=2.0 [shaders][Shader].
5 5
 `glshader` can load an arbitrary number of [shaders][Shader] with the type
6 6
 autodetected by the file extension (using the same rules as the [OpenGL /
7 7
 OpenGL ES Reference Compiler][]). [Uniforms][Uniform] are set with a unified
8
-interface (using template specialization). Data is provided by [OpenGL
8
+interface (using template specialization). Data can be provided by [OpenGL
9 9
 Mathematics (GLM)][] objects (which uses [OpenGL Shading Language (GLSL)][]
10
-naming conventions). For example:
10
+naming conventions). For other types of data [uniform blocks][Uniform block]
11
+are used with (shared, per name) [Uniform Buffer Object (UBO)][] backing
12
+(beware of alignment). UBOs can also be accessed directly and bound to a
13
+uniform block by name. The expected usage pattern (as used by
14
+[`glBufferData`][]) can optionally be specified (defaults to
15
+`GL_DYNAMIC_DRAW`). For example:
11 16
 
12 17
 ```cpp
13 18
 Shader shader({
... ...
@@ -18,6 +23,16 @@ shader.use();
18 23
 
19 24
 glm::vec4 color(1, 0, 0, 1);
20 25
 shader.uniform("color", color);
26
+
27
+struct Matrices {
28
+    glm::mat4 view;
29
+    glm::mat4 projection;
30
+} matrices;
31
+// Either:
32
+shader.uniform("matrices", matrices);
33
+// or:
34
+Shader::ubo("matrices", matrices);
35
+shader.uniform("matrices", "matrices");
21 36
 ```
22 37
 
23 38
 Errors are handled by throwing [`std::runtime_error`][] with a [`what()`][]
... ...
@@ -28,6 +43,9 @@ messages.
28 43
 [OpenGL]: https://www.opengl.org
29 44
 [Shader]: https://www.khronos.org/opengl/wiki/Shader
30 45
 [Uniform]: https://www.khronos.org/opengl/wiki/Uniform
46
+[Uniform block]: https://www.khronos.org/opengl/wiki/Interface_Block_(GLSL)#Uniform_blocks
47
+[Uniform Buffer Object (UBO)]: https://www.khronos.org/opengl/wiki/Uniform_Buffer_Object
48
+[`glBufferData`]: https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glBufferData.xhtml
31 49
 [OpenGL / OpenGL ES Reference Compiler]: https://www.khronos.org/opengles/sdk/tools/Reference-Compiler/
32 50
 [OpenGL Mathematics (GLM)]: https://glm.g-truc.net
33 51
 [OpenGL Shading Language (GLSL)]: https://www.khronos.org/opengl/wiki/OpenGL_Shading_Language
... ...
@@ -11,6 +11,13 @@
11 11
 #include "str.hpp"
12 12
 
13 13
 
14
+Shader::StringCache<Shader::UniformBuffer>
15
+Shader::uniform_buffer_cache_{};
16
+
17
+GLuint
18
+Shader::uniform_buffer_binding_next_{0};
19
+
20
+
14 21
 static void checked_action(
15 22
     void (glAction)(
16 23
         GLuint object
... ...
@@ -65,6 +72,25 @@ static GLenum shader_type(std::string type_str)
65 72
 }
66 73
 
67 74
 
75
+static std::string uniform_buffer_usage_str(GLenum usage)
76
+{
77
+    switch(usage)
78
+    {
79
+        STR_CASE(GL_STREAM_DRAW)
80
+        STR_CASE(GL_STREAM_READ)
81
+        STR_CASE(GL_STREAM_COPY)
82
+        STR_CASE(GL_STATIC_DRAW)
83
+        STR_CASE(GL_STATIC_READ)
84
+        STR_CASE(GL_STATIC_COPY)
85
+        STR_CASE(GL_DYNAMIC_DRAW)
86
+        STR_CASE(GL_DYNAMIC_READ)
87
+        STR_CASE(GL_DYNAMIC_COPY)
88
+        default:
89
+            return STR(std::hex << std::showbase << usage);
90
+    }
91
+}
92
+
93
+
68 94
 Shader::Shader(std::vector<std::string> paths, std::string name)
69 95
 :
70 96
     program_{0},
... ...
@@ -73,7 +99,8 @@ Shader::Shader(std::vector<std::string> paths, std::string name)
73 99
         ? std::move(name)
74 100
         : STR_JOIN(", ", "'" << it << "'", paths_)
75 101
     },
76
-    uniform_location_cache_{}
102
+    uniform_location_cache_{},
103
+    uniform_block_index_cache_{}
77 104
 {
78 105
     new_();
79 106
 }
... ...
@@ -81,10 +108,11 @@ Shader::Shader(std::vector<std::string> paths, std::string name)
81 108
 
82 109
 Shader::Shader(Shader const & other)
83 110
 :
84
-    program_               {0},
85
-    paths_                 {other.paths_},
86
-    name_                  {other.name_},
87
-    uniform_location_cache_{other.uniform_location_cache_}
111
+    program_                  {0},
112
+    paths_                    {other.paths_},
113
+    name_                     {other.name_},
114
+    uniform_location_cache_   {other.uniform_location_cache_},
115
+    uniform_block_index_cache_{other.uniform_block_index_cache_}
88 116
 {
89 117
     new_();
90 118
 }
... ...
@@ -93,10 +121,11 @@ Shader::Shader(Shader const & other)
93 121
 Shader & Shader::operator=(Shader const & other)
94 122
 {
95 123
     delete_();
96
-    program_                = 0;
97
-    paths_                  = other.paths_;
98
-    name_                   = other.name_;
99
-    uniform_location_cache_ = other.uniform_location_cache_;
124
+    program_                   = 0;
125
+    paths_                     = other.paths_;
126
+    name_                      = other.name_;
127
+    uniform_location_cache_    = other.uniform_location_cache_;
128
+    uniform_block_index_cache_ = other.uniform_block_index_cache_;
100 129
     new_();
101 130
     return *this;
102 131
 }
... ...
@@ -104,10 +133,11 @@ Shader & Shader::operator=(Shader const & other)
104 133
 
105 134
 Shader::Shader(Shader && other)
106 135
 :
107
-    program_               {std::move(other.program_)},
108
-    paths_                 {std::move(other.paths_)},
109
-    name_                  {std::move(other.name_)},
110
-    uniform_location_cache_{std::move(other.uniform_location_cache_)}
136
+    program_                  {std::move(other.program_)},
137
+    paths_                    {std::move(other.paths_)},
138
+    name_                     {std::move(other.name_)},
139
+    uniform_location_cache_   {std::move(other.uniform_location_cache_)},
140
+    uniform_block_index_cache_{std::move(other.uniform_block_index_cache_)}
111 141
 {
112 142
     other.program_ = 0;
113 143
 }
... ...
@@ -116,10 +146,11 @@ Shader::Shader(Shader && other)
116 146
 Shader & Shader::operator=(Shader && other)
117 147
 {
118 148
     delete_();
119
-    program_                = std::move(other.program_);
120
-    paths_                  = std::move(other.paths_);
121
-    name_                   = std::move(other.name_);
122
-    uniform_location_cache_ = std::move(other.uniform_location_cache_);
149
+    program_                   = std::move(other.program_);
150
+    paths_                     = std::move(other.paths_);
151
+    name_                      = std::move(other.name_);
152
+    uniform_location_cache_    = std::move(other.uniform_location_cache_);
153
+    uniform_block_index_cache_ = std::move(other.uniform_block_index_cache_);
123 154
     other.program_ = 0;
124 155
     return *this;
125 156
 }
... ...
@@ -240,13 +271,51 @@ void Shader::new_()
240 271
 }
241 272
 
242 273
 
243
-void Shader::delete_()
244
-{
274
+void Shader::delete_() {
245 275
     // Delete program (and detach and delete shaders).
246 276
     glDeleteProgram(program_);
247 277
 }
248 278
 
249 279
 
280
+Shader & Shader::uniform(
281
+    std::string const & name, std::string const & buffer_name
282
+) {
283
+    // Find uniform buffer in cache.
284
+    auto cache_entry = uniform_buffer_cache_.find(buffer_name);
285
+    if (cache_entry == uniform_buffer_cache_.end())
286
+        throw std::runtime_error{STR(
287
+            "Failed to set uniform block '" << name << "' of shader program "
288
+            << name << "; no uniform buffer '" << buffer_name << "'."
289
+        )};
290
+    auto const & uniform_buffer = cache_entry->second;
291
+
292
+    // Bind.
293
+    glUniformBlockBinding(
294
+        program_, uniform_block_index_(name), uniform_buffer.binding
295
+    );
296
+
297
+    // Return.
298
+    return *this;
299
+}
300
+
301
+
302
+void Shader::uniform_buffer_delete(std::string const & name) {
303
+    // Find uniform buffer in cache.
304
+    auto cache_entry = uniform_buffer_cache_.find(name);
305
+    if (cache_entry == uniform_buffer_cache_.end())
306
+        throw std::runtime_error{STR(
307
+            "Failed to delete uniform buffer '" << name << "'; does not exist."
308
+        )};
309
+    auto const & uniform_buffer = cache_entry->second;
310
+
311
+    // Delete buffer.
312
+    glDeleteBuffers(1, &uniform_buffer.buffer);
313
+
314
+    // Erase cache entries.
315
+    uniform_buffer_cache_.erase(cache_entry);
316
+}
317
+
318
+
250 319
 void Shader::ensure_current_(
251 320
     std::string const & operation,
252 321
     std::string const & name
... ...
@@ -285,3 +354,104 @@ GLint Shader::uniform_location_(std::string const & name)
285 354
     uniform_location_cache_.emplace(name, location);
286 355
     return location;
287 356
 }
357
+
358
+
359
+GLuint Shader::uniform_block_index_(std::string const & name) {
360
+    // Try cache.
361
+    auto & cache = uniform_block_index_cache_;
362
+    auto cache_entry = cache.find(name);
363
+    if (cache_entry != cache.end())
364
+        return cache_entry->second;
365
+
366
+    // Query OpenGL.
367
+    auto index = glGetUniformBlockIndex(program_, name.c_str());
368
+    if (index == GL_INVALID_INDEX)
369
+        throw std::runtime_error{STR(
370
+            "Failed to get index of uniform block '" << name <<
371
+            "' in shader program " << name_ << "."
372
+        )};
373
+
374
+    // Save in cache and return.
375
+    cache.emplace(name, index);
376
+    return index;
377
+}
378
+
379
+
380
+GLuint Shader::uniform_buffer_(
381
+    std::string const & name, GLsizeiptr size, GLenum usage
382
+) {
383
+    // Get usage string.
384
+    auto const & usage_str = uniform_buffer_usage_str(usage);
385
+
386
+    // Try cache.
387
+    auto cache_entry = uniform_buffer_cache_.find(name);
388
+    if (cache_entry != uniform_buffer_cache_.end()) {
389
+        // Get uniform buffer.
390
+        auto const & uniform_buffer = cache_entry->second;
391
+
392
+        // Check size and usage.
393
+        if (size != uniform_buffer.size)
394
+            throw std::runtime_error{STR(
395
+                "Failed to set data of uniform buffer '" << name <<
396
+                "'; data has size " << size << " but buffer has size " <<
397
+                uniform_buffer.size << "."
398
+            )};
399
+        if (usage != uniform_buffer.usage)
400
+            throw std::runtime_error{STR(
401
+                "Failed to set data of uniform buffer '" << name <<
402
+                "'; data has usage " << usage_str << " but buffer has usage "
403
+                << uniform_buffer_usage_str(uniform_buffer.usage) << "."
404
+            )};
405
+
406
+        // Return.
407
+        return uniform_buffer.buffer;
408
+    }
409
+
410
+    // Validate size and usage.
411
+    if (size <= 0)
412
+        throw std::runtime_error{STR(
413
+            "Failed to create uniform buffer '" << name << "', invalid size "
414
+            << size << "."
415
+        )};
416
+    if (usage_str.rfind("GL_", 0) != 0)
417
+        throw std::runtime_error{STR(
418
+            "Failed to create uniform buffer '" << name << "', invalid usage "
419
+            << usage_str << "."
420
+        )};
421
+
422
+    // Check max uniform buffer bindings.
423
+    GLuint max_uniform_buffer_bindings;
424
+    glGetIntegerv(
425
+        GL_MAX_UNIFORM_BUFFER_BINDINGS,
426
+        (GLint *)&max_uniform_buffer_bindings
427
+    );
428
+    if (uniform_buffer_binding_next_ >= max_uniform_buffer_bindings)
429
+        throw std::runtime_error{STR(
430
+            "Failed to bind uniform buffer '" << name << "'; max bindings of "
431
+            << max_uniform_buffer_bindings << " exceeded."
432
+        )};
433
+
434
+    // Create uniform buffer.
435
+    UniformBuffer uniform_buffer;
436
+
437
+    // Generate and bind.
438
+    glGenBuffers(1, &uniform_buffer.buffer);
439
+    glBindBuffer(GL_UNIFORM_BUFFER, uniform_buffer.buffer),
440
+
441
+    // Set size and usage.
442
+    uniform_buffer.size = size;
443
+    uniform_buffer.usage = usage;
444
+    glBufferData(
445
+        GL_UNIFORM_BUFFER, uniform_buffer.size, nullptr, uniform_buffer.usage
446
+    );
447
+
448
+    // Allocate binding and bind.
449
+    uniform_buffer.binding = uniform_buffer_binding_next_++;
450
+    glBindBufferBase(
451
+        GL_UNIFORM_BUFFER, uniform_buffer.binding, uniform_buffer.buffer
452
+    );
453
+
454
+    // Save in cache and return.
455
+    uniform_buffer_cache_.emplace(name, uniform_buffer);
456
+    return uniform_buffer.buffer;
457
+}
... ...
@@ -24,13 +24,28 @@ public:
24 24
     Shader & use();
25 25
     Shader & validate();
26 26
     template<typename Value>
27
-    Shader & uniform(std::string const & name, Value const & value) = delete;
27
+    Shader & uniform(std::string const & name, Value const & value);
28
+    Shader & uniform(std::string const & name, std::string const & buffer_name);
29
+    template<typename Value>
30
+    static void uniform_buffer(
31
+        std::string const & name,
32
+        Value const & value,
33
+        GLenum usage = GL_DYNAMIC_DRAW
34
+    );
35
+    static void uniform_buffer_delete(std::string const & name);
28 36
 
29 37
 private:
30 38
 
31 39
     template<typename Value>
32 40
     using StringCache = std::unordered_map<std::string, Value>;
33 41
 
42
+    struct UniformBuffer {
43
+        GLuint buffer;
44
+        GLsizeiptr size;
45
+        GLenum usage;
46
+        GLuint binding;
47
+    };
48
+
34 49
     void new_();
35 50
     void delete_();
36 51
     void ensure_current_(
... ...
@@ -38,11 +53,18 @@ private:
38 53
         std::string const & name = ""
39 54
     );
40 55
     GLint uniform_location_(std::string const & name);
56
+    GLuint uniform_block_index_(std::string const & name);
57
+    static GLuint uniform_buffer_(
58
+        std::string const & name, GLsizeiptr size, GLenum usage
59
+    );
41 60
 
42 61
     GLuint program_;
43 62
     std::vector<std::string> paths_;
44 63
     std::string name_;
45 64
     StringCache<GLint> uniform_location_cache_;
65
+    StringCache<GLuint> uniform_block_index_cache_;
66
+    static StringCache<UniformBuffer> uniform_buffer_cache_;
67
+    static GLuint uniform_buffer_binding_next_;
46 68
 };
47 69
 
48 70
 
... ...
@@ -54,6 +76,29 @@ private:
54 76
 #endif
55 77
 
56 78
 
79
+template<typename Value>
80
+inline Shader & Shader::uniform(
81
+    std::string const & name, Value const & value
82
+) {
83
+    GLSHADER_ENSURE_CURRENT("set uniform block", name);
84
+    uniform_block_index_(name);
85
+    uniform_buffer(name, value);
86
+    uniform(name, name);
87
+    return *this;
88
+}
89
+
90
+
91
+template<typename Value>
92
+inline void Shader::uniform_buffer(
93
+    std::string const & name, Value const & value, GLenum usage
94
+) {
95
+    glBindBuffer(
96
+        GL_UNIFORM_BUFFER, uniform_buffer_(name, sizeof(value), usage)
97
+    );
98
+    glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(value), &value);
99
+}
100
+
101
+
57 102
 #define GLSHADER_UNIFORM(VALUE_TYPE, CODE) \
58 103
     template<> \
59 104
     inline Shader & Shader::uniform( \
... ...
@@ -1,6 +1,12 @@
1 1
 #version 110
2 2
 
3 3
 
4
+// layout(std140) uniform matrices {
5
+//     mat4 view;
6
+//     mat4 projection;
7
+// };
8
+
9
+
4 10
 void main() {
5 11
     gl_Position = gl_Vertex;
6 12
 }
... ...
@@ -93,6 +93,38 @@ void test() {
93 93
         "Failed to get location of uniform 'nonexistent' of shader program 'good.vert', 'good.frag'."
94 94
     )
95 95
     shader.uniform("color", color);
96
+
97
+    struct Matrices {
98
+        glm::mat4 view;
99
+        glm::mat4 projection;
100
+    } matrices;
101
+    EXPECT_EXCEPTION(
102
+        Shader({ "good.vert", "good.frag" }).uniform("noncurrent", matrices),
103
+        "Failed to set uniform block 'noncurrent' of shader program 'good.vert', 'good.frag'; program is not current."
104
+    )
105
+    EXPECT_EXCEPTION(
106
+        shader.uniform("nonexistent", matrices),
107
+        "Failed to get index of uniform block 'nonexistent' in shader program 'good.vert', 'good.frag'."
108
+    )
109
+    // (Allocate), set data and bind UBO implicitly.
110
+    // shader.uniform("matrices", matrices);
111
+    // (Allocate), set data and bind UBO explicitly.
112
+    Shader::uniform_buffer("matrices", matrices);
113
+    // shader.uniform("matrices", "matrices");
114
+    EXPECT_EXCEPTION(
115
+        Shader::uniform_buffer("matrices", matrices, GL_STATIC_DRAW),
116
+        "Failed to set data of uniform buffer 'matrices'; data has usage GL_STATIC_DRAW but buffer has usage GL_DYNAMIC_DRAW."
117
+    )
118
+    EXPECT_EXCEPTION(
119
+        Shader::uniform_buffer("matrices", color),
120
+        "Failed to set data of uniform buffer 'matrices'; data has size 16 but buffer has size 128."
121
+    )
122
+    EXPECT_EXCEPTION(
123
+        Shader::uniform_buffer_delete("nonexistent"),
124
+        "Failed to delete uniform buffer 'nonexistent'; does not exist."
125
+    )
126
+    // Delete UBO.
127
+    // Shader::uniform_buffer_delete("matrices");
96 128
 }
97 129
 
98 130