#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_); } void Shader::validate_() const { // Set error. auto const validate_error = STR("Failed to validate " << program_name_); // Validate program. info_log_action_( validate_error, glValidateProgram, program_, GL_VALIDATE_STATUS, glGetProgramiv, glGetProgramInfoLog ); }