# How to Generate Adjacency Index Buffers for Geometry Shaders and Tessellation

> Generate adjacency index buffers for geometry and tessellation shaders using zeux meshoptimizer library. Optimize your graphics with efficient index generation for displacement mapping and silhouette detection.

- Repository: [Arseny Kapoulkine/meshoptimizer](https://github.com/zeux/meshoptimizer)
- Tags: how-to-guide
- Published: 2026-07-11

---

**Use `meshopt_generateAdjacencyIndexBuffer` to create triangle-list-with-adjacency data for silhouette detection, or `meshopt_generateTessellationIndexBuffer` to build PN-AEN patches for crack-free displacement mapping, both implemented in [`src/indexgenerator.cpp`](https://github.com/zeux/meshoptimizer/blob/main/src/indexgenerator.cpp) of the zeux/meshoptimizer library.**

The zeux/meshoptimizer library provides optimized algorithms for mesh processing, including dedicated functions to generate adjacency index buffers required by modern GPU pipelines. These functions construct specialized index buffers that expose edge-adjacency information, enabling geometry shaders to access neighboring triangles or tessellation shaders to perform crack-free displacement. Understanding how to generate adjacency index buffers correctly ensures your rendering pipeline can implement advanced effects like silhouette outlining and adaptive subdivision.

## Adjacency vs. Tessellation Index Buffers

The library exposes two distinct generators in [`src/meshoptimizer.h`](https://github.com/zeux/meshoptimizer/blob/main/src/meshoptimizer.h) that serve different GPU pipeline requirements.

### Geometry-Shader Adjacency (`meshopt_generateAdjacencyIndexBuffer`)

This function produces a **triangle-list-with-adjacency** layout containing 6 indices per input triangle. The output format follows `[v0, a0, v1, a1, v2, a2]`, where `v*` represents the original triangle vertices and `a*` represents the vertices opposite each corresponding edge. This format enables geometry shaders to access neighboring vertices for effects like silhouette detection or wireframe rendering. The function declaration appears at lines 59–69 in [`src/meshoptimizer.h`](https://github.com/zeux/meshoptimizer/blob/main/src/meshoptimizer.h).

### PN-AEN Tessellation (`meshopt_generateTessellationIndexBuffer`)

This function generates a 12-index patch per input triangle designed for **Position-Normal Approximate Catmull-Clark with Edge Normals (PN-AEN)** tessellation. The output includes original vertices, opposing edge pairs, and dominant vertices for each corner to prevent cracking during displacement mapping. This requires `index_count * 4` storage space and is declared at lines 71–84 in [`src/meshoptimizer.h`](https://github.com/zeux/meshoptimizer/blob/main/src/meshoptimizer.h).

## Algorithm Implementation Details

Both functions share a common three-stage algorithm implemented in [`src/indexgenerator.cpp`](https://github.com/zeux/meshoptimizer/blob/main/src/indexgenerator.cpp), differing only in the final patch generation step.

### Stage 1: Canonical Position Remap

The algorithm begins by calling `buildPositionRemap` to eliminate duplicate positions caused by split attribute streams. A fast hash-based lookup table maps every vertex index to a canonical vertex index representing the unique 3D position. This step executes at lines 92–95 for adjacency generation and lines 63–66 for tessellation generation.

### Stage 2: Edge Set Construction

The code constructs an `edge_table` hash set containing every directed edge in the mesh. Each entry stores the **opposite vertex**—the vertex not belonging to that edge. The hash key uses a 64-bit composite `(i0 << 32) | i1` where `i0` → `i1` follows triangle winding. The population loops run from lines 106–126 (adjacency) and lines 74–88 (tessellation).

### Stage 3: Patch Generation

For each input triangle, the algorithm looks up the opposite edge for all three sides:

- **Adjacency** (lines 128–148): Writes a 6-element patch mapping each edge to its opposing vertex.
- **Tessellation** (lines 91–119): Writes a 12-element patch containing original vertices, opposite edge pairs, and dominant corner vertices.

## Input Requirements and Constraints

Both functions enforce specific prerequisites to ensure correct adjacency calculation:

- **Topology**: The input index buffer must contain triangles (`index_count % 3 == 0`).
- **Vertex Layout**: Positions must be tightly packed as `float3` (first 12 bytes) with a stride between 12 and 256 bytes, aligned to `sizeof(float)`.
- **Buffer Sizing**: Destination buffers require `index_count * 2` elements for adjacency (6 indices per triangle) or `index_count * 4` for tessellation (12 indices per triangle).
- **Determinism**: The algorithms operate solely on position data, requiring no additional per-vertex attributes.

## GPU Pipeline Integration

Feed the generated buffers directly to your graphics API:

- **Adjacency topology**: Use `VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY` in Vulkan or `GL_TRIANGLES_ADJACENCY` in OpenGL.
- **Tessellation topology**: Use `VK_PRIMITIVE_TOPOLOGY_PATCH_LIST` with patch size 12 in Vulkan, or `GL_PATCHES` in OpenGL after calling `glPatchParameteri(GL_PATCH_VERTICES, 12)`.

## Implementation Examples

### C++ Usage

```cpp
#include "meshoptimizer.h"
#include <vector>
#include <cassert>

// Geometry shader adjacency
void buildAdjacency(const std::vector<unsigned int>& indices,
                    const std::vector<float>& positions,
                    std::vector<unsigned int>& adjIndices)
{
    assert(indices.size() % 3 == 0);
    size_t indexCount = indices.size();
    size_t vertexCount = positions.size() / 3;
    
    adjIndices.resize(indexCount * 2);
    
    meshopt_generateAdjacencyIndexBuffer(
        adjIndices.data(),
        indices.data(),
        indexCount,
        positions.data(),
        vertexCount,
        sizeof(float) * 3
    );
}

// PN-AEN tessellation
void buildTessellation(const std::vector<unsigned int>& indices,
                       const std::vector<float>& positions,
                       std::vector<unsigned int>& tessIndices)
{
    assert(indices.size() % 3 == 0);
    size_t indexCount = indices.size();
    size_t vertexCount = positions.size() / 3;
    
    tessIndices.resize(indexCount * 4);
    
    meshopt_generateTessellationIndexBuffer(
        tessIndices.data(),
        indices.data(),
        indexCount,
        positions.data(),
        vertexCount,
        sizeof(float) * 3
    );
}

```

### JavaScript WebAssembly Usage

```javascript
function generateAdjacency(indices, positions) {
    const indexCount = indices.length;
    const vertexCount = positions.length / 3;
    const adj = new Uint32Array(indexCount * 2);
    
    instance.exports.meshopt_generateAdjacencyIndexBuffer(
        adj, indices, indexCount, positions, vertexCount, 12
    );
    
    return adj;
}

```

## Summary

- **`meshopt_generateAdjacencyIndexBuffer`** creates 6-index patches for geometry shaders requiring triangle adjacency data.
- **`meshopt_generateTessellationIndexBuffer`** creates 12-index patches for PN-AEN tessellation workflows.
- Both functions reside in [`src/indexgenerator.cpp`](https://github.com/zeux/meshoptimizer/blob/main/src/indexgenerator.cpp) and use a three-stage algorithm: canonical position remapping, edge hash table construction, and patch emission.
- Input meshes must use `float3` positions with 12-byte stride alignment, and output buffers must be sized to `index_count * 2` or `index_count * 4` respectively.
- The resulting buffers bind directly to `GL_TRIANGLES_ADJACENCY` or `GL_PATCHES` (with 12 vertices) primitives.

## Frequently Asked Questions

### What is the difference between adjacency and tessellation index buffers?

Adjacency index buffers contain 6 indices per triangle exposing only the single vertex opposite each edge, used for geometry shader operations like silhouette detection. Tessellation index buffers contain 12 indices per triangle including dominant vertices and full edge pairs required for PN-AEN displacement mapping to prevent cracks at T-junctions.

### Why does the algorithm need to remap positions canonically?

The `buildPositionRemap` step ensures that vertices sharing the same 3D position but different attribute indices (common in UV seams or hard edge splits) are treated as the same vertex for adjacency calculations. Without this step, edges that are geometrically adjacent but indexed separately would not be recognized as neighbors.

### Can I use these functions on indexed meshes with arbitrary vertex attributes?

Yes, provided the vertex positions are stored as the first 12 bytes of each vertex structure. The `stride` parameter allows the functions to skip over additional attributes (normals, UVs, tangents) when reading positions, as long as the stride is between 12 and 256 bytes and aligned to 4 bytes.

### How do I verify the output buffer size requirements?

For `meshopt_generateAdjacencyIndexBuffer`, allocate `index_count * 2` unsigned integers (6 indices per triangle ÷ 3 original indices = 2x multiplier). For `meshopt_generateTessellationIndexBuffer`, allocate `index_count * 4` unsigned integers (12 indices per triangle ÷ 3 original indices = 4x multiplier). Both functions assume the input `index_count` is divisible by 3.