mirror of
https://github.com/openharmony/third_party_meshoptimizer.git
synced 2026-07-19 20:03:59 -04:00
pretransformanalyzer: Add pre-transform cache analyzer
Generally vertex fetch goes through a more or less traditional cache. This cache can have different size, number of ways, replacement policy and also may or may not be shared with other units, like texture fetch. Modeling the real cache is subtle and complicated; this provides a simple approximation that uses a moderately sized direct-mapped cache. The performance is close to that of a 4-way cache on typical mesh data and direct mapping is slightly simpler to implement. The primary metric for the fetch cache is currently 'overfetch' factor - that is, the ratio between the number of bytes we need to fetch to transform all the vertices to the optimal number of bytes aka the size of the vertex buffer. This will depend on the vertex size, cache alignment and, most importantly, vertex index order.
This commit is contained in:
@@ -12,6 +12,7 @@ set(SOURCES
|
||||
src/overdrawoptimizer.cpp
|
||||
src/posttransformanalyzer.cpp
|
||||
src/posttransformoptimizer.cpp
|
||||
src/pretransformanalyzer.cpp
|
||||
src/pretransformoptimizer.cpp
|
||||
)
|
||||
|
||||
|
||||
+29
-8
@@ -130,7 +130,7 @@ void optRandomShuffle(Mesh& mesh)
|
||||
mesh.indices.swap(result);
|
||||
}
|
||||
|
||||
void optPostTransform(Mesh& mesh)
|
||||
void optTransform(Mesh& mesh)
|
||||
{
|
||||
std::vector<unsigned int> result(mesh.indices.size());
|
||||
|
||||
@@ -139,7 +139,7 @@ void optPostTransform(Mesh& mesh)
|
||||
mesh.indices.swap(result);
|
||||
}
|
||||
|
||||
void optOverdraw(Mesh& mesh)
|
||||
void optTransformOverdraw(Mesh& mesh)
|
||||
{
|
||||
std::vector<unsigned int> result(mesh.indices.size());
|
||||
|
||||
@@ -152,7 +152,7 @@ void optOverdraw(Mesh& mesh)
|
||||
optimizeOverdraw(&mesh.indices[0], &result[0], mesh.indices.size(), &mesh.vertices[0].px, sizeof(Vertex), mesh.vertices.size(), clusters, kCacheSize, kThreshold);
|
||||
}
|
||||
|
||||
void optOverdrawOnly(Mesh& mesh)
|
||||
void optOverdraw(Mesh& mesh)
|
||||
{
|
||||
std::vector<unsigned int> result(mesh.indices.size());
|
||||
|
||||
@@ -165,6 +165,25 @@ void optOverdrawOnly(Mesh& mesh)
|
||||
mesh.indices.swap(result);
|
||||
}
|
||||
|
||||
void optFetchTransformOverdraw(Mesh& mesh)
|
||||
{
|
||||
std::vector<unsigned int> result(mesh.indices.size());
|
||||
|
||||
std::vector<unsigned int> clusters;
|
||||
optimizePostTransform(&result[0], &mesh.indices[0], mesh.indices.size(), mesh.vertices.size(), kCacheSize, &clusters);
|
||||
|
||||
// allow up to 5% worse ACMR to get more reordering opportunities for overdraw
|
||||
const float kThreshold = 1.05f;
|
||||
|
||||
optimizeOverdraw(&mesh.indices[0], &result[0], mesh.indices.size(), &mesh.vertices[0].px, sizeof(Vertex), mesh.vertices.size(), clusters, kCacheSize, kThreshold);
|
||||
|
||||
std::vector<Vertex> newvert(mesh.vertices.size());
|
||||
|
||||
optimizePreTransform(&newvert[0], &mesh.vertices[0], &mesh.indices[0], mesh.indices.size(), mesh.vertices.size(), sizeof(Vertex));
|
||||
|
||||
mesh.vertices.swap(newvert);
|
||||
}
|
||||
|
||||
void optimize(const Mesh& mesh, const char* name, void (*optf)(Mesh& mesh))
|
||||
{
|
||||
Mesh copy = mesh;
|
||||
@@ -173,10 +192,11 @@ void optimize(const Mesh& mesh, const char* name, void (*optf)(Mesh& mesh))
|
||||
optf(copy);
|
||||
clock_t end = clock();
|
||||
|
||||
PostTransformCacheStatistics ptcs = analyzePostTransform(©.indices[0], copy.indices.size(), copy.vertices.size(), kCacheSize);
|
||||
PostTransformCacheStatistics vts = analyzePostTransform(©.indices[0], copy.indices.size(), copy.vertices.size(), kCacheSize);
|
||||
PreTransformCacheStatistics vfs = analyzePreTransform(©.indices[0], copy.indices.size(), copy.vertices.size(), sizeof(Vertex));
|
||||
OverdrawStatistics os = analyzeOverdraw(©.indices[0], copy.indices.size(), ©.vertices[0].px, sizeof(Vertex), copy.vertices.size());
|
||||
|
||||
printf("%-15s: ACMR %f ATVR %f Overdraw %f in %f msec\n", name, ptcs.acmr, ptcs.atvr, os.overdraw, double(end - start) / CLOCKS_PER_SEC * 1000);
|
||||
printf("%-25s: ACMR %f ATVR %f Overfetch %f Overdraw %f in %f msec\n", name, vts.acmr, vts.atvr, vfs.overfetch, os.overdraw, double(end - start) / CLOCKS_PER_SEC * 1000);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
@@ -206,7 +226,8 @@ int main(int argc, char** argv)
|
||||
|
||||
optimize(mesh, "Original", optNone);
|
||||
optimize(mesh, "Random Shuffle", optRandomShuffle);
|
||||
optimize(mesh, "Cache", optPostTransform);
|
||||
optimize(mesh, "Cache+Overdraw", optOverdraw);
|
||||
optimize(mesh, "Overdraw Only", optOverdrawOnly);
|
||||
optimize(mesh, "Transform", optTransform);
|
||||
optimize(mesh, "Transform+Overdraw", optTransformOverdraw);
|
||||
optimize(mesh, "Fetch+Transform+Overdraw", optFetchTransformOverdraw);
|
||||
optimize(mesh, "Overdraw Only", optOverdraw);
|
||||
}
|
||||
|
||||
@@ -70,3 +70,14 @@ struct OverdrawStatistics
|
||||
// vertex_positions should have float3 position in the first 12 bytes of each vertex - similar to glVertexPointer
|
||||
OverdrawStatistics analyzeOverdraw(const unsigned short* indices, size_t index_count, const float* vertex_positions, size_t vertex_positions_stride, size_t vertex_count);
|
||||
OverdrawStatistics analyzeOverdraw(const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_positions_stride, size_t vertex_count);
|
||||
|
||||
struct PreTransformCacheStatistics
|
||||
{
|
||||
float overfetch;
|
||||
};
|
||||
|
||||
// Vertex fetch cache analyzer
|
||||
// Returns cache hit statistics using a simplified direct mapped model
|
||||
// Results will not match actual GPU performance
|
||||
PreTransformCacheStatistics analyzePreTransform(const unsigned short* indices, size_t index_count, size_t vertex_count, size_t vertex_size);
|
||||
PreTransformCacheStatistics analyzePreTransform(const unsigned int* indices, size_t index_count, size_t vertex_count, size_t vertex_size);
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
#include "meshoptimizer.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <cassert>
|
||||
|
||||
namespace
|
||||
{
|
||||
template <typename T>
|
||||
PreTransformCacheStatistics analyzePreTransformImpl(const T* indices, size_t index_count, size_t vertex_count, size_t vertex_size)
|
||||
{
|
||||
PreTransformCacheStatistics result = {};
|
||||
|
||||
const size_t kCacheLine = 64;
|
||||
const size_t kCacheSize = 128*1024;
|
||||
|
||||
// simple direct mapped cache; on typical mesh data this is close to 4-way cache, and this model is a gross approximation anyway
|
||||
size_t cache[kCacheSize / kCacheLine] = {};
|
||||
size_t fetched = 0;
|
||||
|
||||
for (size_t i = 0; i < index_count; ++i)
|
||||
{
|
||||
T index = indices[i];
|
||||
|
||||
size_t start_address = index * vertex_size;
|
||||
size_t end_address = start_address + vertex_size;
|
||||
|
||||
size_t start_tag = start_address / kCacheLine;
|
||||
size_t end_tag = (end_address + kCacheLine - 1) / kCacheLine;
|
||||
|
||||
assert(start_tag < end_tag);
|
||||
|
||||
for (size_t tag = start_tag; tag < end_tag; ++tag)
|
||||
{
|
||||
size_t line = tag % (sizeof(cache) / sizeof(cache[0]));
|
||||
|
||||
// we store +1 since cache is filled with 0 by default
|
||||
fetched += cache[line] != tag + 1;
|
||||
cache[line] = tag + 1;
|
||||
}
|
||||
}
|
||||
|
||||
result.overfetch = static_cast<float>(fetched * kCacheLine) / static_cast<float>(vertex_count * vertex_size);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
PreTransformCacheStatistics analyzePreTransform(const unsigned short* indices, size_t index_count, size_t vertex_count, size_t vertex_size)
|
||||
{
|
||||
return analyzePreTransformImpl(indices, index_count, vertex_count, vertex_size);
|
||||
}
|
||||
|
||||
PreTransformCacheStatistics analyzePreTransform(const unsigned int* indices, size_t index_count, size_t vertex_count, size_t vertex_size)
|
||||
{
|
||||
return analyzePreTransformImpl(indices, index_count, vertex_count, vertex_size);
|
||||
}
|
||||
Reference in New Issue
Block a user