Files
third_party_meshoptimizer/src/vcacheoptimizer.cpp
T
Arseny Kapoulkine eafd83ddc8 Remove clusters from vcacheoptimizer
There are several issues with passing cluster data around from algorithm
to algorithm:

- It complicates the interface, making overdraw optimization slightly
harder to use. There is also some asymmetry in the interface and the
user has to deal with one extra dynamically allocated array if they want
to optimize for overdraw.

- It ties the vertex cache and overdraw optimization algorithms together
- we can't really swap out one algorithm for another easily.

- It uses a specific cluster selection that can in some cases
significantly penalize the post-overdraw ACMR even with threshold=1 -
this is contrary to what Tipsify whitepaper claims.

To solve all these issues, we now generate the cluster information in
the overdraw algorithm. We follow a similar structure where we generate
hard boundaries and then generate soft boundaries based on that; instead
of using vertex cache algorithm flow to generate hard boundaries, we
treat any triangle that has all its three vertices miss the vertex cache
as the hard boundary.

The motivation behind this choice is simple - most vertex cache algorithms
will try to use greedy choices for the next triangle, and will only fall
back to a triangle that's out of the cache as some sort of fallback,
which means that it's likely that a triangle like this starts a
cache-disjoint patch of triangles and as such the penalty for reordering
at this boundary is going to be low.

In practice, while this choice does not mean that we perfectly preserve
ACMR when reordering along hard boundaries, it does result in a closer
match between expected ACMR and real ACMR after overdraw optimization.
2017-07-10 21:14:27 -07:00

237 lines
7.0 KiB
C++

// This file is part of meshoptimizer library; see meshoptimizer.hpp for version/license details
#include "meshoptimizer.hpp"
#include <cassert>
#include <vector>
// This work is based on:
// Pedro Sander, Diego Nehab and Joshua Barcz. Fast Triangle Reordering for Vertex Locality and Reduced Overdraw. 2007
namespace meshopt
{
struct Adjacency
{
std::vector<unsigned int> triangle_counts;
std::vector<unsigned int> offsets;
std::vector<unsigned int> data;
};
template <typename T>
static void buildAdjacency(Adjacency& adjacency, const T* indices, size_t index_count, size_t vertex_count)
{
// fill triangle counts
adjacency.triangle_counts.resize(vertex_count, 0);
for (size_t i = 0; i < index_count; ++i)
{
assert(indices[i] < vertex_count);
adjacency.triangle_counts[indices[i]]++;
}
// fill offset table
adjacency.offsets.resize(vertex_count);
unsigned int offset = 0;
for (size_t i = 0; i < vertex_count; ++i)
{
adjacency.offsets[i] = offset;
offset += adjacency.triangle_counts[i];
}
// fill triangle data
adjacency.data.resize(offset);
std::vector<unsigned int> filled_triangle_counts(vertex_count, 0);
size_t face_count = index_count / 3;
for (size_t i = 0; i < face_count; ++i)
{
unsigned int a = indices[i * 3 + 0], b = indices[i * 3 + 1], c = indices[i * 3 + 2];
adjacency.data[adjacency.offsets[a] + filled_triangle_counts[a]] = static_cast<unsigned int>(i);
filled_triangle_counts[a]++;
adjacency.data[adjacency.offsets[b] + filled_triangle_counts[b]] = static_cast<unsigned int>(i);
filled_triangle_counts[b]++;
adjacency.data[adjacency.offsets[c] + filled_triangle_counts[c]] = static_cast<unsigned int>(i);
filled_triangle_counts[c]++;
}
}
static unsigned int getNextVertexDeadEnd(const std::vector<unsigned int>& dead_end, unsigned int& dead_end_top, unsigned int& input_cursor, const std::vector<unsigned int>& live_triangles)
{
// check dead-end stack
while (dead_end_top)
{
unsigned int vertex = dead_end[--dead_end_top];
if (live_triangles[vertex] > 0)
return vertex;
}
// input order
while (input_cursor < live_triangles.size())
{
if (live_triangles[input_cursor] > 0)
return input_cursor;
++input_cursor;
}
return static_cast<unsigned int>(-1);
}
static unsigned int getNextVertexNeighbour(const unsigned int* next_candidates_begin, const unsigned int* next_candidates_end, const std::vector<unsigned int>& live_triangles, const std::vector<unsigned int>& cache_time_stamps, unsigned int time_stamp, unsigned int cache_size)
{
unsigned int best_candidate = static_cast<unsigned int>(-1);
int best_priority = -1;
for (const unsigned int* next_candidate = next_candidates_begin; next_candidate != next_candidates_end; ++next_candidate)
{
unsigned int vertex = *next_candidate;
// otherwise we don't need to process it
if (live_triangles[vertex] > 0)
{
int priority = 0;
// will it be in cache after fanning?
if (2 * live_triangles[vertex] + time_stamp - cache_time_stamps[vertex] <= cache_size)
{
priority = time_stamp - cache_time_stamps[vertex]; // position in cache
}
if (priority > best_priority)
{
best_candidate = vertex;
best_priority = priority;
}
}
}
return best_candidate;
}
template <typename T>
static void optimizeVertexCacheTipsify(T* destination, const T* indices, size_t index_count, size_t vertex_count, unsigned int cache_size)
{
// guard for empty meshes
if (index_count == 0 || vertex_count == 0)
{
return;
}
// support in-place optimization
std::vector<T> indices_copy;
if (destination == indices)
{
indices_copy.assign(indices, indices + index_count);
indices = &indices_copy[0];
}
// build adjacency information
Adjacency adjacency;
buildAdjacency(adjacency, indices, index_count, vertex_count);
// live triangle counts
std::vector<unsigned int> live_triangles = adjacency.triangle_counts;
// cache time stamps
std::vector<unsigned int> cache_time_stamps(vertex_count, 0);
// dead-end stack
std::vector<unsigned int> dead_end(index_count);
unsigned int dead_end_top = 0;
// emitted flags
std::vector<char> emitted_flags(index_count / 3, false);
unsigned int current_vertex = 0;
unsigned int time_stamp = cache_size + 1;
unsigned int input_cursor = 1; // vertex to restart from in case of dead-end
unsigned int output_triangle = 0;
while (current_vertex != static_cast<unsigned int>(-1))
{
const unsigned int* next_candidates_begin = &dead_end[0] + dead_end_top;
// emit all vertex neighbours
const unsigned int* neighbours_begin = &adjacency.data[0] + adjacency.offsets[current_vertex];
const unsigned int* neighbours_end = neighbours_begin + adjacency.triangle_counts[current_vertex];
for (const unsigned int* it = neighbours_begin; it != neighbours_end; ++it)
{
unsigned int triangle = *it;
if (!emitted_flags[triangle])
{
unsigned int a = indices[triangle * 3 + 0], b = indices[triangle * 3 + 1], c = indices[triangle * 3 + 2];
// output indices
destination[output_triangle * 3 + 0] = T(a);
destination[output_triangle * 3 + 1] = T(b);
destination[output_triangle * 3 + 2] = T(c);
output_triangle++;
// update dead-end stack
dead_end[dead_end_top + 0] = a;
dead_end[dead_end_top + 1] = b;
dead_end[dead_end_top + 2] = c;
dead_end_top += 3;
// update live triangle counts
live_triangles[a]--;
live_triangles[b]--;
live_triangles[c]--;
// update cache info
// if vertex is not in cache, put it in cache
if (time_stamp - cache_time_stamps[a] > cache_size)
cache_time_stamps[a] = time_stamp++;
if (time_stamp - cache_time_stamps[b] > cache_size)
cache_time_stamps[b] = time_stamp++;
if (time_stamp - cache_time_stamps[c] > cache_size)
cache_time_stamps[c] = time_stamp++;
// update emitted flags
emitted_flags[triangle] = true;
}
}
// next candidates are the ones we pushed to dead-end stack just now
const unsigned int* next_candidates_end = &dead_end[0] + dead_end_top;
// get next vertex
current_vertex = getNextVertexNeighbour(next_candidates_begin, next_candidates_end, live_triangles, cache_time_stamps, time_stamp, cache_size);
if (current_vertex == static_cast<unsigned int>(-1))
{
current_vertex = getNextVertexDeadEnd(dead_end, dead_end_top, input_cursor, live_triangles);
}
}
assert(output_triangle == index_count / 3);
}
void optimizeVertexCache(unsigned short* destination, const unsigned short* indices, size_t index_count, size_t vertex_count, unsigned int cache_size)
{
optimizeVertexCacheTipsify(destination, indices, index_count, vertex_count, cache_size);
}
void optimizeVertexCache(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count, unsigned int cache_size)
{
optimizeVertexCacheTipsify(destination, indices, index_count, vertex_count, cache_size);
}
} // namespace meshopt