Quick answer: EGL contexts bind to one thread. For multi-threaded GL: create a shared context (via eglCreateContext with sharedContext), eglMakeCurrent it on the worker.
An Android game loads textures on a background thread. glTexImage2D returns “no current context”. The main thread holds the EGL context; the worker has none.
Shared Context Pattern
EGLContext sharedCtx = eglCreateContext(display, config, mainCtx, attribs);
// On worker thread:
eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, sharedCtx);
glTexImage2D(...); // works
Shared context inherits resources from main; uploads on worker propagate to main’s view. Surfaceless = no rendering output needed for upload.
EGL_PBUFFER for Render to Texture
If the worker also needs to render (e.g. for icons), create a small PBuffer surface:
EGLint pbufferAttribs[] = { EGL_WIDTH, 256, EGL_HEIGHT, 256, EGL_NONE };
EGLSurface pb = eglCreatePbufferSurface(display, config, pbufferAttribs);
eglMakeCurrent(display, pb, pb, sharedCtx);
Synchronize Across Threads
Use fence sync (EGL_KHR_fence_sync) to ensure worker uploads complete before main thread reads:
EGLSyncKHR sync = eglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, NULL);
eglWaitSyncKHR(display, sync, 0);
Otherwise main may read stale GL state.
Verifying
Background worker uploads textures. Main thread uses them next frame without artifacts. No “no current context” errors.
“EGL contexts are per-thread. Share via shared-context creation, sync via fence.”
For Vulkan, this is much cleaner — resources are not thread-bound. If you’re building new mobile renderers, Vulkan over GLES is the modern choice.