... | ... |
@@ -20,6 +20,9 @@ Overview of usage: |
20 | 20 |
|
21 | 21 |
// Global data. |
22 | 22 |
constexpr auto light_count_max = 32; |
23 |
+constexpr auto vert_position = 0; |
|
24 |
+constexpr auto vert_tex_coord = 1; |
|
25 |
+constexpr auto frag_color = 0; |
|
23 | 26 |
|
24 | 27 |
|
25 | 28 |
int main() |
... | ... |
@@ -29,6 +32,13 @@ int main() |
29 | 32 |
Shader::defines({ |
30 | 33 |
{"light_count_max", std::to_string(light_count_max)}, |
31 | 34 |
}); |
35 |
+ Shader::verts({ |
|
36 |
+ {"vert_position", vert_position}, |
|
37 |
+ {"vert_tex_coord", vert_tex_coord}, |
|
38 |
+ }); |
|
39 |
+ Shader::frags({ |
|
40 |
+ {"frag_color", frag_color}, |
|
41 |
+ }); |
|
32 | 42 |
|
33 | 43 |
// Create. |
34 | 44 |
auto player = Shader({ |
... | ... |
@@ -202,6 +212,37 @@ been seen before. |
202 | 212 |
[`glNamedStringARB`]: https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_shading_language_include.txt |
203 | 213 |
[`glCompileShaderIncludeARB`]: https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_shading_language_include.txt |
204 | 214 |
|
215 |
+### User-defined vertex inputs (attributes) and fragment outputs (data) |
|
216 |
+ |
|
217 |
+Locations for user-defined [vertex inputs][] (attributes) and [fragment |
|
218 |
+outputs][] (data, requires OpenGL >= 3.0) can be specified by calling |
|
219 |
+`Shader::verts(Shader::Locations const & verts)` and |
|
220 |
+`Shader::frags(Shader::Locations const & frags)` respectively before |
|
221 |
+instantiating a `Shader` that uses them. `Shader::Locations` is an alias for |
|
222 |
+`std::map<std::string, GLuint>`. Any `location` [layout qualifier][] overrides |
|
223 |
+the values specified this way. Note that a `Shader` object retains the input |
|
224 |
+and output locations that were in effect when it was instantiated. See |
|
225 |
+[`glBindAttribLocation`][] and [`glBindFragDataLocation`][]. Note that fragment |
|
226 |
+outputs are subject to [`glDrawBuffer`][] / [`glDrawBuffers`][]. |
|
227 |
+ |
|
228 |
+An error is thrown if a shader uses a non-specified input. No error is thrown |
|
229 |
+for non-specified outputs (this is mostly a result of the inability to |
|
230 |
+enumerate shader outputs before the introduction of the [program interface |
|
231 |
+query][]). Shaders need not use all of the specified inputs/outputs. |
|
232 |
+ |
|
233 |
+The intent is for the application to define global unchanging input/output |
|
234 |
+locations that are used by all shaders, geometry and framebuffers and set them |
|
235 |
+up once at application startup. |
|
236 |
+ |
|
237 |
+[vertex inputs]: https://www.khronos.org/opengl/wiki/Vertex_Shader#Inputs |
|
238 |
+[fragment outputs]: https://www.khronos.org/opengl/wiki/Fragment_Shader#Outputs |
|
239 |
+[layout qualifier]: https://www.khronos.org/opengl/wiki/Layout_Qualifier_(GLSL) |
|
240 |
+[`glBindAttribLocation`]: https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glBindAttribLocation.xhtml |
|
241 |
+[`glBindFragDataLocation`]: https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glBindFragDataLocation.xhtml |
|
242 |
+[`glDrawBuffer`]: https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glDrawBuffer.xhtml |
|
243 |
+[`glDrawBuffers`]: https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glDrawBuffers.xhtml |
|
244 |
+[program interface query]: https://www.khronos.org/opengl/wiki/Program_Introspection#Interface_query |
|
245 |
+ |
|
205 | 246 |
## Dependencies |
206 | 247 |
|
207 | 248 |
Public (interface): |
... | ... |
@@ -23,9 +23,12 @@ public: |
23 | 23 |
Shader & operator=(Shader const &) = delete; |
24 | 24 |
|
25 | 25 |
using Defines = std::map<std::string, std::string>; |
26 |
+ using Locations = std::map<std::string, GLuint>; |
|
26 | 27 |
|
27 | 28 |
static void root(std::string const & root); |
28 | 29 |
static void defines(Defines const & defines); |
30 |
+ static void verts(Locations const & verts); |
|
31 |
+ static void frags(Locations const & frags); |
|
29 | 32 |
|
30 | 33 |
GLuint program() const; |
31 | 34 |
|
... | ... |
@@ -43,6 +46,8 @@ protected: |
43 | 46 |
std::string program_name_; |
44 | 47 |
std::string static root_; |
45 | 48 |
Defines static defines_; |
49 |
+ Locations static verts_; |
|
50 |
+ Locations static frags_; |
|
46 | 51 |
}; |
47 | 52 |
|
48 | 53 |
|
... | ... |
@@ -64,6 +69,8 @@ protected: |
64 | 69 |
} |
65 | 70 |
GLSHADER_SET_(std::string, root) |
66 | 71 |
GLSHADER_SET_(Defines, defines) |
72 |
+GLSHADER_SET_(Locations, verts) |
|
73 |
+GLSHADER_SET_(Locations, frags) |
|
67 | 74 |
|
68 | 75 |
inline GLuint Shader::program() const |
69 | 76 |
{ |
... | ... |
@@ -23,10 +23,15 @@ |
23 | 23 |
using Here = std::tuple<std::string, int, std::string>; |
24 | 24 |
|
25 | 25 |
|
26 |
+constexpr auto max_length_workaround = 4096; |
|
27 |
+ |
|
28 |
+ |
|
26 | 29 |
// NOLINTNEXTLINE |
27 | 30 |
#define GLSHADER_INIT_(NAME, INIT) decltype(NAME) NAME INIT; |
28 | 31 |
GLSHADER_INIT_(Shader::root_, {}) |
29 | 32 |
GLSHADER_INIT_(Shader::defines_, {}) |
33 |
+GLSHADER_INIT_(Shader::verts_, {}) |
|
34 |
+GLSHADER_INIT_(Shader::frags_, {}) |
|
30 | 35 |
|
31 | 36 |
|
32 | 37 |
template<typename Type> |
... | ... |
@@ -340,6 +345,34 @@ static std::string source_( |
340 | 345 |
} |
341 | 346 |
|
342 | 347 |
|
348 |
+template<typename Function> |
|
349 |
+static void for_variable_( |
|
350 |
+ GLuint program, |
|
351 |
+ GLenum count_enum, |
|
352 |
+ GLenum max_length_enum, |
|
353 |
+ Function function |
|
354 |
+) |
|
355 |
+{ |
|
356 |
+ // Get count. |
|
357 |
+ auto count = GLuint{}; |
|
358 |
+ glGetProgramiv(program, count_enum, (GLint *)&count); |
|
359 |
+ |
|
360 |
+ // Get max length. |
|
361 |
+ auto max_length = GLsizei{}; |
|
362 |
+ glGetProgramiv(program, max_length_enum, &max_length); |
|
363 |
+ |
|
364 |
+ // Work around driver bugs. |
|
365 |
+ if (max_length == 0 && count != 0) |
|
366 |
+ max_length = max_length_workaround; |
|
367 |
+ |
|
368 |
+ // Allocate and call function. |
|
369 |
+ // NOLINTNEXTLINE |
|
370 |
+ auto name = std::unique_ptr<GLchar[]>(new GLchar[max_length]); |
|
371 |
+ for (auto index = GLuint{0}; index < count; ++index) |
|
372 |
+ function(index, max_length, &name[0]); |
|
373 |
+} |
|
374 |
+ |
|
375 |
+ |
|
343 | 376 |
Shader::Shader(Paths const & paths) |
344 | 377 |
: |
345 | 378 |
program_{0}, |
... | ... |
@@ -441,6 +474,19 @@ Shader::Shader(Paths const & paths) |
441 | 474 |
); |
442 | 475 |
} |
443 | 476 |
|
477 |
+ // Set vertex input locations. |
|
478 |
+ for (auto const & vert : verts_) |
|
479 |
+ glBindAttribLocation( |
|
480 |
+ program_, vert.second, vert.first.c_str() |
|
481 |
+ ); |
|
482 |
+ |
|
483 |
+ // Set fragment output locations. |
|
484 |
+ if (GLEW_VERSION_3_0) |
|
485 |
+ for (auto const & frag : frags_) |
|
486 |
+ glBindFragDataLocation( |
|
487 |
+ program_, frag.second, frag.first.c_str() |
|
488 |
+ ); |
|
489 |
+ |
|
444 | 490 |
// Link program. |
445 | 491 |
info_log_action_( |
446 | 492 |
STR("Failed to link " << program_name_), |
... | ... |
@@ -451,6 +497,27 @@ Shader::Shader(Paths const & paths) |
451 | 497 |
// Detach shaders. |
452 | 498 |
for (auto const & shader : shaders) |
453 | 499 |
glDetachShader(program_, shader); |
500 |
+ |
|
501 |
+ // Initialize vertex inputs. |
|
502 |
+ for_variable_( |
|
503 |
+ program_, |
|
504 |
+ GL_ACTIVE_ATTRIBUTES, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, |
|
505 |
+ [&](GLuint index, GLsizei max_length, GLchar * name) |
|
506 |
+ { |
|
507 |
+ GLint size{}; |
|
508 |
+ GLenum type{}; |
|
509 |
+ glGetActiveAttrib( |
|
510 |
+ program_, index, max_length, |
|
511 |
+ nullptr, &size, &type, name |
|
512 |
+ ); |
|
513 |
+ auto location = glGetAttribLocation(program_, name); |
|
514 |
+ if (location != -1 && verts_.find(name) == verts_.end()) |
|
515 |
+ throw std::runtime_error{STR( |
|
516 |
+ "Failed to initialize vertex input '" << name << |
|
517 |
+ "' of " << program_name_ << "." |
|
518 |
+ )}; |
|
519 |
+ } |
|
520 |
+ ); |
|
454 | 521 |
} |
455 | 522 |
catch (...) |
456 | 523 |
{ |
... | ... |
@@ -125,6 +125,18 @@ GLTEST(2, 0, 640, 480, glshader) |
125 | 125 |
"assets/shaders/tests/include_nested_extension.vert:7: #include \"include_nested_extension.h\"" |
126 | 126 |
) |
127 | 127 |
|
128 |
+ // Vertex inputs. |
|
129 |
+ constexpr auto vert_position = 0; |
|
130 |
+ constexpr auto vert_tex_coord = 1; |
|
131 |
+ Shader::verts({ |
|
132 |
+ {"vert_position", vert_position}, |
|
133 |
+ {"vert_tex_coord", vert_tex_coord}, |
|
134 |
+ }); |
|
135 |
+ GLTEST_EXPECT_EXCEPTION(false, |
|
136 |
+ Shader({"tests/vert_unset.vert"}), |
|
137 |
+ "Failed to initialize vertex input 'vert_unset' of shader program 'tests/vert_unset.vert'." |
|
138 |
+ ); |
|
139 |
+ |
|
128 | 140 |
auto all = Shader({ |
129 | 141 |
"tests/all.vert", |
130 | 142 |
"tests/all.frag", |