From 937bdc530f65faae7c4eeb1559abc2e36b157b2b Mon Sep 17 00:00:00 2001 From: gradient Date: Mon, 29 May 2017 11:47:14 -0500 Subject: [PATCH] Add project and shader models, and (de)serialization of projects --- src/project.cpp | 49 +++++++++++++++++ src/project.h | 39 ++++++++++++++ src/shaders.cpp | 162 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/shaders.h | 52 ++++++++++++++++++ 4 files changed, 302 insertions(+) create mode 100644 src/project.cpp create mode 100644 src/project.h create mode 100644 src/shaders.cpp create mode 100644 src/shaders.h diff --git a/src/project.cpp b/src/project.cpp new file mode 100644 index 0000000..a8dc39e --- /dev/null +++ b/src/project.cpp @@ -0,0 +1,49 @@ +#include "project.h" +#include + +std::unique_ptr Project::fromFile(std::string path) { + auto manifest = cpptoml::parse_file(path); + auto basepath = filesystem::path(path).parent_path().make_absolute(); + return Project::fromToml(manifest, basepath); +} + +std::unique_ptr Project::fromToml(std::shared_ptr manifest, + filesystem::path basepath = filesystem::path(".")) { + auto project = Project(); + project.name = manifest->get_qualified_as("project.name").value_or(""); + project.shader_bundle = std::move(std::make_shared()); + + auto sceneconf = manifest->get_table_array("scene"); + if (sceneconf) { + for (const auto& conf_scene : *sceneconf) { + auto scene = std::make_shared(); + scene->name = conf_scene->get_as("name").value_or(""); + auto p_frag = conf_scene->get_as("frag"); + auto p_vert = conf_scene->get_as("vert"); + + if (p_frag && p_vert) { + std::vector> shaders; + shaders.push_back(std::make_pair((basepath / *p_vert).str(), GL_VERTEX_SHADER)); + shaders.push_back(std::make_pair((basepath / *p_frag).str(), GL_FRAGMENT_SHADER)); + scene->shader_p = project.shader_bundle->add_program(shaders); + } + + project.scenes.push_back(std::move(scene)); + } + } + + auto boot_scene = manifest->get_qualified_as("project.boot_scene"); + if (boot_scene) { + printf("loading boot scene %i\n", *boot_scene); + project.load_scene(project.scenes[*boot_scene]); + + project.shader_bundle->recompile().link(); + } + + return std::make_unique(project); +} + +void Project::load_scene(std::shared_ptr scene) { + this->current_scene = scene; + this->scene_loaded = true; +} \ No newline at end of file diff --git a/src/project.h b/src/project.h new file mode 100644 index 0000000..1d9dc69 --- /dev/null +++ b/src/project.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include +#include + +#include "shaders.h" +#include "pipeline/post.h" + +class Scene { +public: + std::string name; + + ShaderBundle::ProgramHandle* shader_p; + + PostConfig postConfig; +}; + +class NullScene : public Scene { +}; + +class Project { +public: + Project() = default; + + static std::unique_ptr fromFile(std::string path); + static std::unique_ptr fromToml(std::shared_ptr manifest, filesystem::path basepath); + + void load_scene(std::shared_ptr scene); + + + std::vector> scenes; + std::shared_ptr current_scene; + bool scene_loaded = false; + std::string name; + + std::shared_ptr shader_bundle; +}; \ No newline at end of file diff --git a/src/shaders.cpp b/src/shaders.cpp new file mode 100644 index 0000000..e3e1b3f --- /dev/null +++ b/src/shaders.cpp @@ -0,0 +1,162 @@ +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 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>& shaderPairs) { + std::vector 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()(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 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 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, Program>& program : this->programs) { + bool needs_relink = true; + /*for (const ShaderId p_shader : program.first) { + for (std::pair 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); +} \ No newline at end of file diff --git a/src/shaders.h b/src/shaders.h new file mode 100644 index 0000000..972ecd0 --- /dev/null +++ b/src/shaders.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "filesystem/path.h" + +class ShaderBundle { +public: + using ShaderHandle = GLuint; + using ProgramHandle = GLuint; + using ShaderType = GLenum; + + struct ShaderId { + std::string name; + ShaderType type; + bool operator<(const ShaderId& rhs) const { + return std::tie(name, type) < std::tie(rhs.name, rhs.type); + } + bool operator==(const ShaderId& rhs) const { + return std::tie(name, type) == std::tie(rhs.name, rhs.type); + } + }; + + struct Shader { + ShaderHandle handle; + + int32_t hash; + }; + + struct Program { + // public handle; 0 until the program has successfully linked + ProgramHandle handle; + + ProgramHandle internal_handle; + }; + + ShaderBundle() = default; + ~ShaderBundle(); + + std::map shader_pool; + std::map, Program> programs; + + ProgramHandle* add_program(const std::vector>& shaders); + + ShaderBundle& recompile(); + ShaderBundle& link(); + + static std::string preprocess(filesystem::path path, std::string shader); +}; \ No newline at end of file