Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Fixed
^^^^^

* Fixed OVRTX object and camera transform updates to write a caller-owned GPU buffer instead of mapping and unmapping OVRTX memory every frame.
52 changes: 37 additions & 15 deletions source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@
decode_stable_id_map,
decode_stable_id_semantic_id_map,
)
from .ovrtx_mapping import map_attribute_for_warp_writes
from .ovrtx_renderer_cfg import OVRTXRendererCfg
from .ovrtx_renderer_kernels import (
compute_cable_points_world_kernel,
Expand Down Expand Up @@ -422,8 +421,16 @@ def _create_object_scale_array(self, object_paths: list[str]) -> wp.array:
return wp.array(scales, dtype=wp.vec3f, device=self._device)

def _init_fields_legacy(self) -> None:
"""Initialize the legacy-path instance fields.

Counterpart to :meth:`_init_fields_ovstage`. Only fields the ovstage path never touches live
here: the ``bind_attribute``/``bind_array_attribute`` handles and the caller-owned object
transform buffer. State shared by both paths (``_object_newton_indices``, the particle
offset/count lists) stays in :meth:`__init__`.
"""
self._camera_xform_binding = None
self._object_xform_binding = None
self._object_transform_buffer: wp.array | None = None
self._deformable_points_binding = None
self._particle_points_binding = None
self._particle_workaround_applied = False
Expand Down Expand Up @@ -563,6 +570,7 @@ def _setup_xform_bindings_legacy(self):
raise RuntimeError("Failed to create OVRTX object bindings")
self._object_newton_indices = wp.array(newton_indices, dtype=wp.int32, device=self._device)
self._object_scales = self._create_object_scale_array(object_paths)
self._object_transform_buffer = wp.zeros(len(newton_indices), dtype=wp.mat44d, device=self._device)

def _setup_deformable_bindings_legacy(self, num_envs: int):
try:
Expand Down Expand Up @@ -719,7 +727,13 @@ def set_outputs(self, render_data: OVRTXRenderData, output_data: dict[str, Proxy
)

def _update_transforms_legacy(self) -> None:
if self._object_xform_binding is None or self._object_newton_indices is None or self._object_scales is None:
"""Sync transforms to OVRTX."""
if (
self._object_xform_binding is None
or self._object_newton_indices is None
or self._object_scales is None
or self._object_transform_buffer is None
):
return
from isaaclab_newton.physics import NewtonManager

Expand All @@ -729,15 +743,21 @@ def _update_transforms_legacy(self) -> None:
body_q = getattr(newton_state, "body_q", None)
if body_q is None:
return
with map_attribute_for_warp_writes(
self._object_xform_binding, self._warp_device, wp.mat44d
) as ovrtx_transforms:
wp.launch(
kernel=sync_newton_transforms_kernel,
dim=len(self._object_newton_indices),
inputs=[ovrtx_transforms, self._object_newton_indices, body_q, self._object_scales],
device=self._device,
)

wp.launch(
kernel=sync_newton_transforms_kernel,
dim=len(self._object_newton_indices),
inputs=[self._object_transform_buffer, self._object_newton_indices, body_q, self._object_scales],
device=self._device,
)
# Blocking ``write()`` so the buffer stays valid until OVRTX finishes reading it.
# ``DataAccess.ASYNC`` + the Warp CUDA stream let OVRTX read in place and wait
# on-GPU for the kernel; ``SYNC`` is rejected for GPU buffers.
self._object_xform_binding.write(
self._object_transform_buffer,
data_access=DataAccess.ASYNC,
cuda_stream=self._warp_device.stream.cuda_stream,
)

def _update_geometries_legacy(self) -> None:
if self._deformable_points_binding is not None:
Expand Down Expand Up @@ -801,10 +821,11 @@ def _update_camera_legacy(
device=self._device,
)
if self._camera_xform_binding is not None:
with map_attribute_for_warp_writes(
self._camera_xform_binding, self._warp_device, wp.mat44d
) as transforms_view:
wp.copy(transforms_view, camera_transforms)
self._camera_xform_binding.write(
camera_transforms,
data_access=DataAccess.ASYNC,
cuda_stream=self._warp_device.stream.cuda_stream,
)

def read_output(self, render_data: OVRTXRenderData, camera_data: CameraData) -> None:
assert camera_data.info is not None, "CameraData.info should be created in CameraData.allocate"
Expand Down Expand Up @@ -1063,6 +1084,7 @@ def _safe_unbind(binding, name: str) -> None:
):
_safe_unbind(getattr(self, attr), name)
setattr(self, attr, None)
self._object_transform_buffer = None
self._deformable_particle_offsets = []
self._deformable_particle_counts = []
self._particle_visual_offsets = []
Expand Down
53 changes: 53 additions & 0 deletions source/isaaclab_ov/test/test_ovrtx_deformable_bindings.py
Original file line number Diff line number Diff line change
Expand Up @@ -590,3 +590,56 @@ def _write(query, attribute, **kwargs):
assert tensors[0].data == particle_q[1:4].ptr
assert tensors[0].shape_tuple == (3,)
assert tensors[0].dtype.lanes == 3


def test_update_transforms_writes_caller_owned_buffer(monkeypatch: pytest.MonkeyPatch):
"""Object xforms fill a persistent GPU buffer and blocking ASYNC write, not map/unmap."""
renderer, _ = _make_renderer_without_backend()
buffer = object()
renderer._object_xform_binding = _FakePointsBinding("omni:xform")
renderer._object_newton_indices = [0, 1]
renderer._object_scales = object()
renderer._object_transform_buffer = buffer

monkeypatch.setattr(NewtonManager, "get_state", classmethod(lambda cls: SimpleNamespace(body_q=object())))
launch_kwargs: dict = {}

def _capture_launch(*args, **kwargs):
launch_kwargs.update(kwargs)

monkeypatch.setattr(ovrtx_renderer_module.wp, "launch", _capture_launch)
renderer._warp_device = SimpleNamespace(stream=SimpleNamespace(cuda_stream=99))

renderer.update_transforms()

assert launch_kwargs["inputs"][0] is buffer
assert launch_kwargs["dim"] == 2
assert renderer._object_xform_binding.written is buffer
assert renderer._object_xform_binding.write_kwargs["data_access"] is DataAccess.ASYNC
assert renderer._object_xform_binding.write_kwargs["cuda_stream"] == 99


def test_update_camera_writes_without_mapping(monkeypatch: pytest.MonkeyPatch):
"""Camera xforms are handed to ``write()`` instead of copied into a mapped OVRTX buffer."""
renderer, _ = _make_renderer_without_backend()
renderer._camera_xform_binding = _FakePointsBinding("omni:xform")
camera_transforms = []

monkeypatch.setattr(ovrtx_renderer_module, "convert_camera_frame_orientation_convention_wp", lambda **kwargs: None)
monkeypatch.setattr(ovrtx_renderer_module.wp, "empty", lambda *args, **kwargs: object())

def _fake_zeros(*args, **kwargs):
arr = object()
camera_transforms.append(arr)
return arr

monkeypatch.setattr(ovrtx_renderer_module.wp, "zeros", _fake_zeros)
monkeypatch.setattr(ovrtx_renderer_module.wp, "launch", lambda *args, **kwargs: None)
renderer._warp_device = SimpleNamespace(stream=SimpleNamespace(cuda_stream=7))

positions = SimpleNamespace(shape=(2,), warp=object())
renderer.update_camera(object(), positions, SimpleNamespace(warp=object()), object())

assert renderer._camera_xform_binding.written is camera_transforms[0]
assert renderer._camera_xform_binding.write_kwargs["data_access"] is DataAccess.ASYNC
assert renderer._camera_xform_binding.write_kwargs["cuda_stream"] == 7
1 change: 1 addition & 0 deletions source/isaaclab_ov/test/test_ovrtx_renderer_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,7 @@ def test_ovrtx_close_releases_legacy_renderer_state():
]
assert renderer._camera_xform_binding is None
assert renderer._object_xform_binding is None
assert renderer._object_transform_buffer is None
assert renderer._deformable_points_binding is None
assert renderer._particle_points_binding is None
assert renderer._cable_points_binding is None
Expand Down
Loading