fix: add PrepareForReuse to FabricFrameView, remove sync_usd_on_fabric_write#5380
fix: add PrepareForReuse to FabricFrameView, remove sync_usd_on_fabric_write#5380pv-nvidia wants to merge 1 commit intoisaac-sim:developfrom
Conversation
There was a problem hiding this comment.
🤖 Isaac Lab Review Bot
Summary
This PR replaces the sync_usd_on_fabric_write workaround in FabricFrameView with proper PrepareForReuse() calls to notify the RTX renderer of Fabric data changes. The change removes the performance-defeating USD writeback pattern while maintaining correct rendering behavior. The implementation is architecturally sound with good test coverage, though there are a few correctness concerns around the placement and frequency of _prepare_for_reuse() calls.
Architecture Impact
- Camera sensor: Removes
sync_usd_on_fabric_write=Truefrom FrameView construction, now relying onPrepareForReuse()for renderer synchronization. - FabricFrameView: Internal API change -
sync_usd_on_fabric_writeparameter removed (with deprecation warning via**kwargs). - Downstream callers: Any code passing
sync_usd_on_fabric_write=TruetoFrameViewwill now get a deprecation warning but continue to function (the flag is simply ignored). - Kit extensions: Adds
omni.kit.viewport.windowandomni.kit.hydra_textureas dependencies for headless rendering.
Implementation Verdict
Minor fixes needed — The core approach is correct, but _prepare_for_reuse() is called in getters where it shouldn't be, and there's a potential race condition in the initialization flow.
Test Coverage
Good coverage for this change:
test_camera_pose_update_reflected_in_render: Validates that pose changes propagate to rendered depth (critical regression test)test_fabric_set_world_does_not_write_back_to_usd: Confirms USD writeback removaltest_prepare_for_reuse_detects_topology_change: Basic API contract testtest_set_world_updates_local: Properly marked as xfail documenting known limitation
Missing: No test for the topology-changed path (_rebuild_fabric_arrays). The test only verifies PrepareForReuse() returns False when topology hasn't changed.
CI Status
No CI checks available yet — cannot verify the changes work on the required Kit version.
Findings
🟡 Warning: source/isaaclab_physx/isaaclab_physx/sim/views/fabric_frame_view.py:189-190 — _prepare_for_reuse() called in get_world_poses() is semantically incorrect
PrepareForReuse() is a write notification mechanism per the PR description ("tells the renderer that Fabric data is about to be modified"). Calling it before a read operation is incorrect — the purpose is to notify before writes, not reads. This could cause unnecessary renderer invalidation on every pose query.
def get_world_poses(self, indices=None):
...
self._prepare_for_reuse() # <-- Should not be here for a getterSame issue at line 281 in get_scales(). The calls should only be in set_world_poses() and set_scales().
🟡 Warning: source/isaaclab_physx/isaaclab_physx/sim/views/fabric_frame_view.py:146 — _prepare_for_reuse() called before Fabric arrays may be valid
In set_world_poses(), _prepare_for_reuse() is called at line 146, but _fabric_selection is only set during _initialize_fabric() which happens at line 143. On the first call, _initialize_fabric() runs, sets _fabric_selection, then _prepare_for_reuse() is called. However, if _initialize_fabric() fails partway through, _fabric_selection could be partially initialized. The guard in _prepare_for_reuse() (line 320: if self._fabric_selection is None) handles this, but the ordering is confusing.
Consider calling _prepare_for_reuse() after the writes complete, not before.
🔵 Improvement: source/isaaclab_physx/isaaclab_physx/sim/views/fabric_frame_view.py:326-327 — Topology change handling logs at INFO but doesn't re-sync from USD
When topology changes, _rebuild_fabric_arrays() rebuilds the index mappings but doesn't re-sync transforms from USD. If prims were added, their initial Fabric world matrices may be stale. The existing _sync_fabric_from_usd_once() is a one-shot sync; a topology change might warrant another sync.
def _prepare_for_reuse(self) -> None:
...
if topology_changed:
logger.info("Fabric topology changed — rebuilding view-to-fabric index mapping.")
self._rebuild_fabric_arrays()
# Consider: self._fabric_usd_sync_done = False # Force re-sync🔵 Improvement: source/isaaclab_physx/test/sim/test_views_xform_prim_fabric.py:127-132 — The xfail override shadows the parametrized device fixture
The test function test_set_world_updates_local takes device and view_factory as parameters, but there's no @pytest.mark.parametrize("device", ...) decorator. This relies on fixture injection from the shared contract utils. If the shared test was parametrized with multiple devices, this override won't run for all of them.
@pytest.mark.xfail(...)
def test_set_world_updates_local(device, view_factory): # Where does 'device' come from?Verify that the device fixture is properly inherited or add explicit parametrization.
🔵 Improvement: source/isaaclab/test/sensors/test_tiled_camera.py:215-216 — Test creates camera without cleanup guard
The setup_camera fixture cleans up a specific camera, but test_camera_pose_update_reflected_in_render creates an additional camera at /World/PoseTestCam. While del camera is called at line 268, if an assertion fails before that line, the camera may leak.
Consider using a context manager or adding the camera to a cleanup list.
🔵 Improvement: source/isaaclab_physx/isaaclab_physx/sim/views/fabric_frame_view.py:63-71 — Constructor signature changed without updating all callers
The old signature had sync_usd_on_fabric_write: bool = False as a positional-or-keyword parameter. Now it's removed and handled via **kwargs. The test file at line 97 shows the update, but any external code calling FabricFrameView(..., sync_usd_on_fabric_write=True) will get a deprecation warning rather than a hard error. This is fine for backward compatibility but should be documented in the changelog.
Greptile SummaryThis PR replaces the Confidence Score: 5/5Safe to merge; all findings are P2 style/documentation suggestions with no impact on correctness. The core logic is sound — sync_usd_on_fabric_write is fully removed, PrepareForReuse is wired in correctly for write paths, and the deprecation shim preserves backward compatibility. The three P2 findings (PrepareForReuse on reads, incomplete rebuild documentation, missing CI marker) are non-blocking quality observations that do not affect runtime correctness. fabric_frame_view.py — review the PrepareForReuse-on-reads concern and add a comment in _rebuild_fabric_arrays explaining the count-invariant assumption. Important Files Changed
Sequence DiagramsequenceDiagram
participant Caller
participant FabricFrameView
participant FabricSelection
participant WarpKernel
participant Renderer
Caller->>FabricFrameView: set_world_poses(positions, orientations)
FabricFrameView->>FabricSelection: PrepareForReuse()
FabricSelection-->>FabricFrameView: topology_changed (bool)
alt topology changed
FabricFrameView->>FabricFrameView: _rebuild_fabric_arrays()
end
FabricSelection-->>Renderer: [dirty signal — next frame picks up Fabric data]
FabricFrameView->>WarpKernel: compose_fabric_transformation_matrix(...)
WarpKernel->>FabricSelection: write worldMatrix
FabricFrameView->>FabricFrameView: _fabric_hierarchy.update_world_xforms()
Caller->>FabricFrameView: get_world_poses()
FabricFrameView->>FabricSelection: PrepareForReuse()
FabricSelection-->>FabricFrameView: topology_changed (bool)
FabricFrameView->>WarpKernel: decompose_fabric_transformation_matrix(...)
WarpKernel-->>FabricFrameView: positions_wp, orientations_wp
FabricFrameView-->>Caller: (positions_wp, orientations_wp)
Reviews (1): Last reviewed commit: "fix: add PrepareForReuse to FabricFrameV..." | Re-trigger Greptile |
| if not self._fabric_usd_sync_done: | ||
| self._sync_fabric_from_usd_once() | ||
|
|
||
| self._prepare_for_reuse() |
There was a problem hiding this comment.
PrepareForReuse called on pure reads
_prepare_for_reuse() is invoked inside both get_world_poses() (line 190) and get_scales() (line 282), operations that do not modify Fabric data. According to the PR description, PrepareForReuse() "tells the renderer that Fabric data is about to be modified" — signalling a modification on every read may cause the renderer to unnecessarily dirty its render state and force re-renders even when no transform has changed.
The topology-change-detection benefit (return value handling) is legitimate for reads, but the renderer-notification side effect is not needed here. Consider separating the two concerns: call PrepareForReuse() only in write paths for renderer notification, and poll a separate topology-change check in read paths, or at minimum document why the dirty signal on reads is acceptable.
| 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
_rebuild_fabric_arrays() recreates _view_to_fabric, _fabric_to_view, and _fabric_world_matrices, but leaves _fabric_positions_buf, _fabric_orientations_buf, _fabric_scales_buf, _fabric_dummy_buffer, and _default_view_indices pointing to buffers sized at the original self.count. Since self.count is derived from _usd_view.count (fixed by the USD prim-path pattern) and Fabric topology changes only rearrange internal memory without adding/removing the tracked prims, the sizes stay correct in practice — but this invariant is not documented. A brief comment (or an assertion assert self.count == self._default_view_indices.shape[0]) would make the assumption explicit and guard against future changes that could break the rebuild path.
|
|
||
| # ------------------------------------------------------------------ | ||
| # Camera pose → render validation (PrepareForReuse / Fabric path) | ||
| # ------------------------------------------------------------------ | ||
|
|
||
|
|
||
| @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"), | ||
| ], | ||
| ) | ||
| def test_camera_pose_update_reflected_in_render(setup_camera, device, camera_cls): | ||
| """Camera pose changes via FrameView should be visible in rendered depth. | ||
|
|
There was a problem hiding this comment.
Missing
@pytest.mark.isaacsim_ci marker
Every other test in this file has @pytest.mark.isaacsim_ci, but test_camera_pose_update_reflected_in_render does not. If CI filters on that marker, the render-validation test — the primary integration guard for the PrepareForReuse path — will be silently skipped in CI runs. If this is intentional (e.g., it requires a full RTX renderer not available in CI), a comment explaining the omission would prevent future confusion.
Code Coverage —
|
Updated Code Coverage —
|
Compute local poses from Fabric world matrices instead of falling back to stale USD data. This fixes the inconsistency where set_world_poses() modified Fabric worldMatrix but get_local_poses() still read from USD, returning stale values (Issue isaac-sim#5). How it works: - get_local_poses: reads child world pose from Fabric, parent world pose from USD, computes local = inv(parent) * child - set_local_poses: reads parent world from USD, computes child world = parent * local, writes to Fabric via set_world_poses Added quaternion math helpers (_quat_mul, _quat_conjugate, _quat_rotate) for the parent/child transform composition. Test changes: - Remove xfail from test_set_world_updates_local (now passes) Addresses Piotr's Issue isaac-sim#5 (localMatrix). Depends on: fix/fabric-prepare-for-reuse (PR isaac-sim#5380)
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)
73f83d7 to
6653e10
Compare
Compute local poses from Fabric world matrices instead of falling back to stale USD data. This fixes the inconsistency where set_world_poses() modified Fabric worldMatrix but get_local_poses() still read from USD, returning stale values (Issue isaac-sim#5). How it works: - get_local_poses: reads child world pose from Fabric, parent world pose from USD, computes local = inv(parent) * child - set_local_poses: reads parent world from USD, computes child world = parent * local, writes to Fabric via set_world_poses Added quaternion math helpers (_quat_mul, _quat_conjugate, _quat_rotate) for the parent/child transform composition. Test changes: - Remove xfail from test_set_world_updates_local (now passes) Addresses Piotr's Issue isaac-sim#5 (localMatrix). Depends on: fix/fabric-prepare-for-reuse (PR isaac-sim#5380)
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)
6653e10 to
c14b74e
Compare
Compute local poses from Fabric world matrices instead of falling back to stale USD data. This fixes the inconsistency where set_world_poses() modified Fabric worldMatrix but get_local_poses() still read from USD, returning stale values (Issue isaac-sim#5). How it works: - get_local_poses: reads child world pose from Fabric, parent world pose from USD, computes local = inv(parent) * child - set_local_poses: reads parent world from USD, computes child world = parent * local, writes to Fabric via set_world_poses Added quaternion math helpers (_quat_mul, _quat_conjugate, _quat_rotate) for the parent/child transform composition. Test changes: - Remove xfail from test_set_world_updates_local (now passes) Addresses Piotr's Issue isaac-sim#5 (localMatrix). Depends on: fix/fabric-prepare-for-reuse (PR isaac-sim#5380)
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)
c14b74e to
49c51f7
Compare
Compute local poses from Fabric world matrices instead of falling back to stale USD data. This fixes the inconsistency where set_world_poses() modified Fabric worldMatrix but get_local_poses() still read from USD, returning stale values (Issue isaac-sim#5). How it works: - get_local_poses: reads child world pose from Fabric, parent world pose from USD, computes local = inv(parent) * child - set_local_poses: reads parent world from USD, computes child world = parent * local, writes to Fabric via set_world_poses Added quaternion math helpers (_quat_mul, _quat_conjugate, _quat_rotate) for the parent/child transform composition. Test changes: - Remove xfail from test_set_world_updates_local (now passes) Addresses Piotr's Issue isaac-sim#5 (localMatrix). Depends on: fix/fabric-prepare-for-reuse (PR isaac-sim#5380)
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)
…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).
49c51f7 to
caa1949
Compare
Compute local poses from Fabric world matrices instead of falling back to stale USD data. This fixes the inconsistency where set_world_poses() modified Fabric worldMatrix but get_local_poses() still read from USD, returning stale values (Issue isaac-sim#5). How it works: - get_local_poses: reads child world pose from Fabric, parent world pose from USD, computes local = inv(parent) * child - set_local_poses: reads parent world from USD, computes child world = parent * local, writes to Fabric via set_world_poses Added quaternion math helpers (_quat_mul, _quat_conjugate, _quat_rotate) for the parent/child transform composition. Test changes: - Remove xfail from test_set_world_updates_local (now passes) Addresses Piotr's Issue isaac-sim#5 (localMatrix). Depends on: fix/fabric-prepare-for-reuse (PR isaac-sim#5380)
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)
Summary
Replace the
sync_usd_on_fabric_writeworkaround inFabricFrameViewwith properPrepareForReuse()calls on the FabricPrimSelection. This tells the renderer (FSD/Storm) that Fabric data is about to be modified, so the next rendered frame reflects updated transforms — eliminating the need to copy Fabric writes back to USD.Motivation
The existing
sync_usd_on_fabric_writeflag worked by mirroring every Fabric write back to USD, which defeated the performance benefits of Fabric. With Kit 110.1.1 (build 300709+),PrepareForReuse()properly notifies the rendering pipeline of Fabric data changes, including camera transforms.This addresses two of the issues raised in Piotr Barejko's review of PR #4923:
PrepareForReuse()instead of USD writebackChanges
Core (FabricFrameView)
_prepare_for_reuse()before every Fabric read/write to notify the renderersync_usd_on_fabric_writeparameter (accepted via**kwargswith deprecation warning for backward compat)_rebuild_fabric_arrays()for topology change recovery whenPrepareForReuse()returns TrueCamera
sync_usd_on_fabric_write=Truefrom FrameView construction in camera.pyHeadless rendering
omni.kit.viewport.windowandomni.kit.hydra_texturetoisaaclab.python.headless.rendering.kit(required for camera render tests, also fixes fix: add missing viewport.window + hydra_texture deps for headless rendering #5375)Tests Added
test_camera_pose_update_reflected_in_rendertest_fabric_set_world_does_not_write_back_to_usdtest_prepare_for_reuse_detects_topology_changetest_set_world_updates_local(xfail)Test Results
Dependencies
Requires Kit 110.1.1 build 300709+ (with PrepareForReuse camera fix).