-
Notifications
You must be signed in to change notification settings - Fork 3.4k
feat: add fabric_read/fabric_write context managers to FabricFrameView #5382
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -50,6 +50,10 @@ class FabricFrameView(BaseFrameView): | |
| Warp kernels operating on ``omni:fabric:worldMatrix``. All other operations | ||
| delegate to the internal USD view. | ||
|
|
||
| After every Fabric write, :meth:`PrepareForReuse` is called on the | ||
| ``PrimSelection`` to notify the renderer (FSD/Storm) that Fabric data | ||
| has changed. | ||
|
|
||
| All getters return ``wp.array``. Setters accept ``wp.array``. | ||
| """ | ||
|
|
||
|
|
@@ -58,12 +62,11 @@ def __init__( | |
| prim_path: str, | ||
| device: str = "cpu", | ||
| validate_xform_ops: bool = True, | ||
| sync_usd_on_fabric_write: bool = False, | ||
| stage: Usd.Stage | None = None, | ||
| **kwargs, | ||
| ): | ||
| self._usd_view = UsdFrameView(prim_path, device=device, validate_xform_ops=validate_xform_ops, stage=stage) | ||
| self._device = device | ||
| self._sync_usd_on_fabric_write = sync_usd_on_fabric_write | ||
|
|
||
| settings = SettingsManager.instance() | ||
| self._use_fabric = bool(settings.get("/physics/fabricEnabled", False)) | ||
|
|
@@ -134,6 +137,8 @@ def set_world_poses(self, positions=None, orientations=None, indices=None): | |
| if not self._fabric_initialized: | ||
| self._initialize_fabric() | ||
|
|
||
| self._prepare_for_reuse() | ||
|
|
||
| indices_wp = self._resolve_indices_wp(indices) | ||
| count = indices_wp.shape[0] | ||
|
|
||
|
|
@@ -165,8 +170,6 @@ def set_world_poses(self, positions=None, orientations=None, indices=None): | |
|
|
||
| self._fabric_hierarchy.update_world_xforms() | ||
| self._fabric_usd_sync_done = True | ||
| if self._sync_usd_on_fabric_write: | ||
| self._usd_view.set_world_poses(positions, orientations, indices) | ||
|
|
||
| def get_world_poses(self, indices=None): | ||
| if not self._use_fabric: | ||
|
|
@@ -177,6 +180,8 @@ def get_world_poses(self, indices=None): | |
| if not self._fabric_usd_sync_done: | ||
| self._sync_fabric_from_usd_once() | ||
|
|
||
| self._prepare_for_reuse() | ||
|
|
||
| indices_wp = self._resolve_indices_wp(indices) | ||
| count = indices_wp.shape[0] | ||
|
|
||
|
|
@@ -228,6 +233,8 @@ def set_scales(self, scales, indices=None): | |
| if not self._fabric_initialized: | ||
| self._initialize_fabric() | ||
|
|
||
| self._prepare_for_reuse() | ||
|
|
||
| indices_wp = self._resolve_indices_wp(indices) | ||
| count = indices_wp.shape[0] | ||
|
|
||
|
|
@@ -255,8 +262,6 @@ def set_scales(self, scales, indices=None): | |
|
|
||
| self._fabric_hierarchy.update_world_xforms() | ||
| self._fabric_usd_sync_done = True | ||
| if self._sync_usd_on_fabric_write: | ||
| self._usd_view.set_scales(scales, indices) | ||
|
|
||
| def get_scales(self, indices=None): | ||
| if not self._use_fabric: | ||
|
|
@@ -267,6 +272,8 @@ def get_scales(self, indices=None): | |
| if not self._fabric_usd_sync_done: | ||
| self._sync_fabric_from_usd_once() | ||
|
|
||
| self._prepare_for_reuse() | ||
|
|
||
| indices_wp = self._resolve_indices_wp(indices) | ||
| count = indices_wp.shape[0] | ||
|
|
||
|
|
@@ -294,6 +301,93 @@ def get_scales(self, indices=None): | |
| wp.synchronize() | ||
| return scales_wp | ||
|
|
||
| # ------------------------------------------------------------------ | ||
| # Internal — PrepareForReuse (renderer notification + topology tracking) | ||
| # ------------------------------------------------------------------ | ||
|
|
||
| def _prepare_for_reuse(self) -> None: | ||
| """Call PrepareForReuse on the PrimSelection to notify the renderer. | ||
|
|
||
| PrepareForReuse serves two purposes: | ||
|
|
||
| 1. **Renderer notification**: Tells FSD/Storm that Fabric data has | ||
| been (or will be) modified, so the next rendered frame reflects | ||
| the updated transforms. | ||
| 2. **Topology change detection**: Returns True when Fabric's | ||
| internal memory layout changed (e.g., prims added/removed). | ||
| In that case, view-to-fabric index mappings and fabricarrays | ||
| must be rebuilt. | ||
| """ | ||
| if self._fabric_selection is None: | ||
| return | ||
|
|
||
| topology_changed = self._fabric_selection.PrepareForReuse() | ||
| if topology_changed: | ||
| logger.info("Fabric topology changed — rebuilding view-to-fabric index mapping.") | ||
| self._rebuild_fabric_arrays() | ||
|
|
||
| def _rebuild_fabric_arrays(self) -> None: | ||
| """Rebuild fabricarray and view↔fabric mappings after a topology change.""" | ||
| self._view_to_fabric = wp.zeros((self.count,), dtype=wp.uint32, device=self._fabric_device) | ||
| self._fabric_to_view = wp.fabricarray(self._fabric_selection, self._view_index_attr) | ||
|
|
||
| wp.launch( | ||
| kernel=fabric_utils.set_view_to_fabric_array, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks @pv-nvidia , will this sync and kernel go away if we switch to |
||
| dim=self._fabric_to_view.shape[0], | ||
| inputs=[self._fabric_to_view, self._view_to_fabric], | ||
| device=self._fabric_device, | ||
| ) | ||
| wp.synchronize() | ||
|
|
||
| self._fabric_world_matrices = wp.fabricarray(self._fabric_selection, "omni:fabric:worldMatrix") | ||
|
Comment on lines
+329
to
+342
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If |
||
|
|
||
| # ------------------------------------------------------------------ | ||
| # Context managers for raw Fabric access | ||
| # ------------------------------------------------------------------ | ||
|
|
||
| def fabric_write(self): | ||
| """Context manager for raw Fabric write operations. | ||
|
|
||
| Calls ``PrepareForReuse()`` on entry (notifying the renderer that | ||
| data is about to change) and ``update_world_xforms()`` + | ||
| ``PrepareForReuse()`` on exit (propagating changes through the | ||
| hierarchy). | ||
|
|
||
| Example:: | ||
|
|
||
| with view.fabric_write() as fab: | ||
| # fab.world_matrices is the fabricarray | ||
| wp.launch(my_kernel, dim=N, inputs=[fab.world_matrices, ...]) | ||
| """ | ||
| return _FabricWriteContext(self) | ||
|
Comment on lines
+348
to
+362
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The docstring says: "and |
||
|
|
||
| def fabric_read(self): | ||
| """Context manager for raw Fabric read operations. | ||
|
|
||
| Calls ``PrepareForReuse()`` on entry to ensure the view’s | ||
| fabricarray pointers are still valid after potential topology | ||
| changes. | ||
|
|
||
| Example:: | ||
|
|
||
| with view.fabric_read() as fab: | ||
| wp.launch(my_read_kernel, dim=N, inputs=[fab.world_matrices, ...]) | ||
| """ | ||
| return _FabricReadContext(self) | ||
|
|
||
| @property | ||
| def world_matrices(self) -> wp.fabricarray | None: | ||
| """The raw Fabric world-matrix array (read-only property). | ||
|
|
||
| Returns None if Fabric is not initialized. | ||
| """ | ||
| return getattr(self, "_fabric_world_matrices", None) | ||
|
|
||
| @property | ||
| def view_to_fabric_mapping(self) -> wp.array | None: | ||
| """View-index → Fabric-index mapping array.""" | ||
| return getattr(self, "_view_to_fabric", None) | ||
|
|
||
| # ------------------------------------------------------------------ | ||
| # Internal — Fabric initialization | ||
| # ------------------------------------------------------------------ | ||
|
|
@@ -384,11 +478,8 @@ def _sync_fabric_from_usd_once(self) -> None: | |
| positions_usd, orientations_usd = self._usd_view.get_world_poses() | ||
| scales_usd = self._usd_view.get_scales() | ||
|
|
||
| prev_sync = self._sync_usd_on_fabric_write | ||
| self._sync_usd_on_fabric_write = False | ||
| self.set_world_poses(positions_usd, orientations_usd) | ||
| self.set_scales(scales_usd) | ||
| self._sync_usd_on_fabric_write = prev_sync | ||
|
|
||
| self._fabric_usd_sync_done = True | ||
|
|
||
|
|
@@ -401,3 +492,90 @@ def _resolve_indices_wp(self, indices: wp.array | None) -> wp.array: | |
| if indices.dtype != wp.uint32: | ||
| return wp.array(indices.numpy().astype("uint32"), dtype=wp.uint32, device=self._device) | ||
| return indices | ||
|
|
||
|
|
||
| # ====================================================================== | ||
| # Context manager helpers (module-level, not inside FabricFrameView) | ||
| # ====================================================================== | ||
|
|
||
|
|
||
| class _FabricWriteContext: | ||
| """RAII context manager for Fabric write operations. | ||
|
|
||
| On entry: ensures Fabric is initialized, calls PrepareForReuse. | ||
| On exit (no exception): synchronizes, propagates hierarchy, marks sync done. | ||
| """ | ||
|
|
||
| __slots__ = ("_view",) | ||
|
|
||
| def __init__(self, view: FabricFrameView): | ||
| self._view = view | ||
|
|
||
| def __enter__(self): | ||
| if not self._view._fabric_initialized: | ||
| self._view._initialize_fabric() | ||
| if not self._view._fabric_usd_sync_done: | ||
| self._view._sync_fabric_from_usd_once() | ||
| self._view._prepare_for_reuse() | ||
| return self | ||
|
|
||
| def __exit__(self, exc_type, exc_val, exc_tb): | ||
| if exc_type is None: | ||
| wp.synchronize() | ||
| self._view._fabric_hierarchy.update_world_xforms() | ||
| self._view._fabric_usd_sync_done = True | ||
| return False | ||
|
|
||
| @property | ||
| def world_matrices(self) -> wp.fabricarray: | ||
| """The fabricarray of omni:fabric:worldMatrix.""" | ||
| return self._view._fabric_world_matrices | ||
|
|
||
| @property | ||
| def view_to_fabric(self) -> wp.array: | ||
| """View-index to Fabric-index mapping.""" | ||
| return self._view._view_to_fabric | ||
|
|
||
| @property | ||
| def count(self) -> int: | ||
| """Number of prims in the view.""" | ||
| return self._view.count | ||
|
|
||
|
|
||
| class _FabricReadContext: | ||
| """RAII context manager for Fabric read operations. | ||
|
|
||
| On entry: ensures Fabric is initialized, calls PrepareForReuse. | ||
| On exit: no-op. | ||
| """ | ||
|
|
||
| __slots__ = ("_view",) | ||
|
|
||
| def __init__(self, view: FabricFrameView): | ||
| self._view = view | ||
|
|
||
| def __enter__(self): | ||
| if not self._view._fabric_initialized: | ||
| self._view._initialize_fabric() | ||
| if not self._view._fabric_usd_sync_done: | ||
| self._view._sync_fabric_from_usd_once() | ||
| self._view._prepare_for_reuse() | ||
| return self | ||
|
|
||
| def __exit__(self, exc_type, exc_val, exc_tb): | ||
| return False | ||
|
|
||
| @property | ||
| def world_matrices(self) -> wp.fabricarray: | ||
| """The fabricarray of omni:fabric:worldMatrix.""" | ||
| return self._view._fabric_world_matrices | ||
|
|
||
| @property | ||
| def view_to_fabric(self) -> wp.array: | ||
| """View-index to Fabric-index mapping.""" | ||
| return self._view._view_to_fabric | ||
|
|
||
| @property | ||
| def count(self) -> int: | ||
| """Number of prims in the view.""" | ||
| return self._view.count | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pytest.mark.isaacsim_cimarkerEvery other test in this file carries
@pytest.mark.isaacsim_ci, which gates execution in the CI pipeline. The newtest_camera_pose_update_reflected_in_renderis missing it, so it will be silently skipped by CI runs that filter on that marker — defeating the intent of validating the Fabric/PrepareForReuse pose propagation fix.