@@ -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
@@ -1161,7 +1160,7 @@ def clear(cls):
11611160 NewtonManager ._graph = None
11621161 NewtonManager ._graph_capture_pending = False
11631162 NewtonManager ._sensor_tasks = {}
1164- NewtonManager ._invalidate_sensor_graph ()
1163+ NewtonManager ._invalidate_sensor_graphs ()
11651164 NewtonManager ._sensor_state = None
11661165 NewtonManager ._sensor_state_dirty = True
11671166 NewtonManager ._sensor_graph_capture_failed = False
@@ -2698,13 +2697,13 @@ def _register_sensor_task(cls, name: str, update_fn: Callable[[], None]) -> None
26982697 cls ._sensor_tasks [name ] = update_fn
26992698 cls ._sensor_state = state
27002699 cls ._sensor_state_dirty = True
2701- cls ._invalidate_sensor_graph ()
2700+ cls ._invalidate_sensor_graphs ()
27022701
27032702 @classmethod
27042703 def _unregister_sensor_task (cls , name : str ) -> None :
27052704 """Remove a scene-query task, ignoring unknown names."""
27062705 if cls ._sensor_tasks .pop (name , None ) is not None :
2707- cls ._invalidate_sensor_graph ()
2706+ cls ._invalidate_sensor_graphs ()
27082707
27092708 @classmethod
27102709 def _update_sensor_tasks (cls , * names : str ) -> None :
@@ -2718,29 +2717,24 @@ def _update_sensor_tasks(cls, *names: str) -> None:
27182717 if state is not cls ._sensor_state :
27192718 cls ._sensor_state = state
27202719 cls ._sensor_state_dirty = True
2721- cls ._invalidate_sensor_graph ()
2720+ cls ._invalidate_sensor_graphs ()
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 :
2725- cls ._capture_sensor_graph ()
2726- if cls ._sensor_graph is None :
2723+ if use_cuda_graph and cls ._sensor_refit_graph is None and not cls ._sensor_graph_capture_failed :
2724+ cls ._capture_sensor_graphs ()
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 :
@@ -2757,7 +2751,7 @@ def _mark_sensor_state_dirty(cls) -> None:
27572751 return
27582752 if NewtonManager ._state_0 is not NewtonManager ._sensor_state :
27592753 NewtonManager ._sensor_state = NewtonManager ._state_0
2760- NewtonManager ._invalidate_sensor_graph ()
2754+ NewtonManager ._invalidate_sensor_graphs ()
27612755 NewtonManager ._sensor_state_dirty = True
27622756
27632757 @classmethod
@@ -2781,49 +2775,61 @@ def _refit_sensor_bvh(cls) -> None:
27812775 cls ._model .bvh_refit_particles (cls ._sensor_state )
27822776
27832777 @classmethod
2784- def _invalidate_sensor_graph (cls ) -> None :
2785- """Discard captured scene-query graph resources."""
2786- cls ._sensor_graph = None
2787- cls ._sensor_flags = None
2788- cls ._sensor_flags_host = None
2778+ def _invalidate_sensor_graphs (cls ) -> None :
2779+ """Discard the captured scene-query graphs."""
2780+ cls ._sensor_refit_graph = None
2781+ cls ._sensor_task_graphs = {}
27892782 cls ._sensor_graph_capture_failed = False
27902783
27912784 @classmethod
2792- def _capture_sensor_graph (cls ) -> None :
2793- """Capture BVH refit and scene-query tasks into a conditional graph."""
2794- with wp .ScopedDevice (PhysicsManager ._device ):
2785+ def _capture_sensor_graphs (cls ) -> None :
2786+ """Capture the BVH refit and each scene-query task into its own graph.
2787+
2788+ Each one 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_graph (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_graph (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_graphs ()
2812+ # Latch after invalidating: _invalidate_sensor_graphs() 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_graph (cls , device : str , capture_target : Callable [[], None ]) -> wp .Graph | None :
2823+ """Capture one scene-query callable 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