virtualx-engine/thirdparty/meshoptimizer/vcacheoptimizer.cpp
reduz 77a045e902 Rework Mesh handling on scene importing.
-Reworked how meshes are treated by importer by using EditorSceneImporterMesh and EditorSceneImporterMeshNode. Instead of Mesh and MeshInstance, this allows more efficient processing of meshes before they are actually registered in the RenderingServer.
-Integrated MeshOptimizer
-Reworked internals of SurfaceTool to use arrays, making it more performant and easy to run optimizatons on.
2020-12-13 21:29:51 -03:00

473 lines
14 KiB
C++

// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details
#include "meshoptimizer.h"
#include <assert.h>
#include <string.h>
// This work is based on:
// Tom Forsyth. Linear-Speed Vertex Cache Optimisation. 2006
// Pedro Sander, Diego Nehab and Joshua Barczak. Fast Triangle Reordering for Vertex Locality and Reduced Overdraw. 2007
namespace meshopt
{
const size_t kCacheSizeMax = 16;
const size_t kValenceMax = 8;
struct VertexScoreTable
{
float cache[1 + kCacheSizeMax];
float live[1 + kValenceMax];
};
// Tuned to minimize the ACMR of a GPU that has a cache profile similar to NVidia and AMD
static const VertexScoreTable kVertexScoreTable = {
{0.f, 0.779f, 0.791f, 0.789f, 0.981f, 0.843f, 0.726f, 0.847f, 0.882f, 0.867f, 0.799f, 0.642f, 0.613f, 0.600f, 0.568f, 0.372f, 0.234f},
{0.f, 0.995f, 0.713f, 0.450f, 0.404f, 0.059f, 0.005f, 0.147f, 0.006f},
};
// Tuned to minimize the encoded index buffer size
static const VertexScoreTable kVertexScoreTableStrip = {
{0.f, 1.000f, 1.000f, 1.000f, 0.453f, 0.561f, 0.490f, 0.459f, 0.179f, 0.526f, 0.000f, 0.227f, 0.184f, 0.490f, 0.112f, 0.050f, 0.131f},
{0.f, 0.956f, 0.786f, 0.577f, 0.558f, 0.618f, 0.549f, 0.499f, 0.489f},
};
struct TriangleAdjacency
{
unsigned int* counts;
unsigned int* offsets;
unsigned int* data;
};
static void buildTriangleAdjacency(TriangleAdjacency& adjacency, const unsigned int* indices, size_t index_count, size_t vertex_count, meshopt_Allocator& allocator)
{
size_t face_count = index_count / 3;
// allocate arrays
adjacency.counts = allocator.allocate<unsigned int>(vertex_count);
adjacency.offsets = allocator.allocate<unsigned int>(vertex_count);
adjacency.data = allocator.allocate<unsigned int>(index_count);
// fill triangle counts
memset(adjacency.counts, 0, vertex_count * sizeof(unsigned int));
for (size_t i = 0; i < index_count; ++i)
{
assert(indices[i] < vertex_count);
adjacency.counts[indices[i]]++;
}
// fill offset table
unsigned int offset = 0;
for (size_t i = 0; i < vertex_count; ++i)
{
adjacency.offsets[i] = offset;
offset += adjacency.counts[i];
}
assert(offset == index_count);
// fill triangle data
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]++] = unsigned(i);
adjacency.data[adjacency.offsets[b]++] = unsigned(i);
adjacency.data[adjacency.offsets[c]++] = unsigned(i);
}
// fix offsets that have been disturbed by the previous pass
for (size_t i = 0; i < vertex_count; ++i)
{
assert(adjacency.offsets[i] >= adjacency.counts[i]);
adjacency.offsets[i] -= adjacency.counts[i];
}
}
static unsigned int getNextVertexDeadEnd(const unsigned int* dead_end, unsigned int& dead_end_top, unsigned int& input_cursor, const unsigned int* live_triangles, size_t vertex_count)
{
// 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 < vertex_count)
{
if (live_triangles[input_cursor] > 0)
return input_cursor;
++input_cursor;
}
return ~0u;
}
static unsigned int getNextVertexNeighbour(const unsigned int* next_candidates_begin, const unsigned int* next_candidates_end, const unsigned int* live_triangles, const unsigned int* cache_timestamps, unsigned int timestamp, unsigned int cache_size)
{
unsigned int best_candidate = ~0u;
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] + timestamp - cache_timestamps[vertex] <= cache_size)
{
priority = timestamp - cache_timestamps[vertex]; // position in cache
}
if (priority > best_priority)
{
best_candidate = vertex;
best_priority = priority;
}
}
}
return best_candidate;
}
static float vertexScore(const VertexScoreTable* table, int cache_position, unsigned int live_triangles)
{
assert(cache_position >= -1 && cache_position < int(kCacheSizeMax));
unsigned int live_triangles_clamped = live_triangles < kValenceMax ? live_triangles : kValenceMax;
return table->cache[1 + cache_position] + table->live[live_triangles_clamped];
}
static unsigned int getNextTriangleDeadEnd(unsigned int& input_cursor, const unsigned char* emitted_flags, size_t face_count)
{
// input order
while (input_cursor < face_count)
{
if (!emitted_flags[input_cursor])
return input_cursor;
++input_cursor;
}
return ~0u;
}
} // namespace meshopt
void meshopt_optimizeVertexCacheTable(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count, const meshopt::VertexScoreTable* table)
{
using namespace meshopt;
assert(index_count % 3 == 0);
meshopt_Allocator allocator;
// guard for empty meshes
if (index_count == 0 || vertex_count == 0)
return;
// support in-place optimization
if (destination == indices)
{
unsigned int* indices_copy = allocator.allocate<unsigned int>(index_count);
memcpy(indices_copy, indices, index_count * sizeof(unsigned int));
indices = indices_copy;
}
unsigned int cache_size = 16;
assert(cache_size <= kCacheSizeMax);
size_t face_count = index_count / 3;
// build adjacency information
TriangleAdjacency adjacency = {};
buildTriangleAdjacency(adjacency, indices, index_count, vertex_count, allocator);
// live triangle counts
unsigned int* live_triangles = allocator.allocate<unsigned int>(vertex_count);
memcpy(live_triangles, adjacency.counts, vertex_count * sizeof(unsigned int));
// emitted flags
unsigned char* emitted_flags = allocator.allocate<unsigned char>(face_count);
memset(emitted_flags, 0, face_count);
// compute initial vertex scores
float* vertex_scores = allocator.allocate<float>(vertex_count);
for (size_t i = 0; i < vertex_count; ++i)
vertex_scores[i] = vertexScore(table, -1, live_triangles[i]);
// compute triangle scores
float* triangle_scores = allocator.allocate<float>(face_count);
for (size_t i = 0; i < face_count; ++i)
{
unsigned int a = indices[i * 3 + 0];
unsigned int b = indices[i * 3 + 1];
unsigned int c = indices[i * 3 + 2];
triangle_scores[i] = vertex_scores[a] + vertex_scores[b] + vertex_scores[c];
}
unsigned int cache_holder[2 * (kCacheSizeMax + 3)];
unsigned int* cache = cache_holder;
unsigned int* cache_new = cache_holder + kCacheSizeMax + 3;
size_t cache_count = 0;
unsigned int current_triangle = 0;
unsigned int input_cursor = 1;
unsigned int output_triangle = 0;
while (current_triangle != ~0u)
{
assert(output_triangle < face_count);
unsigned int a = indices[current_triangle * 3 + 0];
unsigned int b = indices[current_triangle * 3 + 1];
unsigned int c = indices[current_triangle * 3 + 2];
// output indices
destination[output_triangle * 3 + 0] = a;
destination[output_triangle * 3 + 1] = b;
destination[output_triangle * 3 + 2] = c;
output_triangle++;
// update emitted flags
emitted_flags[current_triangle] = true;
triangle_scores[current_triangle] = 0;
// new triangle
size_t cache_write = 0;
cache_new[cache_write++] = a;
cache_new[cache_write++] = b;
cache_new[cache_write++] = c;
// old triangles
for (size_t i = 0; i < cache_count; ++i)
{
unsigned int index = cache[i];
if (index != a && index != b && index != c)
{
cache_new[cache_write++] = index;
}
}
unsigned int* cache_temp = cache;
cache = cache_new, cache_new = cache_temp;
cache_count = cache_write > cache_size ? cache_size : cache_write;
// update live triangle counts
live_triangles[a]--;
live_triangles[b]--;
live_triangles[c]--;
// remove emitted triangle from adjacency data
// this makes sure that we spend less time traversing these lists on subsequent iterations
for (size_t k = 0; k < 3; ++k)
{
unsigned int index = indices[current_triangle * 3 + k];
unsigned int* neighbours = &adjacency.data[0] + adjacency.offsets[index];
size_t neighbours_size = adjacency.counts[index];
for (size_t i = 0; i < neighbours_size; ++i)
{
unsigned int tri = neighbours[i];
if (tri == current_triangle)
{
neighbours[i] = neighbours[neighbours_size - 1];
adjacency.counts[index]--;
break;
}
}
}
unsigned int best_triangle = ~0u;
float best_score = 0;
// update cache positions, vertex scores and triangle scores, and find next best triangle
for (size_t i = 0; i < cache_write; ++i)
{
unsigned int index = cache[i];
int cache_position = i >= cache_size ? -1 : int(i);
// update vertex score
float score = vertexScore(table, cache_position, live_triangles[index]);
float score_diff = score - vertex_scores[index];
vertex_scores[index] = score;
// update scores of vertex triangles
const unsigned int* neighbours_begin = &adjacency.data[0] + adjacency.offsets[index];
const unsigned int* neighbours_end = neighbours_begin + adjacency.counts[index];
for (const unsigned int* it = neighbours_begin; it != neighbours_end; ++it)
{
unsigned int tri = *it;
assert(!emitted_flags[tri]);
float tri_score = triangle_scores[tri] + score_diff;
assert(tri_score > 0);
if (best_score < tri_score)
{
best_triangle = tri;
best_score = tri_score;
}
triangle_scores[tri] = tri_score;
}
}
// step through input triangles in order if we hit a dead-end
current_triangle = best_triangle;
if (current_triangle == ~0u)
{
current_triangle = getNextTriangleDeadEnd(input_cursor, &emitted_flags[0], face_count);
}
}
assert(input_cursor == face_count);
assert(output_triangle == face_count);
}
void meshopt_optimizeVertexCache(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count)
{
meshopt_optimizeVertexCacheTable(destination, indices, index_count, vertex_count, &meshopt::kVertexScoreTable);
}
void meshopt_optimizeVertexCacheStrip(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count)
{
meshopt_optimizeVertexCacheTable(destination, indices, index_count, vertex_count, &meshopt::kVertexScoreTableStrip);
}
void meshopt_optimizeVertexCacheFifo(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count, unsigned int cache_size)
{
using namespace meshopt;
assert(index_count % 3 == 0);
assert(cache_size >= 3);
meshopt_Allocator allocator;
// guard for empty meshes
if (index_count == 0 || vertex_count == 0)
return;
// support in-place optimization
if (destination == indices)
{
unsigned int* indices_copy = allocator.allocate<unsigned int>(index_count);
memcpy(indices_copy, indices, index_count * sizeof(unsigned int));
indices = indices_copy;
}
size_t face_count = index_count / 3;
// build adjacency information
TriangleAdjacency adjacency = {};
buildTriangleAdjacency(adjacency, indices, index_count, vertex_count, allocator);
// live triangle counts
unsigned int* live_triangles = allocator.allocate<unsigned int>(vertex_count);
memcpy(live_triangles, adjacency.counts, vertex_count * sizeof(unsigned int));
// cache time stamps
unsigned int* cache_timestamps = allocator.allocate<unsigned int>(vertex_count);
memset(cache_timestamps, 0, vertex_count * sizeof(unsigned int));
// dead-end stack
unsigned int* dead_end = allocator.allocate<unsigned int>(index_count);
unsigned int dead_end_top = 0;
// emitted flags
unsigned char* emitted_flags = allocator.allocate<unsigned char>(face_count);
memset(emitted_flags, 0, face_count);
unsigned int current_vertex = 0;
unsigned int timestamp = 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 != ~0u)
{
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.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] = a;
destination[output_triangle * 3 + 1] = b;
destination[output_triangle * 3 + 2] = 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 (timestamp - cache_timestamps[a] > cache_size)
cache_timestamps[a] = timestamp++;
if (timestamp - cache_timestamps[b] > cache_size)
cache_timestamps[b] = timestamp++;
if (timestamp - cache_timestamps[c] > cache_size)
cache_timestamps[c] = timestamp++;
// 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[0], &cache_timestamps[0], timestamp, cache_size);
if (current_vertex == ~0u)
{
current_vertex = getNextVertexDeadEnd(&dead_end[0], dead_end_top, input_cursor, &live_triangles[0], vertex_count);
}
}
assert(output_triangle == face_count);
}