Skip to content

Commit 25e958f

Browse files
committed
Address Greptile feedback on Fabric write path
- Replace assert in _rebuild_fabric_arrays with RuntimeError so the prim-count invariant survives python -O. - Factor a single _compose_fabric_transform helper used by set_world_poses, set_scales, and the initial USD->Fabric sync. The sync now does one combined kernel launch with one PrepareForReuse, removing the prior double-call concern. - Refresh the changelog fragments accordingly.
1 parent b98e05d commit 25e958f

4 files changed

Lines changed: 67 additions & 71 deletions

File tree

source/isaaclab/changelog.d/fix-fabric-prepare-for-reuse.rst

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ Changed
33

44
* Updated :class:`~isaaclab.sensors.camera.Camera` to construct its internal
55
:class:`~isaaclab.sim.views.FrameView` without the now-removed
6-
``sync_usd_on_fabric_write`` kwarg. USD attributes on camera prims are
7-
no longer kept in sync with Fabric writes; read poses through the view's
8-
getters instead.
6+
``sync_usd_on_fabric_write`` keyword (see the corresponding
7+
``isaaclab_physx`` entry). No user-facing migration is required —
8+
callers reading camera poses through the sensor's data buffers are
9+
unaffected. Direct readers of the camera prim's USD attributes during a
10+
simulation step should switch to the sensor getters, since USD attributes
11+
are no longer updated in lock-step with Fabric writes.
Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
Changed
2-
^^^^^^^
1+
Fixed
2+
^^^^^
33

4-
* Removed the ``cuda:0``-only restriction in
5-
:class:`~isaaclab_physx.sim.views.FabricFrameView`. USDRT ``SelectPrims``
6-
now accepts any CUDA device index, so Fabric acceleration runs on the
7-
simulation device (e.g., ``cuda:1``) instead of silently falling back to
8-
the slower USD path. This unblocks distributed training where each
9-
process is pinned to a specific GPU.
4+
* Fixed :class:`~isaaclab_physx.sim.views.FabricFrameView` falling back to
5+
the slow USD path on every CUDA device other than ``cuda:0``. USDRT
6+
``SelectPrims`` now accepts any CUDA device index, so Fabric acceleration
7+
runs on the simulation device the view was constructed with (e.g.
8+
``cuda:1``). This unblocks distributed training where each rank is
9+
pinned to a non-primary GPU.

source/isaaclab_physx/changelog.d/fix-fabric-prepare-for-reuse.rst

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,27 @@ Changed
33

44
* **Breaking:** Removed the ``sync_usd_on_fabric_write`` keyword argument from
55
:class:`~isaaclab_physx.sim.views.FabricFrameView`. Fabric writes
6-
(``set_world_poses``, ``set_scales``) now notify the renderer via
7-
``PrepareForReuse()`` on the underlying ``PrimSelection`` instead of writing
8-
back to USD, which is ~200x faster and avoids the stale USD shadow state the
9-
old path produced. Callers passing ``sync_usd_on_fabric_write=True`` should
10-
remove the argument; if they relied on USD reflecting Fabric writes, they
11-
should now read Fabric poses directly via the view's getters or refresh USD
12-
explicitly.
6+
(:meth:`~isaaclab_physx.sim.views.FabricFrameView.set_world_poses`,
7+
:meth:`~isaaclab_physx.sim.views.FabricFrameView.set_scales`) now notify the
8+
renderer via ``PrepareForReuse`` on the underlying ``PrimSelection`` and
9+
detect Fabric topology changes, instead of writing back to USD. This is
10+
~200x faster and removes the stale USD shadow state the old path produced.
11+
Migration: drop the ``sync_usd_on_fabric_write=True`` argument; if you
12+
previously relied on USD reflecting Fabric writes, read poses through the
13+
view's getters or refresh USD explicitly at your sync point.
14+
15+
* Combined the initial USD→Fabric sync into a single Fabric write so
16+
``PrepareForReuse`` is invoked exactly once per logical update (positions,
17+
orientations, and scales are composed in one kernel launch). This avoids
18+
the possibility of a second non-idempotent ``PrepareForReuse`` call masking
19+
a topology-change signal that should have triggered a fabricarray rebuild.
20+
21+
Fixed
22+
^^^^^
23+
24+
* Fixed the topology-change invariant guard in
25+
:class:`~isaaclab_physx.sim.views.FabricFrameView` not surviving
26+
``python -O``. The check now raises :class:`RuntimeError` instead of using
27+
``assert`` so the prim-count mismatch between view and Fabric is reported
28+
at every optimisation level rather than silently producing wrong poses or
29+
out-of-bounds kernel indices.

source/isaaclab_physx/isaaclab_physx/sim/views/fabric_frame_view.py

Lines changed: 29 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -134,43 +134,7 @@ def set_world_poses(self, positions=None, orientations=None, indices=None):
134134
if not self._use_fabric:
135135
self._usd_view.set_world_poses(positions, orientations, indices)
136136
return
137-
138-
if not self._fabric_initialized:
139-
self._initialize_fabric()
140-
141-
self._prepare_for_reuse()
142-
143-
indices_wp = self._resolve_indices_wp(indices)
144-
count = indices_wp.shape[0]
145-
146-
dummy = wp.zeros((0, 3), dtype=wp.float32, device=self._device)
147-
positions_wp = _to_float32_2d(positions) if positions is not None else dummy
148-
orientations_wp = (
149-
_to_float32_2d(orientations)
150-
if orientations is not None
151-
else wp.zeros((0, 4), dtype=wp.float32, device=self._device)
152-
)
153-
154-
wp.launch(
155-
kernel=fabric_utils.compose_fabric_transformation_matrix_from_warp_arrays,
156-
dim=count,
157-
inputs=[
158-
self._fabric_world_matrices,
159-
positions_wp,
160-
orientations_wp,
161-
dummy,
162-
False,
163-
False,
164-
False,
165-
indices_wp,
166-
self._view_to_fabric,
167-
],
168-
device=self._fabric_device,
169-
)
170-
wp.synchronize()
171-
172-
self._fabric_hierarchy.update_world_xforms()
173-
self._fabric_usd_sync_done = True
137+
self._compose_fabric_transform(positions=positions, orientations=orientations, indices=indices)
174138

175139
def get_world_poses(self, indices: wp.array | None = None) -> tuple[ProxyArray, ProxyArray]:
176140
if not self._use_fabric:
@@ -229,7 +193,15 @@ def set_scales(self, scales, indices=None):
229193
if not self._use_fabric:
230194
self._usd_view.set_scales(scales, indices)
231195
return
196+
self._compose_fabric_transform(scales=scales, indices=indices)
197+
198+
def _compose_fabric_transform(self, positions=None, orientations=None, scales=None, indices=None):
199+
"""Write the given subset of (position, orientation, scale) into Fabric in one kernel launch.
232200
201+
Components left as ``None`` are skipped via empty input arrays — the kernel reads them
202+
from the existing Fabric matrix. Always invokes :meth:`_prepare_for_reuse` exactly once
203+
per write, even when multiple components are updated together.
204+
"""
233205
if not self._fabric_initialized:
234206
self._initialize_fabric()
235207

@@ -238,17 +210,19 @@ def set_scales(self, scales, indices=None):
238210
indices_wp = self._resolve_indices_wp(indices)
239211
count = indices_wp.shape[0]
240212

241-
dummy3 = wp.zeros((0, 3), dtype=wp.float32, device=self._device)
242-
dummy4 = wp.zeros((0, 4), dtype=wp.float32, device=self._device)
243-
scales_wp = _to_float32_2d(scales)
213+
empty3 = wp.zeros((0, 3), dtype=wp.float32, device=self._device)
214+
empty4 = wp.zeros((0, 4), dtype=wp.float32, device=self._device)
215+
positions_wp = _to_float32_2d(positions) if positions is not None else empty3
216+
orientations_wp = _to_float32_2d(orientations) if orientations is not None else empty4
217+
scales_wp = _to_float32_2d(scales) if scales is not None else empty3
244218

245219
wp.launch(
246220
kernel=fabric_utils.compose_fabric_transformation_matrix_from_warp_arrays,
247221
dim=count,
248222
inputs=[
249223
self._fabric_world_matrices,
250-
dummy3,
251-
dummy4,
224+
positions_wp,
225+
orientations_wp,
252226
scales_wp,
253227
False,
254228
False,
@@ -332,10 +306,11 @@ def _rebuild_fabric_arrays(self) -> None:
332306
pattern (via ``_usd_view.count``) and does not change when Fabric rearranges its
333307
internal memory layout. The assertion below guards this invariant.
334308
"""
335-
assert self.count == self._default_view_indices.shape[0], (
336-
f"Prim count changed ({self.count} vs {self._default_view_indices.shape[0]}). "
337-
"Fabric topology change added/removed tracked prims — full re-initialization required."
338-
)
309+
if self.count != self._default_view_indices.shape[0]:
310+
raise RuntimeError(
311+
f"Prim count changed ({self.count} vs {self._default_view_indices.shape[0]}). "
312+
"Fabric topology change added/removed tracked prims — full re-initialization required."
313+
)
339314
self._view_to_fabric = wp.zeros((self.count,), dtype=wp.uint32, device=self._fabric_device)
340315
self._fabric_to_view = wp.fabricarray(self._fabric_selection, self._view_index_attr)
341316

@@ -424,19 +399,20 @@ def _initialize_fabric(self) -> None:
424399
def _sync_fabric_from_usd_once(self) -> None:
425400
"""Sync Fabric world matrices from USD once, on the first read.
426401
427-
``set_world_poses`` and ``set_scales`` each set ``_fabric_usd_sync_done``
428-
themselves, so no explicit flag assignment is needed here.
402+
Combines position/orientation/scale into a single Fabric write so
403+
:meth:`_prepare_for_reuse` (and its underlying ``PrepareForReuse``) is invoked
404+
exactly once across the full sync.
429405
"""
430406
if not self._fabric_initialized:
431407
self._initialize_fabric()
432408

433409
positions_usd_ta, orientations_usd_ta = self._usd_view.get_world_poses()
434-
positions_usd = positions_usd_ta.warp
435-
orientations_usd = orientations_usd_ta.warp
436410
scales_usd = self._usd_view.get_scales()
437-
438-
self.set_world_poses(positions_usd, orientations_usd)
439-
self.set_scales(scales_usd)
411+
self._compose_fabric_transform(
412+
positions=positions_usd_ta.warp,
413+
orientations=orientations_usd_ta.warp,
414+
scales=scales_usd,
415+
)
440416

441417
def _resolve_indices_wp(self, indices: wp.array | None) -> wp.array:
442418
"""Resolve view indices as a Warp uint32 array."""

0 commit comments

Comments
 (0)