src/glshader.cpp
fa2758de
 #include <glshader.hpp>
 
 #include <algorithm>
 #include <array>
 #include <cerrno>
 #include <cstring>
 #include <fstream>
 #include <memory>
 #include <stdexcept>
 #include <string>
 #include <utility>
 #include <vector>
 
 #include <GL/glew.h>
 
 #include <str.hpp>
 
 
 // NOLINTNEXTLINE
 #define GLSHADER_INIT_(NAME, INIT) decltype(NAME) NAME INIT;
 GLSHADER_INIT_(Shader::root_, {})
 
 
 template<typename Type>
 static Type get_integer_(GLenum name, bool supported = true)
 {
     auto data = GLint{};
     if (supported)
         glGetIntegerv(name, &data);
     return (Type)data;
 }
 
 
 static void info_log_action_(
     std::string const & error,
     void (action)(GLuint object),
     GLuint object,
     GLenum status_enum,
     void (GLAPIENTRY * getObjectiv)(
         GLuint object, GLenum pname, GLint * params
     ),
     void (GLAPIENTRY * getObjectInfoLog)(
         GLuint object, GLsizei max_length, GLsizei * length, GLchar * info_log
     )
 )
 {
     // Perform action.
     action(object);
 
     // Check status.
     auto status = GLint{};
     getObjectiv(object, status_enum, &status);
     if (status)
         return;
 
     // Get info log length.
     auto info_log_length = GLint{};
     getObjectiv(object, GL_INFO_LOG_LENGTH, &info_log_length);
 
     // Get info log content.
     // NOLINTNEXTLINE
     auto info_log = std::unique_ptr<GLchar[]>(new GLchar[info_log_length]);
     if (info_log_length)
         getObjectInfoLog(object, info_log_length, nullptr, &info_log[0]);
 
     // Throw.
     throw std::runtime_error{STR(
         error << (!info_log_length ? "." : STR(":\n" << &info_log[0]))
     )};
 }
 
 
 Shader::Shader(Paths const & paths)
 :
     program_{0},
     program_name_{STR(
         "shader program " << STR_JOIN(", ", it, "'" << it << "'", paths)
     )}
 {
     // Get label limits.
     static auto const max_label_length = get_integer_<GLsizei>(
         GL_MAX_LABEL_LENGTH, GLEW_VERSION_4_3 || GLEW_KHR_debug
     );
 
     try
     {
         // Create program.
         program_ = glCreateProgram();
         if (!program_)
             throw std::runtime_error{STR(
                 "Failed to create " << program_name_ << "."
             )};
 
         // Label program.
         if (GLEW_VERSION_4_3 || GLEW_KHR_debug)
             glObjectLabel(
                 GL_PROGRAM,
                 program_,
                 std::min(max_label_length, (GLsizei)program_name_.length()),
                 program_name_.c_str()
             );
 
         // Process shader paths.
         auto shaders = std::vector<GLuint>{};
         shaders.reserve(paths.size());
         for (auto const & path : paths)
         {
             // Set shader name.
             auto const shader_name = STR(
                 "shader '" << path << "' of " << program_name_
             );
 
             // Infer shader type from path extension.
             auto const type_error = STR(
                 "Failed to infer type of " << shader_name
             );
             auto const type_pos = path.rfind('.');
             if (type_pos == path.npos)
                 throw std::runtime_error{STR(
                     type_error << "; " <<
                     "no file extension."
                 )};
             auto const type_name = path.substr(type_pos + 1);
             auto const type =
                 type_name == "vert" ? GL_VERTEX_SHADER :
                 type_name == "tesc" ? GL_TESS_CONTROL_SHADER :
                 type_name == "tese" ? GL_TESS_EVALUATION_SHADER :
                 type_name == "geom" ? GL_GEOMETRY_SHADER :
                 type_name == "frag" ? GL_FRAGMENT_SHADER :
                 type_name == "comp" ? GL_COMPUTE_SHADER :
                 GLenum{0};
             if (!type)
                 throw std::runtime_error{STR(
                     type_error << "; " <<
                     "unknown file extension '" << type_name << "'."
                 )};
 
             // Create, attach, and flag shader for deletion when detached.
             auto const shader = glCreateShader(type);
             if (!shader)
                 throw std::runtime_error{STR(
                     "Failed to create " << type_name << " shader for " <<
                     shader_name << "."
                 )};
             shaders.push_back(shader);
             glAttachShader(program_, shader);
             glDeleteShader(shader);
 
             // Label shader.
             if (GLEW_VERSION_4_3 || GLEW_KHR_debug)
                 glObjectLabel(
                     GL_SHADER,
                     shader,
                     std::min(max_label_length, (GLsizei)shader_name.length()),
                     shader_name.c_str()
                 );
 
             // Set shader source.
             auto const source_error = STR("Failed to source " << shader_name);
             auto path_full = path;
             if (!root_.empty())
                 path_full = STR(root_ << "/" << path_full);
             auto source_istream = std::ifstream{path_full};
             if (!source_istream)
                 throw std::runtime_error{STR(
                     source_error << "; " <<
                     "could not open file '" << path_full << "':\n" <<
                     std::strerror(errno)
                 )};
             auto const source = STR(source_istream.rdbuf());
             auto const sources = std::array<char const *, 1>{{
                 source.c_str()
             }};
             glShaderSource(shader, sources.size(), &sources[0], nullptr);
 
             // Compile shader.
             info_log_action_(
                 STR("Failed to compile " << shader_name),
                 glCompileShader, shader,
                 GL_COMPILE_STATUS, glGetShaderiv, glGetShaderInfoLog
             );
         }
 
         // Link program.
         info_log_action_(
             STR("Failed to link " << program_name_),
             glLinkProgram, program_,
             GL_LINK_STATUS, glGetProgramiv, glGetProgramInfoLog
         );
 
         // Detach shaders.
         for (auto const & shader : shaders)
             glDetachShader(program_, shader);
     }
     catch (...)
     {
         // Delete program (and detach and delete shaders).
         if (program_)
             glDeleteProgram(program_);
         throw;
     }
 }
 
 
 Shader::Shader(Shader && other) noexcept
 :
     program_     {other.program_},
     program_name_{std::move(other.program_name_)}
 {
     other.program_ = 0;
 }
 
 
 Shader::~Shader()
 {
     if (program_)
         glDeleteProgram(program_);
 }