e0fdd020 |
#include "shader.hpp"
#include <vector>
#include <string>
#include <fstream>
#include <stdexcept>
#include <iostream>
#include <GL/glew.h>
#include "str.hpp"
|
d8d951a6 |
Shader::StringCache<Shader::UniformBuffer>
Shader::uniform_buffer_cache_{};
GLuint
Shader::uniform_buffer_binding_next_{0};
|
e0fdd020 |
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;
}
|
d8d951a6 |
static std::string uniform_buffer_usage_str(GLenum usage)
{
switch(usage)
{
STR_CASE(GL_STREAM_DRAW)
STR_CASE(GL_STREAM_READ)
STR_CASE(GL_STREAM_COPY)
STR_CASE(GL_STATIC_DRAW)
STR_CASE(GL_STATIC_READ)
STR_CASE(GL_STATIC_COPY)
STR_CASE(GL_DYNAMIC_DRAW)
STR_CASE(GL_DYNAMIC_READ)
STR_CASE(GL_DYNAMIC_COPY)
default:
return STR(std::hex << std::showbase << usage);
}
}
|
e0fdd020 |
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_)
|
67ab19a4 |
},
|
d8d951a6 |
uniform_location_cache_{},
uniform_block_index_cache_{}
|
e0fdd020 |
{
new_();
}
Shader::Shader(Shader const & other)
:
|
d8d951a6 |
program_ {0},
paths_ {other.paths_},
name_ {other.name_},
uniform_location_cache_ {other.uniform_location_cache_},
uniform_block_index_cache_{other.uniform_block_index_cache_}
|
e0fdd020 |
{
new_();
}
Shader & Shader::operator=(Shader const & other)
{
delete_();
|
d8d951a6 |
program_ = 0;
paths_ = other.paths_;
name_ = other.name_;
uniform_location_cache_ = other.uniform_location_cache_;
uniform_block_index_cache_ = other.uniform_block_index_cache_;
|
e0fdd020 |
new_();
return *this;
}
Shader::Shader(Shader && other)
:
|
d8d951a6 |
program_ {std::move(other.program_)},
paths_ {std::move(other.paths_)},
name_ {std::move(other.name_)},
uniform_location_cache_ {std::move(other.uniform_location_cache_)},
uniform_block_index_cache_{std::move(other.uniform_block_index_cache_)}
|
e0fdd020 |
{
other.program_ = 0;
}
Shader & Shader::operator=(Shader && other)
{
delete_();
|
d8d951a6 |
program_ = std::move(other.program_);
paths_ = std::move(other.paths_);
name_ = std::move(other.name_);
uniform_location_cache_ = std::move(other.uniform_location_cache_);
uniform_block_index_cache_ = std::move(other.uniform_block_index_cache_);
|
e0fdd020 |
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;
}
}
|
d8d951a6 |
void Shader::delete_() {
|
e0fdd020 |
// Delete program (and detach and delete shaders).
glDeleteProgram(program_);
}
|
d8d951a6 |
Shader & Shader::uniform(
std::string const & name, std::string const & buffer_name
) {
// Find uniform buffer in cache.
auto cache_entry = uniform_buffer_cache_.find(buffer_name);
if (cache_entry == uniform_buffer_cache_.end())
throw std::runtime_error{STR(
"Failed to set uniform block '" << name << "' of shader program "
<< name << "; no uniform buffer '" << buffer_name << "'."
)};
auto const & uniform_buffer = cache_entry->second;
// Bind.
glUniformBlockBinding(
program_, uniform_block_index_(name), uniform_buffer.binding
);
// Return.
return *this;
}
void Shader::uniform_buffer_delete(std::string const & name) {
// Find uniform buffer in cache.
auto cache_entry = uniform_buffer_cache_.find(name);
if (cache_entry == uniform_buffer_cache_.end())
throw std::runtime_error{STR(
"Failed to delete uniform buffer '" << name << "'; does not exist."
)};
auto const & uniform_buffer = cache_entry->second;
// Delete buffer.
glDeleteBuffers(1, &uniform_buffer.buffer);
// Erase cache entries.
uniform_buffer_cache_.erase(cache_entry);
}
|
e0fdd020 |
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."
)};
}
}
|
67ab19a4 |
GLint Shader::uniform_location_(std::string const & name)
{
// Try cache.
auto cache_entry = uniform_location_cache_.find(name);
if (cache_entry != uniform_location_cache_.end())
return cache_entry->second;
// Query OpenGL.
auto location = glGetUniformLocation(program_, name.c_str());
if (location == -1)
throw std::runtime_error{STR(
"Failed to get location of uniform '" << name <<
"' of shader program " << name_ << "."
)};
// Save in cache and return.
uniform_location_cache_.emplace(name, location);
return location;
}
|
d8d951a6 |
GLuint Shader::uniform_block_index_(std::string const & name) {
// Try cache.
auto & cache = uniform_block_index_cache_;
auto cache_entry = cache.find(name);
if (cache_entry != cache.end())
return cache_entry->second;
// Query OpenGL.
auto index = glGetUniformBlockIndex(program_, name.c_str());
if (index == GL_INVALID_INDEX)
throw std::runtime_error{STR(
"Failed to get index of uniform block '" << name <<
"' in shader program " << name_ << "."
)};
// Save in cache and return.
cache.emplace(name, index);
return index;
}
GLuint Shader::uniform_buffer_(
std::string const & name, GLsizeiptr size, GLenum usage
) {
// Get usage string.
auto const & usage_str = uniform_buffer_usage_str(usage);
// Try cache.
auto cache_entry = uniform_buffer_cache_.find(name);
if (cache_entry != uniform_buffer_cache_.end()) {
// Get uniform buffer.
auto const & uniform_buffer = cache_entry->second;
// Check size and usage.
if (size != uniform_buffer.size)
throw std::runtime_error{STR(
"Failed to set data of uniform buffer '" << name <<
"'; data has size " << size << " but buffer has size " <<
uniform_buffer.size << "."
)};
if (usage != uniform_buffer.usage)
throw std::runtime_error{STR(
"Failed to set data of uniform buffer '" << name <<
"'; data has usage " << usage_str << " but buffer has usage "
<< uniform_buffer_usage_str(uniform_buffer.usage) << "."
)};
// Return.
return uniform_buffer.buffer;
}
// Validate size and usage.
if (size <= 0)
throw std::runtime_error{STR(
"Failed to create uniform buffer '" << name << "', invalid size "
<< size << "."
)};
if (usage_str.rfind("GL_", 0) != 0)
throw std::runtime_error{STR(
"Failed to create uniform buffer '" << name << "', invalid usage "
<< usage_str << "."
)};
// Check max uniform buffer bindings.
GLuint max_uniform_buffer_bindings;
glGetIntegerv(
GL_MAX_UNIFORM_BUFFER_BINDINGS,
(GLint *)&max_uniform_buffer_bindings
);
if (uniform_buffer_binding_next_ >= max_uniform_buffer_bindings)
throw std::runtime_error{STR(
"Failed to bind uniform buffer '" << name << "'; max bindings of "
<< max_uniform_buffer_bindings << " exceeded."
)};
// Create uniform buffer.
UniformBuffer uniform_buffer;
// Generate and bind.
glGenBuffers(1, &uniform_buffer.buffer);
glBindBuffer(GL_UNIFORM_BUFFER, uniform_buffer.buffer),
// Set size and usage.
uniform_buffer.size = size;
uniform_buffer.usage = usage;
glBufferData(
GL_UNIFORM_BUFFER, uniform_buffer.size, nullptr, uniform_buffer.usage
);
// Allocate binding and bind.
uniform_buffer.binding = uniform_buffer_binding_next_++;
glBindBufferBase(
GL_UNIFORM_BUFFER, uniform_buffer.binding, uniform_buffer.buffer
);
// Save in cache and return.
uniform_buffer_cache_.emplace(name, uniform_buffer);
return uniform_buffer.buffer;
}
|