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.