@@ -395,7 +395,11 @@ def write_output(
395395 output_name : str ,
396396 output_data : torch .Tensor ,
397397 ) -> None :
398- """Copy from render_data warp buffer to output tensor."""
398+ """Copy from render_data warp buffer to output tensor.
399+
400+ Triggers deferred tile extraction on first access after render().
401+ """
402+ self ._ensure_extraction (render_data )
399403 if output_name not in render_data .warp_buffers :
400404 return
401405 src = render_data .warp_buffers [output_name ]
@@ -515,7 +519,12 @@ def _process_render_frame(self, render_data: OVRTXRenderData, frame, output_buff
515519 )
516520
517521 def render (self , render_data : OVRTXRenderData ) -> None :
518- """Render the scene into the provided RenderData."""
522+ """Render the scene and defer tile extraction until buffers are needed.
523+
524+ Instead of immediately extracting tiles (which stalls on rendering completion),
525+ we store the render products and defer extraction to write_output(). This allows
526+ training/physics work to overlap with rendering on the GPU.
527+ """
519528 if not self ._initialized_scene :
520529 raise RuntimeError ("Scene not initialized. Call initialize() first." )
521530 if self ._renderer is None or len (self ._render_product_paths ) == 0 :
@@ -527,13 +536,30 @@ def render(self, render_data: OVRTXRenderData) -> None:
527536 )
528537 product_path = self ._render_product_paths [0 ]
529538 if product_path in products and len (products [product_path ].frames ) > 0 :
530- self ._process_render_frame (
531- render_data ,
532- products [product_path ].frames [0 ],
533- render_data .warp_buffers ,
534- )
539+ # Store frame for deferred extraction instead of extracting immediately
540+ render_data ._pending_frame = products [product_path ].frames [0 ]
541+ render_data ._pending_products = products
542+ render_data ._extraction_done = False
543+ else :
544+ render_data ._pending_frame = None
545+ render_data ._pending_products = None
546+ render_data ._extraction_done = True
535547 except Exception as e :
536548 logger .warning ("OVRTX rendering failed: %s" , e , exc_info = True )
549+ render_data ._pending_frame = None
550+ render_data ._pending_products = None
551+ render_data ._extraction_done = True
552+
553+ def _ensure_extraction (self , render_data : OVRTXRenderData ) -> None :
554+ """Extract tiles from the pending frame if not already done."""
555+ if getattr (render_data , '_extraction_done' , True ):
556+ return
557+ frame = getattr (render_data , '_pending_frame' , None )
558+ if frame is not None :
559+ self ._process_render_frame (render_data , frame , render_data .warp_buffers )
560+ render_data ._extraction_done = True
561+ render_data ._pending_frame = None
562+ render_data ._pending_products = None
537563
538564 def cleanup (self , render_data : OVRTXRenderData | None ) -> None :
539565 """Release renderer resources. See :meth:`~isaaclab.renderers.base_renderer.BaseRenderer.cleanup`."""
0 commit comments