Recommended Pipeline Order for Mesh Optimization: Index → Cache → Overdraw → Fetch → Quantization
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, 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 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 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 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—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 and operate in-place on the buffers produced by preceding stages.
#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. - Vertex cache optimization follows to improve post-transform cache hits via
meshopt_optimizeVertexCacheinsrc/vcacheoptimizer.cpp. - Overdraw optimization reduces wasted pixel shading using
meshopt_optimizeOverdrawinsrc/overdrawoptimizer.cppwith a configurable threshold. - Vertex fetch optimization reorders the vertex buffer for memory locality using
meshopt_optimizeVertexFetchinsrc/vfetchoptimizer.cpp. - Quantization finalizes the pipeline by compressing attributes using functions from
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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →