This commit is contained in:
sam-kpm 2026-05-02 09:08:59 -07:00 committed by GitHub
commit 7ffd770f59
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -2,6 +2,7 @@ import os
import sys
import re
import logging
import ctypes
import ctypes.util
import importlib.util
from typing import TypedDict
@ -196,6 +197,73 @@ def _init_glfw():
raise
def _egl_device_display(eglInitialize):
"""Obtain an EGLDisplay via EGL_EXT_platform_device for headless GPU rendering.
EGL_DEFAULT_DISPLAY fails on headless NVIDIA (EGL_BAD_ACCESS) because there is
no X/Wayland compositor. The correct approach is to enumerate EGL devices and
obtain a display from a specific device handle using EGL_EXT_platform_device.
PyOpenGL's egl_get_devices() wrapper does not reliably resolve eglQueryDevicesEXT
in this scenario, so both extension functions are loaded from libEGL directly via
ctypes. Returns an initialized (display, major, minor) tuple.
"""
logger.debug("_egl_device_display: starting")
libegl_name = ctypes.util.find_library("EGL")
if not libegl_name:
raise RuntimeError("libEGL not found")
_libegl = ctypes.CDLL(libegl_name)
_get_proc = _libegl.eglGetProcAddress
_get_proc.restype = ctypes.c_void_p
_get_proc.argtypes = [ctypes.c_char_p]
_query_devices_ptr = _get_proc(b"eglQueryDevicesEXT")
if not _query_devices_ptr:
raise RuntimeError("eglQueryDevicesEXT not available — install libnvidia-egl-gbm1 or libegl-mesa0")
# EGLboolean is unsigned int (32-bit) in the EGL spec, not C99 _Bool.
_query_devices = ctypes.CFUNCTYPE(
ctypes.c_uint32,
ctypes.c_int32, ctypes.POINTER(ctypes.c_void_p), ctypes.POINTER(ctypes.c_int32),
)(_query_devices_ptr)
_get_platform_display_ptr = _get_proc(b"eglGetPlatformDisplayEXT")
if not _get_platform_display_ptr:
raise RuntimeError("eglGetPlatformDisplayEXT not available in libEGL")
_get_platform_display = ctypes.CFUNCTYPE(
ctypes.c_void_p, ctypes.c_uint32, ctypes.c_void_p, ctypes.c_void_p,
)(_get_platform_display_ptr)
# Two-pass query: first get the count, then fetch all devices.
count = ctypes.c_int32(0)
if not _query_devices(0, None, ctypes.byref(count)) or count.value == 0:
raise RuntimeError("eglQueryDevicesEXT() found no EGL devices")
raw_devices = (ctypes.c_void_p * count.value)()
if not _query_devices(count.value, raw_devices, ctypes.byref(count)):
raise RuntimeError("eglQueryDevicesEXT() failed on second call")
logger.debug(f"_egl_device_display: found {count.value} EGL device(s)")
EGL_PLATFORM_DEVICE_EXT = 0x313F
# Try each device in order; some may not support eglInitialize (e.g. non-render nodes).
for i, raw_device in enumerate(raw_devices[:count.value]):
raw_display = _get_platform_display(EGL_PLATFORM_DEVICE_EXT, raw_device, None)
if not raw_display:
logger.debug(f"_egl_device_display: device {i} eglGetPlatformDisplayEXT returned NULL, skipping")
continue
# Cast the raw pointer to the opaque EGLDisplay type (c_void_p) that PyOpenGL uses.
display = ctypes.c_void_p(raw_display)
major, minor = ctypes.c_int32(0), ctypes.c_int32(0)
try:
if eglInitialize(display, major, minor):
logger.debug(f"_egl_device_display: device {i} succeeded, EGL version {major.value}.{minor.value}")
return display, major, minor
except Exception:
pass
logger.debug(f"_egl_device_display: device {i} eglInitialize failed, skipping")
raise RuntimeError(f"eglInitialize() failed on all {count.value} enumerated EGL device(s)")
def _init_egl():
"""Initialize EGL for headless rendering. Returns (display, context, surface, EGL_module). Raises RuntimeError on failure."""
logger.debug("_init_egl: starting")
@ -216,16 +284,24 @@ def _init_egl():
surface = None
try:
logger.debug("_init_egl: calling eglGetDisplay()")
display = eglGetDisplay(EGL_DEFAULT_DISPLAY)
if display == _EGL.EGL_NO_DISPLAY:
raise RuntimeError("eglGetDisplay() failed")
logger.debug("_init_egl: calling eglInitialize()")
# Try EGL_DEFAULT_DISPLAY first (works when a display server is present).
# Fall back to device enumeration for headless setups (e.g. NVIDIA with no
# X/Wayland). Both eglGetDisplay failure and eglInitialize failure (which may
# return False or raise EGLError depending on PyOpenGL version) trigger the
# fallback.
major, minor = _EGL.EGLint(), _EGL.EGLint()
if not eglInitialize(display, major, minor):
default_ok = False
try:
display = eglGetDisplay(EGL_DEFAULT_DISPLAY)
if display != _EGL.EGL_NO_DISPLAY and eglInitialize(display, major, minor):
default_ok = True
except Exception:
pass
if not default_ok:
display = None # Not initialized, don't terminate
raise RuntimeError("eglInitialize() failed")
logger.debug("_init_egl: EGL_DEFAULT_DISPLAY failed, falling back to EGL device enumeration")
display, major, minor = _egl_device_display(eglInitialize)
logger.debug(f"_init_egl: EGL version {major.value}.{minor.value}")
config_attribs = [