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_);
}
|