aboutsummaryrefslogtreecommitdiff
path: root/source/Shader.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'source/Shader.cpp')
-rw-r--r--source/Shader.cpp327
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;
+}