diff --git a/comfy_extras/nodes_glsl.py b/comfy_extras/nodes_glsl.py index ef5c1bee0..1a9180933 100644 --- a/comfy_extras/nodes_glsl.py +++ b/comfy_extras/nodes_glsl.py @@ -315,16 +315,18 @@ class GLContext: Tries backends in order: GLFW (desktop) → EGL (headless GPU) → OSMesa (software). """ - _instance = None - _initialized = False + __instance: 'GLContext' = None # The singleton def __new__(cls): - if cls._instance is None: - cls._instance = super().__new__(cls) - return cls._instance + # Since ``GLContext`` is a singleton anyway, we should store it + # explicitly in ``GLContext.__instance``, NOT in ``cls.__instance``. + if GLContext.__instance is None: + GLContext.__instance = super().__new__(cls) + assert isinstance(GLContext.__instance, GLContext) + return GLContext.__instance def __init__(self): - if GLContext._initialized: + if GLContext.__instance is not None: logger.debug("GLContext.__init__: already initialized, skipping") return @@ -333,7 +335,7 @@ class GLContext: global glfw, EGL import time - start = time.perf_counter() + start_time: float = time.perf_counter() self._backend = None self._window = None @@ -344,64 +346,7 @@ class GLContext: self._osmesa_buffer = None self._vao = None - # Try backends in order: GLFW → EGL → OSMesa - errors = [] - - logger.debug("GLContext.__init__: trying GLFW backend") - try: - self._window, glfw = _init_glfw() - self._backend = "glfw" - logger.debug("GLContext.__init__: GLFW backend succeeded") - except Exception as e: - logger.debug(f"GLContext.__init__: GLFW backend failed: {e}") - errors.append(("GLFW", e)) - - if self._backend is None: - logger.debug("GLContext.__init__: trying EGL backend") - try: - self._egl_display, self._egl_context, self._egl_surface, EGL = _init_egl() - self._backend = "egl" - logger.debug("GLContext.__init__: EGL backend succeeded") - except Exception as e: - logger.debug(f"GLContext.__init__: EGL backend failed: {e}") - errors.append(("EGL", e)) - - if self._backend is None: - logger.debug("GLContext.__init__: trying OSMesa backend") - try: - self._osmesa_ctx, self._osmesa_buffer = _init_osmesa() - self._backend = "osmesa" - logger.debug("GLContext.__init__: OSMesa backend succeeded") - except Exception as e: - logger.debug(f"GLContext.__init__: OSMesa backend failed: {e}") - errors.append(("OSMesa", e)) - - if self._backend is None: - if sys.platform == "win32": - platform_help = ( - "Windows: Ensure GPU drivers are installed and display is available.\n" - " CPU-only/headless mode is not supported on Windows." - ) - elif sys.platform == "darwin": - platform_help = ( - "macOS: GLFW is not supported.\n" - " Install OSMesa via Homebrew: brew install mesa\n" - " Then: pip install PyOpenGL PyOpenGL-accelerate" - ) - else: - platform_help = ( - "Linux: Install one of these backends:\n" - " Desktop: sudo apt install libgl1-mesa-glx libglfw3\n" - " Headless with GPU: sudo apt install libegl1-mesa libgl1-mesa-dri\n" - " Headless (CPU): sudo apt install libosmesa6" - ) - - error_details = "\n".join(f" {name}: {err}" for name, err in errors) - raise RuntimeError( - f"Failed to create OpenGL context.\n\n" - f"Backend errors:\n{error_details}\n\n" - f"{platform_help}" - ) + self._init_try_backend() # Now import OpenGL.GL (after context is current) logger.debug("GLContext.__init__: importing OpenGL.GL") @@ -424,7 +369,7 @@ class GLContext: except Exception: pass - elapsed = (time.perf_counter() - start) * 1000 + elapsed = (time.perf_counter() - start_time) * 1000 # Log device info renderer = gl.glGetString(gl.GL_RENDERER) @@ -434,9 +379,75 @@ class GLContext: vendor = vendor.decode() if vendor else "Unknown" version = version.decode() if version else "Unknown" - GLContext._initialized = True logger.info(f"GLSL context initialized in {elapsed:.1f}ms ({self._backend}) - {renderer} ({vendor}), GL {version}") + def _init_try_backend(self): + """Try to init backends in fallback order: GLFW → EGL → OSMesa. Raises RuntimeError on failure. + """ + global glfw, EGL + + errors = [] + self._backend = None + + logger.debug("GLContext.__init__: trying GLFW backend") + try: + self._window, glfw = _init_glfw() + self._backend = "glfw" + logger.debug("GLContext.__init__: GLFW backend succeeded") + return + except Exception as e: + logger.debug(f"GLContext.__init__: GLFW backend failed: {e}") + errors.append(("GLFW", e)) + + logger.debug("GLContext.__init__: trying EGL backend") + try: + self._egl_display, self._egl_context, self._egl_surface, EGL = _init_egl() + self._backend = "egl" + logger.debug("GLContext.__init__: EGL backend succeeded") + return + except Exception as e: + logger.debug(f"GLContext.__init__: EGL backend failed: {e}") + errors.append(("EGL", e)) + + logger.debug("GLContext.__init__: trying OSMesa backend") + try: + self._osmesa_ctx, self._osmesa_buffer = _init_osmesa() + self._backend = "osmesa" + logger.debug("GLContext.__init__: OSMesa backend succeeded") + return + except Exception as e: + logger.debug(f"GLContext.__init__: OSMesa backend failed: {e}") + errors.append(("OSMesa", e)) + + # If we still haven't returned, none of the backends succeeded. + # Let's raise the error. + + if sys.platform == "win32": + platform_help = ( + "Windows: Ensure GPU drivers are installed and display is available.\n" + " CPU-only/headless mode is not supported on Windows." + ) + elif sys.platform == "darwin": + platform_help = ( + "macOS: GLFW is not supported.\n" + " Install OSMesa via Homebrew: brew install mesa\n" + " Then: pip install PyOpenGL PyOpenGL-accelerate" + ) + else: + platform_help = ( + "Linux: Install one of these backends:\n" + " Desktop: sudo apt install libgl1-mesa-glx libglfw3\n" + " Headless with GPU: sudo apt install libegl1-mesa libgl1-mesa-dri\n" + " Headless (CPU): sudo apt install libosmesa6" + ) + + error_details = "\n".join(f" {name}: {err}" for name, err in errors) + raise RuntimeError( + f"Failed to create OpenGL context.\n\n" + f"Backend errors:\n{error_details}\n\n" + f"{platform_help}" + ) + def make_current(self): if self._backend == "glfw": glfw.make_context_current(self._window)