Skip to content

Add opt-in async OVRTX rendering - #6484

Closed
pv-nvidia wants to merge 5 commits into
isaac-sim:developfrom
pv-nvidia:pv/ovrtx-async-slot-buffers
Closed

Add opt-in async OVRTX rendering#6484
pv-nvidia wants to merge 5 commits into
isaac-sim:developfrom
pv-nvidia:pv/ovrtx-async-slot-buffers

Conversation

@pv-nvidia

@pv-nvidia pv-nvidia commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Description

Adds an opt-in asynchronous render path to the OVRTX renderer, so rendering can overlap with simulation and Python work. It is off by default (async_rendering=False), leaving existing behavior unchanged.

What this adds

A single on/off switch, OVRTXRendererCfg.async_rendering (default False):

  • False: synchronous rendering.
  • True: asynchronous rendering. Each render() submits the render and returns immediately, so rendering overlaps simulation and Python work; camera outputs arrive one frame later. The first frame after (re)initialization is consumed synchronously so the first camera read returns a valid frame instead of the zero-initialized buffer.

Internally the renderer delegates frame execution to a _RenderStrategy pair: _SyncRenderStrategy writes transforms straight into OVRTX and consumes each step inline, while _AsyncRenderStrategy pipelines steps and double-buffers transform staging. The async strategy is created only when async is enabled, so sync call sites carry no branching.

Async works on both scene-ownership paths. Under ovstage the renderer borrows the stage's storage, so each ovstage scene write first settles any render still in flight.

Two environment variables support testing and benchmarking:

  • OVRTX_ASYNC_RENDERING overrides async_rendering (0/false/no/off disable, any other non-empty value enables). This is the only task-agnostic switch; a Hydra override such as env.scene.base_camera.renderer_cfg.async_rendering=True names a camera that a given task may not define.
  • OVRTX_NUM_BUFFERS sets the render queue depth: renders kept in flight before the oldest is drained (default 2, values below 2 are clamped). Larger values overlap more simulation at the cost of extra frames of camera latency.

Camera-output read ordering

Also improves camera-output throughput for synchronous rendering as well as asynchronous. OVRTX waits for render completion with a GPU-side wait on the CUDA default stream, which on Linux hits a driver GPU-context scheduling issue that runs most of the graphics work while the render-output copy waits. Camera outputs are now read without a GPU-side wait, with the calling thread waiting instead — roughly 30% higher throughput on a camera task on Linux, and slightly faster on Windows too.

Two variables select how the read is ordered against the render: ISAAC_LAB_OVRTX_RENDER_VAR_SYNC_STREAM picks the GPU-side barrier (0 default/none, default, or warp), and ISAAC_LAB_OVRTX_RENDER_VAR_MAP_WAIT (default 1) toggles the host-side wait. Selecting neither would race the render and is rejected.

Correctness

The async path is covered by golden-image tests that reuse the synchronous goldens, since async output must match within the existing SSIM / pixel-difference tolerances: test_rendering_cartpole_kitless.py::test_rendering_cartpole_kitless_async for both kitless physics backends (ovphysx, newton). Per-env tolerances are declared as [sync, async] and resolved through max_different_pixels_percentage_for(), guarded by test_rendering_tolerance_lookup.py.

Unit tests cover the pieces that are hard to observe end to end:

  • test_ovrtx_scene_write_barrier.py — an ovstage scene write settles in-flight renders, and delivery targets the correct frame's buffers.
  • test_ovrtx_scene_backend_binding.py — every scene operation resolves to the selected backend, with no unpaired implementation.
  • test_ovrtx_strategy_selection.py, test_ovrtx_render_ordinal.py, test_ovrtx_strategy_drain_state.py, test_ovrtx_async_teardown.py.

The isaaclab_ov suite passes (164 tests), including the clone-plan tests after the async-state refactor.

Benchmarks

Measured with the RSL-RL training benchmark (scripts/benchmarks/training.py) with training in the loop (real PPO updates, not just environment stepping) on the KukaAllegro lift camera task — a manipulation task with per-env RGB cameras, so representative of a real training workload rather than a rendering microbenchmark.

FPS is RSL-RL Perf/total_fps (whole training step, including the policy update); collection FPS is the rollout phase where rendering runs. Single runs with schema output — treat as throughput signals.

Benchmark provenance:

  • GPU: NVIDIA L40
  • OVRTX: 0.3.0.312915
  • Task: Isaac-Lift-KukaAllegro-Camera, 4096 envs, seed 42, 200 iterations
  • Selectors: physics=newton_mjwarp renderer=ovrtx presets=rgb64

KukaAllegro lift camera (RSL-RL, training in the loop)

metric sync async speedup
total FPS (mean) 31,864 45,545 1.43x
collection FPS (mean) 33,663 50,199 1.49x

End-to-end training throughput improves ~1.43x; the rollout phase, where rendering overlaps simulation, improves ~1.49x. A per-phase profile of this task shows rendering is ~63% of the iteration, so overlapping it with the remaining ~37% of non-render work is where the gain comes from.

Command (prefix with OVRTX_ASYNC_RENDERING=1 for async, OVRTX_ASYNC_RENDERING=0 for sync):

OVRTX_ASYNC_RENDERING=1 python scripts/benchmarks/training.py \
    --rl_library rsl_rl \
    --task Isaac-Lift-KukaAllegro-Camera \
    --seed 42 \
    --max_iterations 200 \
    --benchmark_formatter schema \
    --output_path ./results/kuka_<sync_or_async> \
    --headless \
    physics=newton_mjwarp renderer=ovrtx presets=rgb64

Enabling async adds one frame of camera latency.

Type of change

  • New feature (non-breaking change which adds functionality)

Screenshots

Not applicable — performance/API change with no visual UI. Async RGB output is validated against the sync goldens by the correctness tests above.

Checklist

  • I have read and understood the contribution guidelines
  • I have run the pre-commit checks with ./isaaclab.sh --format
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • I have added a changelog fragment under source/<pkg>/changelog.d/ for every touched package (do not edit CHANGELOG.rst or bump extension.toml — CI handles that)
  • I have added my name to the CONTRIBUTORS.md or my name already exists there

@github-actions github-actions Bot added the isaac-lab Related to Isaac Lab team label Jul 13, 2026
@pv-nvidia
pv-nvidia force-pushed the pv/ovrtx-async-slot-buffers branch 6 times, most recently from 83b23b3 to 926879d Compare July 13, 2026 19:26
@pv-nvidia pv-nvidia changed the title Pv/ovrtx async slot buffers Add opt-in async OVRTX rendering Jul 13, 2026
@pv-nvidia
pv-nvidia force-pushed the pv/ovrtx-async-slot-buffers branch from 926879d to 01afef6 Compare July 13, 2026 21:58
Comment thread source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_renderer.py Outdated
Comment thread source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_renderer.py Outdated
Comment thread source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_renderer.py Outdated
Comment thread source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_renderer.py Outdated
Comment thread source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_renderer.py Outdated
Comment thread source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_renderer.py
Comment thread source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_renderer.py Outdated
Comment thread source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_renderer.py Outdated
Comment thread source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_renderer.py Outdated
Comment thread source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_renderer.py Outdated
Comment thread source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_renderer.py Outdated
Comment thread source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_renderer.py Outdated
Comment thread source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_renderer.py Outdated
Comment thread source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_renderer.py Outdated
@pv-nvidia
pv-nvidia force-pushed the pv/ovrtx-async-slot-buffers branch from 2474442 to 5c0b779 Compare July 15, 2026 15:29
Comment thread source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_renderer.py Outdated
Comment thread source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_renderer.py Outdated
Comment thread source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_renderer.py Outdated
Comment thread source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_renderer.py Outdated
Comment thread source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_renderer.py Outdated
Comment thread source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_renderer.py Outdated
@pv-nvidia
pv-nvidia force-pushed the pv/ovrtx-async-slot-buffers branch from 1c4d859 to e818859 Compare July 27, 2026 17:44
@pv-nvidia
pv-nvidia dismissed r-schmitt’s stale review July 27, 2026 18:37

affected files were reverted, features already on develop

@pv-nvidia
pv-nvidia force-pushed the pv/ovrtx-async-slot-buffers branch from 100b503 to 589a873 Compare July 29, 2026 09:59
@pv-nvidia
pv-nvidia requested a review from myurasov-nv as a code owner July 29, 2026 16:48
@pv-nvidia
pv-nvidia force-pushed the pv/ovrtx-async-slot-buffers branch from 3b04eaa to 67417cf Compare July 29, 2026 17:36
@pv-nvidia
pv-nvidia marked this pull request as draft July 29, 2026 20:02
Comment on lines +892 to +929
# If self._object_newton_indices is not None, then Newton's the current physics backend

# A non-None self._object_newton_indices means Newton is the current physics backend.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change was not needed

Comment on lines -2149 to -2163
product_path = self._render_product_paths[0]
if product_path in products and len(products[product_path].frames) > 0:
self._process_render_frame(
render_data,
products[product_path].frames[0],
render_data.warp_buffers,
)

# Post-render PPISP: HDR scene-linear → LDR RGBA. Source/destination
# buffers are the same warp buffer map used by extraction.
if render_data.ppisp_pipeline is not None:
render_data.ppisp_pipeline.apply(
render_data.warp_buffers[str(RenderBufferKind.RGB_HDR)],
render_data.warp_buffers[str(RenderBufferKind.RGBA)],
)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code was deleted, where did it end up?

@pv-nvidia
pv-nvidia force-pushed the pv/ovrtx-async-slot-buffers branch 3 times, most recently from f3e3fce to 92d2ea0 Compare August 6, 2026 14:39
@pv-nvidia
pv-nvidia force-pushed the pv/ovrtx-async-slot-buffers branch 3 times, most recently from 319b01e to ed0a259 Compare August 7, 2026 22:50
@pv-nvidia
pv-nvidia marked this pull request as ready for review August 8, 2026 07:59
@pv-nvidia
pv-nvidia requested a review from hujc7 as a code owner August 8, 2026 07:59
@pv-nvidia
pv-nvidia force-pushed the pv/ovrtx-async-slot-buffers branch from 95c55fa to dccab4b Compare August 9, 2026 05:01
@pv-nvidia
pv-nvidia force-pushed the pv/ovrtx-async-slot-buffers branch 2 times, most recently from 4aae62e to 49e3170 Compare August 11, 2026 07:42
pv-nvidia and others added 5 commits August 12, 2026 15:41
Rendering blocked the caller on every frame, so simulation and Python
work could not overlap with the GPU. This adds an opt-in asynchronous
path that pipelines render steps instead.

Frame execution moves behind a render-strategy hierarchy so the
renderer's call sites carry no sync/async branching. The synchronous
strategy writes transforms straight into OVRTX and consumes each step
inline; the asynchronous strategy submits the step, double-buffers
transform staging, and consumes the products one frame later. Both the
legacy and ovstage backends go through it.

The path is off by default (async_rendering=False), so existing
behavior is unchanged. The render queue depth is fixed at two, giving
one frame of camera latency; per-frame latency control is left to a
future Isaac Lab-side setting.

The first frame after (re)initialization is waited on and consumed
immediately. OVRTX zero-initializes its output buffers, so without this
the first read returns a black frame while streaming and quality
stabilization are still in flight.

For tests, OVRTX_ASYNC_RENDERING overrides the config, per-environment
pixel tolerances carry separate synchronous and asynchronous values
resolved through a single helper, and the post-merge rendering subset
runs the asynchronous cartpole cases.
Shorten comments and docstrings to describe the current behavior only, dropping
rationale narrative, hypothetical justifications and forward-looking remarks.
No functional change.
Rewrite the _resolve_render_strategy and _write_attribute_ovstage docstrings
to state why an ovstage scene write must drain in-flight renders: OVRTX reads
the stage's storage in place. Align the changelog fragment wording.

Validate _SceneBackendOperation declarations in __set_name__ so an operation
missing either implementation fails at class creation instead of raising
AttributeError on first dispatch.
Replace the _SceneBackendOperation descriptor with the explicit
if/else dispatch methods, restoring them unchanged from develop. The
descriptor erased the signature of every dispatched call site and was
unrelated to asynchronous rendering; ovstage becoming the default will
remove the branches outright.

Keep the pairing coverage: every operation must implement both
backends, every pair must be listed, and each dispatch method must
call the selected backend and no other.
@pv-nvidia
pv-nvidia force-pushed the pv/ovrtx-async-slot-buffers branch from e9056a3 to 37fca6b Compare August 12, 2026 15:48
@pv-nvidia
pv-nvidia requested a review from marcodiiga as a code owner August 12, 2026 15:48
@pv-nvidia pv-nvidia closed this by deleting the head repository Aug 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation infrastructure isaac-lab Related to Isaac Lab team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants