shader.cpp
e0fdd020
 #include "shader.hpp"
 
 #include <vector>
 #include <string>
 #include <fstream>
 #include <stdexcept>
 #include <iostream>
 
 #include <GL/glew.h>
 
 #include "str.hpp"
 
 
 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;
 }
 
 
 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_)
     }
 {
     new_();
 }
 
 
 Shader::Shader(Shader const & other)
 :
     program_{0},
     paths_  {other.paths_},
     name_   {other.name_}
 {
     new_();
 }
 
 
 Shader & Shader::operator=(Shader const & other)
 {
     delete_();
     program_ = 0;
     paths_   = other.paths_;
     name_    = other.name_;
     new_();
     return *this;
 }
 
 
 Shader::Shader(Shader && other)
 :
     program_{std::move(other.program_)},
     paths_  {std::move(other.paths_)},
     name_   {std::move(other.name_)}
 {
     other.program_ = 0;
 }
 
 
 Shader & Shader::operator=(Shader && other)
 {
     delete_();
     program_ = std::move(other.program_);
     paths_   = std::move(other.paths_);
     name_    = std::move(other.name_);
     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;
     }
 }
 
 
 void Shader::delete_()
 {
     // Delete program (and detach and delete shaders).
     glDeleteProgram(program_);
 }
 
 
 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."
         )};
     }
 }