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