sim2sim render benchmark — 2 scenes, 1 env

Two single-environment scenes rendered by six physics engines, rasterized and path-traced natively: Scene 1 a tilted cube dropped onto a plane, and Scene 2 the real LeRobot bipedal platform (STL meshes, 12 DOF) holding a stance with its built-in PD servos. This measures per-frame render/readback overhead — not the massively-parallel multi-env workloads the GPU engines (Genesis, mjlab, Isaac, Nexus) are designed for. Generated by examples/cube_drop/benchmark.py · site by make_site.py.

switch machines — numbers, charts, table and videos update

Results

Raster fps = per-frame loop (physics + render) with frames staying on the GPU — no full-frame readback (rows not re-measured on that basis are marked). Native-RT rows are each engine path-tracing its own scene. Not apples-to-apples across resolutions. On the RTX 5090 desktop the three portable backends (MuJoCo, mjlab, Genesis) were re-measured (median of 3, machine idle), and the Nexus raster + native-RT rows were freshly measured on the 5090 from a full source build of haixuanTao's headless-render Nexus + kiss3d (offscreen WebGPU, no display). The RT row is far faster than the laptop's because that build includes kiss3d's transform-only TLAS fast path, which drops the per-frame BLAS re-bake (242 ms/frame on the laptop → 2.2 ms here).

Nexus numbers use a patched kiss3d: its read_pixels converted pixels by indexing uncached mapped GPU memory per byte (~99 ms/frame at 640×480); converting from a cached row copy + reusing the staging buffer cut readback to ~5 ms (18×) — see kiss3d #397 (readback fix), nexus #7 (frame export) and nexus #8 (Python ray tracing).

Isaac Sim on the desktop: Isaac Sim 5.x's Omniverse RTX renderer crashes on driver 595.71.05 (R590/595-branch incompatibility — this box rendered fine on driver 580 until the 2026-07-10 upgrade), so the 5.x raster/RT rows exist only on the laptop panel; the desktop Scene-3 and Startup rows run headless physics via Isaac Lab's AppLauncher kit experience, which skips the crashing plugins. Isaac Sim 6.0.1.0 fixes the renderer on this driver (verified 2026-07-15: full RTX experience boots in 11 s and renders camera pixels; separate venv at ~/rt_build/isaac6-venv, not yet paired with Isaac Lab) — the desktop Isaac RT rows below were measured on 6.0 (render demos ported to its convert-then-reference URDF flow); they land below the laptop's 5.x numbers — kit fights a nonexistent second CUDA device on this box — so read them as lower bounds. Genesis native ray tracing on the desktop runs via a from-source LuisaRender build (CUDA backend, pip-wheel CUDA 12.9 toolchain).

Reading the ray-tracing rows: fps compares each engine's tracer at its own quality point, not equal image quality — kiss3d is a minimal path tracer (simple PBR, analytic lights) while Omniverse RTX carries a production pipeline (MDL materials, per-frame denoise/AOVs) that costs more per sample and pays a large accumulation restart whenever the scene moves (a static scene path-traces at 142 fps on the same box; the tumbling cube drops it to ~5). Nexus's speed on dynamic scenes is structural (transform-only TLAS updates), and these demo scenes are lit so shadows are actually visible in both raster and RT output — earlier recordings used near-headlight suns that hid them.

Bars show time per frame (lower is better, fastest first), split by where that time goes: physics (solver steps), sync (GPU-sim state to host), render (draw / path-trace), readback (frame pixels to CPU). Hover a bar for per-segment ms. Gray bars have no breakdown yet.

PhysicsSync (GPU→host)Renderno breakdown measured
GPUNVIDIA GeForce RTX 5090
Driver580.159.03
VRAM32607 MiB
CPUIntel(R) Core(TM) Ultra 9 285K
RAM62 GB
OSLinux 6.17.0-35-generic

41 shown measurements · generated 2026-07-28T11:01:32 · fps are the no-frame-readback loop (frames stay on the GPU; GL renderers sync via a 1×1 readPixels); rows marked * were not re-measured and still include readback; rows marked † include it inherently (the renderer returns the frame as part of rendering) but it is ≤1% of the frame time.

Scene 1 — Cube drop (single rigid body)

Rasterized (native renderer)

Ray traced — engine's own path tracer

Scene 2 — LeRobot bipedal platform (STL meshes, 12 DOF, PD stance hold)

Rasterized (native renderer)

Ray traced — engine's own path tracer

Scene 3 — LeRobot × 2,048 parallel envs (physics throughput, no rendering)

Physics only — 2,048 parallel envs

Bars show env-steps/s — higher is better (physics only, no rendering; steady-state window with one device sync at the end. Per-engine dt differs — an env-step is one engine step, see the table).

Scene 3b — LeRobot × 4,096 parallel envs (RL-training scale)

Scene 4 — Unitree G1 (29-DOF humanoid) × 4,096 parallel envs

Startup — time to first physics step (LeRobot scene, single env)

Time to first step — single env, warm JIT caches

Wall time from process start to the first completed physics step, single env, warm JIT caches — the cost of every script rerun / config change. Bar segments: ■ imports · ■ engine init · ■ scene build + first step. Where a cold caches figure is shown, that's the same run with the engine's kernel cache emptied — the first-ever-run / post-upgrade cost. Hover a bar for exact segments.

Full data table
BackendModeRender fps (no readback)ms/frameBreakdown ms (physics / sync / render)Physics steps/sResolutionsppGPUSource
MuJoCoCube drop — Rasterized (native renderer)1,971 *0.510.04 / — / 0.47———NVIDIA GeForce RTX 5090measured 2026-07-28
mjlab (MuJoCo-Warp)Cube drop — Rasterized (native renderer)592 *1.690.20 / 0.92 / 0.57———NVIDIA GeForce RTX 5090measured 2026-07-28
GenesisCube drop — Rasterized (native renderer)446 *2.241.11 / — / 1.13———NVIDIA GeForce RTX 5090measured 2026-07-28
NexusCube drop — Rasterized (native renderer)437 *2.291.42 / 0.05 / 0.14———NVIDIA GeForce RTX 5090measured 2026-07-28
Nexus (cuda-oxide + CUDA graph)Cube drop — Rasterized (native renderer)2643.793.60 / 0.01 / 0.18—640x480—NVIDIA GeForce RTX 5090measured 2026-07-09 (idle GPU)
Nexus (CPU)Cube drop — Rasterized (native renderer)37.5 *27——640x480—NVIDIA GeForce RTX 5090measured 2026-07-08
Nexus (cuda-oxide)Cube drop — Rasterized (native renderer)18.25555 / 0.02 / 0.24—640x480—NVIDIA GeForce RTX 5090measured 2026-07-09 (idle GPU)
MuJoCoCube drop — Ray traced — shared tracer (Mitsuba)———441,096——NVIDIA GeForce RTX 5090measured 2026-07-28
mjlab (MuJoCo-Warp)Cube drop — Ray traced — shared tracer (Mitsuba)———6,838——NVIDIA GeForce RTX 5090measured 2026-07-28
GenesisCube drop — Ray traced — shared tracer (Mitsuba)———3,641——NVIDIA GeForce RTX 5090measured 2026-07-28
Isaac SimCube drop — Ray traced — shared tracer (Mitsuba)———2,309——NVIDIA GeForce RTX 5090measured 2026-07-28
Genesis (Nyx)Cube drop — Ray traced — engine's own path tracer3203.121.11 / — / 2.013,419480x36864NVIDIA GeForce RTX 5090measured 2026-07-16 (idle GPU, headless RTX 5090); median of 3. NOTE: driver 580.159.03 — the other rows on this panel were measured on 595.71.05.
NexusCube drop — Ray traced — engine's own path tracer202 *5.001.52 / 0.07 / 2.93———NVIDIA GeForce RTX 5090measured 2026-07-28
GenesisCube drop — Ray traced — engine's own path tracer12.1 †82—2,283480x36864NVIDIA GeForce RTX 5090measured 2026-07-15 (idle GPU, headless RTX 5090)
Isaac SimCube drop — Ray traced — engine's own path tracer8.7 *115—1,145——NVIDIA GeForce RTX 5090measured 2026-07-28
MuJoCoLeRobot legs — Rasterized (native renderer)536 *1.870.06 / — / 1.81—640x480—NVIDIA GeForce RTX 5090measured 2026-07-08
GenesisLeRobot legs — Rasterized (native renderer)1188.485.09 / — / 3.39—640x480—NVIDIA GeForce RTX 5090measured 2026-07-09
Nexus (cuda-oxide + CUDA graph)LeRobot legs — Rasterized (native renderer)35.12826 / 0.02 / 2.67—640x480—NVIDIA GeForce RTX 5090measured 2026-07-09 (idle GPU)
NexusLeRobot legs — Rasterized (native renderer)6.6152143 / 3.98 / 4.62—640x480—NVIDIA GeForce RTX 5090measured 2026-07-08
Genesis (Nyx)LeRobot legs — Ray traced — engine's own path tracer1327.603.16 / — / 4.442,251640x48032NVIDIA GeForce RTX 5090measured 2026-07-16 (idle GPU, headless RTX 5090); median of 3. NOTE: driver 580.159.03 — the other rows on this panel were measured on 595.71.05.
Isaac SimLeRobot legs — Ray traced — engine's own path tracer70.1 *14—3,351640x48032NVIDIA GeForce RTX 5090measured 2026-07-09 (idle GPU)
Nexus (cuda-oxide + CUDA graph)LeRobot legs — Ray traced — engine's own path tracer4.9 *20427 / 0.06 / 0.84—640x48032NVIDIA GeForce RTX 5090measured 2026-07-09 (idle GPU)
NexusLeRobot legs — Ray traced — engine's own path tracer3.2 *312101 / 0.65 / 207—640x48032NVIDIA GeForce RTX 5090measured 2026-07-09 (idle GPU)
GenesisLeRobot legs — Ray traced — engine's own path tracer2.7 *365—758640x48032NVIDIA GeForce RTX 5090measured 2026-07-09 (idle GPU)
MuJoCoLeRobot legs × 2,048 — Physics only — 2,048 parallel envs———367,359——NVIDIA GeForce RTX 5090measured 2026-07-15 (idle GPU, headless RTX 5090)
mjlab (MuJoCo-Warp)LeRobot legs × 2,048 — Physics only — 2,048 parallel envs———2,921,962——NVIDIA GeForce RTX 5090measured 2026-07-15 (idle GPU, headless RTX 5090)
GenesisLeRobot legs × 2,048 — Physics only — 2,048 parallel envs———2,296,140——NVIDIA GeForce RTX 5090measured 2026-07-15 (idle GPU, headless RTX 5090)
NexusLeRobot legs × 2,048 — Physics only — 2,048 parallel envs———138,640——NVIDIA GeForce RTX 5090measured 2026-07-15 (idle GPU, headless RTX 5090)
Nexus (cuda-oxide + CUDA graph)LeRobot legs × 2,048 — Physics only — 2,048 parallel envs———113,159——NVIDIA GeForce RTX 5090measured 2026-07-15 (idle GPU, headless RTX 5090)
Isaac SimLeRobot legs × 2,048 — Physics only — 2,048 parallel envs———262,096——NVIDIA GeForce RTX 5090measured 2026-07-15 (idle GPU, headless RTX 5090)
MuJoCoUnitree G1 × 4,096 — Physics only — Unitree G1 29-DOF × 4,096 envs———555,709——NVIDIA GeForce RTX 5090measured 2026-07-28 (idle GPU, headless RTX 5090)
mjlab (MuJoCo-Warp)Unitree G1 × 4,096 — Physics only — Unitree G1 29-DOF × 4,096 envs———4,089,794——NVIDIA GeForce RTX 5090measured 2026-07-28 (idle GPU, headless RTX 5090)
GenesisUnitree G1 × 4,096 — Physics only — Unitree G1 29-DOF × 4,096 envs———2,557,591——NVIDIA GeForce RTX 5090measured 2026-07-28 (idle GPU, headless RTX 5090)
NexusUnitree G1 × 4,096 — Physics only — Unitree G1 29-DOF × 4,096 envs———54,127——NVIDIA GeForce RTX 5090measured 2026-07-28 (idle GPU, headless RTX 5090)
Isaac SimUnitree G1 × 4,096 — Physics only — Unitree G1 29-DOF × 4,096 envsfailed: version matrix: this box has Isaac Sim 6.0.1 (py3.12), whose matching Isaac Lab NVIDIA GeForce RTX 5090pending
MuJoCoStartup — Time to first step — single env, warm JIT caches—0.25 s————NVIDIA GeForce RTX 5090measured 2026-07-15 (idle GPU, headless RTX 5090)
mjlab (MuJoCo-Warp)Startup — Time to first step — single env, warm JIT caches—2.08 s————NVIDIA GeForce RTX 5090measured 2026-07-15 (idle GPU, headless RTX 5090)
GenesisStartup — Time to first step — single env, warm JIT caches—5.11 s————NVIDIA GeForce RTX 5090measured 2026-07-15 (idle GPU, headless RTX 5090)
NexusStartup — Time to first step — single env, warm JIT caches—0.90 s————NVIDIA GeForce RTX 5090measured 2026-07-15 (idle GPU, headless RTX 5090)
Nexus (cuda-oxide + CUDA graph)Startup — Time to first step — single env, warm JIT caches—1.12 s————NVIDIA GeForce RTX 5090measured 2026-07-15 (idle GPU, headless RTX 5090)
Isaac SimStartup — Time to first step — single env, warm JIT caches—5.47 s————NVIDIA GeForce RTX 5090measured 2026-07-15 (idle GPU, headless RTX 5090)

Resource usage — cube scene, 480×368 @ 64 spp

The fps rows say how fast each engine is; they say nothing about what it costs to run. The three engines here are the ones that could actually be rebuilt and run on this machine — LuisaRender is not built and mjlab does not import, so they are absent rather than estimated. The picture is a set of trades, not a ranking: Genesis uses ~3.7× Nexus's host RAM but a third of its VRAM; Isaac costs ~12× Nexus's RAM (5.8 GB — the Kit runtime and USD stage) and works the GPU hardest (79%) while path-tracing slowest. VRAM is the constraint that decides how many instances fit on a card: at 3.4 GB Nexus fits ~9 on this 32 GB GPU, Genesis ~30.

Method: RAM is USS summed over the process tree (not RSS, which double-counts shared pages); VRAM is per-PID from nvidia-smi's compute-apps table; GPU util is device-wide and only sampled with the GPU otherwise idle. Peaks include one-time JIT / shader-cache / stage-load costs, deliberately — peak footprint is what sizes a machine. Reproduce with tools/resource_profile.py. Time to first frame is process start → first path-traced frame in hand, on warm caches: it is what you wait through before seeing anything, and the spread is 16×. It is not the same as the Startup rows further down — those stop at the first physics step and never touch the render pipeline, so they miss the RTX shader cache and the first path-trace accumulation. Cold is far worse: Genesis's Taichi JIT costs ~74 s and Isaac's first-ever launch compiles an RTX shader cache for ~2 min.

BackendRAM peak (MB)VRAM peak (MB)CPU meanGPU meanTime to first frame
Genesis (Nyx)1,7761,04679%49%3.89 s
Isaac Sim5,8082,842105%81%6.26 s
Nexus4873,35553%59%0.35 s

Physics vs render, separated

The headline fps folds physics and rendering together — and the rows do not fold them the same way. genesis_rt_native.py and isaac_rt_native.py time only the render call, so their published fps is render-only; nexus_rt_native.py and genesis_nyx_native.py time the whole loop. So the fps column above does not compare like with like, and separating the stages changes the conclusion: on the cube, Nyx and Nexus are within ~7% as path tracers (474 vs 442 render-only fps) rather than the ~1.7× the combined number suggests — and on the robot Nexus's renderer is the faster of the two (347 vs 229), losing only because its physics costs 24.4 ms/frame against Genesis's 3.1. Physics ms is derived from physics_steps_s at each script's steps-per-frame where segments were not recorded.

Cube drop — 480×368 @ 64 spp

BackendPhysics ms/frameRender ms/frameRender-only fpsPublished fpsPublished fps measures
Genesis (Nyx)1.172.11474320whole loop
Genesis1.7582.0012.212.1render-only

LeRobot legs — 640×480 @ 32 spp

BackendPhysics ms/frameRender ms/frameRender-only fpsPublished fpsPublished fps measures
Genesis (Nyx)3.114.37229132whole loop

Genesis-Nyx — cost vs resolution (cube scene, 64 spp)

The cube ray-traced row above is measured at 480×368 because that is what every other native-RT row uses — but at that size the frame is small enough that fixed per-frame overhead, not ray tracing, sets the cost. Per-sample cost keeps falling as the frame grows, which means the 480×368 row understates Nyx: 28× the pixels costs only ~5× the time, and it still path-traces 2560×1920 at 91 fps. Reproduce with tools/nyx_res_sweep.py.

ResolutionPixelsRender ms/frameRender fpsns/sample
480x368177 k2.124720.1871.0× cheaper/sample
960x736707 k2.813560.0623.0× cheaper/sample
1280x9601229 k3.672730.0474.0× cheaper/sample
1920x14402765 k6.291590.0365.2× cheaper/sample
2560x19204915 k11.0090.90.0355.3× cheaper/sample

Scene parity across the ray-traced rows

The native-RT rows above do not light the same scene, so read that column as indicative rather than exact:

RowLight setup
Genesislights=[radius 1.5] + env_surface=Emissionsoft shadows + emissive env — genesis_rt_native.py
Genesis (Nyx)lights=[radius 1.5]soft shadows; matched to the LuisaRender row — genesis_nyx_native.py
Isaac SimUsdLux.DomeLight + UsdLux.DistantLightdome env sampling; its row note attributes ~73 ms/frame to the rebuild — isaac_rt_native.py
Nexusadd_directional_light()hard shadows, cheapest of the three — nexus_rt_native.py

How much is the disagreement worth? LuisaRender, Isaac and Nexus cannot be re-measured on this machine (no from-source LuisaRender build, no isaac-venv, no nexus3d installed), so tools/nyx_light_sweep.py sweeps the regimes on Nyx instead. The answer: ~10% at a tracing-bound resolution, inside the noise at the row's 480×368 — and soft vs hard shadows costs nothing measurable. So lighting choice does not explain cross-engine gaps of 24×/58×. Isaac is the exception its own row note already records: its dome light costs ~73 ms/frame of env-sampling rebuild.

480x368 @ 64 spp

LightingRender ms/frameRender fps
unlit1.74575—
point (radius 0, hard shadow)2.08481+20% vs unlit
area (radius 1.5, soft shadow)1.94516+11% vs unlit
area (radius 4.0, very soft)2.11474+21% vs unlit
3 area lights (radius 1.5)2.10477+21% vs unlit

1920x1440 @ 64 spp

LightingRender ms/frameRender fps
unlit6.25160—
point (radius 0, hard shadow)6.79147+9% vs unlit
area (radius 1.5, soft shadow)6.72149+8% vs unlit
area (radius 4.0, very soft)6.23161-0% vs unlit
3 area lights (radius 1.5)7.11141+14% vs unlit

Videos

Scene 1 — Cube drop (single rigid body)

Rasterized (native renderer)

Ray traced — engine's own path tracer

Scene 2 — LeRobot bipedal platform (STL meshes, 12 DOF, PD stance hold)

Rasterized (native renderer)

Ray traced — engine's own path tracer

Scene 3 — LeRobot × 2,048 parallel envs (physics throughput, no rendering)

Scene 3b — LeRobot × 4,096 parallel envs (RL-training scale)

Scene 4 — Unitree G1 (29-DOF humanoid) × 4,096 parallel envs

Startup — time to first physics step (LeRobot scene, single env)