Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ jobs:
- name: Run tests
run: |
docker run --rm \
-e LIBGL_ALWAYS_SOFTWARE=1 \
-e GALLIUM_DRIVER=llvmpipe \
${{ steps.meta.outputs.tags }} \
bash -c "
colcon test &&
Expand Down Expand Up @@ -103,6 +105,8 @@ jobs:
-v ${{ github.workspace }}/.sccache:/workspace/.sccache \
-w /workspace \
-e SCCACHE_DIR=/workspace/.sccache \
-e LIBGL_ALWAYS_SOFTWARE=1 \
-e GALLIUM_DRIVER=llvmpipe \
mujoco_ros2_control_pixi:ci \
bash -c "
pixi run setup-colcon && \
Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,20 @@ Additionally camera_info, color, and depth images will be published to topics ca
Also note that MuJuCo's conventions for cameras are different than ROS's, and which must be accounted for.
Refer to the documentation for more information.

#### Headless Rendering

Camera rendering is supported in headless environments (without a display).
The system automatically detects whether a display is available:

- **With display**: Uses GLFW for OpenGL context creation (default behavior)
- **Without display**: Falls back to EGL for GPU-accelerated headless rendering

This allows camera topics to be published even when running in headless mode (e.g., on a server, in Docker containers, or in CI environments).

> [!NOTE]
> EGL requires proper GPU drivers and EGL libraries to be installed (e.g., `libegl1-mesa` on Ubuntu).
> If both GLFW and EGL fail to initialize, camera publishing will be disabled with a warning.

For example,

```xml
Expand Down
10 changes: 9 additions & 1 deletion docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,15 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
tmux \
wget \
xterm \
vim
vim \
libegl1 \
libgbm1 \
libosmesa6 \
libosmesa6-dev

# Enable software rendering with LLVMpipe for headless environments (CI, Docker without GPU)
ENV LIBGL_ALWAYS_SOFTWARE=1
ENV GALLIUM_DRIVER=llvmpipe

# Setup a ROS workspace
ENV ROS_WS="/opt/mujoco/ws"
Expand Down
9 changes: 9 additions & 0 deletions docker/Dockerfile.pixi
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,20 @@ RUN apt-get update && apt-get install -y \
git \
build-essential \
ca-certificates \
libegl1 \
libgbm1 \
libgl1-mesa-dri \
libosmesa6 \
libosmesa6-dev \
&& rm -rf /var/lib/apt/lists/*

RUN curl -fsSL https://pixi.sh/install.sh | bash
ENV PATH="/root/.pixi/bin:${PATH}"

# Enable software rendering with LLVMpipe for headless environments (CI, Docker without GPU)
ENV LIBGL_ALWAYS_SOFTWARE=1
ENV GALLIUM_DRIVER=llvmpipe

WORKDIR /workspace

CMD ["/bin/bash"]
22 changes: 21 additions & 1 deletion mujoco_ros2_control/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,18 @@ find_package(ament_cmake REQUIRED)
find_package(controller_manager REQUIRED)
find_package(Eigen3 REQUIRED)
find_package(glfw3 REQUIRED)
find_package(OpenGL REQUIRED COMPONENTS EGL)

# Find OSMesa for headless software rendering fallback
find_library(OSMESA_LIBRARY OSMesa)
find_path(OSMESA_INCLUDE_DIR GL/osmesa.h)
if(OSMESA_LIBRARY AND OSMESA_INCLUDE_DIR)
message(STATUS "Found OSMesa: ${OSMESA_LIBRARY}")
set(OSMESA_FOUND TRUE)
else()
message(STATUS "OSMesa not found - headless software rendering fallback will be disabled")
set(OSMESA_FOUND FALSE)
endif()
find_package(control_toolbox REQUIRED)
find_package(hardware_interface REQUIRED)
find_package(nav_msgs REQUIRED)
Expand Down Expand Up @@ -143,7 +155,15 @@ PRIVATE
Threads::Threads
lodepng
glfw
)
OpenGL::EGL
OpenGL::OpenGL
${CMAKE_DL_LIBS}
)
if(OSMESA_FOUND)
target_link_libraries(mujoco_ros2_control PRIVATE ${OSMESA_LIBRARY})
target_include_directories(mujoco_ros2_control PRIVATE ${OSMESA_INCLUDE_DIR})
target_compile_definitions(mujoco_ros2_control PRIVATE OSMESA_AVAILABLE)
endif()
target_include_directories(mujoco_ros2_control PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
Expand Down
39 changes: 39 additions & 0 deletions mujoco_ros2_control/include/mujoco_ros2_control/mujoco_cameras.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@
#include <thread>
#include <vector>

#include <EGL/egl.h>
#include <GLFW/glfw3.h>
#ifdef OSMESA_AVAILABLE
#include <GL/osmesa.h>
#endif
#include <mujoco/mujoco.h>

#include <hardware_interface/hardware_info.hpp>
Expand Down Expand Up @@ -134,6 +138,41 @@ class MujocoCameras
// Camera processing thread
std::thread rendering_thread_;
std::atomic_bool publish_images_;

// EGL context for headless rendering (used when GLFW is unavailable)
EGLDisplay egl_display_;
EGLContext egl_context_;
EGLSurface egl_surface_;
bool use_egl_;

#ifdef OSMESA_AVAILABLE
// OSMesa context for software-only headless rendering (fallback when EGL fails)
OSMesaContext osmesa_context_;
std::vector<unsigned char> osmesa_buffer_;
bool use_osmesa_;

/**
* @brief Initializes OSMesa context for software-only headless rendering.
* @return true if OSMesa initialization succeeded, false otherwise.
*/
bool init_osmesa_context();

/**
* @brief Cleans up OSMesa resources.
*/
void cleanup_osmesa_context();
#endif

/**
* @brief Initializes EGL context for headless rendering.
* @return true if EGL initialization succeeded, false otherwise.
*/
bool init_egl_context();

/**
* @brief Cleans up EGL resources.
*/
void cleanup_egl_context();
};

} // namespace mujoco_ros2_control
4 changes: 3 additions & 1 deletion mujoco_ros2_control/package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
<depend>control_toolbox</depend>
<depend>controller_manager</depend>
<depend>hardware_interface</depend>
<depend>nav_msgs</depend>
<depend>libegl-dev</depend>
<depend>libglfw3-dev</depend>
<depend>libosmesa6-dev</depend>
<depend>nav_msgs</depend>
<!-- mujoco_vendor is optional - MuJoCo can also be provided via MUJOCO_INSTALL_DIR env or auto-download -->
<depend>mujoco_vendor</depend>
<depend>pluginlib</depend>
Expand Down
Loading
Loading