feat: add fabric_read/fabric_write context managers to FabricFrameView#5382
feat: add fabric_read/fabric_write context managers to FabricFrameView#5382pv-nvidia wants to merge 2 commits intoisaac-sim:developfrom
Conversation
There was a problem hiding this comment.
🤖 Isaac Lab Review Bot
Summary
This PR adds RAII-style fabric_read() and fabric_write() context managers to FabricFrameView, enabling safe raw Fabric access for custom Warp kernels. It also removes the deprecated sync_usd_on_fabric_write parameter in favor of calling PrepareForReuse() to notify the renderer of Fabric data changes. The implementation is generally correct and well-tested, but there are a few issues that should be addressed.
Architecture Impact
- Camera sensor (
camera.py): Now relies onPrepareForReuse()instead ofsync_usd_on_fabric_write. This is a behavioral change—Fabric writes no longer sync back to USD, which is intentional but changes observable behavior. - FabricFrameView public API: Adds
fabric_read(),fabric_write(),world_matrices, andview_to_fabric_mappingas new public API surface. These expose internal Fabric arrays, creating a contract that must be maintained. - Dependencies: Adds
omni.kit.viewport.windowandomni.kit.hydra_textureto the headless rendering kit file, which may affect startup time and memory usage.
Implementation Verdict
Minor fixes needed
Test Coverage
Good coverage for the new context managers with test_fabric_write_context_manager and test_fabric_read_context_manager. The test_camera_pose_update_reflected_in_render test validates the PrepareForReuse integration with the renderer. However, tests for topology change handling in _rebuild_fabric_arrays are limited to verifying the return type of PrepareForReuse() without actually triggering a topology change.
CI Status
No CI checks available yet.
Findings
🟡 Warning: source/isaaclab_physx/isaaclab_physx/sim/views/fabric_frame_view.py:340-343 — _rebuild_fabric_arrays doesn't rebuild all cached buffers
When topology changes, _rebuild_fabric_arrays rebuilds _view_to_fabric, _fabric_to_view, and _fabric_world_matrices, but does NOT rebuild the cached position/orientation/scale buffers (_fabric_positions_buf, _fabric_orientations_buf, _fabric_scales_buf). If the prim count changes, these buffers would have the wrong size, causing kernel launch dimension mismatches.
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)
# ...
# Missing: _fabric_positions_buf, _fabric_orientations_buf, _fabric_scales_buf🟡 Warning: source/isaaclab_physx/isaaclab_physx/sim/views/fabric_frame_view.py:525 — _FabricWriteContext.__exit__ doesn't call _prepare_for_reuse() after hierarchy update
The docstring says it calls "PrepareForReuse() on exit", but the actual implementation only calls update_world_xforms() without a subsequent _prepare_for_reuse(). This means the renderer isn't notified of the write completion in the same way as set_world_poses().
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 # Missing: self._view._prepare_for_reuse()🔵 Improvement: source/isaaclab_physx/isaaclab_physx/sim/views/fabric_frame_view.py:359-370 — Context manager properties access private members without validation
The world_matrices and view_to_fabric properties on the context managers access _fabric_world_matrices and _view_to_fabric directly. While __enter__ ensures initialization, a user could still access these properties before entering the context or after a topology change invalidates them during the context.
@property
def world_matrices(self) -> "wp.fabricarray":
"""The fabricarray of omni:fabric:worldMatrix."""
return self._view._fabric_world_matrices # Could be stale after topology change🔵 Improvement: source/isaaclab_physx/test/sim/test_views_xform_prim_fabric.py:119-126 — test_set_world_updates_local shadow definition
The test imports the shared test via from frame_view_contract_utils import * (line ~19) and then redefines it with xfail. This works but relies on pytest's name-based collection. Consider using pytest.param with marks or a different function name to make the override relationship explicit.
🔵 Improvement: source/isaaclab/test/sensors/test_tiled_camera.py:248-267 — Test doesn't clean up camera on failure
The test creates a camera and uses del camera at the end, but if an assertion fails mid-test, the camera won't be cleaned up. Consider using a fixture or try/finally.
def test_camera_pose_update_reflected_in_render(setup_camera, device, camera_cls):
# ...
camera = camera_cls(cam_cfg)
# ... assertions that could fail ...
del camera # Not reached on assertion failure🔵 Improvement: source/isaaclab_physx/isaaclab_physx/sim/views/fabric_frame_view.py:376-377 — world_matrices and view_to_fabric_mapping use getattr with None default
These properties use getattr(self, "_fabric_world_matrices", None) which is fine, but the return type annotation says wp.fabricarray | None while the internal code assumes non-None after initialization. Consider documenting that users should only access these after calling get_world_poses() or within a context manager.
Greptile SummaryThis PR adds RAII-style
Confidence Score: 4/5Safe to merge after addressing the incomplete One P1 finding:
Important Files Changed
Sequence DiagramsequenceDiagram
participant User as User Code
participant FW as fabric_write() ctx
participant FFV as FabricFrameView
participant WP as Warp Runtime
participant Renderer as FSD/Storm Renderer
User->>FW: with view.fabric_write() as fab
FW->>FFV: _initialize_fabric() (if needed)
FW->>FFV: _sync_fabric_from_usd_once() (if needed)
FW->>FFV: _prepare_for_reuse()
FFV->>Renderer: PrepareForReuse() — notify of pending changes
Note over FFV: topology_changed? → _rebuild_fabric_arrays()
FW-->>User: fab (world_matrices, view_to_fabric, count)
User->>WP: wp.launch(my_kernel, inputs=[fab.world_matrices, ...])
WP->>Renderer: (Fabric data modified in-place)
User->>FW: __exit__ (no exception)
FW->>WP: wp.synchronize()
FW->>FFV: _fabric_hierarchy.update_world_xforms()
FW->>FFV: _fabric_usd_sync_done = True
Note over User,Renderer: Next render frame picks up updated transforms
Reviews (1): Last reviewed commit: "feat: add fabric_read/fabric_write conte..." | Re-trigger Greptile |
| 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, | ||
| 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") |
There was a problem hiding this comment.
Incomplete rebuild after topology change may leave stale/wrong-sized buffers
_rebuild_fabric_arrays re-creates the fabricarrays and the _view_to_fabric / _fabric_to_view mappings, but the docstring explicitly documents this as the handler for "prims added/removed" — a scenario that changes self.count. The following count-dependent buffers are not rebuilt:
_default_view_indices(shape=(self.count,))_fabric_positions_buf(shape=(self.count, 3))_fabric_orientations_buf(shape=(self.count, 4))_fabric_scales_buf(shape=(self.count, 3))
If self.count changes after a topology event, subsequent get_world_poses() / get_scales() calls that use these cached buffers will either produce wrong results or crash with a shape mismatch. _initialize_fabric creates all of them; _rebuild_fabric_arrays should mirror that.
| 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) |
There was a problem hiding this comment.
Docstring incorrectly promises a second
PrepareForReuse() call on exit
The docstring says: "and update_world_xforms() + PrepareForReuse() on exit", but _FabricWriteContext.__exit__ only calls wp.synchronize() and update_world_xforms() — there is no second PrepareForReuse() call. Either the exit should call self._view._prepare_for_reuse() after update_world_xforms(), or the docstring should be corrected to match the implementation.
| @pytest.mark.parametrize( | ||
| "device, camera_cls", | ||
| [ | ||
| pytest.param("cpu", TiledCamera, id="cpu-tiled"), | ||
| pytest.param("cpu", Camera, id="cpu-non_tiled"), | ||
| pytest.param("cuda:0", TiledCamera, id="cuda:0-tiled"), | ||
| pytest.param("cuda:0", Camera, id="cuda:0-non_tiled"), | ||
| ], | ||
| ) |
There was a problem hiding this comment.
Missing
@pytest.mark.isaacsim_ci marker
Every other test in this file carries @pytest.mark.isaacsim_ci, which gates execution in the CI pipeline. The new test_camera_pose_update_reflected_in_render is 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.
Code Coverage —
|
86db063 to
60ff515
Compare
…c_write Replace the sync_usd_on_fabric_write workaround with proper PrepareForReuse() calls on the Fabric PrimSelection. This tells the renderer (FSD/Storm) that Fabric data is about to be modified, so the next rendered frame reflects updated transforms. Key changes: - FabricFrameView: call _prepare_for_reuse() before every Fabric read/write to notify the renderer and detect topology changes - FabricFrameView: remove sync_usd_on_fabric_write parameter (deprecated with warning via **kwargs for backward compat) - FabricFrameView: add _rebuild_fabric_arrays() for topology change recovery when PrepareForReuse() returns True - camera.py: remove sync_usd_on_fabric_write=True from FrameView construction (PrepareForReuse makes it unnecessary) - usd_frame_view.py: clean stale docstring reference Tests added: - test_camera_pose_update_reflected_in_render: validates camera pose changes propagate to rendered depth (close vs far position) for both CPU and GPU paths, tiled and non-tiled cameras - test_fabric_set_world_does_not_write_back_to_usd: confirms Fabric writes stay in Fabric without USD writeback - test_prepare_for_reuse_detects_topology_change: validates the PrepareForReuse API returns correct topology status - xfail for test_set_world_updates_local (Issue isaac-sim#5: localMatrix not updated from Fabric world pose — tracked separately) Also includes headless rendering kit deps (viewport.window + hydra_texture) needed for camera render tests. Addresses Piotr's Issue isaac-sim#1 (USD write-back) and Issue isaac-sim#4 (PrepareForReuse / renderer notification).
| self._fabric_to_view = wp.fabricarray(self._fabric_selection, self._view_index_attr) | ||
|
|
||
| wp.launch( | ||
| kernel=fabric_utils.set_view_to_fabric_array, |
There was a problem hiding this comment.
Thanks @pv-nvidia , will this sync and kernel go away if we switch to indexedfabricarray? Is this a temporary solution?
Add RAII-style context managers for safe raw Fabric access: - fabric_write(): calls PrepareForReuse on entry, update_world_xforms + sync on exit. Provides world_matrices fabricarray and view_to_fabric mapping for custom warp kernel launches. - fabric_read(): calls PrepareForReuse on entry (ensures valid pointers after topology changes), no-op on exit. Also exposes read-only properties: - world_matrices: the raw fabricarray of omni:fabric:worldMatrix - view_to_fabric_mapping: the view-index to fabric-index mapping This addresses Piotr's Issue isaac-sim#6 (reader/writer pattern) by providing a structured way to bracket Fabric operations that ensures PrepareForReuse and hierarchy updates are never forgotten. Tests added: - test_fabric_write_context_manager: validates write + readback - test_fabric_read_context_manager: validates read without side effects Depends on: fix/fabric-prepare-for-reuse (PR isaac-sim#5380)
60ff515 to
8ab1bdb
Compare
Summary
Add RAII-style
fabric_read()andfabric_write()context managers toFabricFrameViewfor safe raw Fabric access.Problem
Users who need to launch custom Warp kernels against Fabric data must manually call
PrepareForReuse(),update_world_xforms(), and manage sync state. Forgetting any of these steps leads to stale data or renderer notification bugs that are hard to debug.This was Piotr's Issue #6 (reader/writer pattern).
Solution
Context Managers
Properties
view.world_matrices— raw fabricarray ofomni:fabric:worldMatrixview.view_to_fabric_mapping— view-index to Fabric-index mappingTest Results
Dependencies
Depends on PR #5380 (
fix/fabric-prepare-for-reuse).