optimizeVertexCache function
mesh with its triangles and vertices reordered for GPU cache reuse:
optimizeTriangleOrder on the index buffer, then optimizeVertexFetch to
renumber vertices by first use in that new order — the same two-pass
scheme real engines and meshoptimizer split into, because the two caches
they target (post- and pre-transform) are optimized by different orders.
The same triangles, drawn in the same orientation, described by the same vertex data — nothing here changes what the mesh looks like, only which byte offset a vertex or an index lands at. MeshData.morphTargets are carried through the same vertex permutation, so a blend shape still targets the vertex it always did.
A mesh with no triangles is returned unchanged, since there is nothing to reorder and Forsyth's scoring divides by a vertex's remaining triangle count.
Implementation
MeshData optimizeVertexCache(
MeshData mesh, {
int cacheSize = kDefaultVertexCacheSize,
}) {
if (mesh.triangleCount == 0) return mesh;
final cacheOrdered = optimizeTriangleOrder(
mesh.indices,
mesh.vertexCount,
cacheSize: cacheSize,
);
final fetch = optimizeVertexFetch(cacheOrdered, mesh.vertexCount);
final oldToNew = fetch.oldToNew;
final stride = mesh.layout.floatsPerVertex;
final vertices = Float32List(mesh.vertices.length);
for (var oldV = 0; oldV < mesh.vertexCount; oldV++) {
final newV = oldToNew[oldV];
final from = oldV * stride;
final to = newV * stride;
for (var c = 0; c < stride; c++) {
vertices[to + c] = mesh.vertices[from + c];
}
}
return MeshData(
layout: mesh.layout,
vertices: vertices,
indices: fetch.indices,
morphTargets: [
for (final target in mesh.morphTargets)
_remapMorphTarget(target, oldToNew),
],
);
}