// MobileGL - MobileGL/MG_Backend/DirectGLES/Managers.cpp // Copyright (c) 2025-2026 MobileGL-Dev // Licensed under the GNU Lesser General Public License v3.0: // https://www.gnu.org/licenses/gpl-3.0.txt // https://www.gnu.org/licenses/lgpl-3.0.txt // SPDX-License-Identifier: LGPL-3.0-only // End of Source File Header #include "Managers.h" #include "Utils.h" #include "DirectGLES.h" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace MobileGL::MG_Backend::DirectGLES { constexpr Bool PREFER_MAP_BUFFER_RANGE_FOR_BUFFER_SYNC = true; namespace BufferImpl { BackendBufferObject::BackendBufferObject() { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif g_GLESFuncs.glGenBuffers(1, &m_backendBufferId); if (m_backendBufferId == 0) { MGLOG_E("Failed to generate buffer object."); MGLOG_E("ES glGetError(): %s", MG_Util::ConvertGLEnumToString(g_GLESFuncs.glGetError()).c_str()); } else { MGLOG_D("Generated buffer object with ID: %u.", m_backendBufferId); } } void BackendBufferObject::SyncToBackend(const SharedPtr& stateBufferObject) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif if (!stateBufferObject) { MGLOG_E("State buffer object is null, cannot sync to backend."); return; } SizeT bufferSize = stateBufferObject->GetSize(); if (bufferSize == 0) { MGLOG_W("Buffer size is zero, skipping sync for object with ID: %u", m_backendBufferId); return; } MGLOG_D("Syncing buffer object with backend ID %u to backend for state ID %u", m_backendBufferId, stateBufferObject->GetExternalIndex()); // Decide sync method // glBufferData Bool needsRegeneration = !m_isInitialized || (stateBufferObject->GetChangeBits() & BufferChangeBits::PreferReallocationBit); if (needsRegeneration) { MGLOG_D("Buffer size changed significantly or not initialized, regenerating buffer with ID: %u", m_backendBufferId); SyncToBackend_glBufferData(stateBufferObject); m_isInitialized = true; m_prevBufferSize = bufferSize; stateBufferObject->ClearDirty(); return; } // glMapBufferRange or glBufferSubData Bool useInvalidationMap = !(stateBufferObject->GetChangeBits() & BufferChangeBits::ForbidInvalidationBit); Bool useUnsynchronizedMap = !(stateBufferObject->GetChangeBits() & BufferChangeBits::ForbidUnsynchronizationBit); Bool useMapBufferRange = useInvalidationMap || useUnsynchronizedMap; if (!useMapBufferRange && PREFER_MAP_BUFFER_RANGE_FOR_BUFFER_SYNC) { auto usage = stateBufferObject->GetUsage(); if (usage == BufferUsage::DynamicDraw || usage == BufferUsage::StreamDraw || usage == BufferUsage::StreamCopy || usage == BufferUsage::DynamicCopy) { useMapBufferRange = true; } } if (useMapBufferRange) { MGLOG_D("Using glMapBufferRange to sync buffer with ID: %u", m_backendBufferId); SyncToBackend_glMapBufferRange(stateBufferObject, useInvalidationMap, useUnsynchronizedMap); } else { MGLOG_D("Using glBufferSubData to sync buffer with ID: %u", m_backendBufferId); SyncToBackend_glBufferSubData(stateBufferObject); } // Clear dirty state stateBufferObject->ClearDirty(); m_prevBufferSize = bufferSize; } void BackendBufferObject::SyncToBackend_glBufferData( const SharedPtr& stateBufferObject) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif MGLOG_D("Syncing buffer data (glBufferData) for object with ID : %u", m_backendBufferId); const void* data = stateBufferObject->GetDataReadOnly()->data(); SizeT size = stateBufferObject->GetSize(); GLenum usage = MG_Util::ConvertBufferUsageToGLEnum(stateBufferObject->GetUsage()); Bind(); g_GLESFuncs.glBufferData(TempBufferTarget, (GLsizeiptr)size, data, usage); } void BackendBufferObject::SyncToBackend_glBufferSubData( const SharedPtr& stateBufferObject) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif MGLOG_D("Syncing buffer sub-data (glBufferSubData) for object with ID : %u", m_backendBufferId); const void* data = stateBufferObject->GetDataReadOnly()->data(); // dirty range: [range.start, range.end) auto& ranges = stateBufferObject->GetDirtyRanges(); if (ranges.empty()) { MGLOG_D("No dirty range to sync for buffer with ID: %u", m_backendBufferId); return; } for (const auto& range : ranges) { Bind(); g_GLESFuncs.glBufferSubData(TempBufferTarget, (GLintptr)range.start, (GLintptr)(range.end - range.start), reinterpret_cast(data) + range.start); } } void BackendBufferObject::SyncToBackend_glMapBufferRange( const SharedPtr& stateBufferObject, Bool invalidate, Bool unsynchronized) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif MGLOG_D("Syncing buffer map (glMapBuffer) for object with ID : %u", m_backendBufferId); MGLOG_D("Mapping buffer with ID: %u", m_backendBufferId); auto& ranges = stateBufferObject->GetDirtyRanges(); if (ranges.empty()) { MGLOG_D("No dirty range to sync for buffer with ID: %u", m_backendBufferId); return; } SizeT minStart = ranges.GetOverallMinStart(); SizeT maxEnd = ranges.GetOverallMaxEnd(); Bind(); void* mappedData = g_GLESFuncs.glMapBufferRange( TempBufferTarget, (GLintptr)minStart, (GLintptr)(maxEnd - minStart), (invalidate ? GL_MAP_INVALIDATE_RANGE_BIT : 0) | (unsynchronized ? GL_MAP_UNSYNCHRONIZED_BIT : 0) | GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT); const void* data = stateBufferObject->GetDataReadOnly()->data(); if (mappedData) { MGLOG_D("Mapped buffer data successfully for object with ID: %u", m_backendBufferId); Memcpy(mappedData, reinterpret_cast(data) + minStart, maxEnd - minStart); // Explicitly flush the dirty ranges for (const auto& range : ranges) { g_GLESFuncs.glFlushMappedBufferRange(TempBufferTarget, (GLintptr)(range.start - minStart), (GLintptr)(range.end - range.start)); } g_GLESFuncs.glUnmapBuffer(TempBufferTarget); } else { MGLOG_E("Failed to map buffer with ID: %u", m_backendBufferId); } } void BackendBufferObject::Bind(GLenum target) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif if (target == GL_ARRAY_BUFFER) { if (g_boundVertexBufferObject == this) { return; } g_boundVertexBufferObject = this; } g_GLESFuncs.glBindBuffer(target, m_backendBufferId); } StateBackendObjectRegistry g_backendBufferObjects; BackendBufferObject* g_boundVertexBufferObject = nullptr; } // namespace BufferImpl namespace VertexArrayImpl { BackendVertexArrayObject::BackendVertexArrayObject() { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif g_GLESFuncs.glGenVertexArrays(1, &m_backendVAOId); if (m_backendVAOId == 0) { MGLOG_E("Failed to generate vertex array object."); MGLOG_E("ES glGetError(): %s", MG_Util::ConvertGLEnumToString(g_GLESFuncs.glGetError()).c_str()); } else { MGLOG_D("Generated vertex array object with ID: %u.", m_backendVAOId); } } void BackendVertexArrayObject::Bind() const { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif g_GLESFuncs.glBindVertexArray(m_backendVAOId); } inline void BindAttributeBuffer(const MG_State::GLState::VertexAttribute& attrib) { const auto& bufferObject = attrib.Buffer; if (!bufferObject) { MGLOG_W("Attribute has no bound buffer, skipping."); return; } const auto& backendBufferIt = BufferImpl::g_backendBufferObjects.find(bufferObject.get()); if (backendBufferIt == BufferImpl::g_backendBufferObjects.end()) { MGLOG_E("No backend buffer found for attribute's buffer, cannot bind attribute."); return; } const auto& backendBufferObject = backendBufferIt->second; backendBufferObject->Bind(GL_ARRAY_BUFFER); } void BackendVertexArrayObject::SyncToBackend( const SharedPtr& stateVAOObject) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif if (!stateVAOObject) { MGLOG_E("State VAO object is null, cannot sync to backend."); return; } MGLOG_D("Syncing VAO with backend ID %u to backend for state ID %u", m_backendVAOId, stateVAOObject->GetExternalIndex()); Bind(); const auto& allAttributeVersions = stateVAOObject->GetAllAttributeVersions(); const auto& allAttributes = stateVAOObject->GetAllAttributes(); for (Uint attribIndex = 0; attribIndex < allAttributes.size(); ++attribIndex) { const auto& attrib = allAttributes[attribIndex]; Bool needsSyncSwitch = allAttributeVersions[attribIndex].SwitchVersion != m_syncedAttributeVersions[attribIndex].SwitchVersion; if (needsSyncSwitch) { if (attrib.Enabled) { g_GLESFuncs.glEnableVertexAttribArray(attribIndex); } else { g_GLESFuncs.glDisableVertexAttribArray(attribIndex); } } Bool needsSyncFormat = allAttributeVersions[attribIndex].FormatVersion != m_syncedAttributeVersions[attribIndex].FormatVersion; Bool needsSyncBuffer = allAttributeVersions[attribIndex].BufferVersion != m_syncedAttributeVersions[attribIndex].BufferVersion; if (!needsSyncFormat && !needsSyncBuffer) continue; BindAttributeBuffer(attrib); if (!attrib.IsInteger) { g_GLESFuncs.glVertexAttribPointer( attribIndex, attrib.Size, MG_Util::ConvertDataTypeToGLEnum(attrib.Type), attrib.Normalized ? GL_TRUE : GL_FALSE, attrib.Stride, (const void*)attrib.Offset); } else { g_GLESFuncs.glVertexAttribIPointer(attribIndex, attrib.Size, MG_Util::ConvertDataTypeToGLEnum(attrib.Type), attrib.Stride, (const void*)attrib.Offset); } if (needsSyncFormat) { g_GLESFuncs.glVertexAttribDivisor(attribIndex, attrib.Divisor); } } Uint16 currentIndexBufferVersion = stateVAOObject->GetIndexBufferBindingSlot().GetVersion(); if (currentIndexBufferVersion != m_syncedIndexBufferVersion) { const auto& indexBufferBinding = stateVAOObject->GetIndexBufferBindingSlot().GetBoundObject(); if (indexBufferBinding) { const auto& backendBufferIt = BufferImpl::g_backendBufferObjects.find(indexBufferBinding.get()); if (backendBufferIt != BufferImpl::g_backendBufferObjects.end()) { const auto& backendBufferObject = backendBufferIt->second; backendBufferObject->Bind(GL_ELEMENT_ARRAY_BUFFER); } else { MGLOG_W("No backend buffer found for index buffer binding, cannot bind index buffer."); } } m_syncedIndexBufferVersion = currentIndexBufferVersion; } m_syncedAttributeVersions = allAttributeVersions; } StateBackendObjectRegistry g_backendVertexArrayObjects; } // namespace VertexArrayImpl namespace TextureImpl { BackendTextureObject::BackendTextureObject() { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif g_GLESFuncs.glGenTextures(1, &m_backendTextureId); if (m_backendTextureId == 0) { MGLOG_E("Failed to generate texture object."); MGLOG_E("ES glGetError(): %s", MG_Util::ConvertGLEnumToString(g_GLESFuncs.glGetError()).c_str()); } else { MGLOG_D("Generated texture object with ID: %u.", m_backendTextureId); } } void BackendTextureObject::Bind(GLenum target, Uint unit) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif if (g_activeTextureUnit != unit) { ActivateTextureUnit(unit); } auto targetN = static_cast(MG_Util::ConvertGLEnumToTextureTarget(target)); if (this == g_boundTexturesCache[unit][targetN]) return; g_GLESFuncs.glBindTexture(target, m_backendTextureId); g_boundTexturesCache[unit][targetN] = this; } Uint BackendTextureObject::GetBackendTextureId() const { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif return m_backendTextureId; } void BackendTextureObject::SyncMipmapsToBackend( const SharedPtr& stateTextureObject) { if (!stateTextureObject) { MGLOG_E("State texture object is null, cannot sync to backend."); return; } #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif MGLOG_D("Syncing texture mipmaps with backend ID %u to backend for state ID %u", m_backendTextureId, stateTextureObject->GetExternalIndex()); GLenum target = MG_Util::ConvertTextureTargetToGLEnum(stateTextureObject->GetTarget()); auto targetInternal = stateTextureObject->GetTarget(); MGLOG_D(" Texture target for syncing is %s", MG_Util::ConvertTextureTargetToString(targetInternal).c_str()); if (!IsSupportedTextureTarget(targetInternal)) { MGLOG_E(" Texture target %s is not supported, skipping.", MG_Util::ConvertTextureTargetToString(targetInternal).c_str()); return; } // The texture needs to be regenerated completely with glTexImage* calls if: // 1. Not initialized // 2. InternalFormat changed // 3. Size changed // 4. Mipmap levels changed if (!stateTextureObject->IsComplete()) { MGLOG_D("Texture object with ID: %u is not complete, skipping sync.", stateTextureObject->GetExternalIndex()); return; } Bind(target); DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__, func = __func__](GLenum err) { MGLOG_D("%s(%s:%d) ES error: %s", func, file, line, MG_Util::ConvertGLEnumToString(err).c_str()); }); const auto baseSize = stateTextureObject->GetBaseSize(); StateTextureBasicInfo currentTextureInfo = {stateTextureObject->GetFormat(), static_cast(baseSize.x()), static_cast(baseSize.y()), static_cast(baseSize.z()), 0, 0}; switch (stateTextureObject->GetStorageType()) { case TextureStorageType::Mipmap: { auto* textureMipmapObject = static_cast(stateTextureObject.get()); const auto mipmapCount = textureMipmapObject->GetMipmapLevelCount(); currentTextureInfo.mipmapLevels = mipmapCount; Bool needsRegeneration = !m_isInitialized || (currentTextureInfo != m_prevTextureInfo); MGLOG_D("%s: Got texture info: %dx%dx%d, mips %d, format %s", __func__, baseSize.x(), baseSize.y(), baseSize.z(), mipmapCount, MG_Util::ConvertTextureInternalFormatToString(textureMipmapObject->GetFormat()).c_str()); if (needsRegeneration) { MGLOG_D("Texture state changed significantly or not initialized, regenerating texture with ID: %u", m_backendTextureId); // Regenerate all mipmap levels GLenum glInternalFormat, glType, glFormat; TextureImpl::GenerateTextureFormatInfo(textureMipmapObject->GetFormat(), &glInternalFormat, &glFormat, &glType); const auto& uploadTargets = textureMipmapObject->GetUploadTargets(); for (auto& uploadTarget : uploadTargets) { for (SizeT level = 0; level < mipmapCount; ++level) { auto levelTexelSize = textureMipmapObject->GetMipmapTexelSize(uploadTarget, level); auto levelByteSize = textureMipmapObject->GetMipmapByteSize(uploadTarget, level); bool levelDirty = textureMipmapObject->IsStorageDirty(uploadTarget, level); auto glUploadTarget = MG_Util::ConvertTextureUploadTargetToGLEnum(uploadTarget); auto* pData = (levelDirty && levelByteSize != 0) ? textureMipmapObject->MapMipmapData(uploadTarget, level) : nullptr; MGLOG_D( "%s: target: %s: syncing mip %d: %dx%dx%d, byteSize = %d, pData = %p, levelDirty = %s", __func__, MG_Util::ConvertTextureUploadTargetToString(uploadTarget).c_str(), level, levelTexelSize.x(), levelTexelSize.y(), levelTexelSize.z(), levelByteSize, pData, levelDirty ? "true" : "false"); DebugImpl::ErrorLopper::Clear(); g_GLESFuncs.glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); auto textureTarget = stateTextureObject->GetTarget(); // TODO: handle more texture types switch (textureTarget) { case TextureTarget::Texture2D: case TextureTarget::TextureCubeMap: { g_GLESFuncs.glTexImage2D( glUploadTarget, static_cast(level), (GLint)glInternalFormat, static_cast(levelTexelSize.x()), static_cast(levelTexelSize.y()), 0, glFormat, glType, pData); break; } case TextureTarget::Texture3D: { g_GLESFuncs.glTexImage3D( glUploadTarget, static_cast(level), (GLint)glInternalFormat, static_cast(levelTexelSize.x()), static_cast(levelTexelSize.y()), static_cast(levelTexelSize.z()), 0, glFormat, glType, pData); break; } default: { MGLOG_E("Unhandled texture target %s", MG_Util::ConvertTextureTargetToString(textureTarget).c_str()); } } DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__, func = __func__, glUploadTarget, glInternalFormat, glFormat, glType, pData](GLenum err) { MGLOG_D("%s(%s:%d) ES error: %s. glTexImage*: target=%s, internalformat=%s, format=%s, " "type=%s, pixels=%p", func, file, line, MG_Util::ConvertGLEnumToString(err).c_str(), MG_Util::ConvertGLEnumToString(glUploadTarget).c_str(), MG_Util::ConvertGLEnumToString(glInternalFormat).c_str(), MG_Util::ConvertGLEnumToString(glFormat).c_str(), MG_Util::ConvertGLEnumToString(glType).c_str(), pData); }); MGLOG_D("Regenerated mipmap level %d for texture with ID: %u", level, m_backendTextureId); textureMipmapObject->MarkStorageDirty(uploadTarget, level, false); } } m_isInitialized = true; } { // Update all dirty mipmap levels const auto mipmapCount = textureMipmapObject->GetMipmapLevelCount(); GLenum glInternalFormat, glType, glFormat; TextureImpl::GenerateTextureFormatInfo(textureMipmapObject->GetFormat(), &glInternalFormat, &glFormat, &glType); const auto& uploadTargets = textureMipmapObject->GetUploadTargets(); for (auto& uploadTarget : uploadTargets) { for (SizeT level = 0; level < mipmapCount; ++level) { if (!textureMipmapObject->IsStorageDirty(uploadTarget, level)) { continue; } auto byteSize = textureMipmapObject->GetMipmapByteSize(uploadTarget, level); if (byteSize == 0) { MGLOG_W("Mipmap level %d has no data, skipping update.", level); continue; } if (level > 0) MGLOG_D("%s: Updating dirty mip %d for texture ID %u, size: %dx%d, " "byteSize: %d", __func__, level, m_backendTextureId, textureMipmapObject->GetMipmapTexelSize(uploadTarget, level).x(), textureMipmapObject->GetMipmapTexelSize(uploadTarget, level).y(), byteSize); auto glUploadTarget = MG_Util::ConvertTextureUploadTargetToGLEnum(uploadTarget); g_GLESFuncs.glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); DebugImpl::ErrorLopper::Loop( [file = __FILE__, line = __LINE__, func = __func__](GLenum err) { MGLOG_D("%s(%s:%d) ES error: %s", func, file, line, MG_Util::ConvertGLEnumToString(err).c_str()); }); auto texelSize = textureMipmapObject->GetMipmapTexelSize(uploadTarget, level); g_GLESFuncs.glTexSubImage2D(glUploadTarget, static_cast(level), 0, 0, static_cast(texelSize.x()), static_cast(texelSize.y()), glFormat, glType, textureMipmapObject->MapMipmapData(uploadTarget, level)); textureMipmapObject->MarkStorageDirty(uploadTarget, level, false); } } } break; } case TextureStorageType::Buffer: { auto* textureBufferObject = static_cast(stateTextureObject.get()); auto& slot = textureBufferObject->GetBufferBindingSlot(); auto& buffer = slot.GetBoundObject(); auto bufferIndex = buffer->GetExternalIndex(); currentTextureInfo.bufferExternalIndex = bufferIndex; Bool needsRegeneration = !m_isInitialized || (currentTextureInfo != m_prevTextureInfo); MGLOG_D("Texture state changed significantly or not initialized, regenerating texture (tex buffer) " "with ID: %u", m_backendTextureId); // Need to sync texture buffer if not synced yet auto& backendBuffers = BufferImpl::g_backendBufferObjects; SharedPtr backendBufferObject; const auto& backendBufferIt = backendBuffers.find(buffer.get()); if (backendBufferIt == backendBuffers.end()) { auto& backendBufferSlot = backendBuffers.GetOrCreate(buffer); if (!backendBufferSlot) { backendBufferSlot = MakeShared(); } backendBufferObject = backendBufferSlot; } else { backendBufferObject = backendBufferIt->second; } backendBufferObject->SyncToBackend(buffer); // Bind buffer to texture auto backendId = backendBufferObject->GetBackendBufferId(); GLenum glInternalFormat, glType, glFormat; TextureImpl::GenerateTextureFormatInfo(textureBufferObject->GetFormat(), &glInternalFormat, &glFormat, &glType); g_GLESFuncs.glTexBuffer(GL_TEXTURE_BUFFER, glInternalFormat, backendId); break; } default: THROW_UNIMPL_EXCEPTION; } DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__, func = __func__](GLenum err) { MGLOG_D("%s(%s:%d) ES error: %s", func, file, line, MG_Util::ConvertGLEnumToString(err).c_str()); }); m_prevTextureInfo = currentTextureInfo; } void BackendTextureObject::SyncBuiltinSamplerToBackend( const SharedPtr& stateTextureObject) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif if (!stateTextureObject) { MGLOG_E("State texture object is null, cannot sync to backend."); return; } auto* samplerObject = stateTextureObject->GetSamplerObject().get(); Uint currentSamplerVersion = samplerObject->GetVersion(); if (m_syncedSamplerVersion == currentSamplerVersion) { MGLOG_D("Sampler parameters have not changed for texture ID: %u, skipping sync.", m_backendTextureId); return; } m_syncedSamplerVersion = currentSamplerVersion; MGLOG_D("Syncing texture built-in sampler with backend ID %u to backend for state ID %u", m_backendTextureId, stateTextureObject->GetExternalIndex()); GLenum target = MG_Util::ConvertTextureTargetToGLEnum(stateTextureObject->GetTarget()); auto targetInternal = stateTextureObject->GetTarget(); MGLOG_D(" Texture target for syncing is %s", MG_Util::ConvertTextureTargetToString(targetInternal).c_str()); if (!IsSupportedTextureTarget(targetInternal)) { MGLOG_E(" Texture target %s is not supported, skipping.", MG_Util::ConvertTextureTargetToString(targetInternal).c_str()); return; } Bind(target); DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__, func = __func__](GLenum err) { MGLOG_D("%s(%s:%d) ES error: %s", func, file, line, MG_Util::ConvertGLEnumToString(err).c_str()); }); // Update built-in sampler parameters MGLOG_D("Updating sampler parameters for texture with ID: %u", m_backendTextureId); const auto& samplerParams = samplerObject->GetAllSamplerParameters(); #define SYNC_TEX_SAMPLER_PARAM_IF_CHANGED(internalName, glName, type) \ if (m_cacheSamplerParameters.internalName != samplerParams.internalName) { \ g_GLESFuncs.glTexParameteri(target, glName, \ MG_Util::ConvertSampler##type##ToGLEnum(samplerParams.internalName)); \ m_cacheSamplerParameters.internalName = samplerParams.internalName; \ DebugImpl::ErrorLopper::Loop( \ [file = __FILE__, line = __LINE__, func = __func__, \ t = MG_Util::ConvertSampler##type##ToGLEnum(samplerParams.internalName)](GLenum err) { \ MGLOG_D("%s(%s:%d) ES error %s, GL_TEXTURE_MIN_FILTER = %s", func, file, line, \ MG_Util::ConvertGLEnumToString(err).c_str(), MG_Util::ConvertGLEnumToString(t).c_str()); \ }); \ } if (m_cacheSamplerParameters.minFilter != samplerParams.minFilter || m_cacheSamplerParameters.mipmapMode != samplerParams.mipmapMode) { g_GLESFuncs.glTexParameteri(target, GL_TEXTURE_MIN_FILTER, (GLint)MG_Util::ConvertSamplerFilterModeToGLEnum(samplerParams.minFilter, samplerParams.mipmapMode)); m_cacheSamplerParameters.minFilter = samplerParams.minFilter; m_cacheSamplerParameters.mipmapMode = samplerParams.mipmapMode; } if (m_cacheSamplerParameters.magFilter != samplerParams.magFilter) { g_GLESFuncs.glTexParameteri( target, GL_TEXTURE_MAG_FILTER, (GLint)MG_Util::ConvertSamplerFilterModeToGLEnum(samplerParams.magFilter, SamplerMipmapMode::None)); m_cacheSamplerParameters.magFilter = samplerParams.magFilter; } DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__, func = __func__](GLenum err) { MGLOG_D("%s(%s:%d) ES error %s", func, file, line, MG_Util::ConvertGLEnumToString(err).c_str()); }); SYNC_TEX_SAMPLER_PARAM_IF_CHANGED(wrapS, GL_TEXTURE_WRAP_S, WrapMode) SYNC_TEX_SAMPLER_PARAM_IF_CHANGED(wrapT, GL_TEXTURE_WRAP_T, WrapMode) SYNC_TEX_SAMPLER_PARAM_IF_CHANGED(wrapR, GL_TEXTURE_WRAP_R, WrapMode) SYNC_TEX_SAMPLER_PARAM_IF_CHANGED(compareFunc, GL_TEXTURE_COMPARE_FUNC, CompareFunc) SYNC_TEX_SAMPLER_PARAM_IF_CHANGED(compareMode, GL_TEXTURE_COMPARE_MODE, CompareMode) if (m_cacheSamplerParameters.minLod != samplerParams.minLod) { g_GLESFuncs.glTexParameterf(target, GL_TEXTURE_MIN_LOD, samplerParams.minLod); m_cacheSamplerParameters.minLod = samplerParams.minLod; } if (m_cacheSamplerParameters.maxLod != samplerParams.maxLod) { g_GLESFuncs.glTexParameterf(target, GL_TEXTURE_MAX_LOD, samplerParams.maxLod); m_cacheSamplerParameters.maxLod = samplerParams.maxLod; } DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__, func = __func__](GLenum err) { MGLOG_D("%s(%s:%d) ES error %s", func, file, line, MG_Util::ConvertGLEnumToString(err).c_str()); }); #undef SYNC_TEX_SAMPLER_PARAM_IF_CHANGED } void BackendTextureObject::SyncTextureParamsToBackend( const SharedPtr& stateTextureObject) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif if (!stateTextureObject) { MGLOG_E("State texture object is null, cannot sync to backend."); return; } Uint16 currentTextureParamsVersion = stateTextureObject->GetTextureParamsVersion(); if (m_syncedTextureParamsVersion == currentTextureParamsVersion) { MGLOG_D("Texture parameters have not changed for texture ID: %u, skipping sync.", m_backendTextureId); return; } m_syncedTextureParamsVersion = currentTextureParamsVersion; MGLOG_D("Syncing texture params with backend ID %u to backend for state ID %u", m_backendTextureId, stateTextureObject->GetExternalIndex()); GLenum target = MG_Util::ConvertTextureTargetToGLEnum(stateTextureObject->GetTarget()); auto targetInternal = stateTextureObject->GetTarget(); MGLOG_D(" Texture target for syncing is %s", MG_Util::ConvertTextureTargetToString(targetInternal).c_str()); if (!IsSupportedTextureTarget(targetInternal)) { MGLOG_E(" Texture target %s is not supported, skipping.", MG_Util::ConvertTextureTargetToString(targetInternal).c_str()); return; } Bind(target); DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__, func = __func__](GLenum err) { MGLOG_D("%s(%s:%d) ES error: %s", func, file, line, MG_Util::ConvertGLEnumToString(err).c_str()); }); // Update texture parameters MGLOG_D("Updating texture parameters for texture with ID: %u", m_backendTextureId); const auto& levelRange = stateTextureObject->GetLevelRange(); if (m_cacheLodRange.x() != levelRange.x()) { g_GLESFuncs.glTexParameteri(target, GL_TEXTURE_BASE_LEVEL, static_cast(levelRange.x())); m_cacheLodRange.x() = levelRange.x(); } DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__, func = __func__](GLenum err) { MGLOG_D("%s(%s:%d) ES error %s", func, file, line, MG_Util::ConvertGLEnumToString(err).c_str()); }); if (m_cacheLodRange.y() != levelRange.y()) { g_GLESFuncs.glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, static_cast(levelRange.y())); m_cacheLodRange.y() = levelRange.y(); } DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__, func = __func__](GLenum err) { MGLOG_D("%s(%s:%d) ES error %s", func, file, line, MG_Util::ConvertGLEnumToString(err).c_str()); }); const auto& swizzleParams = stateTextureObject->GetAllSwizzleParams(); if (swizzleParams != m_cacheSwizzleParams) { #define SYNC_TEX_SWIZZLE_PARAM_IF_CHANGED(func, glEnum) \ if (m_cacheSwizzleParams.func != swizzleParams.func) { \ g_GLESFuncs.glTexParameteri(target, glEnum, MG_Util::ConvertTextureSwizzleParamToGLEnum(swizzleParams.func)); \ m_cacheSwizzleParams.func = swizzleParams.func; \ } SYNC_TEX_SWIZZLE_PARAM_IF_CHANGED(r(), GL_TEXTURE_SWIZZLE_R); SYNC_TEX_SWIZZLE_PARAM_IF_CHANGED(g(), GL_TEXTURE_SWIZZLE_G); SYNC_TEX_SWIZZLE_PARAM_IF_CHANGED(b(), GL_TEXTURE_SWIZZLE_B); SYNC_TEX_SWIZZLE_PARAM_IF_CHANGED(a(), GL_TEXTURE_SWIZZLE_A); #undef SYNC_TEX_SWIZZLE_PARAM_IF_CHANGED m_cacheSwizzleParams = swizzleParams; DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__, func = __func__](GLenum err) { MGLOG_D("%s(%s:%d) ES error %s", func, file, line, MG_Util::ConvertGLEnumToString(err).c_str()); }); } if (m_cacheBorderColor != stateTextureObject->GetBorderColor()) { const auto& borderColor = stateTextureObject->GetBorderColor(); GLfloat borderColorArray[4] = {borderColor.x(), borderColor.y(), borderColor.z(), borderColor.w()}; g_GLESFuncs.glTexParameterfv(target, GL_TEXTURE_BORDER_COLOR, borderColorArray); m_cacheBorderColor = borderColor; DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__, func = __func__](GLenum err) { MGLOG_D("%s(%s:%d) ES error %s", func, file, line, MG_Util::ConvertGLEnumToString(err).c_str()); }); } } void ActivateTextureUnit(Uint unit) { if (unit == g_activeTextureUnit) { return; } g_GLESFuncs.glActiveTexture(GL_TEXTURE0 + unit); g_activeTextureUnit = unit; } void UnbindTexture(Uint unit, GLenum target) { // Active unit will be modified if (unit != g_activeTextureUnit) { ActivateTextureUnit(unit); } auto targetN = static_cast(MG_Util::ConvertGLEnumToTextureTarget(target)); if (g_boundTexturesCache[unit][targetN] == nullptr) return; g_GLESFuncs.glBindTexture(target, 0); g_boundTexturesCache[unit][targetN] = nullptr; } Uint g_activeTextureUnit = 0; Array, MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS> g_boundTexturesCache; StateBackendObjectRegistry g_backendTextureObjects; } // namespace TextureImpl namespace FramebufferImpl { BackendFramebufferObject::BackendFramebufferObject() { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif g_GLESFuncs.glGenFramebuffers(1, &m_backendFBOId); if (m_backendFBOId == 0) { MGLOG_E("Failed to generate framebuffer object."); MGLOG_E("ES glGetError(): %s", MG_Util::ConvertGLEnumToString(g_GLESFuncs.glGetError()).c_str()); } else { MGLOG_D("Generated framebuffer object with ID: %u.", m_backendFBOId); } } void BackendFramebufferObject::Bind(FramebufferTarget target) const { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif if (target == FramebufferTarget::Read) g_GLESFuncs.glBindFramebuffer(GL_READ_FRAMEBUFFER, m_backendFBOId); else g_GLESFuncs.glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_backendFBOId); } static Bool SyncAttachmentObject(GLenum glFBOTarget, const MG_State::GLState::FramebufferAttachmentObject& attachmentObject, GLenum glBackendAttachment) { if (attachmentObject.IsTexture()) { const auto& textureObject = attachmentObject.GetTexture(); const auto& backendTextureIt = TextureImpl::g_backendTextureObjects.find(textureObject.get()); if (backendTextureIt == TextureImpl::g_backendTextureObjects.end()) { MGLOG_E("%s: No backend texture found for FBO attachment, cannot bind texture.", __func__); return false; } const auto& backendTextureObject = backendTextureIt->second; auto glTextureTarget = MG_Util::ConvertTextureTargetToGLEnum(textureObject->GetTarget()); backendTextureObject->Bind(glTextureTarget); g_GLESFuncs.glFramebufferTexture2D(glFBOTarget, glBackendAttachment, glTextureTarget, backendTextureObject->GetBackendTextureId(), static_cast(attachmentObject.GetTextureLevel())); } else if (attachmentObject.IsRenderbuffer()) { const auto& renderbufferObject = attachmentObject.GetRenderbuffer(); const auto& backendRenderbufferIt = RenderbufferImpl::g_backendRenderbufferObjects.find(renderbufferObject.get()); SharedPtr backendRenderbufferObject; if (backendRenderbufferIt == RenderbufferImpl::g_backendRenderbufferObjects.end()) { auto& backendRenderbufferSlot = RenderbufferImpl::g_backendRenderbufferObjects.GetOrCreate(renderbufferObject); if (!backendRenderbufferSlot) { backendRenderbufferSlot = MakeShared(); } backendRenderbufferObject = backendRenderbufferSlot; } else { backendRenderbufferObject = backendRenderbufferIt->second; } backendRenderbufferObject->SyncToBackend(renderbufferObject); backendRenderbufferObject->Bind(); g_GLESFuncs.glFramebufferRenderbuffer(glFBOTarget, glBackendAttachment, GL_RENDERBUFFER, backendRenderbufferObject->GetBackendRenderbufferId()); } return true; } void BackendFramebufferObject::SyncToBackend( const SharedPtr& stateFBOObject, FramebufferTarget asTarget) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif if (!stateFBOObject) { MGLOG_E("State FBO object is null, cannot sync to backend."); return; } MGLOG_D("Syncing FBO with backend ID %u to backend for state ID %u, as %s FBO", m_backendFBOId, stateFBOObject->GetExternalIndex(), (asTarget == FramebufferTarget::Draw ? "DRAW" : "READ")); GLenum glFBOTarget = MG_Util::ConvertFramebufferTargetToGLEnum(asTarget); Bind(asTarget); // -------------------- Connect attachments (set buffers) ----------------------- // 1. Remap draw buffers auto& stateDrawBuffers = stateFBOObject->GetDrawBuffers(); Bool drawBufferClean = false; if (memcmp(m_frontendDrawBuffers, stateDrawBuffers.data(), FramebufferObject::MAX_DRAW_BUFFERS * sizeof(FramebufferAttachmentType)) == 0) { drawBufferClean = true; } if (!drawBufferClean) { memcpy(m_frontendDrawBuffers, stateDrawBuffers.data(), FramebufferObject::MAX_DRAW_BUFFERS * sizeof(FramebufferAttachmentType)); std::fill(m_backendDrawBuffers, m_backendDrawBuffers + FramebufferObject::MAX_DRAW_BUFFERS, GL_NONE); int nEffectiveBuffers = 0; for (GLint i = 0; i < FramebufferObject::MAX_DRAW_BUFFERS; ++i) { auto& frontendBuf = stateDrawBuffers[i]; if (frontendBuf == FramebufferAttachmentType::None) { m_backendDrawBuffers[i] = GL_NONE; continue; } // Create compacted mapping if (frontendBuf == FramebufferAttachmentType::FrontLeft || frontendBuf == FramebufferAttachmentType::FrontRight || frontendBuf == FramebufferAttachmentType::BackLeft || frontendBuf == FramebufferAttachmentType::BackRight) { MGLOG_D("%s: frontend buf token found for default fbo, shouldn't remap", __func__); m_backendDrawBuffers[i] = MG_Util::ConvertFramebufferAttachmentTypeToGLEnum(frontendBuf); } else { m_backendDrawBuffers[i] = GL_COLOR_ATTACHMENT0 + i; } nEffectiveBuffers = i + 1; } g_GLESFuncs.glDrawBuffers(nEffectiveBuffers, m_backendDrawBuffers); } // 2. Remap read buffer auto frontendReadBuf = stateFBOObject->GetReadBuffer(); if (frontendReadBuf != m_frontendReadBuffer) { m_frontendReadBuffer = frontendReadBuf; GLenum glBackendReadBuffer = GetBackendAttachmentType(frontendReadBuf); if (m_backendReadBuffer != glBackendReadBuffer) { m_backendReadBuffer = glBackendReadBuffer; g_GLESFuncs.glReadBuffer(glBackendReadBuffer); } } // -------------------- Attach texture to backend FBO ----------------------- const auto& attachments = stateFBOObject->GetAllAttachmentObjects(); const auto& attachmentVersions = stateFBOObject->GetAllFramebufferAttachmentVersions(); for (SizeT i = 0; i < attachments.size(); ++i) { const auto& attachmentObject = attachments[i]; auto frontendType = static_cast(i); GLenum glBackendAttachment = GL_NONE; if (frontendType >= FramebufferAttachmentType::Color0 && frontendType <= FramebufferAttachmentType::Color31) glBackendAttachment = GetBackendAttachmentType(frontendType); else glBackendAttachment = MG_Util::ConvertFramebufferAttachmentTypeToGLEnum(frontendType); // relevant FRONTEND!!! version should be checked and updated if (m_syncedFrontendAttachmentVersions[i] != attachmentVersions[i]) { SyncAttachmentObject(glFBOTarget, attachmentObject, glBackendAttachment); m_syncedFrontendAttachmentVersions[i] = attachmentVersions[i]; } #if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG else { MGLOG_D("%s: Skipped SyncAttachmentObject(target=%s, frontendObj=(%dx%dx%d, %s), backendAtt=%s), " "version = %u", __func__, MG_Util::ConvertGLEnumToString(glFBOTarget).c_str(), attachmentObject.GetSize().x(), attachmentObject.GetSize().y(), attachmentObject.GetSize().z(), MG_Util::ConvertFramebufferAttachmentTypeToString(frontendType).c_str(), MG_Util::ConvertGLEnumToString(glBackendAttachment).c_str(), m_syncedFrontendAttachmentVersions[i]); GLint objectType = GL_NONE; g_GLESFuncs.glGetFramebufferAttachmentParameteriv( glFBOTarget, glBackendAttachment, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &objectType); MOBILEGL_ASSERT((objectType == GL_NONE) || (attachmentObject.IsTexture() && objectType == GL_TEXTURE) || (attachmentObject.IsRenderbuffer() && objectType == GL_RENDERBUFFER), "Attachment type not match!"); GLint objectName = 0; g_GLESFuncs.glGetFramebufferAttachmentParameteriv( glFBOTarget, glBackendAttachment, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &objectName); // Verify that the backend object's name and parameters match the frontend attachment state if (attachmentObject.IsTexture()) { const auto& textureObject = attachmentObject.GetTexture(); auto backendTextureIt = TextureImpl::g_backendTextureObjects.find(textureObject.get()); MOBILEGL_ASSERT(backendTextureIt != TextureImpl::g_backendTextureObjects.end(), "No backend texture found while framebuffer reports texture attachment."); GLuint backendTexId = backendTextureIt->second->GetBackendTextureId(); MOBILEGL_ASSERT(static_cast(backendTexId) == objectName, "Attachment texture name mismatch between GLES (%d) and backend texture object " "(%d), frontend texture object ID=%d.", objectName, backendTexId, textureObject->GetExternalIndex()); GLint texLevel = 0; g_GLESFuncs.glGetFramebufferAttachmentParameteriv( glFBOTarget, glBackendAttachment, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, &texLevel); MOBILEGL_ASSERT(texLevel == static_cast(attachmentObject.GetTextureLevel()), "Attachment texture level mismatch between GLES and state object."); } else if (attachmentObject.IsRenderbuffer()) { const auto& renderbufferObject = attachmentObject.GetRenderbuffer(); auto backendRboIt = RenderbufferImpl::g_backendRenderbufferObjects.find(renderbufferObject.get()); MOBILEGL_ASSERT( backendRboIt != RenderbufferImpl::g_backendRenderbufferObjects.end(), "No backend renderbuffer found while framebuffer reports renderbuffer attachment."); GLuint backendRboId = backendRboIt->second->GetBackendRenderbufferId(); MOBILEGL_ASSERT(static_cast(backendRboId) == objectName, "Attachment renderbuffer name mismatch between GLES and state object."); } } #endif } } GLenum BackendFramebufferObject::GetBackendAttachmentType(FramebufferAttachmentType frontendAtt) const { GLenum glBackendReadBuffer = GL_NONE; auto it = std::find(m_frontendDrawBuffers, m_frontendDrawBuffers + FramebufferObject::MAX_DRAW_BUFFERS, frontendAtt); Bool notFound = (it == m_frontendDrawBuffers + FramebufferObject::MAX_DRAW_BUFFERS); if (notFound) { MGLOG_D( "%s: frontendAtt not found in draw buffer (probably not remapped), just use the same as frontend", __func__); glBackendReadBuffer = MG_Util::ConvertFramebufferAttachmentTypeToGLEnum(frontendAtt); } else { MGLOG_D("%s: frontendAtt found in draw buffer, keep it consistent as in read buffers", __func__); auto index = std::distance(m_frontendDrawBuffers, it); glBackendReadBuffer = m_backendDrawBuffers[index]; } return glBackendReadBuffer; } StateBackendObjectRegistry g_backendFramebufferObjects; Array g_fboBindVersions = {0}; } // namespace FramebufferImpl namespace PrgramImpl { StateBackendObjectRegistry g_backendProgramObjects; BackendProgramObjectImpl::BackendProgramObjectImpl() { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif m_backendProgramId = g_GLESFuncs.glCreateProgram(); if (m_backendProgramId == 0) { MGLOG_E("Failed to create program object in backend."); MGLOG_E("ES glGetError(): %s", MG_Util::ConvertGLEnumToString(g_GLESFuncs.glGetError()).c_str()); } else { MGLOG_D("Created backend program object with ID: %u", m_backendProgramId); } } BackendProgramObjectImpl::~BackendProgramObjectImpl() { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif if (m_backendProgramId != 0) { MGLOG_D("Deleting backend program object with ID: %u", m_backendProgramId); g_GLESFuncs.glDeleteProgram(m_backendProgramId); } } void BackendProgramObjectImpl::SyncToBackend( const SharedPtr& stateProgramObject) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif if (!stateProgramObject) { MGLOG_E("State program object is null, skipping backend sync."); return; } if (!stateProgramObject->GetLinkStatus()) { MGLOG_E("Program object is not linked, skipping backend sync. State program ID: %u", stateProgramObject->GetExternalIndex()); return; } MGLOG_D("Syncing program to backend. State program ID: %u, Backend ID: %u", stateProgramObject->GetExternalIndex(), m_backendProgramId); // Detach all existing shaders GLint attachedCount = 0; g_GLESFuncs.glGetProgramiv(m_backendProgramId, GL_ATTACHED_SHADERS, &attachedCount); MGLOG_D("Currently attached shaders count: %d", attachedCount); if (attachedCount > 0) { Vector attachedShaders(attachedCount); GLsizei actualCount; g_GLESFuncs.glGetAttachedShaders(m_backendProgramId, attachedCount, &actualCount, attachedShaders.data()); MGLOG_D("Detaching %d existing shaders from program %u", actualCount, m_backendProgramId); for (GLsizei i = 0; i < actualCount; ++i) { MGLOG_D("Detaching shader ID: %u from program %u", attachedShaders[i], m_backendProgramId); g_GLESFuncs.glDetachShader(m_backendProgramId, attachedShaders[i]); } } // Attach current shaders auto& attachedShaders = stateProgramObject->GetAttachedShaders(); MGLOG_D("Attaching %zu shaders to program %u", attachedShaders.size(), m_backendProgramId); for (auto& shader : attachedShaders) { const auto& src = shader->GetShaderSource(); const auto& stage = MG_Util::ConvertGLEnumToString(MG_Util::ConvertShaderStageToGLEnum(shader->GetShaderStage())); MGLOG_D("Original src @ %s: \n", stage.c_str()); MGLOG_D("%s:", src.empty() ? "" : src.c_str()); } auto& shaderSpirvs = stateProgramObject->GetGeneratedSpirv(); for (int index = 0; index < attachedShaders.size(); ++index) { auto& shader = attachedShaders[index]; GLenum glShaderType = MG_Util::ConvertShaderStageToGLEnum(shader->GetShaderStage()); GLuint backendShaderId = g_GLESFuncs.glCreateShader(glShaderType); if (backendShaderId == 0) { MGLOG_E("Failed to create backend shader for attachment."); continue; } String source; auto& spirvCode = shaderSpirvs[index]; MG_Util::ShaderTranspiler::SpvcSession spvcSession(spirvCode, MG_Util::ShaderTranspiler::SessionUsageBit::Transpile); spvc_compiler_options options; spvcSession.CreateOptions(&options); // TODO: check ESSL version supported by backend driver spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_GLSL_VERSION, 320); spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_GLSL_ES, SPVC_TRUE); spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_GLSL_VULKAN_SEMANTICS, SPVC_FALSE); spvcSession.SetOptions(options); const char* result = nullptr; spvcSession.Compile(&result); if (!result) { MG_Util::ShaderTranspiler::ResultInfo r; r.log += "Failed to compile the shader to GLSL: \n"; r.log += spvcSession.GetLastErrorString(); r.errc = -5; MGLOG_E("%s", r.log.c_str()); continue; } source = result; source = RemoveLayoutBinding(source); source = ProcessOutColorLocations(source); source = ForceSupporterOutput(source); // Patch for Photon compiler precision issue String findStr = "1000000.0"; String replaceStr = "65500.0"; auto pos = source.find(findStr); while (pos != String::npos) { MGLOG_D("Applying patch #2 to Photon..."); source.replace(pos, findStr.length(), replaceStr); pos = source.find(findStr, pos); } const char* sourceCStr = source.c_str(); MGLOG_D("Setting shader source for backend shader ID: %u\nsrc:\n%s", backendShaderId, sourceCStr); g_GLESFuncs.glShaderSource(backendShaderId, 1, &sourceCStr, nullptr); g_GLESFuncs.glCompileShader(backendShaderId); GLint compileStatus; g_GLESFuncs.glGetShaderiv(backendShaderId, GL_COMPILE_STATUS, &compileStatus); if (compileStatus == GL_FALSE) { GLint logLength; g_GLESFuncs.glGetShaderiv(backendShaderId, GL_INFO_LOG_LENGTH, &logLength); Vector log(logLength); g_GLESFuncs.glGetShaderInfoLog(backendShaderId, logLength, nullptr, log.data()); MGLOG_E("Shader compilation failed for backend ID %u: %s", backendShaderId, log.data()); continue; } MGLOG_D("Attaching shader ID: %u to program %u", backendShaderId, m_backendProgramId); g_GLESFuncs.glAttachShader(m_backendProgramId, backendShaderId); MGLOG_D("Processed shader source length: %zu", source.length()); } // Link program MGLOG_D("Linking program %u", m_backendProgramId); g_GLESFuncs.glLinkProgram(m_backendProgramId); GLint linkStatus; g_GLESFuncs.glGetProgramiv(m_backendProgramId, GL_LINK_STATUS, &linkStatus); if (linkStatus != GL_TRUE) { GLint logLength; g_GLESFuncs.glGetProgramiv(m_backendProgramId, GL_INFO_LOG_LENGTH, &logLength); Vector log(logLength); g_GLESFuncs.glGetProgramInfoLog(m_backendProgramId, logLength, nullptr, log.data()); MGLOG_E("Program %u linking failed for %u: %s", stateProgramObject->GetExternalIndex(), m_backendProgramId, log.data()); } else { MGLOG_D("Program linked successfully. ID: %u", m_backendProgramId); } // Create global UBO if (stateProgramObject->GetUBOSize() > 0) { g_GLESFuncs.glGenBuffers(1, &m_backendGlobalUBOId); g_GLESFuncs.glBindBuffer(GL_UNIFORM_BUFFER, m_backendGlobalUBOId); g_GLESFuncs.glBufferData(GL_UNIFORM_BUFFER, stateProgramObject->GetUBOSize(), nullptr, GL_STREAM_DRAW); g_GLESFuncs.glBindBuffer(GL_UNIFORM_BUFFER, 0); } else { m_backendGlobalUBOId = 0; } m_isInitialized = true; MGLOG_D("Program sync completed. backend ID %u", m_backendProgramId); } void BackendProgramObjectImpl::Use() const { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif MGLOG_D("Using program %u", m_backendProgramId); g_GLESFuncs.glUseProgram(m_backendProgramId); } } // namespace PrgramImpl namespace SamplerImpl { BackendSamplerObject::BackendSamplerObject() { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif g_GLESFuncs.glGenSamplers(1, &m_backendSamplerId); if (m_backendSamplerId == 0) { MGLOG_E("Failed to generate sampler object."); MGLOG_E("ES glGetError(): %s", MG_Util::ConvertGLEnumToString(g_GLESFuncs.glGetError()).c_str()); } else { MGLOG_D("Generated sampler object with ID: %u.", m_backendSamplerId); } } void BackendSamplerObject::SyncToBackend( const SharedPtr& stateSamplerObject) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif if (!stateSamplerObject) { MGLOG_E("State sampler object is null, cannot sync to backend."); return; } Uint currentSamplerVersion = stateSamplerObject->GetVersion(); if (m_isInitialized && m_syncedSamplerVersion == currentSamplerVersion) { MGLOG_D("Sampler parameters have not changed for sampler ID: %u, skipping sync.", stateSamplerObject->GetExternalIndex()); return; } m_syncedSamplerVersion = currentSamplerVersion; MGLOG_D("Syncing sampler with backend ID %u to backend for state ID %u", m_backendSamplerId, stateSamplerObject->GetExternalIndex()); const auto& samplerParams = stateSamplerObject->GetAllSamplerParameters(); #define SYNC_SAMPLER_PARAM_IF_CHANGED(internalName, glName, type) \ if (m_cacheSamplerParameters.internalName != samplerParams.internalName) { \ g_GLESFuncs.glSamplerParameteri(m_backendSamplerId, glName, \ (GLint)MG_Util::ConvertSampler##type##ToGLEnum(samplerParams.internalName)); \ m_cacheSamplerParameters.internalName = samplerParams.internalName; \ } if (m_cacheSamplerParameters.minFilter != samplerParams.minFilter || m_cacheSamplerParameters.mipmapMode != samplerParams.mipmapMode) { g_GLESFuncs.glSamplerParameteri(m_backendSamplerId, GL_TEXTURE_MIN_FILTER, (GLint)MG_Util::ConvertSamplerFilterModeToGLEnum( samplerParams.minFilter, samplerParams.mipmapMode)); m_cacheSamplerParameters.minFilter = samplerParams.minFilter; m_cacheSamplerParameters.mipmapMode = samplerParams.mipmapMode; } if (m_cacheSamplerParameters.magFilter != samplerParams.magFilter) { g_GLESFuncs.glSamplerParameteri( m_backendSamplerId, GL_TEXTURE_MAG_FILTER, (GLint)MG_Util::ConvertSamplerFilterModeToGLEnum(samplerParams.magFilter, SamplerMipmapMode::None)); m_cacheSamplerParameters.magFilter = samplerParams.magFilter; } SYNC_SAMPLER_PARAM_IF_CHANGED(wrapS, GL_TEXTURE_WRAP_S, WrapMode) SYNC_SAMPLER_PARAM_IF_CHANGED(wrapT, GL_TEXTURE_WRAP_T, WrapMode) SYNC_SAMPLER_PARAM_IF_CHANGED(wrapR, GL_TEXTURE_WRAP_R, WrapMode) SYNC_SAMPLER_PARAM_IF_CHANGED(compareFunc, GL_TEXTURE_COMPARE_FUNC, CompareFunc) SYNC_SAMPLER_PARAM_IF_CHANGED(compareMode, GL_TEXTURE_COMPARE_MODE, CompareMode) if (m_cacheSamplerParameters.minLod != samplerParams.minLod) { g_GLESFuncs.glSamplerParameterf(m_backendSamplerId, GL_TEXTURE_MIN_LOD, samplerParams.minLod); m_cacheSamplerParameters.minLod = samplerParams.minLod; } if (m_cacheSamplerParameters.maxLod != samplerParams.maxLod) { g_GLESFuncs.glSamplerParameterf(m_backendSamplerId, GL_TEXTURE_MAX_LOD, samplerParams.maxLod); m_cacheSamplerParameters.maxLod = samplerParams.maxLod; } #undef SYNC_SAMPLER_PARAM_IF_CHANGED m_isInitialized = true; } void BackendSamplerObject::Bind(Uint unit) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif if (g_boundSamplersCache[unit] == this) return; g_GLESFuncs.glBindSampler(static_cast(unit), m_backendSamplerId); g_boundSamplersCache[unit] = this; } Uint BackendSamplerObject::GetBackendSamplerId() const { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif return m_backendSamplerId; } void UnbindSampler(Uint unit) { if (g_boundSamplersCache[unit] == nullptr) return; g_GLESFuncs.glBindSampler(static_cast(unit), 0); g_boundSamplersCache[unit] = nullptr; } Array g_boundSamplersCache; StateBackendObjectRegistry g_backendSamplerObjects; } // namespace SamplerImpl namespace RenderbufferImpl { BackendRenderbufferObject::BackendRenderbufferObject() { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif g_GLESFuncs.glGenRenderbuffers(1, &m_backendRBOId); if (m_backendRBOId == 0) { MGLOG_E("Failed to generate renderbuffer object."); MGLOG_E("ES glGetError(): %s", MG_Util::ConvertGLEnumToString(g_GLESFuncs.glGetError()).c_str()); } } void BackendRenderbufferObject::Bind() const { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif g_GLESFuncs.glBindRenderbuffer(GL_RENDERBUFFER, m_backendRBOId); } void BackendRenderbufferObject::SyncToBackend( const SharedPtr& stateRBOObject) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif if (!stateRBOObject) { MGLOG_E("State RBO object is null, cannot sync to backend."); return; } MGLOG_D("Syncing RBO with backend ID %u to backend for state ID %u", m_backendRBOId, stateRBOObject->GetExternalIndex()); if (m_isInitialized && m_cacheInternalFormat == stateRBOObject->GetInternalFormat() && m_cacheWidth == stateRBOObject->GetWidth() && m_cacheHeight == stateRBOObject->GetHeight()) { MGLOG_D("RBO %u already initialized with matching parameters, skipping re-allocation.", stateRBOObject->GetExternalIndex()); return; } Bind(); // Allocate storage TextureInternalFormat internalFormat = stateRBOObject->GetInternalFormat(); Int width = static_cast(stateRBOObject->GetWidth()); Int height = static_cast(stateRBOObject->GetHeight()); GLenum glInternalFormat, glType, glFormat; TextureImpl::GenerateTextureFormatInfo(internalFormat, &glInternalFormat, &glFormat, &glType); g_GLESFuncs.glRenderbufferStorage(GL_RENDERBUFFER, glInternalFormat, static_cast(width), static_cast(height)); m_cacheInternalFormat = internalFormat; m_cacheWidth = width; m_cacheHeight = height; m_isInitialized = true; MGLOG_D("RBO %u sync completed. backend ID %u", stateRBOObject->GetExternalIndex(), m_backendRBOId); } StateBackendObjectRegistry g_backendRenderbufferObjects; } // namespace RenderbufferImpl } // namespace MobileGL::MG_Backend::DirectGLES