#include "shader.hpp" #include #include #include #include #include #include #include "str.hpp" Shader::StringCache Shader::uniform_buffer_cache_{}; GLuint Shader::uniform_buffer_binding_next_{0}; static void checked_action( void (glAction)( GLuint object ), GLuint object, GLenum status_enum, void (glGetObjectiv)( GLuint object, GLenum pname, GLint *params ), void (glGetObjectInfoLog)( GLuint object, GLsizei maxLength, GLsizei *length, GLchar *infoLog ), std::string const & what ) { // Perform action. glAction(object); // Check status. GLint status; glGetObjectiv(object, status_enum, &status); if (status) return; // Get info log length. GLint info_log_length; glGetObjectiv(object, GL_INFO_LOG_LENGTH, &info_log_length); // Get info log content. std::string info_log; if (info_log_length) { std::vector info_log_buf(info_log_length); glGetObjectInfoLog(object, info_log_length, nullptr, &info_log_buf[0]); info_log = STR("\n" << &info_log[0]); } // Throw. throw std::runtime_error{STR(what << info_log)}; } static GLenum shader_type(std::string type_str) { return type_str == "vert" ? GL_VERTEX_SHADER : type_str == "tesc" ? GL_TESS_CONTROL_SHADER : type_str == "tese" ? GL_TESS_EVALUATION_SHADER : type_str == "geom" ? GL_GEOMETRY_SHADER : type_str == "frag" ? GL_FRAGMENT_SHADER : type_str == "comp" ? GL_COMPUTE_SHADER : 0; } static std::string uniform_buffer_usage_str(GLenum usage) { switch(usage) { STR_CASE(GL_STREAM_DRAW) STR_CASE(GL_STREAM_READ) STR_CASE(GL_STREAM_COPY) STR_CASE(GL_STATIC_DRAW) STR_CASE(GL_STATIC_READ) STR_CASE(GL_STATIC_COPY) STR_CASE(GL_DYNAMIC_DRAW) STR_CASE(GL_DYNAMIC_READ) STR_CASE(GL_DYNAMIC_COPY) default: return STR(std::hex << std::showbase << usage); } } Shader::Shader(std::vector paths, std::string name) : program_{0}, paths_{std::move(paths)}, name_{!name.empty() ? std::move(name) : STR_JOIN(", ", "'" << it << "'", paths_) }, uniform_location_cache_{}, uniform_block_index_cache_{} { new_(); } Shader::Shader(Shader const & other) : program_ {0}, paths_ {other.paths_}, name_ {other.name_}, uniform_location_cache_ {other.uniform_location_cache_}, uniform_block_index_cache_{other.uniform_block_index_cache_} { new_(); } Shader & Shader::operator=(Shader const & other) { delete_(); program_ = 0; paths_ = other.paths_; name_ = other.name_; uniform_location_cache_ = other.uniform_location_cache_; uniform_block_index_cache_ = other.uniform_block_index_cache_; new_(); return *this; } Shader::Shader(Shader && other) : program_ {std::move(other.program_)}, paths_ {std::move(other.paths_)}, name_ {std::move(other.name_)}, uniform_location_cache_ {std::move(other.uniform_location_cache_)}, uniform_block_index_cache_{std::move(other.uniform_block_index_cache_)} { other.program_ = 0; } Shader & Shader::operator=(Shader && other) { delete_(); program_ = std::move(other.program_); paths_ = std::move(other.paths_); name_ = std::move(other.name_); uniform_location_cache_ = std::move(other.uniform_location_cache_); uniform_block_index_cache_ = std::move(other.uniform_block_index_cache_); other.program_ = 0; return *this; } Shader::~Shader() { delete_(); } Shader & Shader::use() { glUseProgram(program_); return *this; } Shader & Shader::validate() { // Validate shader and check for errors. checked_action( glValidateProgram, program_, GL_VALIDATE_STATUS, glGetProgramiv, glGetProgramInfoLog, STR("Failed to validate shader program " << name_ << ".") ); return *this; } void Shader::new_() { try { // Create program. program_ = glCreateProgram(); if (!program_) throw std::runtime_error{STR( "Failed to create shader program for " << name_ << "." )}; // Label program. glObjectLabel(GL_PROGRAM, program_, -1, name_.c_str()); // Process shader paths. std::vector shaders; for (auto const & path : paths_) { // Infer shader type from path extension. // https://www.khronos.org/opengles/sdk/tools/Reference-Compiler/ auto pos = path.rfind("."); if (pos == path.npos) throw std::runtime_error{STR( "Failed to infer shader type of '" << path << "' of shader program " << name_ << "; no file extension." )}; auto type_str = path.substr(pos + 1); auto type = shader_type(type_str); if (!type) throw std::runtime_error{STR( "Failed to infer shader type of '" << path << "' of shader program " << name_ << "; unknown file extension '" << type_str << "'." )}; // Create, attach, and flag shader for deletion when detached. auto shader = glCreateShader(type); if (!shader) throw std::runtime_error{STR( "Failed to create " << type_str << " shader for " << path << " of shader program " << name_ << "." )}; glAttachShader(program_, shader); glDeleteShader(shader); shaders.push_back(shader); // Label shader. glObjectLabel(GL_SHADER, shader, -1, path.c_str()); // Set shader source. std::ifstream source_file(path); if (!source_file) throw std::runtime_error{STR( "Failed to open " << type_str << " shader '" << path << "' of shader program " << name_ << "." )}; auto source_str = STR(source_file.rdbuf()); std::vector sources = { source_str.c_str() }; glShaderSource(shader, sources.size(), &sources[0], nullptr); // Compile shader and check for errors. checked_action( glCompileShader, shader, GL_COMPILE_STATUS, glGetShaderiv, glGetShaderInfoLog, STR( "Failed to compile " << type_str << " shader '" << path << "' of shader program " << name_ << "." ) ); } // Link program and check for errors. checked_action( glLinkProgram, program_, GL_LINK_STATUS, glGetProgramiv, glGetProgramInfoLog, STR("Failed to link shader program " << name_ << ".") ); // Detach shaders. for (auto shader : shaders) glDetachShader(program_, shader); } catch (...) { delete_(); throw; } } void Shader::delete_() { // Delete program (and detach and delete shaders). glDeleteProgram(program_); } Shader & Shader::uniform( std::string const & name, std::string const & buffer_name ) { // Find uniform buffer in cache. auto cache_entry = uniform_buffer_cache_.find(buffer_name); if (cache_entry == uniform_buffer_cache_.end()) throw std::runtime_error{STR( "Failed to set uniform block '" << name << "' of shader program " << name << "; no uniform buffer '" << buffer_name << "'." )}; auto const & uniform_buffer = cache_entry->second; // Bind. glUniformBlockBinding( program_, uniform_block_index_(name), uniform_buffer.binding ); // Return. return *this; } void Shader::uniform_buffer_delete(std::string const & name) { // Find uniform buffer in cache. auto cache_entry = uniform_buffer_cache_.find(name); if (cache_entry == uniform_buffer_cache_.end()) throw std::runtime_error{STR( "Failed to delete uniform buffer '" << name << "'; does not exist." )}; auto const & uniform_buffer = cache_entry->second; // Delete buffer. glDeleteBuffers(1, &uniform_buffer.buffer); // Erase cache entries. uniform_buffer_cache_.erase(cache_entry); } void Shader::ensure_current_( std::string const & operation, std::string const & name ) { GLuint current_program; glGetIntegerv(GL_CURRENT_PROGRAM, (GLint *)¤t_program); if (current_program != program_) { auto action = name.empty() ? operation : STR(operation << " '" << name << "'"); throw std::runtime_error{STR( "Failed to " << action << " of shader program " << name_ << "; program is not current." )}; } } GLint Shader::uniform_location_(std::string const & name) { // Try cache. auto cache_entry = uniform_location_cache_.find(name); if (cache_entry != uniform_location_cache_.end()) return cache_entry->second; // Query OpenGL. auto location = glGetUniformLocation(program_, name.c_str()); if (location == -1) throw std::runtime_error{STR( "Failed to get location of uniform '" << name << "' of shader program " << name_ << "." )}; // Save in cache and return. uniform_location_cache_.emplace(name, location); return location; } GLuint Shader::uniform_block_index_(std::string const & name) { // Try cache. auto & cache = uniform_block_index_cache_; auto cache_entry = cache.find(name); if (cache_entry != cache.end()) return cache_entry->second; // Query OpenGL. auto index = glGetUniformBlockIndex(program_, name.c_str()); if (index == GL_INVALID_INDEX) throw std::runtime_error{STR( "Failed to get index of uniform block '" << name << "' in shader program " << name_ << "." )}; // Save in cache and return. cache.emplace(name, index); return index; } GLuint Shader::uniform_buffer_( std::string const & name, GLsizeiptr size, GLenum usage ) { // Get usage string. auto const & usage_str = uniform_buffer_usage_str(usage); // Try cache. auto cache_entry = uniform_buffer_cache_.find(name); if (cache_entry != uniform_buffer_cache_.end()) { // Get uniform buffer. auto const & uniform_buffer = cache_entry->second; // Check size and usage. if (size != uniform_buffer.size) throw std::runtime_error{STR( "Failed to set data of uniform buffer '" << name << "'; data has size " << size << " but buffer has size " << uniform_buffer.size << "." )}; if (usage != uniform_buffer.usage) throw std::runtime_error{STR( "Failed to set data of uniform buffer '" << name << "'; data has usage " << usage_str << " but buffer has usage " << uniform_buffer_usage_str(uniform_buffer.usage) << "." )}; // Return. return uniform_buffer.buffer; } // Validate size and usage. if (size <= 0) throw std::runtime_error{STR( "Failed to create uniform buffer '" << name << "', invalid size " << size << "." )}; if (usage_str.rfind("GL_", 0) != 0) throw std::runtime_error{STR( "Failed to create uniform buffer '" << name << "', invalid usage " << usage_str << "." )}; // Check max uniform buffer bindings. GLuint max_uniform_buffer_bindings; glGetIntegerv( GL_MAX_UNIFORM_BUFFER_BINDINGS, (GLint *)&max_uniform_buffer_bindings ); if (uniform_buffer_binding_next_ >= max_uniform_buffer_bindings) throw std::runtime_error{STR( "Failed to bind uniform buffer '" << name << "'; max bindings of " << max_uniform_buffer_bindings << " exceeded." )}; // Create uniform buffer. UniformBuffer uniform_buffer; // Generate and bind. glGenBuffers(1, &uniform_buffer.buffer); glBindBuffer(GL_UNIFORM_BUFFER, uniform_buffer.buffer), // Set size and usage. uniform_buffer.size = size; uniform_buffer.usage = usage; glBufferData( GL_UNIFORM_BUFFER, uniform_buffer.size, nullptr, uniform_buffer.usage ); // Allocate binding and bind. uniform_buffer.binding = uniform_buffer_binding_next_++; glBindBufferBase( GL_UNIFORM_BUFFER, uniform_buffer.binding, uniform_buffer.buffer ); // Save in cache and return. uniform_buffer_cache_.emplace(name, uniform_buffer); return uniform_buffer.buffer; }