// MobileGL - MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp // Copyright (c) 2025-2026 MobileGL-Dev // Licensed under the GNU Lesser General Public License v2.1: // http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html // SPDX-License-Identifier: LGPL-2.1-only // End of Source File Header #include "DirectGLES.h" #include "Utils.h" #include "Managers.h" #include #include #include #include #include #include #include #include #include #include namespace MobileGL::MG_Backend::DirectGLES { enum class DrawSyncBit : Uint32 { None = 0, IndexBuffer = 1 << 0, IndirectBuffer = 1 << 1, Instancing = 1 << 2 }; inline DrawSyncBit operator|(DrawSyncBit a, DrawSyncBit b) { return static_cast(static_cast(a) | static_cast(b)); } inline DrawSyncBit& operator|=(DrawSyncBit& a, DrawSyncBit b) { a = a | b; return a; } namespace DebugImpl { #if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG void ErrorLopper::Loop(std::function func) { GLenum err = MG_External::GLES::glGetError(); while (err != GL_NO_ERROR) { func(err); err = MG_External::GLES::glGetError(); } } void ErrorLopper::Clear() { GLenum err = MG_External::GLES::glGetError(); while (err != GL_NO_ERROR) { MGLOG_D("Stray GL Error cleared: %s", MG_Util::ConvertGLEnumToString(err).c_str()); err = MG_External::GLES::glGetError(); } } ErrorLopper::ErrorLopper() { Clear(); } ErrorLopper::~ErrorLopper() { Clear(); } #else void ErrorLopper::Loop(std::function func) {} void ErrorLopper::Clear() {} ErrorLopper::ErrorLopper() {} ErrorLopper::~ErrorLopper() {} #endif } // namespace DebugImpl // TODO: deletion for deleted objects namespace BufferImpl { void SyncNeccessaryBuffers(Bool includeIBO = false, Bool includeIndirectBuffer = false) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif // All buffers we need are: // 1.VBO 2.IBO (if needed) 3.UBO 4.IndirectBuffer (if needed) 5.SSBO (TODO) // PBO is not needed since it should be handled in frontend Vector> buffersToSync; const auto& currentVAOObject = MG_State::pGLContext->GetBoundVertexArray(); if (!currentVAOObject) { MGLOG_E("No VAO is currently bound, cannot sync necessary buffers."); return; } // VBO for (const auto& attrib : currentVAOObject->GetAllAttributes()) { if (!attrib.Enabled) continue; const auto& bufferObject = attrib.Buffer; if (bufferObject) { const auto& end = buffersToSync.end(); if (std::find(buffersToSync.begin(), end, bufferObject) == end) { buffersToSync.push_back(bufferObject); } } } // IBO if (includeIBO) { const auto& possibleIBO = currentVAOObject->GetIndexBufferBindingSlot().GetBoundObject(); if (possibleIBO) { const auto& end = buffersToSync.end(); if (std::find(buffersToSync.begin(), end, possibleIBO) == end) { buffersToSync.push_back(possibleIBO); } } } // Indirect Buffer Object if (includeIndirectBuffer) { const auto& possibleIndirectBuffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject(); if (possibleIndirectBuffer) { const auto& end = buffersToSync.end(); if (std::find(buffersToSync.begin(), end, possibleIndirectBuffer) == end) { buffersToSync.push_back(possibleIndirectBuffer); } } } // UBO auto uboBindingPointCnt = MG_State::pGLContext->GetBufferBindingPointCount(BufferTarget::Uniform); for (SizeT i = 0; i < uboBindingPointCnt; ++i) { auto& point = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::Uniform, i); auto obj = point.GetBoundObject(); if (obj) { const auto& end = buffersToSync.end(); if (std::find(buffersToSync.begin(), end, obj) == end) { buffersToSync.push_back(obj); } } } // Do real sync for (auto& bufferObject : buffersToSync) { const auto& backendBufferIt = g_backendBufferObjects.find(bufferObject); SharedPtr backendBufferObject; if (backendBufferIt == g_backendBufferObjects.end()) { backendBufferObject = MakeShared(); g_backendBufferObjects[bufferObject] = backendBufferObject; } else { backendBufferObject = backendBufferIt->second; } backendBufferObject->SyncToBackend(bufferObject); } } } // namespace BufferImpl namespace VertexArrayImpl { void SyncCurrentVAO(Bool needDivisor) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif auto currentVAOObject = MG_State::pGLContext->GetBoundVertexArray(); if (!currentVAOObject) { MGLOG_E("No VAO is currently bound, cannot sync current VAO."); return; } const auto& backendVAOIt = g_backendVertexArrayObjects.find(currentVAOObject); SharedPtr backendVAOObject; if (backendVAOIt == g_backendVertexArrayObjects.end()) { backendVAOObject = MakeShared(); g_backendVertexArrayObjects[currentVAOObject] = backendVAOObject; } else { backendVAOObject = backendVAOIt->second; } backendVAOObject->SyncToBackend(currentVAOObject, needDivisor); } } // namespace VertexArrayImpl namespace TextureImpl { SharedPtr SyncTextureObjectToBackend( SharedPtr& textureObject) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif const auto& backendTextureIt = g_backendTextureObjects.find(textureObject); SharedPtr backendTextureObject; if (backendTextureIt == g_backendTextureObjects.end()) { backendTextureObject = MakeShared(); g_backendTextureObjects[textureObject] = backendTextureObject; } else { backendTextureObject = backendTextureIt->second; } backendTextureObject->SyncToBackend(textureObject); return backendTextureObject; } void SyncNeccessaryTextures() { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif // All textures we need are: // 1. textures bound to texture units (TODO: only sync ones that are used in current program) // 2. textures used in current FBO // 3. textures bound to image units (TODO) constexpr SizeT TextureTargetCount = static_cast(TextureTarget::TextureTargetCount); std::bitset dirtyTextureTargetBits; Vector> texturesToSync; for (int index = 0; index < MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS; ++index) { auto& unit = MG_State::pGLContext->GetTextureUnitObject(index); for (const auto& bindingSlot : unit.GetAllBindingSlots()) { const auto& textureObject = bindingSlot.GetBoundObject(); if (textureObject) { const auto& end = texturesToSync.end(); if (std::find(texturesToSync.begin(), end, textureObject) == end) { texturesToSync.push_back(textureObject); dirtyTextureTargetBits.set(static_cast(textureObject->GetTarget())); } } } } const auto& currentFBO = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); if (currentFBO) { for (const auto& attachment : currentFBO->GetAllAttachments()) { if (!attachment.IsTexture()) continue; const auto& textureObject = attachment.GetTexture(); if (textureObject) { const auto& end = texturesToSync.end(); if (std::find(texturesToSync.begin(), end, textureObject) == end) { texturesToSync.push_back(textureObject); dirtyTextureTargetBits.set(static_cast(textureObject->GetTarget())); } } } } BufferImpl::BackendBufferBindingProtector pixelUnpackProtector = BufferImpl::BackendBufferBindingProtector(GL_PIXEL_UNPACK_BUFFER); Vector textureBindingProtectors; for (SizeT target = 0; target < TextureTargetCount; ++target) { if (dirtyTextureTargetBits[target]) { textureBindingProtectors.emplace_back( MG_Util::ConvertTextureTargetToGLEnum(static_cast(target))); } } // Do real sync for (auto& textureObject : texturesToSync) { SyncTextureObjectToBackend(textureObject); } } } // namespace TextureImpl namespace FramebufferImpl { void SyncCurrentFBO() { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif const FramebufferTarget fboTargets[] = {FramebufferTarget::Draw, FramebufferTarget::Read}; MG_State::GLState::FramebufferObject* lastUpdatedFBO = nullptr; for (auto target : fboTargets) { auto currentFBO = MG_State::pGLContext->GetFramebufferBindingSlot(target).GetBoundObject(); if (!currentFBO) { MGLOG_E("No FBO is currently bound, cannot sync current FBO."); continue; } if (currentFBO == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO) { // Default FBO, nothing to sync continue; } const auto& backendFBOIt = g_backendFramebufferObjects.find(currentFBO); SharedPtr backendFBOObject; if (backendFBOIt == g_backendFramebufferObjects.end()) { backendFBOObject = MakeShared(); g_backendFramebufferObjects[currentFBO] = backendFBOObject; } else { backendFBOObject = backendFBOIt->second; } if (currentFBO.get() == lastUpdatedFBO) { MGLOG_D("Draw FBO and read FBO are the same, skipping sync."); } else { backendFBOObject->SyncToBackend(currentFBO, target); } backendFBOObject->Bind(target); lastUpdatedFBO = currentFBO.get(); } } } // namespace FramebufferImpl namespace RenderStateImpl { void SyncRenderState() { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif MG_External::GLES::glViewport( MG_State::pGLContext->GetViewport().x(), MG_State::pGLContext->GetViewport().y(), MG_State::pGLContext->GetViewport().z(), MG_State::pGLContext->GetViewport().w()); #define SYNC_CAPABILITY(cap_mg, cap_gl) \ if (MG_State::pGLContext->IsCapabilityEnabled(cap_mg)) { \ MG_External::GLES::glEnable(cap_gl); \ } else { \ MG_External::GLES::glDisable(cap_gl); \ } SYNC_CAPABILITY(CapabilityInput::Blend, GL_BLEND); SYNC_CAPABILITY(CapabilityInput::DepthTest, GL_DEPTH_TEST); SYNC_CAPABILITY(CapabilityInput::ScissorTest, GL_SCISSOR_TEST); SYNC_CAPABILITY(CapabilityInput::CullFace, GL_CULL_FACE); #undef SYNC_CAPABILITY const auto& ToGLBoolean = [](Bool b) -> GLboolean { return b ? GL_TRUE : GL_FALSE; }; { // Blend func BlendFactor srcRGB, dstRGB, srcAlpha, dstAlpha; MG_State::pGLContext->GetBlendFunc(srcRGB, dstRGB, srcAlpha, dstAlpha); MG_External::GLES::glBlendFuncSeparate( MG_Util::ConvertBlendFactorToGLEnum(srcRGB), MG_Util::ConvertBlendFactorToGLEnum(dstRGB), MG_Util::ConvertBlendFactorToGLEnum(srcAlpha), MG_Util::ConvertBlendFactorToGLEnum(dstAlpha)); } { // Blend equation DepthTestFunc df = MG_State::pGLContext->GetDepthFunc(); MG_External::GLES::glDepthFunc(MG_Util::ConvertDepthTestFuncToGLEnum(df)); MG_External::GLES::glDepthMask(MG_State::pGLContext->GetDepthMask() ? GL_TRUE : GL_FALSE); } { // Color mask BoolVec4 colorMask = MG_State::pGLContext->GetColorMask(); MG_External::GLES::glColorMask(ToGLBoolean(colorMask.x()), ToGLBoolean(colorMask.y()), ToGLBoolean(colorMask.z()), ToGLBoolean(colorMask.w())); } { // Clear values const FloatVec4& clearCol = MG_State::pGLContext->GetClearColor(); MG_External::GLES::glClearColor(clearCol.x(), clearCol.y(), clearCol.z(), clearCol.w()); MG_External::GLES::glClearDepthf(MG_State::pGLContext->GetClearDepth()); } { // Cull face mode CullFaceMode cfm = MG_State::pGLContext->GetCullFaceMode(); MG_External::GLES::glCullFace(MG_Util::ConvertCullFaceModeToGLEnum(cfm)); } { // Scissor box const IntVec4& scissorBox = MG_State::pGLContext->GetScissorBox(); MG_External::GLES::glScissor(scissorBox.x(), scissorBox.y(), scissorBox.z(), scissorBox.w()); } } } // namespace RenderStateImpl namespace PrgramImpl { void SyncCurrentProgram() { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif auto currentProgram = MG_State::pGLContext->GetCurrentProgram(); if (!currentProgram || !currentProgram->GetLinkStatus()) { MG_External::GLES::glUseProgram(0); return; } const auto& backendProgramIt = g_backendProgramObjects.find(currentProgram); SharedPtr backendProgram; if (backendProgramIt == g_backendProgramObjects.end()) { backendProgram = MakeShared(); g_backendProgramObjects[currentProgram] = backendProgram; backendProgram->SyncToBackend(currentProgram); } else { backendProgram = backendProgramIt->second; if (!backendProgram->GetBackendProgramId()) { backendProgram->SyncToBackend(currentProgram); } } } } // namespace PrgramImpl void BindCurrentFBO(FramebufferTarget target) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif const auto& currentFBO = MG_State::pGLContext->GetFramebufferBindingSlot(target).GetBoundObject(); if (currentFBO && currentFBO != MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO) { const auto& backendFBOIt = FramebufferImpl::g_backendFramebufferObjects.find(currentFBO); if (backendFBOIt != FramebufferImpl::g_backendFramebufferObjects.end()) { backendFBOIt->second->Bind(target); } else { MGLOG_E("No backend FBO found (maybe not synced) for current %s FBO, cannot bind FBO.", (target == FramebufferTarget::Read ? "READ" : "DRAW")); } } else { MGLOG_D("Binding default framebuffer as %s FBO", (target == FramebufferTarget::Read ? "READ" : "DRAW")); MG_External::GLES::glBindFramebuffer( target == FramebufferTarget::Draw ? GL_DRAW_FRAMEBUFFER : GL_READ_FRAMEBUFFER, 0); } } void PrepareForDraw(DrawSyncBit syncBit) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif BufferImpl::SyncNeccessaryBuffers(syncBit & DrawSyncBit::IndexBuffer, syncBit & DrawSyncBit::IndirectBuffer); VertexArrayImpl::SyncCurrentVAO(syncBit & DrawSyncBit::Instancing); TextureImpl::SyncNeccessaryTextures(); FramebufferImpl::SyncCurrentFBO(); PrgramImpl::SyncCurrentProgram(); RenderStateImpl::SyncRenderState(); BindCurrentFBO(FramebufferTarget::Draw); { #ifdef TRACY_ENABLE ZoneScopedNC("BindCurrentVAO", TRACY_ZONECOLOR_BACKEND); #endif const auto& currentVAO = MG_State::pGLContext->GetBoundVertexArray(); if (currentVAO) { const auto& backendVAOIt = VertexArrayImpl::g_backendVertexArrayObjects.find(currentVAO); if (backendVAOIt != VertexArrayImpl::g_backendVertexArrayObjects.end()) { backendVAOIt->second->Bind(); } } else { MG_External::GLES::glBindVertexArray(0); } } { #ifdef TRACY_ENABLE ZoneScopedNC("BindCurrentTextures", TRACY_ZONECOLOR_BACKEND); #endif Int maxTextureUnits = MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS; for (Int unit = 0; unit < maxTextureUnits; ++unit) { auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(unit); MG_External::GLES::glActiveTexture(GL_TEXTURE0 + unit); for (const auto& bindingSlot : textureUnit.GetAllBindingSlots()) { const auto& textureObject = bindingSlot.GetBoundObject(); if (!textureObject) continue; // Bind texture object auto target = textureObject->GetTarget(); if (target == TextureTarget::Texture1D || target == TextureTarget::TextureRectangle || target == TextureTarget::Texture2DMultisampleArray || target == TextureTarget::Texture1DArray || target == TextureTarget::Texture3D || target == TextureTarget::Texture2DMultisample || target == TextureTarget::Texture2DArray) { MGLOG_D(" Texture target %s is not supported, skipping.", MG_Util::ConvertTextureTargetToString(target).c_str()); continue; } const auto& backendTextureIt = TextureImpl::g_backendTextureObjects.find(textureObject); if (backendTextureIt == TextureImpl::g_backendTextureObjects.end()) continue; GLenum targetGL = MG_Util::ConvertTextureTargetToGLEnum(target); backendTextureIt->second->Bind(targetGL); } // Bind sampler object const auto& samplerObject = textureUnit.GetSamplerObject(); if (samplerObject) { const auto& backendSamplerIt = SamplerImpl::g_backendSamplerObjects.find(samplerObject); if (backendSamplerIt != SamplerImpl::g_backendSamplerObjects.end()) { backendSamplerIt->second->Bind(unit); } } else { MG_External::GLES::glBindSampler(unit, 0); } } } const auto& currentProgram = MG_State::pGLContext->GetCurrentProgram(); if (currentProgram && currentProgram->GetLinkStatus()) { #ifdef TRACY_ENABLE ZoneScopedNC("BindCurrentProgram", TRACY_ZONECOLOR_BACKEND); #endif const auto& backendProgramIt = PrgramImpl::g_backendProgramObjects.find(currentProgram); if (backendProgramIt != PrgramImpl::g_backendProgramObjects.end()) { backendProgramIt->second->Use(); auto backendProgramId = backendProgramIt->second->GetBackendProgramId(); // Global UBO if (currentProgram->GetUBOSize() > 0) { #ifdef TRACY_ENABLE ZoneScopedNC("UpdateGlobalUBO", TRACY_ZONECOLOR_BACKEND); #endif MG_External::GLES::glBindBuffer(GL_UNIFORM_BUFFER, backendProgramIt->second->GetBackendGlobalUBOId()); MG_External::GLES::glBufferSubData(GL_UNIFORM_BUFFER, 0, currentProgram->GetUBOSize(), currentProgram->MapUBO()); MG_External::GLES::glBindBuffer(GL_UNIFORM_BUFFER, 0); Uint blockIndex = MG_External::GLES::glGetUniformBlockIndex( backendProgramId, MG_Util::ShaderTranspiler::GLOBAL_UBO_NAME); MG_External::GLES::glUniformBlockBinding(backendProgramId, blockIndex, 0); MG_External::GLES::glBindBufferBase(GL_UNIFORM_BUFFER, 0, backendProgramIt->second->GetBackendGlobalUBOId()); } { #ifdef TRACY_ENABLE ZoneScopedNC("UpdateUBO", TRACY_ZONECOLOR_BACKEND); #endif // Normal UBO auto uboCount = currentProgram->GetActiveUniformBlocksCount(); Uint lastUBOBinding = 0; // to prevent overlapping bindings between global UBO and normal UBOs for (Int i = 0; i < uboCount; ++i) { ++lastUBOBinding; // program state binding index == backend binding index // Connect program ubo index to backend binding point auto binding = currentProgram->GetUniformBlockBinding(i); auto& name = currentProgram->GetUniformBlockName(i); GLuint backendBlkIdx = MG_External::GLES::glGetUniformBlockIndex(backendProgramId, name.c_str()); MG_External::GLES::glUniformBlockBinding(backendProgramId, backendBlkIdx, lastUBOBinding); // Connect buffer to backend binding point auto& point = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::Uniform, binding); auto bufferObj = point.GetBoundObject(); auto range = point.GetRange(); if (bufferObj) { const auto& backendBufferIt = BufferImpl::g_backendBufferObjects.find(bufferObj); if (backendBufferIt != BufferImpl::g_backendBufferObjects.end()) { const auto& backendBufferObject = backendBufferIt->second; backendBufferObject->Bind(GL_UNIFORM_BUFFER); if (range.end == 0) { MG_External::GLES::glBindBufferBase(GL_UNIFORM_BUFFER, lastUBOBinding, backendBufferObject->GetBackendBufferId()); } else { MG_External::GLES::glBindBufferRange(GL_UNIFORM_BUFFER, lastUBOBinding, backendBufferObject->GetBackendBufferId(), range.start, range.end - range.start); } } else { MGLOG_E("No backend buffer found for UBO binding, cannot bind UBO."); } } } } { #ifdef TRACY_ENABLE ZoneScopedNC("BindSamplerUnit", TRACY_ZONECOLOR_BACKEND); #endif // Sampler unit binding auto maxUniformLoc = currentProgram->GetMaxUniformLocation(); for (int loc = 0; loc < maxUniformLoc; ++loc) { auto unit = currentProgram->GetUniformSamplerOrImageUnitIndex(loc); if (unit == -1) continue; auto& name = currentProgram->GetUniformName(loc); auto locAtBackend = MG_External::GLES::glGetUniformLocation( backendProgramIt->second->GetBackendProgramId(), name.c_str()); MG_External::GLES::glUniform1i(locAtBackend, unit); auto samplerObject = MG_State::pGLContext->GetTextureUnitObject(unit).GetSamplerObject(); if (samplerObject) { const auto& backendSamplerIt = SamplerImpl::g_backendSamplerObjects.find(samplerObject); SharedPtr backendSamplerObject; if (backendSamplerIt == SamplerImpl::g_backendSamplerObjects.end()) { backendSamplerObject = MakeShared(); SamplerImpl::g_backendSamplerObjects[samplerObject] = backendSamplerObject; } else { backendSamplerObject = backendSamplerIt->second; } backendSamplerObject->SyncToBackend(samplerObject); } else { MG_External::GLES::glBindSampler(unit, 0); } } } } else { MG_External::GLES::glUseProgram(0); MGLOG_E("No backend program found (maybe not synced) for current program, cannot use program."); } } } void Clear(GLbitfield mask) { TextureImpl::SyncNeccessaryTextures(); FramebufferImpl::SyncCurrentFBO(); RenderStateImpl::SyncRenderState(); BindCurrentFBO(FramebufferTarget::Draw); MG_External::GLES::glClear(mask); } void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices) { DrawSyncBit syncBit = DrawSyncBit::IndexBuffer; PrepareForDraw(syncBit); MG_External::GLES::glDrawElements(mode, count, type, indices); } void DrawArrays(GLenum mode, GLint first, GLsizei count) { DrawSyncBit syncBit = DrawSyncBit::None; PrepareForDraw(syncBit); MG_External::GLES::glDrawArrays(mode, first, count); } void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLint basevertex) { DrawSyncBit syncBit = DrawSyncBit::IndexBuffer; PrepareForDraw(syncBit); MG_External::GLES::glDrawElementsBaseVertex(mode, count, type, indices, basevertex); } void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices, GLsizei drawcount) { DrawSyncBit syncBit = DrawSyncBit::IndexBuffer; PrepareForDraw(syncBit); for (GLsizei i = 0; i < drawcount; ++i) { MG_External::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) { DrawSyncBit syncBit = DrawSyncBit::IndexBuffer; PrepareForDraw(syncBit); for (GLsizei i = 0; i < drawcount; ++i) { MG_External::GLES::glDrawElementsBaseVertex(mode, count[i], type, indices[i], basevertex[i]); } } void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride) { DrawSyncBit syncBit = DrawSyncBit::IndexBuffer | DrawSyncBit::IndirectBuffer; PrepareForDraw(syncBit); for (GLsizei i = 0; i < drawcount; ++i) { const GLvoid* cmd = reinterpret_cast(reinterpret_cast(indirect) + i * (stride ? stride : sizeof(GLsizei) * 4)); MG_External::GLES::glDrawElementsIndirect(mode, type, cmd); } } void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride) { DrawSyncBit syncBit = DrawSyncBit::IndirectBuffer; PrepareForDraw(syncBit); for (GLsizei i = 0; i < drawcount; ++i) { const GLvoid* cmd = reinterpret_cast(reinterpret_cast(indirect) + i * (stride ? stride : sizeof(GLsizei) * 4)); MG_External::GLES::glDrawArraysIndirect(mode, cmd); } } void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void* indices, GLint basevertex) { DrawSyncBit syncBit = DrawSyncBit::IndexBuffer; PrepareForDraw(syncBit); MG_External::GLES::glDrawRangeElementsBaseVertex(mode, start, end, count, type, indices, basevertex); } void DrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void* indices) { DrawSyncBit syncBit = DrawSyncBit::IndexBuffer; PrepareForDraw(syncBit); MG_External::GLES::glDrawRangeElements(mode, start, end, count, type, indices); } void DrawElementsInstancedBaseVertexBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount, GLint basevertex, GLuint baseinstance) { // Not supported in OpenGL ES MGLOG_W("DrawElementsInstancedBaseVertexBaseInstance is not supported in OpenGL ES."); } void DrawElementsInstancedBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount, GLint basevertex) { DrawSyncBit syncBit = DrawSyncBit::IndexBuffer | DrawSyncBit::Instancing; PrepareForDraw(syncBit); MG_External::GLES::glDrawElementsInstancedBaseVertex(mode, count, type, indices, instancecount, basevertex); } void DrawElementsInstancedBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount, GLuint baseinstance) { // Not supported in OpenGL ES MGLOG_W("DrawElementsInstancedBaseInstance is not supported in OpenGL ES."); } void DrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount) { DrawSyncBit syncBit = DrawSyncBit::IndexBuffer | DrawSyncBit::Instancing; PrepareForDraw(syncBit); MG_External::GLES::glDrawElementsInstanced(mode, count, type, indices, instancecount); } void DrawElementsIndirect(GLenum mode, GLenum type, const void* indirect) { DrawSyncBit syncBit = DrawSyncBit::IndexBuffer | DrawSyncBit::IndirectBuffer; PrepareForDraw(syncBit); MG_External::GLES::glDrawElementsIndirect(mode, type, indirect); } void DrawArraysInstancedBaseInstance(GLenum mode, GLint first, GLsizei count, GLsizei instancecount, GLuint baseinstance) { // Not supported in OpenGL ES MGLOG_W("DrawArraysInstancedBaseInstance is not supported in OpenGL ES."); } void DrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instancecount) { DrawSyncBit syncBit = DrawSyncBit::Instancing; PrepareForDraw(syncBit); MG_External::GLES::glDrawArraysInstanced(mode, first, count, instancecount); } void DrawArraysIndirect(GLenum mode, const void* indirect) { DrawSyncBit syncBit = DrawSyncBit::IndirectBuffer; PrepareForDraw(syncBit); MG_External::GLES::glDrawArraysIndirect(mode, indirect); } void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter) { DebugImpl::ErrorLopper errorLopper; TextureImpl::SyncNeccessaryTextures(); errorLopper.Loop([file = __FILE__, line = __LINE__](auto err) { MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); }); FramebufferImpl::SyncCurrentFBO(); errorLopper.Loop([file = __FILE__, line = __LINE__](auto err) { MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); }); RenderStateImpl::SyncRenderState(); errorLopper.Loop([file = __FILE__, line = __LINE__](auto err) { MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); }); BindCurrentFBO(FramebufferTarget::Draw); BindCurrentFBO(FramebufferTarget::Read); errorLopper.Loop([file = __FILE__, line = __LINE__](auto err) { MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); }); MG_External::GLES::glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); errorLopper.Loop([file = __FILE__, line = __LINE__](auto err) { MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); }); } void CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border) { DebugImpl::ErrorLopper errorLopper; MGLOG_D("%s: Backend", __func__); TextureImpl::SyncNeccessaryTextures(); errorLopper.Loop([file = __FILE__, line = __LINE__](auto err) { MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); }); FramebufferImpl::SyncCurrentFBO(); errorLopper.Loop([file = __FILE__, line = __LINE__](auto err) { MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); }); RenderStateImpl::SyncRenderState(); errorLopper.Loop([file = __FILE__, line = __LINE__](auto err) { MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); }); GLint realInternalFormat; MG_External::GLES::glGetTexLevelParameteriv(target, level, GL_TEXTURE_INTERNAL_FORMAT, &realInternalFormat); errorLopper.Loop([file = __FILE__, line = __LINE__](auto err) { MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); }); internalformat = (GLenum)realInternalFormat; auto mglInternalFormat = MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat); GLenum format = GL_DEPTH_COMPONENT; GLenum type = GL_UNSIGNED_INT; TextureImpl::GenerateTextureFormatInfo(mglInternalFormat, &internalformat, &format, &type); TexturePixelDataType texturePixelDataType = MG_Util::ConvertGLEnumToTexturePixelDataType(type); bool isDepthFormat = MG_Util::IsDepthFormatInternalFormat(MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat)); bool isStencilFormat = MG_Util::IsStencilFormatInternalFormat(MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat)); if (!isDepthFormat) { MG_External::GLES::glCopyTexImage2D(target, level, internalformat, x, y, width, height, border); errorLopper.Loop([file = __FILE__, line = __LINE__](auto err) { MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); }); } else { MGLOG_D("%s: Backend depth", __func__); MG_External::GLES::glTexImage2D(target, level, (GLint)internalformat, width, height, border, format, type, nullptr); FramebufferImpl::BackendFramebufferBindingProtector drawFboProtector(GL_DRAW_FRAMEBUFFER); FramebufferImpl::BackendFramebufferBindingProtector readFboProtector(GL_READ_FRAMEBUFFER); errorLopper.Loop([file = __FILE__, line = __LINE__](auto err) { MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); }); FramebufferImpl::BackendFramebufferBindingProtector::BindTempFBO(FramebufferTarget::Draw); errorLopper.Loop([file = __FILE__, line = __LINE__](auto err) { MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); }); GLint currentTex; MG_External::GLES::glGetIntegerv(Utils::GetBindingQuery(target, false), ¤tTex); errorLopper.Loop([file = __FILE__, line = __LINE__](auto err) { MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); }); GLenum attachment = isStencilFormat ? GL_DEPTH_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT; MG_External::GLES::glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, attachment, target, currentTex, level); if (MG_External::GLES::glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { // Protector will automatically revert to previous fbo states return; } MG_External::GLES::glBlitFramebuffer(x, y, x + width, y + height, 0, 0, width, height, GL_DEPTH_BUFFER_BIT | (isStencilFormat ? GL_STENCIL_BUFFER_BIT : 0), GL_NEAREST); errorLopper.Loop([file = __FILE__, line = __LINE__](auto err) { MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); }); // Protector will automatically revert to previous fbo states } // TODO: potential desync between MG_State and backend auto activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit()); TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget); auto textureObject = bindingSlot.GetBoundObject(); textureObject->SetInternalFormat(mglInternalFormat); MOBILEGL_ASSERT(nullptr != dynamic_cast(textureObject.get()), "Texture object here should always be an object with mipmap"); auto textureMipmapObject = static_cast(textureObject.get()); textureMipmapObject->AllocateStorage(TextureUploadTarget::Texture2D, level, {{width, height, 1}, 0}); } void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height) { DebugImpl::ErrorLopper errorLopper; MGLOG_D("%s: Backend", __func__); TextureImpl::SyncNeccessaryTextures(); errorLopper.Loop([file = __FILE__, line = __LINE__](auto err) { MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); }); FramebufferImpl::SyncCurrentFBO(); errorLopper.Loop([file = __FILE__, line = __LINE__](auto err) { MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); }); RenderStateImpl::SyncRenderState(); errorLopper.Loop([file = __FILE__, line = __LINE__](auto err) { MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); }); Int unit = MG_State::pGLContext->GetActiveTextureUnit(); auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(unit); TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget); const auto& textureObject = bindingSlot.GetBoundObject(); const auto& backendTextureIt = TextureImpl::g_backendTextureObjects.find(textureObject); SharedPtr backendTextureObject; if (backendTextureIt == TextureImpl::g_backendTextureObjects.end()) { backendTextureObject = MakeShared(); TextureImpl::g_backendTextureObjects[textureObject] = backendTextureObject; } else { backendTextureObject = backendTextureIt->second; } backendTextureObject->Bind(target); BindCurrentFBO(FramebufferTarget::Read); errorLopper.Loop([file = __FILE__, line = __LINE__](auto err) { MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); }); GLenum internalFormat; MG_External::GLES::glGetTexLevelParameteriv(target, level, GL_TEXTURE_INTERNAL_FORMAT, (GLint*)&internalFormat); errorLopper.Loop([file = __FILE__, line = __LINE__](auto err) { MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); }); auto mglInternalFormat = MG_Util::ConvertGLEnumToTextureInternalFormat(internalFormat); bool isDepthFormat = MG_Util::IsDepthFormatInternalFormat(mglInternalFormat); bool isStencilFormat = MG_Util::IsStencilFormatInternalFormat(mglInternalFormat); if (!isDepthFormat) { MG_External::GLES::glCopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height); errorLopper.Loop([file = __FILE__, line = __LINE__](auto err) { MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); }); } else { MGLOG_D("%s: Backend depth", __func__); FramebufferImpl::BackendFramebufferBindingProtector drawFboProtector(GL_DRAW_FRAMEBUFFER); FramebufferImpl::BackendFramebufferBindingProtector readFboProtector(GL_READ_FRAMEBUFFER); errorLopper.Loop([file = __FILE__, line = __LINE__](auto err) { MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); }); FramebufferImpl::BackendFramebufferBindingProtector::BindTempFBO(FramebufferTarget::Draw); errorLopper.Loop([file = __FILE__, line = __LINE__](auto err) { MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); }); GLint currentTex; MG_External::GLES::glGetIntegerv(Utils::GetBindingQuery(target, false), ¤tTex); errorLopper.Loop([file = __FILE__, line = __LINE__](auto err) { MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); }); GLenum attachment = isStencilFormat ? GL_DEPTH_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT; MG_External::GLES::glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, attachment, target, currentTex, level); errorLopper.Loop([file = __FILE__, line = __LINE__](auto err) { MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); }); if (MG_External::GLES::glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { // Protector will automatically revert to previous fbo states return; } MG_External::GLES::glBlitFramebuffer( x, y, x + width, y + height, xoffset, yoffset, xoffset + width, yoffset + height, GL_DEPTH_BUFFER_BIT | (isStencilFormat ? GL_STENCIL_BUFFER_BIT : 0), GL_NEAREST); errorLopper.Loop([file = __FILE__, line = __LINE__](auto err) { MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); }); // Protector will automatically revert to previous fbo states } } void GenerateMipmap(GLenum target) { auto unitIndex = MG_State::pGLContext->GetActiveTextureUnit(); auto& unit = MG_State::pGLContext->GetTextureUnitObject(unitIndex); auto& slot = unit.GetBindingSlot(MG_Util::ConvertGLEnumToTextureTarget(target)); auto texture = slot.GetBoundObject(); auto backendTexture = TextureImpl::SyncTextureObjectToBackend(texture); TextureImpl::BackendTextureBindingProtector protector(target); backendTexture->Bind(target); MG_External::GLES::glGenerateMipmap(target); } const GLubyte* GetString(GLenum name) { return MG_External::GLES::glGetString(name); } void ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil) { TextureImpl::SyncNeccessaryTextures(); FramebufferImpl::SyncCurrentFBO(); RenderStateImpl::SyncRenderState(); BindCurrentFBO(FramebufferTarget::Draw); MG_External::GLES::glClearBufferfi(buffer, drawbuffer, depth, stencil); } void ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value) { TextureImpl::SyncNeccessaryTextures(); FramebufferImpl::SyncCurrentFBO(); RenderStateImpl::SyncRenderState(); BindCurrentFBO(FramebufferTarget::Draw); auto backendFBOIt = FramebufferImpl::g_backendFramebufferObjects.find( MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject()); if (backendFBOIt == FramebufferImpl::g_backendFramebufferObjects.end()) { MGLOG_E("No backend FBO found for current draw FBO, cannot clear buffer."); return; } auto backendFBO = backendFBOIt->second; GLint realDrawbuffer = drawbuffer; if (buffer == GL_COLOR) { auto& stateDrawBuffers = backendFBOIt->first->GetDrawBuffers(); if (drawbuffer < 0 || drawbuffer >= MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS) { MGLOG_E("Invalid drawbuffer index: %d", drawbuffer); return; } FramebufferAttachmentType attachmentType = stateDrawBuffers[drawbuffer]; if (attachmentType == FramebufferAttachmentType::None) { MGLOG_D("Drawbuffer %d has no attachment, skipping clear", drawbuffer); return; } bool found = false; for (int i = 0; i < MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS; i++) { if (backendFBO->GetCompactedAttachmentTypeAtDrawBufferIndex(i) == attachmentType) { realDrawbuffer = i; found = true; break; } } if (!found) { MGLOG_E("Failed to find backend drawbuffer for attachment type: %d", static_cast(attachmentType)); return; } } else if (buffer == GL_DEPTH || buffer == GL_STENCIL) { if (drawbuffer != 0) { MGLOG_W("Depth/stencil clear buffer index must be 0, got %d. Using 0.", drawbuffer); } realDrawbuffer = 0; } MG_External::GLES::glClearBufferfv(buffer, realDrawbuffer, value); } void ClearBufferiv(GLenum buffer, GLint drawbuffer, const GLint* value) { TextureImpl::SyncNeccessaryTextures(); FramebufferImpl::SyncCurrentFBO(); RenderStateImpl::SyncRenderState(); BindCurrentFBO(FramebufferTarget::Draw); auto backendFBOIt = FramebufferImpl::g_backendFramebufferObjects.find( MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject()); if (backendFBOIt == FramebufferImpl::g_backendFramebufferObjects.end()) { MGLOG_E("No backend FBO found for current draw FBO, cannot clear buffer."); return; } auto backendFBO = backendFBOIt->second; GLint realDrawbuffer = drawbuffer; if (buffer == GL_COLOR) { auto& stateDrawBuffers = backendFBOIt->first->GetDrawBuffers(); if (drawbuffer < 0 || drawbuffer >= MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS) { MGLOG_E("Invalid drawbuffer index: %d", drawbuffer); return; } FramebufferAttachmentType attachmentType = stateDrawBuffers[drawbuffer]; if (attachmentType == FramebufferAttachmentType::None) { MGLOG_D("Drawbuffer %d has no attachment, skipping clear", drawbuffer); return; } bool found = false; for (int i = 0; i < MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS; i++) { if (backendFBO->GetCompactedAttachmentTypeAtDrawBufferIndex(i) == attachmentType) { realDrawbuffer = i; found = true; break; } } if (!found) { MGLOG_E("Failed to find backend drawbuffer for attachment type: %d", static_cast(attachmentType)); return; } } else if (buffer == GL_STENCIL) { if (drawbuffer != 0) { MGLOG_W("Stencil clear buffer index must be 0, got %d. Using 0.", drawbuffer); } realDrawbuffer = 0; } MG_External::GLES::glClearBufferiv(buffer, realDrawbuffer, value); } void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value) { TextureImpl::SyncNeccessaryTextures(); FramebufferImpl::SyncCurrentFBO(); RenderStateImpl::SyncRenderState(); BindCurrentFBO(FramebufferTarget::Draw); auto backendFBOIt = FramebufferImpl::g_backendFramebufferObjects.find( MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject()); if (backendFBOIt == FramebufferImpl::g_backendFramebufferObjects.end()) { MGLOG_E("No backend FBO found for current draw FBO, cannot clear buffer."); return; } auto backendFBO = backendFBOIt->second; GLint realDrawbuffer = drawbuffer; if (buffer == GL_COLOR) { auto& stateDrawBuffers = backendFBOIt->first->GetDrawBuffers(); if (drawbuffer < 0 || drawbuffer >= MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS) { MGLOG_E("Invalid drawbuffer index: %d", drawbuffer); return; } FramebufferAttachmentType attachmentType = stateDrawBuffers[drawbuffer]; if (attachmentType == FramebufferAttachmentType::None) { MGLOG_D("Drawbuffer %d has no attachment, skipping clear", drawbuffer); return; } bool found = false; for (int i = 0; i < MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS; i++) { if (backendFBO->GetCompactedAttachmentTypeAtDrawBufferIndex(i) == attachmentType) { realDrawbuffer = i; found = true; break; } } if (!found) { MGLOG_E("Failed to find backend drawbuffer for attachment type: %d", static_cast(attachmentType)); return; } } else { MGLOG_E("ClearBufferuiv can only be used with GL_COLOR buffer, got %s", MG_Util::ConvertGLEnumToString(buffer).c_str()); return; } MG_External::GLES::glClearBufferuiv(buffer, realDrawbuffer, value); } } // namespace MobileGL::MG_Backend::DirectGLES