diff --git a/genesis/engine/sensors/imu.py b/genesis/engine/sensors/imu.py index 9298b087cb..329ff04f03 100644 --- a/genesis/engine/sensors/imu.py +++ b/genesis/engine/sensors/imu.py @@ -271,7 +271,7 @@ def _draw_debug(self, context: "RasterizerContext", buffer_updates: dict[str, np data = self.read(env_idx) acc_vec = data.lin_acc.reshape((3,)) * self._options.debug_acc_scale gyro_vec = data.ang_vel.reshape((3,)) * self._options.debug_gyro_scale - mag_vec = data.mag.reshape((3,)) * self._options.debug_mag_scale # added mag debug + mag_vec = data.mag.reshape((3,)) * self._options.debug_mag_scale # transform from local frame to world frame offset_quat = transform_quat_by_quat(self.quat_offset, quat) @@ -286,8 +286,8 @@ def _draw_debug(self, context: "RasterizerContext", buffer_updates: dict[str, np self.debug_objects += filter( None, ( - context.draw_debug_arrow(pos=pos, vec=acc_vec, color=self._options.debug_acc_color), - context.draw_debug_arrow(pos=pos, vec=gyro_vec, color=self._options.debug_gyro_color), - context.draw_debug_arrow(pos=pos, vec=mag_vec, color=self._options.debug_mag_color), + context.draw_debug_arrow(pos=pos, vec=acc_vec, radius=0.006, color=self._options.debug_acc_color), + context.draw_debug_arrow(pos=pos, vec=gyro_vec, radius=0.0055, color=self._options.debug_gyro_color), + context.draw_debug_arrow(pos=pos, vec=mag_vec, radius=0.005, color=self._options.debug_mag_color), ), ) diff --git a/genesis/ext/pyrender/viewer.py b/genesis/ext/pyrender/viewer.py index e3d331e199..a10ac20538 100644 --- a/genesis/ext/pyrender/viewer.py +++ b/genesis/ext/pyrender/viewer.py @@ -59,6 +59,7 @@ pyglet.options["shadow_window"] = False +pyglet.options["dpi_scaling"] = "real" MODULE_DIR = os.path.dirname(__file__) @@ -366,9 +367,7 @@ def __init__( ####################################################################### # Initialize OpenGL context and renderer ####################################################################### - self._renderer = Renderer( - self._viewport_size[0], self._viewport_size[1], context.jit, self.render_flags["point_size"] - ) + self._renderer = Renderer(*self._viewport_size, context.jit, self.render_flags["point_size"]) self._is_active = True # Starting the viewer would raise an exception if the OpenGL context is invalid for some reason. This exception @@ -654,12 +653,18 @@ def on_close(self): super().close() except Exception: pass - finally: + try: super().on_close() - try: - pyglet.app.exit() - except Exception: - pass + except Exception: + pass + try: + pyglet.app.exit() + except Exception: + pass + try: + pyglet.app.platform_event_loop.stop() + except Exception: + pass self._offscreen_semaphore.release() @@ -788,8 +793,8 @@ def on_resize(self, width: int, height: int) -> EVENT_HANDLE_STATE: self._viewport_size = (width, height) self._trackball.resize(self._viewport_size) - self._renderer.viewport_width = self._viewport_size[0] - self._renderer.viewport_height = self._viewport_size[1] + self._renderer.viewport_width = width + self._renderer.viewport_height = height self.on_draw() def on_mouse_motion(self, x: int, y: int, dx: int, dy: int) -> EVENT_HANDLE_STATE: @@ -998,9 +1003,9 @@ def _render(self, camera_node=None, renderer=None, normal=False): elif self.render_flags["all_solid"]: flags |= RenderFlags.ALL_SOLID - if self.render_flags["shadows"]: + if self.render_flags["shadows"] and not self._is_software: flags |= RenderFlags.SHADOWS_ALL - if self.render_flags["plane_reflection"]: + if self.render_flags["plane_reflection"] and not self._is_software: flags |= RenderFlags.REFLECTIVE_FLOOR if self.render_flags["env_separate_rigid"]: flags |= RenderFlags.ENV_SEPARATE @@ -1149,12 +1154,16 @@ def start(self, auto_refresh=True): confs.insert(0, conf) raise + # Determine if software emulation is being used + glinfo = self.context.get_info() + renderer = glinfo.get_renderer() + self._is_software = any(e in renderer for e in ("llvmpipe", "Apple Software Renderer")) + # Run the entire rendering pipeline first without window, to make sure that all kernels are compiled self.refresh() - # At this point, we are all set to display the graphical window if requested - if not pyglet.options["headless"]: - self.set_visible(True) + # At this point, we are all set to display the graphical window + self.set_visible(True) # Run the entire rendering pipeline once again, as a final validation that everything is fine self.refresh() @@ -1202,6 +1211,13 @@ def start(self, auto_refresh=True): if not self._initialized_event.is_set(): self._initialized_event.set() + gs.logger.debug(f"Using interactive viewer OpenGL device: {renderer}") + if self._is_software: + gs.logger.info( + "Software rendering context detected. Shadows and plane reflection not supported. Beware rendering " + "will be extremely slow." + ) + if auto_refresh: while self._is_active: try: diff --git a/genesis/options/sensors/options.py b/genesis/options/sensors/options.py index 0d40449a78..562ccdaf40 100644 --- a/genesis/options/sensors/options.py +++ b/genesis/options/sensors/options.py @@ -226,15 +226,15 @@ class IMU(RigidSensorOptionsMixin, NoisySensorOptionsMixin, SensorOptions): mag_random_walk : tuple[float, float, float] The standard deviation of the bias drift for each axis of the magnetometer. debug_acc_color : float, optional - The rgba color of the debug acceleration arrow. Defaults to (0.0, 1.0, 1.0, 0.5). + The rgba color of the debug acceleration arrow. Defaults to (1.0, 0.0, 0.0, 0.6). debug_acc_scale: float, optional The scale factor for the debug acceleration arrow. Defaults to 0.01. debug_gyro_color : float, optional - The rgba color of the debug gyroscope arrow. Defaults to (1.0, 1.0, 0.0, 0.5). + The rgba color of the debug gyroscope arrow. Defaults to (0.0, 1.0, 0.0, 0.6). debug_gyro_scale: float, optional The scale factor for the debug gyroscope arrow. Defaults to 0.01. debug_mag_color : float, optional - The rgba color of the debug magnetometer arrow. Defaults to (1.0, 1.0, 0.0, 0.5). + The rgba color of the debug magnetometer arrow. Defaults to (0.0, 0.0, 1.0, 0.6). debug_mag_scale: float, optional The scale factor for the debug magnetometer arrow. Defaults to 0.01. """ @@ -261,12 +261,12 @@ class IMU(RigidSensorOptionsMixin, NoisySensorOptionsMixin, SensorOptions): mag_random_walk: MaybeTuple3FType = 0.0 magnetic_field: MaybeTuple3FType = (0.0, 0.0, 0.5) - debug_acc_color: tuple[float, float, float, float] = (0.0, 1.0, 1.0, 0.5) + debug_acc_color: tuple[float, float, float, float] = (1.0, 0.0, 0.0, 0.6) debug_acc_scale: float = 0.01 - debug_gyro_color: tuple[float, float, float, float] = (1.0, 1.0, 0.0, 0.5) + debug_gyro_color: tuple[float, float, float, float] = (0.0, 1.0, 0.0, 0.6) debug_gyro_scale: float = 0.01 - debug_mag_color: tuple[float, float, float, float] = (0.0, 0.0, 1.0, 0.5) - debug_mag_scale: float = 2.0 + debug_mag_color: tuple[float, float, float, float] = (0.0, 0.0, 1.0, 0.6) + debug_mag_scale: float = 0.5 def model_post_init(self, _): self._validate_cross_axis_coupling(self.acc_cross_axis_coupling) diff --git a/genesis/vis/viewer.py b/genesis/vis/viewer.py index e5f64e7cae..4f2383b274 100644 --- a/genesis/vis/viewer.py +++ b/genesis/vis/viewer.py @@ -137,10 +137,6 @@ def build(self, scene): gs.logger.info(f"Viewer created. Resolution: ~<{self._res[0]}×{self._res[1]}>~, max_FPS: ~<{self._max_FPS}>~.") - glinfo = self._pyrender_viewer.context.get_info() - renderer = glinfo.get_renderer() - gs.logger.debug(f"Using interactive viewer OpenGL device: {renderer}") - self._is_built = True def run(self): diff --git a/tests/test_render.py b/tests/test_render.py index 7bf621d70e..0a1c297acd 100644 --- a/tests/test_render.py +++ b/tests/test_render.py @@ -1036,17 +1036,21 @@ def test_draw_debug(renderer, show_viewer): @pytest.mark.parametrize("n_envs", [0, 2]) @pytest.mark.parametrize("renderer_type", [RENDERER_TYPE.RASTERIZER]) @pytest.mark.skipif(not IS_INTERACTIVE_VIEWER_AVAILABLE, reason="Interactive viewer not supported on this platform.") -def test_sensors_draw_debug(n_envs, renderer, png_snapshot): +def test_sensors_draw_debug(n_envs, renderer_type, renderer, png_snapshot): """Test that sensor debug drawing works correctly and renders visible debug elements.""" scene = gs.Scene( viewer_options=gs.options.ViewerOptions( camera_pos=(2.0, 2.0, 2.0), camera_lookat=(0.0, 0.0, 0.2), # Force screen-independent low-quality resolution when running unit tests for consistency - res=(640, 480), + res=(480, 320), # Enable running in background thread if supported by the platform run_in_thread=(sys.platform == "linux"), ), + vis_options=gs.options.VisOptions( + # Disable shadows systematically for Rasterizer because they are forcibly disabled on CPU backend anyway + shadow=(renderer_type != RENDERER_TYPE.RASTERIZER), + ), profiling_options=gs.options.ProfilingOptions( show_FPS=False, ), @@ -1143,19 +1147,13 @@ def test_sensors_draw_debug(n_envs, renderer, png_snapshot): if renderer == "Apple Software Renderer": pytest.xfail("Tile ground colors are altered on Apple Software Renderer.") - try: - assert rgb_array_to_png_bytes(rgb_arr) == png_snapshot - except AssertionError: - # TODO: Need to investigate root cause and either fix rendering consistency - if sys.platform == "linux" and gs.use_ndarray: - pytest.xfail("Sensor debug drawing produces inconsistent results on Linux with static array mode.") - raise + assert rgb_array_to_png_bytes(rgb_arr) == png_snapshot @pytest.mark.required @pytest.mark.parametrize("renderer_type", [RENDERER_TYPE.RASTERIZER]) @pytest.mark.skipif(not IS_INTERACTIVE_VIEWER_AVAILABLE, reason="Interactive viewer not supported on this platform.") -def test_interactive_viewer_key_press(tmp_path, monkeypatch, renderer, png_snapshot): +def test_interactive_viewer_key_press(renderer_type, tmp_path, monkeypatch, renderer, png_snapshot): IMAGE_FILENAME = tmp_path / "screenshot.png" # Mock 'get_save_filename' to avoid poping up an interactive dialog @@ -1181,13 +1179,17 @@ def on_key_press(self, symbol: int, modifiers: int): scene = gs.Scene( viewer_options=gs.options.ViewerOptions( # Force screen-independent low-quality resolution when running unit tests for consistency - res=(640, 480), + res=(480, 320), # Enable running in background thread if supported by the platform. # Note that windows is not supported because it would trigger the following exception if some previous tests # was only using rasterizer without interactive viewer: # 'EventLoop.run() must be called from the same thread that imports pyglet.app'. run_in_thread=(sys.platform == "linux"), ), + vis_options=gs.options.VisOptions( + # Disable shadows systematically for Rasterizer because they are forcibly disabled on CPU backend anyway + shadow=(renderer_type != RENDERER_TYPE.RASTERIZER), + ), renderer=renderer, show_viewer=True, show_FPS=False, diff --git a/tests/utils.py b/tests/utils.py index 50cf63c700..6c4ba2da54 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -37,7 +37,7 @@ DEFAULT_BRANCH_NAME = "main" HUGGINGFACE_ASSETS_REVISION = "ca29b66018b449a37738257a3a76a78529d29bcc" -HUGGINGFACE_SNAPSHOT_REVISION = "0cf1780dd70b67dc426023cd97738037f0d834e3" +HUGGINGFACE_SNAPSHOT_REVISION = "137990d0632610c575a3ac3a769031f632454254" MESH_EXTENSIONS = (".mtl", *MESH_FORMATS, *GLTF_FORMATS, *USD_FORMATS) IMAGE_EXTENSIONS = (".png", ".jpg")