@@ -477,9 +477,8 @@ class NewtonManager(PhysicsManager):
477477
478478 # Newton scene-query scheduling and graph execution.
479479 _sensor_tasks : dict [str , Callable [[], None ]] = {}
480- _sensor_graph : wp .Graph | None = None
481- _sensor_flags : wp .array | None = None
482- _sensor_flags_host : np .ndarray | None = None
480+ _sensor_refit_graph : wp .Graph | None = None
481+ _sensor_task_graphs : dict [str , wp .Graph ] = {}
483482 _sensor_state : State | None = None
484483 _sensor_state_dirty : bool = True
485484 _sensor_graph_capture_failed : bool = False
@@ -2721,26 +2720,21 @@ def _update_sensor_tasks(cls, *names: str) -> None:
27212720 cls ._invalidate_sensor_graph ()
27222721 cfg = PhysicsManager ._cfg
27232722 use_cuda_graph = bool (getattr (cfg , "use_cuda_graph" , False )) and "cuda" in str (PhysicsManager ._device )
2724- if use_cuda_graph and cls ._sensor_graph is None and not cls ._sensor_graph_capture_failed :
2723+ if use_cuda_graph and cls ._sensor_refit_graph is None and not cls ._sensor_graph_capture_failed :
27252724 cls ._capture_sensor_graph ()
2726- if cls ._sensor_graph is None :
2725+ if cls ._sensor_refit_graph is None :
27272726 if cls ._sensor_state_dirty :
27282727 cls ._refit_sensor_bvh ()
27292728 cls ._sensor_state_dirty = False
27302729 for name in names :
27312730 cls ._sensor_tasks [name ]()
27322731 return
27332732
2734- assert cls ._sensor_flags_host is not None
2735- assert cls ._sensor_flags is not None
2736- cls ._sensor_flags_host .fill (0 )
2737- cls ._sensor_flags_host [0 ] = int (cls ._sensor_state_dirty )
2738- task_names = tuple (cls ._sensor_tasks )
2733+ if cls ._sensor_state_dirty :
2734+ wp .capture_launch (cls ._sensor_refit_graph )
2735+ cls ._sensor_state_dirty = False
27392736 for name in names :
2740- cls ._sensor_flags_host [1 + task_names .index (name )] = 1
2741- cls ._sensor_flags .assign (cls ._sensor_flags_host )
2742- wp .capture_launch (cls ._sensor_graph )
2743- cls ._sensor_state_dirty = False
2737+ wp .capture_launch (cls ._sensor_task_graphs [name ])
27442738
27452739 @classmethod
27462740 def _mark_sensor_state_dirty (cls ) -> None :
@@ -2783,47 +2777,59 @@ def _refit_sensor_bvh(cls) -> None:
27832777 @classmethod
27842778 def _invalidate_sensor_graph (cls ) -> None :
27852779 """Discard captured scene-query graph resources."""
2786- cls ._sensor_graph = None
2787- cls ._sensor_flags = None
2788- cls ._sensor_flags_host = None
2780+ cls ._sensor_refit_graph = None
2781+ cls ._sensor_task_graphs = {}
27892782 cls ._sensor_graph_capture_failed = False
27902783
27912784 @classmethod
27922785 def _capture_sensor_graph (cls ) -> None :
2793- """Capture BVH refit and scene-query tasks into a conditional graph."""
2794- with wp .ScopedDevice (PhysicsManager ._device ):
2786+ """Capture the BVH refit and each scene-query task into its own graph.
2787+
2788+ Each step gets a standalone top-level graph rather than a ``wp.capture_if``
2789+ conditional body of one shared graph: Warp rejects memory allocation inside a
2790+ conditional body, and ``wp.Mesh.refit`` allocates scratch on every call, so
2791+ deformable geometry in the tiled-camera render path would fail to capture.
2792+ """
2793+ device = PhysicsManager ._device
2794+ with wp .ScopedDevice (device ):
27952795 cls ._refit_sensor_bvh ()
27962796 for update_fn in cls ._sensor_tasks .values ():
27972797 update_fn ()
27982798
2799- cls ._sensor_flags = wp .zeros (1 + len (cls ._sensor_tasks ), dtype = wp .int32 , device = PhysicsManager ._device )
2800- cls ._sensor_flags_host = np .zeros (1 + len (cls ._sensor_tasks ), dtype = np .int32 )
2801- update_fns = tuple (cls ._sensor_tasks .values ())
2799+ refit_graph = cls ._capture_sensor_step (device , cls ._refit_sensor_bvh )
2800+ failed = None if refit_graph is not None else "bvh refit"
2801+ task_graphs : dict [str , wp .Graph ] = {}
2802+ if failed is None :
2803+ for name , update_fn in cls ._sensor_tasks .items ():
2804+ graph = cls ._capture_sensor_step (device , update_fn )
2805+ if graph is None :
2806+ failed = name
2807+ break
2808+ task_graphs [name ] = graph
2809+
2810+ if failed is not None :
2811+ cls ._invalidate_sensor_graph ()
2812+ # Latch after invalidating: _invalidate_sensor_graph() clears the flag.
2813+ cls ._sensor_graph_capture_failed = True
2814+ logger .warning ("Newton sensor graph capture failed for '%s'; falling back to eager execution." , failed )
2815+ return
28022816
2803- def pipeline () -> None :
2804- assert cls ._sensor_flags is not None
2805- wp .capture_if (cls ._sensor_flags [0 :1 ], cls ._refit_sensor_bvh )
2806- for index , update_fn in enumerate (update_fns ):
2807- wp .capture_if (cls ._sensor_flags [index + 1 : index + 2 ], update_fn )
2817+ cls ._sensor_refit_graph = refit_graph
2818+ cls ._sensor_task_graphs = task_graphs
2819+ logger .info ("Captured Newton sensor graphs for %d task(s)." , len (task_graphs ))
28082820
2809- device = PhysicsManager ._device
2821+ @classmethod
2822+ def _capture_sensor_step (cls , device : str , capture_target : Callable [[], None ]) -> wp .Graph | None :
2823+ """Capture one scene-query step into a standalone graph, or ``None`` on failure."""
28102824 if cls ._usdrt_stage is not None :
2811- cls ._sensor_graph = cls ._capture_relaxed_graph (device , capture_target = pipeline )
2812- else :
2813- try :
2814- with wp .ScopedCapture (device = device ) as capture :
2815- pipeline ()
2816- cls ._sensor_graph = capture .graph
2817- except Exception :
2818- logger .exception ("[NewtonManager] sensor CUDA graph capture failed" )
2819- cls ._sensor_graph = None
2820- if cls ._sensor_graph is None :
2821- cls ._sensor_flags = None
2822- cls ._sensor_flags_host = None
2823- cls ._sensor_graph_capture_failed = True
2824- logger .warning ("Newton sensor graph capture failed; falling back to eager execution." )
2825- else :
2826- logger .info ("Captured Newton sensor graph with %d task(s)." , len (cls ._sensor_tasks ))
2825+ return cls ._capture_relaxed_graph (device , capture_target = capture_target )
2826+ try :
2827+ with _paused_gc (), wp .ScopedCapture (device = device ) as capture :
2828+ capture_target ()
2829+ return capture .graph
2830+ except Exception :
2831+ logger .exception ("[NewtonManager] sensor CUDA graph capture failed" )
2832+ return None
28272833
28282834 @classmethod
28292835 def get_num_envs (cls ) -> int :
0 commit comments