@@ -47,9 +47,13 @@ class FabricFrameView(BaseFrameView):
4747 fallback and non-accelerated operations (local poses, visibility, scales
4848 when Fabric is disabled).
4949
50- When Fabric is enabled, world-pose and scale operations use GPU-accelerated
51- Warp kernels operating on ``omni:fabric:worldMatrix``. All other operations
52- delegate to the internal USD view.
50+ When Fabric is enabled, world-pose and scale operations use Warp kernels
51+ operating on ``omni:fabric:worldMatrix``. All other operations delegate
52+ to the internal USD view.
53+
54+ After every Fabric write, :meth:`PrepareForReuse` is called on the
55+ ``PrimSelection`` to notify the renderer (FSD/Storm) that Fabric data
56+ has changed.
5357
5458 Pose getters return :class:`~isaaclab.utils.warp.ProxyArray`. Setters accept ``wp.array``.
5559 """
@@ -59,31 +63,15 @@ def __init__(
5963 prim_path : str ,
6064 device : str = "cpu" ,
6165 validate_xform_ops : bool = True ,
62- sync_usd_on_fabric_write : bool = False ,
6366 stage : Usd .Stage | None = None ,
67+ ** kwargs ,
6468 ):
6569 self ._usd_view = UsdFrameView (prim_path , device = device , validate_xform_ops = validate_xform_ops , stage = stage )
6670 self ._device = device
67- self ._sync_usd_on_fabric_write = sync_usd_on_fabric_write
6871
6972 settings = SettingsManager .instance ()
7073 self ._use_fabric = bool (settings .get ("/physics/fabricEnabled" , False ))
7174
72- if self ._use_fabric and self ._device == "cpu" :
73- logger .warning (
74- "Fabric mode with Warp fabric-array operations is not supported on CPU devices. "
75- "Falling back to standard USD operations on the CPU. This may impact performance."
76- )
77- self ._use_fabric = False
78-
79- if self ._use_fabric and self ._device not in ("cuda" , "cuda:0" ):
80- logger .warning (
81- f"Fabric mode is not supported on device '{ self ._device } '. "
82- "USDRT SelectPrims and Warp fabric arrays only support cuda:0. "
83- "Falling back to standard USD operations. This may impact performance."
84- )
85- self ._use_fabric = False
86-
8775 self ._fabric_initialized = False
8876 self ._fabric_usd_sync_done = False
8977 self ._fabric_selection = None
@@ -136,6 +124,8 @@ def set_world_poses(self, positions=None, orientations=None, indices=None):
136124 if not self ._fabric_initialized :
137125 self ._initialize_fabric ()
138126
127+ self ._prepare_for_reuse ()
128+
139129 indices_wp = self ._resolve_indices_wp (indices )
140130 count = indices_wp .shape [0 ]
141131
@@ -167,8 +157,6 @@ def set_world_poses(self, positions=None, orientations=None, indices=None):
167157
168158 self ._fabric_hierarchy .update_world_xforms ()
169159 self ._fabric_usd_sync_done = True
170- if self ._sync_usd_on_fabric_write :
171- self ._usd_view .set_world_poses (positions , orientations , indices )
172160
173161 def get_world_poses (self , indices : wp .array | None = None ) -> tuple [ProxyArray , ProxyArray ]:
174162 if not self ._use_fabric :
@@ -179,6 +167,8 @@ def get_world_poses(self, indices: wp.array | None = None) -> tuple[ProxyArray,
179167 if not self ._fabric_usd_sync_done :
180168 self ._sync_fabric_from_usd_once ()
181169
170+ self ._prepare_for_reuse ()
171+
182172 indices_wp = self ._resolve_indices_wp (indices )
183173 count = indices_wp .shape [0 ]
184174
@@ -231,6 +221,8 @@ def set_scales(self, scales, indices=None):
231221 if not self ._fabric_initialized :
232222 self ._initialize_fabric ()
233223
224+ self ._prepare_for_reuse ()
225+
234226 indices_wp = self ._resolve_indices_wp (indices )
235227 count = indices_wp .shape [0 ]
236228
@@ -258,8 +250,6 @@ def set_scales(self, scales, indices=None):
258250
259251 self ._fabric_hierarchy .update_world_xforms ()
260252 self ._fabric_usd_sync_done = True
261- if self ._sync_usd_on_fabric_write :
262- self ._usd_view .set_scales (scales , indices )
263253
264254 def get_scales (self , indices = None ):
265255 if not self ._use_fabric :
@@ -270,6 +260,8 @@ def get_scales(self, indices=None):
270260 if not self ._fabric_usd_sync_done :
271261 self ._sync_fabric_from_usd_once ()
272262
263+ self ._prepare_for_reuse ()
264+
273265 indices_wp = self ._resolve_indices_wp (indices )
274266 count = indices_wp .shape [0 ]
275267
@@ -297,6 +289,46 @@ def get_scales(self, indices=None):
297289 wp .synchronize ()
298290 return scales_wp
299291
292+ # ------------------------------------------------------------------
293+ # Internal — PrepareForReuse (renderer notification + topology tracking)
294+ # ------------------------------------------------------------------
295+
296+ def _prepare_for_reuse (self ) -> None :
297+ """Call PrepareForReuse on the PrimSelection to notify the renderer.
298+
299+ PrepareForReuse serves two purposes:
300+
301+ 1. **Renderer notification**: Tells FSD/Storm that Fabric data has
302+ been (or will be) modified, so the next rendered frame reflects
303+ the updated transforms.
304+ 2. **Topology change detection**: Returns True when Fabric's
305+ internal memory layout changed (e.g., prims added/removed).
306+ In that case, view-to-fabric index mappings and fabricarrays
307+ must be rebuilt.
308+ """
309+ if self ._fabric_selection is None :
310+ return
311+
312+ topology_changed = self ._fabric_selection .PrepareForReuse ()
313+ if topology_changed :
314+ logger .info ("Fabric topology changed — rebuilding view-to-fabric index mapping." )
315+ self ._rebuild_fabric_arrays ()
316+
317+ def _rebuild_fabric_arrays (self ) -> None :
318+ """Rebuild fabricarray and view↔fabric mappings after a topology change."""
319+ self ._view_to_fabric = wp .zeros ((self .count ,), dtype = wp .uint32 , device = self ._fabric_device )
320+ self ._fabric_to_view = wp .fabricarray (self ._fabric_selection , self ._view_index_attr )
321+
322+ wp .launch (
323+ kernel = fabric_utils .set_view_to_fabric_array ,
324+ dim = self ._fabric_to_view .shape [0 ],
325+ inputs = [self ._fabric_to_view , self ._view_to_fabric ],
326+ device = self ._fabric_device ,
327+ )
328+ wp .synchronize ()
329+
330+ self ._fabric_world_matrices = wp .fabricarray (self ._fabric_selection , "omni:fabric:worldMatrix" )
331+
300332 # ------------------------------------------------------------------
301333 # Internal — Fabric initialization
302334 # ------------------------------------------------------------------
@@ -391,11 +423,8 @@ def _sync_fabric_from_usd_once(self) -> None:
391423 orientations_usd = orientations_usd_ta .warp
392424 scales_usd = self ._usd_view .get_scales ()
393425
394- prev_sync = self ._sync_usd_on_fabric_write
395- self ._sync_usd_on_fabric_write = False
396426 self .set_world_poses (positions_usd , orientations_usd )
397427 self .set_scales (scales_usd )
398- self ._sync_usd_on_fabric_write = prev_sync
399428
400429 self ._fabric_usd_sync_done = True
401430
0 commit comments