Skip to content

Commit 44dd9d8

Browse files
committed
Support offscreen rendering
1 parent 96631a5 commit 44dd9d8

File tree

2 files changed

+69
-11
lines changed

2 files changed

+69
-11
lines changed

examples/offscreen_demo.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import mujoco
2+
import mujoco_viewer
3+
import PIL.Image
4+
5+
model = mujoco.MjModel.from_xml_path('humanoid.xml')
6+
data = mujoco.MjData(model)
7+
8+
viewer = mujoco_viewer.MujocoViewer(model, data, 'offscreen')
9+
mujoco.mj_step(model, data)
10+
img = viewer.read_pixels(camid=2)
11+
img = PIL.Image.fromarray(img)
12+
img.show()

mujoco_viewer/mujoco_viewer.py

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,16 @@ def __init__(
1111
self,
1212
model,
1313
data,
14+
mode='window',
1415
title="mujoco-python-viewer",
1516
width=None,
1617
height=None,
1718
hide_menus=True):
1819
self.model = model
1920
self.data = data
21+
self.render_mode = mode
22+
if self.render_mode not in ['offscreen', 'window']:
23+
raise NotImplementedError("Invalid mode. Only 'offscreen' and 'window' are supported.")
2024

2125
self.is_alive = True
2226

@@ -25,6 +29,8 @@ def __init__(
2529
self._button_right_pressed = False
2630
self._left_double_click_pressed = False
2731
self._right_double_click_pressed = False
32+
self._last_left_click_time = None
33+
self._last_right_click_time = None
2834
self._last_mouse_x = 0
2935
self._last_mouse_y = 0
3036
self._paused = False
@@ -54,24 +60,28 @@ def __init__(
5460
if not height:
5561
_, height = glfw.get_video_mode(glfw.get_primary_monitor()).size
5662

63+
if self.render_mode=='offscreen':
64+
glfw.window_hint(glfw.VISIBLE, 0)
65+
5766
self.window = glfw.create_window(
5867
width, height, title, None, None)
5968
glfw.make_context_current(self.window)
6069
glfw.swap_interval(1)
6170

6271
framebuffer_width, framebuffer_height = glfw.get_framebuffer_size(
6372
self.window)
64-
window_width, _ = glfw.get_window_size(self.window)
65-
self._scale = framebuffer_width * 1.0 / window_width
66-
67-
# set callbacks
68-
glfw.set_cursor_pos_callback(self.window, self._cursor_pos_callback)
69-
glfw.set_mouse_button_callback(
70-
self.window, self._mouse_button_callback)
71-
glfw.set_scroll_callback(self.window, self._scroll_callback)
72-
glfw.set_key_callback(self.window, self._key_callback)
73-
self._last_left_click_time = None
74-
self._last_right_click_time = None
73+
74+
# install callbacks only for 'window' mode
75+
if self.render_mode=='window':
76+
window_width, _ = glfw.get_window_size(self.window)
77+
self._scale = framebuffer_width * 1.0 / window_width
78+
79+
# set callbacks
80+
glfw.set_cursor_pos_callback(self.window, self._cursor_pos_callback)
81+
glfw.set_mouse_button_callback(
82+
self.window, self._mouse_button_callback)
83+
glfw.set_scroll_callback(self.window, self._scroll_callback)
84+
glfw.set_key_callback(self.window, self._key_callback)
7585

7686
# create options, camera, scene, context
7787
self.vopt = mujoco.MjvOption()
@@ -484,7 +494,43 @@ def apply_perturbations(self):
484494
mujoco.mjv_applyPerturbPose(self.model, self.data, self.pert, 0)
485495
mujoco.mjv_applyPerturbForce(self.model, self.data, self.pert)
486496

497+
def read_pixels(self, camid=None):
498+
if self.render_mode is 'window':
499+
raise NotImplementedError(
500+
"Use 'render()' in 'window' mode.")
501+
502+
if camid is not None:
503+
if camid==-1:
504+
self.cam.type = mujoco.mjtCamera.mjCAMERA_FREE
505+
else:
506+
self.cam.type = mujoco.mjtCamera.mjCAMERA_FIXED
507+
self.cam.fixedcamid = camid
508+
509+
self.viewport.width, self.viewport.height = glfw.get_framebuffer_size(
510+
self.window)
511+
# update scene
512+
mujoco.mjv_updateScene(
513+
self.model,
514+
self.data,
515+
self.vopt,
516+
self.pert,
517+
self.cam,
518+
mujoco.mjtCatBit.mjCAT_ALL.value,
519+
self.scn)
520+
# render
521+
mujoco.mjr_render(self.viewport, self.scn, self.ctx)
522+
523+
img = np.zeros(
524+
(glfw.get_framebuffer_size(
525+
self.window)[1], glfw.get_framebuffer_size(
526+
self.window)[0], 3), dtype=np.uint8)
527+
mujoco.mjr_readPixels(img, None, self.viewport, self.ctx)
528+
return np.flipud(img)
529+
487530
def render(self):
531+
if self.render_mode is 'offscreen':
532+
raise NotImplementedError(
533+
"Use 'read_pixels()' for 'offscreen' mode.")
488534
if not self.is_alive:
489535
raise Exception(
490536
"GLFW window does not exist but you tried to render.")

0 commit comments

Comments
 (0)