Skip to content

Commit 65ad531

Browse files
author
Lalumo Admin
committed
WIP: add EGL context synchronization to prevent EGL_BAD_ACCESS errors on Android
1 parent aae0eee commit 65ad531

File tree

5 files changed

+388
-6
lines changed

5 files changed

+388
-6
lines changed

app/src/main/java/org/tuxpaint/tuxpaintActivity.java

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ public class tuxpaintActivity extends SDLActivity {
1818
private static native boolean managertojni(AssetManager mgr);
1919
private static native void setnativelibdir(String path);
2020

21+
// Lock object for OpenGL context synchronization
22+
private static final Object sGLLock = new Object();
23+
24+
// Native methods for EGL context synchronization to fix EGL_BAD_ACCESS errors
25+
private static native void initEGLContextManager();
26+
private static native boolean lockEGLContext();
27+
private static native boolean unlockEGLContext();
2128

2229
@Override
2330
protected void onCreate(Bundle savedInstanceState) {
@@ -31,11 +38,45 @@ protected void onCreate(Bundle savedInstanceState) {
3138
}
3239
}
3340

34-
35-
super.onCreate(savedInstanceState);
36-
mgr = getResources().getAssets();
37-
managertojni(mgr);
38-
setnativelibdir(getApplicationInfo().nativeLibraryDir + "/");
41+
synchronized (sGLLock) {
42+
super.onCreate(savedInstanceState);
43+
mgr = getResources().getAssets();
44+
managertojni(mgr);
45+
setnativelibdir(getApplicationInfo().nativeLibraryDir + "/");
46+
}
47+
}
48+
49+
// Synchronize OpenGL context access
50+
@Override
51+
public void onResume() {
52+
synchronized (sGLLock) {
53+
super.onResume();
54+
}
55+
}
56+
57+
@Override
58+
public void onPause() {
59+
synchronized (sGLLock) {
60+
super.onPause();
61+
}
62+
}
63+
64+
// Important: Add EGL synchronization to the main activity methods
65+
// to ensure OpenGL operations are properly synchronized between threads
66+
@Override
67+
public void onWindowFocusChanged(boolean hasFocus) {
68+
synchronized (sGLLock) {
69+
Log.v(TAG, "Synchronized onWindowFocusChanged: " + hasFocus);
70+
super.onWindowFocusChanged(hasFocus);
71+
}
72+
}
73+
74+
@Override
75+
public void onLowMemory() {
76+
synchronized (sGLLock) {
77+
Log.v(TAG, "Synchronized onLowMemory");
78+
super.onLowMemory();
79+
}
3980
}
4081

4182
static {
@@ -64,5 +105,8 @@ protected void onCreate(Bundle savedInstanceState) {
64105
System.loadLibrary("SDL2_Pango");
65106
System.loadLibrary("SDL2_gfx");
66107
System.loadLibrary("tuxpaint");
108+
109+
// Initialize the EGL context manager to prevent EGL_BAD_ACCESS errors
110+
initEGLContextManager();
67111
}
68112
}

app/src/main/jni/tuxpaint/Android.mk

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ LOCAL_SRC_FILES := \
3333
src/fill.c \
3434
src/android_assets.c \
3535
src/gifenc.c\
36-
src/sounds.c
36+
src/sounds.c \
37+
src/tuxpaint_egl_sync.c
3738

3839
MY_CFLAGS:= -O0 -g -W -Wall -fno-common -ffloat-store \
3940
-Wcast-align -Wredundant-decls \
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
/*
2+
* Tuxpaint EGL Context Synchronization
3+
*
4+
* This file adds thread synchronization to SDL's EGL context management
5+
* to fix EGL_BAD_ACCESS errors when multiple threads attempt to access
6+
* the same EGL context.
7+
*
8+
* This implementation uses function interception to wrap EGL calls with
9+
* mutex protection, ensuring only one thread can access the EGL context
10+
* at a time.
11+
*/
12+
13+
#include <jni.h>
14+
#include <android/log.h>
15+
#include <pthread.h>
16+
#include <dlfcn.h>
17+
#include <EGL/egl.h>
18+
#include <unistd.h>
19+
20+
#define TAG "TuxPaint-EGL-Sync"
21+
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
22+
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
23+
24+
// Global mutex for EGL context operations
25+
static pthread_mutex_t egl_mutex = PTHREAD_MUTEX_INITIALIZER;
26+
static int egl_mutex_initialized = 0;
27+
28+
// Function pointers for original EGL functions
29+
static EGLBoolean (*real_eglMakeCurrent)(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx);
30+
static EGLBoolean (*real_eglSwapBuffers)(EGLDisplay dpy, EGLSurface surface);
31+
static EGLContext (*real_eglCreateContext)(EGLDisplay dpy, EGLConfig config, EGLContext share_context, const EGLint *attrib_list);
32+
static EGLBoolean (*real_eglDestroyContext)(EGLDisplay dpy, EGLContext ctx);
33+
34+
// Thread tracking to detect when multiple threads try to use same context
35+
static pthread_t egl_context_owner = 0;
36+
static EGLContext current_context = EGL_NO_CONTEXT;
37+
38+
// JNI methods to initialize and control the EGL mutex
39+
40+
JNIEXPORT void JNICALL
41+
Java_org_tuxpaint_tuxpaintActivity_initEGLContextManager(JNIEnv *env, jclass clazz) {
42+
if (egl_mutex_initialized) {
43+
LOGI("EGL mutex already initialized");
44+
return;
45+
}
46+
47+
pthread_mutexattr_t attr;
48+
pthread_mutexattr_init(&attr);
49+
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
50+
int result = pthread_mutex_init(&egl_mutex, &attr);
51+
pthread_mutexattr_destroy(&attr);
52+
53+
if (result == 0) {
54+
egl_mutex_initialized = 1;
55+
LOGI("EGL mutex initialized successfully");
56+
} else {
57+
LOGE("Failed to initialize EGL mutex, error code: %d", result);
58+
}
59+
}
60+
61+
JNIEXPORT jboolean JNICALL
62+
Java_org_tuxpaint_tuxpaintActivity_lockEGLContext(JNIEnv *env, jclass clazz) {
63+
if (!egl_mutex_initialized) {
64+
LOGE("Attempting to lock uninitialized EGL mutex");
65+
return JNI_FALSE;
66+
}
67+
68+
int result = pthread_mutex_lock(&egl_mutex);
69+
if (result == 0) {
70+
LOGI("EGL mutex locked");
71+
return JNI_TRUE;
72+
} else {
73+
LOGE("Failed to lock EGL mutex, error code: %d", result);
74+
return JNI_FALSE;
75+
}
76+
}
77+
78+
JNIEXPORT jboolean JNICALL
79+
Java_org_tuxpaint_tuxpaintActivity_unlockEGLContext(JNIEnv *env, jclass clazz) {
80+
if (!egl_mutex_initialized) {
81+
LOGE("Attempting to unlock uninitialized EGL mutex");
82+
return JNI_FALSE;
83+
}
84+
85+
int result = pthread_mutex_unlock(&egl_mutex);
86+
if (result == 0) {
87+
LOGI("EGL mutex unlocked");
88+
return JNI_TRUE;
89+
} else {
90+
LOGE("Failed to unlock EGL mutex, error code: %d", result);
91+
return JNI_FALSE;
92+
}
93+
}
94+
95+
// Functions to be called from the SDL native code
96+
void tuxpaint_egl_lock(void) {
97+
if (egl_mutex_initialized) {
98+
pthread_mutex_lock(&egl_mutex);
99+
}
100+
}
101+
102+
void tuxpaint_egl_unlock(void) {
103+
if (egl_mutex_initialized) {
104+
pthread_mutex_unlock(&egl_mutex);
105+
}
106+
}
107+
108+
// Wrapper functions that intercept EGL calls
109+
EGLBoolean eglMakeCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
110+
EGLBoolean result;
111+
112+
if (!real_eglMakeCurrent) {
113+
// If we haven't loaded the real function yet, do it now
114+
real_eglMakeCurrent = dlsym(RTLD_NEXT, "eglMakeCurrent");
115+
if (!real_eglMakeCurrent) {
116+
LOGE("Failed to find original eglMakeCurrent function");
117+
return EGL_FALSE;
118+
}
119+
}
120+
121+
// Lock before changing context
122+
tuxpaint_egl_lock();
123+
124+
// Track which thread is using this context
125+
if (ctx != EGL_NO_CONTEXT) {
126+
pthread_t current_thread = pthread_self();
127+
128+
// If this is a different thread trying to use an already-owned context
129+
if (ctx == current_context && egl_context_owner != 0 &&
130+
!pthread_equal(current_thread, egl_context_owner)) {
131+
LOGE("EGL CONTEXT VIOLATION: Thread %lu trying to use context %p owned by thread %lu",
132+
(unsigned long)current_thread, ctx, (unsigned long)egl_context_owner);
133+
// We still try to make it work by updating the owner
134+
}
135+
136+
// Update context owner
137+
current_context = ctx;
138+
egl_context_owner = current_thread;
139+
}
140+
141+
// Call the real function
142+
result = real_eglMakeCurrent(dpy, draw, read, ctx);
143+
144+
// If we're releasing the context, clear the owner
145+
if (ctx == EGL_NO_CONTEXT) {
146+
current_context = EGL_NO_CONTEXT;
147+
egl_context_owner = 0;
148+
}
149+
150+
// Unlock
151+
tuxpaint_egl_unlock();
152+
153+
return result;
154+
}
155+
156+
EGLBoolean eglSwapBuffers(EGLDisplay dpy, EGLSurface surface) {
157+
EGLBoolean result;
158+
159+
if (!real_eglSwapBuffers) {
160+
real_eglSwapBuffers = dlsym(RTLD_NEXT, "eglSwapBuffers");
161+
if (!real_eglSwapBuffers) {
162+
LOGE("Failed to find original eglSwapBuffers function");
163+
return EGL_FALSE;
164+
}
165+
}
166+
167+
// Lock before swapping buffers
168+
tuxpaint_egl_lock();
169+
170+
// Call the real function
171+
result = real_eglSwapBuffers(dpy, surface);
172+
173+
// Unlock
174+
tuxpaint_egl_unlock();
175+
176+
return result;
177+
}
178+
179+
EGLContext eglCreateContext(EGLDisplay dpy, EGLConfig config, EGLContext share_context, const EGLint *attrib_list) {
180+
EGLContext result;
181+
182+
if (!real_eglCreateContext) {
183+
real_eglCreateContext = dlsym(RTLD_NEXT, "eglCreateContext");
184+
if (!real_eglCreateContext) {
185+
LOGE("Failed to find original eglCreateContext function");
186+
return EGL_NO_CONTEXT;
187+
}
188+
}
189+
190+
// Lock before creating context
191+
tuxpaint_egl_lock();
192+
193+
// Call the real function
194+
result = real_eglCreateContext(dpy, config, share_context, attrib_list);
195+
196+
// Unlock
197+
tuxpaint_egl_unlock();
198+
199+
return result;
200+
}
201+
202+
EGLBoolean eglDestroyContext(EGLDisplay dpy, EGLContext ctx) {
203+
EGLBoolean result;
204+
205+
if (!real_eglDestroyContext) {
206+
real_eglDestroyContext = dlsym(RTLD_NEXT, "eglDestroyContext");
207+
if (!real_eglDestroyContext) {
208+
LOGE("Failed to find original eglDestroyContext function");
209+
return EGL_FALSE;
210+
}
211+
}
212+
213+
// Lock before destroying context
214+
tuxpaint_egl_lock();
215+
216+
// If this is the current context, clear the owner
217+
if (ctx == current_context) {
218+
current_context = EGL_NO_CONTEXT;
219+
egl_context_owner = 0;
220+
}
221+
222+
// Call the real function
223+
result = real_eglDestroyContext(dpy, ctx);
224+
225+
// Unlock
226+
tuxpaint_egl_unlock();
227+
228+
return result;
229+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/*
2+
* Tuxpaint EGL Context Synchronization Header
3+
*/
4+
5+
#ifndef TUXPAINT_EGL_SYNC_H
6+
#define TUXPAINT_EGL_SYNC_H
7+
8+
// Functions to be called from SDL native code to synchronize EGL context access
9+
void tuxpaint_egl_lock(void);
10+
void tuxpaint_egl_unlock(void);
11+
12+
#endif /* TUXPAINT_EGL_SYNC_H */

0 commit comments

Comments
 (0)