... | ... |
@@ -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( \ |
... | ... |
@@ -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 |
|