diff --git a/libopenage/renderer/opengl/shader_program.cpp b/libopenage/renderer/opengl/shader_program.cpp index af7d0b6e330..428ece7c9a3 100644 --- a/libopenage/renderer/opengl/shader_program.cpp +++ b/libopenage/renderer/opengl/shader_program.cpp @@ -3,6 +3,7 @@ #include "shader_program.h" #include +#include #include "../../error/error.h" #include "../../log/log.h" @@ -47,7 +48,8 @@ static void check_program_status(GLuint program, GLenum what_to_check) { } GlShaderProgram::GlShaderProgram(const std::vector &srcs, const gl_context_capabilities &caps) - : GlSimpleObject([] (GLuint handle) { glDeleteProgram(handle); } ) { + : GlSimpleObject([] (GLuint handle) { glDeleteProgram(handle); } ) + , validated(false) { GLuint handle = glCreateProgram(); this->handle = handle; @@ -61,9 +63,6 @@ GlShaderProgram::GlShaderProgram(const std::vector &src glLinkProgram(handle); check_program_status(handle, GL_LINK_STATUS); - glValidateProgram(handle); - check_program_status(handle, GL_VALIDATE_STATUS); - // after linking we can delete the shaders for (auto const& shdr : shaders) { glDetachShader(handle, shdr.get_handle()); @@ -85,44 +84,12 @@ GlShaderProgram::GlShaderProgram(const std::vector &src max_name_len = std::max(size_t(val), max_name_len); std::vector name(max_name_len); - - GLuint tex_unit = 0; - for (GLuint i_unif = 0; i_unif < unif_count; ++i_unif) { - GLint count; - GLenum type; - glGetActiveUniform( - handle, - i_unif, - name.size(), - nullptr, - &count, - &type, - name.data() - ); - - this->uniforms.insert(std::make_pair( - name.data(), - GlUniform { - type, - GLint(i_unif), - size_t(count) * GL_SHADER_TYPE_SIZE.get(type), - {}, - 0, 0, size_t(count) - } - )); - - if (type == GL_SAMPLER_2D) { - ENSURE(tex_unit < caps.max_texture_slots, - "Tried to create an OpenGL shader that uses more texture sampler uniforms " - << "than there are texture unit slots (" << caps.max_texture_slots << " available)." - ); - - this->texunits_per_unifs.insert(std::make_pair(name.data(), tex_unit)); - tex_unit += 1; - } - } + // Indices of uniforms within named blocks. + std::unordered_set in_block_unifs; GLuint block_binding = 0; + + // Extract uniform block descriptions. for (GLuint i_unif_block = 0; i_unif_block < unif_block_count; ++i_unif_block) { glGetActiveUniformBlockName( handle, @@ -141,51 +108,111 @@ GlShaderProgram::GlShaderProgram(const std::vector &src std::vector uniform_indices(val); glGetActiveUniformBlockiv(handle, i_unif_block, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, uniform_indices.data()); - // Names of uniforms within this block - std::vector uniform_names{}; + std::unordered_map uniforms; for (GLuint const i_unif : uniform_indices) { - glGetActiveUniformName(handle, i_unif, name.size(), nullptr, name.data()); - try { - auto& unif = this->uniforms.at(name.data()); - unif.block_name = block_name; - uniform_names.push_back(name.data()); - - glGetActiveUniformsiv(handle, 1, &i_unif, GL_UNIFORM_OFFSET, &val); - unif.offset = val; - glGetActiveUniformsiv(handle, 1, &i_unif, GL_UNIFORM_ARRAY_STRIDE, &val); - unif.stride = val; - if (unif.stride == 0) { - // The uniform is not an array, but it's declared in a named block and hence might - // be a matrix whose stride we need to know. - glGetActiveUniformsiv(handle, 1, &i_unif, GL_UNIFORM_MATRIX_STRIDE, &val); - unif.stride = val; - } - } catch (std::out_of_range const&) { - throw Error(MSG(err) << "Could not find OpenGL uniform " << i_unif - << " from block " << name.data() << " in uniform list."); + in_block_unifs.insert(i_unif); + + GLenum type; + GLint offset, count, stride; + + glGetActiveUniform( + handle, + i_unif, + name.size(), + nullptr, + &count, + &type, + name.data() + ); + + glGetActiveUniformsiv(handle, 1, &i_unif, GL_UNIFORM_OFFSET, &offset); + glGetActiveUniformsiv(handle, 1, &i_unif, GL_UNIFORM_ARRAY_STRIDE, &stride); + if (stride == 0) { + // The uniform is not an array, but it's declared in a named block and hence might + // be a matrix whose stride we need to know. + glGetActiveUniformsiv(handle, 1, &i_unif, GL_UNIFORM_MATRIX_STRIDE, &stride); } + + // We do not need to handle sampler types here like in the uniform loop below, + // because named blocks cannot contain samplers. + + uniforms.insert(std::make_pair( + name.data(), + GlInBlockUniform { + type, + size_t(offset), + size_t(count) * GL_SHADER_TYPE_SIZE.get(type), + size_t(stride), + size_t(count) + } + )); } + ENSURE(block_binding < caps.max_uniform_buffer_bindings, + "Tried to create an OpenGL shader that uses more uniform blocks " + << "than there are binding points (" << caps.max_uniform_buffer_bindings + << " available)." + ); + + glUniformBlockBinding(handle, i_unif_block, block_binding); + this->uniform_blocks.insert(std::make_pair( block_name, GlUniformBlock { - GLint(i_unif_block), + i_unif_block, size_t(data_size), - std::move(uniform_names), + std::move(uniforms), block_binding } )); - ENSURE(block_binding < caps.max_uniform_buffer_bindings, - "Tried to create an OpenGL shader that uses more uniform blocks " - << "than there are binding points (" << caps.max_uniform_buffer_bindings - << " available)." + block_binding += 1; + } + + GLuint tex_unit = 0; + + // Extract information about uniforms in the default block. + for (GLuint i_unif = 0; i_unif < unif_count; ++i_unif) { + if (in_block_unifs.count(i_unif) == 1) { + // Skip uniforms within named blocks. + continue; + } + + GLint count; + GLenum type; + glGetActiveUniform( + handle, + i_unif, + name.size(), + nullptr, + &count, + &type, + name.data() ); - glUniformBlockBinding(handle, i_unif_block, block_binding); - block_binding += 1; + GLuint loc = glGetUniformLocation(handle, name.data()); + + this->uniforms.insert(std::make_pair( + name.data(), + GlUniform { + type, + loc + } + )); + + if (type == GL_SAMPLER_2D) { + ENSURE(tex_unit < caps.max_texture_slots, + "Tried to create an OpenGL shader that uses more texture sampler uniforms " + << "than there are texture unit slots (" << caps.max_texture_slots << " available)." + ); + + this->texunits_per_unifs.insert(std::make_pair(name.data(), tex_unit)); + + tex_unit += 1; + } } + // Extract vertex attribute descriptions. for (GLuint i_attrib = 0; i_attrib < attrib_count; ++i_attrib) { GLint size; GLenum type; @@ -211,32 +238,47 @@ GlShaderProgram::GlShaderProgram(const std::vector &src log::log(MSG(info) << "Created OpenGL shader program"); - log::log(MSG(dbg) << "Uniform blocks: "); - for (auto const &pair : this->uniform_blocks) { - log::log(MSG(dbg) << "(" << pair.second.location << ") " << pair.first - << " (size " << pair.second.data_size << ") {"); - for (auto const& unif_name : pair.second.uniforms) { - auto const& unif = this->uniforms[unif_name]; - log::log(MSG(dbg) << "\t(" << unif.location << ") +" << unif.offset << " " - << unif_name << ": " << GLSL_TYPE_NAME.get(unif.type)); + if (!this->uniform_blocks.empty()) { + log::log(MSG(dbg) << "Uniform blocks: "); + for (auto const& pair : this->uniform_blocks) { + log::log(MSG(dbg) << "(" << pair.second.index << ") " << pair.first + << " (size: " << pair.second.data_size << ") {"); + for (auto const& unif_pair : pair.second.uniforms) { + log::log(MSG(dbg) << "\t+" << unif_pair.second.offset + << " " << unif_pair.first << ": " + << GLSL_TYPE_NAME.get(unif_pair.second.type)); + } + log::log(MSG(dbg) << "}"); } - log::log(MSG(dbg) << "}"); } - log::log(MSG(dbg) << "Uniforms: "); - for (auto const &pair : this->uniforms) { - if (!pair.second.block_name) { + + if (!this->uniforms.empty()) { + log::log(MSG(dbg) << "Uniforms: "); + for (auto const& pair : this->uniforms) { log::log(MSG(dbg) << "(" << pair.second.location << ") " << pair.first << ": " - << GLSL_TYPE_NAME.get(pair.second.type)); + << GLSL_TYPE_NAME.get(pair.second.type)); } } - log::log(MSG(dbg) << "Vertex attributes: "); - for (auto const &pair : this->attribs) { - log::log(MSG(dbg) << "(" << pair.second.location << ") " << pair.first << ": " - << GLSL_TYPE_NAME.get(pair.second.type)); + + if (!this->attribs.empty()) { + log::log(MSG(dbg) << "Vertex attributes: "); + for (auto const& pair : this->attribs) { + log::log(MSG(dbg) << "(" << pair.second.location << ") " << pair.first << ": " + << GLSL_TYPE_NAME.get(pair.second.type)); + } } } -void GlShaderProgram::use() const { +void GlShaderProgram::use() { + if (!this->validated) { + // TODO(Vtec234): validation depends on the context state, so this might be worth calling + // more than once. However, once per frame is probably too much. + glValidateProgram(*this->handle); + check_program_status(*this->handle, GL_VALIDATE_STATUS); + + this->validated = true; + } + glUseProgram(*this->handle); for (auto const &pair : this->textures_per_texunits) { diff --git a/libopenage/renderer/opengl/shader_program.h b/libopenage/renderer/opengl/shader_program.h index 08245e600d4..0fe2ee566a0 100644 --- a/libopenage/renderer/opengl/shader_program.h +++ b/libopenage/renderer/opengl/shader_program.h @@ -27,7 +27,7 @@ class GlShaderProgram final : public ShaderProgram, public GlSimpleObject { explicit GlShaderProgram(const std::vector&, const gl_context_capabilities&); /// Bind this program as the currently used one in the OpenGL context. - void use() const; + void use(); /// Does what the description of Renderable specifies - updates the uniform values /// and draws the Geometry if it's not nullptr. If geometry is null, only the @@ -53,21 +53,22 @@ class GlShaderProgram final : public ShaderProgram, public GlSimpleObject { private: void set_unif(UniformInput*, const char*, void const*, GLenum); - /// Represents a uniform location in the shader program. + /// Represents a uniform in the default block, i.e. not within a named block. struct GlUniform { GLenum type; - GLint location; - /// The size in bytes of the whole uniform. If the uniform is an array, - /// the size of the whole array. - size_t size; - /// If this uniform is within a uniform block, the block name, otherwise empty. - /// Its existence (or lack thereof) can be used to check whether the uniform - /// is in a named block. - std::optional block_name; + /// Location of the uniform for use with glUniform and glGetUniform. + /// NOT the same as the uniform index. + GLuint location; + }; - // The members below are only relevant for uniforms in named uniform blocks. + /// Represents a uniform in a named block. + struct GlInBlockUniform { + GLenum type; /// Offset from the beginning of the block at which this uniform is placed. size_t offset; + /// The size in bytes of the whole uniform. If the uniform is an array, + /// the size of the whole array. + size_t size; /// Only relevant for arrays and matrices. /// In arrays, specifies the distance between the start of each element. /// In row-major matrices, specifies the distance between the start of each row. @@ -79,12 +80,13 @@ class GlShaderProgram final : public ShaderProgram, public GlSimpleObject { /// Represents a uniform block in the shader program. struct GlUniformBlock { - GLint location; + GLuint index; /// Size of the entire block. How uniforms are packed within depends /// on the block layout and is described in corresponding GlUniforms. size_t data_size; - /// Names of the uniforms within this block. - std::vector uniforms; + /// Maps uniform names within this block to their descriptions. + std::unordered_map uniforms; + /// The binding point assigned to this block. GLuint binding_point; }; @@ -97,8 +99,8 @@ class GlShaderProgram final : public ShaderProgram, public GlSimpleObject { GLint size; }; - /// Maps uniform names to their descriptions. Contains all - /// uniforms, both within and outside of uniform blocks. + /// Maps uniform names to their descriptions. Contains only + /// uniforms in the default block, i.e. not within named blocks. std::unordered_map uniforms; /// Maps uniform block names to their descriptions. @@ -111,6 +113,9 @@ class GlShaderProgram final : public ShaderProgram, public GlSimpleObject { std::unordered_map texunits_per_unifs; /// Maps texture units to the texture handles that are currently bound to them. std::unordered_map textures_per_texunits; + + /// Whether this program has been validated. + bool validated; }; }}} // openage::renderer::opengl