Skip to content

Commit 86f851d

Browse files
committed
Accelerate Newton Fabric transform sync
1 parent c9dca35 commit 86f851d

2 files changed

Lines changed: 77 additions & 100 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fixed
2+
^^^^^
3+
4+
* Accelerated Newton-to-Isaac RTX transform synchronization by reusing Fabric bindings and GPU hierarchy propagation.

source/isaaclab_newton/isaaclab_newton/physics/newton_manager.py

Lines changed: 73 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -164,12 +164,13 @@ def _capture_fabric_scales(
164164

165165
@wp.kernel(enable_backward=False)
166166
def _set_fabric_transforms(
167-
fabric_transforms: wp.fabricarray(dtype=wp.mat44d),
167+
fabric_world_transforms: wp.fabricarray(dtype=wp.mat44d),
168+
fabric_local_transforms: wp.fabricarray(dtype=wp.mat44d),
168169
newton_indices: wp.fabricarray(dtype=wp.uint32),
169170
newton_body_q: wp.array(ndim=1, dtype=wp.transformf),
170171
body_scales: wp.array(dtype=wp.vec3f),
171172
):
172-
"""Write Newton body poses to Fabric world matrices with their initialized scale.
173+
"""Write Newton body poses to Fabric local matrices with their initialized scale.
173174
174175
For each Fabric prim at thread ``i``, reads the Newton body transform at
175176
``newton_body_q[newton_indices[i]]`` and combines its translation and rotation
@@ -181,15 +182,12 @@ def _set_fabric_transforms(
181182
idx = int(newton_indices[i])
182183
transform = newton_body_q[idx]
183184
scale = body_scales[idx]
184-
fabric_transforms[i] = wp.mat44d(
185-
wp.transpose(
186-
wp.transform_compose(
187-
wp.transform_get_translation(transform),
188-
wp.transform_get_rotation(transform),
189-
scale,
190-
)
185+
world_transform = wp.transpose(
186+
wp.mat44d(
187+
wp.transform_compose(wp.transform_get_translation(transform), wp.transform_get_rotation(transform), scale)
191188
)
192189
)
190+
fabric_local_transforms[i] = world_transform * wp.inverse(fabric_world_transforms[i]) * fabric_local_transforms[i]
193191

194192

195193
@wp.kernel(enable_backward=False)
@@ -488,9 +486,13 @@ class NewtonManager(PhysicsManager):
488486
# USD/Fabric sync
489487
_newton_stage_path = None
490488
_usdrt_stage = None
489+
_fabric_hierarchy = None
491490
_newton_index_attr = "newton:index"
492-
# Body-indexed world scales captured before Newton first overwrites Fabric transforms.
491+
# Body-indexed world scales captured before Newton first updates Fabric transforms.
493492
_fabric_body_scales: wp.array | None = None
493+
# Persistent query and arrays: selection, world matrix, local matrix, and Newton index.
494+
_fabric_body_sync: tuple[Any, Any, Any, Any] | None = None
495+
_fabric_body_binding_count: int = 0
494496
_clone_physics_only = False
495497
_transforms_dirty: bool = False
496498
_transforms_may_change_on_graph_replay: bool = False
@@ -504,9 +506,6 @@ class NewtonManager(PhysicsManager):
504506
_newton_particle_count_attr = "newton:particleCount"
505507
_particle_visual_prims: dict[str, _ParticleVisualPrim] = {}
506508

507-
# Cached after the first fabric sync that probes IFabricHierarchy GPU APIs.
508-
_use_fabric_gpu_hierarchy: bool | None = None
509-
510509
# Set to True after sync_transforms_to_usd() successfully writes body positions for
511510
# the first time in each simulation session. Reset to False in clear(). Polled by
512511
# test drain helpers to know when the GPU has propagated the newton:index Fabric
@@ -687,15 +686,8 @@ def sync_transforms_to_usd(cls) -> None:
687686
Uses ``wp.fabricarray`` directly (no ``isaacsim.physics.newton`` extension needed).
688687
On the first successful sync, a Warp kernel captures each initialized Fabric
689688
world scale by Newton body index. The pose kernel then combines that scale with
690-
``state_0.body_q[newton_index[i]]`` and writes the corresponding ``mat44d`` to
691-
``omni:fabric:worldMatrix`` for each prim.
692-
693-
When ``IFabricHierarchy.update_world_xforms_gpu_with_options`` is
694-
available the method mirrors PhysX's ``DirectGpuHelper`` pattern: pause
695-
Fabric change tracking, write transforms, resume tracking, then run the
696-
GPU hierarchy update with ``RIGID_BODY | FORCE_UPDATE`` so Newton-authored
697-
world matrices stay authoritative on rigid-body prims. Otherwise it
698-
falls back to the CPU ``update_world_xforms()`` path.
689+
``state_0.body_q[newton_index[i]]``, derives the corresponding local matrix,
690+
and lets Fabric propagate the hierarchy on the GPU.
699691
"""
700692
if cls._usdrt_stage is None or cls._model is None or cls._state_0 is None:
701693
return
@@ -704,95 +696,73 @@ def sync_transforms_to_usd(cls) -> None:
704696
try:
705697
import usdrt
706698

707-
fabric_hierarchy = None
708-
gpu_opts_cls = None
709-
if hasattr(usdrt, "hierarchy"):
710-
fabric_hierarchy = usdrt.hierarchy.IFabricHierarchy().get_fabric_hierarchy(
711-
cls._usdrt_stage.GetFabricId(), cls._usdrt_stage.GetStageIdAsStageId()
712-
)
713-
gpu_opts_cls = getattr(usdrt.hierarchy, "FabricHierarchyGpuUpdateOptions", None)
714-
715-
if cls._use_fabric_gpu_hierarchy is None and hasattr(usdrt, "hierarchy"):
716-
# Probe the pybind class once so a transient null hierarchy handle does
717-
# not permanently disable the GPU path for the session.
718-
NewtonManager._use_fabric_gpu_hierarchy = gpu_opts_cls is not None and hasattr(
719-
usdrt.hierarchy.IFabricHierarchy, "update_world_xforms_gpu_with_options"
720-
)
721-
if cls._use_fabric_gpu_hierarchy:
722-
logger.info("Fabric GPU transform hierarchy enabled via IFabricHierarchy")
723-
else:
724-
logger.info("Fabric GPU transform hierarchy unavailable; falling back to update_world_xforms()")
725-
726-
use_gpu_hierarchy = bool(
727-
cls._use_fabric_gpu_hierarchy and fabric_hierarchy is not None and gpu_opts_cls is not None
728-
)
729-
730-
# Pause hierarchy change tracking BEFORE SelectPrims.
731-
# SelectPrims with ReadWrite access calls getAttributeArrayGpu
732-
# internally, which marks Fabric buffers dirty. If tracking is
733-
# still active at that point the hierarchy records the change and
734-
# Kit's updateWorldXforms will do an expensive connectivity
735-
# rebuild every frame. PhysX avoids this via ScopedUSDRT which
736-
# pauses tracking before any Fabric writes.
737-
if use_gpu_hierarchy:
738-
fabric_hierarchy.track_world_xform_changes(False)
739-
fabric_hierarchy.track_local_xform_changes(False)
740-
741-
try:
699+
topology_changed = False
700+
fabric_sync = cls._fabric_body_sync
701+
if fabric_sync is None:
742702
selection = cls._usdrt_stage.SelectPrims(
743703
require_attrs=[
744-
(usdrt.Sdf.ValueTypeNames.Matrix4d, "omni:fabric:worldMatrix", usdrt.Usd.Access.ReadWrite),
704+
(usdrt.Sdf.ValueTypeNames.Matrix4d, "omni:fabric:worldMatrix", usdrt.Usd.Access.Read),
705+
(
706+
usdrt.Sdf.ValueTypeNames.Matrix4d,
707+
"omni:fabric:localMatrix",
708+
usdrt.Usd.Access.ReadWrite,
709+
),
745710
(usdrt.Sdf.ValueTypeNames.UInt, cls._newton_index_attr, usdrt.Usd.Access.Read),
746711
],
747712
device=str(PhysicsManager._device),
748713
)
749-
if selection.GetCount() == 0:
750-
# The newton:index attribute is written CPU-side by start_simulation() but
751-
# GPU propagation is deferred. Keep _transforms_dirty=True so the next
752-
# pre_render() retries once initialize_solver() has completed (FK delegate
753-
# bound) and body_q holds valid values.
754-
if cls._eval_fk is _eval_fk_unbound:
714+
else:
715+
selection, fabric_world_transforms, fabric_local_transforms, newton_indices = fabric_sync
716+
topology_changed = selection.PrepareForReuse()
717+
718+
if fabric_sync is None or topology_changed:
719+
if selection.GetCount() != cls._fabric_body_binding_count:
720+
NewtonManager._fabric_body_sync = None
721+
# CPU-authored newton:index attributes may not have reached Fabric yet.
722+
if selection.GetCount() == 0 and cls._eval_fk is _eval_fk_unbound:
755723
NewtonManager._transforms_dirty = False
756724
return
757-
758-
fabric_transforms = wp.fabricarray(selection, "omni:fabric:worldMatrix")
725+
fabric_world_transforms = wp.fabricarray(selection, "omni:fabric:worldMatrix")
726+
fabric_local_transforms = wp.fabricarray(selection, "omni:fabric:localMatrix")
759727
newton_indices = wp.fabricarray(selection, cls._newton_index_attr)
760-
if cls._fabric_body_scales is None:
761-
NewtonManager._fabric_body_scales = wp.empty(
762-
cls._model.body_count,
763-
dtype=wp.vec3f,
764-
device=PhysicsManager._device,
765-
)
766-
wp.launch(
767-
_capture_fabric_scales,
768-
dim=newton_indices.shape[0],
769-
inputs=[fabric_transforms, newton_indices, cls._fabric_body_scales],
770-
device=PhysicsManager._device,
771-
)
728+
NewtonManager._fabric_body_sync = (
729+
selection,
730+
fabric_world_transforms,
731+
fabric_local_transforms,
732+
newton_indices,
733+
)
734+
if cls._fabric_body_scales is None:
735+
NewtonManager._fabric_body_scales = wp.empty(
736+
cls._model.body_count,
737+
dtype=wp.vec3f,
738+
device=PhysicsManager._device,
739+
)
772740
wp.launch(
773-
_set_fabric_transforms,
741+
_capture_fabric_scales,
774742
dim=newton_indices.shape[0],
775-
inputs=[fabric_transforms, newton_indices, cls._state_0.body_q, cls._fabric_body_scales],
743+
inputs=[fabric_world_transforms, newton_indices, cls._fabric_body_scales],
776744
device=PhysicsManager._device,
777745
)
778-
wp.synchronize_device(PhysicsManager._device)
746+
wp.launch(
747+
_set_fabric_transforms,
748+
dim=newton_indices.shape[0],
749+
inputs=[
750+
fabric_world_transforms,
751+
fabric_local_transforms,
752+
newton_indices,
753+
cls._state_0.body_q,
754+
cls._fabric_body_scales,
755+
],
756+
device=PhysicsManager._device,
757+
)
779758

780-
NewtonManager._newton_fabric_ready = True
781-
NewtonManager._transforms_dirty = False
759+
wp.synchronize_stream(PhysicsManager._device)
760+
if not cls._fabric_hierarchy.update_world_xforms_gpu(not topology_changed):
761+
raise RuntimeError("Fabric GPU hierarchy propagation failed.")
762+
wp.synchronize_device(PhysicsManager._device)
782763

783-
if use_gpu_hierarchy:
784-
# RIGID_BODY: inverse-propagate on PhysicsRigidBodyAPI buckets
785-
# (keep Newton world matrices, derive local). FORCE_UPDATE:
786-
# bypass the change-listener dirty check after tracking pause.
787-
fabric_hierarchy.update_world_xforms_gpu_with_options(
788-
gpu_opts_cls.RIGID_BODY | gpu_opts_cls.FORCE_UPDATE
789-
)
790-
elif fabric_hierarchy is not None:
791-
fabric_hierarchy.update_world_xforms()
792-
finally:
793-
if use_gpu_hierarchy:
794-
fabric_hierarchy.track_world_xform_changes(True)
795-
fabric_hierarchy.track_local_xform_changes(True)
764+
NewtonManager._newton_fabric_ready = True
765+
NewtonManager._transforms_dirty = False
796766
except Exception:
797767
logger.exception("[NewtonManager] sync_transforms_to_usd FAILED")
798768

@@ -1123,7 +1093,6 @@ def clear(cls):
11231093
NewtonManager._visualization_stop_callback = None
11241094
if callback is not None:
11251095
callback.deregister()
1126-
NewtonManager._use_fabric_gpu_hierarchy = None
11271096
NewtonManager._newton_fabric_ready = False
11281097
NewtonManager._builder = None
11291098
NewtonManager._model = None
@@ -1168,7 +1137,10 @@ def clear(cls):
11681137
NewtonManager._sensor_bvh_shape_flags = ShapeFlags.VISIBLE
11691138
NewtonManager._newton_stage_path = None
11701139
NewtonManager._usdrt_stage = None
1140+
NewtonManager._fabric_hierarchy = None
11711141
NewtonManager._fabric_body_scales = None
1142+
NewtonManager._fabric_body_sync = None
1143+
NewtonManager._fabric_body_binding_count = 0
11721144
NewtonManager._transforms_dirty = False
11731145
NewtonManager._transforms_may_change_on_graph_replay = False
11741146
NewtonManager._particles_dirty = False
@@ -1656,10 +1628,13 @@ def start_simulation(cls) -> None:
16561628
if body_bindings is None:
16571629
# Non-replicated Newton stages do not pass through NewtonReplicateContext.
16581630
body_bindings = [(body_path, i) for i, body_path in enumerate(body_paths)]
1631+
NewtonManager._fabric_body_sync = None
1632+
NewtonManager._fabric_body_binding_count = len(body_bindings)
16591633

16601634
fabric_hierarchy = usdrt.hierarchy.IFabricHierarchy().get_fabric_hierarchy(
16611635
cls._usdrt_stage.GetFabricId(), cls._usdrt_stage.GetStageIdAsStageId()
16621636
)
1637+
NewtonManager._fabric_hierarchy = fabric_hierarchy
16631638

16641639
NewtonManager._initialize_fabric_body_prims(cls._usdrt_stage, fabric_hierarchy, usdrt, body_bindings)
16651640
NewtonManager._initialize_fabric_cable_prims(cls._usdrt_stage, fabric_hierarchy, usdrt)
@@ -1690,9 +1665,7 @@ def _initialize_fabric_body_prims(stage, fabric_hierarchy, usdrt, body_bindings:
16901665

16911666
prim.CreateAttribute(NewtonManager._newton_index_attr, usdrt.Sdf.ValueTypeNames.UInt, custom=True)
16921667
prim.GetAttribute(NewtonManager._newton_index_attr).Set(body_index)
1693-
# Tag with PhysicsRigidBodyAPI so FabricHierarchyGpuUpdateOptions.RIGID_BODY
1694-
# applies Inverse propagation (preserves Newton's world transforms and derives
1695-
# local) instead of Forward.
1668+
# Generated Xforms need the same rigid-body identity as authored body prims.
16961669
prim.AddAppliedSchema("PhysicsRigidBodyAPI")
16971670

16981671
fabric_hierarchy.update_world_xforms()

0 commit comments

Comments
 (0)