Skip to content

Commit d10ff66

Browse files
author
David Hoeller
authored
Fixes the rendering logic regression in environments (isaac-sim#614)
# Description The previous rendering logic had a bug where a render call would also step physics `render_interval` number of times. This was a regression introduced in 59493b8 ## Type of change - Bug fix (non-breaking change which fixes an issue) ## Checklist - [x] I have run the [`pre-commit` checks](https://pre-commit.com/) with `./isaaclab.sh --format` - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] I have updated the changelog and the corresponding version in the extension's `config/extension.toml` file - [x] I have added my name to the `CONTRIBUTORS.md` or my name already exists there
1 parent 678dfcc commit d10ff66

File tree

7 files changed

+245
-15
lines changed

7 files changed

+245
-15
lines changed

source/extensions/omni.isaac.lab/config/extension.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22

33
# Note: Semantic Versioning is used: https://semver.org/
4-
version = "0.18.5"
4+
version = "0.18.6"
55

66
# Description
77
title = "Isaac Lab framework for Robot Learning"

source/extensions/omni.isaac.lab/docs/CHANGELOG.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
Changelog
22
---------
33

4+
0.18.6 (2024-07-01)
5+
~~~~~~~~~~~~~~~~~~~
6+
7+
Fixed
8+
^^^^^
9+
10+
* Fixed the environment stepping logic. Earlier, the environments' rendering logic was updating the kit app which
11+
would in turn step the physics :attr:`omni.isaac.lab.sim.SimulationCfg.render_interval` times. Now, a render
12+
call only does rendering and does not step the physics.
13+
14+
415
0.18.5 (2024-06-26)
516
~~~~~~~~~~~~~~~~~~~
617

source/extensions/omni.isaac.lab/omni/isaac/lab/app/app_launcher.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -534,7 +534,8 @@ def _create_app(self):
534534
for key, value in hacked_modules.items():
535535
sys.modules[key] = value
536536

537-
def _rendering_enabled(self):
537+
def _rendering_enabled(self) -> bool:
538+
"""Check if rendering is required by the app."""
538539
# Indicates whether rendering is required by the app.
539540
# Extensions required for rendering bring startup and simulation costs, so we do not enable them if not required.
540541
return not self._headless or self._livestream >= 1 or self._enable_cameras

source/extensions/omni.isaac.lab/omni/isaac/lab/envs/direct_rl_env.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -288,21 +288,27 @@ def step(self, action: torch.Tensor) -> VecEnvStepReturn:
288288
# add action noise
289289
if self.cfg.action_noise_model:
290290
action = self._action_noise_model.apply(action.clone())
291-
292291
# process actions
293292
self._pre_physics_step(action)
293+
294+
# check if we need to do rendering within the physics loop
295+
# note: checked here once to avoid multiple checks within the loop
296+
is_rendering = self.sim.has_gui() or self.sim.has_rtx_sensors()
297+
294298
# perform physics stepping
295299
for _ in range(self.cfg.decimation):
296300
self._sim_step_counter += 1
297301
# set actions into buffers
298302
self._apply_action()
299303
# set actions into simulator
300304
self.scene.write_data_to_sim()
301-
render = self._sim_step_counter % self.cfg.sim.render_interval == 0 and (
302-
self.sim.has_gui() or self.sim.has_rtx_sensors()
303-
)
304305
# simulate
305-
self.sim.step(render=render)
306+
self.sim.step(render=False)
307+
# render between steps only if the GUI or an RTX sensor needs it
308+
# note: we assume the render interval to be the shortest accepted rendering interval.
309+
# If a camera needs rendering at a faster frequency, this will lead to unexpected behavior.
310+
if self._sim_step_counter % self.cfg.sim.render_interval == 0 and is_rendering:
311+
self.sim.render()
306312
# update buffers at sim dt
307313
self.scene.update(dt=self.physics_dt)
308314

@@ -423,6 +429,8 @@ def render(self, recompute: bool = False) -> np.ndarray | None:
423429
def close(self):
424430
"""Cleanup for the environment."""
425431
if not self._is_closed:
432+
# close entities related to the environment
433+
# note: this is order-sensitive to avoid any dangling references
426434
if self.cfg.events:
427435
del self.event_manager
428436
del self.scene

source/extensions/omni.isaac.lab/omni/isaac/lab/envs/manager_based_env.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -262,18 +262,25 @@ def step(self, action: torch.Tensor) -> tuple[VecEnvObs, dict]:
262262
"""
263263
# process actions
264264
self.action_manager.process_action(action)
265+
266+
# check if we need to do rendering within the physics loop
267+
# note: checked here once to avoid multiple checks within the loop
268+
is_rendering = self.sim.has_gui() or self.sim.has_rtx_sensors()
269+
265270
# perform physics stepping
266271
for _ in range(self.cfg.decimation):
267272
self._sim_step_counter += 1
268273
# set actions into buffers
269274
self.action_manager.apply_action()
270275
# set actions into simulator
271276
self.scene.write_data_to_sim()
272-
render = self._sim_step_counter % self.cfg.sim.render_interval == 0 and (
273-
self.sim.has_gui() or self.sim.has_rtx_sensors()
274-
)
275277
# simulate
276-
self.sim.step(render=render)
278+
self.sim.step(render=False)
279+
# render between steps only if the GUI or an RTX sensor needs it
280+
# note: we assume the render interval to be the shortest accepted rendering interval.
281+
# If a camera needs rendering at a faster frequency, this will lead to unexpected behavior.
282+
if self._sim_step_counter % self.cfg.sim.render_interval == 0 and is_rendering:
283+
self.sim.render()
277284
# update buffers at sim dt
278285
self.scene.update(dt=self.physics_dt)
279286

source/extensions/omni.isaac.lab/omni/isaac/lab/envs/manager_based_rl_env.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -154,18 +154,25 @@ def step(self, action: torch.Tensor) -> VecEnvStepReturn:
154154
"""
155155
# process actions
156156
self.action_manager.process_action(action)
157+
158+
# check if we need to do rendering within the physics loop
159+
# note: checked here once to avoid multiple checks within the loop
160+
is_rendering = self.sim.has_gui() or self.sim.has_rtx_sensors()
161+
157162
# perform physics stepping
158163
for _ in range(self.cfg.decimation):
159164
self._sim_step_counter += 1
160165
# set actions into buffers
161166
self.action_manager.apply_action()
162167
# set actions into simulator
163168
self.scene.write_data_to_sim()
164-
render = self._sim_step_counter % self.cfg.sim.render_interval == 0 and (
165-
self.sim.has_gui() or self.sim.has_rtx_sensors()
166-
)
167169
# simulate
168-
self.sim.step(render=render)
170+
self.sim.step(render=False)
171+
# render between steps only if the GUI or an RTX sensor needs it
172+
# note: we assume the render interval to be the shortest accepted rendering interval.
173+
# If a camera needs rendering at a faster frequency, this will lead to unexpected behavior.
174+
if self._sim_step_counter % self.cfg.sim.render_interval == 0 and is_rendering:
175+
self.sim.render()
169176
# update buffers at sim dt
170177
self.scene.update(dt=self.physics_dt)
171178

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
2+
# All rights reserved.
3+
#
4+
# SPDX-License-Identifier: BSD-3-Clause
5+
6+
"""Launch Isaac Sim Simulator first."""
7+
8+
from omni.isaac.lab.app import AppLauncher, run_tests
9+
10+
# launch omniverse app
11+
# need to set "enable_cameras" true to be able to do rendering tests
12+
app_launcher = AppLauncher(headless=True, enable_cameras=True)
13+
simulation_app = app_launcher.app
14+
15+
"""Rest everything follows."""
16+
17+
import torch
18+
import unittest
19+
20+
import omni.usd
21+
22+
from omni.isaac.lab.envs import (
23+
DirectRLEnv,
24+
DirectRLEnvCfg,
25+
ManagerBasedEnv,
26+
ManagerBasedEnvCfg,
27+
ManagerBasedRLEnv,
28+
ManagerBasedRLEnvCfg,
29+
)
30+
from omni.isaac.lab.scene import InteractiveSceneCfg
31+
from omni.isaac.lab.sim import SimulationCfg, SimulationContext
32+
from omni.isaac.lab.utils import configclass
33+
34+
35+
@configclass
36+
class EmptyManagerCfg:
37+
"""Empty specifications for the environment."""
38+
39+
pass
40+
41+
42+
def create_manager_based_env(render_interval: int):
43+
"""Create a manager based environment."""
44+
45+
@configclass
46+
class EnvCfg(ManagerBasedEnvCfg):
47+
"""Configuration for the test environment."""
48+
49+
decimation: int = 4
50+
sim: SimulationCfg = SimulationCfg(dt=0.005, render_interval=render_interval)
51+
scene: InteractiveSceneCfg = InteractiveSceneCfg(num_envs=1, env_spacing=1.0)
52+
actions: EmptyManagerCfg = EmptyManagerCfg()
53+
observations: EmptyManagerCfg = EmptyManagerCfg()
54+
55+
return ManagerBasedEnv(cfg=EnvCfg())
56+
57+
58+
def create_manager_based_rl_env(render_interval: int):
59+
"""Create a manager based RL environment."""
60+
61+
@configclass
62+
class EnvCfg(ManagerBasedRLEnvCfg):
63+
"""Configuration for the test environment."""
64+
65+
decimation: int = 4
66+
sim: SimulationCfg = SimulationCfg(dt=0.005, render_interval=render_interval)
67+
scene: InteractiveSceneCfg = InteractiveSceneCfg(num_envs=1, env_spacing=1.0)
68+
actions: EmptyManagerCfg = EmptyManagerCfg()
69+
observations: EmptyManagerCfg = EmptyManagerCfg()
70+
71+
return ManagerBasedRLEnv(cfg=EnvCfg())
72+
73+
74+
def create_direct_rl_env(render_interval: int):
75+
"""Create a direct RL environment."""
76+
77+
@configclass
78+
class EnvCfg(DirectRLEnvCfg):
79+
"""Configuration for the test environment."""
80+
81+
decimation: int = 4
82+
num_actions: int = 0
83+
num_observations: int = 0
84+
sim: SimulationCfg = SimulationCfg(dt=0.005, render_interval=render_interval)
85+
scene: InteractiveSceneCfg = InteractiveSceneCfg(num_envs=1, env_spacing=1.0)
86+
87+
class Env(DirectRLEnv):
88+
"""Test environment."""
89+
90+
def _pre_physics_step(self, actions):
91+
pass
92+
93+
def _apply_action(self):
94+
pass
95+
96+
def _get_observations(self):
97+
return {}
98+
99+
def _get_rewards(self):
100+
return {}
101+
102+
def _get_dones(self):
103+
return torch.zeros(1, dtype=torch.bool), torch.zeros(1, dtype=torch.bool)
104+
105+
return Env(cfg=EnvCfg())
106+
107+
108+
class TestEnvRenderingLogic(unittest.TestCase):
109+
"""Test the rendering logic of the different environment workflows."""
110+
111+
def _physics_callback(self, dt):
112+
# called at every physics step
113+
self.physics_time += dt
114+
self.num_physics_steps += 1
115+
116+
def _render_callback(self, event):
117+
# called at every render step
118+
self.render_time += event.payload["dt"]
119+
self.num_render_steps += 1
120+
121+
def test_env_rendering_logic(self):
122+
for env_type in ["manager_based_env", "manager_based_rl_env", "direct_rl_env"]:
123+
for render_interval in [1, 2, 4, 8, 10]:
124+
with self.subTest(env_type=env_type, render_interval=render_interval):
125+
# time tracking variables
126+
self.physics_time = 0.0
127+
self.render_time = 0.0
128+
# step tracking variables
129+
self.num_physics_steps = 0
130+
self.num_render_steps = 0
131+
132+
# create a new stage
133+
omni.usd.get_context().new_stage()
134+
135+
# create environment
136+
if env_type == "manager_based_env":
137+
env = create_manager_based_env(render_interval)
138+
elif env_type == "manager_based_rl_env":
139+
env = create_manager_based_rl_env(render_interval)
140+
else:
141+
env = create_direct_rl_env(render_interval)
142+
143+
# enable the flag to render the environment
144+
# note: this is only done for the unit testing to "fake" camera rendering.
145+
# normally this is set to True when cameras are created.
146+
env.sim.set_setting("/isaaclab/render/rtx_sensors", True)
147+
148+
# disable the app from shutting down when the environment is closed
149+
# FIXME: Why is this needed in this test but not in the other tests?
150+
# Without it, the test will exit after the environment is closed
151+
env.sim._app_control_on_stop_handle = None # type: ignore
152+
153+
# check that we are in partial rendering mode for the environment
154+
# this is enabled due to app launcher setting "enable_cameras=True"
155+
self.assertEqual(env.sim.render_mode, SimulationContext.RenderMode.PARTIAL_RENDERING)
156+
157+
# add physics and render callbacks
158+
env.sim.add_physics_callback("physics_step", self._physics_callback)
159+
env.sim.add_render_callback("render_step", self._render_callback)
160+
161+
# create a zero action tensor for stepping the environment
162+
actions = torch.zeros((env.num_envs, 0), device=env.device)
163+
164+
# run the environment and check the rendering logic
165+
for i in range(50):
166+
# apply zero actions
167+
env.step(action=actions)
168+
169+
# check that we have completed the correct number of physics steps
170+
self.assertEqual(
171+
self.num_physics_steps, (i + 1) * env.cfg.decimation, msg="Physics steps mismatch"
172+
)
173+
# check that we have simulated physics for the correct amount of time
174+
self.assertAlmostEqual(
175+
self.physics_time, self.num_physics_steps * env.cfg.sim.dt, msg="Physics time mismatch"
176+
)
177+
178+
# check that we have completed the correct number of rendering steps
179+
self.assertEqual(
180+
self.num_render_steps,
181+
(i + 1) * env.cfg.decimation // env.cfg.sim.render_interval,
182+
msg="Render steps mismatch",
183+
)
184+
# check that we have rendered for the correct amount of time
185+
self.assertAlmostEqual(
186+
self.render_time,
187+
self.num_render_steps * env.cfg.sim.dt * env.cfg.sim.render_interval,
188+
msg="Render time mismatch",
189+
)
190+
191+
# close the environment
192+
env.close()
193+
194+
195+
if __name__ == "__main__":
196+
run_tests()

0 commit comments

Comments
 (0)