Skip to content

Commit 5a8d765

Browse files
ybleiCopilot
andcommitted
Register modular render backend, add mjpython in viewer thread.
Co-authored-by: Copilot <copilot@github.com>
1 parent 248d6b3 commit 5a8d765

6 files changed

Lines changed: 84 additions & 22 deletions

File tree

‎include/rcs/utils.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ Eigen::Matrix<double, N, M, Eigen::ColMajor> array2eigen(
3737
}
3838
void bootstrap_egl(std::uintptr_t fn_addr, std::uintptr_t display,
3939
std::uintptr_t context);
40+
void bootstrap_gl_context();
4041
void ensure_current();
4142

4243
} // namespace common

‎python/rcs/_core/common.pyi‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,7 @@ def IdentityRotMatrix() -> numpy.ndarray[tuple[typing.Literal[3], typing.Literal
321321
def IdentityRotQuatVec() -> numpy.ndarray[tuple[typing.Literal[4]], numpy.dtype[numpy.float64]]: ...
322322
def IdentityTranslation() -> numpy.ndarray[tuple[typing.Literal[3]], numpy.dtype[numpy.float64]]: ...
323323
def _bootstrap_egl(fn_addr: int, display: int, context: int) -> None: ...
324+
def _bootstrap_gl_context() -> None: ...
324325

325326
HARDWARE: RobotPlatform # value = <RobotPlatform.HARDWARE: 1>
326327
LATERAL_GRASP: GraspType # value = <GraspType.LATERAL_GRASP: 2>

‎python/rcs/sim/egl_bootstrap.py‎

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,60 @@
11
"""
2-
Load the EGL library, create a persistent GLContext, and register it with the C++ backend.
2+
Bootstrap the rendering backend used by the C++ simulation camera path.
33
4-
Globals prevent the library and context from being garbage-collected.
5-
Call `bootstrap()` to complete initialization.
4+
Linux uses MuJoCo's EGL backend. macOS uses a persistent MuJoCo GLContext
5+
that is made current on the Python thread before C++ creates rendering
6+
contexts.
67
"""
78

89
import ctypes
910
import ctypes.util
1011
import os
12+
import sys
1113

1214
_egl_available = False
1315
_addr_make_current = None
1416
_egl_display = None
1517
_egl_context = None
18+
_backend = None
19+
_gl_context = None
1620

17-
name = ctypes.util.find_library("EGL")
18-
if name is not None:
21+
if sys.platform == "darwin":
1922
try:
20-
import mujoco.egl
21-
from mujoco.egl import GLContext
22-
23-
_egl = ctypes.CDLL(name, mode=os.RTLD_LOCAL | os.RTLD_NOW)
24-
_addr_make_current = ctypes.cast(_egl.eglMakeCurrent, ctypes.c_void_p).value
25-
_ctx = GLContext(max_width=3840, max_height=2160)
26-
_egl_display = int(mujoco.egl.EGL_DISPLAY.address)
27-
_egl_context = int(_ctx._context.address)
28-
_egl_available = True
23+
import mujoco
24+
25+
_gl_context = mujoco.GLContext(3840, 2160)
26+
_backend = "gl_context"
2927
except Exception:
3028
pass
29+
else:
30+
name = ctypes.util.find_library("EGL")
31+
if name is not None:
32+
try:
33+
import mujoco.egl
34+
from mujoco.egl import GLContext
35+
36+
_egl = ctypes.CDLL(name, mode=os.RTLD_LOCAL | os.RTLD_NOW)
37+
_addr_make_current = ctypes.cast(_egl.eglMakeCurrent, ctypes.c_void_p).value
38+
_ctx = GLContext(max_width=3840, max_height=2160)
39+
_egl_display = int(mujoco.egl.EGL_DISPLAY.address)
40+
_egl_context = int(_ctx._context.address)
41+
_egl_available = True
42+
_backend = "egl"
43+
except Exception:
44+
pass
3145

3246

3347
def bootstrap():
34-
if not _egl_available:
35-
return
3648
import rcs._core as _cxx
3749

38-
assert _addr_make_current is not None
39-
assert _egl_display is not None
40-
assert _egl_context is not None
41-
_cxx.common._bootstrap_egl(_addr_make_current, _egl_display, _egl_context)
50+
if _backend == "gl_context":
51+
assert _gl_context is not None
52+
_gl_context.make_current()
53+
_cxx.common._bootstrap_gl_context()
54+
return
55+
56+
if _backend == "egl":
57+
assert _addr_make_current is not None
58+
assert _egl_display is not None
59+
assert _egl_context is not None
60+
_cxx.common._bootstrap_egl(_addr_make_current, _egl_display, _egl_context)

‎python/rcs/sim/sim.py‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import atexit
22
import contextlib
33
import multiprocessing as mp
4+
import shutil
5+
import sys
46
import typing
57
import uuid
68
from logging import getLogger
@@ -25,6 +27,21 @@
2527
logger = getLogger(__name__)
2628

2729

30+
def _configure_spawn_executable() -> None:
31+
if sys.platform != "darwin":
32+
return
33+
34+
mjpython = shutil.which("mjpython")
35+
if mjpython is None:
36+
logger.warning("mjpython was not found on PATH; passive MuJoCo viewer may fail on macOS")
37+
return
38+
39+
mp.set_executable(mjpython)
40+
41+
42+
_configure_spawn_executable()
43+
44+
2845
# Target frames per second
2946
FPS = 60
3047
RAW_STATE_ENCODING = "raw"

‎src/pybind/rcs.cpp‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
#include <pybind11/cast.h>
21
#include <pybind11/eigen.h>
32
#include <pybind11/operators.h>
43
#include <pybind11/pybind11.h>
@@ -253,6 +252,7 @@ PYBIND11_MODULE(_core, m) {
253252

254253
common.def("_bootstrap_egl", &rcs::common::bootstrap_egl, py::arg("fn_addr"),
255254
py::arg("display"), py::arg("context"));
255+
common.def("_bootstrap_gl_context", &rcs::common::bootstrap_gl_context);
256256
common.def("IdentityTranslation", &rcs::common::IdentityTranslation);
257257
common.def("IdentityRotMatrix", &rcs::common::IdentityRotMatrix);
258258
common.def("IdentityRotQuatVec", &rcs::common::IdentityRotQuatVec);

‎src/rcs/utils.cpp‎

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,44 @@
66

77
namespace rcs {
88
namespace common {
9+
enum class RenderBackend {
10+
none,
11+
egl,
12+
current_context,
13+
};
14+
915
static PFNEGLMAKECURRENTPROC g_makeCurrent = nullptr;
1016
static EGLDisplay g_display = EGL_NO_DISPLAY;
1117
static EGLSurface g_surface = EGL_NO_SURFACE;
1218
static EGLContext g_context = EGL_NO_CONTEXT;
19+
static RenderBackend g_render_backend = RenderBackend::none;
1320

1421
void bootstrap_egl(uintptr_t fn_addr, uintptr_t dpy, uintptr_t ctx) {
1522
g_makeCurrent = reinterpret_cast<PFNEGLMAKECURRENTPROC>(fn_addr);
1623
g_display = reinterpret_cast<EGLDisplay>(dpy);
1724
g_context = reinterpret_cast<EGLContext>(ctx);
25+
g_render_backend = RenderBackend::egl;
26+
}
27+
28+
void bootstrap_gl_context() {
29+
g_makeCurrent = nullptr;
30+
g_display = EGL_NO_DISPLAY;
31+
g_context = EGL_NO_CONTEXT;
32+
g_render_backend = RenderBackend::current_context;
1833
}
1934

2035
void ensure_current() {
21-
if (!g_makeCurrent(g_display, g_surface, g_surface, g_context))
36+
if (g_render_backend == RenderBackend::current_context) {
37+
return;
38+
}
39+
if (g_render_backend != RenderBackend::egl || g_makeCurrent == nullptr) {
40+
throw std::runtime_error(
41+
"No rendering context backend initialized. "
42+
"Call the Python rendering bootstrap before using simulation cameras.");
43+
}
44+
if (!g_makeCurrent(g_display, g_surface, g_surface, g_context)) {
2245
throw std::runtime_error("eglMakeCurrent failed");
46+
}
2347
}
2448
} // namespace common
2549
} // namespace rcs

0 commit comments

Comments
 (0)