|
|
@ -0,0 +1,162 @@ |
|
|
|
#include <iostream>
|
|
|
|
#include <fstream>
|
|
|
|
#include <sstream>
|
|
|
|
#include <string>
|
|
|
|
#include <utility>
|
|
|
|
#include <cerrno>
|
|
|
|
|
|
|
|
#include <GL/gl3w.h>
|
|
|
|
#include <algorithm>
|
|
|
|
|
|
|
|
#include "shaders.h"
|
|
|
|
|
|
|
|
static std::string string_from_file(std::string path) { |
|
|
|
std::ifstream in(path, std::ios::in); |
|
|
|
if (!in) { |
|
|
|
std::cerr << "Opening file " << path << " failed.\n"; |
|
|
|
} |
|
|
|
std::ostringstream contents; |
|
|
|
contents << in.rdbuf(); |
|
|
|
in.close(); |
|
|
|
|
|
|
|
return contents.str(); |
|
|
|
} |
|
|
|
|
|
|
|
ShaderBundle::~ShaderBundle() { |
|
|
|
for (std::pair<ShaderBundle::ShaderId, ShaderBundle::Shader> shader : this->shader_pool) { |
|
|
|
glDeleteShader(shader.second.handle); |
|
|
|
} |
|
|
|
|
|
|
|
for (auto& program : this->programs) { |
|
|
|
glDeleteProgram(program.second.internal_handle); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
ShaderBundle::ProgramHandle* ShaderBundle::add_program( |
|
|
|
const std::vector<std::pair<std::string, ShaderType>>& shaderPairs) { |
|
|
|
std::vector<ShaderId> shader_ids; |
|
|
|
for (auto shaderPair : shaderPairs) { |
|
|
|
ShaderId shader_id; |
|
|
|
std::tie(shader_id.name, shader_id.type) = shaderPair; |
|
|
|
|
|
|
|
auto upserted = this->shader_pool.emplace(std::move(shader_id), Shader()).first; |
|
|
|
// if the shader doesn't have a handle from OpenGL, get one
|
|
|
|
if (!upserted->second.handle) { |
|
|
|
upserted->second.handle = glCreateShader(shaderPair.second); |
|
|
|
upserted->second.hash = |
|
|
|
(int32_t)std::hash<std::string>()(shaderPair.first) & 0x7FFFFFFF; |
|
|
|
} |
|
|
|
shader_ids.push_back(upserted->first); |
|
|
|
} |
|
|
|
|
|
|
|
std::sort(shader_ids.begin(), shader_ids.end()); |
|
|
|
shader_ids.erase(std::unique(shader_ids.begin(), shader_ids.end()), shader_ids.end()); |
|
|
|
|
|
|
|
auto upserted = this->programs.emplace(shader_ids, Program()).first; |
|
|
|
if (!upserted->second.internal_handle) { |
|
|
|
upserted->second.handle = 0; |
|
|
|
upserted->second.internal_handle = glCreateProgram(); |
|
|
|
for (const auto shader : shader_ids) { |
|
|
|
glAttachShader(upserted->second.internal_handle, this->shader_pool[shader].handle); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return &upserted->second.handle; |
|
|
|
} |
|
|
|
|
|
|
|
ShaderBundle& ShaderBundle::recompile() { |
|
|
|
for (std::pair<const ShaderId, Shader> shader : this->shader_pool) { |
|
|
|
auto source_s = string_from_file(shader.first.name); |
|
|
|
source_s = ShaderBundle::preprocess(filesystem::path(shader.first.name), source_s); |
|
|
|
const char *source_buf = source_s.c_str(); |
|
|
|
glShaderSource(shader.second.handle, 1, &source_buf, NULL); |
|
|
|
glCompileShader(shader.second.handle); |
|
|
|
|
|
|
|
GLint status; |
|
|
|
glGetShaderiv(shader.second.handle, GL_COMPILE_STATUS, &status); |
|
|
|
if (!status) { |
|
|
|
GLint log_len; |
|
|
|
glGetShaderiv(shader.second.handle, GL_INFO_LOG_LENGTH, &log_len); |
|
|
|
|
|
|
|
std::vector<char> log(log_len + 1); |
|
|
|
glGetShaderInfoLog(shader.second.handle, log_len, NULL, log.data()); |
|
|
|
|
|
|
|
std::string log_s = log.data(); |
|
|
|
|
|
|
|
std::cerr << "Error compiling: " << log_s << '\n'; |
|
|
|
} |
|
|
|
std::cout << "Successfully compiled " << shader.first.name << '\n'; |
|
|
|
} |
|
|
|
|
|
|
|
return *this; |
|
|
|
} |
|
|
|
|
|
|
|
ShaderBundle& ShaderBundle::link() { |
|
|
|
for (std::pair<const std::vector<ShaderId>, Program>& program : this->programs) { |
|
|
|
bool needs_relink = true; |
|
|
|
/*for (const ShaderId p_shader : program.first) {
|
|
|
|
for (std::pair<const ShaderId, Shader> shader : this->shader_pool) { |
|
|
|
if (shader.first == p_shader) { |
|
|
|
needs_relink = true; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (needs_relink) |
|
|
|
break; |
|
|
|
}*/ |
|
|
|
|
|
|
|
// Don't relink programs that depend on shaders for which compilation failed
|
|
|
|
bool can_relink = true; |
|
|
|
if (needs_relink) { |
|
|
|
for (const ShaderId p_shader : program.first) { |
|
|
|
GLint status; |
|
|
|
glGetShaderiv(this->shader_pool[p_shader].handle, GL_COMPILE_STATUS, &status); |
|
|
|
if (!status) { |
|
|
|
can_relink = false; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (needs_relink && can_relink) { |
|
|
|
glLinkProgram(program.second.internal_handle); |
|
|
|
|
|
|
|
GLint status; |
|
|
|
glGetProgramiv(program.second.internal_handle, GL_LINK_STATUS, &status); |
|
|
|
if (!status) { |
|
|
|
std::cerr << "Error linking shaders!\n"; |
|
|
|
program.second.handle = 0; |
|
|
|
} else { |
|
|
|
std::cout << "Successfully linked shaders\n"; |
|
|
|
} |
|
|
|
program.second.handle = program.second.internal_handle; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return *this; |
|
|
|
} |
|
|
|
|
|
|
|
std::string ShaderBundle::preprocess(filesystem::path path, std::string shader) { |
|
|
|
// TODO: this is probably not idiomatic C++; perhaps use a stringstream?
|
|
|
|
// TODO: proper erroring out.
|
|
|
|
|
|
|
|
std::string inc = "\n#include "; |
|
|
|
auto directive_idx = shader.find(inc); |
|
|
|
|
|
|
|
// if we didn't find an include directive, return the original shader.
|
|
|
|
if (directive_idx == std::string::npos) |
|
|
|
return shader; |
|
|
|
|
|
|
|
// skip to the end of the directive. read until \n to get the included file.
|
|
|
|
auto idx_end = directive_idx + inc.length(); |
|
|
|
auto idx_nl = shader.find('\n', idx_end); |
|
|
|
|
|
|
|
auto file_str = shader.substr(idx_end + 1, idx_nl - idx_end - 2); |
|
|
|
auto file_path = path.parent_path().make_absolute() / file_str; |
|
|
|
|
|
|
|
auto included_glsl = preprocess(file_path, string_from_file(file_path.str())); |
|
|
|
shader.replace(directive_idx+1, idx_nl - directive_idx - 1, included_glsl); |
|
|
|
|
|
|
|
return preprocess(path, shader); |
|
|
|
} |