Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
authored by a frame view. Backends also release best-effort on garbage
collection, but only an explicit close is deterministic.
16 changes: 11 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,17 @@ 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)
# Build a path -> output-index map once, then resolve each input path
# against it. This replaces an ``paths.index(path)`` linear scan per
# input path, which is quadratic overall and took minutes at the ~200k
# rigid bodies of an 8192-env scene. First occurrence wins, preserving
# the ``list.index`` semantics for duplicate paths; measured over 200k
# paths the guard costs nothing versus an unguarded dict comprehension.
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
6 changes: 4 additions & 2 deletions source/isaaclab/isaaclab/sensors/camera/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -899,5 +899,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,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