diff options
author | rtk0c <[email protected]> | 2023-10-19 22:50:07 -0700 |
---|---|---|
committer | rtk0c <[email protected]> | 2025-08-16 11:31:16 -0700 |
commit | 297232d21594b138bb368a42b5b0d085ff9ed6aa (patch) | |
tree | 075d5407e1e12a9d35cbee6e4c20ad34e0765c42 /src/brussel.engine/Shader.cpp | |
parent | d5cd34ff69f7fd134d5450696f298af1a864afbc (diff) |
The great renaming: switch to "module style"
Diffstat (limited to 'src/brussel.engine/Shader.cpp')
-rw-r--r-- | src/brussel.engine/Shader.cpp | 711 |
1 files changed, 711 insertions, 0 deletions
diff --git a/src/brussel.engine/Shader.cpp b/src/brussel.engine/Shader.cpp new file mode 100644 index 0000000..9bf2e0e --- /dev/null +++ b/src/brussel.engine/Shader.cpp @@ -0,0 +1,711 @@ +#include "Shader.hpp" + +#include "AppConfig.hpp" + +#include <Metadata.hpp> +#include <RapidJsonHelper.hpp> +#include <ScopeGuard.hpp> +#include <Utils.hpp> + +#include <fmt/format.h> +#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))); +} + +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); +} + +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; + + default: break; + } + + 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; + + default: break; + } + + return false; +} + +std::variant<ShaderMathVariable, ShaderSamplerVariable> CreateVariable(GLenum type, GLuint loc) { + GLenum scalarType; + int width; + int height; + if (QueryMathInfo(type, scalarType, width, height)) { + ShaderMathVariable res; + res.location = loc; + res.scalarType = type; + res.width = width; + res.height = height; + return res; + } + + if (QuerySamplerInfo(type)) { + ShaderSamplerVariable res; + res.location = loc; + res.samplerType = type; + return res; + } + + throw std::runtime_error(fmt::format("Unknown OpenGL shader uniform type {}", type)); +} +} // 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; +} + +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) { + std::visit([](auto&& v) { v.ShowInfo(); }, uniform); + } + 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); + json_dto::json_output_t out( value, root.GetAllocator() ); + out << mInstance->mInfo; +} + +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()); + } +} |