shader.cpp
e0fdd020
 #include "shader.hpp"
 
 #include <vector>
 #include <string>
 #include <fstream>
 #include <stdexcept>
 #include <iostream>
 
 #include <GL/glew.h>
 
 #include "str.hpp"
 
 
d8d951a6
 Shader::StringCache<Shader::UniformBuffer>
 Shader::uniform_buffer_cache_{};
 
 GLuint
 Shader::uniform_buffer_binding_next_{0};
 
 
e0fdd020
 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<char> 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;
 }
 
 
d8d951a6
 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);
     }
 }
 
 
e0fdd020
 Shader::Shader(std::vector<std::string> paths, std::string name)
 :
     program_{0},
     paths_{std::move(paths)},
     name_{!name.empty()
         ? std::move(name)
         : STR_JOIN(", ", "'" << it << "'", paths_)
67ab19a4
     },
d8d951a6
     uniform_location_cache_{},
     uniform_block_index_cache_{}
e0fdd020
 {
     new_();
 }
 
 
 Shader::Shader(Shader const & other)
 :
d8d951a6
     program_                  {0},
     paths_                    {other.paths_},
     name_                     {other.name_},
     uniform_location_cache_   {other.uniform_location_cache_},
     uniform_block_index_cache_{other.uniform_block_index_cache_}
e0fdd020
 {
     new_();
 }
 
 
 Shader & Shader::operator=(Shader const & other)
 {
     delete_();
d8d951a6
     program_                   = 0;
     paths_                     = other.paths_;
     name_                      = other.name_;
     uniform_location_cache_    = other.uniform_location_cache_;
     uniform_block_index_cache_ = other.uniform_block_index_cache_;
e0fdd020
     new_();
     return *this;
 }
 
 
 Shader::Shader(Shader && other)
 :
d8d951a6
     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_)}
e0fdd020
 {
     other.program_ = 0;
 }
 
 
 Shader & Shader::operator=(Shader && other)
 {
     delete_();
d8d951a6
     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_);
e0fdd020
     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<GLuint> 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<char const *> 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;
     }
 }
 
 
d8d951a6
 void Shader::delete_() {
e0fdd020
     // Delete program (and detach and delete shaders).
     glDeleteProgram(program_);
 }
 
 
d8d951a6
 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);
 }
 
 
e0fdd020
 void Shader::ensure_current_(
     std::string const & operation,
     std::string const & name
 )
 {
     GLuint current_program;
     glGetIntegerv(GL_CURRENT_PROGRAM, (GLint *)&current_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."
         )};
     }
 }
67ab19a4
 
 
 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;
 }
d8d951a6
 
 
 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;
 }