Scene build and sync
How a RayD Scene is assembled, built, updated in place, and synchronized, and how mesh, primitive, and edge ids are numbered.
A Scene is a list of meshes plus the acceleration structures that answer queries over them. It moves through three states: assembled, built, and synchronized after edits. This page explains those states, the backends a scene can use, and the id spaces that query results report.
Lifecycle#
Scene() --add_mesh/add_instance--> assembled --build()--> ready
ready --update_mesh_vertices / set_mesh_transform / set_edge_mask--> pending updates
pending updates --sync()--> ready
ready --add_mesh/add_instance--> assembled (call build() again)- Assemble.
add_mesh(mesh, dynamic=False)appends a mesh and returns its mesh id (0, 1, 2, ...). In the Dr.Jit backend the scene stores a copy of the mesh, so later edits to theMeshobject do not reach the scene. - Build.
build()creates the triangle acceleration structure (an OptiX GAS per geometry under one IAS, or a CUDA BVH) and the scene-global edge BVH. Every query requires a built scene. Adding a mesh afterbuild()invalidates it, and you must callbuild()again. - Update. For meshes added with
dynamic=True,update_mesh_vertices(mesh_id, positions)and the transform setters queue an edit. Queued edits are not visible to queries yet:has_pending_updates()becomesTrue, and the Dr.Jit backend rejects edge queries until you sync. - Sync.
sync()applies every queued edit by refitting the affected acceleration structures in place. It is much cheaper thanbuild()because the topology stays the same. In the Dr.Jit backend,scene.last_sync_profilereports what the sync touched and how long each stage took.
Static meshes (dynamic=False) cannot be updated after build(). Mark only the meshes you move as dynamic; static geometry gets a faster acceleration structure.
Transforms#
A Dr.Jit Mesh has three transform factors. Its world transform is to_world_left @ to_world @ to_world_right, with column vectors (p_world = M @ p_object). Mesh.set_transform(mat, set_left=True) replaces the left or right factor, and append_transform(mat, append_left=True) multiplies onto it. On a built scene, use Scene.set_mesh_transform(mesh_id, mat) and Scene.append_mesh_transform(mesh_id, mat) for dynamic meshes, then sync(). An AD Matrix4f makes the transform differentiable.
A Torch Mesh takes optional to_world_left and to_world_right (4, 4) tensors with the same column-vector convention.
Instancing#
add_instance(geometry_id, transform, dynamic=True) adds another copy of an existing static mesh with its own transform. The instance shares the source mesh's OptiX geometry (GAS) but gets its own instance id and its own range of scene-global primitive ids. A dynamic instance accepts transform-only updates: Scene.set_mesh_transform in Dr.Jit, Scene.set_instance_transform in Torch, followed by sync(). You cannot instance a dynamic mesh or an instance, and you cannot change an instance's vertices. In 0.8.0 RayD still keeps a world-space copy of each instance's triangles and edges for edge queries and for the CUDA fallback.
Trace and edge backends#
Two independent backends serve a scene: one for triangle queries and one for edge queries. The defaults pick the best one available.
| Argument | Dr.Jit values | Torch values | "auto" resolves to |
|---|---|---|---|
trace_backend | "auto", "optix", "cuda", "none" | "auto", "optix", "cuda" | "optix" if the driver accepts an OptiX context, else "cuda" |
edge_bvh_backend | "auto", "optix", "drjit", "optix_drjit" | "auto", "optix", "cuda" | "optix" if available, else the software BVH |
"optix"uses hardware ray tracing. Requesting it explicitly fails atbuild()when OptiX is unavailable."cuda"is RayD's pure-CUDA BVH. It produces the same results and runs eagerly (it cannot fuse into a Dr.Jit traced program)."none"(Dr.Jit only) builds no triangle structure, for scenes used only for edge queries."optix_drjit"(Dr.Jit only) answers point and top-k edge queries with the Dr.Jit BVH and ray edge queries with OptiX.
Check what a scene resolved to with scene.trace_backend_name() and scene.edge_bvh_backend in Dr.Jit, or scene.trace_backend and scene.edge_bvh_backend in Torch (after build()).
Note
The two constructors take these arguments in different orders: Dr.Jit is Scene(edge_bvh_backend="auto", trace_backend="auto") and Torch is Scene(trace_backend="auto", edge_bvh_backend="auto"). Pass them by keyword.
Id spaces#
Query results identify geometry in up to three id spaces:
| Field | Meaning |
|---|---|
shape_id | Mesh or instance id, in add_mesh / add_instance order. -1 means no hit. |
prim_id, local_prim_id | Triangle index within that mesh. The two fields are equal. |
global_prim_id | Triangle index in the scene-global face list. |
edge_id | Edge index within its mesh. |
global_edge_id | Edge index in the scene-global edge list. This is the index space of set_edge_mask. |
Meshes are concatenated in id order. In the Dr.Jit backend, mesh_face_offsets(), mesh_edge_offsets(), and mesh_vertex_offsets() return prefix sums, so mesh m owns global faces offset[m] up to but not including offset[m + 1]. global_geometry() returns all faces in the global space, and scene.version and scene.edge_version increase whenever the geometry or edge set changes, which is useful for cache invalidation.