Repository navigation
Add OptiX accelerated backend for the batch renderer - #1784
Conversation
|
a few suggestions: 1. Keep OptiX strictly lazy-imported so it has zero import-time impact when off by default (
|
|
Thanks for the review. This moves the backend to the released NVIDIA/warp-optix and its new API, as an
|
|
thanks for updating the pr @StafaH! 3 recommendations and then i think we are good to merge 1. Keep
|
create_render_context(..., backend="optix") renders with OptiX hardware ray tracing through warp_optix. The render megakernel is unchanged and runs as an OptiX raygen program; only its cast_ray function is swapped. Meshes and heightfields become triangle acceleration structures, analytic geoms become custom primitives whose intersection program reuses the shared ray_primitive dispatch, and planes are intersected analytically. Worlds are packed into a grid of cells in a single instance acceleration structure that refit_bvh rewrites on the GPU. The analytic intersection dispatch and geom color lookup move to ray.py so both backends share them. OptiX work cannot be recorded into CUDA graphs, so testspeed runs the OptiX backend without capture. Flexes and splats are not supported yet.
The backend now depends on NVIDIA/warp-optix through an optix extra (Python 3.12, built from git since there are no Linux wheels yet) and uses its new API: implicit context, wo.Accel with refit(), wo.Pipeline, and accel_query_ray / accel_query_ray_anyhit. warp_optix and render_optix are imported lazily. ray_geom is now make_ray_primitive(). Analytic geoms share one unit-cube custom-primitive GAS scaled per instance by geom_aabb, so per-world sizes need no rebuild. Instance writes and IAS build/refit are shared between create_scene and refit. Shadow rays use any-hit and return early, planes check alpha first, tmax is clamped to >= 0, and OptixScene moves to types.py. The new test renders an inline scene with primitives, a mesh, a heightfield and a plane for nworld 1 and 2 and compares OptiX against BVH. It found that BVH shadow rays tested heightfields one-sided while meshes are two-sided, so BVH heightfield shadows now use the any-hit cast too.
ed2e1e6 to
f1e0487
Compare
Adds an optix extra pinned to warp-optix>=0.3.0 (Python 3.12 only, the only wheels published) and updates uv.lock. warp_optix is now imported at module level with wo = None when it is missing, and create_render_context falls back to the BVH backend with a warning when backend='optix' is requested without it.
backend now defaults to None, which uses OptiX when warp-optix is installed, the device can create an OptiX context, and the model has no flexes or splats; otherwise it uses the Warp BVH backend, renamed from 'bvh' to 'warp'. Requesting 'optix' when it is unavailable warns with the reason and uses 'warp'. The resolved backend is stored on RenderContext.backend, which cli.unroll and refit_bvh dispatch on. The OptiX scene build and refit are renamed to build_scene_bvh and refit_scene_bvh to mirror bvh.py, and rays() now rejects OptiX render contexts instead of passing the IAS to the Warp BVH query.
Limiting the render raygen to 64 registers raises occupancy, which outweighs the extra spills: primitives renders 20% faster, aloha_clutter_vision 10% and unitree_g1_hfield_render 8% on an RTX PRO 6000 Blackwell. Results are flat between 48 and 80 registers.
…gration # Conflicts: # mujoco_warp/_src/render.py # mujoco_warp/_src/render_test.py # uv.lock
The test set zfar relative to the model extent, which put the far plane exactly on the box's front face, so it only passed when roundoff landed the hit just inside. With main's near-plane ray origins and the OptiX world grid offset, the hit landed just outside. Use an absolute zfar and check the nearest hit depth.
After the merge, 72 registers is 1 to 5% faster than 64 on the primitives, Shadow Hand and ALOHA render benchmarks and 2% slower on the G1 heightfield scene.
OptiX orders its launch indices for coherence itself, so the 8-row ray tiling that speeds up the Warp BVH only scatters the pixel writes and made OptiX renders about 9% slower. The tile height is now chosen per backend, and the backend is resolved before rays are precomputed.
Loading it before the trace keeps it live across OptiX traversal. Loading it on a miss is 3 to 4% faster with OptiX (7.5% at 2x2 supersampling), while the Warp BVH is up to 43% faster with the upstream early load, so Warp keeps it.
|
Thanks for the updates @StafaH! Two remaining recommendations regarding making OptiX opt-in ( 1. Default
|
Importing warp_optix initializes a CUDA context on device 0 and OptiX, so importing mujoco_warp no longer loads it. render_optix.get_wo imports it on first use.
OptiX is now opt-in with backend="optix", since it cannot be used with mjw.ray, graph capture or the near-coincident geom tie-breaking. This removes the backend="warp" workarounds from the tests.
|
Thanks @thowell, both done:
|
Adds an OptiX hardware ray tracing backend to the batch renderer, built on the warp-optix 0.3.0 release.
Usage
create_render_context(..., backend=...)accepts:"warp"(default): the existing Warp BVH renderer."optix": OptiX. If it is unavailable, warns with the reason and uses"warp".pip install mujoco-warp[optix]), the GPU can create an OptiX context, and the model has no flexes or splats.rc.backend.warp_optixis imported only whenbackend="optix"is requested, so importingmujoco_warpdoes not load it.How it works
ray.py. Planes are unbounded, so they are intersected directly in the ray generation program.refit_bvhrewrites the instance transforms and refits the instance acceleration structure each step.cast_raydiffers. Capping it at 72 registers was the fastest setting.Benchmarks
Refit + render throughput (
refit_bvhfollowed byrender, in world-steps per second) frommjwarp-testspeed --function=render, median of 3 runs.primitives(RGB + depth)unitree_g1_hfield_render(RGB)aloha_clutter_vision(4 cameras, RGB + depth + seg, shadows)cast_ray. Against main it is -1 to +2% onprimitivesand Shadow Hand and 41 to 51% faster on the other four scenes. On Spot the two match withrender_skybox=False, so the gap comes from how main'scast_raycompiles together with the early skybox load from render improvements #1959.cloth_renderuses flexes, so it runs on the Warp backend either way.Limitations
cli.unrolllaunches the OptiX backend directly, and code that capturesrenderin a graph should keep the defaultbackend="warp".rays()needs a Warp render context and raises for an OptiX one.optixextra is limited to Python 3.12.