aboutsummaryrefslogtreecommitdiff
path: root/source/Game/Shader.cpp
diff options
context:
space:
mode:
authorrtk0c <[email protected]>2022-06-03 23:26:44 -0700
committerrtk0c <[email protected]>2022-06-03 23:26:44 -0700
commit60ccc62f4934e44ad5b905fdbcf458302b8d8a09 (patch)
tree02ec83cc8387abfd08bd5ee7ea4e8115f1bfb8d0 /source/Game/Shader.cpp
parentc2ef7737536bf1f8c81fcfae95c0183b21c9753f (diff)
Changeset: 63 [WIP] Rename directories
Diffstat (limited to 'source/Game/Shader.cpp')
-rw-r--r--source/Game/Shader.cpp773
1 files changed, 773 insertions, 0 deletions
diff --git a/source/Game/Shader.cpp b/source/Game/Shader.cpp
new file mode 100644
index 0000000..4a58635
--- /dev/null
+++ b/source/Game/Shader.cpp
@@ -0,0 +1,773 @@
+#include "Shader.hpp"
+
+#include "AppConfig.hpp"
+
+#include <Metadata.hpp>
+#include <RapidJsonHelper.hpp>
+#include <ScopeGuard.hpp>
+#include <Utils.hpp>
+
+#include <imgui.h>
+#include <misc/cpp/imgui_stdlib.h>
+#include <rapidjson/document.h>
+#include <cassert>
+#include <cstddef>
+#include <cstdlib>
+#include <utility>
+
+using namespace std::literals;
+
+void ShaderMathVariable::ShowInfo() const {
+ ImGui::BulletText("Location: %d\nName: %s\nSemantic: %.*s\nType: %.*s %dx%d",
+ location,
+ name.c_str(),
+ PRINTF_STRING_VIEW(Metadata::EnumToString(semantic)),
+ PRINTF_STRING_VIEW(Tags::GLTypeToString(scalarType)),
+ width,
+ height);
+}
+
+void ShaderSamplerVariable::ShowInfo() const {
+ ImGui::BulletText("Location: %d\nName: %s\nSemantic: %.*s\nType: Sampler",
+ location,
+ name.c_str(),
+ PRINTF_STRING_VIEW(Metadata::EnumToString(semantic)));
+}
+
+bool ShaderThingId::IsValid() const {
+ return kind == KD_Invalid;
+}
+
+namespace ProjectBrussel_UNITY_ID {
+GLuint FindLocation(const std::vector<ShaderMathVariable>& vars, Tags::VertexElementSemantic semantic) {
+ for (auto& var : vars) {
+ if (var.semantic == semantic) {
+ return var.location;
+ }
+ }
+ return Tags::kInvalidLocation;
+}
+
+constexpr auto kAfnTransform = "transform";
+constexpr auto kAfnTime = "time";
+constexpr auto kAfnDeltaTime = "deltaTime";
+constexpr auto kAfnTextureAtlas = "textureAtlas";
+
+void InitAutoFill(const char* name, GLuint program, GLuint& location) {
+ GLint result = glGetUniformLocation(program, name);
+ if (result != -1) {
+ location = result;
+ }
+}
+
+void InitAutoFills(Shader& shader) {
+ GLuint pg = shader.GetProgram();
+ InitAutoFill(kAfnTransform, pg, shader.autofill_Transform);
+ InitAutoFill(kAfnTime, pg, shader.autofill_Time);
+ InitAutoFill(kAfnDeltaTime, pg, shader.autofill_DeltaTime);
+ InitAutoFill(kAfnTextureAtlas, pg, shader.autofill_TextureAtlas);
+}
+} // namespace ProjectBrussel_UNITY_ID
+
+GLuint ShaderInfo::FindInputLocation(Tags::VertexElementSemantic semantic) {
+ using namespace ProjectBrussel_UNITY_ID;
+ return FindLocation(inputs, semantic);
+}
+
+GLuint ShaderInfo::FindOutputLocation(Tags::VertexElementSemantic semantic) {
+ using namespace ProjectBrussel_UNITY_ID;
+ return FindLocation(outputs, semantic);
+}
+
+ShaderVariable* ShaderInfo::FindVariable(const ShaderThingId& thing) {
+ switch (thing.kind) {
+ case ShaderThingId::KD_Input: return &inputs[thing.index];
+ case ShaderThingId::KD_Output: return &outputs[thing.index];
+ case ShaderThingId::KD_Uniform: return uniforms[thing.index].get();
+ case ShaderThingId::KD_Invalid: break;
+ }
+ return nullptr;
+}
+
+Shader::Shader() {
+}
+
+Shader::~Shader() {
+ glDeleteProgram(mProgram);
+}
+
+namespace ProjectBrussel_UNITY_ID {
+// 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 ::EC_CompilationFailed;
+ }
+
+ return Shader::EC_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::EC_LinkingFailed;
+ }
+
+ return Shader::EC_Success;
+}
+} // namespace ProjectBrussel_UNITY_ID
+
+#define CATCH_ERROR_IMPL(x, name) \
+ auto name = x; \
+ if (name != Shader::EC_Success) { \
+ return name; \
+ }
+#define CATCH_ERROR(x) CATCH_ERROR_IMPL(x, UNIQUE_NAME(result))
+
+Shader::ErrorCode Shader::InitFromSources(const ShaderSources& sources) {
+ using namespace ProjectBrussel_UNITY_ID;
+
+ if (IsValid()) {
+ return EC_AlreadyInitialized;
+ }
+
+ GLuint program = glCreateProgram();
+ ScopeGuard sg = [&]() { glDeleteProgram(program); };
+
+ GLuint vertex = 0;
+ DEFER { glDeleteShader(vertex); };
+ if (!sources.vertex.empty()) {
+ CATCH_ERROR(CreateShader(vertex, sources.vertex, GL_VERTEX_SHADER));
+ glAttachShader(program, vertex);
+ }
+
+ GLuint geometry = 0;
+ DEFER { glDeleteShader(geometry); };
+ if (!sources.geometry.empty()) {
+ CATCH_ERROR(CreateShader(geometry, sources.geometry, GL_GEOMETRY_SHADER));
+ glAttachShader(program, geometry);
+ }
+
+ GLuint tessControl = 0;
+ DEFER { glDeleteShader(tessControl); };
+ if (!sources.tessControl.empty()) {
+ CATCH_ERROR(CreateShader(tessControl, sources.tessControl, GL_TESS_CONTROL_SHADER));
+ glAttachShader(program, tessControl);
+ }
+
+ GLuint tessEval = 0;
+ DEFER { glDeleteShader(tessEval); };
+ if (!sources.tessEval.empty()) {
+ CATCH_ERROR(CreateShader(tessEval, sources.tessEval, GL_TESS_EVALUATION_SHADER));
+ glAttachShader(program, tessEval);
+ }
+
+ GLuint fragment = 0;
+ DEFER { glDeleteShader(fragment); };
+ if (!sources.fragment.empty()) {
+ CATCH_ERROR(CreateShader(fragment, sources.fragment, GL_FRAGMENT_SHADER));
+ glAttachShader(program, fragment);
+ }
+
+ CATCH_ERROR(LinkShaderProgram(program));
+
+ sg.Dismiss();
+ mProgram = program;
+
+ InitAutoFills(*this);
+
+ return EC_Success;
+}
+
+Shader::ErrorCode Shader::InitFromSource(std::string_view source) {
+ using namespace ProjectBrussel_UNITY_ID;
+
+ 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 EC_Success;
+ }
+
+ if (prevShaderVariant == "vertex" && !vertex) {
+ CATCH_ERROR(CreateShader(vertex, source.data(), prevBegin, prevEnd, GL_VERTEX_SHADER));
+ } else if (prevShaderVariant == "geometry" && !geometry) {
+ CATCH_ERROR(CreateShader(geometry, source.data(), prevBegin, prevEnd, GL_GEOMETRY_SHADER));
+ } else if (prevShaderVariant == "tessellation_control" && !tessControl) {
+ CATCH_ERROR(CreateShader(tessControl, source.data(), prevBegin, prevEnd, GL_TESS_CONTROL_SHADER));
+ } else if (prevShaderVariant == "tessellation_evaluation" && !tessEval) {
+ CATCH_ERROR(CreateShader(tessEval, source.data(), prevBegin, prevEnd, GL_TESS_EVALUATION_SHADER));
+ } else if (prevShaderVariant == "fragment" && !fragment) {
+ CATCH_ERROR(CreateShader(fragment, source.data(), prevBegin, prevEnd, GL_FRAGMENT_SHADER));
+ } else {
+ return EC_InvalidShaderVariant;
+ }
+
+ prevBegin = -1;
+ prevEnd = -1;
+ prevShaderVariant.clear();
+
+ return EC_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(LinkShaderProgram(program));
+
+ sg.Dismiss();
+ mProgram = program;
+
+ InitAutoFills(*this);
+
+ return EC_Success;
+}
+
+#undef CATCH_ERROR
+
+namespace ProjectBrussel_UNITY_ID {
+bool QueryMathInfo(GLenum type, GLenum& scalarType, int& width, int& height) {
+ auto DoOutput = [&](GLenum scalarTypeIn, int widthIn, int heightIn) {
+ width = widthIn;
+ height = heightIn;
+ scalarType = scalarTypeIn;
+ };
+
+ switch (type) {
+ case GL_FLOAT:
+ case GL_DOUBLE:
+ case GL_INT:
+ case GL_UNSIGNED_INT:
+ case GL_BOOL: {
+ DoOutput(type, 1, 1);
+ return true;
+ }
+
+ case GL_FLOAT_VEC2: DoOutput(GL_FLOAT, 1, 2); return true;
+ case GL_FLOAT_VEC3: DoOutput(GL_FLOAT, 1, 3); return true;
+ case GL_FLOAT_VEC4: DoOutput(GL_FLOAT, 1, 4); return true;
+ case GL_DOUBLE_VEC2: DoOutput(GL_DOUBLE, 1, 2); return true;
+ case GL_DOUBLE_VEC3: DoOutput(GL_DOUBLE, 1, 3); return true;
+ case GL_DOUBLE_VEC4: DoOutput(GL_DOUBLE, 1, 4); return true;
+ case GL_INT_VEC2: DoOutput(GL_INT, 1, 2); return true;
+ case GL_INT_VEC3: DoOutput(GL_INT, 1, 3); return true;
+ case GL_INT_VEC4: DoOutput(GL_INT, 1, 4); return true;
+ case GL_UNSIGNED_INT_VEC2: DoOutput(GL_UNSIGNED_INT, 1, 2); return true;
+ case GL_UNSIGNED_INT_VEC3: DoOutput(GL_UNSIGNED_INT, 1, 3); return true;
+ case GL_UNSIGNED_INT_VEC4: DoOutput(GL_UNSIGNED_INT, 1, 4); return true;
+ case GL_BOOL_VEC2: DoOutput(GL_BOOL, 1, 2); return true;
+ case GL_BOOL_VEC3: DoOutput(GL_BOOL, 1, 3); return true;
+ case GL_BOOL_VEC4: DoOutput(GL_BOOL, 1, 4); return true;
+
+ case GL_FLOAT_MAT2: DoOutput(GL_FLOAT, 2, 2); return true;
+ case GL_FLOAT_MAT3: DoOutput(GL_FLOAT, 3, 3); return true;
+ case GL_FLOAT_MAT4: DoOutput(GL_FLOAT, 4, 4); return true;
+ case GL_FLOAT_MAT2x3: DoOutput(GL_FLOAT, 2, 3); return true;
+ case GL_FLOAT_MAT2x4: DoOutput(GL_FLOAT, 2, 4); return true;
+ case GL_FLOAT_MAT3x2: DoOutput(GL_FLOAT, 3, 2); return true;
+ case GL_FLOAT_MAT3x4: DoOutput(GL_FLOAT, 3, 4); return true;
+ case GL_FLOAT_MAT4x2: DoOutput(GL_FLOAT, 4, 2); return true;
+ case GL_FLOAT_MAT4x3: DoOutput(GL_FLOAT, 4, 3); return true;
+
+ case GL_DOUBLE_MAT2: DoOutput(GL_DOUBLE, 2, 2); return true;
+ case GL_DOUBLE_MAT3: DoOutput(GL_DOUBLE, 3, 3); return true;
+ case GL_DOUBLE_MAT4: DoOutput(GL_DOUBLE, 4, 4); return true;
+ case GL_DOUBLE_MAT2x3: DoOutput(GL_DOUBLE, 2, 3); return true;
+ case GL_DOUBLE_MAT2x4: DoOutput(GL_DOUBLE, 2, 4); return true;
+ case GL_DOUBLE_MAT3x2: DoOutput(GL_DOUBLE, 3, 2); return true;
+ case GL_DOUBLE_MAT3x4: DoOutput(GL_DOUBLE, 3, 4); return true;
+ case GL_DOUBLE_MAT4x2: DoOutput(GL_DOUBLE, 4, 2); return true;
+ case GL_DOUBLE_MAT4x3: DoOutput(GL_DOUBLE, 4, 3); return true;
+ }
+
+ return false;
+}
+
+bool QuerySamplerInfo(GLenum type) {
+ switch (type) {
+ case GL_SAMPLER_1D:
+ case GL_SAMPLER_2D:
+ case GL_SAMPLER_3D:
+ case GL_SAMPLER_CUBE:
+ case GL_SAMPLER_1D_SHADOW:
+ case GL_SAMPLER_2D_SHADOW:
+ case GL_SAMPLER_1D_ARRAY:
+ case GL_SAMPLER_2D_ARRAY:
+ case GL_SAMPLER_1D_ARRAY_SHADOW:
+ case GL_SAMPLER_2D_ARRAY_SHADOW:
+ case GL_SAMPLER_2D_MULTISAMPLE:
+ case GL_SAMPLER_2D_MULTISAMPLE_ARRAY:
+ case GL_SAMPLER_CUBE_SHADOW:
+ case GL_SAMPLER_BUFFER:
+ case GL_SAMPLER_2D_RECT:
+ case GL_SAMPLER_2D_RECT_SHADOW:
+
+ case GL_INT_SAMPLER_1D:
+ case GL_INT_SAMPLER_2D:
+ case GL_INT_SAMPLER_3D:
+ case GL_INT_SAMPLER_CUBE:
+ case GL_INT_SAMPLER_1D_ARRAY:
+ case GL_INT_SAMPLER_2D_ARRAY:
+ case GL_INT_SAMPLER_2D_MULTISAMPLE:
+ case GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY:
+ case GL_INT_SAMPLER_BUFFER:
+ case GL_INT_SAMPLER_2D_RECT:
+
+ case GL_UNSIGNED_INT_SAMPLER_1D:
+ case GL_UNSIGNED_INT_SAMPLER_2D:
+ case GL_UNSIGNED_INT_SAMPLER_3D:
+ case GL_UNSIGNED_INT_SAMPLER_CUBE:
+ case GL_UNSIGNED_INT_SAMPLER_1D_ARRAY:
+ case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY:
+ case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE:
+ case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY:
+ case GL_UNSIGNED_INT_SAMPLER_BUFFER:
+ case GL_UNSIGNED_INT_SAMPLER_2D_RECT:
+ return true;
+ }
+
+ return false;
+}
+
+std::unique_ptr<ShaderVariable> CreateVariable(GLenum type, GLuint loc) {
+ GLenum scalarType;
+ int width;
+ int height;
+ if (QueryMathInfo(type, scalarType, width, height)) {
+ auto res = std::make_unique<ShaderMathVariable>();
+ res->location = loc;
+ res->scalarType = type;
+ res->width = width;
+ res->height = height;
+ return res;
+ }
+
+ if (QuerySamplerInfo(type)) {
+ auto res = std::make_unique<ShaderSamplerVariable>();
+ res->location = loc;
+ res->type = type;
+ return res;
+ }
+
+ return nullptr;
+}
+} // namespace ProjectBrussel_UNITY_ID
+
+bool Shader::GatherInfoShaderIntrospection() {
+ using namespace ProjectBrussel_UNITY_ID;
+
+ mInfo = {};
+
+ // TODO handle differnt types of variables with the same name
+
+ // TODO work with OpenGL < 4.3, possibly with glslang
+ return true;
+
+ int inputCount;
+ glGetProgramInterfaceiv(mProgram, GL_PROGRAM_INPUT, GL_ACTIVE_RESOURCES, &inputCount);
+ int outputCount;
+ glGetProgramInterfaceiv(mProgram, GL_PROGRAM_OUTPUT, GL_ACTIVE_RESOURCES, &outputCount);
+ int uniformBlockCount;
+ glGetProgramInterfaceiv(mProgram, GL_UNIFORM_BLOCK, GL_ACTIVE_RESOURCES, &uniformBlockCount);
+ int uniformCount;
+ glGetProgramInterfaceiv(mProgram, GL_UNIFORM, GL_ACTIVE_RESOURCES, &uniformCount);
+
+ // Gather inputs
+ auto GatherMathVars = [&](int count, GLenum resourceType, ShaderThingId::Kind resourceKind, std::vector<ShaderMathVariable>& list) {
+ for (int i = 0; i < count; ++i) {
+ const GLenum query[] = { GL_NAME_LENGTH, GL_TYPE, GL_LOCATION, GL_TOP_LEVEL_ARRAY_SIZE };
+ GLint props[std::size(query)];
+ glGetProgramResourceiv(mProgram, resourceType, i, std::size(query), query, std::size(props), nullptr, props);
+ auto& nameLength = props[0];
+ auto& type = props[1];
+ auto& loc = props[2];
+ auto& arrayLength = props[3];
+
+ std::string fieldName(nameLength - 1, '\0');
+ glGetProgramResourceName(mProgram, GL_UNIFORM, i, nameLength, nullptr, fieldName.data());
+
+ mInfo.things.try_emplace(fieldName, ShaderThingId{ resourceKind, i });
+
+ auto& thing = list.emplace_back();
+ thing.name = std::move(fieldName);
+ thing.arrayLength = arrayLength;
+ QueryMathInfo(type, thing.scalarType, thing.width, thing.height);
+ }
+ };
+ GatherMathVars(inputCount, GL_PROGRAM_INPUT, ShaderThingId::KD_Input, mInfo.inputs);
+ GatherMathVars(outputCount, GL_PROGRAM_OUTPUT, ShaderThingId::KD_Output, mInfo.outputs);
+
+ // Gather uniform variables
+ for (int i = 0; i < uniformCount; ++i) {
+ const GLenum query[] = { GL_BLOCK_INDEX, GL_NAME_LENGTH, GL_TYPE, GL_LOCATION, GL_TOP_LEVEL_ARRAY_SIZE };
+ GLint props[std::size(query)];
+ glGetProgramResourceiv(mProgram, GL_UNIFORM, i, std::size(query), query, std::size(props), nullptr, props);
+ auto& blockIndex = props[0]; // Index in interface block
+ if (blockIndex != -1) { // If this is an interface block uniform, skip because it will be handled by our uniform blocks inspector
+ continue;
+ }
+ auto& nameLength = props[1];
+ auto& type = props[2];
+ auto& loc = props[3];
+ auto& arrayLength = props[4];
+
+ std::string fieldName(nameLength - 1, '\0');
+ glGetProgramResourceName(mProgram, GL_UNIFORM, i, nameLength, nullptr, fieldName.data());
+
+ mInfo.things.try_emplace(fieldName, ShaderThingId{ ShaderThingId::KD_Uniform, i });
+ mInfo.uniforms.push_back(CreateVariable(type, loc));
+ }
+
+ return true;
+}
+
+bool Shader::IsValid() const {
+ return mProgram != 0;
+}
+
+namespace ProjectBrussel_UNITY_ID {
+void WriteShaderVariable(rapidjson::Value& value, rapidjson::Document& root, const ShaderVariable& var) {
+ value.AddMember("Name", var.name, root.GetAllocator());
+ value.AddMember("Semantic", rapidjson::StringRef(Metadata::EnumToString(var.semantic)), root.GetAllocator());
+ value.AddMember("OpenGLLocation", var.location, root.GetAllocator());
+}
+
+bool ReadShaderVariable(const rapidjson::Value& value, ShaderVariable& var) {
+ using namespace Tags;
+
+ BRUSSEL_JSON_GET(value, "Name", std::string, var.name, return false);
+ { // Semantic
+ auto rvSemantic = rapidjson::GetProperty(value, rapidjson::kStringType, "Semantic"sv);
+ if (!rvSemantic) {
+ var.semantic = VES_Generic;
+ } else {
+ auto str = rapidjson::AsStringView(*rvSemantic);
+ auto lookup = Metadata::EnumFromString<VertexElementSemantic>(str);
+ var.semantic = lookup.value_or(VES_Generic);
+ }
+ }
+ BRUSSEL_JSON_GET_DEFAULT(value, "OpenGLLocation", int, var.location, 0);
+ return true;
+}
+
+void WriteShaderMathVariable(rapidjson::Value& value, rapidjson::Document& root, const ShaderMathVariable& var) {
+ WriteShaderVariable(value, root, var);
+ value.AddMember("ScalarType", rapidjson::StringRef(Tags::GLTypeToString(var.scalarType)), root.GetAllocator());
+ value.AddMember("Width", var.width, root.GetAllocator());
+ value.AddMember("Height", var.height, root.GetAllocator());
+ value.AddMember("ArrayLength", var.arrayLength, root.GetAllocator());
+}
+
+bool ReadShaderMathVariable(const rapidjson::Value& value, ShaderMathVariable& var) {
+ if (!ReadShaderVariable(value, var)) return false;
+ {
+ auto rvScalar = rapidjson::GetProperty(value, rapidjson::kStringType, "ScalarType"sv);
+ if (!rvScalar) return false;
+ var.scalarType = Tags::GLTypeFromString(rapidjson::AsStringView(*rvScalar));
+ }
+ BRUSSEL_JSON_GET(value, "Width", int, var.width, return false);
+ BRUSSEL_JSON_GET(value, "Height", int, var.height, return false);
+ BRUSSEL_JSON_GET_DEFAULT(value, "ArrayLength", int, var.arrayLength, 1);
+ return true;
+}
+
+void WriteShaderSamplerVariable(rapidjson::Value& value, rapidjson::Document& root, const ShaderSamplerVariable& var) {
+ WriteShaderVariable(value, root, var);
+ // TODO
+}
+
+bool ReadShaderSamplerVariable(const rapidjson::Value& value, ShaderSamplerVariable& var) {
+ if (!ReadShaderVariable(value, var)) return false;
+ BRUSSEL_JSON_GET_DEFAULT(value, "ArrayLength", int, var.arrayLength, 1);
+ return true;
+}
+} // namespace ProjectBrussel_UNITY_ID
+
+IresShader::IresShader()
+ : IresObject(KD_Shader) {
+ InvalidateInstance();
+}
+
+Shader* IresShader::GetInstance() const {
+ return mInstance.Get();
+}
+
+void IresShader::InvalidateInstance() {
+ if (mInstance != nullptr) {
+ mInstance->mIres = nullptr;
+ }
+ mInstance.Attach(new Shader());
+ mInstance->mIres = this;
+}
+
+void IresShader::ShowEditor(IEditor& editor) {
+ using namespace Tags;
+ using namespace ProjectBrussel_UNITY_ID;
+
+ IresObject::ShowEditor(editor);
+
+ if (ImGui::Button("Gather info")) {
+ mInstance->GatherInfoShaderIntrospection();
+ }
+
+ if (ImGui::InputText("Source file", &mSourceFile, ImGuiInputTextFlags_EnterReturnsTrue)) {
+ InvalidateInstance();
+ }
+ // In other cases, mSourceFile will be reverted to before edit
+
+ // TODO macros
+
+ ImGui::Separator();
+
+ auto& info = mInstance->GetInfo();
+ if (ImGui::CollapsingHeader("General")) {
+ ImGui::Text("OpenGL program ID: %u", mInstance->GetProgram());
+ }
+ if (ImGui::CollapsingHeader("Inputs")) {
+ for (auto& input : info.inputs) {
+ input.ShowInfo();
+ }
+ }
+ if (ImGui::CollapsingHeader("Outputs")) {
+ for (auto& output : info.outputs) {
+ output.ShowInfo();
+ }
+ }
+ if (ImGui::CollapsingHeader("Uniforms")) {
+ for (auto& uniform : info.uniforms) {
+ uniform->ShowInfo();
+ }
+ if (auto loc = mInstance->autofill_Transform; loc != kInvalidLocation) {
+ ImGui::BulletText("(Autofill)\nLocation: %d\nName: %s", loc, kAfnTransform);
+ }
+ if (auto loc = mInstance->autofill_Time; loc != kInvalidLocation) {
+ ImGui::BulletText("(Autofill)\nLocation: %d\nName: %s", loc, kAfnTime);
+ }
+ if (auto loc = mInstance->autofill_DeltaTime; loc != kInvalidLocation) {
+ ImGui::BulletText("(Autofill)\nLocation: %d\nName: %s", loc, kAfnDeltaTime);
+ }
+ if (auto loc = mInstance->autofill_TextureAtlas; loc != kInvalidLocation) {
+ ImGui::BulletText("(Autofill)\nLocation: %d\nName: %s", loc, kAfnTextureAtlas);
+ }
+ }
+}
+
+void IresShader::Write(IresWritingContext& ctx, rapidjson::Value& value, rapidjson::Document& root) const {
+ using namespace ProjectBrussel_UNITY_ID;
+
+ IresObject::Write(ctx, value, root);
+
+ auto& shaderInfo = mInstance->mInfo;
+
+ value.AddMember("SourceFile", mSourceFile, root.GetAllocator());
+
+ auto SaveMathVars = [&](const char* name, const std::vector<ShaderMathVariable>& vars) {
+ rapidjson::Value rvThings(rapidjson::kArrayType);
+ for (auto& thing : vars) {
+ rapidjson::Value rvThing(rapidjson::kObjectType);
+ WriteShaderMathVariable(rvThing, root, thing);
+
+ rvThings.PushBack(rvThing, root.GetAllocator());
+ }
+ value.AddMember(rapidjson::StringRef(name), rvThings, root.GetAllocator());
+ };
+ SaveMathVars("Inputs", shaderInfo.inputs);
+ SaveMathVars("Outputs", shaderInfo.outputs);
+
+ // TODO uniforms
+}
+
+void IresShader::Read(IresLoadingContext& ctx, const rapidjson::Value& value) {
+ using namespace ProjectBrussel_UNITY_ID;
+
+ IresObject::Read(ctx, value);
+
+ auto rvSourceFile = rapidjson::GetProperty(value, rapidjson::kStringType, "SourceFile"sv);
+ if (!rvSourceFile) {
+ return;
+ } else {
+ this->mSourceFile = rapidjson::AsString(*rvSourceFile);
+
+ char shaderFilePath[256];
+ snprintf(shaderFilePath, sizeof(shaderFilePath), "%s/%s", AppConfig::assetDir.c_str(), rvSourceFile->GetString());
+
+ auto shaderFile = Utils::OpenCstdioFile(shaderFilePath, Utils::Read);
+ if (!shaderFile) return;
+ DEFER { fclose(shaderFile); };
+
+ fseek(shaderFile, 0, SEEK_END);
+ auto shaderFileSize = ftell(shaderFile);
+ rewind(shaderFile);
+
+ // Also add \0 ourselves
+ auto buffer = std::make_unique<char[]>(shaderFileSize + 1);
+ fread(buffer.get(), shaderFileSize, 1, shaderFile);
+ buffer[shaderFileSize] = '\0';
+ std::string_view source(buffer.get(), shaderFileSize);
+
+ if (mInstance->InitFromSource(source) != Shader::EC_Success) {
+ return;
+ }
+ }
+
+ auto& shaderInfo = mInstance->mInfo;
+ auto shaderProgram = mInstance->GetProgram();
+
+ auto LoadMathVars = [&](std::string_view name, ShaderThingId::Kind kind, std::vector<ShaderMathVariable>& vars) {
+ auto rvThings = rapidjson::GetProperty(value, rapidjson::kArrayType, name);
+ if (!rvThings) return;
+
+ for (auto& rv : rvThings->GetArray()) {
+ if (!rv.IsObject()) continue;
+ ShaderMathVariable thing;
+ ReadShaderMathVariable(rv, thing);
+
+ shaderInfo.things.try_emplace(thing.name, ShaderThingId{ kind, (int)vars.size() });
+ vars.push_back(std::move(thing));
+ }
+ };
+ LoadMathVars("Inputs"sv, ShaderThingId::KD_Input, shaderInfo.inputs);
+ LoadMathVars("Outputs"sv, ShaderThingId::KD_Output, shaderInfo.outputs);
+
+ auto rvUniforms = rapidjson::GetProperty(value, rapidjson::kArrayType, "Uniforms"sv);
+ if (!rvUniforms) return;
+ for (auto& rvUniform : rvUniforms->GetArray()) {
+ if (!rvUniform.IsObject()) continue;
+
+ auto rvType = rapidjson::GetProperty(rvUniform, rapidjson::kStringType, "Type"sv);
+ if (!rvType) continue;
+ auto type = rapidjson::AsStringView(*rvType);
+
+ auto rvValue = rapidjson::GetProperty(rvUniform, rapidjson::kObjectType, "Value"sv);
+ if (!rvValue) continue;
+
+ bool autoFill; // TODO store autofill uniforms somewhere else
+ BRUSSEL_JSON_GET_DEFAULT(rvUniform, "AutoFill", bool, autoFill, false);
+ if (autoFill) continue;
+
+ auto uniform = [&]() -> std::unique_ptr<ShaderVariable> {
+ if (type == "Math"sv) {
+ auto uniform = std::make_unique<ShaderMathVariable>();
+ ReadShaderMathVariable(*rvValue, *uniform);
+
+ return uniform;
+ } else if (type == "Sampler"sv) {
+ auto uniform = std::make_unique<ShaderSamplerVariable>();
+ ReadShaderSamplerVariable(*rvValue, *uniform);
+
+ return uniform;
+ }
+
+ return nullptr;
+ }();
+ if (uniform) {
+ shaderInfo.things.try_emplace(uniform->name, ShaderThingId{ ShaderThingId::KD_Uniform, (int)shaderInfo.uniforms.size() });
+ shaderInfo.uniforms.emplace_back(std::move(uniform));
+ }
+ }
+
+ for (auto& uniform : shaderInfo.uniforms) {
+ uniform->location = glGetUniformLocation(shaderProgram, uniform->name.c_str());
+ }
+}