add diagnostics, update mac initialization
Some checks failed
Python Linting / Run Ruff (push) Has been cancelled
Python Linting / Run Pylint (push) Has been cancelled

This commit is contained in:
pythongosssss 2026-01-30 12:26:04 -08:00
parent 59b955ff54
commit c3d07bec6d

View File

@ -19,12 +19,15 @@ logger = logging.getLogger(__name__)
def _check_opengl_availability(): def _check_opengl_availability():
"""Early check for OpenGL availability. Raises RuntimeError if unlikely to work.""" """Early check for OpenGL availability. Raises RuntimeError if unlikely to work."""
logger.debug("_check_opengl_availability: starting")
missing = [] missing = []
# Check Python packages (using find_spec to avoid importing) # Check Python packages (using find_spec to avoid importing)
logger.debug("_check_opengl_availability: checking for glfw package")
if importlib.util.find_spec("glfw") is None: if importlib.util.find_spec("glfw") is None:
missing.append("glfw") missing.append("glfw")
logger.debug("_check_opengl_availability: checking for OpenGL package")
if importlib.util.find_spec("OpenGL") is None: if importlib.util.find_spec("OpenGL") is None:
missing.append("PyOpenGL") missing.append("PyOpenGL")
@ -34,11 +37,15 @@ def _check_opengl_availability():
) )
# On Linux without display, check if headless backends are available # On Linux without display, check if headless backends are available
logger.debug(f"_check_opengl_availability: platform={sys.platform}")
if sys.platform.startswith("linux"): if sys.platform.startswith("linux"):
has_display = os.environ.get("DISPLAY") or os.environ.get("WAYLAND_DISPLAY") has_display = os.environ.get("DISPLAY") or os.environ.get("WAYLAND_DISPLAY")
logger.debug(f"_check_opengl_availability: has_display={bool(has_display)}")
if not has_display: if not has_display:
# Check for EGL or OSMesa libraries # Check for EGL or OSMesa libraries
logger.debug("_check_opengl_availability: checking for EGL library")
has_egl = ctypes.util.find_library("EGL") has_egl = ctypes.util.find_library("EGL")
logger.debug("_check_opengl_availability: checking for OSMesa library")
has_osmesa = ctypes.util.find_library("OSMesa") has_osmesa = ctypes.util.find_library("OSMesa")
# Error disabled for CI as it fails this check # Error disabled for CI as it fails this check
@ -49,8 +56,11 @@ def _check_opengl_availability():
# ) # )
logger.debug(f"Headless mode: EGL={'yes' if has_egl else 'no'}, OSMesa={'yes' if has_osmesa else 'no'}") logger.debug(f"Headless mode: EGL={'yes' if has_egl else 'no'}, OSMesa={'yes' if has_osmesa else 'no'}")
logger.debug("_check_opengl_availability: completed")
# Run early check at import time # Run early check at import time
logger.debug("nodes_glsl: running _check_opengl_availability at import time")
_check_opengl_availability() _check_opengl_availability()
# OpenGL modules - initialized lazily when context is created # OpenGL modules - initialized lazily when context is created
@ -63,8 +73,10 @@ def _import_opengl():
"""Import OpenGL module. Called after context is created.""" """Import OpenGL module. Called after context is created."""
global gl global gl
if gl is None: if gl is None:
logger.debug("_import_opengl: importing OpenGL.GL")
import OpenGL.GL as _gl import OpenGL.GL as _gl
gl = _gl gl = _gl
logger.debug("_import_opengl: import completed")
return gl return gl
@ -136,30 +148,44 @@ def _detect_output_count(source: str) -> int:
def _init_glfw(): def _init_glfw():
"""Initialize GLFW. Returns (window, glfw_module). Raises RuntimeError on failure.""" """Initialize GLFW. Returns (window, glfw_module). Raises RuntimeError on failure."""
logger.debug("_init_glfw: starting")
# On macOS, glfw.init() must be called from main thread or it hangs forever
if sys.platform == "darwin":
logger.debug("_init_glfw: skipping on macOS")
raise RuntimeError("GLFW backend not supported on macOS")
logger.debug("_init_glfw: importing glfw module")
import glfw as _glfw import glfw as _glfw
logger.debug("_init_glfw: calling glfw.init()")
if not _glfw.init(): if not _glfw.init():
raise RuntimeError("glfw.init() failed") raise RuntimeError("glfw.init() failed")
try: try:
logger.debug("_init_glfw: setting window hints")
_glfw.window_hint(_glfw.VISIBLE, _glfw.FALSE) _glfw.window_hint(_glfw.VISIBLE, _glfw.FALSE)
_glfw.window_hint(_glfw.CONTEXT_VERSION_MAJOR, 3) _glfw.window_hint(_glfw.CONTEXT_VERSION_MAJOR, 3)
_glfw.window_hint(_glfw.CONTEXT_VERSION_MINOR, 3) _glfw.window_hint(_glfw.CONTEXT_VERSION_MINOR, 3)
_glfw.window_hint(_glfw.OPENGL_PROFILE, _glfw.OPENGL_CORE_PROFILE) _glfw.window_hint(_glfw.OPENGL_PROFILE, _glfw.OPENGL_CORE_PROFILE)
logger.debug("_init_glfw: calling create_window()")
window = _glfw.create_window(64, 64, "ComfyUI GLSL", None, None) window = _glfw.create_window(64, 64, "ComfyUI GLSL", None, None)
if not window: if not window:
raise RuntimeError("glfw.create_window() failed") raise RuntimeError("glfw.create_window() failed")
logger.debug("_init_glfw: calling make_context_current()")
_glfw.make_context_current(window) _glfw.make_context_current(window)
logger.debug("_init_glfw: completed successfully")
return window, _glfw return window, _glfw
except Exception: except Exception:
logger.debug("_init_glfw: failed, terminating glfw")
_glfw.terminate() _glfw.terminate()
raise raise
def _init_egl(): def _init_egl():
"""Initialize EGL for headless rendering. Returns (display, context, surface, EGL_module). Raises RuntimeError on failure.""" """Initialize EGL for headless rendering. Returns (display, context, surface, EGL_module). Raises RuntimeError on failure."""
logger.debug("_init_egl: starting")
from OpenGL import EGL as _EGL from OpenGL import EGL as _EGL
from OpenGL.EGL import ( from OpenGL.EGL import (
eglGetDisplay, eglInitialize, eglChooseConfig, eglCreateContext, eglGetDisplay, eglInitialize, eglChooseConfig, eglCreateContext,
@ -170,20 +196,24 @@ def _init_egl():
EGL_RED_SIZE, EGL_GREEN_SIZE, EGL_BLUE_SIZE, EGL_ALPHA_SIZE, EGL_DEPTH_SIZE, EGL_RED_SIZE, EGL_GREEN_SIZE, EGL_BLUE_SIZE, EGL_ALPHA_SIZE, EGL_DEPTH_SIZE,
EGL_WIDTH, EGL_HEIGHT, EGL_OPENGL_API, EGL_WIDTH, EGL_HEIGHT, EGL_OPENGL_API,
) )
logger.debug("_init_egl: imports completed")
display = None display = None
context = None context = None
surface = None surface = None
try: try:
logger.debug("_init_egl: calling eglGetDisplay()")
display = eglGetDisplay(EGL_DEFAULT_DISPLAY) display = eglGetDisplay(EGL_DEFAULT_DISPLAY)
if display == _EGL.EGL_NO_DISPLAY: if display == _EGL.EGL_NO_DISPLAY:
raise RuntimeError("eglGetDisplay() failed") raise RuntimeError("eglGetDisplay() failed")
logger.debug("_init_egl: calling eglInitialize()")
major, minor = _EGL.EGLint(), _EGL.EGLint() major, minor = _EGL.EGLint(), _EGL.EGLint()
if not eglInitialize(display, major, minor): if not eglInitialize(display, major, minor):
display = None # Not initialized, don't terminate display = None # Not initialized, don't terminate
raise RuntimeError("eglInitialize() failed") raise RuntimeError("eglInitialize() failed")
logger.debug(f"_init_egl: EGL version {major.value}.{minor.value}")
config_attribs = [ config_attribs = [
EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
@ -196,10 +226,12 @@ def _init_egl():
if not eglChooseConfig(display, config_attribs, configs, 1, num_configs) or num_configs.value == 0: if not eglChooseConfig(display, config_attribs, configs, 1, num_configs) or num_configs.value == 0:
raise RuntimeError("eglChooseConfig() failed") raise RuntimeError("eglChooseConfig() failed")
config = configs[0] config = configs[0]
logger.debug(f"_init_egl: config chosen, num_configs={num_configs.value}")
if not eglBindAPI(EGL_OPENGL_API): if not eglBindAPI(EGL_OPENGL_API):
raise RuntimeError("eglBindAPI() failed") raise RuntimeError("eglBindAPI() failed")
logger.debug("_init_egl: calling eglCreateContext()")
context_attribs = [ context_attribs = [
_EGL.EGL_CONTEXT_MAJOR_VERSION, 3, _EGL.EGL_CONTEXT_MAJOR_VERSION, 3,
_EGL.EGL_CONTEXT_MINOR_VERSION, 3, _EGL.EGL_CONTEXT_MINOR_VERSION, 3,
@ -210,17 +242,21 @@ def _init_egl():
if context == EGL_NO_CONTEXT: if context == EGL_NO_CONTEXT:
raise RuntimeError("eglCreateContext() failed") raise RuntimeError("eglCreateContext() failed")
logger.debug("_init_egl: calling eglCreatePbufferSurface()")
pbuffer_attribs = [EGL_WIDTH, 64, EGL_HEIGHT, 64, EGL_NONE] pbuffer_attribs = [EGL_WIDTH, 64, EGL_HEIGHT, 64, EGL_NONE]
surface = eglCreatePbufferSurface(display, config, pbuffer_attribs) surface = eglCreatePbufferSurface(display, config, pbuffer_attribs)
if surface == _EGL.EGL_NO_SURFACE: if surface == _EGL.EGL_NO_SURFACE:
raise RuntimeError("eglCreatePbufferSurface() failed") raise RuntimeError("eglCreatePbufferSurface() failed")
logger.debug("_init_egl: calling eglMakeCurrent()")
if not eglMakeCurrent(display, surface, surface, context): if not eglMakeCurrent(display, surface, surface, context):
raise RuntimeError("eglMakeCurrent() failed") raise RuntimeError("eglMakeCurrent() failed")
logger.debug("_init_egl: completed successfully")
return display, context, surface, _EGL return display, context, surface, _EGL
except Exception: except Exception:
logger.debug("_init_egl: failed, cleaning up")
# Clean up any resources on failure # Clean up any resources on failure
if surface is not None: if surface is not None:
eglDestroySurface(display, surface) eglDestroySurface(display, surface)
@ -235,13 +271,16 @@ def _init_osmesa():
"""Initialize OSMesa for software rendering. Returns (context, buffer). Raises RuntimeError on failure.""" """Initialize OSMesa for software rendering. Returns (context, buffer). Raises RuntimeError on failure."""
import ctypes import ctypes
logger.debug("_init_osmesa: starting")
os.environ["PYOPENGL_PLATFORM"] = "osmesa" os.environ["PYOPENGL_PLATFORM"] = "osmesa"
logger.debug("_init_osmesa: importing OpenGL.osmesa")
from OpenGL import GL as _gl from OpenGL import GL as _gl
from OpenGL.osmesa import ( from OpenGL.osmesa import (
OSMesaCreateContextExt, OSMesaMakeCurrent, OSMesaDestroyContext, OSMesaCreateContextExt, OSMesaMakeCurrent, OSMesaDestroyContext,
OSMESA_RGBA, OSMESA_RGBA,
) )
logger.debug("_init_osmesa: imports completed")
ctx = OSMesaCreateContextExt(OSMESA_RGBA, 24, 0, 0, None) ctx = OSMesaCreateContextExt(OSMESA_RGBA, 24, 0, 0, None)
if not ctx: if not ctx:
@ -250,10 +289,12 @@ def _init_osmesa():
width, height = 64, 64 width, height = 64, 64
buffer = (ctypes.c_ubyte * (width * height * 4))() buffer = (ctypes.c_ubyte * (width * height * 4))()
logger.debug("_init_osmesa: calling OSMesaMakeCurrent()")
if not OSMesaMakeCurrent(ctx, buffer, _gl.GL_UNSIGNED_BYTE, width, height): if not OSMesaMakeCurrent(ctx, buffer, _gl.GL_UNSIGNED_BYTE, width, height):
OSMesaDestroyContext(ctx) OSMesaDestroyContext(ctx)
raise RuntimeError("OSMesaMakeCurrent() failed") raise RuntimeError("OSMesaMakeCurrent() failed")
logger.debug("_init_osmesa: completed successfully")
return ctx, buffer return ctx, buffer
@ -273,9 +314,12 @@ class GLContext:
def __init__(self): def __init__(self):
if GLContext._initialized: if GLContext._initialized:
logger.debug("GLContext.__init__: already initialized, skipping")
return return
GLContext._initialized = True GLContext._initialized = True
logger.debug("GLContext.__init__: starting initialization")
global glfw, EGL global glfw, EGL
import time import time
@ -292,24 +336,33 @@ class GLContext:
# Try backends in order: GLFW → EGL → OSMesa # Try backends in order: GLFW → EGL → OSMesa
errors = [] errors = []
logger.debug("GLContext.__init__: trying GLFW backend")
try: try:
self._window, glfw = _init_glfw() self._window, glfw = _init_glfw()
self._backend = "glfw" self._backend = "glfw"
logger.debug("GLContext.__init__: GLFW backend succeeded")
except Exception as e: except Exception as e:
logger.debug(f"GLContext.__init__: GLFW backend failed: {e}")
errors.append(("GLFW", e)) errors.append(("GLFW", e))
if self._backend is None: if self._backend is None:
logger.debug("GLContext.__init__: trying EGL backend")
try: try:
self._egl_display, self._egl_context, self._egl_surface, EGL = _init_egl() self._egl_display, self._egl_context, self._egl_surface, EGL = _init_egl()
self._backend = "egl" self._backend = "egl"
logger.debug("GLContext.__init__: EGL backend succeeded")
except Exception as e: except Exception as e:
logger.debug(f"GLContext.__init__: EGL backend failed: {e}")
errors.append(("EGL", e)) errors.append(("EGL", e))
if self._backend is None: if self._backend is None:
logger.debug("GLContext.__init__: trying OSMesa backend")
try: try:
self._osmesa_ctx, self._osmesa_buffer = _init_osmesa() self._osmesa_ctx, self._osmesa_buffer = _init_osmesa()
self._backend = "osmesa" self._backend = "osmesa"
logger.debug("GLContext.__init__: OSMesa backend succeeded")
except Exception as e: except Exception as e:
logger.debug(f"GLContext.__init__: OSMesa backend failed: {e}")
errors.append(("OSMesa", e)) errors.append(("OSMesa", e))
if self._backend is None: if self._backend is None:
@ -320,7 +373,9 @@ class GLContext:
) )
elif sys.platform == "darwin": elif sys.platform == "darwin":
platform_help = ( platform_help = (
"macOS: Ensure display is available. For headless, try virtual display." "macOS: GLFW is not supported.\n"
" Install OSMesa via Homebrew: brew install mesa\n"
" Then: pip install PyOpenGL PyOpenGL-accelerate"
) )
else: else:
platform_help = ( platform_help = (
@ -334,20 +389,23 @@ class GLContext:
raise RuntimeError( raise RuntimeError(
f"Failed to create OpenGL context.\n\n" f"Failed to create OpenGL context.\n\n"
f"Backend errors:\n{error_details}\n\n" f"Backend errors:\n{error_details}\n\n"
f"{platform_help}\n\n" f"{platform_help}"
"Python packages: pip install PyOpenGL PyOpenGL-accelerate glfw"
) )
# Now import OpenGL.GL (after context is current) # Now import OpenGL.GL (after context is current)
logger.debug("GLContext.__init__: importing OpenGL.GL")
_import_opengl() _import_opengl()
# Create VAO (required for core profile, but OSMesa may use compat profile) # Create VAO (required for core profile, but OSMesa may use compat profile)
logger.debug("GLContext.__init__: creating VAO")
self._vao = None self._vao = None
try: try:
vao = gl.glGenVertexArrays(1) vao = gl.glGenVertexArrays(1)
gl.glBindVertexArray(vao) gl.glBindVertexArray(vao)
self._vao = vao # Only store after successful bind self._vao = vao # Only store after successful bind
except Exception: logger.debug("GLContext.__init__: VAO created successfully")
except Exception as e:
logger.debug(f"GLContext.__init__: VAO creation failed (may be expected for OSMesa): {e}")
# OSMesa with older Mesa may not support VAOs # OSMesa with older Mesa may not support VAOs
# Clean up if we created but couldn't bind # Clean up if we created but couldn't bind
if vao: if vao: