#include <glshader.hpp> #include <algorithm> #include <array> #include <cerrno> #include <cstring> #include <fstream> #include <memory> #include <regex> #include <sstream> #include <stdexcept> #include <string> #include <tuple> #include <utility> #include <vector> #include <GL/glew.h> #include <str.hpp> using Here = std::tuple<std::string, int, std::string>; // NOLINTNEXTLINE #define GLSHADER_INIT_(NAME, INIT) decltype(NAME) NAME INIT; GLSHADER_INIT_(Shader::root_, {}) GLSHADER_INIT_(Shader::defines_, {}) 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])) )}; } static std::string source_( std::string const & error, std::string const & path, std::string const & root, Shader::Defines const & defines ) { // Set here error. auto const here_error = [](Here const & here) { return STR( std::get<0>(here) << ":" << std::get<1>(here) << ": " << std::get<2>(here) ); }; // Set full path. auto path_full = path; if (!root.empty()) path_full = STR(root << "/" << path_full); // Define and open input stream. auto istream = std::ifstream{path_full}; if (!istream) throw std::runtime_error{STR( error << "; " << "could not open file '" << path_full << "':\n" << std::strerror(errno) )}; // Define output stream. auto ostream = std::ostringstream{}; // Define parse regexes. static auto const re_ignored = std::regex{R"(\s*//.*$)"}; static auto const re_words = std::regex{R"((\w+(?:\s+\w+)*))"}; static auto const re_version = std::regex{R"(\s*#\s*version\s*(.*))"}; // Parse. auto version_number = 0; auto line_number = 0; auto line = std::string{}; auto match = std::smatch{}; auto here = [&]() { return Here{path_full, line_number, line}; }; while (++line_number, std::getline(istream, line)) { // Remove ignored. auto const content = std::regex_replace(line, re_ignored, ""); // Process version. if (std::regex_match(content, match, re_version)) { // Parse. auto const words = match.str(1); if (!std::regex_match(words, match, re_words)) throw std::runtime_error{STR( error << "; " << "malformed #version:\n" << here_error(here()) )}; auto const version = match.str(1); // Check for errors. if (version_number) throw std::runtime_error{STR( error << "; " << "found repeated #version:\n" << here_error(here()) )}; // Process. version_number = std::stoi(version); // Output. ostream << line << "\n"; for (auto const & define : defines) ostream << "#define " << define.first << " " << define.second << "\n"; } // Non-processed line. else { // Output. ostream << line << "\n"; } } // Check for version. if (!version_number) throw std::runtime_error{STR( error << "; " << "found no #version." )}; // Return. return ostream.str(); } 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 const source = source_(source_error, path, root_, defines_); 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 ); // Assert current. current_(validate_error); } void Shader::current_(std::string const & error) const { // Error if not current. if (get_integer_<GLuint>(GL_CURRENT_PROGRAM) != program_) throw std::runtime_error{STR( error << "; " << "shader program not current." )}; }