Skip to content

stripifier: Tweak continuation/restart heuristics for strip length - #1101

Merged
zeux merged 4 commits into
masterfrom
stripify
Oct 6, 2026
Merged

zeux merged 4 commits into
masterfrom
stripify

Conversation

@zeux

@zeux zeux commented Oct 5, 2026 •

Copy link
Copy Markdown
Owner

Instead of always blindly continuing along the 'cont' edge, we now can
use the swap edge even if cont edge is valid; this is done when the
number of neighboring triangles that we haven't emitted yet in the swap
case is lower.

This is actually part of the SGI algorithm that's also described in the
Evans paper; during the original implementation I must have missed this
fact. This seems suboptimal: cont edge results in zero new indices
emitted. However, taking swaps more eagerly results in better
stripification - a locally suboptimal choice is more optimal globally.

For restarts, the lowest-valence heuristic by itself is reasonable but if we
run into a situation where we pick between cont & swap, then it is better
to restart the strip from that skipped triangle instead. This is the same
idea as the one Hoppe uses in his stripification paper; that paper
maintains a full queue whereas we just record the last triangle for
simplicity.

Together these result in significant ACMR improvement and a moderate
overall improvement in the triangle strip size / reduction in indices;
because this gives us ACMR budget, we use it to increase the buffer size
to 12 which maintains ~neutral ACMR but significantly improves
stripification quality, producing ~10% fewer indices and ~50% longer strips.

This contribution is sponsored by Valve.

zeux added 4 commits October 4, 2026 15:33
Instead of always blindly continuing along the 'cont' edge, we now can
use the swap edge even if cont edge is valid; this is done when the
number of neighboring triangles that we haven't emitted yet in the swap
case is lower.

This is actually part of the SGI algorithm that's also described in the
Evans paper; during the original implementation I must have missed this
fact. This seems suboptimal: cont edge results in zero new indices
emitted. However, taking swaps more eagerly results in better
stripification - a locally suboptimal choice is more optimal globally.
This is because when the swap triangle has few remaining neighbors, it's
expensive to re-stripify it later in the process, and may create many
single-triangle strips.

Number of neighboring triangles is an approximation through valence; the
shared vertex between cont & swap must have valence >= 2, so
countSharedVertices returns 1, 2 or 3 for each triangle, which
corresponds to 0, 1 or 3 shared edges, assuming manifold topology.

By itself, this results in significant ACMR improvement and a moderate
overall improvement in the triangle strip size / reduction in indices;
because this gives us ACMR budget, we use it to increase the buffer size
to 11 which maintains ~neutral ACMR but significantly improves
stripification quality (5.5% fewer indices, 41% more triangles per
strip, small improvement in ACMR on a test set of 32 meshes).
The lowest-valence heuristic by itself is reasonable but if we run into
a situation where we pick between cont & swap, then it is better to
restart the strip from that skipped triangle instead. This is the same
idea as the one Hoppe uses in his stripification paper; that paper
maintains a full queue whereas we just record the last triangle for
simplicity.

By itself, this improves ACMR a bit, as it favors more recent triangles;
we bump up buffer size a little to compensate for that and take the win
in strip sizes instead.

On a test mesh set, this results in ~7.8% more triangles/strip and ~1.6%
fewer indices, at a small ACMR loss (0.4%); the previous change was a
slightly more significant net improvement on ACMR so overall this is
still a bit ahead of the original code (with buffer size 8).
Instead of checking individual edge endpoints, we now check the entire
edge code with a single 64-bit comparison; this reduces branch
mispredictions significantly.

Also, when we try to continue the strip along one of the edges, we can
check if the remaining valence of the vertex is already at 0; if it is,
we don't need to scan as we have already run out of triangles.

The second optimization makes stripify ~8% faster, and the first one
gives ~16% extra performance on top. Between these two we're now fairly
close to the original performance of the stripifier (with a smaller
buffer and old heuristics).
With the updates to the stripification heuristics, we now have longer
strips which makes restart indices a bit less profitable than before; on
average they still save around 6% of indices.

Also some comments about valence uses are now out of date, so this
change fixes them too.
@zeux zeux changed the title stripifier: Improve continuation/restart heuristics to create longer strips stripifier: Improve continuation/restart heuristics to lengthen strips Oct 5, 2026
@zeux zeux changed the title stripifier: Improve continuation/restart heuristics to lengthen strips stripifier: Tweak continuation/restart heuristics for strip length Oct 5, 2026
@zeux

zeux commented Oct 5, 2026 •

Copy link
Copy Markdown
Owner Author

Comparison before/after on a set of meshes; indices/triangle improves more with disabled restarts because longer strips make degenerate triangles used for restart cost less.

image

And one particular mesh, with vcache-optimized(meshopt_optimizeVertexCache) input indices:

image

When using meshopt_optimizeVertexCacheStrip, you get longer strips in general but that improves too with the new changes, just less significantly - naturally, ACMR is worse vs variants above, but still improve with the new changes:

image

@zeux
zeux merged commit 7d0e9df into master Oct 6, 2026
13 checks passed
@zeux
zeux deleted the stripify branch October 6, 2026 00:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant