From b4d34fef8209ecb6f9fff77917042a356520059c Mon Sep 17 00:00:00 2001 From: Yifeng Zhu Date: Wed, 28 Sep 2022 11:36:34 -0500 Subject: [PATCH 01/16] Initial implementation of gpu rendering specification --- robosuite/utils/binding_utils.py | 62 ++++++++++++++- robosuite/utils/egl_context.py | 122 ++++++++++++++++++++++++++++++ robosuite/utils/glfw_context.py | 42 ++++++++++ robosuite/utils/osmesa_context.py | 82 ++++++++++++++++++++ 4 files changed, 304 insertions(+), 4 deletions(-) create mode 100644 robosuite/utils/egl_context.py create mode 100644 robosuite/utils/glfw_context.py create mode 100644 robosuite/utils/osmesa_context.py diff --git a/robosuite/utils/binding_utils.py b/robosuite/utils/binding_utils.py index 92155a5cb7..abee688bd2 100644 --- a/robosuite/utils/binding_utils.py +++ b/robosuite/utils/binding_utils.py @@ -14,6 +14,42 @@ _MjSim_render_lock = Lock() +import ctypes +import ctypes.util +import os +import platform +import subprocess + +_SYSTEM = platform.system() +if _SYSTEM == 'Windows': + ctypes.WinDLL(os.path.join(os.path.dirname(__file__), 'mujoco.dll')) + +# pylint: disable=g-import-not-at-top +if os.environ.get('CUDA_VISIBLE_DEVICES', '') == '': + _MUJOCO_GL = "osmesa" +_MUJOCO_GL = os.environ.get('MUJOCO_GL', '').lower().strip() +if _MUJOCO_GL not in ('disable', 'disabled', 'off', 'false', '0'): + _VALID_MUJOCO_GL = ('enable', 'enabled', 'on', 'true', '1' , 'glfw', '') + if _SYSTEM == 'Linux': + _VALID_MUJOCO_GL += ('glx', 'egl', 'osmesa') + elif _SYSTEM == 'Windows': + _VALID_MUJOCO_GL += ('wgl',) + elif _SYSTEM == 'Darwin': + _VALID_MUJOCO_GL += ('cgl',) + if _MUJOCO_GL not in _VALID_MUJOCO_GL: + raise RuntimeError( + f'invalid value for environment variable MUJOCO_GL: {_MUJOCO_GL}') + import pdb; pdb.set_trace() + + if _SYSTEM == 'Linux' and _MUJOCO_GL == 'osmesa': + os.environ['PYOPENGL_PLATFORM'] = 'osmesa' + from robosuite.utils.osmesa_context import OSMesaGLContext + elif _SYSTEM == 'Linux' and _MUJOCO_GL == 'egl': + os.environ['PYOPENGL_PLATFORM'] = 'egl' + from robosuite.utils.egl_context import EGLGLContext + else: + from robosuite.utils.glfw_context import GLFWGLContext + class MjRenderContext: """ @@ -23,7 +59,7 @@ class MjRenderContext: See https://github.com/openai/mujoco-py/blob/4830435a169c1f3e3b5f9b58a7c3d9c39bdf4acb/mujoco_py/mjrendercontext.pyx """ - def __init__(self, sim, offscreen=True, device_id=-1): + def __init__(self, sim, offscreen=True, device_id=-1, max_width=640, max_height=480): assert offscreen, "only offscreen supported for now" self.sim = sim self.offscreen = offscreen @@ -33,11 +69,29 @@ def __init__(self, sim, offscreen=True, device_id=-1): if self.device_id is not None and self.device_id >= 0: os.environ["MUJOCO_GL"] = "egl" os.environ["MUJOCO_EGL_DEVICE_ID"] = str(self.device_id) + os.environ['PYOPENGL_PLATFORM'] = 'egl' + + GLContext = EGLGLContext else: os.environ["MUJOCO_GL"] = "osmesa" + os.environ['PYOPENGL_PLATFORM'] = 'osmesa' + GLContext = OSMesaGLContext + else: + os.environ['PYOPENGL_PLATFORM'] = 'glfw' + GLContext = GLFWGLContext # setup GL context with defaults for now - self.gl_ctx = mujoco.GLContext(max_width=640, max_height=480) + + import time; + t1 = time.time_ns() + self.gl_ctx = mujoco.GLContext(max_width=max_width, max_height=max_height) + + t2 = time.time_ns() + + self.gl_ctx = GLContext(max_width=max_width, max_height=max_height, device_id=self.device_id) + + t3 = time.time_ns() + print((t2 - t1) / (10 ** 9), (t3 - t2) / (10 ** 9)) self.gl_ctx.make_current() # Ensure the model data has been updated so that there @@ -163,8 +217,8 @@ def __del__(self): class MjRenderContextOffscreen(MjRenderContext): - def __init__(self, sim, device_id): - super().__init__(sim, offscreen=True, device_id=device_id) + def __init__(self, sim, device_id, max_width=640, max_height=480): + super().__init__(sim, offscreen=True, device_id=device_id, max_width=max_width, max_height=max_height) class MjSimState: diff --git a/robosuite/utils/egl_context.py b/robosuite/utils/egl_context.py new file mode 100644 index 0000000000..bbdbd52a9d --- /dev/null +++ b/robosuite/utils/egl_context.py @@ -0,0 +1,122 @@ +import atexit +import ctypes +import os + +PYOPENGL_PLATFORM = os.environ.get('PYOPENGL_PLATFORM') + +if not PYOPENGL_PLATFORM: + os.environ['PYOPENGL_PLATFORM'] = 'egl' +elif PYOPENGL_PLATFORM.lower() != 'egl': + raise ImportError( + 'Cannot use EGL rendering platform. ' + 'The PYOPENGL_PLATFORM environment variable is set to {!r} ' + '(should be either unset or \'egl\').') + +from mujoco.egl import egl_ext as EGL +from OpenGL import error + + +def create_initialized_egl_device_display(device_id=0): + """Creates an initialized EGL display directly on a device.""" + all_devices = EGL.eglQueryDevicesEXT() + selected_device = os.environ.get('CUDA_VISIBLE_DEVICES', None) if os.environ.get('MUJOCO_EGL_DEVICE_ID', None) is None else os.environ.get('MUJOCO_EGL_DEVICE_ID', None) + if selected_device is None: + candidates = all_devices + else: + if not selected_device.isdigit(): + device_inds = [int(x) for x in selected_device.split(",")] + device_idx = device_inds[device_id] + else: + device_idx = int(selected_device) + if not 0 <= device_idx < len(all_devices): + raise RuntimeError( + f'The MUJOCO_EGL_DEVICE_ID environment variable must be an integer ' + f'between 0 and {len(all_devices)-1} (inclusive), got {device_idx}.') + candidates = all_devices[device_idx:device_idx + 1] + for device in candidates: + display = EGL.eglGetPlatformDisplayEXT( + EGL.EGL_PLATFORM_DEVICE_EXT, device, None) + if display != EGL.EGL_NO_DISPLAY and EGL.eglGetError() == EGL.EGL_SUCCESS: + # `eglInitialize` may or may not raise an exception on failure depending + # on how PyOpenGL is configured. We therefore catch a `GLError` and also + # manually check the output of `eglGetError()` here. + try: + initialized = EGL.eglInitialize(display, None, None) + except error.GLError: + pass + else: + if initialized == EGL.EGL_TRUE and EGL.eglGetError() == EGL.EGL_SUCCESS: + return display + return EGL.EGL_NO_DISPLAY + +global EGL_DISPLAY +EGL_DISPLAY = None + +EGL_ATTRIBUTES = ( + EGL.EGL_RED_SIZE, 8, + EGL.EGL_GREEN_SIZE, 8, + EGL.EGL_BLUE_SIZE, 8, + EGL.EGL_ALPHA_SIZE, 8, + EGL.EGL_DEPTH_SIZE, 24, + EGL.EGL_STENCIL_SIZE, 8, + EGL.EGL_COLOR_BUFFER_TYPE, EGL.EGL_RGB_BUFFER, + EGL.EGL_SURFACE_TYPE, EGL.EGL_PBUFFER_BIT, + EGL.EGL_RENDERABLE_TYPE, EGL.EGL_OPENGL_BIT, + EGL.EGL_NONE +) + + +class EGLGLContext(): + """An EGL context for headless accelerated OpenGL rendering on GPU devices.""" + + def __init__(self, max_width, max_height, device_id=0): + + del max_width, max_height # unused + num_configs = ctypes.c_long() + config_size = 1 + config = EGL.EGLConfig() + EGL.eglReleaseThread() + global EGL_DISPLAY + if EGL_DISPLAY is None: + # only initialize for the first time + EGL_DISPLAY = create_initialized_egl_device_display(device_id=device_id) + if EGL_DISPLAY == EGL.EGL_NO_DISPLAY: + raise ImportError( + 'Cannot initialize a EGL device display. This likely means that your EGL ' + 'driver does not support the PLATFORM_DEVICE extension, which is ' + 'required for creating a headless rendering context.') + atexit.register(EGL.eglTerminate, EGL_DISPLAY) + EGL.eglChooseConfig( + EGL_DISPLAY, + EGL_ATTRIBUTES, + ctypes.byref(config), + config_size, + num_configs) + if num_configs.value < 1: + raise RuntimeError( + 'EGL failed to find a framebuffer configuration that matches the ' + 'desired attributes: {}'.format(EGL_ATTRIBUTES)) + EGL.eglBindAPI(EGL.EGL_OPENGL_API) + self._context = EGL.eglCreateContext( + EGL_DISPLAY, config, EGL.EGL_NO_CONTEXT, None) + if not self._context: + raise RuntimeError('Cannot create an EGL context.') + + def make_current(self): + if not EGL.eglMakeCurrent( + EGL_DISPLAY, EGL.EGL_NO_SURFACE, EGL.EGL_NO_SURFACE, self._context): + raise RuntimeError('Failed to make the EGL context current.') + + def free(self): + """Frees resources associated with this context.""" + if self._context: + current_context = EGL.eglGetCurrentContext() + if current_context and self._context.address == current_context.address: + EGL.eglMakeCurrent(EGL_DISPLAY, EGL.EGL_NO_SURFACE, + EGL.EGL_NO_SURFACE, EGL.EGL_NO_CONTEXT) + EGL.eglDestroyContext(EGL_DISPLAY, self._context) + EGL.eglReleaseThread() + self._context = None + + def __del__(self): + self.free() diff --git a/robosuite/utils/glfw_context.py b/robosuite/utils/glfw_context.py new file mode 100644 index 0000000000..a214efdc2e --- /dev/null +++ b/robosuite/utils/glfw_context.py @@ -0,0 +1,42 @@ + +# Copyright 2017 The dm_control Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""An OpenGL context created via GLFW.""" + +import glfw + + +class GLFWGLContext(): + """An OpenGL context created via GLFW.""" + + def __init__(self, max_width, max_height, device_id=0): + glfw.init() + glfw.window_hint(glfw.VISIBLE, device_id) + self._context = glfw.create_window(width=max_width, height=max_height, + title='Invisible window', monitor=None, + share=None) + + def make_current(self): + glfw.make_context_current(self._context) + + def free(self): + if self._context: + if glfw.get_current_context() == self._context: + glfw.make_context_current(None) + glfw.destroy_window(self._context) + self._context = None + + def __del__(self): + self.free() diff --git a/robosuite/utils/osmesa_context.py b/robosuite/utils/osmesa_context.py new file mode 100644 index 0000000000..36c841ec15 --- /dev/null +++ b/robosuite/utils/osmesa_context.py @@ -0,0 +1,82 @@ +# Copyright 2018 The dm_control Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""An OSMesa context for software-based OpenGL rendering.""" + +import os + +PYOPENGL_PLATFORM = os.environ.get('PYOPENGL_PLATFORM') + +if not PYOPENGL_PLATFORM: + os.environ['PYOPENGL_PLATFORM'] = 'osmesa' +elif PYOPENGL_PLATFORM.lower() != 'osmesa': + raise ImportError( + 'Cannot use OSMesa rendering platform. ' + 'The PYOPENGL_PLATFORM environment variable is set to {!r} ' + '(should be either unset or \'osmesa\').' + .format(PYOPENGL_PLATFORM)) + +# pylint: disable=g-import-not-at-top +from OpenGL import GL +from OpenGL import osmesa +from OpenGL.GL import arrays + +_DEPTH_BITS = 24 +_STENCIL_BITS = 8 +_ACCUM_BITS = 0 + + +class OSMesaGLContext(): + """An OSMesa context for software-based OpenGL rendering.""" + + def __init__(self, max_width, max_height, device_id=-1): + """Initializes this OSMesa context.""" + self._context = osmesa.OSMesaCreateContextExt( + osmesa.OSMESA_RGBA, + _DEPTH_BITS, + _STENCIL_BITS, + _ACCUM_BITS, + None, # sharelist + ) + if not self._context: + raise RuntimeError('Failed to create OSMesa GL context.') + + self._height = max_height + self._width = max_width + + # Allocate a buffer to render into. + self._buffer = arrays.GLfloatArray.zeros((max_height, max_width, 4)) + + def make_current(self): + if self._context: + success = osmesa.OSMesaMakeCurrent( + self._context, + self._buffer, + GL.GL_FLOAT, + self._width, + self._height) + if not success: + raise RuntimeError('Failed to make OSMesa context current.') + + def free(self): + """Frees resources associated with this context.""" + if self._context and self._context == osmesa.OSMesaGetCurrentContext(): + osmesa.OSMesaMakeCurrent(None, None, GL.GL_FLOAT, 0, 0) + osmesa.OSMesaDestroyContext(self._context) + self._buffer = None + self._context = None + + def __del__(self): + self.free() + From 64302c784cb7da6cdc01c6cf62bda2d11599e1df Mon Sep 17 00:00:00 2001 From: Yifeng Zhu Date: Wed, 28 Sep 2022 11:38:01 -0500 Subject: [PATCH 02/16] Remove pdb --- robosuite/utils/binding_utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/robosuite/utils/binding_utils.py b/robosuite/utils/binding_utils.py index abee688bd2..ed5c9a86e5 100644 --- a/robosuite/utils/binding_utils.py +++ b/robosuite/utils/binding_utils.py @@ -39,7 +39,6 @@ if _MUJOCO_GL not in _VALID_MUJOCO_GL: raise RuntimeError( f'invalid value for environment variable MUJOCO_GL: {_MUJOCO_GL}') - import pdb; pdb.set_trace() if _SYSTEM == 'Linux' and _MUJOCO_GL == 'osmesa': os.environ['PYOPENGL_PLATFORM'] = 'osmesa' From f552829de96046b0fe5eacb4086bed86202e9c1b Mon Sep 17 00:00:00 2001 From: Yifeng Zhu Date: Wed, 28 Sep 2022 21:46:40 -0400 Subject: [PATCH 03/16] WIP: testing rendering on non-gpu machines --- robosuite/utils/binding_utils.py | 69 +++++----- robosuite/utils/egl_context.py | 201 ++++++++++++++++-------------- robosuite/utils/glfw_context.py | 37 +++--- robosuite/utils/macros.py | 2 + robosuite/utils/osmesa_context.py | 87 ++++++------- 5 files changed, 206 insertions(+), 190 deletions(-) diff --git a/robosuite/utils/binding_utils.py b/robosuite/utils/binding_utils.py index ed5c9a86e5..356289eec3 100644 --- a/robosuite/utils/binding_utils.py +++ b/robosuite/utils/binding_utils.py @@ -20,35 +20,47 @@ import platform import subprocess -_SYSTEM = platform.system() -if _SYSTEM == 'Windows': - ctypes.WinDLL(os.path.join(os.path.dirname(__file__), 'mujoco.dll')) +from robosuite.utils import macros # pylint: disable=g-import-not-at-top -if os.environ.get('CUDA_VISIBLE_DEVICES', '') == '': + +_SYSTEM = platform.system() +if _SYSTEM == "Windows": + ctypes.WinDLL(os.path.join(os.path.dirname(__file__), "mujoco.dll")) +elif _SYSTEM == "Linux": + ctypes.CDLL(ctypes.util.find_library('GL'), ctypes.RTLD_GLOBAL) + ctypes.CDLL(ctypes.util.find_library('OSMesa'), ctypes.RTLD_GLOBAL) + +if os.environ.get("MUJOCO_GL", None) == "osmesa" or not macros.MUJOCO_GPU_RENDERING: _MUJOCO_GL = "osmesa" -_MUJOCO_GL = os.environ.get('MUJOCO_GL', '').lower().strip() -if _MUJOCO_GL not in ('disable', 'disabled', 'off', 'false', '0'): - _VALID_MUJOCO_GL = ('enable', 'enabled', 'on', 'true', '1' , 'glfw', '') - if _SYSTEM == 'Linux': - _VALID_MUJOCO_GL += ('glx', 'egl', 'osmesa') - elif _SYSTEM == 'Windows': - _VALID_MUJOCO_GL += ('wgl',) - elif _SYSTEM == 'Darwin': - _VALID_MUJOCO_GL += ('cgl',) +else: + CUDA_VISIBLE_DEVICES = os.environ.get("CUDA_VISIBLE_DEVICES", None) + if CUDA_VISIBLE_DEVICES is not None: + MUJOCO_EGL_DEVICE_ID = os.environ.get("MUJOCO_EGL_DEVICE_ID", None) + if MUJOCO_EGL_DEVICE_ID is not None: + assert MUJOCO_EGL_DEVICE_ID.isdigit() and MUJOCO_EGL_DEVICE_ID in CUDA_VISIBLE_DEVICES + +_MUJOCO_GL = os.environ.get("MUJOCO_GL", "").lower().strip() +if _MUJOCO_GL not in ("disable", "disabled", "off", "false", "0"): + _VALID_MUJOCO_GL = ("enable", "enabled", "on", "true", "1", "glfw", "") + if _SYSTEM == "Linux": + _VALID_MUJOCO_GL += ("glx", "egl", "osmesa") + elif _SYSTEM == "Windows": + _VALID_MUJOCO_GL += ("wgl",) + elif _SYSTEM == "Darwin": + _VALID_MUJOCO_GL += ("cgl",) if _MUJOCO_GL not in _VALID_MUJOCO_GL: - raise RuntimeError( - f'invalid value for environment variable MUJOCO_GL: {_MUJOCO_GL}') - - if _SYSTEM == 'Linux' and _MUJOCO_GL == 'osmesa': - os.environ['PYOPENGL_PLATFORM'] = 'osmesa' + raise RuntimeError(f"invalid value for environment variable MUJOCO_GL: {_MUJOCO_GL}") + + if _SYSTEM == "Linux" and _MUJOCO_GL == "osmesa": + os.environ["PYOPENGL_PLATFORM"] = "osmesa" from robosuite.utils.osmesa_context import OSMesaGLContext - elif _SYSTEM == 'Linux' and _MUJOCO_GL == 'egl': - os.environ['PYOPENGL_PLATFORM'] = 'egl' + elif _SYSTEM == "Linux" and _MUJOCO_GL == "egl": + os.environ["PYOPENGL_PLATFORM"] = "egl" from robosuite.utils.egl_context import EGLGLContext else: from robosuite.utils.glfw_context import GLFWGLContext - + class MjRenderContext: """ @@ -68,29 +80,30 @@ def __init__(self, sim, offscreen=True, device_id=-1, max_width=640, max_height= if self.device_id is not None and self.device_id >= 0: os.environ["MUJOCO_GL"] = "egl" os.environ["MUJOCO_EGL_DEVICE_ID"] = str(self.device_id) - os.environ['PYOPENGL_PLATFORM'] = 'egl' + os.environ["PYOPENGL_PLATFORM"] = "egl" GLContext = EGLGLContext else: os.environ["MUJOCO_GL"] = "osmesa" - os.environ['PYOPENGL_PLATFORM'] = 'osmesa' GLContext = OSMesaGLContext + import pdb; pdb.set_trace() else: - os.environ['PYOPENGL_PLATFORM'] = 'glfw' + os.environ["PYOPENGL_PLATFORM"] = "glfw" GLContext = GLFWGLContext # setup GL context with defaults for now - - import time; + + import time + t1 = time.time_ns() self.gl_ctx = mujoco.GLContext(max_width=max_width, max_height=max_height) t2 = time.time_ns() - + self.gl_ctx = GLContext(max_width=max_width, max_height=max_height, device_id=self.device_id) t3 = time.time_ns() - print((t2 - t1) / (10 ** 9), (t3 - t2) / (10 ** 9)) + print((t2 - t1) / (10**9), (t3 - t2) / (10**9)) self.gl_ctx.make_current() # Ensure the model data has been updated so that there diff --git a/robosuite/utils/egl_context.py b/robosuite/utils/egl_context.py index bbdbd52a9d..a46cc77d69 100644 --- a/robosuite/utils/egl_context.py +++ b/robosuite/utils/egl_context.py @@ -2,121 +2,130 @@ import ctypes import os -PYOPENGL_PLATFORM = os.environ.get('PYOPENGL_PLATFORM') +PYOPENGL_PLATFORM = os.environ.get("PYOPENGL_PLATFORM") if not PYOPENGL_PLATFORM: - os.environ['PYOPENGL_PLATFORM'] = 'egl' -elif PYOPENGL_PLATFORM.lower() != 'egl': - raise ImportError( - 'Cannot use EGL rendering platform. ' - 'The PYOPENGL_PLATFORM environment variable is set to {!r} ' - '(should be either unset or \'egl\').') + os.environ["PYOPENGL_PLATFORM"] = "egl" +elif PYOPENGL_PLATFORM.lower() != "egl": + raise ImportError( + "Cannot use EGL rendering platform. " + "The PYOPENGL_PLATFORM environment variable is set to {!r} " + "(should be either unset or 'egl')." + ) from mujoco.egl import egl_ext as EGL from OpenGL import error def create_initialized_egl_device_display(device_id=0): - """Creates an initialized EGL display directly on a device.""" - all_devices = EGL.eglQueryDevicesEXT() - selected_device = os.environ.get('CUDA_VISIBLE_DEVICES', None) if os.environ.get('MUJOCO_EGL_DEVICE_ID', None) is None else os.environ.get('MUJOCO_EGL_DEVICE_ID', None) - if selected_device is None: - candidates = all_devices - else: - if not selected_device.isdigit(): - device_inds = [int(x) for x in selected_device.split(",")] - device_idx = device_inds[device_id] + """Creates an initialized EGL display directly on a device.""" + all_devices = EGL.eglQueryDevicesEXT() + selected_device = ( + os.environ.get("CUDA_VISIBLE_DEVICES", None) + if os.environ.get("MUJOCO_EGL_DEVICE_ID", None) is None + else os.environ.get("MUJOCO_EGL_DEVICE_ID", None) + ) + if selected_device is None: + candidates = all_devices else: - device_idx = int(selected_device) - if not 0 <= device_idx < len(all_devices): - raise RuntimeError( - f'The MUJOCO_EGL_DEVICE_ID environment variable must be an integer ' - f'between 0 and {len(all_devices)-1} (inclusive), got {device_idx}.') - candidates = all_devices[device_idx:device_idx + 1] - for device in candidates: - display = EGL.eglGetPlatformDisplayEXT( - EGL.EGL_PLATFORM_DEVICE_EXT, device, None) - if display != EGL.EGL_NO_DISPLAY and EGL.eglGetError() == EGL.EGL_SUCCESS: - # `eglInitialize` may or may not raise an exception on failure depending - # on how PyOpenGL is configured. We therefore catch a `GLError` and also - # manually check the output of `eglGetError()` here. - try: - initialized = EGL.eglInitialize(display, None, None) - except error.GLError: - pass - else: - if initialized == EGL.EGL_TRUE and EGL.eglGetError() == EGL.EGL_SUCCESS: - return display - return EGL.EGL_NO_DISPLAY + if not selected_device.isdigit(): + device_inds = [int(x) for x in selected_device.split(",")] + device_idx = device_inds[device_id] + else: + device_idx = int(selected_device) + if not 0 <= device_idx < len(all_devices): + raise RuntimeError( + f"The MUJOCO_EGL_DEVICE_ID environment variable must be an integer " + f"between 0 and {len(all_devices)-1} (inclusive), got {device_idx}." + ) + candidates = all_devices[device_idx : device_idx + 1] + for device in candidates: + display = EGL.eglGetPlatformDisplayEXT(EGL.EGL_PLATFORM_DEVICE_EXT, device, None) + if display != EGL.EGL_NO_DISPLAY and EGL.eglGetError() == EGL.EGL_SUCCESS: + # `eglInitialize` may or may not raise an exception on failure depending + # on how PyOpenGL is configured. We therefore catch a `GLError` and also + # manually check the output of `eglGetError()` here. + try: + initialized = EGL.eglInitialize(display, None, None) + except error.GLError: + pass + else: + if initialized == EGL.EGL_TRUE and EGL.eglGetError() == EGL.EGL_SUCCESS: + return display + return EGL.EGL_NO_DISPLAY + global EGL_DISPLAY EGL_DISPLAY = None EGL_ATTRIBUTES = ( - EGL.EGL_RED_SIZE, 8, - EGL.EGL_GREEN_SIZE, 8, - EGL.EGL_BLUE_SIZE, 8, - EGL.EGL_ALPHA_SIZE, 8, - EGL.EGL_DEPTH_SIZE, 24, - EGL.EGL_STENCIL_SIZE, 8, - EGL.EGL_COLOR_BUFFER_TYPE, EGL.EGL_RGB_BUFFER, - EGL.EGL_SURFACE_TYPE, EGL.EGL_PBUFFER_BIT, - EGL.EGL_RENDERABLE_TYPE, EGL.EGL_OPENGL_BIT, - EGL.EGL_NONE + EGL.EGL_RED_SIZE, + 8, + EGL.EGL_GREEN_SIZE, + 8, + EGL.EGL_BLUE_SIZE, + 8, + EGL.EGL_ALPHA_SIZE, + 8, + EGL.EGL_DEPTH_SIZE, + 24, + EGL.EGL_STENCIL_SIZE, + 8, + EGL.EGL_COLOR_BUFFER_TYPE, + EGL.EGL_RGB_BUFFER, + EGL.EGL_SURFACE_TYPE, + EGL.EGL_PBUFFER_BIT, + EGL.EGL_RENDERABLE_TYPE, + EGL.EGL_OPENGL_BIT, + EGL.EGL_NONE, ) -class EGLGLContext(): - """An EGL context for headless accelerated OpenGL rendering on GPU devices.""" +class EGLGLContext: + """An EGL context for headless accelerated OpenGL rendering on GPU devices.""" - def __init__(self, max_width, max_height, device_id=0): + def __init__(self, max_width, max_height, device_id=0): - del max_width, max_height # unused - num_configs = ctypes.c_long() - config_size = 1 - config = EGL.EGLConfig() - EGL.eglReleaseThread() - global EGL_DISPLAY - if EGL_DISPLAY is None: - # only initialize for the first time - EGL_DISPLAY = create_initialized_egl_device_display(device_id=device_id) - if EGL_DISPLAY == EGL.EGL_NO_DISPLAY: - raise ImportError( - 'Cannot initialize a EGL device display. This likely means that your EGL ' - 'driver does not support the PLATFORM_DEVICE extension, which is ' - 'required for creating a headless rendering context.') - atexit.register(EGL.eglTerminate, EGL_DISPLAY) - EGL.eglChooseConfig( - EGL_DISPLAY, - EGL_ATTRIBUTES, - ctypes.byref(config), - config_size, - num_configs) - if num_configs.value < 1: - raise RuntimeError( - 'EGL failed to find a framebuffer configuration that matches the ' - 'desired attributes: {}'.format(EGL_ATTRIBUTES)) - EGL.eglBindAPI(EGL.EGL_OPENGL_API) - self._context = EGL.eglCreateContext( - EGL_DISPLAY, config, EGL.EGL_NO_CONTEXT, None) - if not self._context: - raise RuntimeError('Cannot create an EGL context.') + del max_width, max_height # unused + num_configs = ctypes.c_long() + config_size = 1 + config = EGL.EGLConfig() + EGL.eglReleaseThread() + global EGL_DISPLAY + if EGL_DISPLAY is None: + # only initialize for the first time + EGL_DISPLAY = create_initialized_egl_device_display(device_id=device_id) + if EGL_DISPLAY == EGL.EGL_NO_DISPLAY: + raise ImportError( + "Cannot initialize a EGL device display. This likely means that your EGL " + "driver does not support the PLATFORM_DEVICE extension, which is " + "required for creating a headless rendering context." + ) + atexit.register(EGL.eglTerminate, EGL_DISPLAY) + EGL.eglChooseConfig(EGL_DISPLAY, EGL_ATTRIBUTES, ctypes.byref(config), config_size, num_configs) + if num_configs.value < 1: + raise RuntimeError( + "EGL failed to find a framebuffer configuration that matches the " + "desired attributes: {}".format(EGL_ATTRIBUTES) + ) + EGL.eglBindAPI(EGL.EGL_OPENGL_API) + self._context = EGL.eglCreateContext(EGL_DISPLAY, config, EGL.EGL_NO_CONTEXT, None) + if not self._context: + raise RuntimeError("Cannot create an EGL context.") - def make_current(self): - if not EGL.eglMakeCurrent( - EGL_DISPLAY, EGL.EGL_NO_SURFACE, EGL.EGL_NO_SURFACE, self._context): - raise RuntimeError('Failed to make the EGL context current.') + def make_current(self): + if not EGL.eglMakeCurrent(EGL_DISPLAY, EGL.EGL_NO_SURFACE, EGL.EGL_NO_SURFACE, self._context): + raise RuntimeError("Failed to make the EGL context current.") - def free(self): - """Frees resources associated with this context.""" - if self._context: - current_context = EGL.eglGetCurrentContext() - if current_context and self._context.address == current_context.address: - EGL.eglMakeCurrent(EGL_DISPLAY, EGL.EGL_NO_SURFACE, - EGL.EGL_NO_SURFACE, EGL.EGL_NO_CONTEXT) - EGL.eglDestroyContext(EGL_DISPLAY, self._context) - EGL.eglReleaseThread() - self._context = None + def free(self): + """Frees resources associated with this context.""" + if self._context: + current_context = EGL.eglGetCurrentContext() + if current_context and self._context.address == current_context.address: + EGL.eglMakeCurrent(EGL_DISPLAY, EGL.EGL_NO_SURFACE, EGL.EGL_NO_SURFACE, EGL.EGL_NO_CONTEXT) + EGL.eglDestroyContext(EGL_DISPLAY, self._context) + EGL.eglReleaseThread() + self._context = None - def __del__(self): - self.free() + def __del__(self): + self.free() diff --git a/robosuite/utils/glfw_context.py b/robosuite/utils/glfw_context.py index a214efdc2e..1bbe2886de 100644 --- a/robosuite/utils/glfw_context.py +++ b/robosuite/utils/glfw_context.py @@ -1,4 +1,3 @@ - # Copyright 2017 The dm_control Authors # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,25 +17,25 @@ import glfw -class GLFWGLContext(): - """An OpenGL context created via GLFW.""" +class GLFWGLContext: + """An OpenGL context created via GLFW.""" - def __init__(self, max_width, max_height, device_id=0): - glfw.init() - glfw.window_hint(glfw.VISIBLE, device_id) - self._context = glfw.create_window(width=max_width, height=max_height, - title='Invisible window', monitor=None, - share=None) + def __init__(self, max_width, max_height, device_id=0): + glfw.init() + glfw.window_hint(glfw.VISIBLE, device_id) + self._context = glfw.create_window( + width=max_width, height=max_height, title="Invisible window", monitor=None, share=None + ) - def make_current(self): - glfw.make_context_current(self._context) + def make_current(self): + glfw.make_context_current(self._context) - def free(self): - if self._context: - if glfw.get_current_context() == self._context: - glfw.make_context_current(None) - glfw.destroy_window(self._context) - self._context = None + def free(self): + if self._context: + if glfw.get_current_context() == self._context: + glfw.make_context_current(None) + glfw.destroy_window(self._context) + self._context = None - def __del__(self): - self.free() + def __del__(self): + self.free() diff --git a/robosuite/utils/macros.py b/robosuite/utils/macros.py index 8a7f221fa7..9083b0192f 100644 --- a/robosuite/utils/macros.py +++ b/robosuite/utils/macros.py @@ -34,3 +34,5 @@ # In general, observations are concatenated together by modality. However, image observations are expensive memory-wise, # so we skip concatenating all images together by default, unless this flag is set to True CONCATENATE_IMAGES = False + +MUJOCO_GPU_RENDERING = True diff --git a/robosuite/utils/osmesa_context.py b/robosuite/utils/osmesa_context.py index 36c841ec15..d93bbcb643 100644 --- a/robosuite/utils/osmesa_context.py +++ b/robosuite/utils/osmesa_context.py @@ -16,20 +16,19 @@ import os -PYOPENGL_PLATFORM = os.environ.get('PYOPENGL_PLATFORM') +PYOPENGL_PLATFORM = os.environ.get("PYOPENGL_PLATFORM") if not PYOPENGL_PLATFORM: - os.environ['PYOPENGL_PLATFORM'] = 'osmesa' -elif PYOPENGL_PLATFORM.lower() != 'osmesa': - raise ImportError( - 'Cannot use OSMesa rendering platform. ' - 'The PYOPENGL_PLATFORM environment variable is set to {!r} ' - '(should be either unset or \'osmesa\').' - .format(PYOPENGL_PLATFORM)) + os.environ["PYOPENGL_PLATFORM"] = "osmesa" +elif PYOPENGL_PLATFORM.lower() != "osmesa": + raise ImportError( + "Cannot use OSMesa rendering platform. " + "The PYOPENGL_PLATFORM environment variable is set to {!r} " + "(should be either unset or 'osmesa').".format(PYOPENGL_PLATFORM) + ) # pylint: disable=g-import-not-at-top -from OpenGL import GL -from OpenGL import osmesa +from OpenGL import GL, osmesa from OpenGL.GL import arrays _DEPTH_BITS = 24 @@ -37,46 +36,40 @@ _ACCUM_BITS = 0 -class OSMesaGLContext(): - """An OSMesa context for software-based OpenGL rendering.""" +class OSMesaGLContext: + """An OSMesa context for software-based OpenGL rendering.""" - def __init__(self, max_width, max_height, device_id=-1): - """Initializes this OSMesa context.""" - self._context = osmesa.OSMesaCreateContextExt( - osmesa.OSMESA_RGBA, - _DEPTH_BITS, - _STENCIL_BITS, - _ACCUM_BITS, - None, # sharelist - ) - if not self._context: - raise RuntimeError('Failed to create OSMesa GL context.') + def __init__(self, max_width, max_height, device_id=-1): + """Initializes this OSMesa context.""" + self._context = osmesa.OSMesaCreateContextExt( + osmesa.OSMESA_RGBA, + _DEPTH_BITS, + _STENCIL_BITS, + _ACCUM_BITS, + None, # sharelist + ) + if not self._context: + raise RuntimeError("Failed to create OSMesa GL context.") - self._height = max_height - self._width = max_width + self._height = max_height + self._width = max_width - # Allocate a buffer to render into. - self._buffer = arrays.GLfloatArray.zeros((max_height, max_width, 4)) + # Allocate a buffer to render into. + self._buffer = arrays.GLfloatArray.zeros((max_height, max_width, 4)) - def make_current(self): - if self._context: - success = osmesa.OSMesaMakeCurrent( - self._context, - self._buffer, - GL.GL_FLOAT, - self._width, - self._height) - if not success: - raise RuntimeError('Failed to make OSMesa context current.') + def make_current(self): + if self._context: + success = osmesa.OSMesaMakeCurrent(self._context, self._buffer, GL.GL_FLOAT, self._width, self._height) + if not success: + raise RuntimeError("Failed to make OSMesa context current.") - def free(self): - """Frees resources associated with this context.""" - if self._context and self._context == osmesa.OSMesaGetCurrentContext(): - osmesa.OSMesaMakeCurrent(None, None, GL.GL_FLOAT, 0, 0) - osmesa.OSMesaDestroyContext(self._context) - self._buffer = None - self._context = None + def free(self): + """Frees resources associated with this context.""" + if self._context and self._context == osmesa.OSMesaGetCurrentContext(): + osmesa.OSMesaMakeCurrent(None, None, GL.GL_FLOAT, 0, 0) + osmesa.OSMesaDestroyContext(self._context) + self._buffer = None + self._context = None - def __del__(self): - self.free() - + def __del__(self): + self.free() From 2ed152781fc01823f33160a54d47b423ec7ae01b Mon Sep 17 00:00:00 2001 From: Yifeng Zhu Date: Wed, 28 Sep 2022 22:48:35 -0400 Subject: [PATCH 04/16] Changes that are verified on laptop --- robosuite/utils/binding_utils.py | 83 +++++++++++++++---------------- robosuite/utils/glfw_context.py | 24 ++------- robosuite/utils/osmesa_context.py | 57 ++------------------- 3 files changed, 47 insertions(+), 117 deletions(-) diff --git a/robosuite/utils/binding_utils.py b/robosuite/utils/binding_utils.py index 356289eec3..a6c75a7c01 100644 --- a/robosuite/utils/binding_utils.py +++ b/robosuite/utils/binding_utils.py @@ -27,20 +27,15 @@ _SYSTEM = platform.system() if _SYSTEM == "Windows": ctypes.WinDLL(os.path.join(os.path.dirname(__file__), "mujoco.dll")) -elif _SYSTEM == "Linux": - ctypes.CDLL(ctypes.util.find_library('GL'), ctypes.RTLD_GLOBAL) - ctypes.CDLL(ctypes.util.find_library('OSMesa'), ctypes.RTLD_GLOBAL) - -if os.environ.get("MUJOCO_GL", None) == "osmesa" or not macros.MUJOCO_GPU_RENDERING: - _MUJOCO_GL = "osmesa" -else: - CUDA_VISIBLE_DEVICES = os.environ.get("CUDA_VISIBLE_DEVICES", None) - if CUDA_VISIBLE_DEVICES is not None: - MUJOCO_EGL_DEVICE_ID = os.environ.get("MUJOCO_EGL_DEVICE_ID", None) - if MUJOCO_EGL_DEVICE_ID is not None: - assert MUJOCO_EGL_DEVICE_ID.isdigit() and MUJOCO_EGL_DEVICE_ID in CUDA_VISIBLE_DEVICES - -_MUJOCO_GL = os.environ.get("MUJOCO_GL", "").lower().strip() + + +CUDA_VISIBLE_DEVICES = os.environ.get("CUDA_VISIBLE_DEVICES", '') +if CUDA_VISIBLE_DEVICES != '': + MUJOCO_EGL_DEVICE_ID = os.environ.get("MUJOCO_EGL_DEVICE_ID", None) + if MUJOCO_EGL_DEVICE_ID is not None: + assert MUJOCO_EGL_DEVICE_ID.isdigit() and (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" + +_MUJOCO_GL = os.environ.get("MUJOCO_GL", "").lower().strip() or macros.MUJOCO_GPU_RENDERING if _MUJOCO_GL not in ("disable", "disabled", "off", "false", "0"): _VALID_MUJOCO_GL = ("enable", "enabled", "on", "true", "1", "glfw", "") if _SYSTEM == "Linux": @@ -53,14 +48,18 @@ raise RuntimeError(f"invalid value for environment variable MUJOCO_GL: {_MUJOCO_GL}") if _SYSTEM == "Linux" and _MUJOCO_GL == "osmesa": - os.environ["PYOPENGL_PLATFORM"] = "osmesa" - from robosuite.utils.osmesa_context import OSMesaGLContext + # os.environ["PYOPENGL_PLATFORM"] = "osmesa" + from robosuite.utils.osmesa_context import OSMesaGLContext as GLContext + # from mujoco.osmesa import GLContext + elif _SYSTEM == "Linux" and _MUJOCO_GL == "egl": - os.environ["PYOPENGL_PLATFORM"] = "egl" - from robosuite.utils.egl_context import EGLGLContext - else: - from robosuite.utils.glfw_context import GLFWGLContext + # os.environ["PYOPENGL_PLATFORM"] = "egl" + from robosuite.utils.egl_context import EGLGLContext as GLContext + # from mujoco.egl import GLContext + else: + from robosuite.utils.glfw_context import GLFWGLContext as GLContext + # from mujoco.glfw import GLContext class MjRenderContext: """ @@ -76,34 +75,34 @@ def __init__(self, sim, offscreen=True, device_id=-1, max_width=640, max_height= self.offscreen = offscreen self.device_id = device_id - if offscreen: - if self.device_id is not None and self.device_id >= 0: - os.environ["MUJOCO_GL"] = "egl" - os.environ["MUJOCO_EGL_DEVICE_ID"] = str(self.device_id) - os.environ["PYOPENGL_PLATFORM"] = "egl" - - GLContext = EGLGLContext - else: - os.environ["MUJOCO_GL"] = "osmesa" - GLContext = OSMesaGLContext - import pdb; pdb.set_trace() - else: - os.environ["PYOPENGL_PLATFORM"] = "glfw" - GLContext = GLFWGLContext + if self.device_id is None: + import pdb; pdb.set_trace() + # if offscreen: + # if self.device_id is not None: + # os.environ["MUJOCO_GL"] = "egl" + # os.environ["MUJOCO_EGL_DEVICE_ID"] = str(self.device_id) + # # os.environ["PYOPENGL_PLATFORM"] = "egl" + # else: + # os.environ["MUJOCO_GL"] = "osmesa" + # GLContext = OSMesaGLContext + # # import pdb; pdb.set_trace() + # else: + # os.environ["PYOPENGL_PLATFORM"] = "glfw" # setup GL context with defaults for now import time - t1 = time.time_ns() - self.gl_ctx = mujoco.GLContext(max_width=max_width, max_height=max_height) + # t1 = time.time_ns() + # self.gl_ctx = mujoco.GLContext(max_width=max_width, max_height=max_height) + # t2 = time.time_ns() - t2 = time.time_ns() - - self.gl_ctx = GLContext(max_width=max_width, max_height=max_height, device_id=self.device_id) - - t3 = time.time_ns() - print((t2 - t1) / (10**9), (t3 - t2) / (10**9)) + + self.gl_ctx = GLContext(max_width=max_width, max_height=max_height, + device_id=self.device_id + ) + # t3 = time.time_ns() + # print((t2 - t1) / (10**9), (t3 - t2) / (10**9)) self.gl_ctx.make_current() # Ensure the model data has been updated so that there diff --git a/robosuite/utils/glfw_context.py b/robosuite/utils/glfw_context.py index 1bbe2886de..b86c5c15a4 100644 --- a/robosuite/utils/glfw_context.py +++ b/robosuite/utils/glfw_context.py @@ -14,28 +14,10 @@ # ============================================================================== """An OpenGL context created via GLFW.""" -import glfw +from mujoco.glfw import GLContext - -class GLFWGLContext: +class GLFWGLContext(GLContext): """An OpenGL context created via GLFW.""" def __init__(self, max_width, max_height, device_id=0): - glfw.init() - glfw.window_hint(glfw.VISIBLE, device_id) - self._context = glfw.create_window( - width=max_width, height=max_height, title="Invisible window", monitor=None, share=None - ) - - def make_current(self): - glfw.make_context_current(self._context) - - def free(self): - if self._context: - if glfw.get_current_context() == self._context: - glfw.make_context_current(None) - glfw.destroy_window(self._context) - self._context = None - - def __del__(self): - self.free() + super().__init__(max_width, max_height) diff --git a/robosuite/utils/osmesa_context.py b/robosuite/utils/osmesa_context.py index d93bbcb643..0ae9b2e5b7 100644 --- a/robosuite/utils/osmesa_context.py +++ b/robosuite/utils/osmesa_context.py @@ -15,61 +15,10 @@ """An OSMesa context for software-based OpenGL rendering.""" import os +from mujoco.osmesa import GLContext -PYOPENGL_PLATFORM = os.environ.get("PYOPENGL_PLATFORM") - -if not PYOPENGL_PLATFORM: - os.environ["PYOPENGL_PLATFORM"] = "osmesa" -elif PYOPENGL_PLATFORM.lower() != "osmesa": - raise ImportError( - "Cannot use OSMesa rendering platform. " - "The PYOPENGL_PLATFORM environment variable is set to {!r} " - "(should be either unset or 'osmesa').".format(PYOPENGL_PLATFORM) - ) - -# pylint: disable=g-import-not-at-top -from OpenGL import GL, osmesa -from OpenGL.GL import arrays - -_DEPTH_BITS = 24 -_STENCIL_BITS = 8 -_ACCUM_BITS = 0 - - -class OSMesaGLContext: +class OSMesaGLContext(GLContext): """An OSMesa context for software-based OpenGL rendering.""" def __init__(self, max_width, max_height, device_id=-1): - """Initializes this OSMesa context.""" - self._context = osmesa.OSMesaCreateContextExt( - osmesa.OSMESA_RGBA, - _DEPTH_BITS, - _STENCIL_BITS, - _ACCUM_BITS, - None, # sharelist - ) - if not self._context: - raise RuntimeError("Failed to create OSMesa GL context.") - - self._height = max_height - self._width = max_width - - # Allocate a buffer to render into. - self._buffer = arrays.GLfloatArray.zeros((max_height, max_width, 4)) - - def make_current(self): - if self._context: - success = osmesa.OSMesaMakeCurrent(self._context, self._buffer, GL.GL_FLOAT, self._width, self._height) - if not success: - raise RuntimeError("Failed to make OSMesa context current.") - - def free(self): - """Frees resources associated with this context.""" - if self._context and self._context == osmesa.OSMesaGetCurrentContext(): - osmesa.OSMesaMakeCurrent(None, None, GL.GL_FLOAT, 0, 0) - osmesa.OSMesaDestroyContext(self._context) - self._buffer = None - self._context = None - - def __del__(self): - self.free() + super().__init__(max_width, max_height) From b748425c216a736c962add59e9f5f2a3c64bcb24 Mon Sep 17 00:00:00 2001 From: Yifeng Zhu Date: Wed, 28 Sep 2022 22:49:16 -0400 Subject: [PATCH 05/16] Remove pdb --- robosuite/utils/binding_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/robosuite/utils/binding_utils.py b/robosuite/utils/binding_utils.py index a6c75a7c01..1913dfc8ca 100644 --- a/robosuite/utils/binding_utils.py +++ b/robosuite/utils/binding_utils.py @@ -75,8 +75,8 @@ def __init__(self, sim, offscreen=True, device_id=-1, max_width=640, max_height= self.offscreen = offscreen self.device_id = device_id - if self.device_id is None: - import pdb; pdb.set_trace() + # if self.device_id is None: + # import pdb; pdb.set_trace() # if offscreen: # if self.device_id is not None: # os.environ["MUJOCO_GL"] = "egl" From c1e052e3c80faf4201f0f5ec7ac567dc19ffcf30 Mon Sep 17 00:00:00 2001 From: Yifeng Zhu Date: Wed, 28 Sep 2022 23:19:27 -0500 Subject: [PATCH 06/16] Double checked gpu rendering on gpu server --- robosuite/utils/binding_utils.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/robosuite/utils/binding_utils.py b/robosuite/utils/binding_utils.py index 1913dfc8ca..f84fc29394 100644 --- a/robosuite/utils/binding_utils.py +++ b/robosuite/utils/binding_utils.py @@ -28,14 +28,19 @@ if _SYSTEM == "Windows": ctypes.WinDLL(os.path.join(os.path.dirname(__file__), "mujoco.dll")) +GL_IMPORT = "" -CUDA_VISIBLE_DEVICES = os.environ.get("CUDA_VISIBLE_DEVICES", '') -if CUDA_VISIBLE_DEVICES != '': +CUDA_VISIBLE_DEVICES = os.environ.get("CUDA_VISIBLE_DEVICES", "") +if CUDA_VISIBLE_DEVICES != "": MUJOCO_EGL_DEVICE_ID = os.environ.get("MUJOCO_EGL_DEVICE_ID", None) if MUJOCO_EGL_DEVICE_ID is not None: assert MUJOCO_EGL_DEVICE_ID.isdigit() and (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" -_MUJOCO_GL = os.environ.get("MUJOCO_GL", "").lower().strip() or macros.MUJOCO_GPU_RENDERING +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 + os.environ["MUJOCO_GL"] = "egl" +_MUJOCO_GL = os.environ.get("MUJOCO_GL", "").lower().strip() if _MUJOCO_GL not in ("disable", "disabled", "off", "false", "0"): _VALID_MUJOCO_GL = ("enable", "enabled", "on", "true", "1", "glfw", "") if _SYSTEM == "Linux": From 89cd2f2e7f75dcd7e7beb0ec535ff7ae286bd995 Mon Sep 17 00:00:00 2001 From: Yifeng Zhu Date: Thu, 6 Oct 2022 10:10:57 -0500 Subject: [PATCH 07/16] Move context definition under renderers folder --- robosuite/renderers/context/__init__.py | 0 .../{utils => renderers/context}/egl_context.py | 0 .../{utils => renderers/context}/glfw_context.py | 0 .../{utils => renderers/context}/osmesa_context.py | 0 robosuite/utils/binding_utils.py | 13 +++---------- 5 files changed, 3 insertions(+), 10 deletions(-) create mode 100644 robosuite/renderers/context/__init__.py rename robosuite/{utils => renderers/context}/egl_context.py (100%) rename robosuite/{utils => renderers/context}/glfw_context.py (100%) rename robosuite/{utils => renderers/context}/osmesa_context.py (100%) diff --git a/robosuite/renderers/context/__init__.py b/robosuite/renderers/context/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/robosuite/utils/egl_context.py b/robosuite/renderers/context/egl_context.py similarity index 100% rename from robosuite/utils/egl_context.py rename to robosuite/renderers/context/egl_context.py diff --git a/robosuite/utils/glfw_context.py b/robosuite/renderers/context/glfw_context.py similarity index 100% rename from robosuite/utils/glfw_context.py rename to robosuite/renderers/context/glfw_context.py diff --git a/robosuite/utils/osmesa_context.py b/robosuite/renderers/context/osmesa_context.py similarity index 100% rename from robosuite/utils/osmesa_context.py rename to robosuite/renderers/context/osmesa_context.py diff --git a/robosuite/utils/binding_utils.py b/robosuite/utils/binding_utils.py index f84fc29394..ce711ada0b 100644 --- a/robosuite/utils/binding_utils.py +++ b/robosuite/utils/binding_utils.py @@ -54,16 +54,16 @@ if _SYSTEM == "Linux" and _MUJOCO_GL == "osmesa": # os.environ["PYOPENGL_PLATFORM"] = "osmesa" - from robosuite.utils.osmesa_context import OSMesaGLContext as GLContext + from robosuite.renderers.context.osmesa_context import OSMesaGLContext as GLContext # from mujoco.osmesa import GLContext elif _SYSTEM == "Linux" and _MUJOCO_GL == "egl": # os.environ["PYOPENGL_PLATFORM"] = "egl" - from robosuite.utils.egl_context import EGLGLContext as GLContext + from robosuite.renderers.context.egl_context import EGLGLContext as GLContext # from mujoco.egl import GLContext else: - from robosuite.utils.glfw_context import GLFWGLContext as GLContext + from robosuite.renderers.context.glfw_context import GLFWGLContext as GLContext # from mujoco.glfw import GLContext class MjRenderContext: @@ -96,13 +96,6 @@ def __init__(self, sim, offscreen=True, device_id=-1, max_width=640, max_height= # setup GL context with defaults for now - import time - - # t1 = time.time_ns() - # self.gl_ctx = mujoco.GLContext(max_width=max_width, max_height=max_height) - # t2 = time.time_ns() - - self.gl_ctx = GLContext(max_width=max_width, max_height=max_height, device_id=self.device_id ) From 28bdd61d6f28a8de08ac791bfde88cbf4f6f66b0 Mon Sep 17 00:00:00 2001 From: Yifeng Zhu Date: Thu, 6 Oct 2022 10:29:14 -0500 Subject: [PATCH 08/16] Update egl context --- robosuite/renderers/context/egl_context.py | 5 ++++- robosuite/utils/binding_utils.py | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/robosuite/renderers/context/egl_context.py b/robosuite/renderers/context/egl_context.py index a46cc77d69..61de27d479 100644 --- a/robosuite/renderers/context/egl_context.py +++ b/robosuite/renderers/context/egl_context.py @@ -25,8 +25,11 @@ def create_initialized_egl_device_display(device_id=0): if os.environ.get("MUJOCO_EGL_DEVICE_ID", None) is None else os.environ.get("MUJOCO_EGL_DEVICE_ID", None) ) + if device_id == -1: + device_id = 0 if selected_device is None: candidates = all_devices + device_idx = device_id else: if not selected_device.isdigit(): device_inds = [int(x) for x in selected_device.split(",")] @@ -38,7 +41,7 @@ def create_initialized_egl_device_display(device_id=0): f"The MUJOCO_EGL_DEVICE_ID environment variable must be an integer " f"between 0 and {len(all_devices)-1} (inclusive), got {device_idx}." ) - candidates = all_devices[device_idx : device_idx + 1] + candidates = all_devices[device_idx : device_idx + 1] for device in candidates: display = EGL.eglGetPlatformDisplayEXT(EGL.EGL_PLATFORM_DEVICE_EXT, device, None) if display != EGL.EGL_NO_DISPLAY and EGL.eglGetError() == EGL.EGL_SUCCESS: diff --git a/robosuite/utils/binding_utils.py b/robosuite/utils/binding_utils.py index ce711ada0b..59fafdd80b 100644 --- a/robosuite/utils/binding_utils.py +++ b/robosuite/utils/binding_utils.py @@ -95,7 +95,6 @@ def __init__(self, sim, offscreen=True, device_id=-1, max_width=640, max_height= # os.environ["PYOPENGL_PLATFORM"] = "glfw" # setup GL context with defaults for now - self.gl_ctx = GLContext(max_width=max_width, max_height=max_height, device_id=self.device_id ) From 4077ca6bcdc7de26e2922f66c2e274f3a3132917 Mon Sep 17 00:00:00 2001 From: Yifeng Zhu Date: Thu, 6 Oct 2022 10:32:44 -0500 Subject: [PATCH 09/16] Remove comments --- robosuite/renderers/context/egl_context.py | 2 ++ robosuite/utils/binding_utils.py | 28 ---------------------- 2 files changed, 2 insertions(+), 28 deletions(-) diff --git a/robosuite/renderers/context/egl_context.py b/robosuite/renderers/context/egl_context.py index 61de27d479..164332a23f 100644 --- a/robosuite/renderers/context/egl_context.py +++ b/robosuite/renderers/context/egl_context.py @@ -1,3 +1,5 @@ +"""This is a modified EGLContext class that allows programmatic specification of gpu ids""" + import atexit import ctypes import os diff --git a/robosuite/utils/binding_utils.py b/robosuite/utils/binding_utils.py index 59fafdd80b..9e262b32da 100644 --- a/robosuite/utils/binding_utils.py +++ b/robosuite/utils/binding_utils.py @@ -22,14 +22,10 @@ from robosuite.utils import macros -# pylint: disable=g-import-not-at-top - _SYSTEM = platform.system() if _SYSTEM == "Windows": ctypes.WinDLL(os.path.join(os.path.dirname(__file__), "mujoco.dll")) -GL_IMPORT = "" - CUDA_VISIBLE_DEVICES = os.environ.get("CUDA_VISIBLE_DEVICES", "") if CUDA_VISIBLE_DEVICES != "": MUJOCO_EGL_DEVICE_ID = os.environ.get("MUJOCO_EGL_DEVICE_ID", None) @@ -51,20 +47,12 @@ _VALID_MUJOCO_GL += ("cgl",) if _MUJOCO_GL not in _VALID_MUJOCO_GL: raise RuntimeError(f"invalid value for environment variable MUJOCO_GL: {_MUJOCO_GL}") - if _SYSTEM == "Linux" and _MUJOCO_GL == "osmesa": - # os.environ["PYOPENGL_PLATFORM"] = "osmesa" from robosuite.renderers.context.osmesa_context import OSMesaGLContext as GLContext - # from mujoco.osmesa import GLContext - elif _SYSTEM == "Linux" and _MUJOCO_GL == "egl": - # os.environ["PYOPENGL_PLATFORM"] = "egl" from robosuite.renderers.context.egl_context import EGLGLContext as GLContext - # from mujoco.egl import GLContext - else: from robosuite.renderers.context.glfw_context import GLFWGLContext as GLContext - # from mujoco.glfw import GLContext class MjRenderContext: """ @@ -80,26 +68,10 @@ def __init__(self, sim, offscreen=True, device_id=-1, max_width=640, max_height= self.offscreen = offscreen self.device_id = device_id - # if self.device_id is None: - # import pdb; pdb.set_trace() - # if offscreen: - # if self.device_id is not None: - # os.environ["MUJOCO_GL"] = "egl" - # os.environ["MUJOCO_EGL_DEVICE_ID"] = str(self.device_id) - # # os.environ["PYOPENGL_PLATFORM"] = "egl" - # else: - # os.environ["MUJOCO_GL"] = "osmesa" - # GLContext = OSMesaGLContext - # # import pdb; pdb.set_trace() - # else: - # os.environ["PYOPENGL_PLATFORM"] = "glfw" - # setup GL context with defaults for now self.gl_ctx = GLContext(max_width=max_width, max_height=max_height, device_id=self.device_id ) - # t3 = time.time_ns() - # print((t2 - t1) / (10**9), (t3 - t2) / (10**9)) self.gl_ctx.make_current() # Ensure the model data has been updated so that there From a38010f9712c9ea4b2419ea85e989e2a3c3ea2a8 Mon Sep 17 00:00:00 2001 From: Yifeng Zhu Date: Thu, 6 Oct 2022 10:35:17 -0500 Subject: [PATCH 10/16] Check with pre commits --- robosuite/renderers/context/egl_context.py | 2 +- robosuite/renderers/context/glfw_context.py | 1 + robosuite/renderers/context/osmesa_context.py | 2 ++ robosuite/utils/binding_utils.py | 11 ++++++----- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/robosuite/renderers/context/egl_context.py b/robosuite/renderers/context/egl_context.py index 164332a23f..c75ccb03c7 100644 --- a/robosuite/renderers/context/egl_context.py +++ b/robosuite/renderers/context/egl_context.py @@ -28,7 +28,7 @@ def create_initialized_egl_device_display(device_id=0): else os.environ.get("MUJOCO_EGL_DEVICE_ID", None) ) if device_id == -1: - device_id = 0 + device_id = 0 if selected_device is None: candidates = all_devices device_idx = device_id diff --git a/robosuite/renderers/context/glfw_context.py b/robosuite/renderers/context/glfw_context.py index b86c5c15a4..087fe0d496 100644 --- a/robosuite/renderers/context/glfw_context.py +++ b/robosuite/renderers/context/glfw_context.py @@ -16,6 +16,7 @@ from mujoco.glfw import GLContext + class GLFWGLContext(GLContext): """An OpenGL context created via GLFW.""" diff --git a/robosuite/renderers/context/osmesa_context.py b/robosuite/renderers/context/osmesa_context.py index 0ae9b2e5b7..b2c8918cc2 100644 --- a/robosuite/renderers/context/osmesa_context.py +++ b/robosuite/renderers/context/osmesa_context.py @@ -15,8 +15,10 @@ """An OSMesa context for software-based OpenGL rendering.""" import os + from mujoco.osmesa import GLContext + class OSMesaGLContext(GLContext): """An OSMesa context for software-based OpenGL rendering.""" diff --git a/robosuite/utils/binding_utils.py b/robosuite/utils/binding_utils.py index 9e262b32da..cc59303137 100644 --- a/robosuite/utils/binding_utils.py +++ b/robosuite/utils/binding_utils.py @@ -30,9 +30,11 @@ if CUDA_VISIBLE_DEVICES != "": MUJOCO_EGL_DEVICE_ID = os.environ.get("MUJOCO_EGL_DEVICE_ID", None) if MUJOCO_EGL_DEVICE_ID is not None: - assert MUJOCO_EGL_DEVICE_ID.isdigit() and (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" + assert MUJOCO_EGL_DEVICE_ID.isdigit() and ( + 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 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 os.environ["MUJOCO_GL"] = "egl" @@ -54,6 +56,7 @@ else: from robosuite.renderers.context.glfw_context import GLFWGLContext as GLContext + class MjRenderContext: """ Class that encapsulates rendering functionality for a @@ -69,9 +72,7 @@ def __init__(self, sim, offscreen=True, device_id=-1, max_width=640, max_height= self.device_id = device_id # setup GL context with defaults for now - self.gl_ctx = GLContext(max_width=max_width, max_height=max_height, - device_id=self.device_id - ) + self.gl_ctx = GLContext(max_width=max_width, max_height=max_height, device_id=self.device_id) self.gl_ctx.make_current() # Ensure the model data has been updated so that there From b8b88717f266bc5c8806f72a3a984db7ff9c3417 Mon Sep 17 00:00:00 2001 From: Yifeng Zhu Date: Thu, 6 Oct 2022 14:56:47 -0500 Subject: [PATCH 11/16] Update egl device id --- robosuite/renderers/context/egl_context.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/robosuite/renderers/context/egl_context.py b/robosuite/renderers/context/egl_context.py index c75ccb03c7..2056992c5b 100644 --- a/robosuite/renderers/context/egl_context.py +++ b/robosuite/renderers/context/egl_context.py @@ -27,15 +27,18 @@ def create_initialized_egl_device_display(device_id=0): if os.environ.get("MUJOCO_EGL_DEVICE_ID", None) is None else os.environ.get("MUJOCO_EGL_DEVICE_ID", None) ) - if device_id == -1: - device_id = 0 if selected_device is None: candidates = all_devices - device_idx = device_id + if device_id == -1: + device_idx = 0 else: if not selected_device.isdigit(): device_inds = [int(x) for x in selected_device.split(",")] - device_idx = device_inds[device_id] + if device_id == -1: + device_idx = 0 + else: + assert(device_id in device_inds) + device_idx = device_id else: device_idx = int(selected_device) if not 0 <= device_idx < len(all_devices): From c1139000cb8049347e82b5403e4f589aaa68b1ce Mon Sep 17 00:00:00 2001 From: Yifeng Zhu Date: Thu, 6 Oct 2022 15:09:22 -0500 Subject: [PATCH 12/16] Make sure environment variables override device_id variable in programs --- robosuite/renderers/context/egl_context.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/robosuite/renderers/context/egl_context.py b/robosuite/renderers/context/egl_context.py index 2056992c5b..4d2633e67b 100644 --- a/robosuite/renderers/context/egl_context.py +++ b/robosuite/renderers/context/egl_context.py @@ -31,13 +31,15 @@ def create_initialized_egl_device_display(device_id=0): candidates = all_devices if device_id == -1: device_idx = 0 + else: + device_idx = device_id else: if not selected_device.isdigit(): device_inds = [int(x) for x in selected_device.split(",")] if device_id == -1: - device_idx = 0 + device_idx = device_inds[0] else: - assert(device_id in device_inds) + assert(device_id in device_inds), "specified device id is not made visible in environment variables." device_idx = device_id else: device_idx = int(selected_device) From b218b5945458d597eb08874d15937278804130be Mon Sep 17 00:00:00 2001 From: Yifeng Zhu Date: Thu, 20 Oct 2022 23:51:32 -0500 Subject: [PATCH 13/16] Add moficiation notice in egl_context file --- robosuite/renderers/context/egl_context.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/robosuite/renderers/context/egl_context.py b/robosuite/renderers/context/egl_context.py index 2056992c5b..2abea1c3e0 100644 --- a/robosuite/renderers/context/egl_context.py +++ b/robosuite/renderers/context/egl_context.py @@ -1,4 +1,18 @@ -"""This is a modified EGLContext class that allows programmatic specification of gpu ids""" +# Modifications Copyright 2022 The robosuite Authors +# Original Copyright 2018 The dm_control Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== import atexit import ctypes @@ -35,7 +49,7 @@ def create_initialized_egl_device_display(device_id=0): if not selected_device.isdigit(): device_inds = [int(x) for x in selected_device.split(",")] if device_id == -1: - device_idx = 0 + device_idx = device_inds[0] else: assert(device_id in device_inds) device_idx = device_id From 29d713788371b63bb94fb92a3448897b1077604c Mon Sep 17 00:00:00 2001 From: Yifeng Zhu Date: Wed, 26 Oct 2022 14:34:21 -0500 Subject: [PATCH 14/16] Add a description of apache 2.0 of deepmind in the license file --- LICENSE | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/LICENSE b/LICENSE index 776d386d53..797bcc1db7 100644 --- a/LICENSE +++ b/LICENSE @@ -19,3 +19,9 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +This software includes the partial implementation of Deepmind Mujoco https://github.com/deepmind/mujoco. Deepmind Mujoco is licensed under the Apache License, Version 2.0 (the "License"); +you may not use the files except in compliance with the License. + +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 \ No newline at end of file From 71c92a12d2fe4cc57865f433edbd868ee927b581 Mon Sep 17 00:00:00 2001 From: Yifeng Zhu Date: Wed, 26 Oct 2022 14:49:31 -0500 Subject: [PATCH 15/16] Modify macros import in utils/binding_utils.py --- robosuite/utils/binding_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/robosuite/utils/binding_utils.py b/robosuite/utils/binding_utils.py index cc59303137..e132cfeb5e 100644 --- a/robosuite/utils/binding_utils.py +++ b/robosuite/utils/binding_utils.py @@ -20,7 +20,7 @@ import platform import subprocess -from robosuite.utils import macros +import robosuite.macros as macros _SYSTEM = platform.system() if _SYSTEM == "Windows": From 696cae1170f8226bdce1ba2bc9ea11a00867e319 Mon Sep 17 00:00:00 2001 From: Yuke Zhu Date: Mon, 31 Oct 2022 20:48:49 -0500 Subject: [PATCH 16/16] Update LICENSE --- LICENSE | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 797bcc1db7..e20189628c 100644 --- a/LICENSE +++ b/LICENSE @@ -20,8 +20,9 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -This software includes the partial implementation of Deepmind Mujoco https://github.com/deepmind/mujoco. Deepmind Mujoco is licensed under the Apache License, Version 2.0 (the "License"); +This software includes the partial implementation of Deepmind Mujoco https://github.com/deepmind/mujoco. +Deepmind Mujoco is licensed under the Apache License, Version 2.0 (the "License"); you may not use the files except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 \ No newline at end of file + http://www.apache.org/licenses/LICENSE-2.0