// // Created by BZLZHH on 2025/3/15. // #include "GL_Drawing.h" #undef MOBILEGL_GLSLTOOL_H #include "../../../../Includes.h" namespace MG_GL::GL { template using unordered_map = ankerl::unordered_dense::map; void NormalizePixelFormat(GLenum internalFormat, GLenum type, GLenum format, GLenum* outInternalFormat, GLenum* outType, GLenum* outFormat) { // if (format && *format == GL_BGRA) // *format = GL_RGBA; switch (internalFormat) { case GL_DEPTH_COMPONENT16: if (outInternalFormat) *outInternalFormat = internalFormat; if (outType) *outType = GL_UNSIGNED_SHORT; if (outFormat) *outFormat = format; break; case GL_DEPTH_COMPONENT24: if (outInternalFormat) *outInternalFormat = internalFormat; if (outType) *outType = GL_UNSIGNED_INT; if (outFormat) *outFormat = format; break; case GL_DEPTH_COMPONENT32: if (outInternalFormat) *outInternalFormat = GL_DEPTH_COMPONENT32F; if (outType) *outType = GL_FLOAT; if (outFormat) *outFormat = format; break; case GL_DEPTH_COMPONENT32F: if (outInternalFormat) *outInternalFormat = internalFormat; if (outType) *outType = GL_FLOAT; if (outFormat) *outFormat = format; break; case GL_DEPTH_COMPONENT: MG_Util::Debug::LogD("Find GL_DEPTH_COMPONENT: internalFormat: %s, format: %s, type: %s", MG_Util::Debug::GLEnumToString(internalFormat), MG_Util::Debug::GLEnumToString(format), MG_Util::Debug::GLEnumToString(type)); if (outInternalFormat) *outInternalFormat = internalFormat; if (outType) *outType = GL_UNSIGNED_INT; if (outFormat) *outFormat = format; break; case GL_DEPTH_STENCIL: if (outInternalFormat) *outInternalFormat = GL_DEPTH32F_STENCIL8; if (outType) *outType = GL_FLOAT_32_UNSIGNED_INT_24_8_REV; if (outFormat) *outFormat = format; break; case GL_RGB10_A2: if (outInternalFormat) *outInternalFormat = internalFormat; if (outType) *outType = GL_UNSIGNED_INT_2_10_10_10_REV; if (outFormat) *outFormat = format; break; case GL_RGB5_A1: if (outInternalFormat) *outInternalFormat = internalFormat; if (outType) *outType = GL_UNSIGNED_SHORT_5_5_5_1; if (outFormat) *outFormat = format; break; case GL_COMPRESSED_RED_RGTC1: case GL_COMPRESSED_RG_RGTC2: MG_Util::Debug::LogD("GL_COMPRESSED_RED_RGTC1 or GL_COMPRESSED_RG_RGTC2 is not supported!"); break; case GL_SRGB8: if (outInternalFormat) *outInternalFormat = internalFormat; if (outType) *outType = GL_UNSIGNED_BYTE; if (outFormat) *outFormat = format; break; case GL_RGBA32F: case GL_RGB32F: case GL_RG32F: case GL_R32F: if (outInternalFormat) *outInternalFormat = internalFormat; if (outType) *outType = GL_FLOAT; if (outFormat) *outFormat = format; break; case GL_RGB9_E5: if (outInternalFormat) *outInternalFormat = internalFormat; if (outType) *outType = GL_UNSIGNED_INT_5_9_9_9_REV; if (outFormat) *outFormat = format; break; case GL_R11F_G11F_B10F: if (outInternalFormat) *outInternalFormat = internalFormat; if (outType) *outType = GL_UNSIGNED_INT_10F_11F_11F_REV; if (outFormat) *outFormat = GL_RGB; break; case GL_RGBA32UI: case GL_RGB32UI: case GL_RG32UI: case GL_R32UI: if (outInternalFormat) *outInternalFormat = internalFormat; if (outType) *outType = GL_UNSIGNED_INT; if (outFormat) *outFormat = format; break; case GL_RGBA32I: case GL_RGB32I: case GL_RG32I: case GL_R32I: if (outInternalFormat) *outInternalFormat = internalFormat; if (outType) *outType = GL_INT; if (outFormat) *outFormat = format; break; case GL_RGBA16: { // if (!checked_rgba16) { // support_rgba16 = check_rgba16(); // checked_rgba16 = true; // } // if (support_rgba16) { // if(type) // *type = GL_UNSIGNED_SHORT; // } else { // *internal_format = GL_RGBA16F; // if(type) // *type = GL_FLOAT; // } if (outInternalFormat) *outInternalFormat = internalFormat; if (outType) *outType = GL_FLOAT; if (outFormat) *outFormat = format; break; } case GL_RGBA8: if (outInternalFormat) *outInternalFormat = internalFormat; if (outType) *outType = GL_UNSIGNED_BYTE; if (outFormat) *outFormat = GL_RGBA; break; case GL_RGBA: if (outInternalFormat) *outInternalFormat = internalFormat; if (outType) *outType = GL_UNSIGNED_BYTE; if (outFormat) *outFormat = GL_RGBA; break; case GL_RGBA16F: case GL_R16F: if (outInternalFormat) *outInternalFormat = internalFormat; if (outType) *outType = GL_HALF_FLOAT; if (outFormat) *outFormat = format; break; case GL_R16: if (outInternalFormat) *outInternalFormat = GL_R16F; if (outType) *outType = GL_FLOAT; if (outFormat) *outFormat = format; break; case GL_RGB16: if (outInternalFormat) *outInternalFormat = GL_RGB16F; if (outType) *outType = GL_HALF_FLOAT; if (outFormat) *outFormat = GL_RGB; break; case GL_RGB16F: if (outInternalFormat) *outInternalFormat = internalFormat; if (outType) *outType = GL_HALF_FLOAT; if (outFormat) *outFormat = GL_RGB; break; case GL_RG16: case GL_RG16F: if (outInternalFormat) *outInternalFormat = GL_RG16F; if (outType) *outType = GL_HALF_FLOAT; if (outFormat) *outFormat = GL_RG; break; case GL_R8: if (outInternalFormat) *outInternalFormat = internalFormat; if (outType) *outType = GL_UNSIGNED_BYTE; if (outFormat) *outFormat = GL_RED; break; case GL_R8UI: if (outInternalFormat) *outInternalFormat = internalFormat; if (outType) *outType = GL_UNSIGNED_BYTE; if (outFormat) *outFormat = GL_RED_INTEGER; break; case GL_RGB8_SNORM: case GL_RGBA8_SNORM: if (outInternalFormat) *outInternalFormat = internalFormat; if (outType) *outType = GL_BYTE; if (outFormat) *outFormat = format; break; case GL_RGB8: if (outInternalFormat) *outInternalFormat = internalFormat; if (outType && type != GL_UNSIGNED_BYTE) *outType = GL_UNSIGNED_BYTE; if (outFormat) *outFormat = GL_RGB; break; case GL_RGBA16_SNORM: if (outInternalFormat) *outInternalFormat = internalFormat; if (outType) *outType = GL_SHORT; if (outFormat) *outFormat = format; break; default: MG_Util::Debug::LogD("NormalizePixelFormat: no conversion"); if (outInternalFormat) *outInternalFormat = internalFormat; if (outType) *outType = type; if (outFormat) *outFormat = format; break; } } 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 unordered_map s_textureMap; static unordered_map s_vaoMap; static unordered_map s_bufferMap; static unordered_map s_programMap; static unordered_map s_framebufferMap; struct TexParamCache { std::unordered_map lastInt; std::unordered_map lastFloat; }; static std::unordered_map cache; 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; GLuint glTexId = 0; if (s_textureMap.find(mgTexId) == s_textureMap.end()) { CallAndCheck(::GLES::glGenTextures(1, &glTexId);) s_textureMap[mgTexId] = glTexId; MG_Util::Debug::LogD("Created new GLES texture %u (MobileGL ID)", mgTexId); } else { glTexId = s_textureMap[mgTexId]; } GLenum target = texObj.target; CallAndCheck(::GLES::glBindTexture(target, glTexId);) TexParamCache& entry = cache[&texObj]; for (auto& [pname, param] : texObj.params.texPropertiesInt) { auto it = entry.lastInt.find(pname); if (it == entry.lastInt.end() || it->second != param) { CallAndCheck(::GLES::glTexParameteri(target, pname, param);) entry.lastInt[pname] = param; } } for (auto& [pname, param] : texObj.params.texPropertiesFloat) { auto it = entry.lastFloat.find(pname); if (it == entry.lastFloat.end() || it->second != param) { CallAndCheck(::GLES::glTexParameterf(target, pname, param);) entry.lastFloat[pname] = param; } } for (auto& [level, mip] : texObj.params.mipmapData) { if (!mip.dirty) continue; mip.dirty = false; const void* data = !mip.pixelData.empty() ? mip.pixelData.data() : nullptr; switch (target) { case GL_TEXTURE_2D: { GLenum internalFormat = 0, type = 0, format = 0; NormalizePixelFormat(mip.internalFormat, mip.type, mip.format, &internalFormat, &type, &format); CallAndCheck(::GLES::glTexImage2D( target, level, internalFormat, mip.width, mip.height, 0, format, type, data );) 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("Updated GLES texture %u (MobileGL ID)", mgTexId); } } void SyncAllBuffersToGLES(BufferState* bufferState) { GLint prev_buf = 0; // Delete removed buffers in GLES std::vector buffersToErase; for (auto it = s_bufferMap.begin(); it != s_bufferMap.end(); ++it) { if (bufferState->buffers_.find(it->first) == bufferState->buffers_.end()) { CallAndCheck(::GLES::glDeleteBuffers(1, &it->second);) buffersToErase.push_back(it->first); } } for (GLuint mgname : buffersToErase) { s_bufferMap.erase(mgname); } CallAndCheck(::GLES::glGetIntegerv(GL_COPY_WRITE_BUFFER_BINDING, &prev_buf);) for (auto& [mgname, obj] : bufferState->buffers_) { if (!obj.generated) continue; if (!obj.dirty) continue; // keep mapped buffer dirty, // as it can change at any time if (!obj.isMapped) obj.dirty = false; GLuint glname = 0; // Gen real buffers at ES if (s_bufferMap.find(mgname) == s_bufferMap.end()) { CallAndCheck(::GLES::glGenBuffers(1, &glname);) s_bufferMap[mgname] = glname; if constexpr (MG_Global::Common::LogLevel <= MG_Constants::Common::LOG_LEVEL_DEBUG) { MG_Util::Debug::LogD("Creating buffer MG %d -> ES %d", mgname, glname); std::string name = std::format("MG Buffer {}", mgname); CallAndCheck(::GLES::glObjectLabel(GL_BUFFER, glname, name.length(), name.c_str());) } } else { glname = s_bufferMap[mgname]; } // Populate data to ES CallAndCheck(::GLES::glBindBuffer(GL_COPY_WRITE_BUFFER, glname);) // TODO: Check why obj.dataValid is broken for Minecraft 1.21.1- MG_Util::Debug::LogD("bufferdata MG %d -> ES %d, size=%d, usage=%s", mgname, glname, obj.data.size(), MG_Util::Debug::GLEnumToString(obj.usage)); CallAndCheck(::GLES::glBufferData( GL_COPY_WRITE_BUFFER, obj.data.size(), obj.data.data(), //obj.dataValid || obj.isMapped ? obj.data.data() : nullptr, obj.usage);) } CallAndCheck(::GLES::glBindBuffer(GL_COPY_WRITE_BUFFER, prev_buf);) } void GenCurrentVAONameToGLES(VertexArrayState* vaState) { auto mgid = vaState->currentVao_; auto& vao = vaState->vaos_[mgid]; if (!vao.generated) return; GLuint glvao = 0; if (s_vaoMap.find(mgid) == s_vaoMap.end()) { CallAndCheck(::GLES::glGenVertexArrays(1, &glvao);) s_vaoMap[mgid] = glvao; MG_Util::Debug::LogD("Creating MG VAO: %d", mgid); } else { glvao = s_vaoMap[mgid]; } MG_Util::Debug::LogD("Updating MG VAO: %d", mgid); CallAndCheck(::GLES::glBindVertexArray(glvao);) } void SyncCurrentVAOToGLES(VertexArrayState* vaState) { auto mgid = vaState->currentVao_; auto& vao = vaState->vaos_[mgid]; { if (!vao.generated) return; GLuint glvao = 0; if (vao.attribDirty) { MG_Util::Debug::LogD("Updating MG VAO %d, dirty attrib", mgid); // Update attrib vao.attribDirty = false; GLint prevArrayBuffer; CallAndCheck(::GLES::glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &prevArrayBuffer);) for (auto& [index, attrib] : vao.attribs) { GLuint buffer = attrib.buffer; GLuint glBuffer = (buffer != 0) ? s_bufferMap[buffer] : 0; CallAndCheck(::GLES::glBindBuffer(GL_ARRAY_BUFFER, glBuffer);) MG_Util::Debug::LogD("attrib #%d: size=%d, type=%s, stride=%d, pointer=%d, %s, isInt=%s", index, attrib.size, MG_Util::Debug::GLEnumToString(attrib.type), attrib.stride, attrib.pointer, (attrib.enabled ? "enabled" : "disabled"), (attrib.isInteger ? "true" : "false")); if (!attrib.isInteger) { CallAndCheck(::GLES::glVertexAttribPointer( index, attrib.size, attrib.type, attrib.normalized ? GL_TRUE : GL_FALSE, attrib.stride, attrib.pointer);) } else { CallAndCheck(::GLES::glVertexAttribIPointer( index, attrib.size, attrib.type, attrib.stride, attrib.pointer);) } if (attrib.enabled) { CallAndCheck(::GLES::glEnableVertexAttribArray(index);) } else { CallAndCheck(::GLES::glDisableVertexAttribArray(index);) } } CallAndCheck(::GLES::glBindBuffer(GL_ARRAY_BUFFER, prevArrayBuffer);) } if (vao.eboDirty) { MG_Util::Debug::LogD("Updating MG VAO %d, dirty ebo", mgid); // Update EBO vao.eboDirty = false; if (vao.elementBuffer != 0) { CallAndCheck(::GLES::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, s_bufferMap[vao.elementBuffer]);) } else { CallAndCheck(::GLES::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);) } } } } void SyncAllVAOsToGLES(VertexArrayState* vaState) { for (auto& [mgid, vao] : vaState->vaos_) { if (!vao.generated) continue; GLuint glvao = 0; if (vao.attribDirty || vao.eboDirty) { if (s_vaoMap.find(mgid) == s_vaoMap.end()) { CallAndCheck(::GLES::glGenVertexArrays(1, &glvao);) s_vaoMap[mgid] = glvao; MG_Util::Debug::LogD("Creating MG VAO: %d", mgid); } else { glvao = s_vaoMap[mgid]; } MG_Util::Debug::LogD("Updating MG VAO: %d", mgid); CallAndCheck(::GLES::glBindVertexArray(glvao);) } else continue; if (vao.attribDirty) { MG_Util::Debug::LogD("Updating MG VAO %d, dirty attrib", mgid); // Update attrib vao.attribDirty = false; for (auto& [index, attrib] : vao.attribs) { MG_Util::Debug::LogD("attrib #%d: size=%d, type=%s, stride=%d, pointer=%d, %s, isInt=%s", index, attrib.size, MG_Util::Debug::GLEnumToString(attrib.type), attrib.stride, attrib.pointer, (attrib.enabled ? "enabled" : "disabled"), (attrib.isInteger ? "true" : "false")); if (!attrib.isInteger) { CallAndCheck(::GLES::glVertexAttribPointer( index, attrib.size, attrib.type, attrib.normalized ? GL_TRUE : GL_FALSE, attrib.stride, attrib.pointer);) } else { CallAndCheck(::GLES::glVertexAttribIPointer( index, attrib.size, attrib.type, attrib.stride, attrib.pointer);) } if (attrib.enabled) { CallAndCheck(::GLES::glEnableVertexAttribArray(index);) } else { CallAndCheck(::GLES::glDisableVertexAttribArray(index);) } } } if (vao.eboDirty) { MG_Util::Debug::LogD("Updating MG VAO %d, dirty ebo", mgid); // Update EBO vao.eboDirty = false; if (vao.elementBuffer != 0) { CallAndCheck(::GLES::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, s_bufferMap[vao.elementBuffer]);) } else { CallAndCheck(::GLES::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);) } } CallAndCheck(::GLES::glBindVertexArray(0);) } } static GLuint lastBoundProgram = 0; static GLuint lastBoundFBO[2] = {0}; // 0: DRAW FBO, 1: READ FBO static std::array lastBoundTextures; void RealizeFBOState(GLenum fbtype) { FramebufferState* fbState = MG_State_T::framebufferState; GLuint fb = fbState->currentBindings_[fbtype]; if (fb != lastBoundFBO[(fbtype == GL_DRAW_FRAMEBUFFER) ? 0 : 1]) { GLuint glFBO = 0; if (fb == 0) { CallAndCheck(::GLES::glBindFramebuffer(fbtype, 0);) glFBO = 0; } else { bool isNewGlesFBO = false; if (s_framebufferMap.find(fb) == s_framebufferMap.end()) { CallAndCheck(::GLES::glGenFramebuffers(1, &glFBO);) s_framebufferMap[fb] = glFBO; CallAndCheck(::GLES::glBindFramebuffer(fbtype, glFBO);) MG_Util::Debug::LogD("Generated and bound new GLES FBO %u for MobileGL FBO %u", glFBO, fb); isNewGlesFBO = true; } else { glFBO = s_framebufferMap[fb]; CallAndCheck(::GLES::glBindFramebuffer(fbtype, glFBO);) MG_Util::Debug::LogD("Bound existing GLES FBO %u for MobileGL FBO %u", glFBO, fb); } if (glFBO != 0) { FramebufferObject* mgFBO = fbState->GetCurrentFBO(fbtype); if (mgFBO) { MG_Util::Debug::LogD("Checking/Syncing attachments for GLES FBO %u (MobileGL FBO %u)", glFBO, fb); 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, fb); continue; } GLuint expectedGLTexId = 0; if (mgAtt.handle != 0) { if (s_textureMap.find(mgAtt.handle) != s_textureMap.end()) { expectedGLTexId = s_textureMap[mgAtt.handle]; } else { MG_Util::Debug::LogE("MobileGL Texture %u for FBO %u attachment %s not found in s_textureMap during FBO sync!", mgAtt.handle, fb, MG_Util::Debug::GLEnumToString(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(fbtype, mgAttachmentPoint, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &glesAttachedType);) if (glesAttachedType == GL_TEXTURE) { CallAndCheck(::GLES::glGetFramebufferAttachmentParameteriv(fbtype, 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...", fb, mgAttachmentPoint, expectedGLTexId, glesAttachedName); CallAndCheck(::GLES::glFramebufferTexture2D( fbtype, mgAttachmentPoint, GL_TEXTURE_2D, expectedGLTexId, mgAtt.mipLevel );) } } GLenum status = ::GLES::glCheckFramebufferStatus(fbtype); if (status != GL_FRAMEBUFFER_COMPLETE) { MG_Util::Debug::LogE("Framebuffer %u (GLES FBO %u) is not complete after sync! Status: 0x%X (%s)", fb, glFBO, status, MG_Util::Debug::GLEnumToString(status)); } } else { MG_Util::Debug::LogW("Could not get MobileGL FBO object for ID %u during attachment sync.", fb); } } } lastBoundFBO[(fbtype == GL_DRAW_FRAMEBUFFER) ? 0 : 1] = fb; } } void SyncAllToGLES() { 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]; TexParamCache& entry = cache[&mgTex]; for (auto& [pname, param] : mgTex.params.texPropertiesInt) { auto it = entry.lastInt.find(pname); if (it == entry.lastInt.end() || it->second != param) { CallAndCheck(::GLES::glTexParameteri(target, pname, param);) entry.lastInt[pname] = param; } } for (auto& [pname, param] : mgTex.params.texPropertiesFloat) { auto it = entry.lastFloat.find(pname); if (it == entry.lastFloat.end() || it->second != param) { CallAndCheck(::GLES::glTexParameterf(target, pname, param);) entry.lastFloat[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 GenCurrentVAONameToGLES(vaState); GLuint currentMgVAO = vaState->currentVao_; MG_Util::Debug::LogD("Now binding to VAO %d...", currentMgVAO); CallAndCheck(::GLES::glBindVertexArray(s_vaoMap[currentMgVAO]);) GLuint vbo = bufferState->GetCurrentBinding(GL_ARRAY_BUFFER); CallAndCheck(::GLES::glBindBuffer(GL_ARRAY_BUFFER, s_bufferMap[vbo]);) MG_Util::Debug::LogD("binding vbo MG %d -> ES %d", vbo, s_bufferMap[vbo]); SyncCurrentVAOToGLES(vaState); // EBO GLuint curVaoId = vaState->currentVao_; VertexArrayObject* curvao = &vaState->vaos_[curVaoId]; // 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]; // Attribute Names // before vertex shader attach for (auto& [name, idx]: mgProgram.attribLocations) { MG_Util::Debug::LogD("%s: location = %d", name.c_str(), idx); CallAndCheck(::GLES::glBindAttribLocation(glProgram, idx, name.c_str());) } // 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: { CallAndCheck(::GLES::glUniform1fv(location, uniform.count, uniform.floatData.data());) break; } case GL_FLOAT_VEC2: { CallAndCheck(::GLES::glUniform2fv(location, uniform.count, uniform.floatData.data());) break; } case GL_FLOAT_VEC3: { CallAndCheck(::GLES::glUniform3fv(location, uniform.count, uniform.floatData.data());) break; } case GL_FLOAT_VEC4: { CallAndCheck(::GLES::glUniform4fv(location, uniform.count, 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: { CallAndCheck(::GLES::glUniform1iv(location, uniform.count, uniform.intData.data());) break; } case GL_INT_VEC2: case GL_BOOL_VEC2: { CallAndCheck(::GLES::glUniform2iv(location, uniform.count, uniform.intData.data());) break; } case GL_INT_VEC3: case GL_BOOL_VEC3: { CallAndCheck(::GLES::glUniform3iv(location, uniform.count, uniform.intData.data());) break; } case GL_INT_VEC4: case GL_BOOL_VEC4: { CallAndCheck(::GLES::glUniform4iv(location, uniform.count, uniform.intData.data());) break; } case GL_UNSIGNED_INT: { CallAndCheck(::GLES::glUniform1uiv(location, uniform.count, uniform.uintData.data());) break; } case GL_UNSIGNED_INT_VEC2: { CallAndCheck(::GLES::glUniform2uiv(location, uniform.count, uniform.uintData.data());) break; } case GL_UNSIGNED_INT_VEC3: { CallAndCheck(::GLES::glUniform3uiv(location, uniform.count, uniform.uintData.data());) break; } case GL_UNSIGNED_INT_VEC4: { CallAndCheck(::GLES::glUniform4uiv(location, uniform.count, uniform.uintData.data());) break; } case GL_FLOAT_MAT2: { CallAndCheck(::GLES::glUniformMatrix2fv(location, uniform.count, GL_FALSE, uniform.floatData.data());) break; } case GL_FLOAT_MAT3: { CallAndCheck(::GLES::glUniformMatrix3fv(location, uniform.count, GL_FALSE, uniform.floatData.data());) break; } case GL_FLOAT_MAT4: { CallAndCheck(::GLES::glUniformMatrix4fv(location, uniform.count, GL_FALSE, uniform.floatData.data());) break; } case GL_FLOAT_MAT2x3: { CallAndCheck(::GLES::glUniformMatrix2x3fv(location, uniform.count, GL_FALSE, uniform.floatData.data());) break; } case GL_FLOAT_MAT2x4: { CallAndCheck(::GLES::glUniformMatrix2x4fv(location, uniform.count, GL_FALSE, uniform.floatData.data());) break; } case GL_FLOAT_MAT3x2: { CallAndCheck(::GLES::glUniformMatrix3x2fv(location, uniform.count, GL_FALSE, uniform.floatData.data());) break; } case GL_FLOAT_MAT3x4: { CallAndCheck(::GLES::glUniformMatrix3x4fv(location, uniform.count, GL_FALSE, uniform.floatData.data());) break; } case GL_FLOAT_MAT4x2: { CallAndCheck(::GLES::glUniformMatrix4x2fv(location, uniform.count, GL_FALSE, uniform.floatData.data());) break; } case GL_FLOAT_MAT4x3: { CallAndCheck(::GLES::glUniformMatrix4x3fv(location, uniform.count, GL_FALSE, uniform.floatData.data());) break; } } } } // Framebuffer RealizeFBOState(GL_DRAW_FRAMEBUFFER); RealizeFBOState(GL_READ_FRAMEBUFFER); } void DrawArraysSHITTILY(GLenum mode, GLint first, GLsizei count) { } void DrawElementsSHITTILY(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices) { SyncAllToGLES(); VertexArrayState* vaState = MG_State_T::vertexArrayState; GLuint curVaoId = vaState->currentVao_; VertexArrayObject* curvao = &vaState->vaos_[curVaoId]; if (curvao->elementBuffer != 0 || indices != nullptr) { CallAndCheck(::GLES::glDrawElements( mode, count, type, indices );) } } void DrawElementsBaseVertexSHITTILY(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLint basevertex) { SyncAllToGLES(); VertexArrayState* vaState = MG_State_T::vertexArrayState; GLuint curVaoId = vaState->currentVao_; VertexArrayObject* curvao = &vaState->vaos_[curVaoId]; if (curvao->elementBuffer != 0 || indices != nullptr) { CallAndCheck(::GLES::glDrawElementsBaseVertex( mode, count, type, indices, basevertex );) } } void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter) { MG_Util::Debug::LogD("BlitFramebuffer, srcX0=%d, srcY0=%d, srcX1=%d, srcY1=%d, dstX0=%d, dstY0=%d, dstX1=%d, dstY1=%d, mask=0x%x, filter=%s", srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, MG_Util::Debug::GLEnumToString(filter)); TextureState* textureState = MG_State_T::textureState; // Texture SyncAllTexturesToGLES(textureState); // Realize FBO states RealizeFBOState(GL_DRAW_FRAMEBUFFER); RealizeFBOState(GL_READ_FRAMEBUFFER); ::GLES::glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); } void ClearSHITTILY(GLbitfield mask) { CommonState* commonState = MG_State_T::commonState; FramebufferState* fbState = MG_State_T::framebufferState; TextureState* textureState = MG_State_T::textureState; // Texture SyncAllTexturesToGLES(textureState); RealizeFBOState(GL_DRAW_FRAMEBUFFER); RealizeFBOState(GL_READ_FRAMEBUFFER); 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) { DrawElementsSHITTILY(mode, count, type, indices); } void DrawArrays(GLenum mode, GLint first, GLsizei count) { DrawArraysSHITTILY(mode, first, count); } void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLint basevertex) { DrawElementsBaseVertexSHITTILY(mode, count, type, indices, basevertex); } void MultiDrawElements(GLenum mode, const GLsizei *count, GLenum type, const GLvoid *const *indices, GLsizei drawcount) { SyncAllToGLES(); VertexArrayState* vaState = MG_State_T::vertexArrayState; GLuint curVaoId = vaState->currentVao_; VertexArrayObject* curvao = &vaState->vaos_[curVaoId]; if (curvao->elementBuffer != 0 || indices != nullptr) { for (GLsizei i = 0; i < drawcount; ++i) { CallAndCheck(::GLES::glDrawElements( mode, count[i], type, indices[i] );) } } } void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei *count, GLenum type, const GLvoid *const *indices, GLsizei drawcount, const GLint *basevertex) { SyncAllToGLES(); VertexArrayState* vaState = MG_State_T::vertexArrayState; GLuint curVaoId = vaState->currentVao_; VertexArrayObject* curvao = &vaState->vaos_[curVaoId]; if (curvao->elementBuffer != 0 || indices != nullptr) { for (GLsizei i = 0; i < drawcount; ++i) { CallAndCheck(::GLES::glDrawElementsBaseVertex(mode, count[i], type, indices[i], basevertex[i]);) } } } void DrawBuffer(GLenum buf) { SyncAllToGLES(); RealizeFBOState(GL_READ_FRAMEBUFFER); RealizeFBOState(GL_READ_FRAMEBUFFER); GLint currentFBO; CallAndCheck(::GLES::glGetIntegerv(GL_FRAMEBUFFER_BINDING, ¤tFBO);) if (currentFBO == 0) { GLenum buffers[1] = {GL_NONE}; switch (buf) { case GL_FRONT: case GL_BACK: case GL_NONE: buffers[0] = buf; CallAndCheck(::GLES::glDrawBuffers(1, buffers);) break; default: break; } } else { GLint maxAttachments; CallAndCheck(::GLES::glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &maxAttachments);) if (buf == GL_NONE) { auto *buffers = (GLenum *)alloca(maxAttachments * sizeof(GLenum)); for (int i = 0; i < maxAttachments; i++) { buffers[i] = GL_NONE; } CallAndCheck(::GLES::glDrawBuffers(maxAttachments, buffers);) } else if (buf >= GL_COLOR_ATTACHMENT0 && buf < GL_COLOR_ATTACHMENT0 + maxAttachments) { auto *buffers = (GLenum *)alloca(maxAttachments * sizeof(GLenum)); for (int i = 0; i < maxAttachments; i++) { buffers[i] = (i == (buf - GL_COLOR_ATTACHMENT0)) ? buf : GL_NONE; } CallAndCheck(::GLES::glDrawBuffers(maxAttachments, buffers);) } } } }