PlatformEGL shared compiler contexts do not bind desktop OpenGL API on worker threads
- Dominant language
- C++
- Stars
- 20.5k
- Forks
- 2.3k
- Avg merge
- 2d 16h
- Merged PRs (30d)
- 74
Description
### Reproduction status
Confirmed in Filament v1.54.0 on the actual asynchronous shader-compiler worker path. A matching desktop-OpenGL `eglBindAPI` on that worker changes `eglCreateContext` from `EGL_BAD_MATCH` to `EGL_SUCCESS`. Current Filament main at `6a63790752736e37809ab1a4585c04ab0fe2ebec` retains the same source-level path: the driver thread binds OpenGL in `PlatformEGLHeadless::createDriver()`, while a compiler worker calls `PlatformEGL::createContext(true)` without binding an API on its own thread.
### Environment and versions
```text
Ubuntu 26.04.1 LTS, x86_64
Linux 7.0.0-31-generic
Mesa 26.0.8 / EGL 1.7.0
Filament v1.54.0 tag commit: c1a3450d9c0a64c329a17bff4f3a7f68a2474214
Filament v1.54.0 source archive SHA-256: f4cb4eb81e3a5d66a9612ac131d16183e118b694f4f34c051506c523a8389e8d
Current main inspected: 6a63790752736e37809ab1a4585c04ab0fe2ebec
Clang 21.1.8
DISPLAY unset; EGL_PLATFORM=surfaceless; LIBGL_ALWAYS_SOFTWARE=true
Backend: PlatformEGLHeadless, desktop OpenGL
```
### Separately recorded prerequisites
These changes were identical on both sides and are not the candidate fix:
- Build the GLX and EGL Linux backends and select `PlatformEGLHeadless` for `EGL_PLATFORM=surfaceless` (v1.54.0 is otherwise GLX-only on Linux).
- Select a pbuffer-capable EGL config.
- Guard the v1.54.0 `glGetString(GL_EXTENSIONS)` result before constructing a string view; core desktop GL may return null. Current main already has this guard.
- Apply Clang 21 compatibility headers/flags.
### Minimal Filament-path test
This Google Test uses the existing `BackendTest` fixture. Its constructor creates the real driver through `PlatformFactory`; that initializes the shader-compiler pool and calls the target `platform.createContext(true)` on a worker. The sleep only keeps the fixture alive long enough for the asynchronous diagnostic.
```cpp
#include "BackendTest.h"
#include
#include
namespace test {
TEST_F(BackendTest, AuditWorkerContext) {
std::this_thread::sleep_for(std::chrono::milliseconds(200));
SUCCEED();
}
} // namespace test
```
Run in a fresh process:
```bash
env -u DISPLAY EGL_PLATFORM=surfaceless LIBGL_ALWAYS_SOFTWARE=true \
./backend_test --gtest_filter=BackendTest.AuditWorkerContext
```
Instrumentation immediately around `eglCreateContext` records the worker thread ID, `eglQueryAPI()`, the parent context client type, the complete context attribute array, the result, and an immediate `eglGetError()`.
### Baseline: no worker-thread API bind
```text
AUDIT createContext thread=137620147402432 shared=1 isOpenGL=1 api_before=0x30a0 parent_query=1 parent_api=0x30a2
AUDIT createContext thread=137620147402432 attrs=0x3098 0x4 0x30fb 0x1 0x31b3 0x1 0x3038
AUDIT createContext thread=137620147402432 result=(nil) egl_error=0x3009
eglCreateContext failed with unknown
[ PASSED ] 1 test.
Exit status: 0
```
Decoded constants:
```text
0x30a0 = EGL_OPENGL_ES_API (the worker's thread-local default)
0x30a2 = EGL_OPENGL_API (the parent context's client API)
0x3009 = EGL_BAD_MATCH
```
The Google Test exit is 0 because this Release build has the internal assert disabled; the null context and immediate EGL error are explicit. A debugger breakpoint on the first error captured this worker backtrace:
```text
PlatformEGL::logEglError
PlatformEGL::createContext(bool)
CompilerThreadPool::init(...)::{lambda()#1}
std::__1::__thread_proxy<...>
start_thread
clone3
```
### Isolated control: bind the parent's client API on the worker
The sole A/B change immediately before worker context creation was:
```cpp
if (shared && mIsOpenGL) {
if (!eglBindAPI(EGL_OPENGL_API)) {
logEglError("eglBindAPI");
return;
}
}
```
Control output:
```text
AUDIT createContext thread=134511618668224 shared=1 isOpenGL=1 api_before=0x30a0 parent_query=1 parent_api=0x30a2
AUDIT createContext thread=134511618668224 attrs=0x3098 0x4 0x30fb 0x1 0x31b3 0x1 0x3038
AUDIT createContext thread=134511618668224 api_after_bind=0x30a2
AUDIT createContext thread=134511618668224 result=0x7a5658001010 egl_error=0x3000
[ PASSED ] 1 test.
Exit status: 0
```
`0x3000` is `EGL_SUCCESS`. The attributes and all prerequisites are unchanged.
### Current-main source audit
At `6a63790752736e37809ab1a4585c04ab0fe2ebec`, `PlatformEGLHeadless::createDriver()` still binds `EGL_OPENGL_API` only on the creating/driver thread. The compiler-pool setup still invokes `platform.createContext(true)` on its worker, and `PlatformEGL::createContext` still has no matching bind. I did not substitute that inspection for the measured v1.54.0 run; it establishes that the relevant path has not been removed from current main.
### Result
The issue is the thread-local EGL client API. The driver thread's bind does not propagate to the compiler worker, whose default remains OpenGL ES. Mesa rejects a desktop-OpenGL shared-context request under that API with `EGL_BAD_MATCH`. Binding the matching API on the worker is sufficient to create the context successfully.
Contributor guide
Assessment
This issue has not been assessed yet.