Skip to content

Commit 798722c

Browse files
authored
Multi AOV rendering for OVRTX (isaac-sim#7204)
# Description Add multi-AOV rendering for OVRTX. Fixes # (issue) <!-- As a practice, it is recommended to open an issue to have discussions on the proposed pull request. This makes it easier for the community to keep track of what is being developed or added, and if a given feature is demanded by more than one party. --> ## Type of change <!-- As you go through the list, delete the ones that are not applicable. --> - Bug fix (non-breaking change which fixes an issue) - New feature (non-breaking change which adds functionality) - Breaking change (existing functionality will not work without user modification) - Documentation update ## Screenshots Please attach before and after screenshots of the change if applicable. <!-- Example: | Before | After | | ------ | ----- | | _gif/png before_ | _gif/png after_ | To upload images to a PR -- simply drag and drop an image while in edit mode and it should upload the image directly. You can then paste that source into the above before/after sections. --> ## Checklist - [ ] I have read and understood the [contribution guidelines](https://isaac-sim.github.io/IsaacLab/main/source/refs/contributing.html) - [ ] I have run the [`pre-commit` checks](https://pre-commit.com/) with `./isaaclab.sh --format` - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] I have added a changelog fragment under `source/<pkg>/changelog.d/` for every touched package (do **not** edit `CHANGELOG.rst` or bump `extension.toml` — CI handles that) - [ ] I have added my name to the `CONTRIBUTORS.md` or my name already exists there <!-- As you go through the checklist above, you can mark something as done by putting an x character in it For example, - [x] I have done this task - [ ] I have not done this task -->
1 parent 89aa4d2 commit 798722c

4 files changed

Lines changed: 252 additions & 69 deletions

File tree

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
Fixed
2+
^^^^^
3+
4+
* Fixed :class:`~isaaclab_ov.renderers.OVRTXRenderer` authoring only one pixel render var when a
5+
camera requested several data types, which left every other requested output empty. The render
6+
product now authors one render var per requested data type, so combinations such as ``rgb`` with
7+
``normals``, ``albedo``, ``motion_vectors``, segmentation, and depth are rendered together.
8+
* Fixed :class:`~isaaclab_ov.renderers.OVRTXRenderer` filling ``depth``,
9+
``distance_to_image_plane``, and ``distance_to_camera`` from a single depth render var, which
10+
returned euclidean distance for the image-plane outputs (or the reverse) when they were requested
11+
together. Each output is now extracted from the source that measures it.
12+
13+
Changed
14+
^^^^^^^
15+
16+
* **Breaking:** Changed :class:`~isaaclab_ov.renderers.OVRTXRenderer` to raise :class:`ValueError` when a camera
17+
requests ``rgb`` or ``rgba`` together with a ``simple_shading_*`` data type, or more than one
18+
distinct ``simple_shading_*`` data type. These outputs all read the ``LdrColor`` render var and
19+
simple shading additionally requires the render product to be in RTX Minimal mode, so one render
20+
product cannot serve them. Previously the conflict was resolved silently and produced wrongly
21+
shaded or empty images. Request the conflicting outputs from separate cameras. Repeated identical
22+
simple-shading requests still collapse to one render var.

‎source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_renderer.py‎

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
import re
2727
import sys
2828
import weakref
29-
from collections.abc import Iterator
29+
from collections.abc import Iterator, Sequence
3030
from pathlib import Path
3131
from typing import TYPE_CHECKING, Any, NoReturn, cast
3232

@@ -110,6 +110,12 @@
110110

111111
from isaaclab.renderers.camera_render_spec import CameraRenderSpec
112112

113+
# Maps depth render-var sources to compatible output buffers.
114+
_DEPTH_VAR_BUFFER_KEYS: dict[str, tuple[str, ...]] = {
115+
"DistanceToImagePlaneSD": ("depth", "distance_to_image_plane"),
116+
"DistanceToCameraSD": ("distance_to_camera",),
117+
}
118+
113119
# The resolved integer value is assigned to the ``omni:rtx:minimal:mode`` attribute of the render product.
114120
_RTX_MINIMAL_MODES = {
115121
RenderBufferKind.SIMPLE_SHADING_CONSTANT_DIFFUSE.value: 1,
@@ -1301,10 +1307,22 @@ def _extract_rgba_tiles(
13011307
self._launch_extract_all_tiles(render_data, tiled_data, output_buffer)
13021308

13031309
def _extract_depth_tiles(
1304-
self, render_data: OVRTXRenderData, tiled_depth_data: wp.array, output_buffers: dict
1310+
self,
1311+
render_data: OVRTXRenderData,
1312+
tiled_depth_data: wp.array,
1313+
output_buffers: dict,
1314+
buffer_keys: Sequence[str],
13051315
) -> None:
1306-
"""Extract per-env depth tiles into output_buffers (single kernel launch)."""
1307-
for depth_type in ["depth", "distance_to_image_plane", "distance_to_camera"]:
1316+
"""Extract per-env depth tiles into the given output buffers (one kernel launch each).
1317+
1318+
Args:
1319+
render_data: OVRTX render data for the current frame.
1320+
tiled_depth_data: Tiled depth data mapped from one depth render var.
1321+
output_buffers: Destination warp buffers, keyed by data type.
1322+
buffer_keys: Data types that this depth render var measures. Keys absent from
1323+
``output_buffers`` are skipped.
1324+
"""
1325+
for depth_type in buffer_keys:
13081326
if depth_type in output_buffers:
13091327
self._launch_extract_all_tiles(render_data, tiled_depth_data, output_buffers[depth_type])
13101328

@@ -1359,16 +1377,17 @@ def _process_render_frame(self, render_data: OVRTXRenderData, frame, output_buff
13591377
with self._map_render_var_to_dlpack(frame.render_vars["LdrColor"]) as tiled_data:
13601378
self._extract_rgba_tiles(render_data, tiled_data, output_buffers, buffer_key)
13611379

1362-
for depth_var in ["DistanceToCameraSD", "DistanceToImagePlaneSD", "DepthSD"]:
1380+
for depth_var, buffer_keys in _DEPTH_VAR_BUFFER_KEYS.items():
13631381
if depth_var not in frame.render_vars:
13641382
continue
1383+
if not any(buffer_key in output_buffers for buffer_key in buffer_keys):
1384+
continue
13651385
with self._map_render_var_to_dlpack(frame.render_vars[depth_var]) as tiled_depth_data:
13661386
if tiled_depth_data.dtype == wp.uint32:
13671387
tiled_depth_data = wp.from_torch(
13681388
wp.to_torch(tiled_depth_data).view(torch.float32), dtype=wp.float32
13691389
)
1370-
self._extract_depth_tiles(render_data, tiled_depth_data, output_buffers)
1371-
break
1390+
self._extract_depth_tiles(render_data, tiled_depth_data, output_buffers, buffer_keys)
13721391

13731392
if "DiffuseAlbedoSD" in frame.render_vars and "albedo" in output_buffers:
13741393
with self._map_render_var_to_dlpack(frame.render_vars["DiffuseAlbedoSD"]) as tiled_albedo_data:

‎source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_usd.py‎

Lines changed: 105 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -15,76 +15,121 @@
1515
logger = logging.getLogger(__name__)
1616

1717

18-
def get_render_var_config(data_types: list[str]) -> tuple[str, str, str]:
19-
"""Return (render_var_path, render_var_name, source_name) from data_types."""
20-
use_depth = any(dt in ["depth", "distance_to_image_plane", "distance_to_camera"] for dt in data_types)
21-
use_distance_to_camera = "distance_to_camera" in data_types and not any(
22-
dt in ["depth", "distance_to_image_plane"] for dt in data_types
18+
# Maps camera data types to render-var configs. OVRTX frame vars are keyed by source name,
19+
# so shared sources use one config.
20+
_RENDER_VAR_BY_DATA_TYPE: dict[str, tuple[str, str, str]] = {
21+
"rgb": ("/Render/Vars/LdrColor", "LdrColor", "LdrColor"),
22+
"rgba": ("/Render/Vars/LdrColor", "LdrColor", "LdrColor"),
23+
# Simple shading uses LdrColor in per-product RTX Minimal mode.
24+
"simple_shading_constant_diffuse": ("/Render/Vars/LdrColor", "LdrColor", "LdrColor"),
25+
"simple_shading_diffuse_mdl": ("/Render/Vars/LdrColor", "LdrColor", "LdrColor"),
26+
"simple_shading_full_mdl": ("/Render/Vars/LdrColor", "LdrColor", "LdrColor"),
27+
"rgb_hdr": ("/Render/Vars/HdrColor", "HdrColor", "HdrColor"),
28+
"albedo": ("/Render/Vars/albedo", "albedo", "DiffuseAlbedoSD"),
29+
"depth": ("/Render/Vars/depth", "depth", "DistanceToImagePlaneSD"),
30+
"distance_to_image_plane": ("/Render/Vars/depth", "depth", "DistanceToImagePlaneSD"),
31+
# This source requires a distinct render-var prim.
32+
"distance_to_camera": ("/Render/Vars/DistanceToCameraSD", "DistanceToCameraSD", "DistanceToCameraSD"),
33+
"normals": ("/Render/Vars/NormalSD", "NormalSD", "NormalSD"),
34+
"motion_vectors": ("/Render/Vars/TargetMotionSD", "TargetMotionSD", "TargetMotionSD"),
35+
"semantic_segmentation": ("/Render/Vars/semantic", "semantic", "SemanticSegmentation"),
36+
"instance_segmentation": (
37+
"/Render/Vars/NonStableInstanceSegmentation",
38+
"NonStableInstanceSegmentation",
39+
"NonStableInstanceSegmentation",
40+
),
41+
}
42+
43+
# Data types produced by putting the whole render product into RTX Minimal mode.
44+
_SIMPLE_SHADING_DATA_TYPES = frozenset(
45+
{
46+
"simple_shading_constant_diffuse",
47+
"simple_shading_diffuse_mdl",
48+
"simple_shading_full_mdl",
49+
}
50+
)
51+
52+
_COLOR_DATA_TYPES = frozenset({"rgb", "rgba"})
53+
54+
_DEFAULT_RENDER_VAR = _RENDER_VAR_BY_DATA_TYPE["rgb"]
55+
56+
57+
def _validate_data_type_combination(data_types: list[str]) -> None:
58+
"""Reject data type combinations that a single OVRTX render product cannot serve.
59+
60+
Args:
61+
data_types: Requested camera data types.
62+
63+
Raises:
64+
ValueError: If color and simple-shading data types are combined, or if more than one
65+
simple-shading data type is requested.
66+
"""
67+
simple_shading = list(
68+
dict.fromkeys(data_type for data_type in data_types if data_type in _SIMPLE_SHADING_DATA_TYPES)
2369
)
24-
use_albedo = "albedo" in data_types
25-
use_semantic = "semantic_segmentation" in data_types
26-
use_instance_seg = "instance_segmentation" in data_types
27-
use_normals = "normals" in data_types
28-
use_motion_vectors = "motion_vectors" in data_types
29-
use_rgb = any(dt in ["rgb", "rgba"] for dt in data_types)
30-
use_hdr = "rgb_hdr" in data_types
31-
32-
if use_depth and not (
33-
use_rgb or use_albedo or use_semantic or use_instance_seg or use_normals or use_motion_vectors
34-
):
35-
source = "DistanceToCameraSD" if use_distance_to_camera else "DistanceToImagePlaneSD"
36-
return "/Render/Vars/depth", "depth", source
37-
if use_albedo and not (use_rgb or use_semantic or use_instance_seg or use_normals or use_motion_vectors):
38-
return "/Render/Vars/albedo", "albedo", "DiffuseAlbedoSD"
39-
if use_semantic and not (use_rgb or use_albedo or use_normals or use_motion_vectors):
40-
return "/Render/Vars/semantic", "semantic", "SemanticSegmentation"
41-
if use_instance_seg and not (
42-
use_rgb or use_albedo or use_semantic or use_normals or use_depth or use_hdr or use_motion_vectors
43-
):
44-
return (
45-
"/Render/Vars/NonStableInstanceSegmentation",
46-
"NonStableInstanceSegmentation",
47-
"NonStableInstanceSegmentation",
70+
color = list(dict.fromkeys(data_type for data_type in data_types if data_type in _COLOR_DATA_TYPES))
71+
72+
if simple_shading and color:
73+
raise ValueError(
74+
f"OVRTX cannot render simple shading {simple_shading} together with {color} on one render product:"
75+
" both read the 'LdrColor' render var, and simple shading additionally requires RTX Minimal mode."
76+
" Request them from separate cameras."
77+
)
78+
if len(simple_shading) > 1:
79+
raise ValueError(
80+
f"OVRTX supports at most one simple shading data type per render product, got {simple_shading}."
81+
" RTX Minimal mode is a per-render-product setting. Request them from separate cameras."
4882
)
49-
if use_normals and not (
50-
use_rgb or use_albedo or use_semantic or use_instance_seg or use_depth or use_motion_vectors
51-
):
52-
return "/Render/Vars/NormalSD", "NormalSD", "NormalSD"
53-
if use_motion_vectors and not (
54-
use_rgb or use_albedo or use_semantic or use_instance_seg or use_depth or use_normals
55-
):
56-
return "/Render/Vars/TargetMotionSD", "TargetMotionSD", "TargetMotionSD"
57-
if use_hdr and not use_rgb:
58-
return "/Render/Vars/HdrColor", "HdrColor", "HdrColor"
59-
return "/Render/Vars/LdrColor", "LdrColor", "LdrColor"
83+
84+
85+
def get_render_var_config(data_types: list[str]) -> tuple[str, str, str]:
86+
"""Return the first supported render-var configuration for ``data_types``.
87+
88+
Args:
89+
data_types: Requested camera data types.
90+
91+
Returns:
92+
The render-var config, defaulting to ``LdrColor`` when no entry is supported.
93+
"""
94+
return get_render_var_configs(data_types)[0]
6095

6196

6297
def get_render_var_configs(data_types: list[str]) -> list[tuple[str, str, str]]:
63-
"""Return render var configs needed for the requested data types.
98+
"""Return render-var configs for the requested camera data types.
6499
65-
Each config is a ``(render_var_path, render_var_name, source_name)`` tuple as defined by
66-
:func:`get_render_var_config`. Always includes the single render var resolved by
67-
:func:`get_render_var_config`, plus the following extras when applicable:
100+
Shared sources are de-duplicated. Unsupported data types are logged and skipped; if no
101+
supported type remains, ``LdrColor`` is used. Segmentation requests also add their ID-map vars.
68102
69-
* ``HdrColor`` — when both ``"rgb"`` (or ``"rgba"``) and ``"rgb_hdr"`` are requested, so
70-
PPISP can consume the HDR AOV alongside the LDR destination on the same render product.
71-
* ``SemanticIdMap`` — when ``"semantic_segmentation"`` is requested, so the
72-
semantic-ID-to-label mapping can be decoded for ``camera.data.info``.
73-
* ``StableIdSemanticIdMap``, ``StableIdMap``, ``SemanticIdMap`` — when
74-
``"instance_segmentation"`` is requested, so the instance-ID-to-prim-path
75-
(``idToLabels``) and instance-ID-to-semantic (``idToSemantics``) mappings can be decoded.
103+
Args:
104+
data_types: Requested camera data types.
105+
106+
Returns:
107+
Render-var configs to author on the render product.
76108
77-
Other multi-AOV combinations are not supported.
109+
Raises:
110+
ValueError: If ``data_types`` contains incompatible outputs.
78111
"""
79112
data_types = data_types if data_types else ["rgb"]
80-
render_vars: list[tuple[str, str, str]] = [get_render_var_config(data_types)]
81-
use_rgb = any(dt in ["rgb", "rgba"] for dt in data_types)
82-
if use_rgb and "rgb_hdr" in data_types:
83-
render_vars.append(("/Render/Vars/HdrColor", "HdrColor", "HdrColor"))
84-
# Author the ID-to-label map render vars needed to decode the segmentation info dicts. These are keyed off
85-
# the requested data types (not the single AOV resolved by get_render_var_config) so they are still authored
86-
# when segmentation is combined with other outputs. instance_segmentation needs StableIdSemanticIdMap +
87-
# StableIdMap to resolve each pixel to a prim path.
113+
_validate_data_type_combination(data_types)
114+
115+
render_vars: list[tuple[str, str, str]] = []
116+
unsupported: list[str] = []
117+
for data_type in data_types:
118+
config = _RENDER_VAR_BY_DATA_TYPE.get(data_type)
119+
if config is None:
120+
unsupported.append(data_type)
121+
elif config not in render_vars:
122+
render_vars.append(config)
123+
124+
if unsupported:
125+
logger.warning(
126+
"OVRTX does not support the requested data type(s) %s; no render var is authored for them.", unsupported
127+
)
128+
if not render_vars:
129+
render_vars.append(_DEFAULT_RENDER_VAR)
130+
131+
# Author the ID-to-label map render vars needed to decode the segmentation info dicts.
132+
# instance_segmentation needs StableIdSemanticIdMap + StableIdMap to resolve each pixel to a prim path.
88133
if "instance_segmentation" in data_types:
89134
render_vars.append(("/Render/Vars/StableIdSemanticIdMap", "StableIdSemanticIdMap", "StableIdSemanticIdMap"))
90135
render_vars.append(("/Render/Vars/StableIdMap", "StableIdMap", "StableIdMap"))

‎source/isaaclab_ov/test/test_ovrtx_usd.py‎

Lines changed: 99 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,106 @@ def test_ovrtx_motion_vectors_uses_target_motion_render_var():
133133
)
134134

135135

136-
def test_ovrtx_motion_vectors_with_rgb_falls_back_to_rgb():
137-
"""OVRTX only supports one main AOV at a time; combining motion vectors with RGB keeps RGB."""
136+
def test_ovrtx_primary_render_var_follows_the_first_requested_data_type():
137+
"""The primary render var seeds the single-var arguments from the first requested data type."""
138138
assert get_render_var_config(["rgb", "motion_vectors"]) == ("/Render/Vars/LdrColor", "LdrColor", "LdrColor")
139+
assert get_render_var_config(["motion_vectors", "rgb"]) == (
140+
"/Render/Vars/TargetMotionSD",
141+
"TargetMotionSD",
142+
"TargetMotionSD",
143+
)
144+
145+
146+
def test_ovrtx_authors_one_render_var_per_requested_data_type():
147+
"""Every requested AOV is authored, so combining them no longer drops any."""
148+
data_types = [
149+
"rgb",
150+
"albedo",
151+
"semantic_segmentation",
152+
"instance_segmentation",
153+
"depth",
154+
"distance_to_camera",
155+
"normals",
156+
"motion_vectors",
157+
]
158+
159+
sources = [source for _, _, source in get_render_var_configs(data_types)]
160+
161+
assert sources == [
162+
"LdrColor",
163+
"DiffuseAlbedoSD",
164+
"SemanticSegmentation",
165+
"NonStableInstanceSegmentation",
166+
"DistanceToImagePlaneSD",
167+
"DistanceToCameraSD",
168+
"NormalSD",
169+
"TargetMotionSD",
170+
"StableIdSemanticIdMap",
171+
"StableIdMap",
172+
"SemanticIdMap",
173+
]
174+
175+
176+
def test_ovrtx_data_types_sharing_a_source_author_one_render_var():
177+
"""``rgb``/``rgba`` and ``depth``/``distance_to_image_plane`` collapse onto one render var each."""
178+
render_var_configs = get_render_var_configs(["rgb", "rgba", "depth", "distance_to_image_plane"])
179+
180+
assert render_var_configs == [
181+
("/Render/Vars/LdrColor", "LdrColor", "LdrColor"),
182+
("/Render/Vars/depth", "depth", "DistanceToImagePlaneSD"),
183+
]
184+
185+
186+
def test_ovrtx_depth_and_distance_to_camera_author_distinct_render_vars():
187+
"""Image-plane depth and distance-to-camera are different sources and get separate prims."""
188+
render_scope = build_render_scope_usd(
189+
camera_paths=["/World/envs/env_0/Camera"],
190+
render_product_name="RenderProduct",
191+
render_var_path="/Render/Vars/depth",
192+
render_var_name="depth",
193+
source_name="DistanceToImagePlaneSD",
194+
tiled_width=16,
195+
tiled_height=8,
196+
render_var_configs=get_render_var_configs(["depth", "distance_to_camera"]),
197+
)
198+
199+
assert "rel orderedVars = [</Render/Vars/depth>, </Render/Vars/DistanceToCameraSD>]" in render_scope
200+
assert 'uniform string sourceName = "DistanceToImagePlaneSD"' in render_scope
201+
assert 'uniform string sourceName = "DistanceToCameraSD"' in render_scope
202+
203+
204+
def test_ovrtx_unsupported_data_type_is_skipped_and_falls_back_to_ldr_color():
205+
"""Unsupported data types author no render var; an otherwise empty product keeps LdrColor."""
206+
assert get_render_var_configs(["instance_id_segmentation_fast"]) == [
207+
("/Render/Vars/LdrColor", "LdrColor", "LdrColor")
208+
]
209+
assert get_render_var_configs(["normals", "instance_id_segmentation_fast"]) == [
210+
("/Render/Vars/NormalSD", "NormalSD", "NormalSD")
211+
]
212+
213+
214+
def test_ovrtx_rejects_color_combined_with_simple_shading():
215+
"""Color and simple shading both read LdrColor, so one render product cannot serve both."""
216+
with pytest.raises(ValueError, match="simple shading"):
217+
get_render_var_configs(["rgb", "simple_shading_full_mdl"])
218+
219+
220+
def test_ovrtx_rejects_multiple_simple_shading_data_types():
221+
"""RTX Minimal mode is per render product, so only one simple shading output is possible."""
222+
with pytest.raises(ValueError, match="at most one simple shading"):
223+
get_render_var_configs(["simple_shading_constant_diffuse", "simple_shading_full_mdl"])
224+
225+
226+
def test_ovrtx_simple_shading_alone_uses_ldr_color():
227+
"""A lone simple shading request still reads LdrColor, shaded by RTX Minimal mode."""
228+
assert get_render_var_configs(["simple_shading_diffuse_mdl"]) == [("/Render/Vars/LdrColor", "LdrColor", "LdrColor")]
229+
230+
231+
def test_ovrtx_duplicate_simple_shading_data_types_collapse():
232+
"""Repeated identical simple-shading requests share one LdrColor render var."""
233+
assert get_render_var_configs(["simple_shading_full_mdl", "simple_shading_full_mdl"]) == [
234+
("/Render/Vars/LdrColor", "LdrColor", "LdrColor")
235+
]
139236

140237

141238
def test_render_product_initially_targets_only_the_resolvable_source_camera():

0 commit comments

Comments
 (0)