Skip to content

Commit c35225a

Browse files
committed
Add Kit-less OVRTX rendering with Newton and OvPhysX backends
1 parent 0a478f0 commit c35225a

4 files changed

Lines changed: 294 additions & 28 deletions

File tree

‎compass/utils/renderer_utils.py‎

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@
2424
# pylint: disable=import-outside-toplevel
2525

2626
###############################
27-
# Isaac Sim RTX Renderer Setup
27+
# Camera Renderer Setup
2828
###############################
2929
__all__ = [
30+
"apply_camera_renderer_settings",
3031
"apply_isaac_rtx_camera_renderer_settings",
3132
"apply_nurec_spg_kit_args",
33+
"configure_renderer_runtime",
3234
"configure_nurec_isaacsim_rtx_viewport",
3335
]
3436

@@ -65,6 +67,66 @@
6567

6668

6769
# Public API
70+
def configure_renderer_runtime(args):
71+
"""Configure renderer startup before importing the simulation environment."""
72+
physics_backend = getattr(args, "physics_backend", None)
73+
if physics_backend is None:
74+
physics_backend = "physx" if args.camera_renderer == "isaac_rtx" else "newton"
75+
compatible_backends = {
76+
"isaac_rtx": ("physx",),
77+
"ovrtx": ("newton", "ovphysx"),
78+
}
79+
if physics_backend not in compatible_backends[args.camera_renderer]:
80+
raise ValueError(
81+
f"--camera-renderer {args.camera_renderer} does not support "
82+
f"--physics-backend {physics_backend}; choose "
83+
f"{', '.join(compatible_backends[args.camera_renderer])}.")
84+
args.physics_backend = physics_backend
85+
86+
if args.camera_renderer == "isaac_rtx":
87+
apply_nurec_spg_kit_args(args)
88+
return
89+
90+
from compass.utils.visualizer_utils import requested_visualizers
91+
92+
if "kit" in requested_visualizers(args):
93+
raise ValueError(
94+
"--camera-renderer ovrtx requires a Kit-less process; remove --visualizer kit.")
95+
livestream = getattr(args, "livestream", -1)
96+
if livestream is None or livestream < 0:
97+
livestream = int(os.environ.get("LIVESTREAM", "0"))
98+
if livestream or getattr(args, "xr", False):
99+
raise ValueError("--camera-renderer ovrtx does not support Kit livestreaming or XR.")
100+
if getattr(args, "kit_args", None) or getattr(args, "experience", None):
101+
raise ValueError("--camera-renderer ovrtx does not use --kit_args or --experience.")
102+
103+
args.spg_runtime = uses_nurec_spg_runtime(args)
104+
# AUTO_ANY ISP requests HDR even before a scene's PPISP shader is discovered.
105+
# Match ppisp_camera_ovrtx.py; retain explicit user overrides.
106+
os.environ.setdefault("OVRTX_rtx_rtpt_gaussian_skipTonemapping_enabled", "0")
107+
108+
109+
def apply_camera_renderer_settings(env_cfg, args):
110+
"""Select the camera renderer and its compatible physics backend."""
111+
if args.camera_renderer == "isaac_rtx":
112+
apply_isaac_rtx_camera_renderer_settings(env_cfg, True, args.spg_runtime)
113+
return
114+
115+
from isaaclab_ov.renderers import OVRTXRendererCfg
116+
117+
# Both choices run without Isaac Sim's Kit-based PhysX runtime.
118+
if args.physics_backend == "ovphysx":
119+
from isaaclab_ov.physics import OvPhysxCfg
120+
121+
env_cfg.sim.physics = OvPhysxCfg()
122+
else:
123+
from isaaclab_newton.physics import NewtonCfg
124+
from isaaclab_newton.physics.mjwarp_manager_cfg import MJWarpSolverCfg
125+
126+
env_cfg.sim.physics = NewtonCfg(solver_cfg=MJWarpSolverCfg(), num_substeps=1)
127+
env_cfg.scene.camera.renderer_cfg = OVRTXRendererCfg()
128+
129+
68130
def apply_isaac_rtx_camera_renderer_settings(env_cfg, isaac_rtx, spg_runtime):
69131
"""Apply Isaac RTX camera renderer settings when the Isaac RTX renderer is used."""
70132
if not isaac_rtx:

‎docs/handbook/workflows/nurec_real2sim.md‎

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,64 @@ This command uses the default `particle_spg-runtime.usdz`. Use
134134
`--nurec-usd-file <filename>` to choose another USD from the selected scene
135135
folder.
136136

137+
#### OVRTX camera rendering
138+
139+
Add `--camera-renderer ovrtx` to use OVRTX for onboard RGB/depth cameras.
140+
By default this selects Newton MJWarp physics and runs without Kit, following Isaac Lab's
141+
`scripts/demos/sensors/ppisp_camera_ovrtx.py` example. Isaac RTX with PhysX remains
142+
the default. Switching to OVRTX also changes the physics backend; validate
143+
locomotion policies with the selected backend before training.
144+
145+
Use a Kit-less Isaac Lab Python environment with the `ovrtx` extra installed
146+
(for example, `uv sync --extra ovrtx` in the Isaac Lab checkout), plus the
147+
COMPASS dependencies and `mobility_es` extension. The Docker Python wrapper
148+
described above launches Isaac Sim's Python; use the Kit-less environment for
149+
this command:
150+
151+
```bash
152+
python run.py \
153+
-c configs/train_config_real2sim.gin \
154+
-o <output_dir> \
155+
-b ./assets/x_mobility.ckpt \
156+
--embodiment carter \
157+
--nurec-scene nova_carter-galileo \
158+
--num_envs 4 \
159+
--camera-renderer ovrtx \
160+
--physics-backend newton \
161+
--visualizer none
162+
```
163+
164+
To use standalone PhysX instead, replace `--physics-backend newton` with
165+
`--physics-backend ovphysx`. Install both runtime extras in the Kit-less Isaac Lab
166+
environment (`uv sync --extra ovrtx --extra ovphysx`) before running it.
167+
OvPhysX is experimental; this selects `OvPhysxCfg`, rather than Isaac Sim's
168+
Kit-based `PhysxCfg`. It uses its own backend defaults and USD physics-scene
169+
settings, so do not assume identical dynamics to the Isaac Sim path.
170+
171+
For an OvPhysX smoke run on `nova_carter-galileo`, add
172+
`--nurec-usd-file volume.usdz`. This combination has been checked locally with
173+
Carter at 512x320 resolution, including stepping and reset. The default
174+
`particle_spg-runtime.usdz` has not completed local OvPhysX validation: Isaac
175+
Lab 3.0 EA serializes the full physics input stage as USDA text, and this large
176+
particle scene showed substantial host-memory growth during initialization.
177+
178+
Supported combinations are:
179+
180+
| Camera renderer | Physics backend |
181+
|---|---|
182+
| `isaac_rtx` | `physx` (default) |
183+
| `ovrtx` | `newton` (default, MJWarp solver) or `ovphysx` |
184+
185+
Incompatible combinations are rejected before starting either runtime.
186+
187+
Automatic PPISP discovery remains enabled. COMPASS sets
188+
`OVRTX_rtx_rtpt_gaussian_skipTonemapping_enabled=0` before loading OVRTX unless
189+
you explicitly set it in the environment. OVRTX does not accept the Kit
190+
visualizer, livestreaming, XR, `--kit_args`, or `--experience`. Robot-camera
191+
videos and debug grids remain available with `--video`.
192+
193+
#### Visualization and common options
194+
137195
| Kit viewport | Robot camera |
138196
|---|---|
139197
| <img src="images/nurec_particle_spg_runtime_kit_viewport.jpg" alt="Particle SPG-runtime Kit viewport" width="320"> | <img src="images/nurec_particle_spg_runtime_camera_grid.png" alt="Particle SPG-runtime robot-camera debug grid" width="800"> |

‎run.py‎

Lines changed: 55 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@
2323

2424
from compass.utils.nurec_utils import PARTICLE_SPG_RUNTIME_USD_FILE
2525
from compass.utils.renderer_utils import (
26-
apply_isaac_rtx_camera_renderer_settings,
27-
apply_nurec_spg_kit_args,
26+
apply_camera_renderer_settings,
27+
configure_renderer_runtime,
2828
configure_nurec_isaacsim_rtx_viewport,
2929
)
3030
from compass.utils.visualizer_utils import (
@@ -35,6 +35,19 @@
3535

3636
# add argparse arguments
3737
parser = argparse.ArgumentParser(description="COMPASS Mobility Generalist.")
38+
parser.add_argument(
39+
"--camera-renderer",
40+
choices=["isaac_rtx", "ovrtx"],
41+
default="isaac_rtx",
42+
help="Camera renderer. OVRTX runs without Kit; select physics with --physics-backend.",
43+
)
44+
parser.add_argument(
45+
"--physics-backend",
46+
choices=["physx", "newton", "ovphysx"],
47+
default=None,
48+
help="Physics backend. Defaults to physx for Isaac RTX or newton (MJWarp) for OVRTX. "
49+
"Standalone ovphysx requires --camera-renderer ovrtx.",
50+
)
3851
parser.add_argument(
3952
"--config-files",
4053
"-c",
@@ -142,7 +155,7 @@
142155
"--spg-runtime",
143156
action="store_true",
144157
default=False,
145-
help="Force SPG runtime Kit args before Isaac Sim starts. "
158+
help="Force SPG runtime renderer settings before simulation starts. "
146159
f"This is automatic for {PARTICLE_SPG_RUNTIME_USD_FILE} "
147160
"when --nurec-scene is set.",
148161
)
@@ -187,7 +200,10 @@
187200
args_cli.environment = args_cli.nurec_scene
188201
elif args_cli.nurec_omap_file is not None:
189202
parser.error("--nurec-omap-file requires --nurec-scene.")
190-
apply_nurec_spg_kit_args(args_cli)
203+
try:
204+
configure_renderer_runtime(args_cli)
205+
except ValueError as exc:
206+
parser.error(str(exc))
191207

192208
if args_cli.video:
193209
# Load the FFmpeg-enabled wheel before Kit prepends its bundled OpenCV to sys.path.
@@ -198,9 +214,25 @@
198214
"--video requires FFmpeg-enabled OpenCV. Install requirements.txt and recreate "
199215
f"the Docker container after rebuilding the image. Loaded OpenCV: {cv2.__file__}")
200216

201-
# launch omniverse app
202-
app_launcher = AppLauncher(args_cli, enable_cameras=True)
203-
simulation_app = app_launcher.app
217+
# OVRTX must never construct AppLauncher, even for headless runs.
218+
app_launcher = None
219+
simulation_app = None
220+
if args_cli.camera_renderer == "isaac_rtx":
221+
app_launcher = AppLauncher(args_cli, enable_cameras=True)
222+
simulation_app = app_launcher.app
223+
else:
224+
try:
225+
import ovrtx
226+
except ModuleNotFoundError as exc:
227+
raise ModuleNotFoundError(
228+
"OVRTX requires Isaac Lab's optional ovrtx dependencies. "
229+
"Install the ovrtx extra and run with the Kit-less Python environment.") from exc
230+
ovrtx.register_schema_paths()
231+
AppLauncher.sync_visualizer_cli_settings_to_carb({
232+
**vars(args_cli),
233+
"visualizer_disable_all":
234+
getattr(args_cli, "visualizer_explicit", False) and not args_cli.visualizer,
235+
})
204236

205237
import gin
206238
import torch
@@ -302,14 +334,12 @@ def run(
302334
num_rerenders_on_reset=None,
303335
):
304336

305-
# Multi-GPU distributed setup. With `--distributed`, AppLauncher (already invoked
306-
# at module load) reads LOCAL_RANK / RANK / WORLD_SIZE from torchrun's env, sets
307-
# physics/active GPU per rank, and limits CPU threads. We still need to call
308-
# init_process_group ourselves before any cross-rank op (param broadcast in
309-
# ResidualPPOTrainer.__init__, gradient all-reduce in PPO.update).
337+
# Kit-less runs read torchrun ranks directly because AppLauncher is skipped.
310338
if args_cli.distributed:
311-
local_rank = app_launcher.local_rank
312-
global_rank = app_launcher.global_rank
339+
local_rank = (app_launcher.local_rank if app_launcher is not None else int(
340+
os.environ.get("LOCAL_RANK", "0")))
341+
global_rank = (app_launcher.global_rank if app_launcher is not None else int(
342+
os.environ.get("RANK", "0")))
313343
# Pin PyTorch's current CUDA device to this rank's GPU BEFORE
314344
# init_process_group / any object-collective. NCCL's object
315345
# collectives (dist.all_gather_object in _save_episode_logs)
@@ -326,7 +356,8 @@ def run(
326356
else:
327357
local_rank = 0
328358
global_rank = 0
329-
device = "cuda" if torch.cuda.is_available() else "cpu"
359+
device = (args_cli.device if args_cli.camera_renderer == "ovrtx" else
360+
"cuda" if torch.cuda.is_available() else "cpu")
330361
is_rank_zero = True
331362

332363
# Setup logger. Only rank 0 writes TensorBoard / W&B / artifacts; other ranks get
@@ -398,21 +429,15 @@ def run(
398429

399430
requested_viz = requested_visualizers(args_cli)
400431
configure_visualizers(env_cfg, requested_viz)
401-
apply_isaac_rtx_camera_renderer_settings(
402-
env_cfg,
403-
isaac_rtx=True,
404-
spg_runtime=args_cli.spg_runtime,
405-
)
432+
apply_camera_renderer_settings(env_cfg, args_cli)
406433

407434
# Setup seed. Per-rank offset diversifies env initial conditions across GPUs so
408435
# rollouts collected by each rank explore different states (matches Isaac Lab's
409436
# rsl_rl reference pattern).
410437
env_cfg.seed = seed + global_rank
411438

412-
# Pin PhysX + Isaac Sim's render device to this rank's GPU. Without this every
413-
# rank's env_cfg.sim.device defaults to cuda:0 and all 8 sims pile onto a single
414-
# GPU (caught with a Vulkan OOM during material loading on the first attempt).
415-
if args_cli.distributed:
439+
# Keep physics and rendering on the policy device for Kit-less and distributed runs.
440+
if args_cli.distributed or args_cli.camera_renderer == "ovrtx":
416441
env_cfg.sim.device = device
417442

418443
# Set collision distances and max resample trial from gin config
@@ -513,6 +538,7 @@ def run(
513538
logger.log_config(gin_config_to_dictionary(gin.config._OPERATIVE_CONFIG))
514539

515540
logger.close()
541+
env.close()
516542

517543

518544
def main():
@@ -540,6 +566,8 @@ def main():
540566

541567
if __name__ == "__main__":
542568
# Run the main function.
543-
main()
544-
# Close the sim app.
545-
simulation_app.close()
569+
try:
570+
main()
571+
finally:
572+
if simulation_app is not None:
573+
simulation_app.close()

0 commit comments

Comments
 (0)