google / google/filament

PlatformEGL shared compiler contexts do not bind desktop OpenGL API on worker threads

Open
#10,397 0 comments 0 reactions 1 assignee Claimed by @z3moon View on GitHub
opengl
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.