diff --git a/docs/installation.md b/docs/installation.md index a8d3661f03..e84f315fa9 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -76,17 +76,23 @@ It is common to run into issues when installing **robosuite** on a Windows machi Within the MuJoCo package, there should be a file called `mujoco.dll`. If you installed robosuite using pip, copy and paste this file into `anaconda3\envs\{your env name}\Lib\site-packages\robosuite\utils `. If you installed robosuite from source, copy and paste this file directly into `robosuite\utils`. -4. You may also get an `EGL` issue. If this happens, please go into `robosuite\utils\binding_utils.py` (either in site-packages or in cloned repository depending on whether you installed from pip or source) and change `"egl"` to `"wgl"` at line 43. It should look like this: +4. If you need a non-default MuJoCo render backend, set `MUJOCO_GL` before importing robosuite. robosuite now honors an explicit `MUJOCO_GL` value instead of overwriting it. - ```python - if _SYSTEM == "Darwin": - os.environ["MUJOCO_GL"] = "cgl" - else: - os.environ["MUJOCO_GL"] = "wgl" + Common examples: + + ```sh + # Windows + set MUJOCO_GL=wgl + + # Linux GPU / headless EGL + export MUJOCO_GL=egl + + # Linux CPU under xvfb-run + xvfb-run -a env MUJOCO_GL=glfw python robosuite/demos/demo_random_action.py ``` 5. Test your **robosuite** installation by running ```sh $ python robosuite/demos/demo_random_action.py - ``` \ No newline at end of file + ``` diff --git a/robosuite/utils/binding_utils.py b/robosuite/utils/binding_utils.py index e69acedef7..331350c005 100644 --- a/robosuite/utils/binding_utils.py +++ b/robosuite/utils/binding_utils.py @@ -21,6 +21,7 @@ import subprocess import robosuite.macros as macros +from robosuite.utils.gl_backend import configure_mujoco_gl_backend _SYSTEM = platform.system() if _SYSTEM == "Windows": @@ -34,15 +35,7 @@ MUJOCO_EGL_DEVICE_ID in CUDA_VISIBLE_DEVICES ), "MUJOCO_EGL_DEVICE_ID needs to be set to one of the device id specified in CUDA_VISIBLE_DEVICES" -if macros.MUJOCO_GPU_RENDERING and os.environ.get("MUJOCO_GL", None) not in ["osmesa", "glx"]: - # If gpu rendering is specified in macros, then we enforce gpu - # option for rendering - if _SYSTEM == "Darwin": - os.environ["MUJOCO_GL"] = "cgl" - elif _SYSTEM == "Windows": - os.environ["MUJOCO_GL"] = "wgl" - else: - os.environ["MUJOCO_GL"] = "egl" +configure_mujoco_gl_backend(macros, system=_SYSTEM) _MUJOCO_GL = os.environ.get("MUJOCO_GL", "").lower().strip() diff --git a/robosuite/utils/gl_backend.py b/robosuite/utils/gl_backend.py new file mode 100644 index 0000000000..8c441f0b88 --- /dev/null +++ b/robosuite/utils/gl_backend.py @@ -0,0 +1,56 @@ +from __future__ import annotations + +import os +import platform +from typing import Any, MutableMapping + + +def _normalize_backend(value: str | None) -> str | None: + if value is None: + return None + backend = value.strip().lower() + return backend or None + + +def platform_default_backend(system: str | None = None) -> str: + system_name = platform.system() if system is None else system + if system_name == "Darwin": + return "cgl" + if system_name == "Windows": + return "wgl" + return "egl" + + +def resolve_mujoco_gl_backend( + macros_module: Any, + *, + environ: MutableMapping[str, str] | None = None, + system: str | None = None, +) -> str: + environ = os.environ if environ is None else environ + + explicit_backend = _normalize_backend(environ.get("MUJOCO_GL")) + if explicit_backend is not None: + return explicit_backend + + if getattr(macros_module, "MUJOCO_GPU_RENDERING", False): + return platform_default_backend(system=system) + + return "" + + +def configure_mujoco_gl_backend( + macros_module: Any, + *, + environ: MutableMapping[str, str] | None = None, + system: str | None = None, +) -> str: + environ = os.environ if environ is None else environ + backend = resolve_mujoco_gl_backend( + macros_module, + environ=environ, + system=system, + ) + if backend and _normalize_backend(environ.get("MUJOCO_GL")) is None: + environ["MUJOCO_GL"] = backend + return backend diff --git a/tests/test_renderers/test_gl_backend_selection.py b/tests/test_renderers/test_gl_backend_selection.py new file mode 100644 index 0000000000..0582c2b1d8 --- /dev/null +++ b/tests/test_renderers/test_gl_backend_selection.py @@ -0,0 +1,90 @@ +import os +import platform +import subprocess +import sys +from pathlib import Path +from types import SimpleNamespace + +from robosuite.utils.gl_backend import configure_mujoco_gl_backend, resolve_mujoco_gl_backend + +REPO_ROOT = Path(__file__).resolve().parents[2] + + +def _macros(**kwargs): + values = { + "MUJOCO_GPU_RENDERING": True, + } + values.update(kwargs) + return SimpleNamespace(**values) + + +def _run_binding_utils_probe(*, env_updates=None, unset=None): + env = os.environ.copy() + existing_pythonpath = env.get("PYTHONPATH") + env["PYTHONPATH"] = ( + str(REPO_ROOT) + if not existing_pythonpath + else str(REPO_ROOT) + os.pathsep + existing_pythonpath + ) + + if unset is not None: + for key in unset: + env.pop(key, None) + + if env_updates is not None: + env.update(env_updates) + + result = subprocess.run( + [sys.executable, "-c", "import os, robosuite; print(os.environ.get('MUJOCO_GL', ''))"], + cwd=REPO_ROOT, + env=env, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + check=True, + ) + return result.stdout.strip().splitlines()[-1] + + +def test_explicit_env_backend_is_preserved(): + environ = {"MUJOCO_GL": "glfw"} + + backend = configure_mujoco_gl_backend(_macros(), environ=environ, system="Linux") + + assert backend == "glfw" + assert environ["MUJOCO_GL"] == "glfw" + + +def test_linux_gpu_default_remains_egl(): + backend = resolve_mujoco_gl_backend( + _macros(MUJOCO_GPU_RENDERING=True), + environ={}, + system="Linux", + ) + + assert backend == "egl" + + +def test_gpu_default_is_empty_when_gpu_rendering_is_disabled(): + backend = resolve_mujoco_gl_backend( + _macros(MUJOCO_GPU_RENDERING=False), + environ={}, + system="Linux", + ) + + assert backend == "" + + +def test_binding_utils_import_preserves_explicit_env(): + resolved_backend = _run_binding_utils_probe(env_updates={"MUJOCO_GL": "glfw"}) + + assert resolved_backend == "glfw" + + +def test_binding_utils_import_defaults_to_egl_on_linux(): + if platform.system() != "Linux": + return + + resolved_backend = _run_binding_utils_probe(unset={"MUJOCO_GL"}) + + assert resolved_backend == "egl"