| ... | ... | @@ -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", |