From 2fe8a7279c4bbf6e2057db28a7efb0a7200024cf Mon Sep 17 00:00:00 2001 From: Jerry Ryle Date: Sun, 25 Oct 2026 16:24:43 -0700 Subject: [PATCH] fix(linux): reach the GPU headless through Mesa's surfaceless platform With neither DISPLAY nor WAYLAND_DISPLAY set, every hardware backend failed with "Could not open the default X display", because ANGLE's default display needs X11. A headless Linux process now tries ANGLE's OpenGL ES backend first and puts every candidate on Mesa's surfaceless platform, the attribute the SwiftShader path already uses. A process with a display keeps the old order and the old display. Tested on a Raspberry Pi 5 (Raspberry Pi OS Lite, Mesa 26.2.2) with no display server: the context reports OpenGL ES 3.0 on V3D 7.1, and webgl2-smoke, swiftshader-smoke and three-smoke pass. Co-Authored-By: Claude Opus 5.5 --- binding/egl_context_wrapper.cc | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/binding/egl_context_wrapper.cc b/binding/egl_context_wrapper.cc index 71b8c03..839420a 100644 --- a/binding/egl_context_wrapper.cc +++ b/binding/egl_context_wrapper.cc @@ -159,7 +159,23 @@ bool EGLContextWrapper::InitEGL(napi_env env, return static_cast(std::tolower(character)); }); +#if defined(__linux__) + // With neither an X11 nor a Wayland display, ANGLE's default display cannot + // open. Mesa's surfaceless platform needs no display, and ANGLE's OpenGL ES + // backend on it reaches the GPU, so a headless process tries that first. + auto unset = [](const char* name) { + const char* value = std::getenv(name); + return value == nullptr || value[0] == '\0'; + }; + const bool headless = unset("DISPLAY") && unset("WAYLAND_DISPLAY"); +#endif + std::vector platform_types; +#if defined(__linux__) && defined(EGL_PLATFORM_ANGLE_TYPE_OPENGLES_ANGLE) + if (headless && requested_platform.empty()) { + platform_types.push_back(EGL_PLATFORM_ANGLE_TYPE_OPENGLES_ANGLE); + } +#endif #if defined(__APPLE__) && defined(EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE) if (requested_platform.empty()) { platform_types.push_back(EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE); @@ -185,8 +201,9 @@ bool EGLContextWrapper::InitEGL(napi_env env, display_attributes.push_back(EGL_PLATFORM_ANGLE_TYPE_ANGLE); display_attributes.push_back(platform_type); #if defined(__linux__) - // SwiftShader renders pbuffers without X11/Wayland surface extensions. - if (requested_platform == "swiftshader") { + // SwiftShader renders pbuffers without X11/Wayland surface extensions, and + // a headless process has no X11 or Wayland display to open. + if (requested_platform == "swiftshader" || headless) { display_attributes.push_back(EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE); display_attributes.push_back(EGL_PLATFORM_SURFACELESS_MESA); }