diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b81d0fbb..6a2379a5 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -42,6 +42,28 @@ jobs: - name: Ensure all unittests(pytest) are passing run: xvfb-run make pytest + pythonpackage-macos: + runs-on: macos-14 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v4 + - name: Set up Python 3.11 + uses: actions/setup-python@v5 + with: + python-version: "3.11" + cache: "pip" + - name: upgrade pip + run: pip install --upgrade pip + - name: Install building dependencies + run: python -m pip install --group build_deps + - name: Install wheel + run: python -m pip install wheel + - name: Build and install rcs-core + run: python -m pip install -ve . --no-build-isolation + - name: Import smoke test + run: python -c "import rcs; from rcs import _core; import rcs.sim.sim; print('rcs-core import OK on macOS')" + check-paths: runs-on: ubuntu-latest outputs: diff --git a/README.md b/README.md index 9bd70c7f..86646409 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,7 @@ if __name__ == "__main__": > **Note:** This and other examples can be found in the [`examples/`]() folder. ## 🛠️ Installation +* *Platform support: The core package `rcs-core` (MuJoCo simulation + Python API) is supported on **Linux and macOS** (Apple Silicon / arm64). The hardware extensions are **Linux only**.* * *For Python >3.11: The `rcs_realsense` extension won't work due to the `pyrealsense2` version RCS utilizes.* * *For Python >3.12: The `ompl` python module is currently not available on PyPI. If OMPL is not used, it is safe to remove this dependency in `pyproject.toml`.* ### Via PyPI/pip @@ -170,6 +171,8 @@ export RCS_PREFIX=/path/to/rcs-assets RCS supports various hardware extensions to seamlessly connect your policies to the real world (e.g., FR3, xArm7, YAM, RealSense). These are located in the `extensions` directory. +> **Note:** Hardware extensions are supported on **Linux only**. On macOS you can use the core `rcs-core` package for simulation, but the hardware extensions are not supported. + To install a specific robot extension (example for Franka FR3): ```shell diff --git a/cmake/FindMuJoCo.cmake b/cmake/FindMuJoCo.cmake index 5b670758..39799a5b 100644 --- a/cmake/FindMuJoCo.cmake +++ b/cmake/FindMuJoCo.cmake @@ -38,18 +38,35 @@ if (NOT MuJoCo_FOUND) return() endif() - file(GLOB mujoco_library_path "${MUJOCO_PATH}/libmujoco.so.*") - if (NOT mujoco_library_path) + set(_mujoco_library_globs) + if (APPLE) + list(APPEND _mujoco_library_globs "${MUJOCO_PATH}/libmujoco.*.dylib") + elseif (WIN32) + list(APPEND _mujoco_library_globs "${MUJOCO_PATH}/mujoco.dll") + list(APPEND _mujoco_library_globs "${MUJOCO_PATH}/bin/mujoco.dll") + else() + list(APPEND _mujoco_library_globs "${MUJOCO_PATH}/libmujoco.so.*") + endif() + + file(GLOB mujoco_library_paths LIST_DIRECTORIES FALSE ${_mujoco_library_globs}) + list(LENGTH mujoco_library_paths _mujoco_library_count) + if (_mujoco_library_count EQUAL 0) set(MuJoCo_FOUND FALSE) if (MuJoCo_FIND_REQUIRED) - message(FATAL_ERROR "Could not find MuJoCo. Please install MuJoCo using pip 4.") + message(FATAL_ERROR "Could not find MuJoCo shared library. Searched: ${_mujoco_library_globs}") endif() return() endif() + list(GET mujoco_library_paths 0 mujoco_library_path) # Extract version from the library filename cmake_path(GET mujoco_library_path FILENAME mujoco_library_filename) - string(REPLACE "libmujoco.so." "" MuJoCo_VERSION "${mujoco_library_filename}") + set(MuJoCo_VERSION "") + if (mujoco_library_filename MATCHES "^libmujoco\\.so\\.(.+)$") + set(MuJoCo_VERSION "${CMAKE_MATCH_1}") + elseif (mujoco_library_filename MATCHES "^libmujoco\\.(.+)\\.dylib$") + set(MuJoCo_VERSION "${CMAKE_MATCH_1}") + endif() # Create the imported target add_library(MuJoCo::MuJoCo SHARED IMPORTED) @@ -59,6 +76,9 @@ if (NOT MuJoCo_FOUND) PROPERTIES IMPORTED_LOCATION "${mujoco_library_path}" ) + if (APPLE) + set_target_properties(MuJoCo::MuJoCo PROPERTIES IMPORTED_NO_SONAME TRUE) + endif() set(MuJoCo_FOUND TRUE) endif() diff --git a/cmake/Findpinocchio.cmake b/cmake/Findpinocchio.cmake index fcefcf5c..54e32c09 100644 --- a/cmake/Findpinocchio.cmake +++ b/cmake/Findpinocchio.cmake @@ -17,25 +17,44 @@ if (NOT pinocchio_FOUND) return() endif() - # Check if the library file exists - cmake_path(APPEND Python3_SITELIB cmeel.prefix lib libpinocchio_default.so OUTPUT_VARIABLE pinocchio_library_path) - if (NOT EXISTS ${pinocchio_library_path}) + cmake_path(APPEND Python3_SITELIB cmeel.prefix lib OUTPUT_VARIABLE pinocchio_LIBRARY_DIR) + + set(_pinocchio_default_globs) + set(_pinocchio_parsers_globs) + if (APPLE) + list(APPEND _pinocchio_default_globs "${pinocchio_LIBRARY_DIR}/libpinocchio_default*.dylib") + list(APPEND _pinocchio_parsers_globs "${pinocchio_LIBRARY_DIR}/libpinocchio_parsers*.dylib") + elseif (WIN32) + list(APPEND _pinocchio_default_globs "${pinocchio_LIBRARY_DIR}/pinocchio_default*.dll") + list(APPEND _pinocchio_default_globs "${pinocchio_LIBRARY_DIR}/libpinocchio_default*.dll") + list(APPEND _pinocchio_parsers_globs "${pinocchio_LIBRARY_DIR}/pinocchio_parsers*.dll") + list(APPEND _pinocchio_parsers_globs "${pinocchio_LIBRARY_DIR}/libpinocchio_parsers*.dll") + else() + list(APPEND _pinocchio_default_globs "${pinocchio_LIBRARY_DIR}/libpinocchio_default.so*") + list(APPEND _pinocchio_parsers_globs "${pinocchio_LIBRARY_DIR}/libpinocchio_parsers.so*") + endif() + + file(GLOB pinocchio_library_paths LIST_DIRECTORIES FALSE ${_pinocchio_default_globs}) + list(LENGTH pinocchio_library_paths _pinocchio_library_count) + if (_pinocchio_library_count EQUAL 0) set(pinocchio_FOUND FALSE) if (pinocchio_FIND_REQUIRED) - message(FATAL_ERROR "Could not find pinocchio. Please install pinocchio using pip.") + message(FATAL_ERROR "Could not find pinocchio library. Searched: ${_pinocchio_default_globs}") endif() return() endif() + list(GET pinocchio_library_paths 0 pinocchio_library_path) - # Check if the library file exists - cmake_path(APPEND Python3_SITELIB cmeel.prefix lib libpinocchio_parsers.so OUTPUT_VARIABLE pinocchio_parsers_path) - if (NOT EXISTS ${pinocchio_parsers_path}) + file(GLOB pinocchio_parsers_paths LIST_DIRECTORIES FALSE ${_pinocchio_parsers_globs}) + list(LENGTH pinocchio_parsers_paths _pinocchio_parsers_count) + if (_pinocchio_parsers_count EQUAL 0) set(pinocchio_FOUND FALSE) if (pinocchio_FIND_REQUIRED) - message(FATAL_ERROR "Could not find pinocchio parsers path. Please install pinocchio using pip.") + message(FATAL_ERROR "Could not find pinocchio parsers library. Searched: ${_pinocchio_parsers_globs}") endif() return() endif() + list(GET pinocchio_parsers_paths 0 pinocchio_parsers_path) # Extract version from the library filename file(GLOB pinocchio_dist_info "${Python3_SITELIB}/pin-*.dist-info") diff --git a/docs/extensions/overview.md b/docs/extensions/overview.md index 2a25fd4d..f33539cb 100644 --- a/docs/extensions/overview.md +++ b/docs/extensions/overview.md @@ -2,6 +2,12 @@ RCS is designed to be modular. Core functionality is kept minimal, while specific hardware support and additional features are provided through **extensions**. +```{note} +Extensions are supported on **Linux only**. The core `rcs-core` package (MuJoCo +simulation + Python API) also runs on macOS (Apple Silicon / arm64), but the +hardware extensions are not supported there. +``` + ## What is an Extension? An extension is a separate Python package that integrates with RCS. Extensions can provide: diff --git a/docs/getting_started/index.md b/docs/getting_started/index.md index 72ef4747..d7e60720 100644 --- a/docs/getting_started/index.md +++ b/docs/getting_started/index.md @@ -4,6 +4,12 @@ We build and test RCS on the latest Debian and on the latest Ubuntu LTS. +```{note} +**Platform support:** The core package `rcs-core` (MuJoCo simulation + Python API) +is supported on **Linux and macOS** (Apple Silicon / arm64). The hardware +extensions are **Linux only**. +``` + ### Prerequisites 1. Install the system dependencies: diff --git a/include/rcs/utils.h b/include/rcs/utils.h index 2baf0e08..f12cb85a 100644 --- a/include/rcs/utils.h +++ b/include/rcs/utils.h @@ -40,8 +40,9 @@ Eigen::Matrix array2eigen( Eigen::Matrix matrix(array.data()); return matrix; } -void bootstrap_egl(std::uintptr_t fn_addr, std::uintptr_t display, - std::uintptr_t context); +void bootstrap_egl_context(std::uintptr_t fn_addr, std::uintptr_t display, + std::uintptr_t context); +void bootstrap_gl_context(); void ensure_current(); /*** diff --git a/pyproject.toml b/pyproject.toml index e652022a..21f28587 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,7 @@ dependencies = [ "tilburg-hand", "digit-interface", "pyopengl>=3.1.9", - "ompl>=1.7.0", + "ompl>=1.7.0; sys_platform == 'linux'", "rpyc~=6.0.2", "pyarrow", "simplejpeg", diff --git a/python/rcs/_core/common.pyi b/python/rcs/_core/common.pyi index 95ec05fd..05a073b6 100644 --- a/python/rcs/_core/common.pyi +++ b/python/rcs/_core/common.pyi @@ -321,7 +321,8 @@ def FrankaHandTCPOffset() -> numpy.ndarray[tuple[typing.Literal[4], typing.Liter def IdentityRotMatrix() -> numpy.ndarray[tuple[typing.Literal[3], typing.Literal[3]], numpy.dtype[numpy.float64]]: ... def IdentityRotQuatVec() -> numpy.ndarray[tuple[typing.Literal[4]], numpy.dtype[numpy.float64]]: ... def IdentityTranslation() -> numpy.ndarray[tuple[typing.Literal[3]], numpy.dtype[numpy.float64]]: ... -def _bootstrap_egl(fn_addr: int, display: int, context: int) -> None: ... +def _bootstrap_egl_context(fn_addr: int, display: int, context: int) -> None: ... +def _bootstrap_gl_context() -> None: ... HARDWARE: RobotPlatform # value = LATERAL_GRASP: GraspType # value = diff --git a/python/rcs/camera/sim.py b/python/rcs/camera/sim.py index d508165a..12af75b4 100644 --- a/python/rcs/camera/sim.py +++ b/python/rcs/camera/sim.py @@ -11,7 +11,7 @@ from rcs._core.sim import SimCameraConfig from rcs._core.sim import SimCameraSet as _SimCameraSet from rcs.camera.interface import BaseCameraSet, CameraFrame, DataFrame, Frame, FrameSet -from rcs.sim import egl_bootstrap +from rcs.sim import render_context_bootstrap from rcs import sim @@ -32,7 +32,7 @@ def __init__( self.cameras = cameras self.physical_units = physical_units - egl_bootstrap.require("simulation camera rendering") + render_context_bootstrap.require("simulation camera rendering") super().__init__(simulation, cameras, render_on_demand=render_on_demand) self._sim: sim.Sim diff --git a/python/rcs/sim/egl_bootstrap.py b/python/rcs/sim/egl_bootstrap.py deleted file mode 100644 index 9aa2d95a..00000000 --- a/python/rcs/sim/egl_bootstrap.py +++ /dev/null @@ -1,64 +0,0 @@ -""" -Load the EGL library, create a persistent GLContext, and register it with the C++ backend. - -Globals prevent the library and context from being garbage-collected. -Call `bootstrap()` to complete initialization. -""" - -import ctypes -import ctypes.util -import os - -_egl_available = False -_egl_error = None -_addr_make_current = None -_egl_display = None -_egl_context = None - -name = ctypes.util.find_library("EGL") -if name is None: - _egl_error = "Could not find libEGL via ctypes.util.find_library('EGL')." -else: - try: - import mujoco.egl - from mujoco.egl import GLContext - - _egl = ctypes.CDLL(name, mode=os.RTLD_LOCAL | os.RTLD_NOW) - _addr_make_current = ctypes.cast(_egl.eglMakeCurrent, ctypes.c_void_p).value - _ctx = GLContext(max_width=3840, max_height=2160) - _egl_display = int(mujoco.egl.EGL_DISPLAY.address) - _egl_context = int(_ctx._context.address) - _egl_available = True - except Exception as exc: - _egl_error = f"Failed to initialize MuJoCo EGL context: {exc!r}" - - -def is_available() -> bool: - return _egl_available - - -def failure_reason() -> str | None: - return _egl_error - - -def require(feature: str = "offscreen rendering"): - if _egl_available: - return - reason = _egl_error or "unknown EGL initialization failure" - message = ( - f"EGL is required for {feature}, but it is not available. {reason} " - "If you do not need rendering, run RCS without simulation cameras/viewers. " - "If you do need headless rendering, install the system EGL/OpenGL runtime libraries." - ) - raise RuntimeError(message) - - -def bootstrap(): - if not _egl_available: - return - import rcs._core as _cxx - - assert _addr_make_current is not None - assert _egl_display is not None - assert _egl_context is not None - _cxx.common._bootstrap_egl(_addr_make_current, _egl_display, _egl_context) diff --git a/python/rcs/sim/render_context_bootstrap.py b/python/rcs/sim/render_context_bootstrap.py new file mode 100644 index 00000000..e27f2411 --- /dev/null +++ b/python/rcs/sim/render_context_bootstrap.py @@ -0,0 +1,111 @@ +""" +Bootstrap an offscreen GL render context and register it with the C++ backend. + +Two backends are supported, selected by platform: + +- Linux: load libEGL via ctypes, create a persistent MuJoCo EGL ``GLContext``, + and hand its ``eglMakeCurrent`` / display / context to C++ (the ``egl`` + backend). +- macOS: there is no libEGL. Create a persistent MuJoCo ``GLContext`` and make + it current on this thread; C++ then treats ``ensure_current()`` as a no-op + (the ``current_context`` backend). + +The initialized state is held in a module-level ``_state`` (references keep the +library and context from being garbage-collected). Call ``bootstrap()`` to +complete initialization. +""" + +import ctypes +import ctypes.util +import os +import sys +from dataclasses import dataclass +from typing import Any + + +@dataclass +class _RenderBackend: + backend: str = "none" # "none" | "egl" | "current_context" + available: bool = False + error: str | None = None + # references kept so the GL context / EGL library are not garbage-collected + gl_context: Any = None + egl_lib: Any = None + addr_make_current: int | None = None + egl_display: int | None = None + egl_context: int | None = None + + +def _init_current_context() -> _RenderBackend: + """macOS: no libEGL. Use a MuJoCo GLContext made current on this thread.""" + try: + from mujoco import GLContext + + gl_context = GLContext(max_width=3840, max_height=2160) + except Exception as exc: + return _RenderBackend(error=f"Failed to initialize MuJoCo GL context: {exc!r}") + return _RenderBackend(backend="current_context", available=True, gl_context=gl_context) + + +def _init_egl() -> _RenderBackend: + """Linux/other: load libEGL and create a persistent MuJoCo EGL GLContext.""" + name = ctypes.util.find_library("EGL") + if name is None: + return _RenderBackend(error="Could not find libEGL via ctypes.util.find_library('EGL').") + try: + import mujoco.egl + from mujoco.egl import GLContext + + egl_lib = ctypes.CDLL(name, mode=os.RTLD_LOCAL | os.RTLD_NOW) + addr_make_current = ctypes.cast(egl_lib.eglMakeCurrent, ctypes.c_void_p).value + ctx = GLContext(max_width=3840, max_height=2160) + return _RenderBackend( + backend="egl", + available=True, + gl_context=ctx, + egl_lib=egl_lib, + addr_make_current=addr_make_current, + egl_display=int(mujoco.egl.EGL_DISPLAY.address), + egl_context=int(ctx._context.address), + ) + except Exception as exc: + return _RenderBackend(error=f"Failed to initialize MuJoCo EGL context: {exc!r}") + + +_state = _init_current_context() if sys.platform == "darwin" else _init_egl() + + +def is_available() -> bool: + return _state.available + + +def failure_reason() -> str | None: + return _state.error + + +def require(feature: str = "offscreen rendering"): + if _state.available: + return + reason = _state.error or "unknown rendering initialization failure" + message = ( + f"A GL render context is required for {feature}, but it is not available. {reason} " + "If you do not need rendering, run RCS without simulation cameras/viewers. " + "If you do need headless rendering, install the system EGL/OpenGL runtime libraries." + ) + raise RuntimeError(message) + + +def bootstrap(): + if not _state.available: + return + import rcs._core as _cxx + + if _state.backend == "current_context": + assert _state.gl_context is not None + _state.gl_context.make_current() + _cxx.common._bootstrap_gl_context() + elif _state.backend == "egl": + assert _state.addr_make_current is not None + assert _state.egl_display is not None + assert _state.egl_context is not None + _cxx.common._bootstrap_egl_context(_state.addr_make_current, _state.egl_display, _state.egl_context) diff --git a/python/rcs/sim/sim.py b/python/rcs/sim/sim.py index 43a49a20..4ee8b02b 100644 --- a/python/rcs/sim/sim.py +++ b/python/rcs/sim/sim.py @@ -1,6 +1,8 @@ import atexit import contextlib import multiprocessing as mp +import shutil +import sys import typing import uuid from logging import getLogger @@ -17,11 +19,11 @@ from rcs._core.sim import DynamicJointSchema, DynamicJointState from rcs._core.sim import GuiClient as _GuiClient from rcs._core.sim import Sim as _Sim -from rcs.sim import SimConfig, egl_bootstrap +from rcs.sim import SimConfig, render_context_bootstrap from rcs.sim.composer import ModelComposer from rcs.utils import SimpleFrameRate -egl_bootstrap.bootstrap() +render_context_bootstrap.bootstrap() logger = getLogger(__name__) @@ -31,6 +33,17 @@ ROOT_RELATIVE_FREE_STATE_ENCODING = "root_relative_free" +def configure_viewer_mp_context(ctx: "mp.context.SpawnContext") -> None: + """On macOS the passive MuJoCo viewer must run under ``mjpython``.""" + if sys.platform != "darwin": + return + mjpython = shutil.which("mjpython") + if mjpython is None: + logger.warning("mjpython not found on PATH; the passive MuJoCo viewer will not work on macOS.") + return + ctx.set_executable(mjpython) + + def gui_loop(gui_uuid: str, close_event): frame_rate = SimpleFrameRate(FPS, "gui_loop") gui_client = _GuiClient(gui_uuid) @@ -66,6 +79,7 @@ def __init__(self, mjmdl: str | PathLike | ModelComposer, cfg: SimConfig | None self.data = mj.MjData(self.model) super().__init__(self.model._address, self.data._address) self._mp_context = mp.get_context("spawn") + configure_viewer_mp_context(self._mp_context) self._gui_uuid: Optional[str] = None self._gui_client: Optional[_GuiClient] = None self._gui_process: Optional[mp.context.SpawnProcess] = None @@ -244,6 +258,7 @@ def open_gui(self): self._gui_process = self._mp_context.Process( target=gui_loop, args=(self._gui_uuid, self._stop_event), + daemon=True, ) self._gui_process.start() if not self._gui_atexit_registered: diff --git a/src/pybind/CMakeLists.txt b/src/pybind/CMakeLists.txt index 3baf7822..2f0ca58a 100644 --- a/src/pybind/CMakeLists.txt +++ b/src/pybind/CMakeLists.txt @@ -1,11 +1,30 @@ +if (APPLE) + set(_rpath_origin "@loader_path") +else() + set(_rpath_origin "$ORIGIN") +endif() + pybind11_add_module(_core MODULE rcs.cpp) target_link_libraries(_core PRIVATE sim rcs) target_compile_definitions(_core PRIVATE VERSION_INFO=${PROJECT_VERSION}) set_target_properties(_core PROPERTIES - INSTALL_RPATH "$ORIGIN;$ORIGIN/../mujoco;$ORIGIN/../cmeel.prefix/lib" + INSTALL_RPATH "${_rpath_origin};${_rpath_origin}/../mujoco;${_rpath_origin}/../cmeel.prefix/lib" INTERPROCEDURAL_OPTIMIZATION TRUE ) + +if (APPLE) + get_target_property(_mujoco_library_path MuJoCo::MuJoCo IMPORTED_LOCATION) + cmake_path(GET _mujoco_library_path FILENAME _mujoco_library_name) + add_custom_command(TARGET _core POST_BUILD + COMMAND install_name_tool + -change "@rpath/mujoco.framework/Versions/A/${_mujoco_library_name}" + "@loader_path/../mujoco/${_mujoco_library_name}" + "$" + VERBATIM + ) +endif() + # in pip install(TARGETS _core rcs DESTINATION rcs COMPONENT python_package) install( diff --git a/src/pybind/rcs.cpp b/src/pybind/rcs.cpp index 59833a97..ea40db99 100644 --- a/src/pybind/rcs.cpp +++ b/src/pybind/rcs.cpp @@ -256,8 +256,9 @@ PYBIND11_MODULE(_core, m) { // COMMON MODULE auto common = m.def_submodule("common", "common module"); - common.def("_bootstrap_egl", &rcs::common::bootstrap_egl, py::arg("fn_addr"), - py::arg("display"), py::arg("context")); + common.def("_bootstrap_egl_context", &rcs::common::bootstrap_egl_context, + py::arg("fn_addr"), py::arg("display"), py::arg("context")); + common.def("_bootstrap_gl_context", &rcs::common::bootstrap_gl_context); common.def("IdentityTranslation", &rcs::common::IdentityTranslation); common.def("IdentityRotMatrix", &rcs::common::IdentityRotMatrix); common.def("IdentityRotQuatVec", &rcs::common::IdentityRotQuatVec); diff --git a/src/rcs/CMakeLists.txt b/src/rcs/CMakeLists.txt index 141761d0..56391f74 100644 --- a/src/rcs/CMakeLists.txt +++ b/src/rcs/CMakeLists.txt @@ -1,7 +1,13 @@ +if (APPLE) + set(_rpath_origin "@loader_path") +else() + set(_rpath_origin "$ORIGIN") +endif() + add_library(rcs SHARED) target_include_directories(rcs PUBLIC ${CMAKE_SOURCE_DIR}/include) target_sources(rcs PRIVATE Pose.cpp Robot.cpp Kinematics.cpp utils.cpp) target_link_libraries(rcs PUBLIC Eigen3::Eigen pinocchio::all) set_target_properties(rcs PROPERTIES - INSTALL_RPATH "$ORIGIN;$ORIGIN/../cmeel.prefix/lib" + INSTALL_RPATH "${_rpath_origin};${_rpath_origin}/../cmeel.prefix/lib" ) diff --git a/src/rcs/utils.cpp b/src/rcs/utils.cpp index 18a4e58b..630d850f 100644 --- a/src/rcs/utils.cpp +++ b/src/rcs/utils.cpp @@ -6,28 +6,40 @@ namespace rcs { namespace common { +enum class RenderBackend { none, egl, current_context }; + +static RenderBackend g_backend = RenderBackend::none; static PFNEGLMAKECURRENTPROC g_makeCurrent = nullptr; static EGLDisplay g_display = EGL_NO_DISPLAY; static EGLSurface g_surface = EGL_NO_SURFACE; static EGLContext g_context = EGL_NO_CONTEXT; -void bootstrap_egl(uintptr_t fn_addr, uintptr_t dpy, uintptr_t ctx) { +void bootstrap_egl_context(uintptr_t fn_addr, uintptr_t dpy, uintptr_t ctx) { g_makeCurrent = reinterpret_cast(fn_addr); g_display = reinterpret_cast(dpy); g_context = reinterpret_cast(ctx); + g_backend = RenderBackend::egl; } +void bootstrap_gl_context() { g_backend = RenderBackend::current_context; } + void ensure_current() { - if (g_makeCurrent == nullptr || g_display == EGL_NO_DISPLAY || - g_context == EGL_NO_CONTEXT) { - throw std::runtime_error( - "EGL rendering was requested, but EGL was not bootstrapped. " - "This usually means libEGL or the MuJoCo EGL context is unavailable. " - "Run without cameras/viewers if you do not need rendering, or install " - "the required system EGL/OpenGL runtime libraries."); + switch (g_backend) { + case RenderBackend::current_context: + return; + case RenderBackend::egl: + if (!g_makeCurrent(g_display, g_surface, g_surface, g_context)) + throw std::runtime_error("eglMakeCurrent failed"); + return; + case RenderBackend::none: + default: + throw std::runtime_error( + "Rendering was requested, but no render backend was bootstrapped. " + "This usually means libEGL or the MuJoCo GL context is unavailable. " + "Run without cameras/viewers if you do not need rendering, or " + "install " + "the required system EGL/OpenGL runtime libraries."); } - if (!g_makeCurrent(g_display, g_surface, g_surface, g_context)) - throw std::runtime_error("eglMakeCurrent failed"); } } // namespace common } // namespace rcs diff --git a/src/sim/SimRobot.h b/src/sim/SimRobot.h index 5270bd67..193f2a2b 100644 --- a/src/sim/SimRobot.h +++ b/src/sim/SimRobot.h @@ -6,6 +6,8 @@ #include #include +#include + #include "sim/sim.h" namespace rcs {