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 *)¤t_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."
)};
}
}
|