// MobileGL - MobileGL/MG_State/EGLState/Core.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 "Core.h" #include namespace MobileGL { namespace MG_State { namespace EGLState { namespace { constexpr EGLint EGL_DISPLAY_MAJOR_VERSION = 1; constexpr EGLint EGL_DISPLAY_MINOR_VERSION = 5; constexpr EGLint DEFAULT_MAX_PBUFFER_SIZE = 16384; template Optional ParseAttribValue(const AttrType* attribList, EGLint attrib) { if (!attribList) { return Nullopt; } for (SizeT i = 0; attribList[i] != EGL_NONE; i += 2) { if (attribList[i] == static_cast(attrib)) { return attribList[i + 1]; } } return Nullopt; } EGLint QueryDefaultX11VisualId() { #if defined(__linux__) && !defined(__ANDROID__) const char* displayName = std::getenv("DISPLAY"); if (!displayName) { return 0; } void* x11Lib = dlopen("libX11.so.6", RTLD_LOCAL | RTLD_NOW); if (!x11Lib) { x11Lib = dlopen("libX11.so", RTLD_LOCAL | RTLD_NOW); } if (!x11Lib) { return 0; } using XOpenDisplayFn = void* (*)(const char*); using XDefaultScreenFn = int (*)(void*); using XDefaultVisualFn = void* (*)(void*, int); using XVisualIDFromVisualFn = unsigned long (*)(void*); using XCloseDisplayFn = int (*)(void*); auto* xOpenDisplay = reinterpret_cast(dlsym(x11Lib, "XOpenDisplay")); auto* xDefaultScreen = reinterpret_cast(dlsym(x11Lib, "XDefaultScreen")); auto* xDefaultVisual = reinterpret_cast(dlsym(x11Lib, "XDefaultVisual")); auto* xVisualIDFromVisual = reinterpret_cast(dlsym(x11Lib, "XVisualIDFromVisual")); auto* xCloseDisplay = reinterpret_cast(dlsym(x11Lib, "XCloseDisplay")); if (!xOpenDisplay || !xDefaultScreen || !xDefaultVisual || !xVisualIDFromVisual || !xCloseDisplay) { dlclose(x11Lib); return 0; } void* display = xOpenDisplay(displayName); if (!display) { dlclose(x11Lib); return 0; } const int screen = xDefaultScreen(display); void* visual = xDefaultVisual(display, screen); const auto visualId = visual ? static_cast(xVisualIDFromVisual(visual)) : 0; xCloseDisplay(display); dlclose(x11Lib); return visualId; #else return 0; #endif } } // namespace Bool EGLContext::DisplayLookupKey::operator==(const DisplayLookupKey& rhs) const { return this->NativeDisplayKey == rhs.NativeDisplayKey && this->Platform == rhs.Platform; } SizeT EGLContext::DisplayLookupHasher::operator()(const DisplayLookupKey& key) const { const auto nativeHash = std::hash{}(key.NativeDisplayKey); const auto platformHash = std::hash{}(static_cast(key.Platform)); return nativeHash ^ (platformHash + 0x9e3779b97f4a7c15ull + (nativeHash << 6) + (nativeHash >> 2)); } std::thread::id EGLContext::CurrentThreadKey() { return std::this_thread::get_id(); } void EGLContext::SetError(EGLint errorCode) { if (errorCode == EGL_SUCCESS) { return; } const std::lock_guard lock(m_mutex); m_threadErrors[CurrentThreadKey()] = errorCode; } EGLint EGLContext::ConsumeError() { const std::lock_guard lock(m_mutex); const auto threadKey = CurrentThreadKey(); auto errorIt = m_threadErrors.find(threadKey); if (errorIt == m_threadErrors.end()) { return EGL_SUCCESS; } const EGLint error = errorIt->second; m_threadErrors.erase(errorIt); return error; } void EGLContext::SetBoundAPI(EGLenum api) { const std::lock_guard lock(m_mutex); m_threadBoundAPI[CurrentThreadKey()] = api; } EGLenum EGLContext::GetBoundAPI() const { const std::lock_guard lock(m_mutex); auto apiIt = m_threadBoundAPI.find(CurrentThreadKey()); if (apiIt == m_threadBoundAPI.end()) { return EGL_OPENGL_API; } return apiIt->second; } EGLContext::EGLDisplayHandle EGLContext::GetOrCreateDisplay(Uint64 nativeDisplayKey, EGLenum platform) { const DisplayLookupKey key = { .NativeDisplayKey = nativeDisplayKey, .Platform = platform, }; auto lookupIt = m_displayLookup.find(key); if (lookupIt != m_displayLookup.end()) { return lookupIt->second; } const auto display = EncodeHandle(m_nextDisplayHandle++); DisplayObject displayObject = { .NativeDisplayKey = nativeDisplayKey, .Platform = platform, .Initialized = false, .MajorVersion = EGL_DISPLAY_MAJOR_VERSION, .MinorVersion = EGL_DISPLAY_MINOR_VERSION, .SwapInterval = 1, }; displayObject.Configs.push_back(CreateDefaultConfig(display, 1, 0)); displayObject.Configs.push_back(CreateDefaultConfig(display, 2, 8)); m_displays[display] = displayObject; m_displayLookup[key] = display; return display; } EGLContext::EGLConfigHandle EGLContext::CreateDefaultConfig(EGLDisplayHandle display, EGLint configId, EGLint stencilSize) { const auto config = EncodeHandle(m_nextConfigHandle++); ConfigObject cfg = { .Display = display, .ConfigId = configId, .RedSize = 8, .GreenSize = 8, .BlueSize = 8, .AlphaSize = 8, .DepthSize = 24, .StencilSize = stencilSize, .SurfaceType = EGL_WINDOW_BIT | EGL_PBUFFER_BIT | EGL_PIXMAP_BIT, .RenderableType = EGL_OPENGL_BIT | EGL_OPENGL_ES2_BIT | EGL_OPENGL_ES3_BIT, .MinSwapInterval = 0, .MaxSwapInterval = 4, .NativeVisualId = QueryDefaultX11VisualId(), }; #if defined(ANDROID) || defined(__ANDROID__) cfg.NativeVisualId = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM; #endif m_configs[config] = cfg; return config; } EGLContext::DisplayObject* EGLContext::TryGetDisplay(EGLDisplayHandle display) { auto it = m_displays.find(display); return it == m_displays.end() ? nullptr : &it->second; } const EGLContext::DisplayObject* EGLContext::TryGetDisplay(EGLDisplayHandle display) const { auto it = m_displays.find(display); return it == m_displays.end() ? nullptr : &it->second; } const EGLContext::ConfigObject* EGLContext::TryGetConfig(EGLConfigHandle config) const { auto it = m_configs.find(config); return it == m_configs.end() ? nullptr : &it->second; } const EGLContext::ContextObject* EGLContext::TryGetContext(EGLContextHandle context) const { auto it = m_contexts.find(context); return it == m_contexts.end() ? nullptr : &it->second; } const EGLContext::SurfaceObject* EGLContext::TryGetSurface(EGLSurfaceHandle surface) const { auto it = m_surfaces.find(surface); return it == m_surfaces.end() ? nullptr : &it->second; } const EGLContext::SyncObject* EGLContext::TryGetSync(EGLSyncHandle sync) const { auto it = m_syncs.find(sync); return it == m_syncs.end() ? nullptr : &it->second; } const EGLContext::ImageObject* EGLContext::TryGetImage(EGLImageHandle image) const { auto it = m_images.find(image); return it == m_images.end() ? nullptr : &it->second; } void EGLContext::ReleaseThreadUnlocked(const std::thread::id& threadKey) { auto currentIt = m_threadCurrents.find(threadKey); if (currentIt == m_threadCurrents.end()) { return; } const EGLSurfaceHandle drawSurface = currentIt->second.DrawSurface; const EGLSurfaceHandle readSurface = currentIt->second.ReadSurface; const EGLContextHandle context = currentIt->second.Context; if (context != nullptr) { auto ownerIt = m_contextOwners.find(context); if (ownerIt != m_contextOwners.end() && ownerIt->second == threadKey) { m_contextOwners.erase(ownerIt); } } m_threadCurrents.erase(currentIt); DestroyPendingSurfaceIfUnused(drawSurface); DestroyPendingSurfaceIfUnused(readSurface); } Bool EGLContext::IsSurfaceCurrentUnlocked(EGLSurfaceHandle surface) const { if (surface == EGL_NO_SURFACE) { return false; } for (const auto& current : m_threadCurrents) { if (current.second.DrawSurface == surface || current.second.ReadSurface == surface) { return true; } } return false; } void EGLContext::DestroyPendingSurfaceIfUnused(EGLSurfaceHandle surface) { auto surfaceIt = m_surfaces.find(surface); if (surfaceIt == m_surfaces.end() || !surfaceIt->second.DestroyPending || IsSurfaceCurrentUnlocked(surface)) { return; } m_surfaces.erase(surfaceIt); } void EGLContext::ReleaseDisplayObjects(EGLDisplayHandle display) { Vector removedContexts; for (auto contextIt = m_contexts.begin(); contextIt != m_contexts.end();) { if (contextIt->second.Display == display) { removedContexts.push_back(contextIt->first); contextIt = m_contexts.erase(contextIt); } else { ++contextIt; } } for (const auto context : removedContexts) { m_contextOwners.erase(context); } for (auto surfaceIt = m_surfaces.begin(); surfaceIt != m_surfaces.end();) { if (surfaceIt->second.Display == display) { surfaceIt = m_surfaces.erase(surfaceIt); } else { ++surfaceIt; } } for (auto syncIt = m_syncs.begin(); syncIt != m_syncs.end();) { if (syncIt->second.Display == display) { syncIt = m_syncs.erase(syncIt); } else { ++syncIt; } } for (auto imageIt = m_images.begin(); imageIt != m_images.end();) { if (imageIt->second.Display == display) { imageIt = m_images.erase(imageIt); } else { ++imageIt; } } for (auto threadIt = m_threadCurrents.begin(); threadIt != m_threadCurrents.end();) { if (threadIt->second.Display == display) { if (threadIt->second.Context != nullptr) { auto ownerIt = m_contextOwners.find(threadIt->second.Context); if (ownerIt != m_contextOwners.end() && ownerIt->second == threadIt->first) { m_contextOwners.erase(ownerIt); } } threadIt = m_threadCurrents.erase(threadIt); } else { ++threadIt; } } } EGLContext::EGLDisplayHandle EGLContext::GetDisplay(NativeDisplayType nativeDisplay) { const std::lock_guard lock(m_mutex); return GetOrCreateDisplay(ToNativeKey(nativeDisplay), EGL_NONE); } EGLContext::EGLDisplayHandle EGLContext::GetPlatformDisplay(EGLenum platform, void* nativeDisplay) { const std::lock_guard lock(m_mutex); return GetOrCreateDisplay(ToNativeKey(nativeDisplay), platform); } Bool EGLContext::ValidateDisplay(EGLDisplayHandle display) const { const std::lock_guard lock(m_mutex); return m_displays.find(display) != m_displays.end(); } Bool EGLContext::IsDisplayInitialized(EGLDisplayHandle display) const { const std::lock_guard lock(m_mutex); const auto* displayObject = TryGetDisplay(display); return displayObject && displayObject->Initialized; } Bool EGLContext::InitializeDisplay(EGLDisplayHandle display, EGLint* major, EGLint* minor) { const std::lock_guard lock(m_mutex); auto* displayObject = TryGetDisplay(display); if (!displayObject) { SetError(EGL_BAD_DISPLAY); return false; } displayObject->Initialized = true; displayObject->MajorVersion = EGL_DISPLAY_MAJOR_VERSION; displayObject->MinorVersion = EGL_DISPLAY_MINOR_VERSION; if (major) { *major = displayObject->MajorVersion; } if (minor) { *minor = displayObject->MinorVersion; } return true; } Bool EGLContext::TerminateDisplay(EGLDisplayHandle display) { const std::lock_guard lock(m_mutex); auto* displayObject = TryGetDisplay(display); if (!displayObject) { SetError(EGL_BAD_DISPLAY); return false; } ReleaseDisplayObjects(display); displayObject->Initialized = false; return true; } Bool EGLContext::HasAnyInitializedDisplay() const { const std::lock_guard lock(m_mutex); for (const auto& [handle, displayObject] : m_displays) { if (displayObject.Initialized) { return true; } } return false; } Bool EGLContext::HasAnyCurrentContext() const { const std::lock_guard lock(m_mutex); for (const auto& [threadId, current] : m_threadCurrents) { if (current.Context != nullptr) { return true; } } return false; } Bool EGLContext::ChooseConfig(EGLDisplayHandle display, const EGLint* attribList, EGLConfigHandle* configs, EGLint configSize, EGLint* numConfig) { const std::lock_guard lock(m_mutex); auto* displayObject = TryGetDisplay(display); if (!displayObject) { SetError(EGL_BAD_DISPLAY); return false; } if (!displayObject->Initialized) { SetError(EGL_NOT_INITIALIZED); return false; } if (!numConfig) { SetError(EGL_BAD_PARAMETER); return false; } Vector matchedConfigs; for (const auto config : displayObject->Configs) { const auto* cfg = TryGetConfig(config); if (!cfg) { continue; } Bool matches = true; if (attribList) { for (SizeT i = 0; attribList[i] != EGL_NONE; i += 2) { const EGLint attr = attribList[i]; const EGLint requested = attribList[i + 1]; if (requested == EGL_DONT_CARE) { continue; } switch (attr) { case EGL_CONFIG_ID: matches = cfg->ConfigId == requested; break; case EGL_RED_SIZE: matches = cfg->RedSize >= requested; break; case EGL_GREEN_SIZE: matches = cfg->GreenSize >= requested; break; case EGL_BLUE_SIZE: matches = cfg->BlueSize >= requested; break; case EGL_ALPHA_SIZE: matches = cfg->AlphaSize >= requested; break; case EGL_BUFFER_SIZE: matches = (cfg->RedSize + cfg->GreenSize + cfg->BlueSize + cfg->AlphaSize) >= requested; break; case EGL_DEPTH_SIZE: matches = cfg->DepthSize >= requested; break; case EGL_STENCIL_SIZE: matches = cfg->StencilSize >= requested; break; case EGL_SURFACE_TYPE: matches = (cfg->SurfaceType & requested) == requested; break; case EGL_RENDERABLE_TYPE: case EGL_CONFORMANT: matches = (cfg->RenderableType & requested) == requested; break; case EGL_SAMPLE_BUFFERS: case EGL_SAMPLES: matches = requested == 0; break; case EGL_NATIVE_VISUAL_ID: matches = cfg->NativeVisualId == requested; break; default: break; } if (!matches) { break; } } } if (matches) { matchedConfigs.push_back(config); } } *numConfig = static_cast(matchedConfigs.size()); if (!configs || configSize <= 0) { return true; } const SizeT copyCount = std::min(static_cast(configSize), matchedConfigs.size()); for (SizeT i = 0; i < copyCount; ++i) { configs[i] = matchedConfigs[i]; } return true; } Bool EGLContext::GetConfigs(EGLDisplayHandle display, EGLConfigHandle* configs, EGLint configSize, EGLint* numConfig) { const std::lock_guard lock(m_mutex); return ChooseConfig(display, nullptr, configs, configSize, numConfig); } Bool EGLContext::ValidateConfig(EGLConfigHandle config) const { const std::lock_guard lock(m_mutex); return m_configs.find(config) != m_configs.end(); } Bool EGLContext::ValidateConfigOnDisplay(EGLDisplayHandle display, EGLConfigHandle config) const { const std::lock_guard lock(m_mutex); const auto* cfg = TryGetConfig(config); return cfg && cfg->Display == display; } Bool EGLContext::GetConfigAttrib(EGLDisplayHandle display, EGLConfigHandle config, EGLint attribute, EGLint* value) const { const std::lock_guard lock(m_mutex); if (!value) { const_cast(this)->SetError(EGL_BAD_PARAMETER); return false; } const auto* cfg = TryGetConfig(config); if (!cfg) { const_cast(this)->SetError(EGL_BAD_CONFIG); return false; } if (cfg->Display != display) { const_cast(this)->SetError(EGL_BAD_MATCH); return false; } switch (attribute) { case EGL_BUFFER_SIZE: *value = cfg->RedSize + cfg->GreenSize + cfg->BlueSize + cfg->AlphaSize; return true; case EGL_ALPHA_MASK_SIZE: *value = 0; return true; case EGL_BIND_TO_TEXTURE_RGB: case EGL_BIND_TO_TEXTURE_RGBA: *value = EGL_FALSE; return true; case EGL_COLOR_BUFFER_TYPE: *value = EGL_RGB_BUFFER; return true; case EGL_CONFIG_CAVEAT: *value = EGL_NONE; return true; case EGL_CONFIG_ID: *value = cfg->ConfigId; return true; case EGL_LEVEL: *value = 0; return true; case EGL_LUMINANCE_SIZE: *value = 0; return true; case EGL_MAX_PBUFFER_WIDTH: case EGL_MAX_PBUFFER_HEIGHT: *value = DEFAULT_MAX_PBUFFER_SIZE; return true; case EGL_MAX_PBUFFER_PIXELS: *value = DEFAULT_MAX_PBUFFER_SIZE * DEFAULT_MAX_PBUFFER_SIZE; return true; case EGL_RED_SIZE: *value = cfg->RedSize; return true; case EGL_GREEN_SIZE: *value = cfg->GreenSize; return true; case EGL_BLUE_SIZE: *value = cfg->BlueSize; return true; case EGL_ALPHA_SIZE: *value = cfg->AlphaSize; return true; case EGL_DEPTH_SIZE: *value = cfg->DepthSize; return true; case EGL_STENCIL_SIZE: *value = cfg->StencilSize; return true; case EGL_SURFACE_TYPE: *value = cfg->SurfaceType; return true; case EGL_RENDERABLE_TYPE: case EGL_CONFORMANT: *value = cfg->RenderableType; return true; case EGL_MIN_SWAP_INTERVAL: *value = cfg->MinSwapInterval; return true; case EGL_MAX_SWAP_INTERVAL: *value = cfg->MaxSwapInterval; return true; case EGL_NATIVE_RENDERABLE: *value = EGL_TRUE; return true; case EGL_NATIVE_VISUAL_ID: *value = cfg->NativeVisualId; return true; case EGL_NATIVE_VISUAL_TYPE: *value = cfg->NativeVisualId; return true; case EGL_SAMPLE_BUFFERS: case EGL_SAMPLES: *value = 0; return true; case EGL_TRANSPARENT_TYPE: *value = EGL_NONE; return true; case EGL_TRANSPARENT_RED_VALUE: case EGL_TRANSPARENT_GREEN_VALUE: case EGL_TRANSPARENT_BLUE_VALUE: *value = 0; return true; default: const_cast(this)->SetError(EGL_BAD_ATTRIBUTE); return false; } } EGLContext::EGLContextHandle EGLContext::CreateContext(EGLDisplayHandle display, EGLConfigHandle config, EGLContextHandle shareCtx, const EGLint* attribList) { const std::lock_guard lock(m_mutex); const auto* displayObject = TryGetDisplay(display); if (!displayObject) { SetError(EGL_BAD_DISPLAY); return nullptr; } if (!displayObject->Initialized) { SetError(EGL_NOT_INITIALIZED); return nullptr; } if (!ValidateConfigOnDisplay(display, config)) { SetError(EGL_BAD_CONFIG); return nullptr; } if (shareCtx != nullptr && !ValidateContextOnDisplay(display, shareCtx)) { SetError(EGL_BAD_CONTEXT); return nullptr; } ContextObject contextObject = { .Display = display, .Config = config, .SharedContext = shareCtx, .ClientAPI = GetBoundAPI(), .ClientVersion = 1, .MajorVersion = 1, .MinorVersion = 0, }; if (auto value = ParseAttribValue(attribList, EGL_CONTEXT_CLIENT_VERSION); value) { contextObject.ClientVersion = *value; contextObject.MajorVersion = *value; } if (auto value = ParseAttribValue(attribList, EGL_CONTEXT_MAJOR_VERSION); value) { contextObject.MajorVersion = *value; } if (auto value = ParseAttribValue(attribList, EGL_CONTEXT_MINOR_VERSION); value) { contextObject.MinorVersion = *value; } if (auto value = ParseAttribValue(attribList, EGL_CONTEXT_OPENGL_PROFILE_MASK); value) { contextObject.OpenGLProfileMask = *value; } if (auto value = ParseAttribValue(attribList, EGL_CONTEXT_FLAGS_KHR); value) { contextObject.EGLContextFlags = *value; if (*value & EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR) { contextObject.OpenGLContextFlags |= GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT; } if (*value & EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR) { contextObject.OpenGLContextFlags |= GL_CONTEXT_FLAG_DEBUG_BIT; } if (*value & EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR) { contextObject.OpenGLContextFlags |= GL_CONTEXT_FLAG_ROBUST_ACCESS_BIT; } } if (auto value = ParseAttribValue(attribList, EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE); value && *value == EGL_TRUE) { contextObject.EGLContextFlags |= EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR; contextObject.OpenGLContextFlags |= GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT; } if (auto value = ParseAttribValue(attribList, EGL_CONTEXT_OPENGL_DEBUG); value && *value == EGL_TRUE) { contextObject.EGLContextFlags |= EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR; contextObject.OpenGLContextFlags |= GL_CONTEXT_FLAG_DEBUG_BIT; } if (auto value = ParseAttribValue(attribList, EGL_CONTEXT_OPENGL_ROBUST_ACCESS); value && *value == EGL_TRUE) { contextObject.EGLContextFlags |= EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR; contextObject.OpenGLContextFlags |= GL_CONTEXT_FLAG_ROBUST_ACCESS_BIT; } const auto context = EncodeHandle(m_nextContextHandle++); m_contexts[context] = contextObject; return context; } Bool EGLContext::DestroyContext(EGLDisplayHandle display, EGLContextHandle context) { const std::lock_guard lock(m_mutex); auto contextIt = m_contexts.find(context); if (contextIt == m_contexts.end()) { SetError(EGL_BAD_CONTEXT); return false; } if (contextIt->second.Display != display) { SetError(EGL_BAD_MATCH); return false; } auto ownerIt = m_contextOwners.find(context); if (ownerIt != m_contextOwners.end()) { SetError(EGL_BAD_ACCESS); return false; } m_contexts.erase(contextIt); return true; } Bool EGLContext::QueryContext(EGLDisplayHandle display, EGLContextHandle context, EGLint attribute, EGLint* value) const { const std::lock_guard lock(m_mutex); if (!value) { const_cast(this)->SetError(EGL_BAD_PARAMETER); return false; } const auto* contextObject = TryGetContext(context); if (!contextObject) { const_cast(this)->SetError(EGL_BAD_CONTEXT); return false; } if (contextObject->Display != display) { const_cast(this)->SetError(EGL_BAD_MATCH); return false; } switch (attribute) { case EGL_CONTEXT_CLIENT_TYPE: *value = static_cast(contextObject->ClientAPI); return true; case EGL_CONTEXT_CLIENT_VERSION: #if EGL_CONTEXT_MAJOR_VERSION != EGL_CONTEXT_CLIENT_VERSION case EGL_CONTEXT_MAJOR_VERSION: #endif *value = contextObject->MajorVersion; return true; case EGL_CONTEXT_MINOR_VERSION: *value = contextObject->MinorVersion; return true; case EGL_CONTEXT_FLAGS_KHR: *value = contextObject->EGLContextFlags; return true; case EGL_CONFIG_ID: { const auto* cfg = TryGetConfig(contextObject->Config); if (!cfg) { const_cast(this)->SetError(EGL_BAD_CONFIG); return false; } *value = cfg->ConfigId; return true; } default: const_cast(this)->SetError(EGL_BAD_ATTRIBUTE); return false; } } Bool EGLContext::ValidateContext(EGLContextHandle context) const { const std::lock_guard lock(m_mutex); return m_contexts.find(context) != m_contexts.end(); } Bool EGLContext::ValidateContextOnDisplay(EGLDisplayHandle display, EGLContextHandle context) const { const std::lock_guard lock(m_mutex); const auto* ctx = TryGetContext(context); return ctx && ctx->Display == display; } Bool EGLContext::IsCurrentContextOpenGLCoreProfile() const { const std::lock_guard lock(m_mutex); auto currentIt = m_threadCurrents.find(CurrentThreadKey()); if (currentIt == m_threadCurrents.end()) { return false; } const auto* ctx = TryGetContext(currentIt->second.Context); if (!ctx || ctx->ClientAPI != EGL_OPENGL_API || (ctx->OpenGLProfileMask & EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT)) { return false; } return (ctx->OpenGLProfileMask & EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT) || ctx->MajorVersion > 3 || (ctx->MajorVersion == 3 && ctx->MinorVersion >= 1); } Bool EGLContext::IsCurrentContextOpenGLCompatibilityProfile() const { const std::lock_guard lock(m_mutex); auto currentIt = m_threadCurrents.find(CurrentThreadKey()); if (currentIt == m_threadCurrents.end()) { return false; } const auto* ctx = TryGetContext(currentIt->second.Context); // Affirmative check: true only when the host explicitly requested the // compatibility bit. Attrib-less contexts report false and thus read as core // for GL_CONTEXT_PROFILE_MASK (EGL defaults 3.x contexts to the core profile). return ctx && ctx->ClientAPI == EGL_OPENGL_API && (ctx->OpenGLProfileMask & EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT); } EGLint EGLContext::GetCurrentContextFlags() const { const std::lock_guard lock(m_mutex); auto currentIt = m_threadCurrents.find(CurrentThreadKey()); if (currentIt == m_threadCurrents.end()) { return 0; } const auto* ctx = TryGetContext(currentIt->second.Context); return ctx ? ctx->OpenGLContextFlags : 0; } EGLContext::EGLSurfaceHandle EGLContext::CreateWindowSurface(EGLDisplayHandle display, EGLConfigHandle config, NativeWindowType window, const EGLint* attribList) { const std::lock_guard lock(m_mutex); (void)attribList; if (!IsDisplayInitialized(display)) { SetError(EGL_NOT_INITIALIZED); return EGL_NO_SURFACE; } if (!ValidateConfigOnDisplay(display, config)) { SetError(EGL_BAD_CONFIG); return EGL_NO_SURFACE; } if (ToNativeKey(window) == 0) { SetError(EGL_BAD_NATIVE_WINDOW); return EGL_NO_SURFACE; } const auto surface = EncodeHandle(m_nextSurfaceHandle++); m_surfaces[surface] = SurfaceObject{ .Display = display, .Config = config, .Type = SurfaceType::Window, .NativeHandleKey = ToNativeKey(window), .ClientBuffer = nullptr, .BufferType = EGL_NONE, }; return surface; } EGLContext::EGLSurfaceHandle EGLContext::CreatePbufferSurface(EGLDisplayHandle display, EGLConfigHandle config, const EGLint* attribList) { const std::lock_guard lock(m_mutex); if (!IsDisplayInitialized(display)) { SetError(EGL_NOT_INITIALIZED); return EGL_NO_SURFACE; } if (!ValidateConfigOnDisplay(display, config)) { SetError(EGL_BAD_CONFIG); return EGL_NO_SURFACE; } EGLint width = 1; EGLint height = 1; if (auto value = ParseAttribValue(attribList, EGL_WIDTH); value) { width = *value; } if (auto value = ParseAttribValue(attribList, EGL_HEIGHT); value) { height = *value; } if (width < 0 || height < 0) { SetError(EGL_BAD_ATTRIBUTE); return EGL_NO_SURFACE; } const auto surface = EncodeHandle(m_nextSurfaceHandle++); m_surfaces[surface] = SurfaceObject{ .Display = display, .Config = config, .Type = SurfaceType::Pbuffer, .NativeHandleKey = 0, .ClientBuffer = nullptr, .BufferType = EGL_NONE, .Width = width, .Height = height, .TextureFormat = ParseAttribValue(attribList, EGL_TEXTURE_FORMAT).value_or(EGL_NO_TEXTURE), .TextureTarget = ParseAttribValue(attribList, EGL_TEXTURE_TARGET).value_or(EGL_NO_TEXTURE), .MipmapLevel = ParseAttribValue(attribList, EGL_MIPMAP_LEVEL).value_or(0), .MipmapTexture = ParseAttribValue(attribList, EGL_MIPMAP_TEXTURE).value_or(EGL_FALSE), }; return surface; } EGLContext::EGLSurfaceHandle EGLContext::CreatePixmapSurface(EGLDisplayHandle display, EGLConfigHandle config, EGLNativePixmapType pixmap, const EGLint* attribList) { const std::lock_guard lock(m_mutex); (void)attribList; if (!IsDisplayInitialized(display)) { SetError(EGL_NOT_INITIALIZED); return EGL_NO_SURFACE; } if (!ValidateConfigOnDisplay(display, config)) { SetError(EGL_BAD_CONFIG); return EGL_NO_SURFACE; } if (ToNativeKey(pixmap) == 0) { SetError(EGL_BAD_NATIVE_PIXMAP); return EGL_NO_SURFACE; } const auto surface = EncodeHandle(m_nextSurfaceHandle++); m_surfaces[surface] = SurfaceObject{ .Display = display, .Config = config, .Type = SurfaceType::Pixmap, .NativeHandleKey = ToNativeKey(pixmap), .ClientBuffer = nullptr, .BufferType = EGL_NONE, }; return surface; } EGLContext::EGLSurfaceHandle EGLContext::CreatePbufferFromClientBuffer(EGLDisplayHandle display, EGLenum bufferType, EGLClientBuffer buffer, EGLConfigHandle config, const EGLint* attribList) { const std::lock_guard lock(m_mutex); if (!IsDisplayInitialized(display)) { SetError(EGL_NOT_INITIALIZED); return EGL_NO_SURFACE; } if (!ValidateConfigOnDisplay(display, config)) { SetError(EGL_BAD_CONFIG); return EGL_NO_SURFACE; } if (!buffer) { SetError(EGL_BAD_PARAMETER); return EGL_NO_SURFACE; } EGLint width = 1; EGLint height = 1; if (auto value = ParseAttribValue(attribList, EGL_WIDTH); value) { width = *value; } if (auto value = ParseAttribValue(attribList, EGL_HEIGHT); value) { height = *value; } const auto surface = EncodeHandle(m_nextSurfaceHandle++); m_surfaces[surface] = SurfaceObject{ .Display = display, .Config = config, .Type = SurfaceType::PbufferFromClientBuffer, .NativeHandleKey = 0, .ClientBuffer = buffer, .BufferType = bufferType, .Width = width, .Height = height, }; return surface; } EGLContext::EGLSurfaceHandle EGLContext::CreatePlatformWindowSurface(EGLDisplayHandle display, EGLConfigHandle config, void* nativeWindow, const EGLAttrib* attribList) { const std::lock_guard lock(m_mutex); if (!IsDisplayInitialized(display)) { SetError(EGL_NOT_INITIALIZED); return EGL_NO_SURFACE; } if (!ValidateConfigOnDisplay(display, config)) { SetError(EGL_BAD_CONFIG); return EGL_NO_SURFACE; } if (ToNativeKey(nativeWindow) == 0) { SetError(EGL_BAD_NATIVE_WINDOW); return EGL_NO_SURFACE; } const auto surface = EncodeHandle(m_nextSurfaceHandle++); m_surfaces[surface] = SurfaceObject{ .Display = display, .Config = config, .Type = SurfaceType::PlatformWindow, .NativeHandleKey = ToNativeKey(nativeWindow), .ClientBuffer = nullptr, .BufferType = EGL_NONE, .Width = static_cast(ParseAttribValue(attribList, EGL_WIDTH).value_or(0)), .Height = static_cast(ParseAttribValue(attribList, EGL_HEIGHT).value_or(0)), }; return surface; } EGLContext::EGLSurfaceHandle EGLContext::CreatePlatformPixmapSurface(EGLDisplayHandle display, EGLConfigHandle config, void* nativePixmap, const EGLAttrib* attribList) { const std::lock_guard lock(m_mutex); (void)attribList; if (!IsDisplayInitialized(display)) { SetError(EGL_NOT_INITIALIZED); return EGL_NO_SURFACE; } if (!ValidateConfigOnDisplay(display, config)) { SetError(EGL_BAD_CONFIG); return EGL_NO_SURFACE; } if (ToNativeKey(nativePixmap) == 0) { SetError(EGL_BAD_NATIVE_PIXMAP); return EGL_NO_SURFACE; } const auto surface = EncodeHandle(m_nextSurfaceHandle++); m_surfaces[surface] = SurfaceObject{ .Display = display, .Config = config, .Type = SurfaceType::PlatformPixmap, .NativeHandleKey = ToNativeKey(nativePixmap), .ClientBuffer = nullptr, .BufferType = EGL_NONE, }; return surface; } Bool EGLContext::DestroySurface(EGLDisplayHandle display, EGLSurfaceHandle surface) { const std::lock_guard lock(m_mutex); auto surfaceIt = m_surfaces.find(surface); if (surfaceIt == m_surfaces.end()) { SetError(EGL_BAD_SURFACE); return false; } if (surfaceIt->second.Display != display) { SetError(EGL_BAD_MATCH); return false; } if (IsSurfaceCurrentUnlocked(surface)) { surfaceIt->second.DestroyPending = true; return true; } m_surfaces.erase(surfaceIt); return true; } Bool EGLContext::ResizeSurface(EGLDisplayHandle display, EGLSurfaceHandle surface, EGLint width, EGLint height) { const std::lock_guard lock(m_mutex); auto surfaceIt = m_surfaces.find(surface); if (surfaceIt == m_surfaces.end()) { SetError(EGL_BAD_SURFACE); return false; } if (surfaceIt->second.Display != display) { SetError(EGL_BAD_MATCH); return false; } surfaceIt->second.Width = std::max(width, 1); surfaceIt->second.Height = std::max(height, 1); return true; } Bool EGLContext::QuerySurface(EGLDisplayHandle display, EGLSurfaceHandle surface, EGLint attribute, EGLint* value) const { const std::lock_guard lock(m_mutex); if (!value) { const_cast(this)->SetError(EGL_BAD_PARAMETER); return false; } const auto* surfaceObject = TryGetSurface(surface); if (!surfaceObject) { const_cast(this)->SetError(EGL_BAD_SURFACE); return false; } if (surfaceObject->Display != display) { const_cast(this)->SetError(EGL_BAD_MATCH); return false; } switch (attribute) { case EGL_WIDTH: *value = surfaceObject->Width; return true; case EGL_HEIGHT: *value = surfaceObject->Height; return true; case EGL_TEXTURE_FORMAT: *value = surfaceObject->TextureFormat; return true; case EGL_TEXTURE_TARGET: *value = surfaceObject->TextureTarget; return true; case EGL_MIPMAP_LEVEL: *value = surfaceObject->MipmapLevel; return true; case EGL_MIPMAP_TEXTURE: *value = surfaceObject->MipmapTexture; return true; case EGL_RENDER_BUFFER: *value = surfaceObject->RenderBuffer; return true; case EGL_SWAP_BEHAVIOR: *value = surfaceObject->SwapBehavior; return true; case EGL_CONFIG_ID: { const auto* cfg = TryGetConfig(surfaceObject->Config); if (!cfg) { const_cast(this)->SetError(EGL_BAD_CONFIG); return false; } *value = cfg->ConfigId; return true; } default: const_cast(this)->SetError(EGL_BAD_ATTRIBUTE); return false; } } Bool EGLContext::ValidateSurface(EGLSurfaceHandle surface) const { const std::lock_guard lock(m_mutex); return m_surfaces.find(surface) != m_surfaces.end(); } Bool EGLContext::ValidateSurfaceOnDisplay(EGLDisplayHandle display, EGLSurfaceHandle surface) const { const std::lock_guard lock(m_mutex); const auto* surf = TryGetSurface(surface); return surf && surf->Display == display; } Bool EGLContext::SwapInterval(EGLDisplayHandle display, EGLint interval) { const std::lock_guard lock(m_mutex); auto* displayObject = TryGetDisplay(display); if (!displayObject) { SetError(EGL_BAD_DISPLAY); return false; } if (!displayObject->Initialized) { SetError(EGL_NOT_INITIALIZED); return false; } if (displayObject->Configs.empty()) { SetError(EGL_BAD_CONFIG); return false; } const auto* cfg = TryGetConfig(displayObject->Configs.front()); if (!cfg) { SetError(EGL_BAD_CONFIG); return false; } if (interval < cfg->MinSwapInterval || interval > cfg->MaxSwapInterval) { SetError(EGL_BAD_PARAMETER); return false; } displayObject->SwapInterval = interval; return true; } Bool EGLContext::MakeCurrent(EGLDisplayHandle display, EGLSurfaceHandle draw, EGLSurfaceHandle read, EGLContextHandle context) { const std::lock_guard lock(m_mutex); const Bool releaseCurrentRequest = draw == EGL_NO_SURFACE && read == EGL_NO_SURFACE && context == nullptr; const auto threadKey = CurrentThreadKey(); if (releaseCurrentRequest) { if (display != EGL_NO_DISPLAY) { auto* displayObject = TryGetDisplay(display); if (!displayObject) { SetError(EGL_BAD_DISPLAY); return false; } if (!displayObject->Initialized) { SetError(EGL_NOT_INITIALIZED); return false; } } ReleaseThreadUnlocked(threadKey); return true; } auto* displayObject = TryGetDisplay(display); if (!displayObject) { SetError(EGL_BAD_DISPLAY); return false; } if (!displayObject->Initialized) { SetError(EGL_NOT_INITIALIZED); return false; } if (context != nullptr) { if (!ValidateContextOnDisplay(display, context)) { SetError(EGL_BAD_CONTEXT); return false; } if (draw == EGL_NO_SURFACE || read == EGL_NO_SURFACE) { SetError(EGL_BAD_MATCH); return false; } } else { if (draw != EGL_NO_SURFACE || read != EGL_NO_SURFACE) { SetError(EGL_BAD_MATCH); return false; } } if (draw != EGL_NO_SURFACE && !ValidateSurfaceOnDisplay(display, draw)) { SetError(EGL_BAD_SURFACE); return false; } if (read != EGL_NO_SURFACE && !ValidateSurfaceOnDisplay(display, read)) { SetError(EGL_BAD_SURFACE); return false; } if (context != nullptr) { auto ownerIt = m_contextOwners.find(context); if (ownerIt != m_contextOwners.end() && ownerIt->second != threadKey) { SetError(EGL_BAD_ACCESS); return false; } } ReleaseThreadUnlocked(threadKey); m_threadCurrents[threadKey] = ThreadCurrentState{ .Display = display, .DrawSurface = draw, .ReadSurface = read, .Context = context, }; if (context != nullptr) { m_contextOwners[context] = threadKey; } return true; } void EGLContext::ReleaseThread() { const std::lock_guard lock(m_mutex); ReleaseThreadUnlocked(CurrentThreadKey()); } EGLContext::EGLContextHandle EGLContext::GetCurrentContext() const { const std::lock_guard lock(m_mutex); auto currentIt = m_threadCurrents.find(CurrentThreadKey()); if (currentIt == m_threadCurrents.end()) { return nullptr; } return currentIt->second.Context; } EGLContext::EGLDisplayHandle EGLContext::GetCurrentDisplay() const { const std::lock_guard lock(m_mutex); auto currentIt = m_threadCurrents.find(CurrentThreadKey()); if (currentIt == m_threadCurrents.end()) { return EGL_NO_DISPLAY; } return currentIt->second.Display; } EGLContext::EGLSurfaceHandle EGLContext::GetCurrentSurface(EGLint readdraw) const { const std::lock_guard lock(m_mutex); auto currentIt = m_threadCurrents.find(CurrentThreadKey()); if (currentIt == m_threadCurrents.end()) { return EGL_NO_SURFACE; } if (readdraw == EGL_DRAW) { return currentIt->second.DrawSurface; } if (readdraw == EGL_READ) { return currentIt->second.ReadSurface; } return EGL_NO_SURFACE; } Bool EGLContext::IsDoubleBufferedSurface(EGLSurfaceHandle surface) const { const std::lock_guard lock(m_mutex); const auto* surfaceObject = TryGetSurface(surface); if (!surfaceObject) { return false; } if (surfaceObject->RenderBuffer != EGL_BACK_BUFFER) { return false; } return surfaceObject->Type == SurfaceType::Window || surfaceObject->Type == SurfaceType::PlatformWindow; } EGLContext::EGLSyncHandle EGLContext::CreateSync(EGLDisplayHandle display, EGLenum type, const EGLAttrib* attribList) { const std::lock_guard lock(m_mutex); if (!IsDisplayInitialized(display)) { SetError(EGL_NOT_INITIALIZED); return EGL_NO_SYNC; } if (type != EGL_SYNC_FENCE) { SetError(EGL_BAD_ATTRIBUTE); return EGL_NO_SYNC; } SyncObject sync = { .Display = display, .Type = type, .Condition = EGL_SYNC_PRIOR_COMMANDS_COMPLETE, .Status = EGL_SIGNALED, }; if (auto value = ParseAttribValue(attribList, EGL_SYNC_CONDITION); value) { sync.Condition = static_cast(*value); } const auto syncHandle = EncodeHandle(m_nextSyncHandle++); m_syncs[syncHandle] = sync; return syncHandle; } Bool EGLContext::DestroySync(EGLDisplayHandle display, EGLSyncHandle sync) { const std::lock_guard lock(m_mutex); auto syncIt = m_syncs.find(sync); if (syncIt == m_syncs.end()) { SetError(EGL_BAD_PARAMETER); return false; } if (syncIt->second.Display != display) { SetError(EGL_BAD_MATCH); return false; } m_syncs.erase(syncIt); return true; } EGLint EGLContext::ClientWaitSync(EGLDisplayHandle display, EGLSyncHandle sync, EGLint flags, EGLTime timeout) { const std::lock_guard lock(m_mutex); (void)flags; (void)timeout; const auto* syncObject = TryGetSync(sync); if (!syncObject) { SetError(EGL_BAD_PARAMETER); return EGL_FALSE; } if (syncObject->Display != display) { SetError(EGL_BAD_MATCH); return EGL_FALSE; } return EGL_CONDITION_SATISFIED; } Bool EGLContext::GetSyncAttrib(EGLDisplayHandle display, EGLSyncHandle sync, EGLint attribute, EGLAttrib* value) const { const std::lock_guard lock(m_mutex); if (!value) { const_cast(this)->SetError(EGL_BAD_PARAMETER); return false; } const auto* syncObject = TryGetSync(sync); if (!syncObject) { const_cast(this)->SetError(EGL_BAD_PARAMETER); return false; } if (syncObject->Display != display) { const_cast(this)->SetError(EGL_BAD_MATCH); return false; } switch (attribute) { case EGL_SYNC_TYPE: *value = static_cast(syncObject->Type); return true; case EGL_SYNC_STATUS: *value = static_cast(syncObject->Status); return true; case EGL_SYNC_CONDITION: *value = static_cast(syncObject->Condition); return true; default: const_cast(this)->SetError(EGL_BAD_ATTRIBUTE); return false; } } Bool EGLContext::WaitSync(EGLDisplayHandle display, EGLSyncHandle sync, EGLint flags) const { const std::lock_guard lock(m_mutex); (void)flags; const auto* syncObject = TryGetSync(sync); if (!syncObject) { const_cast(this)->SetError(EGL_BAD_PARAMETER); return false; } if (syncObject->Display != display) { const_cast(this)->SetError(EGL_BAD_MATCH); return false; } return true; } EGLContext::EGLImageHandle EGLContext::CreateImage(EGLDisplayHandle display, EGLContextHandle context, EGLenum target, EGLClientBuffer buffer, const EGLAttrib* attribList) { const std::lock_guard lock(m_mutex); (void)attribList; if (!IsDisplayInitialized(display)) { SetError(EGL_NOT_INITIALIZED); return EGL_NO_IMAGE; } if (context != nullptr && !ValidateContextOnDisplay(display, context)) { SetError(EGL_BAD_CONTEXT); return EGL_NO_IMAGE; } const auto image = EncodeHandle(m_nextImageHandle++); m_images[image] = ImageObject{ .Display = display, .Context = context, .Target = target, .Buffer = buffer, }; return image; } Bool EGLContext::DestroyImage(EGLDisplayHandle display, EGLImageHandle image) { const std::lock_guard lock(m_mutex); auto imageIt = m_images.find(image); if (imageIt == m_images.end()) { SetError(EGL_BAD_PARAMETER); return false; } if (imageIt->second.Display != display) { SetError(EGL_BAD_MATCH); return false; } m_images.erase(imageIt); return true; } } // namespace EGLState // Leak-at-exit storage; see GlobalObjects.cpp. UniquePtr& pEGLContext = *new UniquePtr(); } // namespace MG_State } // namespace MobileGL