diff --git a/MG/Global.h b/MG/Global.h index 2fb584b4..61adf95d 100644 --- a/MG/Global.h +++ b/MG/Global.h @@ -27,7 +27,7 @@ namespace MG_Global { } namespace Common { - inline const int LogLevel = MG_Constants::Common::LOG_LEVEL_DEBUG; + inline const int LogLevel = MG_Constants::Common::LOG_LEVEL_INFO; #ifdef __ANDROID__ inline const char* LOG_FILE_PATH = "/sdcard/MG/latest.log"; diff --git a/MG/MG_GL/Implementations/GL/Common/GL_Common.cpp b/MG/MG_GL/Implementations/GL/Common/GL_Common.cpp index b2389138..abd7f00d 100644 --- a/MG/MG_GL/Implementations/GL/Common/GL_Common.cpp +++ b/MG/MG_GL/Implementations/GL/Common/GL_Common.cpp @@ -43,14 +43,6 @@ namespace MG_GL::GL { MG_Util::Debug::LogE("Error setting separate blend func: %s", MG_Util::Debug::GLEnumToString(result)); } - void Clear(GLbitfield mask) { - MG_Util::Debug::LogD("glClear, mask: 0x%X", mask); - GLenum result = MG_State::glClear(mask); - if (result == GL_NO_ERROR) return; - MG_State::SetError(result); - MG_Util::Debug::LogE("Error clearing buffers: %s", MG_Util::Debug::GLEnumToString(result)); - } - void ClearColor(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) { MG_Util::Debug::LogD("glClearColor, rgba: [%.2f, %.2f, %.2f, %.2f]", red, green, blue, alpha); GLenum result = MG_State::glClearColor(red, green, blue, alpha); diff --git a/MG/MG_GL/Implementations/GL/Drawing/GL_Drawing.cpp b/MG/MG_GL/Implementations/GL/Drawing/GL_Drawing.cpp index 72ea7cbb..8ab473e6 100644 --- a/MG/MG_GL/Implementations/GL/Drawing/GL_Drawing.cpp +++ b/MG/MG_GL/Implementations/GL/Drawing/GL_Drawing.cpp @@ -4,14 +4,850 @@ #include "GL_Drawing.h" +#undef MOBILEGL_GLSLTOOL_H +#include "../../../../Includes.h" + namespace MG_GL::GL { - // This is a test function that directly draws textures. + std::string processOutColorLocations(const std::string& glslCode) { + const static std::regex pattern(R"(\n(out highp vec4 outColor)(\d+);)"); + const std::string replacement = "\nlayout(location=$2) $1$2;"; + return std::regex_replace(glslCode, pattern, replacement); + } + + std::string forceSupporterOutput(const std::string& glslCode) { + bool hasPrecisionFloat = glslCode.find("precision ") != std::string::npos && + glslCode.find("float;") != std::string::npos; + bool hasPrecisionInt = glslCode.find("precision ") != std::string::npos && + glslCode.find("int;") != std::string::npos; + + std::string result = glslCode; + std::string precisionFloat; + std::string precisionInt; + + if (hasPrecisionFloat && hasPrecisionInt) { + std::istringstream iss(result); + std::vector lines; + std::string line; + while (std::getline(iss, line)) { + bool isPrecisionLine = (line.find("precision ") != std::string::npos) && + (line.find("float;") != std::string::npos || line.find("int;") != std::string::npos); + if (!isPrecisionLine) { + lines.push_back(line); + } + } + result.clear(); + for (size_t i = 0; i < lines.size(); ++i) { + if (i != 0) result += '\n'; + result += lines[i]; + } + precisionFloat = "precision highp float;\n"; + precisionInt = "precision highp int;\n"; + } else { + precisionFloat = hasPrecisionFloat ? "" : "precision highp float;\n"; + precisionInt = hasPrecisionInt ? "" : "precision highp int;\n"; + } + + size_t lastExtensionPos = result.rfind("#extension"); + size_t insertionPos = 0; + + if (lastExtensionPos != std::string::npos) { + size_t nextNewline = result.find('\n', lastExtensionPos); + if (nextNewline != std::string::npos) { + insertionPos = nextNewline + 1; + } else { + insertionPos = result.length(); + } + } else { + size_t firstNewline = result.find('\n'); + if (firstNewline != std::string::npos) { + insertionPos = firstNewline + 1; + } else { + result = precisionFloat + precisionInt + result; + return result; + } + } + + result.insert(insertionPos, precisionFloat + precisionInt); + return result; + } + + std::string removeLayoutBinding(const std::string& glslCode) { + static std::regex bindingRegex(R"(layout\s*\(\s*binding\s*=\s*\d+\s*\)\s*)"); + std::string result = std::regex_replace(glslCode, bindingRegex, ""); + static std::regex bindingRegex2(R"(layout\s*\(\s*binding\s*=\s*\d+\s*,)"); + result = std::regex_replace(result, bindingRegex2, "layout("); + return result; + } + + static std::unordered_map s_textureMap; + static std::unordered_map s_vaoMap; + static std::unordered_map s_bufferMap; + static std::unordered_map s_programMap; + static std::unordered_map s_framebufferMap; + struct MipLevelInfo { + GLenum internalFormat; + GLsizei width; + GLsizei height; + GLenum format; + GLenum type; + }; + + void CheckGLESError() { + while (GLenum err = ::GLES::glGetError() != GL_NO_ERROR) { + MG_Util::Debug::LogE("-> GLES Error: %s", MG_Util::Debug::GLEnumToString(err)); + } + } + +#define CallAndCheck(operation) MG_Util::Debug::LogD("GLES call: %s", #operation); operation CheckGLESError(); + static std::unordered_map> s_textureLevelUploaded; + + void SyncAllTexturesToGLES(TextureState* textureState) { + MG_Util::Debug::LogD("Syncing all textures to GLES..."); + for (auto& [mgTexId, texObj] : textureState->textures) { + if (!texObj.generated) continue; + + if (s_textureMap.find(mgTexId) == s_textureMap.end()) { + GLuint glTexId; + CallAndCheck(::GLES::glGenTextures(1, &glTexId);) + s_textureMap[mgTexId] = glTexId; + GLenum target = texObj.target; + CallAndCheck(::GLES::glBindTexture(target, glTexId);) + + for (auto& [pname, param] : texObj.params.texPropertiesInt) { + CallAndCheck(::GLES::glTexParameteri(target, pname, param);) + } + for (auto& [pname, param] : texObj.params.texPropertiesFloat) { + CallAndCheck(::GLES::glTexParameterf(target, pname, param);) + } + + for (auto& [level, mip] : texObj.params.mipmapData) { + const void* data = !mip.pixelData.empty() ? mip.pixelData.data() : nullptr; + switch (target) { + case GL_TEXTURE_2D: + CallAndCheck(::GLES::glTexImage2D( + target, level, mip.internalFormat, + mip.width, mip.height, 0, + mip.format, mip.type, data + );) + s_textureLevelUploaded[mgTexId][level] = true; + MG_Util::Debug::LogD("Initial upload texture %u level %d (size=%zu)", mgTexId, level, mip.pixelData.size()); + break; + default: + MG_Util::Debug::LogE("Unsupported target: %s", MG_Util::Debug::GLEnumToString(target)); + } + } + MG_Util::Debug::LogD("Created and synced new GLES texture %u (MobileGL ID)", mgTexId); + } else { + GLuint glTexId = s_textureMap[mgTexId]; + GLenum target = texObj.target; + CallAndCheck(::GLES::glBindTexture(target, glTexId);) + + for (auto& [level, mip] : texObj.params.mipmapData) { + if (!s_textureLevelUploaded[mgTexId][level]) { + bool levelInitialized = s_textureLevelUploaded[mgTexId].count(level); + const void* data = !mip.pixelData.empty() ? mip.pixelData.data() : nullptr; + + switch (target) { + case GL_TEXTURE_2D: + if (levelInitialized) { + CallAndCheck(::GLES::glTexSubImage2D( + target, level, + 0, 0, + mip.width, mip.height, + mip.format, mip.type, data + );) + s_textureLevelUploaded[mgTexId][level] = true; + MG_Util::Debug::LogD("Updated texture %u level %d with glTexSubImage2D", mgTexId, level); + } else { + CallAndCheck(::GLES::glTexImage2D( + target, level, mip.internalFormat, + mip.width, mip.height, 0, + mip.format, mip.type, data + );) + s_textureLevelUploaded[mgTexId][level] = true; + MG_Util::Debug::LogD("Initialized texture %u level %d with glTexImage2D", mgTexId, level); + } + break; + default: + MG_Util::Debug::LogE("Unsupported target: %s", MG_Util::Debug::GLEnumToString(target)); + } + } + } + MG_Util::Debug::LogD("Updated existing GLES texture %u (MobileGL ID)", mgTexId); + } + } + CallAndCheck(::GLES::glBindTexture(GL_TEXTURE_2D, 0);) + } + + + static std::unordered_map s_bufferDirtyFlags_bufferObj; + void SyncAllBuffersToGLES(BufferState* bufferState) { + GLint currentVBO = 0, currentEBO = 0; + CallAndCheck(::GLES::glGetIntegerv(GL_ARRAY_BUFFER_BINDING, ¤tVBO);) + CallAndCheck(::GLES::glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, ¤tEBO);) + for (auto& [mgBufferId, bufferObj] : bufferState->buffers_) { + if (!bufferObj.generated) continue; + + // TODO: Check if the buffer changes rather than always update it. + if (s_bufferMap.find(mgBufferId) == s_bufferMap.end() || true) { + if (s_bufferMap.find(mgBufferId) == s_bufferMap.end()) { + GLuint glBuffer; + CallAndCheck(::GLES::glGenBuffers(1, &glBuffer);) + s_bufferMap[mgBufferId] = glBuffer; + } + CallAndCheck(::GLES::glBindBuffer(bufferObj.target, s_bufferMap[mgBufferId]);) + CallAndCheck(::GLES::glBufferData( + bufferObj.target, + bufferObj.data.size(), + bufferObj.data.data(), + bufferObj.usage + );) + s_bufferDirtyFlags_bufferObj[mgBufferId] = bufferObj.data.data(); + } + } + CallAndCheck(::GLES::glBindBuffer(GL_ARRAY_BUFFER, currentVBO);) + CallAndCheck(::GLES::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, currentEBO);) + } + + void DrawArraysSHITTILY(GLenum mode, GLint first, GLsizei count) { + + } + + static GLuint lastBoundVAO = 0; + static GLuint lastBoundProgram = 0; + static GLuint lastBoundFBO = 0; + static std::array lastBoundTextures; + void DrawElementsSHITTILY(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices) { + CommonState* commonState = MG_State_T::commonState; + TextureState* textureState = MG_State_T::textureState; + BufferState* bufferState = MG_State_T::bufferState; + VertexArrayState* vaState = MG_State_T::vertexArrayState; + ProgramState* programState = MG_State_T::programState; + FramebufferState* fbState = MG_State_T::framebufferState; + + // Common + const GLint* viewport = commonState->viewport; + if (viewport[2] > 0 && viewport[3] > 0) { + CallAndCheck(::GLES::glViewport(viewport[0], viewport[1], viewport[2], viewport[3]);) + } + + if (commonState->capabilities[GL_BLEND]) { + CallAndCheck(::GLES::glEnable(GL_BLEND);) + CallAndCheck(::GLES::glBlendFuncSeparate( + commonState->blendSrcRGB, commonState->blendDstRGB, + commonState->blendSrcAlpha, commonState->blendDstAlpha + );) + } else { + CallAndCheck(::GLES::glDisable(GL_BLEND);) + } + + if (commonState->capabilities[GL_DEPTH_TEST]) { + CallAndCheck(::GLES::glEnable(GL_DEPTH_TEST);) + CallAndCheck(::GLES::glDepthMask(commonState->depthMask);) + CallAndCheck(::GLES::glDepthFunc(commonState->depthFunc);) + } else { + CallAndCheck(::GLES::glDisable(GL_DEPTH_TEST);) + } + + if (commonState->capabilities[GL_CULL_FACE]) { + CallAndCheck(::GLES::glEnable(GL_CULL_FACE);) + } else { + CallAndCheck(::GLES::glDisable(GL_CULL_FACE);) + } + + CallAndCheck(::GLES::glColorMask( + commonState->colorMask[0], commonState->colorMask[1], + commonState->colorMask[2], commonState->colorMask[3] + );) + + // Texture + SyncAllTexturesToGLES(textureState); + for (size_t unitIdx = 0; unitIdx < textureState->textureUnits_.size(); ++unitIdx) { + TextureUnitState& mgUnit = textureState->textureUnits_[unitIdx]; + GLenum glUnit = GL_TEXTURE0 + unitIdx; + + if (lastBoundTextures[unitIdx] != mgUnit.boundTextures[GL_TEXTURE_2D]) { + CallAndCheck(::GLES::glActiveTexture(glUnit);) + for (auto& [target, texId] : mgUnit.boundTextures) { + if (texId == 0) { + CallAndCheck(::GLES::glBindTexture(target, 0);) + lastBoundTextures[unitIdx] = 0; + continue; + } + + if (s_textureMap.find(texId) == s_textureMap.end()) { + GLuint glTexId; + CallAndCheck(::GLES::glGenTextures(1, &glTexId);) + s_textureMap[texId] = glTexId; + + CallAndCheck(::GLES::glBindTexture(target, glTexId);) + TextureObject& mgTex = textureState->textures[texId]; + + for (auto& [pname, param] : mgTex.params.texPropertiesInt) { + CallAndCheck(::GLES::glTexParameteri(target, pname, param);) + } + for (auto& [pname, param] : mgTex.params.texPropertiesFloat) { + CallAndCheck(::GLES::glTexParameterf(target, pname, param);) + } + + for (auto& [level, mip] : mgTex.params.mipmapData) { + CallAndCheck(::GLES::glTexImage2D( + target, level, mip.internalFormat, + mip.width, mip.height, 0, + mip.format, mip.type, mip.pixelData.data() + );) + } + } + CallAndCheck(::GLES::glBindTexture(target, s_textureMap[texId]);) + lastBoundTextures[unitIdx] = s_textureMap[texId]; + } + } + } + + // Buffer + SyncAllBuffersToGLES(bufferState); + + // VAO + GLuint mgVAO = vaState->currentVao_; + VertexArrayObject* vao = &vaState->vaos_[vaState->currentVao_]; + for (auto& [mgVAOId, mgVAO] : vaState->vaos_) { + if (!mgVAO.generated) continue; + + // TODO: Check is the VAO changes rather than always update it. + //if (!s_vaoMap.count(mgVAOId)) { + GLuint glVAO; + if (!s_vaoMap.count(mgVAOId)) { + CallAndCheck(::GLES::glGenVertexArrays(1, &glVAO);) + s_vaoMap[mgVAOId] = glVAO; + + } else { + glVAO = s_vaoMap[mgVAOId]; + } + CallAndCheck(::GLES::glBindVertexArray(glVAO);) + + if (mgVAO.elementBuffer != 0 && s_bufferMap.count(mgVAO.elementBuffer)) { + CallAndCheck(::GLES::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, s_bufferMap[mgVAO.elementBuffer]);) + } + + for (auto& [index, attrib] : mgVAO.attribs) { + if (attrib.buffer != 0 && s_bufferMap.count(attrib.buffer)) { + CallAndCheck(::GLES::glBindBuffer(GL_ARRAY_BUFFER, s_bufferMap[attrib.buffer]);) + + if (attrib.isInteger) { + CallAndCheck(::GLES::glVertexAttribIPointer( + index, attrib.size, attrib.type, + attrib.stride, attrib.pointer + );) + } else { + CallAndCheck(::GLES::glVertexAttribPointer( + index, attrib.size, attrib.type, + attrib.normalized ? GL_TRUE : GL_FALSE, + attrib.stride, attrib.pointer + );) + } + + if (attrib.enabled) { + CallAndCheck(::GLES::glEnableVertexAttribArray(index);) + } else { + CallAndCheck(::GLES::glDisableVertexAttribArray(index);) + } + } + } + CallAndCheck(::GLES::glBindVertexArray(0);) + //} + } + GLuint currentMgVAO = vaState->currentVao_; + if (s_vaoMap.count(currentMgVAO)) { + CallAndCheck(::GLES::glBindVertexArray(s_vaoMap[currentMgVAO]);) + + VertexArrayObject& currentVAO = vaState->vaos_[currentMgVAO]; + for (auto& [index, attrib] : currentVAO.attribs) { + if (attrib.enabled) { + CallAndCheck(::GLES::glEnableVertexAttribArray(index);) + } else { + CallAndCheck(::GLES::glDisableVertexAttribArray(index);) + } + } + } else { + CallAndCheck(::GLES::glBindVertexArray(0);) + } + + // EBO + if (vao->elementBuffer != 0) { + if (s_bufferMap.count(vao->elementBuffer)) { + CallAndCheck(::GLES::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, s_bufferMap[vao->elementBuffer]);) + } + } else if (indices != nullptr) { + static GLuint dynamicIBO = 0; + if (dynamicIBO == 0) { + CallAndCheck(::GLES::glGenBuffers(1, &dynamicIBO);) + } + + size_t typeSize = 0; + switch(type) { + case GL_UNSIGNED_BYTE: typeSize = sizeof(GLubyte); break; + case GL_UNSIGNED_SHORT: typeSize = sizeof(GLushort); break; + case GL_UNSIGNED_INT: typeSize = sizeof(GLuint); break; + } + size_t dataSize = count * typeSize; + + CallAndCheck(::GLES::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, dynamicIBO);) + CallAndCheck(::GLES::glBufferData(GL_ELEMENT_ARRAY_BUFFER, dataSize, indices, GL_STREAM_DRAW);) + } + + // Program + GLuint currentProgram = programState->GetCurrentProgram(); + if (currentProgram != lastBoundProgram) { + if (s_programMap.find(currentProgram) == s_programMap.end()) { + GLuint glProgram = ::GLES::glCreateProgram(); + ProgramObject& mgProgram = programState->programs_[currentProgram]; + + // Shader + for (GLuint shaderId : mgProgram.attachedShaders) { + ShaderObject& mgShader = programState->shaders_[shaderId]; + GLuint glShader = ::GLES::glCreateShader(mgShader.type); + std::string source = MG_Util::Program::CompileSPIRVToGLSL(mgShader.compiledSpirv, 320, true); + // Post-processing ESSL + source = removeLayoutBinding(source); + source = processOutColorLocations(source); + source = forceSupporterOutput(source); + MG_Util::Debug::LogD( + "shader source:\n%s\nConverted source:\n%s\nSpirv Size:%d", + mgShader.source.c_str(),source.c_str(),mgShader.compiledSpirv.size() + ); + + const char* s[] = { source.c_str() }; + CallAndCheck(::GLES::glShaderSource(glShader, 1, s, nullptr);) + CallAndCheck(::GLES::glCompileShader(glShader);) + + GLint success; + CallAndCheck(::GLES::glGetShaderiv(glShader, GL_COMPILE_STATUS, &success);) + if (!success) { + GLchar infoLog[512]; + CallAndCheck(::GLES::glGetShaderInfoLog(glShader, 512, nullptr, infoLog);) + MG_Util::Debug::LogE("Shader compilation failed: %s", infoLog); + } + CallAndCheck(::GLES::glAttachShader(glProgram, glShader);) + } + + CallAndCheck(::GLES::glLinkProgram(glProgram);) + GLint linkStatus; + CallAndCheck(::GLES::glGetProgramiv(glProgram, GL_LINK_STATUS, &linkStatus);) + if (!linkStatus) { + GLchar infoLog[512]; + CallAndCheck(::GLES::glGetProgramInfoLog(glProgram, 512, nullptr, infoLog);) + MG_Util::Debug::LogE("Program linking failed: %s", infoLog); + } + s_programMap[currentProgram] = glProgram; + } + CallAndCheck(::GLES::glUseProgram(s_programMap[currentProgram]);) + lastBoundProgram = currentProgram; + + // Uniform + ProgramObject& mgProgram = programState->programs_[currentProgram]; + for (auto& [name, uniform] : mgProgram.uniformValues) { + GLint location = ::GLES::glGetUniformLocation(s_programMap[currentProgram], name.c_str()); + if (location == -1) { + MG_Util::Debug::LogE("Uniform location not found: %s", name.c_str()); + continue; + } + MG_Util::Debug::LogD("Uniform %s(type: %u) location: %u(GLES) -- %u(MG)", name.c_str(), uniform.type, location, mgProgram.uniformLocations[name]); + + switch (uniform.type) { + case GL_FLOAT: { + GLsizei num = static_cast(uniform.count / 1); + CallAndCheck(::GLES::glUniform1fv(location, num, uniform.floatData.data());) + break; + } + case GL_FLOAT_VEC2: { + GLsizei num = static_cast(uniform.count / 2); + CallAndCheck(::GLES::glUniform2fv(location, num, uniform.floatData.data());) + break; + } + case GL_FLOAT_VEC3: { + GLsizei num = static_cast(uniform.count / 3); + CallAndCheck(::GLES::glUniform3fv(location, num, uniform.floatData.data());) + break; + } + case GL_FLOAT_VEC4: { + GLsizei num = static_cast(uniform.count / 4); + CallAndCheck(::GLES::glUniform4fv(location, num, uniform.floatData.data());) + break; + } + + case GL_INT: + case GL_BOOL: + 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: { + GLsizei num = static_cast(uniform.count / 1); + CallAndCheck(::GLES::glUniform1iv(location, num, uniform.intData.data());) + break; + } + case GL_INT_VEC2: + case GL_BOOL_VEC2: { + GLsizei num = static_cast(uniform.count / 2); + CallAndCheck(::GLES::glUniform2iv(location, num, uniform.intData.data());) + break; + } + case GL_INT_VEC3: + case GL_BOOL_VEC3: { + GLsizei num = static_cast(uniform.count / 3); + CallAndCheck(::GLES::glUniform3iv(location, num, uniform.intData.data());) + break; + } + case GL_INT_VEC4: + case GL_BOOL_VEC4: { + GLsizei num = static_cast(uniform.count / 4); + CallAndCheck(::GLES::glUniform4iv(location, num, uniform.intData.data());) + break; + } + + case GL_UNSIGNED_INT: { + GLsizei num = static_cast(uniform.count / 1); + CallAndCheck(::GLES::glUniform1uiv(location, num, uniform.uintData.data());) + break; + } + case GL_UNSIGNED_INT_VEC2: { + GLsizei num = static_cast(uniform.count / 2); + CallAndCheck(::GLES::glUniform2uiv(location, num, uniform.uintData.data());) + break; + } + case GL_UNSIGNED_INT_VEC3: { + GLsizei num = static_cast(uniform.count / 3); + CallAndCheck(::GLES::glUniform3uiv(location, num, uniform.uintData.data());) + break; + } + case GL_UNSIGNED_INT_VEC4: { + GLsizei num = static_cast(uniform.count / 4); + CallAndCheck(::GLES::glUniform4uiv(location, num, uniform.uintData.data());) + break; + } + + case GL_FLOAT_MAT2: { + GLsizei num = static_cast(uniform.count / 4); + CallAndCheck(::GLES::glUniformMatrix2fv(location, num, GL_FALSE, uniform.floatData.data());) + break; + } + case GL_FLOAT_MAT3: { + GLsizei num = static_cast(uniform.count / 9); + CallAndCheck(::GLES::glUniformMatrix3fv(location, num, GL_FALSE, uniform.floatData.data());) + break; + } + case GL_FLOAT_MAT4: { + GLsizei num = static_cast(uniform.count / 16); + CallAndCheck(::GLES::glUniformMatrix4fv(location, num, GL_FALSE, uniform.floatData.data());) + break; + } + case GL_FLOAT_MAT2x3: { + GLsizei num = static_cast(uniform.count / 6); + CallAndCheck(::GLES::glUniformMatrix2x3fv(location, num, GL_FALSE, uniform.floatData.data());) + break; + } + case GL_FLOAT_MAT2x4: { + GLsizei num = static_cast(uniform.count / 8); + CallAndCheck(::GLES::glUniformMatrix2x4fv(location, num, GL_FALSE, uniform.floatData.data());) + break; + } + case GL_FLOAT_MAT3x2: { + GLsizei num = static_cast(uniform.count / 6); + CallAndCheck(::GLES::glUniformMatrix3x2fv(location, num, GL_FALSE, uniform.floatData.data());) + break; + } + case GL_FLOAT_MAT3x4: { + GLsizei num = static_cast(uniform.count / 12); + CallAndCheck(::GLES::glUniformMatrix3x4fv(location, num, GL_FALSE, uniform.floatData.data());) + break; + } + case GL_FLOAT_MAT4x2: { + GLsizei num = static_cast(uniform.count / 8); + CallAndCheck(::GLES::glUniformMatrix4x2fv(location, num, GL_FALSE, uniform.floatData.data());) + break; + } + case GL_FLOAT_MAT4x3: { + GLsizei num = static_cast(uniform.count / 12); + CallAndCheck(::GLES::glUniformMatrix4x3fv(location, num, GL_FALSE, uniform.floatData.data());) + break; + } + } + + } + } + + // Framebuffer + GLuint currentFBO = fbState->currentBindings_[GL_DRAW_FRAMEBUFFER]; + if (currentFBO != lastBoundFBO) { + GLuint glFBO = 0; + + if (currentFBO == 0) { + CallAndCheck(::GLES::glBindFramebuffer(GL_FRAMEBUFFER, 0);) + glFBO = 0; + } else { + bool isNewGlesFBO = false; + + if (s_framebufferMap.find(currentFBO) == s_framebufferMap.end()) { + CallAndCheck(::GLES::glGenFramebuffers(1, &glFBO);) + s_framebufferMap[currentFBO] = glFBO; + CallAndCheck(::GLES::glBindFramebuffer(GL_FRAMEBUFFER, glFBO);) + MG_Util::Debug::LogD("Generated and bound new GLES FBO %u for MobileGL FBO %u", glFBO, currentFBO); + GLenum status = ::GLES::glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { + MG_Util::Debug::LogE("Framebuffer %u (GLES FBO %u) is not complete after creation! Status: 0x%X (%s)", + currentFBO, glFBO, status, MG_Util::Debug::GLEnumToString(status)); + } + isNewGlesFBO = true; + } else { + glFBO = s_framebufferMap[currentFBO]; + CallAndCheck(::GLES::glBindFramebuffer(GL_FRAMEBUFFER, glFBO);) + MG_Util::Debug::LogD("Bound existing GLES FBO %u for MobileGL FBO %u", glFBO, currentFBO); + } + + if (glFBO != 0) { + FramebufferObject* mgFBO = fbState->GetCurrentFBO(GL_DRAW_FRAMEBUFFER); + if (mgFBO) { + MG_Util::Debug::LogD("Checking/Syncing attachments for GLES FBO %u (MobileGL FBO %u)", glFBO, currentFBO); + + for (auto const& [mgAttachmentPoint, mgAtt] : mgFBO->attachments) { + + if (mgAtt.type != GL_TEXTURE_2D) { + MG_Util::Debug::LogW("Skipping non-TEXTURE_2D attachment 0x%X for FBO %u", mgAttachmentPoint, currentFBO); + continue; + } + + GLuint expectedGLTexId = 0; + if (mgAtt.handle != 0) { + if (s_textureMap.count(mgAtt.handle)) { + expectedGLTexId = s_textureMap[mgAtt.handle]; + } else { + MG_Util::Debug::LogE("MobileGL Texture %u for FBO %u attachment 0x%X not found in s_textureMap during FBO sync!", mgAtt.handle, currentFBO, mgAttachmentPoint); + for (auto const& [key, val] : s_textureMap) + MG_Util::Debug::LogW(" key: %d, val: %d", key, val); + continue; + } + } + + GLint glesAttachedType = 0; + GLint glesAttachedName = 0; + + CallAndCheck(::GLES::glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, mgAttachmentPoint, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &glesAttachedType);) + + if (glesAttachedType == GL_TEXTURE) { + CallAndCheck(::GLES::glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, mgAttachmentPoint, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &glesAttachedName);) + } else if (glesAttachedType != GL_NONE) { + MG_Util::Debug::LogW("GLES FBO %u attachment 0x%X has non-texture type 0x%X (expected GL_TEXTURE or GL_NONE)", glFBO, mgAttachmentPoint, glesAttachedType); + } + + if ((GLuint)glesAttachedName != expectedGLTexId) { + MG_Util::Debug::LogD("Syncing FBO %u attachment 0x%X: Expected GLES TexID %u, Found GLES ObjName %d. Attaching/Detaching...", + currentFBO, mgAttachmentPoint, expectedGLTexId, glesAttachedName); + + CallAndCheck(::GLES::glFramebufferTexture2D( + GL_FRAMEBUFFER, + mgAttachmentPoint, + GL_TEXTURE_2D, + expectedGLTexId, + mgAtt.mipLevel + );) + } + } + GLenum status = ::GLES::glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { + MG_Util::Debug::LogE("Framebuffer %u (GLES FBO %u) is not complete after sync! Status: 0x%X (%s)", + currentFBO, glFBO, status, MG_Util::Debug::GLEnumToString(status)); + } + + } else { + MG_Util::Debug::LogW("Could not get MobileGL FBO object for ID %u during attachment sync.", currentFBO); + } + } + } + + lastBoundFBO = currentFBO; + } + + + if (vao->elementBuffer != 0 || indices != nullptr) { + CallAndCheck(::GLES::glDrawElements( + mode, + count, + type, + indices + );) + } + } + + void ClearSHITTILY(GLbitfield mask) { + CommonState* commonState = MG_State_T::commonState; + FramebufferState* fbState = MG_State_T::framebufferState; + + GLuint currentFBO = fbState->currentBindings_[GL_DRAW_FRAMEBUFFER]; + if (currentFBO != lastBoundFBO) { + GLuint glFBO = 0; + + if (currentFBO == 0) { + CallAndCheck(::GLES::glBindFramebuffer(GL_FRAMEBUFFER, 0);) + glFBO = 0; + } else { + bool isNewGlesFBO = false; + + if (s_framebufferMap.find(currentFBO) == s_framebufferMap.end()) { + CallAndCheck(::GLES::glGenFramebuffers(1, &glFBO);) + s_framebufferMap[currentFBO] = glFBO; + CallAndCheck(::GLES::glBindFramebuffer(GL_FRAMEBUFFER, glFBO);) + MG_Util::Debug::LogD("Generated and bound new GLES FBO %u for MobileGL FBO %u", glFBO, currentFBO); + isNewGlesFBO = true; + } else { + glFBO = s_framebufferMap[currentFBO]; + CallAndCheck(::GLES::glBindFramebuffer(GL_FRAMEBUFFER, glFBO);) + MG_Util::Debug::LogD("Bound existing GLES FBO %u for MobileGL FBO %u", glFBO, currentFBO); + } + + if (glFBO != 0) { + FramebufferObject* mgFBO = fbState->GetCurrentFBO(GL_DRAW_FRAMEBUFFER); + if (mgFBO) { + MG_Util::Debug::LogD("Checking/Syncing attachments for GLES FBO %u (MobileGL FBO %u)", glFBO, currentFBO); + + for (auto const& [mgAttachmentPoint, mgAtt] : mgFBO->attachments) { + + if (mgAtt.type != GL_TEXTURE_2D) { + MG_Util::Debug::LogW("Skipping non-TEXTURE_2D attachment 0x%X for FBO %u", mgAttachmentPoint, currentFBO); + continue; + } + + GLuint expectedGLTexId = 0; + if (mgAtt.handle != 0) { + if (s_textureMap.count(mgAtt.handle)) { + expectedGLTexId = s_textureMap[mgAtt.handle]; + } else { + MG_Util::Debug::LogE("MobileGL Texture %u for FBO %u attachment 0x%X not found in s_textureMap during FBO sync!", mgAtt.handle, currentFBO, mgAttachmentPoint); + for (auto const& [key, val] : s_textureMap) + MG_Util::Debug::LogW(" key: %d, val: %d", key, val); + continue; + } + } + + GLint glesAttachedType = 0; + GLint glesAttachedName = 0; + + CallAndCheck(::GLES::glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, mgAttachmentPoint, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &glesAttachedType);) + + if (glesAttachedType == GL_TEXTURE) { + CallAndCheck(::GLES::glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, mgAttachmentPoint, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &glesAttachedName);) + } else if (glesAttachedType != GL_NONE) { + MG_Util::Debug::LogW("GLES FBO %u attachment 0x%X has non-texture type 0x%X (expected GL_TEXTURE or GL_NONE)", glFBO, mgAttachmentPoint, glesAttachedType); + } + + if ((GLuint)glesAttachedName != expectedGLTexId) { + MG_Util::Debug::LogD("Syncing FBO %u attachment 0x%X: Expected GLES TexID %u, Found GLES ObjName %d. Attaching/Detaching...", + currentFBO, mgAttachmentPoint, expectedGLTexId, glesAttachedName); + + CallAndCheck(::GLES::glFramebufferTexture2D( + GL_FRAMEBUFFER, + mgAttachmentPoint, + GL_TEXTURE_2D, + expectedGLTexId, + mgAtt.mipLevel + );) + } + } + + GLenum status = ::GLES::glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { + MG_Util::Debug::LogE("Framebuffer %u (GLES FBO %u) is not complete after sync! Status: 0x%X (%s)", + currentFBO, glFBO, status, MG_Util::Debug::GLEnumToString(status)); + } + + } else { + MG_Util::Debug::LogW("Could not get MobileGL FBO object for ID %u during attachment sync.", currentFBO); + } + } + } + + lastBoundFBO = currentFBO; + } + + + static GLfloat lastClearColor[4] = {-1.0f, -1.0f, -1.0f, -1.0f}; + if (memcmp(lastClearColor, commonState->clearColor, sizeof(lastClearColor)) != 0) { + CallAndCheck(::GLES::glClearColor( + commonState->clearColor[0], + commonState->clearColor[1], + commonState->clearColor[2], + commonState->clearColor[3] + );) + memcpy(lastClearColor, commonState->clearColor, sizeof(lastClearColor)); + } + + static GLfloat lastClearDepth = -1.0f; + if (lastClearDepth != commonState->clearDepth) { + CallAndCheck(::GLES::glClearDepthf(commonState->clearDepth);) + lastClearDepth = commonState->clearDepth; + } + + static GLint lastClearStencil = -1; + GLint currentStencil = 0; + if (lastClearStencil != currentStencil) { + CallAndCheck(::GLES::glClearStencil(currentStencil);) + lastClearStencil = currentStencil; + } + + const GLint* viewport = commonState->viewport; + if (viewport[2] > 0 && viewport[3] > 0) { + CallAndCheck(::GLES::glViewport(viewport[0], viewport[1], viewport[2], viewport[3]);) + } + + if (commonState->capabilities[GL_BLEND]) { + CallAndCheck(::GLES::glEnable(GL_BLEND);) + CallAndCheck(::GLES::glBlendFuncSeparate( + commonState->blendSrcRGB, commonState->blendDstRGB, + commonState->blendSrcAlpha, commonState->blendDstAlpha + );) + } else { + CallAndCheck(::GLES::glDisable(GL_BLEND);) + } + + if (commonState->capabilities[GL_DEPTH_TEST]) { + CallAndCheck(::GLES::glEnable(GL_DEPTH_TEST);) + CallAndCheck(::GLES::glDepthMask(commonState->depthMask);) + CallAndCheck(::GLES::glDepthFunc(commonState->depthFunc);) + } else { + CallAndCheck(::GLES::glDisable(GL_DEPTH_TEST);) + } + + if (commonState->capabilities[GL_CULL_FACE]) { + CallAndCheck(::GLES::glEnable(GL_CULL_FACE);) + } else { + CallAndCheck(::GLES::glDisable(GL_CULL_FACE);) + } + + CallAndCheck(::GLES::glColorMask( + commonState->colorMask[0], commonState->colorMask[1], + commonState->colorMask[2], commonState->colorMask[3] + );) + + CallAndCheck(::GLES::glClear(mask);) + + } + + void Clear(GLbitfield mask) { + MG_Util::Debug::LogD("glClear, mask: 0x%X", mask); + GLenum result = MG_State::glClear(mask); + if (result == GL_NO_ERROR) { + ClearSHITTILY(mask); + return; + } + MG_State::SetError(result); + MG_Util::Debug::LogE("Error clearing buffers: %s", MG_Util::Debug::GLEnumToString(result)); + } + void DrawElements(GLenum mode, GLsizei count, GLenum type, const void *indices) { - ::GLES::glViewport(0,0,3200,1440); - MG_RHI::GLES::Test::DrawAllTextures(); - }; + DrawElementsSHITTILY(mode, count, type, indices); + } + void DrawArrays(GLenum mode, GLint first, GLsizei count) { - ::GLES::glViewport(0,0,3200,1440); - MG_RHI::GLES::Test::DrawAllTextures(); - }; + DrawArraysSHITTILY(mode, first, count); + } } \ No newline at end of file diff --git a/MG/MG_GL/Implementations/GL/Drawing/GL_Drawing.h b/MG/MG_GL/Implementations/GL/Drawing/GL_Drawing.h index 0eff0fcb..fe06ce5d 100644 --- a/MG/MG_GL/Implementations/GL/Drawing/GL_Drawing.h +++ b/MG/MG_GL/Implementations/GL/Drawing/GL_Drawing.h @@ -4,9 +4,7 @@ #ifndef MOBILEGL_GL_DRAWING_H #define MOBILEGL_GL_DRAWING_H - #include "../../../../Includes.h" - namespace MG_GL::GL { void DrawElements(GLenum mode, GLsizei count, GLenum type, const void *indices); void DrawArrays(GLenum mode, GLint first, GLsizei count); diff --git a/MG/MG_GL/State/Buffer/BufferState.h b/MG/MG_GL/State/Buffer/BufferState.h index 28db6c1f..948953f3 100644 --- a/MG/MG_GL/State/Buffer/BufferState.h +++ b/MG/MG_GL/State/Buffer/BufferState.h @@ -32,11 +32,11 @@ public: void Delete(GLuint buffer); GLuint GetCurrentBinding(GLenum target) const; -private: + std::unordered_map currentBindings_; std::unordered_map buffers_; +private: std::set freeIds_; GLuint lastId_ = 0; - std::unordered_map currentBindings_; static bool IsValidTarget_(GLenum target); }; diff --git a/MG/MG_GL/State/Common/CommonState.cpp b/MG/MG_GL/State/Common/CommonState.cpp index ec437424..915d57c0 100644 --- a/MG/MG_GL/State/Common/CommonState.cpp +++ b/MG/MG_GL/State/Common/CommonState.cpp @@ -107,6 +107,7 @@ GLenum CommonState::Clear(GLbitfield mask) { if ((mask & ~validBits) != 0) { return GL_INVALID_VALUE; } + clearMask = mask; return GL_NO_ERROR; } diff --git a/MG/MG_GL/State/Common/CommonState.h b/MG/MG_GL/State/Common/CommonState.h index df77e207..b19417bb 100644 --- a/MG/MG_GL/State/Common/CommonState.h +++ b/MG/MG_GL/State/Common/CommonState.h @@ -9,7 +9,13 @@ #include "../../../Includes.h" class CommonState { -private: +public: + // Viewport + GLint viewport[4] = {0, 0, 0, 0}; + + // Color mask + GLboolean colorMask[4] = {GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE}; + // Pixel storage parameters std::unordered_map pixelStoreParams; @@ -22,21 +28,15 @@ private: // Clear state GLfloat clearColor[4] = {0.0f, 0.0f, 0.0f, 0.0f}; GLfloat clearDepth = 1.0f; - - // Color mask - GLboolean colorMask[4] = {GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE}; - + GLenum clearMask; + // Depth state GLenum depthFunc = GL_LESS; GLboolean depthMask = GL_TRUE; - - // Viewport - GLint viewport[4] = {0, 0, 0, 0}; - + // Capability enables std::unordered_map capabilities; -public: CommonState(); // Return: the validity of the operation, according to OpenGL 3 standard @@ -57,6 +57,7 @@ public: GLenum Viewport(GLint x, GLint y, GLsizei width, GLsizei height); GLint QueryPixelStoreInt(GLenum pname); + }; #endif //MOBILEGL_COMMONSTATE_H diff --git a/MG/MG_GL/State/Framebuffer/FramebufferState.cpp b/MG/MG_GL/State/Framebuffer/FramebufferState.cpp index 2c899445..479813b8 100644 --- a/MG/MG_GL/State/Framebuffer/FramebufferState.cpp +++ b/MG/MG_GL/State/Framebuffer/FramebufferState.cpp @@ -48,7 +48,6 @@ GLenum FramebufferState::Bind(GLenum target, GLuint framebuffer) { if (target == GL_FRAMEBUFFER) { currentBindings_[GL_READ_FRAMEBUFFER] = framebuffer; currentBindings_[GL_DRAW_FRAMEBUFFER] = framebuffer; - currentBindings_[GL_FRAMEBUFFER] = framebuffer; } else { currentBindings_[target] = framebuffer; } @@ -60,6 +59,7 @@ GLenum FramebufferState::glAttachTexture2D(GLenum target, GLenum attachment, if (MG_Constants::Framebuffer::VALID_ATTACHMENTS.find(attachment) == MG_Constants::Framebuffer::VALID_ATTACHMENTS.end() || MG_Constants::Texture::VALID_TARGETS.find(textarget) == MG_Constants::Texture::VALID_TARGETS.end()) return GL_INVALID_ENUM; + if (target == GL_FRAMEBUFFER) target = GL_DRAW_FRAMEBUFFER; FramebufferObject* fbo = GetCurrentFBO(target); if (!fbo) return GL_INVALID_OPERATION; if (texture != 0 && !MG_State_T::textureState->IsTexture(texture)) @@ -84,6 +84,7 @@ GLenum FramebufferState::glAttachTexture2D(GLenum target, GLenum attachment, } GLenum FramebufferState::glValidateCompleteness(GLenum target) { + if (target == GL_FRAMEBUFFER) target = GL_DRAW_FRAMEBUFFER; FramebufferObject* fbo = GetCurrentFBO(target); if (!fbo) return GL_INVALID_OPERATION; return fbo->status; diff --git a/MG/MG_GL/State/Framebuffer/FramebufferState.h b/MG/MG_GL/State/Framebuffer/FramebufferState.h index 8cf7b3df..0bbae8a2 100644 --- a/MG/MG_GL/State/Framebuffer/FramebufferState.h +++ b/MG/MG_GL/State/Framebuffer/FramebufferState.h @@ -35,9 +35,10 @@ public: GLenum glValidateCompleteness(GLenum target); FramebufferObject* GetCurrentFBO(GLenum target); + std::unordered_map currentBindings_; // READ/DRAW/FRAMEBUFFER + private: std::unordered_map framebuffers_; - std::unordered_map currentBindings_; // READ/DRAW/FRAMEBUFFER std::set freeIds_; GLuint lastId_ = 0; bool ValidateHandle(GLuint framebuffer); diff --git a/MG/MG_GL/State/Program/ProgramState.h b/MG/MG_GL/State/Program/ProgramState.h index 5ea2ce7c..bbcfe372 100644 --- a/MG/MG_GL/State/Program/ProgramState.h +++ b/MG/MG_GL/State/Program/ProgramState.h @@ -47,7 +47,7 @@ struct ProgramObject { }; class ProgramState { -private: +public: std::unordered_map shaders_; std::unordered_map programs_; std::set freeShaderIds_; @@ -56,7 +56,6 @@ private: GLuint lastProgramId_ = 0; GLuint currentProgram_ = 0; -public: ProgramState(); ~ProgramState(); diff --git a/MG/MG_GL/State/Texture/TextureState.h b/MG/MG_GL/State/Texture/TextureState.h index b192ca76..804bc3a8 100644 --- a/MG/MG_GL/State/Texture/TextureState.h +++ b/MG/MG_GL/State/Texture/TextureState.h @@ -55,7 +55,6 @@ public: class TextureState { private: - std::vector textureUnits_; GLuint activeTextureUnit_ = 0; GLuint lastUsedID_ = 0; @@ -88,6 +87,7 @@ public: GLenum DeleteN(GLsizei n, const GLuint* textures); GLenum QueryLevelPropertyIntVector(GLenum target, GLint level, GLenum pname, GLint* params); + std::vector textureUnits_; private: size_t CalculatePixelDataSize_(GLenum format, GLenum type, GLsizei width, GLsizei height); void InvalidateTextureInAllUnits_(GLuint texture); diff --git a/MG/MG_GL/State/VertexArray/VertexArrayState.h b/MG/MG_GL/State/VertexArray/VertexArrayState.h index 8b02e9b9..ac19561c 100644 --- a/MG/MG_GL/State/VertexArray/VertexArrayState.h +++ b/MG/MG_GL/State/VertexArray/VertexArrayState.h @@ -9,7 +9,7 @@ #include "../../../Includes.h" struct VertexAttribState { - bool enabled = false; + bool enabled; GLint size = 4; GLenum type = GL_FLOAT; GLboolean normalized = GL_FALSE; @@ -43,11 +43,11 @@ public: GLuint GetBoundElementBuffer(); VertexArrayObject* GetCurrentVAO(); -private: + GLuint currentVao_ = 0; std::unordered_map vaos_; +private: std::set freeIds_; GLuint lastId_ = 0; - GLuint currentVao_ = 0; }; #endif //MOBILEGL_VERTEXARRAYSTATE_H diff --git a/MG/MG_UTIL/Program/GLSLTool.cpp b/MG/MG_UTIL/Program/GLSLTool.cpp index 91a77e9d..ad6983b8 100644 --- a/MG/MG_UTIL/Program/GLSLTool.cpp +++ b/MG/MG_UTIL/Program/GLSLTool.cpp @@ -402,4 +402,39 @@ namespace MG_Util::Program { } spvc_context_destroy(context); } + + std::string CompileSPIRVToGLSL(std::vector spirv, uint glslVersion, bool isES) { + spvc_context context = nullptr; + spvc_parsed_ir ir = nullptr; + spvc_compiler compiler_glsl = nullptr; + spvc_compiler_options options = nullptr; + spvc_resources resources = nullptr; + const spvc_reflected_resource *list = nullptr; + const char *result = nullptr; + size_t count; + + const SpvId *p_spirv = spirv.data(); + size_t word_count = spirv.size(); + + spvc_context_create(&context); + spvc_context_parse_spirv(context, p_spirv, word_count, &ir); + spvc_context_create_compiler(context, SPVC_BACKEND_GLSL, ir, SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, &compiler_glsl); + spvc_compiler_create_shader_resources(compiler_glsl, &resources); + spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_UNIFORM_BUFFER, &list, &count); + spvc_compiler_create_compiler_options(compiler_glsl, &options); + spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_GLSL_VERSION, glslVersion); + spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_GLSL_ES, isES ? SPVC_TRUE : SPVC_FALSE); + spvc_compiler_install_compiler_options(compiler_glsl, options); + spvc_compiler_compile(compiler_glsl, &result); + + if (!result) { + return{}; + } + + std::string essl = result; + + spvc_context_destroy(context); + + return essl; + } } \ No newline at end of file diff --git a/MG/MG_UTIL/Program/GLSLTool.h b/MG/MG_UTIL/Program/GLSLTool.h index 3d6812e7..2488f79c 100644 --- a/MG/MG_UTIL/Program/GLSLTool.h +++ b/MG/MG_UTIL/Program/GLSLTool.h @@ -177,6 +177,7 @@ namespace MG_Util::Program { std::string CompileGLSLToTShader(GLenum shaderType, const std::string& source, glslang::TShader *&shader); std::vector> CompileMultipleShadersToSPIRV(const ProgramState& state, ProgramObject& prog, std::string& infoLog); void ReflectSPIRVUniforms(const std::vector>& allSpirv, ProgramObject& prog, std::string& infoLog); + std::string CompileSPIRVToGLSL(std::vector spirv, uint glslVersion, bool isES); } #endif //MOBILEGL_GLSLTOOL_H