[Fix] (MG_Impl/EGLImpl, MG_Backend): handle EGL current handoff for NeoForge earlydisplay

Treat valid-display no-surface eglMakeCurrent calls as EGL release requests, keep EGLState and backend current records consistent across threads, and rebind the native DirectGLES EGL context during attach/release.

Add EGLState coverage for cross-thread owner transfer and same-thread release/reattach behavior.
This commit is contained in:
2026-06-30 16:44:38 +08:00
parent 2f1949e093
commit 5b116696f0
12 changed files with 271 additions and 20 deletions
+15 -3
View File
@@ -33,7 +33,8 @@ namespace MobileGL::MG_Backend {
};
Bool IsReleaseCurrentRequest(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
return dpy == EGL_NO_DISPLAY && draw == EGL_NO_SURFACE && read == EGL_NO_SURFACE && ctx == EGL_NO_CONTEXT;
(void)dpy;
return draw == EGL_NO_SURFACE && read == EGL_NO_SURFACE && ctx == EGL_NO_CONTEXT;
}
std::thread::id CurrentThreadKey() {
@@ -296,7 +297,12 @@ namespace MobileGL::MG_Backend {
m_backendCapabilitiesInitialized = true;
}
m_eglCurrentThreads[threadKey] = true;
m_eglCurrentThreads[threadKey] = EGLCurrentState{
.Display = dpy,
.DrawSurface = draw,
.ReadSurface = read,
.Context = ctx,
};
return true;
}
@@ -314,10 +320,16 @@ namespace MobileGL::MG_Backend {
MGLOG_E("SwapEGLBuffers failed: EGL display mismatch or not initialized");
return false;
}
if (m_eglCurrentThreads.find(CurrentThreadKey()) == m_eglCurrentThreads.end()) {
const auto currentIt = m_eglCurrentThreads.find(CurrentThreadKey());
if (currentIt == m_eglCurrentThreads.end()) {
MGLOG_E("SwapEGLBuffers failed: no current context attached");
return false;
}
if (currentIt->second.Display != dpy || currentIt->second.DrawSurface != draw ||
currentIt->second.Context == EGL_NO_CONTEXT) {
MGLOG_E("SwapEGLBuffers failed: draw surface is not current on this thread");
return false;
}
if (!m_eglSurfaceInitialized || draw == EGL_NO_SURFACE) {
MGLOG_E("SwapEGLBuffers failed: invalid draw surface");
return false;
+8 -1
View File
@@ -268,6 +268,13 @@ namespace MobileGL {
Pbuffer
};
struct EGLCurrentState {
EGLDisplay Display = EGL_NO_DISPLAY;
EGLSurface DrawSurface = EGL_NO_SURFACE;
EGLSurface ReadSurface = EGL_NO_SURFACE;
EGLContext Context = EGL_NO_CONTEXT;
};
void ResetEGLRuntimeState();
virtual Bool InitPbufferSurface(EGLint width, EGLint height);
FormatCapabilityCache& MutableFormatCapabilities();
@@ -280,7 +287,7 @@ namespace MobileGL {
Bool m_eglSurfaceInitialized = false;
Bool m_backendCapabilitiesInitialized = false;
SurfaceKind m_eglSurfaceKind = SurfaceKind::None;
UnorderedMap<std::thread::id, Bool> m_eglCurrentThreads;
UnorderedMap<std::thread::id, EGLCurrentState> m_eglCurrentThreads;
};
} // namespace MG_Backend
} // namespace MobileGL
@@ -22,7 +22,8 @@
namespace MobileGL::MG_Backend::DirectGLES {
namespace {
Bool IsReleaseCurrentRequest(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
return dpy == EGL_NO_DISPLAY && draw == EGL_NO_SURFACE && read == EGL_NO_SURFACE && ctx == EGL_NO_CONTEXT;
(void)dpy;
return draw == EGL_NO_SURFACE && read == EGL_NO_SURFACE && ctx == EGL_NO_CONTEXT;
}
void ClearGLErrors(const MG_External::GLESFunctionsTable& gl) {
@@ -708,11 +709,40 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
Bool BackendObject_DirectGLES::MakeEGLCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
if (IsReleaseCurrentRequest(dpy, draw, read, ctx)) {
if (!DirectGLES::ReleaseCurrent()) {
return false;
}
return BackendObject::MakeEGLCurrent(dpy, draw, read, ctx);
}
return BackendObject::MakeEGLCurrent(dpy, draw, read, ctx);
if (!m_initialized) {
MGLOG_E("DirectGLES backend not initialized");
return false;
}
if (!m_eglDisplayInitialized || m_eglDisplay != dpy) {
MGLOG_E("MakeEGLCurrent failed: EGL display mismatch or not initialized");
return false;
}
if (!m_eglSurfaceInitialized) {
MGLOG_E("MakeEGLCurrent failed: EGL surface is not initialized");
return false;
}
if (draw == EGL_NO_SURFACE || read == EGL_NO_SURFACE || ctx == EGL_NO_CONTEXT) {
MGLOG_E("MakeEGLCurrent failed: draw/read/context is invalid");
return false;
}
if (!DirectGLES::MakeCurrent()) {
return false;
}
if (!BackendObject::MakeEGLCurrent(dpy, draw, read, ctx)) {
(void)DirectGLES::ReleaseCurrent();
return false;
}
return true;
}
Bool BackendObject_DirectGLES::SwapEGLBuffers(EGLDisplay dpy, EGLSurface draw) {
+28 -2
View File
@@ -3222,7 +3222,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
g_Surface = g_EGLFuncs.eglCreateWindowSurface(g_Display, g_Config, window, nullptr);
if (g_Surface == EGL_NO_SURFACE) return false;
if (!g_EGLFuncs.eglMakeCurrent(g_Display, g_Surface, g_Surface, g_Context)) return false;
if (!MakeCurrent()) return false;
MGLOG_D("EGL context created successfully: display=%p, surface=%p, context=%p. window=%p", g_Display, g_Surface,
g_Context, window);
@@ -3237,13 +3237,39 @@ namespace MobileGL::MG_Backend::DirectGLES {
g_Surface = g_EGLFuncs.eglCreatePbufferSurface(g_Display, g_Config, surfaceAttribs);
if (g_Surface == EGL_NO_SURFACE) return false;
if (!g_EGLFuncs.eglMakeCurrent(g_Display, g_Surface, g_Surface, g_Context)) return false;
if (!MakeCurrent()) return false;
MGLOG_D("EGL pbuffer context created successfully: display=%p, surface=%p, context=%p. size=%dx%d", g_Display,
g_Surface, g_Context, width, height);
return true;
}
Bool MakeCurrent() {
if (!g_EGLFuncs.eglMakeCurrent || g_Display == EGL_NO_DISPLAY || g_Surface == EGL_NO_SURFACE ||
g_Context == EGL_NO_CONTEXT) {
MGLOG_E("DirectGLES::MakeCurrent failed: EGL display/surface/context is not initialized");
return false;
}
if (!g_EGLFuncs.eglMakeCurrent(g_Display, g_Surface, g_Surface, g_Context)) {
const EGLint error = g_EGLFuncs.eglGetError ? g_EGLFuncs.eglGetError() : EGL_SUCCESS;
MGLOG_E("DirectGLES::MakeCurrent failed: native eglMakeCurrent returned error 0x%04x", error);
return false;
}
return true;
}
Bool ReleaseCurrent() {
if (!g_EGLFuncs.eglMakeCurrent || g_Display == EGL_NO_DISPLAY) {
return true;
}
if (!g_EGLFuncs.eglMakeCurrent(g_Display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) {
const EGLint error = g_EGLFuncs.eglGetError ? g_EGLFuncs.eglGetError() : EGL_SUCCESS;
MGLOG_E("DirectGLES::ReleaseCurrent failed: native eglMakeCurrent returned error 0x%04x", error);
return false;
}
return true;
}
void Present() {
g_EGLFuncs.eglSwapBuffers(g_Display, g_Surface);
}
@@ -93,6 +93,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
void ShaderStorageBlockBinding(GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding);
Bool InitWindowSurface(NativeWindowType window);
Bool InitPbufferSurface(EGLint width, EGLint height);
Bool MakeCurrent();
Bool ReleaseCurrent();
void Present();
void SetEGLFuncsTable(const MG_External::EGLFunctionsTable& eglFuncs);
void SetGLESFuncsTable(const MG_External::GLESFunctionsTable& glesFuncs);
@@ -20,7 +20,8 @@
namespace MobileGL::MG_Backend::DirectVulkan {
namespace {
Bool IsReleaseCurrentRequest(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
return dpy == EGL_NO_DISPLAY && draw == EGL_NO_SURFACE && read == EGL_NO_SURFACE && ctx == EGL_NO_CONTEXT;
(void)dpy;
return draw == EGL_NO_SURFACE && read == EGL_NO_SURFACE && ctx == EGL_NO_CONTEXT;
}
Bool IsFormatIndexValid(TextureInternalFormat format) {
+38 -5
View File
@@ -10,6 +10,8 @@
#include "../GetProcAddress.h"
#include <MG_Backend/BackendObjects.h>
#include <MG_State/EGLState/Core.h>
#include <mutex>
#include <sstream>
#include <type_traits>
namespace MobileGL::MG_Impl::EGLImpl {
@@ -31,6 +33,17 @@ namespace MobileGL::MG_Impl::EGLImpl {
return backendObject;
}
std::recursive_mutex& EGLOperationMutex() {
static std::recursive_mutex mutex;
return mutex;
}
String CurrentThreadIdString() {
std::ostringstream stream;
stream << std::this_thread::get_id();
return stream.str();
}
MG_Backend::WindowBackend DetectWindowBackend() {
#if defined(ANDROID) || defined(__ANDROID__)
return MG_Backend::WindowBackend::Android;
@@ -55,7 +68,7 @@ namespace MobileGL::MG_Impl::EGLImpl {
return defaultValue;
}
EGLint GetAttribValue(const EGLAttrib* attribList, EGLint attrib, EGLint defaultValue) {
EGLint GetAttribValueAttrib(const EGLAttrib* attribList, EGLint attrib, EGLint defaultValue) {
if (!attribList) {
return defaultValue;
}
@@ -126,6 +139,7 @@ namespace MobileGL::MG_Impl::EGLImpl {
}
EGLBoolean SwapBuffers(EGLDisplay dpy, EGLSurface draw) {
const std::lock_guard<std::recursive_mutex> operationLock(EGLOperationMutex());
auto* state = GetState();
if (!state) {
return EGL_FALSE;
@@ -141,6 +155,7 @@ namespace MobileGL::MG_Impl::EGLImpl {
return EGL_FALSE;
}
if (!backendObject->SwapEGLBuffers(dpy, draw)) {
MGLOG_E("eglSwapBuffers failed on thread=%s dpy=%p draw=%p", CurrentThreadIdString().c_str(), dpy, draw);
state->SetError(EGL_BAD_SURFACE);
return EGL_FALSE;
}
@@ -202,6 +217,7 @@ namespace MobileGL::MG_Impl::EGLImpl {
}
EGLBoolean MakeCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
const std::lock_guard<std::recursive_mutex> operationLock(EGLOperationMutex());
auto* state = GetState();
if (!state) {
return EGL_FALSE;
@@ -211,17 +227,30 @@ namespace MobileGL::MG_Impl::EGLImpl {
const auto oldDraw = state->GetCurrentSurface(EGL_DRAW);
const auto oldRead = state->GetCurrentSurface(EGL_READ);
const auto oldContext = state->GetCurrentContext();
const String threadId = CurrentThreadIdString();
MGLOG_D("eglMakeCurrent begin thread=%s dpy=%p draw=%p read=%p ctx=%p oldDpy=%p oldDraw=%p oldRead=%p oldCtx=%p",
threadId.c_str(), dpy, draw, read, ctx, oldDisplay, oldDraw, oldRead, oldContext);
if (!state->MakeCurrent(dpy, draw, read, ctx)) {
const EGLint error = state->ConsumeError();
MGLOG_D("eglMakeCurrent rejected by EGLState thread=%s error=0x%04x", threadId.c_str(), error);
state->SetError(error);
return EGL_FALSE;
}
const Bool releaseCurrentRequest =
dpy == EGL_NO_DISPLAY && draw == EGL_NO_SURFACE && read == EGL_NO_SURFACE && ctx == EGL_NO_CONTEXT;
draw == EGL_NO_SURFACE && read == EGL_NO_SURFACE && ctx == EGL_NO_CONTEXT;
if (releaseCurrentRequest) {
if (auto* backendObject = MG_Backend::pActiveBackendObject.get()) {
(void)backendObject->MakeEGLCurrent(dpy, draw, read, ctx);
if (!backendObject->MakeEGLCurrent(dpy, draw, read, ctx)) {
MGLOG_E("eglMakeCurrent release failed in backend thread=%s", threadId.c_str());
state->MakeCurrent(oldDisplay, oldDraw, oldRead, oldContext);
state->SetError(EGL_BAD_ACCESS);
return EGL_FALSE;
}
}
MGLOG_D("eglMakeCurrent release succeeded thread=%s", threadId.c_str());
return EGL_TRUE;
}
@@ -232,10 +261,14 @@ namespace MobileGL::MG_Impl::EGLImpl {
return EGL_FALSE;
}
if (!backendObject->MakeEGLCurrent(dpy, draw, read, ctx)) {
MGLOG_E("eglMakeCurrent backend attach failed thread=%s dpy=%p draw=%p read=%p ctx=%p", threadId.c_str(),
dpy, draw, read, ctx);
state->SetError(EGL_BAD_ACCESS);
state->MakeCurrent(oldDisplay, oldDraw, oldRead, oldContext);
return EGL_FALSE;
}
MGLOG_D("eglMakeCurrent attach succeeded thread=%s dpy=%p draw=%p read=%p ctx=%p", threadId.c_str(), dpy, draw,
read, ctx);
return EGL_TRUE;
}
@@ -612,8 +645,8 @@ namespace MobileGL::MG_Impl::EGLImpl {
const MG_Backend::WindowHandle windowHandle = {
.Backend = DetectWindowBackend(),
.Handle = native_window,
.Width = static_cast<Uint32>(std::max<EGLint>(GetAttribValue(attrib_list, EGL_WIDTH, 0), 0)),
.Height = static_cast<Uint32>(std::max<EGLint>(GetAttribValue(attrib_list, EGL_HEIGHT, 0), 0)),
.Width = static_cast<Uint32>(std::max<EGLint>(GetAttribValueAttrib(attrib_list, EGL_WIDTH, 0), 0)),
.Height = static_cast<Uint32>(std::max<EGLint>(GetAttribValueAttrib(attrib_list, EGL_HEIGHT, 0), 0)),
};
if (!backendObject->CreateEGLWindowSurface(windowHandle)) {
state->SetError(EGL_BAD_NATIVE_WINDOW);
+12 -1
View File
@@ -1054,9 +1054,20 @@ namespace MobileGL {
EGLContextHandle context) {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
const Bool releaseCurrentRequest =
display == EGL_NO_DISPLAY && draw == EGL_NO_SURFACE && read == EGL_NO_SURFACE && context == nullptr;
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;
}
+1
View File
@@ -65,6 +65,7 @@ include(GoogleTest)
gtest_discover_tests(SanityTest)
add_subdirectory(Buffer)
add_subdirectory(EGLState)
add_subdirectory(Framebuffer)
add_subdirectory(Texture)
add_subdirectory(VertexArray)
+20
View File
@@ -0,0 +1,20 @@
cmake_minimum_required(VERSION 3.14)
add_executable(
EGLStateTest
EGLStateTest.cpp
)
target_include_directories(EGLStateTest PRIVATE
${MGL_ROOT}/include
${MGL_ROOT}/MobileGL
)
target_link_libraries(
EGLStateTest PRIVATE
GTest::gtest_main
${LINK_LIBRARIES}
)
include(GoogleTest)
gtest_discover_tests(EGLStateTest)
+104
View File
@@ -0,0 +1,104 @@
#include <gtest/gtest.h>
#include <MG_State/EGLState/Core.h>
#include <future>
#include <memory>
#include <thread>
namespace {
using StateContext = MobileGL::MG_State::EGLState::EGLContext;
struct EGLFixture {
StateContext State;
EGLDisplay Display = EGL_NO_DISPLAY;
EGLConfig Config = nullptr;
EGLSurface Surface = EGL_NO_SURFACE;
StateContext::EGLContextHandle Context = EGL_NO_CONTEXT;
};
std::unique_ptr<EGLFixture> CreateFixture() {
auto fixture = std::make_unique<EGLFixture>();
fixture->Display = fixture->State.GetDisplay(EGL_DEFAULT_DISPLAY);
EXPECT_NE(fixture->Display, EGL_NO_DISPLAY);
EXPECT_TRUE(fixture->State.InitializeDisplay(fixture->Display, nullptr, nullptr));
EGLint configCount = 0;
EXPECT_TRUE(fixture->State.ChooseConfig(fixture->Display, nullptr, &fixture->Config, 1, &configCount));
EXPECT_GE(configCount, 1);
EXPECT_NE(fixture->Config, nullptr);
const EGLint surfaceAttribs[] = {EGL_WIDTH, 16, EGL_HEIGHT, 16, EGL_NONE};
fixture->Surface = fixture->State.CreatePbufferSurface(fixture->Display, fixture->Config, surfaceAttribs);
EXPECT_NE(fixture->Surface, EGL_NO_SURFACE);
fixture->Context = fixture->State.CreateContext(fixture->Display, fixture->Config, EGL_NO_CONTEXT, nullptr);
EXPECT_NE(fixture->Context, EGL_NO_CONTEXT);
return fixture;
}
}
TEST(EGLStateMakeCurrent, ContextCannotBeCurrentOnTwoThreadsAtOnce) {
auto fixture = CreateFixture();
std::promise<void> threadAReady;
std::promise<void> threadBAttempted;
std::promise<void> threadAReleased;
auto threadAReadyFuture = threadAReady.get_future();
auto threadBAttemptedFuture = threadBAttempted.get_future();
auto threadAReleasedFuture = threadAReleased.get_future();
bool threadAAttachOk = false;
bool threadAReleaseOk = false;
bool threadBDenied = false;
EGLint threadBDeniedError = EGL_SUCCESS;
bool threadBAttachAfterReleaseOk = false;
EGLint threadBAttachAfterReleaseError = EGL_SUCCESS;
std::thread threadA([&] {
threadAAttachOk = fixture->State.MakeCurrent(fixture->Display, fixture->Surface, fixture->Surface,
fixture->Context);
threadAReady.set_value();
threadBAttemptedFuture.wait();
threadAReleaseOk =
fixture->State.MakeCurrent(fixture->Display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
threadAReleased.set_value();
});
std::thread threadB([&] {
threadAReadyFuture.wait();
threadBDenied = fixture->State.MakeCurrent(fixture->Display, fixture->Surface, fixture->Surface,
fixture->Context);
threadBDeniedError = fixture->State.ConsumeError();
threadBAttempted.set_value();
threadAReleasedFuture.wait();
threadBAttachAfterReleaseOk =
fixture->State.MakeCurrent(fixture->Display, fixture->Surface, fixture->Surface, fixture->Context);
threadBAttachAfterReleaseError = fixture->State.ConsumeError();
if (threadBAttachAfterReleaseOk) {
(void)fixture->State.MakeCurrent(EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
}
});
threadA.join();
threadB.join();
EXPECT_TRUE(threadAAttachOk);
EXPECT_TRUE(threadAReleaseOk);
EXPECT_FALSE(threadBDenied);
EXPECT_EQ(threadBDeniedError, EGL_BAD_ACCESS);
EXPECT_TRUE(threadBAttachAfterReleaseOk);
EXPECT_EQ(threadBAttachAfterReleaseError, EGL_SUCCESS);
}
TEST(EGLStateMakeCurrent, SameThreadRepeatedAttachReleaseDoesNotLeaveStaleOwner) {
auto fixture = CreateFixture();
EXPECT_TRUE(fixture->State.MakeCurrent(fixture->Display, fixture->Surface, fixture->Surface, fixture->Context));
EXPECT_TRUE(fixture->State.MakeCurrent(fixture->Display, fixture->Surface, fixture->Surface, fixture->Context));
EXPECT_TRUE(fixture->State.MakeCurrent(fixture->Display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
EXPECT_TRUE(fixture->State.MakeCurrent(fixture->Display, fixture->Surface, fixture->Surface, fixture->Context));
EXPECT_TRUE(fixture->State.MakeCurrent(EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
EXPECT_TRUE(fixture->State.MakeCurrent(fixture->Display, fixture->Surface, fixture->Surface, fixture->Context));
EXPECT_TRUE(fixture->State.MakeCurrent(EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
EXPECT_EQ(fixture->State.ConsumeError(), EGL_SUCCESS);
}
+9 -5
View File
@@ -1,5 +1,9 @@
apply plugin: 'com.android.library'
def mobileGlLogActiveLevel = {
(rootProject.findProperty('mobilegl.logLevel') ?: System.getenv('MOBILEGL_LOG_ACTIVE_LEVEL') ?: 'MOBILEGL_LOG_LEVEL_INFO') as String
}
android {
namespace 'top.mobilegl.mobilegl'
compileSdk 34
@@ -9,11 +13,11 @@ android {
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
// externalNativeBuild {
// cmake {
// arguments "-DMOBILEGL_ENABLE_TRACY=ON"
// }
// }
externalNativeBuild {
cmake {
arguments "-DMOBILEGL_LOG_ACTIVE_LEVEL=${mobileGlLogActiveLevel()}"
}
}
}
buildTypes {