Key Concepts
This page introduces the core ideas behind RXMesh system. Understanding these concepts will make the rest of the documentation much easier to follow.
Patches
RXMesh does not store the mesh as a single monolithic array. Instead, during construction, the input mesh is partitioned into patches. A patch is a small subset connected mesh faces that fit into GPU shared memory. Each patch contains a group of vertices, edges, and faces along with their local connectivity.
This patch-based design is central to RXMesh's performance. By keeping patches small enough to reside in shared memory, neighborhood queries can be answered without accessing global memory. Users generally do not interact with patches directly, i.e., RXMesh manages partitioning, locality, and load balancing internally. However, patches influence how indexing works and explain why RXMesh uses handles rather than raw integer indices.
Handles
Every mesh element (vertex, edge, or face) is identified by a handle, i.e., a 64-bit identifier that encodes both the patch ID and the element's local index within that patch.
RXMesh defines three handle types:
| Handle Type | Represents |
|---|---|
VertexHandle |
A vertex |
EdgeHandle |
An edge |
FaceHandle |
A face |
Handles are the primary way to refer to mesh elements throughout RXMesh. You use handles to identify elements when you read or write attributes. Handles are used also as the signature of the lambda function passed to for_each operations.
rx.for_each_vertex(
DEVICE, [vertex_color] __device__(const VertexHandle vh) {
vertex_color(vh, 0) = 1.0f;
});
Handles are not contiguous integers. If you need a flat index (e.g., for exporting or interfacing with external libraries), see Indexing.
For the full handle API, see the Handles reference.
Attributes
An attribute is a typed array of values attached to mesh elements. For example:
- A per-vertex 3D position (
VertexAttribute<float>with 3 components) - A per-edge scalar edge length (
EdgeAttribute<float>with 1 component) - A per-face scalar face area (
FaceAttribute<int>with 1 component)
Attributes are strongly typed, i.e., a VertexAttribute can only be indexed with a VertexHandle, preventing accidental misuse.
Attributes can reside on the host (CPU), device (GPU), or both, and you explicitly control data movement between them:
auto color = *rx.add_vertex_attribute<float>("vColor", 3);
// Compute on GPU...
color.move(DEVICE, HOST); // bring results back to CPU
Attributes are covered in detail under Managing Attributes (creating, checking, removing) and Working with Attributes (accessing values, memory movement, Eigen/GLM interop).
for_each Operations
RXMesh provides two main ways to run computations over the mesh:
Element-wise Operations
Use for_each when your computation depends only on a single element, with no need to access neighbors. The lambda receives one handle at a time:
This runs a parallel loop over all vertices (or edges, or faces). See for_each.
Connectivity-based Operations
Use for_each<Op, blockThreads> when you need to access neighboring elements, e.g., the vertices of a face or the one-ring neighbors of a vertex. Neighbor queries are specified using the Op enum:
| Op | Meaning |
|---|---|
Op::VV |
For each vertex, its adjacent vertices |
Op::VE |
For each vertex, its incident edges |
Op::VF |
For each vertex, its incident faces |
Op::EV |
For each edge, its two vertices |
Op::EF |
For each edge, its incident faces |
Op::FV |
For each face, its three vertices |
Op::FE |
For each face, its three edges |
Op::FF |
For each face, its adjacent faces |
Op::EVDiamond |
For each edge, its incident and opposite vertices |
The lambda receives the input element's handle and an iterator over the output elements:
rx.for_each<Op::FV, 256>(
[=] __device__(FaceHandle fh, VertexIterator& fv) {
// fv[0], fv[1], fv[2] are the three vertex handles
auto p0 = coords.to_glm<3>(fv[0]);
auto p1 = coords.to_glm<3>(fv[1]);
auto p2 = coords.to_glm<3>(fv[2]);
});
See Connectivity for_each for the full API and supported query types. For advanced use cases where you need to combine multiple queries or use shared memory, see Custom Kernels.
Host and Device
RXMesh is designed for GPU execution. The two key locations for data are:
HOST: CPU memory. Used for I/O, visualization, debugging.DEVICE: GPU memory. Used for computation.
Most computation in RXMesh happens on the device. Lambdas passed to for_each (element-wise or query overloads) must be annotated with __device__ and must capture data by value (not by reference). After computation, results are typically moved back to the host for output or visualization:
Putting It Together
The most basic workflow in RXMesh looks like this:
- Initialize RXMesh and load a mesh →
RXMeshStatic rx("mesh.obj") - Create attributes to store data on mesh elements →
rx.add_vertex_attribute<float>(...) - Run computations using element-wise
for_eachorfor_each<Op, blockThreads>. - Move results to the host if needed →
attr.move(DEVICE, HOST) - Visualize or export → Polyscope
For the complete API, continue to Static Mesh Processing. Step 3 can also become more advanced, e.g., solving linear systems with Matrices & Solvers, performing dynamic mesh operations that change connectivity, or computing derivatives with automatic differentiation for non-linear optimization.