Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
65292c5
Fix quadratic path lookup in SceneDataProvider
pv-nvidia Jul 30, 2026
4bd8162
Rebuild Fabric view mappings on device per access
pv-nvidia Jul 30, 2026
49d1226
Document why Fabric slot arrays are int32, not uint
pv-nvidia Jul 30, 2026
51f8b9a
Test that Fabric selections are scoped to the view
pv-nvidia Jul 30, 2026
fa139eb
Removed comment
pv-nvidia Jul 30, 2026
43f1476
Avoid redundant string work in Fabric view init
pv-nvidia Aug 2, 2026
691343b
Simplify create_mapping to a plain dict comprehension
pv-nvidia Aug 2, 2026
75b159f
Add carb profiler zones to FabricFrameView hot paths
pv-nvidia Aug 2, 2026
dc22f1b
Merge branch 'develop' into pv/fix-fabric-frameview-stall
pv-nvidia Aug 3, 2026
99640c2
Refresh the child selection once per opposite-space recompute
pv-nvidia Aug 3, 2026
99ee2a0
Keep first-occurrence semantics in create_mapping
pv-nvidia Aug 3, 2026
effba2a
Trim implementation detail from docs and changelogs
pv-nvidia Aug 3, 2026
705fdf5
Remove carb profiler zones from FabricFrameView
pv-nvidia Aug 3, 2026
5462253
Remove Fabric index attributes when a view is released
pv-nvidia Aug 3, 2026
ebc967d
Close Fabric views in tests instead of leaking them
pv-nvidia Aug 3, 2026
9954861
Merge branch 'develop' into pv/fix-fabric-frameview-stall
pv-nvidia Aug 3, 2026
dad3bbd
Fixed slow device<->host roundtrip in _resolve_indices_wp
pv-nvidia Aug 3, 2026
40b77ad
Close frame views at their remaining callsites
pv-nvidia Aug 4, 2026
b08aa74
Add isaaclab_mimic changelog fragment
pv-nvidia Aug 4, 2026
3c2f247
Close the camera's frame view when the camera is dropped
pv-nvidia Aug 4, 2026
80ee2a2
Merge branch 'develop' into pv/fix-fabric-frameview-stall
pv-nvidia Aug 4, 2026
06e11c3
Merge branch 'develop' into pv/fix-fabric-frameview-stall
pv-nvidia Aug 4, 2026
62ed100
Merge branch 'develop' into pv/fix-fabric-frameview-stall
pv-nvidia Aug 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Fixed
^^^^^

* Fixed :class:`~isaaclab.scene_data.SceneDataProvider` transform mapping stalling
at high rigid-body counts, which delayed setup by minutes in scenes with
thousands of environments.

Added
^^^^^

* Added :meth:`~isaaclab.sim.views.BaseFrameView.close` to release backend state

@pbarejko pbarejko Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How releasing the backend state helps the performance?

@pv-nvidia pv-nvidia Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each FabricFrameView assigns a unique index attribute to the selected prims, so these need to be removed when the frame view is deleted. Otherwise every newly created frame view would again create new attributes, and so on.

In general, a frame view must be able to do cleanup, hence the close method.

authored by a frame view. Backends also release best-effort on garbage
collection, but only an explicit close is deterministic.
11 changes: 7 additions & 4 deletions source/isaaclab/isaaclab/envs/utils/camera_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,10 +250,13 @@ def prim_world_positions(
for env_id in env_indices:
prim_path = env_path_from_template(prim_path_template, env_id)
view = FrameView(prim_path, device="cpu", stage=stage)
if view.count != 1:
raise RuntimeError(f"expected one prim, got {view.count}")
pos_w, _ = view.get_world_poses()
pos = pos_w.torch[0].detach().cpu()
try:
if view.count != 1:
raise RuntimeError(f"expected one prim, got {view.count}")
pos_w, _ = view.get_world_poses()
pos = pos_w.torch[0].detach().cpu()
finally:
view.close()
positions.append((float(pos[0]), float(pos[1]), float(pos[2])))
return torch.tensor(positions, dtype=torch.float32)
except Exception:
Expand Down
12 changes: 7 additions & 5 deletions source/isaaclab/isaaclab/scene_data/scene_data_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

from __future__ import annotations

import contextlib
import logging
import re
from collections import deque
Expand Down Expand Up @@ -213,10 +212,13 @@ def create_mapping(self, paths: list[str | None]) -> wp.array(dtype=wp.int32) |
paths or if no mapping is needed.
"""
if input_paths := self.backend.transform_paths:
mapping = [-1] * len(input_paths)
for i, path in enumerate(input_paths):
with contextlib.suppress(ValueError):
mapping[i] = paths.index(path)
# The map keeps resolution linear in the number of paths. For duplicate
# paths the first occurrence wins, matching ``list.index``.
path_to_out: dict[str | None, int] = {}
for out_idx, out_path in enumerate(paths):
if out_path not in path_to_out:
path_to_out[out_path] = out_idx
mapping = [path_to_out.get(path, -1) for path in input_paths]
if not np.array_equal(mapping, np.arange(len(input_paths))):
return wp.array(mapping, dtype=wp.int32)
return None
Expand Down
12 changes: 10 additions & 2 deletions source/isaaclab/isaaclab/sensors/camera/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,12 @@ def __del__(self):
"""Unsubscribes from callbacks and cleans up renderer resources."""
# unsubscribe callbacks
super().__del__()
# release the frame view's backend state (getattr: _view is assigned in
# _initialize_impl, so it is absent if construction failed earlier)
view = getattr(self, "_view", None)
if view is not None:
view.close()
self._view = None
# cleanup render resources (renderer may be None if never initialized)
if self._renderer is not None:
self._renderer.cleanup(self._render_data)
Expand Down Expand Up @@ -901,5 +907,7 @@ def _invalidate_initialize_callback(self, event):
self._renderer = None
# call parent
super()._invalidate_initialize_callback(event)
# set all existing views to None to invalidate them
self._view = None
# release backend state deterministically, then invalidate the view
if self._view is not None:
self._view.close()
self._view = None
11 changes: 11 additions & 0 deletions source/isaaclab/isaaclab/sim/views/base_frame_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,17 @@ def device(self) -> str:
"""Device where arrays are allocated (``"cpu"`` or ``"cuda:0"``)."""
...

def close(self) -> None:
"""Release backend state authored by this view. The view must not be used afterwards.

The base implementation is a no-op; backends that author persistent
state (e.g. the Fabric backend's per-view index attributes) override it.
Backends also release best-effort when the view is garbage collected,
but only an explicit :meth:`close` is deterministic -- collection
timing is up to the interpreter. Calling :meth:`close` more than once
is safe.
"""

# ------------------------------------------------------------------
# Write scope -- recommended API for all transform writes.
# ------------------------------------------------------------------
Expand Down
25 changes: 25 additions & 0 deletions source/isaaclab/isaaclab/utils/warp/fabric.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@
IndexedFabricArrayMat44d = Any
ArrayUInt32 = Any
ArrayUInt32_1d = Any
ArrayInt32_1d = Any
ArrayFloat32_2d = Any
else:
FabricArrayUInt32 = wp.fabricarray(dtype=wp.uint32)
FabricArrayMat44d = wp.fabricarray(dtype=wp.mat44d)
IndexedFabricArrayMat44d = wp.indexedfabricarray(dtype=wp.mat44d)
ArrayUInt32 = wp.array(ndim=1, dtype=wp.uint32)
ArrayUInt32_1d = wp.array(dtype=wp.uint32)
ArrayInt32_1d = wp.array(dtype=wp.int32)
ArrayFloat32_2d = wp.array(ndim=2, dtype=wp.float32)


Expand All @@ -46,6 +48,29 @@ def arange_k(a: ArrayUInt32_1d):
a[tid] = wp.uint32(tid)


@wp.kernel(enable_backward=False)
def map_view_indices_to_fabric_slots(view_indices: FabricArrayUInt32, fabric_slots: ArrayInt32_1d):
"""Invert a selection's per-prim view-index attribute into a slot lookup table.

Inverts a permutation: ``view_indices`` holds each selected prim's view-side
index, and after the launch ``fabric_slots[view_index]`` is that prim's
fabric-side slot, ready to use as :class:`wp.indexedfabricarray` indices.

The launch dimension must equal the selection's prim count, and the stored
view indices must cover ``0..dim-1`` exactly for the table to be complete.
"""
fabric_slot = int(wp.tid())
view_index = int(view_indices[fabric_slot])
fabric_slots[view_index] = fabric_slot


@wp.kernel(enable_backward=False)
def gather_fabric_slots(slots: ArrayInt32_1d, gather_map: ArrayUInt32_1d, out_slots: ArrayInt32_1d):
"""Gather ``slots`` entries through ``gather_map``: ``out_slots[i] = slots[gather_map[i]]``."""
i = int(wp.tid())
out_slots[i] = slots[int(gather_map[i])]


@wp.kernel(enable_backward=False)
def decompose_fabric_transformation_matrix_to_warp_arrays(
fabric_matrices: FabricArrayMat44d,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Fixed
^^^^^

* Fixed :class:`SceneAsset` leaking its cached frame view when the view is rebuilt,
which left the view's backend state to be released on garbage collection.
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ def _get_xform_view(self) -> FrameView:
cloned prims exist.
"""
if self._xform_view is None or self._xform_view.count == 0:
if self._xform_view is not None:
self._xform_view.close()
entity = self.scene[self.entity_name]
prim_path = (
entity.prim_path
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Fixed
^^^^^

* Fixed camera world-pose resolution stalling at high environment counts under the
PhysX backend, which caused multi-second pauses between rendered frames and
benchmark timeouts.

Added
^^^^^

* Added :meth:`close` to the PhysX Fabric frame view, removing its per-view Fabric
index attributes so that views recreated over the same prims no longer accumulate
attributes. Views dropped without closing are cleaned up on garbage collection,
with a warning.
Loading
Loading