diff options
Diffstat (limited to 'source/Shader.cpp')
-rw-r--r-- | source/Shader.cpp | 327 |
1 files changed, 327 insertions, 0 deletions
diff --git a/source/Shader.cpp b/source/Shader.cpp new file mode 100644 index 0000000..458491a --- /dev/null +++ b/source/Shader.cpp @@ -0,0 +1,327 @@ +#include "Shader.hpp" + +#include "ScopeGuard.hpp" + +#include <cstddef> +#include <fstream> +#include <iostream> +#include <sstream> +#include <utility> + +using namespace std::literals::string_literals; +using namespace std::literals::string_view_literals; + +Shader::~Shader() { + glDeleteProgram(mHandle); +} + +namespace ProjectBrussel_UNITY_ID { +Shader::ErrorCode LoadFile(std::string& out, const char* filePath) { + std::ifstream ifs(filePath); + if (!ifs) { + return Shader::FileIOFailed; + } + + std::stringstream buf; + buf << ifs.rdbuf(); + out = buf.str(); + + return Shader::Success; +} + +// Grabs section [begin, end) +Shader::ErrorCode CreateShader(GLuint& out, const char* src, int beginIdx, int endIdx, GLenum type) { + out = glCreateShader(type); + + const GLchar* begin = &src[beginIdx]; + const GLint len = endIdx - beginIdx; + glShaderSource(out, 1, &begin, &len); + + glCompileShader(out); + GLint compileStatus; + glGetShaderiv(out, GL_COMPILE_STATUS, &compileStatus); + if (compileStatus == GL_FALSE) { + GLint len; + glGetShaderiv(out, GL_INFO_LOG_LENGTH, &len); + + std::string log(len, '\0'); + glGetShaderInfoLog(out, len, nullptr, log.data()); + + return Shader ::CompilationFailed; + } + + return Shader::Success; +} + +Shader::ErrorCode CreateShader(GLuint& out, std::string_view str, GLenum type) { + return CreateShader(out, str.data(), 0, str.size(), type); +} + +Shader::ErrorCode LinkShaderProgram(GLuint program) { + glLinkProgram(program); + GLint linkStatus; + glGetProgramiv(program, GL_LINK_STATUS, &linkStatus); + if (linkStatus == GL_FALSE) { + GLint len; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &len); + + std::string log(len, '\0'); + glGetProgramInfoLog(program, len, nullptr, log.data()); + + return Shader::LinkingFailed; + } + + return Shader::Success; +} +} // namespace ProjectBrussel_UNITY_ID + +#define CATCH_ERROR_IMPL(x, name) \ + auto name = x; \ + if (name != Shader::Success) { \ + return name; \ + } +#define CATCH_ERROR(x) CATCH_ERROR_IMPL(x, UNIQUE_NAME(result)) + +Shader::ErrorCode Shader::InitFromSources(const ShaderSources& sources) { + if (IsValid()) { + return ShaderAlreadyCreated; + } + + GLuint program = glCreateProgram(); + ScopeGuard sg = [&]() { glDeleteProgram(program); }; + + GLuint vertex = 0; + DEFER { glDeleteShader(vertex); }; + if (!sources.vertex.empty()) { + CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(vertex, sources.vertex, GL_VERTEX_SHADER)); + glAttachShader(program, vertex); + } + + GLuint geometry = 0; + DEFER { glDeleteShader(geometry); }; + if (!sources.geometry.empty()) { + CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(geometry, sources.geometry, GL_GEOMETRY_SHADER)); + glAttachShader(program, geometry); + } + + GLuint tessControl = 0; + DEFER { glDeleteShader(tessControl); }; + if (!sources.tessControl.empty()) { + CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(tessControl, sources.tessControl, GL_TESS_CONTROL_SHADER)); + glAttachShader(program, tessControl); + } + + GLuint tessEval = 0; + DEFER { glDeleteShader(tessEval); }; + if (!sources.tessEval.empty()) { + CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(tessEval, sources.tessEval, GL_TESS_EVALUATION_SHADER)); + glAttachShader(program, tessEval); + } + + GLuint fragment = 0; + DEFER { glDeleteShader(fragment); }; + if (!sources.fragment.empty()) { + CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(fragment, sources.fragment, GL_FRAGMENT_SHADER)); + glAttachShader(program, fragment); + } + + CATCH_ERROR(ProjectBrussel_UNITY_ID::LinkShaderProgram(program)); + + sg.Dismiss(); + mHandle = program; + + return Success; +} + +Shader::ErrorCode Shader::InitFromFiles(const ShaderFilePaths& files) { + if (IsValid()) { + return ShaderAlreadyCreated; + } + + GLuint program = glCreateProgram(); + ScopeGuard sg = [&]() { glDeleteProgram(program); }; + + GLuint vertex = 0; + DEFER { glDeleteShader(vertex); }; + if (files.vertex) { + std::string src; + CATCH_ERROR(ProjectBrussel_UNITY_ID::LoadFile(src, files.vertex)); + CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(vertex, src, GL_VERTEX_SHADER)); + glAttachShader(program, vertex); + } + + GLuint geometry = 0; + DEFER { glDeleteShader(geometry); }; + if (files.geometry) { + std::string src; + CATCH_ERROR(ProjectBrussel_UNITY_ID::LoadFile(src, files.geometry)); + CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(geometry, src, GL_GEOMETRY_SHADER)); + glAttachShader(program, geometry); + } + + GLuint tessControl = 0; + DEFER { glDeleteShader(tessControl); }; + if (files.tessControl) { + std::string src; + CATCH_ERROR(ProjectBrussel_UNITY_ID::LoadFile(src, files.tessControl)); + CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(tessControl, src, GL_TESS_CONTROL_SHADER)); + glAttachShader(program, tessControl); + } + + GLuint tessEval = 0; + DEFER { glDeleteShader(tessEval); }; + if (files.tessEval) { + std::string src; + CATCH_ERROR(ProjectBrussel_UNITY_ID::LoadFile(src, files.tessEval)); + CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(tessEval, src, GL_TESS_EVALUATION_SHADER)); + glAttachShader(program, tessEval); + } + + GLuint fragment = 0; + DEFER { glDeleteShader(fragment); }; + if (files.fragment) { + std::string src; + CATCH_ERROR(ProjectBrussel_UNITY_ID::LoadFile(src, files.fragment)); + CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(fragment, src, GL_FRAGMENT_SHADER)); + glAttachShader(program, fragment); + } + + CATCH_ERROR(ProjectBrussel_UNITY_ID::LinkShaderProgram(program)); + + sg.Dismiss(); + mHandle = program; + + return Success; +} + +Shader::ErrorCode Shader::InitFromSource(std::string_view source) { + GLuint vertex = 0; + DEFER { glDeleteShader(vertex); }; + + GLuint geometry = 0; + DEFER { glDeleteShader(geometry); }; + + GLuint tessControl = 0; + DEFER { glDeleteShader(tessControl); }; + + GLuint tessEval = 0; + DEFER { glDeleteShader(tessEval); }; + + GLuint fragment = 0; + DEFER { glDeleteShader(fragment); }; + + int prevBegin = -1; // Excluding #type marker + int prevEnd = -1; // [begin, end) + std::string prevShaderVariant; + + auto CommitSection = [&]() -> ErrorCode { + if (prevBegin == -1 || prevEnd == -1) { + // Not actually "succeeding" here, but we just want to skip this call and continue + return Success; + } + + if (prevShaderVariant == "vertex" && !vertex) { + CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(vertex, source.data(), prevBegin, prevEnd, GL_VERTEX_SHADER)); + } else if (prevShaderVariant == "geometry" && !geometry) { + CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(geometry, source.data(), prevBegin, prevEnd, GL_GEOMETRY_SHADER)); + } else if (prevShaderVariant == "tessellation_control" && !tessControl) { + CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(tessControl, source.data(), prevBegin, prevEnd, GL_TESS_CONTROL_SHADER)); + } else if (prevShaderVariant == "tessellation_evaluation" && !tessEval) { + CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(tessEval, source.data(), prevBegin, prevEnd, GL_TESS_EVALUATION_SHADER)); + } else if (prevShaderVariant == "fragment" && !fragment) { + CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(fragment, source.data(), prevBegin, prevEnd, GL_FRAGMENT_SHADER)); + } else { + return InvalidShaderVariant; + } + + prevBegin = -1; + prevEnd = -1; + prevShaderVariant.clear(); + + return Success; + }; + + constexpr const char* kMarker = "#type "; + bool matchingDirective = true; // If true, we are matching marker pattern; if false, we are accumulating shader variant identifier + int matchIndex = 0; // Current index of the pattern trying to match + std::string shaderVariant; + + // Don't use utf8 iterator, shader sources are expected to be ASCII only + for (size_t i = 0; i < source.size(); ++i) { + char c = source[i]; + + if (matchingDirective) { + if (c == kMarker[matchIndex]) { + // Matched the expected character, go to next char in pattern + matchIndex++; + + // If we are at the end of the marker pattern... + if (kMarker[matchIndex] == '\0') { + matchingDirective = false; + matchIndex = 0; + continue; + } + + // This might be a shader variant directive -> might be end of a section + if (c == '#') { + prevEnd = i; + continue; + } + } else { + // Unexpected character, rollback to beginning + matchIndex = 0; + } + } else { + if (c == '\n') { + // Found complete shader variant directive + + CATCH_ERROR(CommitSection()); // Try commit section, for the first apparent of #type this should do nothing, as `prevEnd` will still be -1 + prevBegin = i + 1; // +1 to skip new line (technically not needed) + prevShaderVariant = std::move(shaderVariant); + + matchingDirective = true; + shaderVariant.clear(); + } else { + // Simply accumulate to shader variant buffer + shaderVariant += c; + } + } + } + + // Commit the last section + prevEnd = static_cast<int>(source.size()); + CATCH_ERROR(CommitSection()); + + GLuint program = glCreateProgram(); + ScopeGuard sg = [&]() { glDeleteProgram(program); }; + + if (vertex) glAttachShader(program, vertex); + if (geometry) glAttachShader(program, geometry); + if (tessControl) glAttachShader(program, tessControl); + if (tessEval) glAttachShader(program, tessEval); + if (fragment) glAttachShader(program, fragment); + + CATCH_ERROR(ProjectBrussel_UNITY_ID::LinkShaderProgram(program)); + + sg.Dismiss(); + mHandle = program; + + return Success; +} + +Shader::ErrorCode Shader::InitFromFile(const char* filePath) { + std::string src; + CATCH_ERROR(ProjectBrussel_UNITY_ID::LoadFile(src, filePath)); + + return InitFromSource(src); +} + +#undef CATCH_ERROR + +GLuint Shader::GetProgram() const { + return mHandle; +} + +bool Shader::IsValid() const { + return mHandle != 0; +} |