#include "Shader.hpp" #include "AppConfig.hpp" #include #include #include #include #include #include #include #include #include #include #include #include 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& 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(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 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& 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(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& 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 { if (type == "Math"sv) { auto uniform = std::make_unique(); ReadShaderMathVariable(*rvValue, *uniform); return uniform; } else if (type == "Sampler"sv) { auto uniform = std::make_unique(); 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()); } }