# Recommended Pipeline Order for Mesh Optimization: Index → Cache → Overdraw → Fetch → Quantization

> Learn the recommended mesh optimization pipeline order: Indexing, Cache, Overdraw, Fetch, Quantization. Maximize GPU efficiency with this in-place sequence.

- Repository: [Arseny Kapoulkine/meshoptimizer](https://github.com/zeux/meshoptimizer)
- Tags: best-practices
- Published: 2026-07-12

---

**The recommended mesh optimization pipeline in the meshoptimizer library follows a strict five-stage sequence: Indexing → Vertex Cache → Overdraw → Vertex Fetch → Quantization, where each stage must be applied in-place to the buffers produced by the previous step to maximize GPU rendering efficiency.**

The meshoptimizer library by zeux provides a core set of algorithms for preparing geometry buffers for modern GPUs. According to the repository's README and source code, these optimizations must be performed in a specific order—documented at lines 38-45 of the README—because each transformation builds upon the memory layout and index patterns established by the previous stage.

## The Five-Stage Mesh Optimization Pipeline

### 1. Indexing (Vertex Deduplication)

The first step generates a compact index buffer and eliminates duplicate vertices through binary equivalence testing. In [`src/indexgenerator.cpp`](https://github.com/zeux/meshoptimizer/blob/main/src/indexgenerator.cpp), the `meshopt_generateVertexRemap` function creates a remap table that maps old vertex indices to new, deduplicated indices.

You then apply this remap using `meshopt_remapIndexBuffer` and `meshopt_remapVertexBuffer` to produce clean buffers where no two vertices contain identical attribute data. This compression reduces memory footprint and ensures subsequent optimization stages work on a minimal dataset.

### 2. Vertex Cache Optimization

Once indexed, the triangle list must be reordered to maximize hits in the GPU's post-transform vertex cache. The implementation in [`src/vcacheoptimizer.cpp`](https://github.com/zeux/meshoptimizer/blob/main/src/vcacheoptimizer.cpp) provides `meshopt_optimizeVertexCache`, which rewrites the index buffer to promote spatial locality of vertex references.

This step minimizes the number of times the GPU must transform and shade the same vertex, directly reducing computation costs on the graphics hardware.

### 3. Overdraw Optimization

After optimizing for the vertex cache, the pipeline reorders triangles to reduce early-Z overdraw. The `meshopt_optimizeOverdraw` function in [`src/overdrawoptimizer.cpp`](https://github.com/zeux/meshoptimizer/blob/main/src/overdrawoptimizer.cpp) rearranges the index buffer so that triangles likely to occlude subsequent geometry are drawn earlier.

This function accepts a threshold parameter—typically `1.05f` to allow ≤5% cache hit loss—balancing the trade-off between vertex cache efficiency and pixel fill rate. Without this optimization, the GPU would waste cycles shading pixels that the depth test later discards.

### 4. Vertex Fetch Optimization

With the index buffer now optimized, the pipeline must reorder the actual vertex buffer to match the new access patterns. The `meshopt_optimizeVertexFetch` function in [`src/vfetchoptimizer.cpp`](https://github.com/zeux/meshoptimizer/blob/main/src/vfetchoptimizer.cpp) reorders vertex data so that vertices are stored in the order they are accessed by the index buffer.

This improves CPU cache coherence and allows the GPU to fetch vertex attributes with sequential memory reads, reducing bandwidth usage and latency.

### 5. Vertex Quantization

The final stage reduces memory bandwidth by compressing vertex attributes into compact formats. The quantization functions in [`src/quantization.cpp`](https://github.com/zeux/meshoptimizer/blob/main/src/quantization.cpp)—including `meshopt_quantizeHalf` for 16-bit floating point and `meshopt_quantizeSnorm`/`meshopt_quantizeUnorm` for normalized integers—pack attributes like positions and normals into smaller data types.

This conversion happens after all reordering stages because quantization is lossy and must work on the final vertex layout.

## Complete Implementation Example

Below is a self-contained C++ example that implements the canonical pipeline. All functions are declared in [`src/meshoptimizer.h`](https://github.com/zeux/meshoptimizer/blob/main/src/meshoptimizer.h) and operate in-place on the buffers produced by preceding stages.

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

struct Vertex {
    float x, y, z;       // position
    float nx, ny, nz;    // normal
    float u, v;          // texcoord
};

void OptimizeMesh(std::vector<unsigned int>& indices,
                  std::vector<Vertex>& vertices)
{
    const size_t indexCount  = indices.size();
    const size_t vertexCount = vertices.size();

    // 1. Indexing – generate a remap table and rebuild buffers
    std::vector<unsigned int> remap(vertexCount);
    meshopt_generateVertexRemap(&remap[0], indices.data(), indexCount,
                                vertices.data(), vertexCount, sizeof(Vertex));

    std::vector<unsigned int> newIndices(indexCount);
    meshopt_remapIndexBuffer(newIndices.data(), indices.data(),
                             indexCount, &remap[0]);

    std::vector<Vertex> newVertices(vertexCount);
    meshopt_remapVertexBuffer(newVertices.data(), vertices.data(),
                              vertexCount, sizeof(Vertex), &remap[0]);

    indices.swap(newIndices);
    vertices.swap(newVertices);

    // 2. Vertex-cache optimization
    meshopt_optimizeVertexCache(indices.data(), indices.data(),
                                indexCount, vertexCount);

    // 3. Overdraw optimization (allow ≤5% cache hit loss)
    const float overdrawThreshold = 1.05f;
    meshopt_optimizeOverdraw(indices.data(), indices.data(),
                             indexCount, &vertices[0].x, vertexCount,
                             sizeof(Vertex), overdrawThreshold);

    // 4. Vertex-fetch optimization
    meshopt_optimizeVertexFetch(vertices.data(), indices.data(),
                                indexCount, vertices.data(),
                                vertexCount, sizeof(Vertex));

    // 5. Vertex quantization
    for (Vertex& v : vertices) {
        // Pack normals to 10-10-10 SNORM
        unsigned int packedNormal =
            (meshopt_quantizeSnorm(v.nx, 10) & 0x3FF) << 20 |
            (meshopt_quantizeSnorm(v.ny, 10) & 0x3FF) << 10 |
            (meshopt_quantizeSnorm(v.nz, 10) & 0x3FF);

        // Pack positions to half-float
        unsigned short px = meshopt_quantizeHalf(v.x);
        unsigned short py = meshopt_quantizeHalf(v.y);
        unsigned short pz = meshopt_quantizeHalf(v.z);

        // Store packed values...
    }
}

```

## Summary

- **Indexing** must come first to eliminate duplicate vertices and establish a clean vertex set, implemented in [`src/indexgenerator.cpp`](https://github.com/zeux/meshoptimizer/blob/main/src/indexgenerator.cpp).
- **Vertex cache optimization** follows to improve post-transform cache hits via `meshopt_optimizeVertexCache` in [`src/vcacheoptimizer.cpp`](https://github.com/zeux/meshoptimizer/blob/main/src/vcacheoptimizer.cpp).
- **Overdraw optimization** reduces wasted pixel shading using `meshopt_optimizeOverdraw` in [`src/overdrawoptimizer.cpp`](https://github.com/zeux/meshoptimizer/blob/main/src/overdrawoptimizer.cpp) with a configurable threshold.
- **Vertex fetch optimization** reorders the vertex buffer for memory locality using `meshopt_optimizeVertexFetch` in [`src/vfetchoptimizer.cpp`](https://github.com/zeux/meshoptimizer/blob/main/src/vfetchoptimizer.cpp).
- **Quantization** finalizes the pipeline by compressing attributes using functions from [`src/quantization.cpp`](https://github.com/zeux/meshoptimizer/blob/main/src/quantization.cpp).

## Frequently Asked Questions

### Why must the mesh optimization pipeline follow this specific order?

Each stage modifies buffer layouts in ways that subsequent stages depend upon. Indexing must precede all other steps because it establishes the vertex set that cache and overdraw optimizers rearrange. Fetch optimization must follow cache and overdraw reordering because it aligns the vertex buffer to the final index access pattern. Quantization comes last because it is a lossy compression that should not be undone by remapping operations.

### Can I skip stages in the mesh optimization pipeline?

You can skip stages, but doing so leaves performance on the table. The meshoptimizer README documents this sequence as the canonical "core pipeline" for maximum GPU efficiency. Skipping vertex cache optimization, for example, leaves significant vertex transformation overhead, while skipping fetch optimization wastes memory bandwidth.

### What is the difference between vertex cache and vertex fetch optimization?

Vertex cache optimization (`meshopt_optimizeVertexCache`) reorders the **index buffer** to maximize post-transform cache hits, preventing the GPU from re-processing the same vertex multiple times. Vertex fetch optimization (`meshopt_optimizeVertexFetch`) reorders the **vertex buffer** itself so that the CPU and GPU memory prefetchers can stream vertex data sequentially as the index buffer references it.

### How does the overdraw threshold parameter affect optimization?

The overdraw threshold in `meshopt_optimizeOverdraw` controls the trade-off between vertex cache efficiency and overdraw reduction. A value of `1.05f` means the algorithm will accept up to 5% degradation in vertex cache hits to achieve better early-Z occlusion. Lower values prioritize cache preservation over pixel fill rate, while higher values aggressively reduce overdraw at the cost of more vertex processing.