feat: LOD 网格分级——meshopt 解析期简化 + 按距离/级联选级
vendored meshoptimizer 1.2(submodule 环境故障改直接 vendor)。Mesh 多 LOD:各级索引连续拼进同一 index buffer(共享顶点,LodRange 区间 选择),解析期 meshopt_simplify 生成链(LOD0=原始,每级目标减半, 相对误差 1e-2,小网格复用上级);都在 worker 线程,加载零额外卡顿。 SceneRenderer 按 (mesh, LOD) 聚合 batch(lookup key = 指针<<8|lod): 主视图按屏占比近似 radius/dist 选级(阈值 10/20 倍半径);阴影级联 按级联序降级(越远越简)。Draw 按 batch.lod 选索引区间。 A/B 对比(100 背包 10×10,RTX 5060 Laptop,vsync off): - 纯背包:LOD 开 93-95 fps / 关 90-107 fps —— 无显著差异 - 加 SunTemple:LOD 开 52-56 / 关 51-55 —— 无显著差异 结论:当前场景瓶颈不在顶点数(5060 顶点吞吐富余),LOD 基础设施 保留(几何复杂资产/低端 GPU/更大场景生效),帧率问题转向 Tracy GPU 时间线定位真实瓶颈。 坑:LodRange 记录"LOD 不足复用最后一级"时 first 不得继续累加 (buffer 无重复数据,累加必越界——VUID-08798 抓的)。
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
from z3 import *
|
||||
|
||||
def same8(v):
|
||||
return Or(v == 0, v == 0xff)
|
||||
|
||||
magic = BitVec('magic', 64)
|
||||
|
||||
x = BitVec('x', 64)
|
||||
y = x * magic
|
||||
|
||||
s = Solver()
|
||||
solve_using(s, ForAll([x],
|
||||
Or(
|
||||
Not(And([same8((x >> (i * 8)) & 0xff) for i in range(8)])), # x has bytes that aren't equal to 0xff or 0x00
|
||||
And([(x >> (i * 8 + 7)) & 1 == (y >> (56 + i)) & 1 for i in range(8)]) # every byte of x has bits that are equal to a corresponding top bit of y
|
||||
)))
|
||||
|
||||
print("magic =", hex(s.model().eval(magic).as_long()))
|
||||
@@ -0,0 +1,61 @@
|
||||
#include "../src/meshoptimizer.h"
|
||||
|
||||
#include <float.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* buffer, size_t size)
|
||||
{
|
||||
if (size == 0)
|
||||
return 0;
|
||||
|
||||
srand(buffer[0]);
|
||||
|
||||
float vb[100][4];
|
||||
|
||||
for (int i = 0; i < 100; ++i)
|
||||
{
|
||||
vb[i][0] = rand() % 10;
|
||||
vb[i][1] = rand() % 10;
|
||||
vb[i][2] = rand() % 10;
|
||||
vb[i][3] = rand() % 10;
|
||||
}
|
||||
|
||||
unsigned int ib[999];
|
||||
int indices = (size - 1) < 999 ? (size - 1) / 3 * 3 : 999;
|
||||
|
||||
for (int i = 0; i < indices; ++i)
|
||||
ib[i] = buffer[1 + i] % 100;
|
||||
|
||||
meshopt_Meshlet ml[333];
|
||||
unsigned int mv[333 * 3];
|
||||
unsigned char mt[333 * 4];
|
||||
|
||||
meshopt_buildMeshlets(ml, mv, mt, ib, indices, vb[0], 100, sizeof(float) * 4, /* max_vertices= */ 4, /* max_triangles= */ 4, 0.f);
|
||||
|
||||
meshopt_buildMeshletsScan(ml, mv, mt, ib, indices, 100, /* max_vertices= */ 4, /* max_triangles= */ 4);
|
||||
meshopt_buildMeshletsScan(ml, mv, mt, ib, indices, 100, /* max_vertices= */ 4, /* max_triangles= */ 8);
|
||||
|
||||
meshopt_buildMeshletsFlex(ml, mv, mt, ib, indices, vb[0], 100, sizeof(float) * 4, /* max_vertices= */ 4, /* min_triangles= */ 4, /* max_triangles= */ 8, 0.f, 2.f);
|
||||
meshopt_buildMeshletsFlex(ml, mv, mt, ib, indices, vb[0], 100, sizeof(float) * 4, /* max_vertices= */ 8, /* min_triangles= */ 8, /* max_triangles= */ 16, 0.f, 2.f);
|
||||
meshopt_buildMeshletsFlex(ml, mv, mt, ib, indices, vb[0], 100, sizeof(float) * 4, /* max_vertices= */ 16, /* min_triangles= */ 8, /* max_triangles= */ 32, 0.f, 2.f);
|
||||
meshopt_buildMeshletsFlex(ml, mv, mt, ib, indices, vb[0], 100, sizeof(float) * 4, /* max_vertices= */ 16, /* min_triangles= */ 16, /* max_triangles= */ 32, 0.f, 2.f);
|
||||
|
||||
meshopt_buildMeshletsSpatial(ml, mv, mt, ib, indices, vb[0], 100, sizeof(float) * 4, /* max_vertices= */ 4, /* min_triangles= */ 4, /* max_triangles= */ 8, 0.f);
|
||||
meshopt_buildMeshletsSpatial(ml, mv, mt, ib, indices, vb[0], 100, sizeof(float) * 4, /* max_vertices= */ 8, /* min_triangles= */ 4, /* max_triangles= */ 32, 0.f);
|
||||
meshopt_buildMeshletsSpatial(ml, mv, mt, ib, indices, vb[0], 100, sizeof(float) * 4, /* max_vertices= */ 8, /* min_triangles= */ 8, /* max_triangles= */ 32, 0.f);
|
||||
meshopt_buildMeshletsSpatial(ml, mv, mt, ib, indices, vb[0], 100, sizeof(float) * 4, /* max_vertices= */ 8, /* min_triangles= */ 12, /* max_triangles= */ 32, 0.f);
|
||||
meshopt_buildMeshletsSpatial(ml, mv, mt, ib, indices, vb[0], 100, sizeof(float) * 4, /* max_vertices= */ 16, /* min_triangles= */ 16, /* max_triangles= */ 32, 0.f);
|
||||
meshopt_buildMeshletsSpatial(ml, mv, mt, ib, indices, vb[0], 100, sizeof(float) * 4, /* max_vertices= */ 16, /* min_triangles= */ 32, /* max_triangles= */ 32, 0.f);
|
||||
|
||||
unsigned int part[333];
|
||||
unsigned int clsize[333];
|
||||
|
||||
// treat each triangle as a cluster
|
||||
for (int i = 0; i < indices; ++i)
|
||||
clsize[i / 3] = 3;
|
||||
|
||||
meshopt_partitionClusters(part, ib, indices, clsize, indices / 3, vb[0], 100, sizeof(float) * 4, /* target_group_size= */ 12);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,453 @@
|
||||
#include "../src/meshoptimizer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten.h>
|
||||
|
||||
double timestamp()
|
||||
{
|
||||
return emscripten_get_now() * 1e-3;
|
||||
}
|
||||
#elif defined(_WIN32)
|
||||
struct LARGE_INTEGER
|
||||
{
|
||||
__int64 QuadPart;
|
||||
};
|
||||
extern "C" __declspec(dllimport) int __stdcall QueryPerformanceCounter(LARGE_INTEGER* lpPerformanceCount);
|
||||
extern "C" __declspec(dllimport) int __stdcall QueryPerformanceFrequency(LARGE_INTEGER* lpFrequency);
|
||||
|
||||
double timestamp()
|
||||
{
|
||||
LARGE_INTEGER freq, counter;
|
||||
QueryPerformanceFrequency(&freq);
|
||||
QueryPerformanceCounter(&counter);
|
||||
return double(counter.QuadPart) / double(freq.QuadPart);
|
||||
}
|
||||
#else
|
||||
double timestamp()
|
||||
{
|
||||
timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return double(ts.tv_sec) + 1e-9 * double(ts.tv_nsec);
|
||||
}
|
||||
#endif
|
||||
|
||||
struct Vertex
|
||||
{
|
||||
uint16_t data[16];
|
||||
};
|
||||
|
||||
uint32_t murmur3(uint32_t h)
|
||||
{
|
||||
h ^= h >> 16;
|
||||
h *= 0x85ebca6bu;
|
||||
h ^= h >> 13;
|
||||
h *= 0xc2b2ae35u;
|
||||
h ^= h >> 16;
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
void benchCodecs(const std::vector<Vertex>& vertices, const std::vector<unsigned int>& indices, double& bestvd, double& bestid, bool verbose)
|
||||
{
|
||||
std::vector<Vertex> vb(vertices.size());
|
||||
std::vector<unsigned int> ib(indices.size());
|
||||
|
||||
std::vector<unsigned char> vc(meshopt_encodeVertexBufferBound(vertices.size(), sizeof(Vertex)));
|
||||
std::vector<unsigned char> ic(meshopt_encodeIndexBufferBound(indices.size(), vertices.size()));
|
||||
|
||||
if (verbose)
|
||||
printf("source: vertex data %d bytes, index data %d bytes\n", int(vertices.size() * sizeof(Vertex)), int(indices.size() * 4));
|
||||
|
||||
meshopt_optimizeVertexCache(&ib[0], &indices[0], indices.size(), vertices.size());
|
||||
|
||||
meshopt_optimizeVertexFetch(&vb[0], &ib[0], indices.size(), &vertices[0], vertices.size(), sizeof(Vertex));
|
||||
|
||||
vc.resize(vc.capacity());
|
||||
vc.resize(meshopt_encodeVertexBuffer(&vc[0], vc.size(), &vb[0], vertices.size(), sizeof(Vertex)));
|
||||
|
||||
ic.resize(ic.capacity());
|
||||
ic.resize(meshopt_encodeIndexBuffer(&ic[0], ic.size(), &ib[0], indices.size()));
|
||||
|
||||
if (verbose)
|
||||
printf("encode: vertex data %d bytes, index data %d bytes\n", int(vc.size()), int(ic.size()));
|
||||
|
||||
for (int attempt = 0; attempt < 50; ++attempt)
|
||||
{
|
||||
double t0 = timestamp();
|
||||
|
||||
int rv = meshopt_decodeVertexBuffer(&vb[0], vertices.size(), sizeof(Vertex), &vc[0], vc.size());
|
||||
assert(rv == 0);
|
||||
(void)rv;
|
||||
|
||||
double t1 = timestamp();
|
||||
|
||||
int ri = meshopt_decodeIndexBuffer(&ib[0], indices.size(), 4, &ic[0], ic.size());
|
||||
assert(ri == 0);
|
||||
(void)ri;
|
||||
|
||||
double t2 = timestamp();
|
||||
|
||||
if (verbose)
|
||||
printf("decode: vertex %.2f ms (%.2f GB/sec), index %.2f ms (%.2f GB/sec)\n",
|
||||
(t1 - t0) * 1000, double(vertices.size() * sizeof(Vertex)) / 1e9 / (t1 - t0),
|
||||
(t2 - t1) * 1000, double(indices.size() * 4) / 1e9 / (t2 - t1));
|
||||
|
||||
bestvd = std::max(bestvd, double(vertices.size() * sizeof(Vertex)) / 1e9 / (t1 - t0));
|
||||
bestid = std::max(bestid, double(indices.size() * 4) / 1e9 / (t2 - t1));
|
||||
}
|
||||
}
|
||||
|
||||
void benchFilters(size_t count, double& besto8, double& besto12, double& bestq12, double& bestc8, double& bestc12, double& bestexp, bool verbose)
|
||||
{
|
||||
// note: the filters are branchless so we just run them on runs of zeroes
|
||||
size_t count4 = (count + 3) & ~3;
|
||||
std::vector<unsigned char> d4(count4 * 4);
|
||||
std::vector<unsigned char> d8(count4 * 8);
|
||||
|
||||
if (verbose)
|
||||
printf("filters: oct8 data %d bytes, oct12/quat12 data %d bytes\n", int(d4.size()), int(d8.size()));
|
||||
|
||||
for (int attempt = 0; attempt < 50; ++attempt)
|
||||
{
|
||||
double t0 = timestamp();
|
||||
|
||||
meshopt_decodeFilterOct(&d4[0], count4, 4);
|
||||
|
||||
double t1 = timestamp();
|
||||
|
||||
meshopt_decodeFilterOct(&d8[0], count4, 8);
|
||||
|
||||
double t2 = timestamp();
|
||||
|
||||
meshopt_decodeFilterQuat(&d8[0], count4, 8);
|
||||
|
||||
double t3 = timestamp();
|
||||
|
||||
meshopt_decodeFilterColor(&d4[0], count4, 4);
|
||||
|
||||
double t4 = timestamp();
|
||||
|
||||
meshopt_decodeFilterColor(&d8[0], count4, 8);
|
||||
|
||||
double t5 = timestamp();
|
||||
|
||||
meshopt_decodeFilterExp(&d8[0], count4, 8);
|
||||
|
||||
double t6 = timestamp();
|
||||
|
||||
if (verbose)
|
||||
printf("filter: oct8 %.2f ms (%.2f GB/sec), oct12 %.2f ms (%.2f GB/sec), quat12 %.2f ms (%.2f GB/sec), col8 %.2f ms (%.2f GB/sec), col12 %.2f ms (%.2f GB/sec), exp %.2f ms (%.2f GB/sec)\n",
|
||||
(t1 - t0) * 1000, double(d4.size()) / 1e9 / (t1 - t0),
|
||||
(t2 - t1) * 1000, double(d8.size()) / 1e9 / (t2 - t1),
|
||||
(t3 - t2) * 1000, double(d8.size()) / 1e9 / (t3 - t2),
|
||||
(t4 - t3) * 1000, double(d4.size()) / 1e9 / (t4 - t3),
|
||||
(t5 - t4) * 1000, double(d8.size()) / 1e9 / (t5 - t4),
|
||||
(t6 - t5) * 1000, double(d8.size()) / 1e9 / (t6 - t5));
|
||||
|
||||
besto8 = std::max(besto8, double(d4.size()) / 1e9 / (t1 - t0));
|
||||
besto12 = std::max(besto12, double(d8.size()) / 1e9 / (t2 - t1));
|
||||
bestq12 = std::max(bestq12, double(d8.size()) / 1e9 / (t3 - t2));
|
||||
bestc8 = std::max(bestc8, double(d4.size()) / 1e9 / (t4 - t3));
|
||||
bestc12 = std::max(bestc12, double(d8.size()) / 1e9 / (t5 - t4));
|
||||
bestexp = std::max(bestexp, double(d8.size()) / 1e9 / (t6 - t5));
|
||||
}
|
||||
}
|
||||
|
||||
void benchMeshlets(const std::vector<float>& positions, const std::vector<unsigned int>& indices, bool encoderefs, double& bestml, bool verbose)
|
||||
{
|
||||
const size_t max_vertices = 64;
|
||||
const size_t max_triangles = 96;
|
||||
|
||||
size_t max_meshlets = meshopt_buildMeshletsBound(indices.size(), max_vertices, max_triangles);
|
||||
std::vector<meshopt_Meshlet> meshlets(max_meshlets);
|
||||
std::vector<unsigned int> meshlet_vertices(indices.size());
|
||||
std::vector<unsigned char> meshlet_triangles(indices.size());
|
||||
|
||||
meshlets.resize(meshopt_buildMeshlets(meshlets.data(), meshlet_vertices.data(), meshlet_triangles.data(),
|
||||
indices.data(), indices.size(), positions.data(), positions.size() / 3, sizeof(float) * 3, max_vertices, max_triangles, 0.f));
|
||||
|
||||
const meshopt_Meshlet& last = meshlets.back();
|
||||
|
||||
meshlet_vertices.resize(last.vertex_offset + last.vertex_count);
|
||||
meshlet_triangles.resize(last.triangle_offset + last.triangle_count * 3);
|
||||
|
||||
std::vector<unsigned char> cbuf(meshopt_encodeMeshletBound(max_vertices, max_triangles));
|
||||
|
||||
// optimize each meshlet for locality before encoding
|
||||
for (size_t i = 0; i < meshlets.size(); ++i)
|
||||
meshopt_optimizeMeshletLevel(&meshlet_vertices[meshlets[i].vertex_offset], meshlets[i].vertex_count, &meshlet_triangles[meshlets[i].triangle_offset], meshlets[i].triangle_count, 3);
|
||||
|
||||
// optimize meshlet_vertices for locality (requires vertex reorder)
|
||||
std::vector<float> positionscopy(positions.size());
|
||||
meshopt_optimizeVertexFetch(&positionscopy[0], &meshlet_vertices[0], meshlet_vertices.size(), &positions[0], positions.size() / 3, sizeof(float) * 3);
|
||||
|
||||
#if 0 // TODO: disabled for now to make meshlet decoder tuning more accurate; might make sense to re-enable in the future for SOL timings
|
||||
// pack meshlets in decreasing triangle count order to reduce branch mispredictions
|
||||
// (this matters less on real-world meshes but is more prominent on regular grids?)
|
||||
std::sort(meshlets.begin(), meshlets.end(), [](const meshopt_Meshlet& lhs, const meshopt_Meshlet& rhs)
|
||||
{ return lhs.triangle_count > rhs.triangle_count; });
|
||||
#endif
|
||||
|
||||
std::vector<unsigned char> packed;
|
||||
|
||||
size_t totals = 0;
|
||||
|
||||
for (size_t i = 0; i < meshlets.size(); ++i)
|
||||
{
|
||||
const meshopt_Meshlet& meshlet = meshlets[i];
|
||||
|
||||
size_t mvc = encoderefs ? meshlet.vertex_count : 0;
|
||||
|
||||
size_t mbs = meshopt_encodeMeshlet(&cbuf[0], cbuf.size(), &meshlet_vertices[meshlet.vertex_offset], mvc, &meshlet_triangles[meshlet.triangle_offset], meshlet.triangle_count);
|
||||
assert(mbs > 0);
|
||||
|
||||
packed.push_back((unsigned char)mvc);
|
||||
packed.push_back((unsigned char)meshlet.triangle_count);
|
||||
packed.push_back((unsigned char)(mbs & 0xff));
|
||||
packed.push_back((unsigned char)((mbs >> 8) & 0xff));
|
||||
packed.insert(packed.end(), cbuf.begin(), cbuf.begin() + mbs);
|
||||
|
||||
totals += size_t(meshlet.triangle_count) * 3 + size_t(mvc) * 4;
|
||||
}
|
||||
|
||||
if (verbose)
|
||||
printf("meshlets (%d/%d): %d meshlets, packed %d bytes (%.1f bits/triangle)\n",
|
||||
int(max_vertices), int(max_triangles), int(meshlets.size()), int(packed.size()), double(packed.size() * 8) / double(indices.size() / 3));
|
||||
|
||||
unsigned int rv[256];
|
||||
unsigned char rt[256 * 3];
|
||||
|
||||
for (int attempt = 0; attempt < 50; ++attempt)
|
||||
{
|
||||
double t0 = timestamp();
|
||||
|
||||
const unsigned char* p = &packed[0];
|
||||
|
||||
for (size_t i = 0; i < meshlets.size(); ++i)
|
||||
{
|
||||
size_t mbs = p[2] | (p[3] << 8);
|
||||
int rc = meshopt_decodeMeshlet(rv, p[0], rt, p[1], p + 4, mbs);
|
||||
assert(rc == 0);
|
||||
(void)rc;
|
||||
p += 4 + mbs;
|
||||
}
|
||||
|
||||
assert(p == &packed[0] + packed.size());
|
||||
|
||||
double t1 = timestamp();
|
||||
|
||||
if (verbose)
|
||||
printf("meshlet decode: %.2f ms (%.2f GB/sec)\n", (t1 - t0) * 1000, double(totals) / 1e9 / (t1 - t0));
|
||||
|
||||
bestml = std::max(bestml, double(totals) / 1e9 / (t1 - t0));
|
||||
}
|
||||
}
|
||||
|
||||
struct File
|
||||
{
|
||||
std::vector<unsigned char> data;
|
||||
size_t stride;
|
||||
size_t count;
|
||||
bool index;
|
||||
};
|
||||
|
||||
File readFile(const char* path)
|
||||
{
|
||||
FILE* file = fopen(path, "rb");
|
||||
assert(file);
|
||||
|
||||
const char* name = strrchr(path, '/');
|
||||
name = name ? name + 1 : path;
|
||||
|
||||
std::vector<unsigned char> input;
|
||||
unsigned char buffer[4096];
|
||||
size_t bytes_read;
|
||||
|
||||
while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0)
|
||||
input.insert(input.end(), buffer, buffer + bytes_read);
|
||||
|
||||
fclose(file);
|
||||
|
||||
int vcnt, vsz, icnt;
|
||||
|
||||
if (sscanf(name, "v%d_s%d_", &vcnt, &vsz) == 2)
|
||||
{
|
||||
File result = {};
|
||||
result.count = vcnt;
|
||||
result.stride = vsz;
|
||||
|
||||
std::vector<unsigned char> decoded(result.count * result.stride);
|
||||
int res = meshopt_decodeVertexBuffer(&decoded[0], result.count, result.stride, &input[0], input.size());
|
||||
if (res != 0 && input.size() == decoded.size())
|
||||
{
|
||||
// some files are not encoded
|
||||
memcpy(decoded.data(), input.data(), decoded.size());
|
||||
}
|
||||
|
||||
result.data.resize(meshopt_encodeVertexBufferBound(result.count, result.stride));
|
||||
result.data.resize(meshopt_encodeVertexBuffer(result.data.data(), result.data.size(), decoded.data(), result.count, result.stride));
|
||||
|
||||
return result;
|
||||
}
|
||||
else if (sscanf(name, "i%d_", &icnt) == 1)
|
||||
{
|
||||
File result = {};
|
||||
result.count = icnt;
|
||||
result.stride = icnt <= 0xffff ? 2 : 4; // note: technically 2-byte indices can be used for larger meshes in some cases, but we simplify
|
||||
result.index = true;
|
||||
result.data = input;
|
||||
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(!"Unrecognized file name");
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
bool verbose = false;
|
||||
bool loop = false;
|
||||
bool inputs = false;
|
||||
|
||||
for (int i = 1; i < argc; ++i)
|
||||
{
|
||||
if (strcmp(argv[i], "-v") == 0)
|
||||
verbose = true;
|
||||
if (strcmp(argv[i], "-l") == 0)
|
||||
loop = true;
|
||||
if (argv[i][0] != '-')
|
||||
inputs = true;
|
||||
}
|
||||
|
||||
if (inputs)
|
||||
{
|
||||
std::vector<File> files;
|
||||
for (int i = 1; i < argc; ++i)
|
||||
if (argv[i][0] != '-')
|
||||
files.push_back(readFile(argv[i]));
|
||||
|
||||
size_t max_size = 0;
|
||||
size_t total_size = 0;
|
||||
size_t total_comp = 0;
|
||||
for (size_t i = 0; i < files.size(); ++i)
|
||||
{
|
||||
max_size = std::max(max_size, files[i].count * files[i].stride);
|
||||
total_size += files[i].count * files[i].stride;
|
||||
total_comp += files[i].data.size();
|
||||
}
|
||||
|
||||
printf("%d files, ratio %.2f (%.2f MB => %.2f MB)\n",
|
||||
int(files.size()), double(total_comp) / double(total_size), double(total_size) / 1024 / 1024, double(total_comp) / 1024 / 1024);
|
||||
|
||||
std::vector<unsigned char> buffer(max_size);
|
||||
|
||||
for (int l = 0; l < (loop ? 100 : 1); ++l)
|
||||
{
|
||||
double bestvd = 0;
|
||||
|
||||
for (int attempt = 0; attempt < 50; ++attempt)
|
||||
{
|
||||
double t0 = timestamp();
|
||||
|
||||
for (const File& file : files)
|
||||
{
|
||||
int rc =
|
||||
file.index
|
||||
? meshopt_decodeIndexBuffer(&buffer[0], file.count, int(file.stride), &file.data[0], file.data.size())
|
||||
: meshopt_decodeVertexBuffer(&buffer[0], file.count, file.stride, &file.data[0], file.data.size());
|
||||
|
||||
if (rc != 0)
|
||||
{
|
||||
fprintf(stderr, "Failed to decode %zu bytes into %zu elements with stride %d (index %d)\n", file.data.size(), file.count, int(file.stride), int(file.index));
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
double t1 = timestamp();
|
||||
|
||||
bestvd = std::max(bestvd, double(total_size) / 1e9 / (t1 - t0));
|
||||
}
|
||||
|
||||
printf("Score (GB/s):\t%.2f\n", bestvd);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const int N = 1000;
|
||||
|
||||
std::vector<Vertex> vertices;
|
||||
vertices.reserve((N + 1) * (N + 1));
|
||||
|
||||
std::vector<float> positions((N + 1) * (N + 1) * 3);
|
||||
|
||||
for (int x = 0; x <= N; ++x)
|
||||
{
|
||||
for (int y = 0; y <= N; ++y)
|
||||
{
|
||||
Vertex v;
|
||||
|
||||
for (int k = 0; k < 16; ++k)
|
||||
{
|
||||
uint32_t h = murmur3((x * (N + 1) + y) * 16 + k);
|
||||
|
||||
// use random k-bit sequence for each word to test all encoding types
|
||||
// note: this doesn't stress the sentinel logic too much but it's all branchless so it's probably fine?
|
||||
v.data[k] = h & ((1 << (k + 1)) - 1);
|
||||
}
|
||||
|
||||
size_t index = vertices.size();
|
||||
|
||||
positions[index * 3 + 0] = float(x);
|
||||
positions[index * 3 + 1] = float(y);
|
||||
positions[index * 3 + 2] = 0.f;
|
||||
|
||||
vertices.push_back(v);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<unsigned int> indices;
|
||||
indices.reserve(N * N * 6);
|
||||
|
||||
for (int x = 0; x < N; ++x)
|
||||
{
|
||||
for (int y = 0; y < N; ++y)
|
||||
{
|
||||
indices.push_back((x + 0) * (N + 1) + (y + 0));
|
||||
indices.push_back((x + 1) * (N + 1) + (y + 0));
|
||||
indices.push_back((x + 0) * (N + 1) + (y + 1));
|
||||
|
||||
indices.push_back((x + 0) * (N + 1) + (y + 1));
|
||||
indices.push_back((x + 1) * (N + 1) + (y + 0));
|
||||
indices.push_back((x + 1) * (N + 1) + (y + 1));
|
||||
}
|
||||
}
|
||||
|
||||
printf("Codec:\tvtx\tidx\tmlet\tmletΔ\toct8\toct12\tquat12\tcol8\tcol12\texp\n");
|
||||
|
||||
for (int l = 0; l < (loop ? 100 : 1); ++l)
|
||||
{
|
||||
double bestvd = 0, bestid = 0;
|
||||
benchCodecs(vertices, indices, bestvd, bestid, verbose);
|
||||
|
||||
double bestml = 0, bestmt = 0;
|
||||
benchMeshlets(positions, indices, /* encoderefs= */ true, bestml, verbose);
|
||||
benchMeshlets(positions, indices, /* encoderefs= */ false, bestmt, verbose);
|
||||
|
||||
double besto8 = 0, besto12 = 0, bestq12 = 0, bestc8 = 0, bestc12 = 0, bestexp = 0;
|
||||
benchFilters(8 * N * N, besto8, besto12, bestq12, bestc8, bestc12, bestexp, verbose);
|
||||
|
||||
printf("GB/s :\t%.2f\t%.2f\t%.2f\t%.2f\t%.2f\t%.2f\t%.2f\t%.2f\t%.2f\t%.2f\n",
|
||||
bestvd, bestid, bestml, bestmt, besto8, besto12, bestq12, bestc8, bestc12, bestexp);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
#include "../src/meshoptimizer.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
void fuzzDecoder(const uint8_t* data, size_t size, size_t stride, int (*decode)(void*, size_t, size_t, const unsigned char*, size_t))
|
||||
{
|
||||
size_t count = 66; // must be divisible by 3 for decodeIndexBuffer; should be >=64 to cover large vertex blocks
|
||||
|
||||
void* destination = malloc(count * stride);
|
||||
assert(destination);
|
||||
|
||||
int rc = decode(destination, count, stride, reinterpret_cast<const unsigned char*>(data), size);
|
||||
(void)rc;
|
||||
|
||||
free(destination);
|
||||
}
|
||||
|
||||
void fuzzRoundtrip(const uint8_t* data, size_t size, size_t stride, int level)
|
||||
{
|
||||
size_t count = size / stride;
|
||||
|
||||
size_t bound = meshopt_encodeVertexBufferBound(count, stride);
|
||||
void* encoded = malloc(bound);
|
||||
void* decoded = malloc(count * stride);
|
||||
assert(encoded && decoded);
|
||||
|
||||
size_t res = meshopt_encodeVertexBufferLevel(static_cast<unsigned char*>(encoded), bound, data, count, stride, level, -1);
|
||||
assert(res > 0 && res <= bound);
|
||||
|
||||
// encode again at the boundary to check for memory safety
|
||||
// this should produce the same output because encoder is deterministic
|
||||
size_t rese = meshopt_encodeVertexBufferLevel(static_cast<unsigned char*>(encoded) + bound - res, res, data, count, stride, level, -1);
|
||||
assert(rese == res);
|
||||
|
||||
int rc = meshopt_decodeVertexBuffer(decoded, count, stride, static_cast<unsigned char*>(encoded) + bound - res, res);
|
||||
assert(rc == 0);
|
||||
|
||||
assert(memcmp(data, decoded, count * stride) == 0);
|
||||
|
||||
free(decoded);
|
||||
free(encoded);
|
||||
}
|
||||
|
||||
size_t align(size_t value, size_t alignment)
|
||||
{
|
||||
return (value + alignment - 1) & ~(alignment - 1);
|
||||
}
|
||||
|
||||
void fuzzDecodeMeshlet(size_t vertex_count, size_t triangle_count, const unsigned char* data, size_t size)
|
||||
{
|
||||
// raw decoding: allowed to write align(count, 4) elements
|
||||
unsigned int rt[256];
|
||||
unsigned int rv[256];
|
||||
meshopt_decodeMeshletRaw(rv + 256 - align(vertex_count, 4), vertex_count, rt + 256 - align(triangle_count, 4), triangle_count, data, size);
|
||||
|
||||
// regular decoding: allowed to write align(count * size, 4) bytes
|
||||
// with variations for 3-byte triangles and 2-byte vertex references
|
||||
unsigned short rsv[256];
|
||||
unsigned char rbt[256 * 3];
|
||||
|
||||
meshopt_decodeMeshlet(rv + 256 - vertex_count, vertex_count, 4, rt + 256 - triangle_count, triangle_count, 4, data, size);
|
||||
meshopt_decodeMeshlet(rsv + 256 - align(vertex_count, 2), vertex_count, 2, rt + 256 - triangle_count, triangle_count, 4, data, size);
|
||||
meshopt_decodeMeshlet(rv + 256 - vertex_count, vertex_count, 4, rbt + 256 * 3 - align(triangle_count * 3, 4), triangle_count, 3, data, size);
|
||||
meshopt_decodeMeshlet(rsv + 256 - align(vertex_count, 2), vertex_count, 2, rbt + 256 * 3 - align(triangle_count * 3, 4), triangle_count, 3, data, size);
|
||||
}
|
||||
|
||||
void fuzzRoundtripMeshlet(const uint8_t* data, size_t size)
|
||||
{
|
||||
size_t triangle_count = size / 3;
|
||||
if (triangle_count > 256)
|
||||
triangle_count = 256;
|
||||
|
||||
unsigned char buf[4096];
|
||||
size_t enc = meshopt_encodeMeshlet(buf, sizeof(buf), NULL, 0, reinterpret_cast<const unsigned char*>(data), triangle_count);
|
||||
assert(enc > 0);
|
||||
assert(enc <= meshopt_encodeMeshletBound(0, triangle_count));
|
||||
|
||||
unsigned int rt4[256];
|
||||
int rc4 = meshopt_decodeMeshlet(static_cast<unsigned int*>(NULL), 0, rt4, triangle_count, buf, enc);
|
||||
assert(rc4 == 0);
|
||||
|
||||
for (size_t i = 0; i < triangle_count; ++i)
|
||||
{
|
||||
unsigned char a = data[i * 3 + 0], b = data[i * 3 + 1], c = data[i * 3 + 2];
|
||||
|
||||
unsigned int abc = (a << 0) | (b << 8) | (c << 16);
|
||||
unsigned int bca = (b << 0) | (c << 8) | (a << 16);
|
||||
unsigned int cba = (c << 0) | (a << 8) | (b << 16);
|
||||
|
||||
unsigned int tri = rt4[i];
|
||||
|
||||
assert(tri == abc || tri == bca || tri == cba);
|
||||
}
|
||||
|
||||
unsigned char rt3[256 * 3];
|
||||
int rc3 = meshopt_decodeMeshlet(static_cast<unsigned int*>(NULL), 0, rt3, triangle_count, buf, enc);
|
||||
assert(rc3 == 0);
|
||||
|
||||
for (size_t i = 0; i < triangle_count; ++i)
|
||||
{
|
||||
unsigned char a = data[i * 3 + 0], b = data[i * 3 + 1], c = data[i * 3 + 2];
|
||||
|
||||
unsigned int abc = (a << 0) | (b << 8) | (c << 16);
|
||||
unsigned int bca = (b << 0) | (c << 8) | (a << 16);
|
||||
unsigned int cba = (c << 0) | (a << 8) | (b << 16);
|
||||
|
||||
unsigned int tri = rt3[i * 3 + 0] | (rt3[i * 3 + 1] << 8) | (rt3[i * 3 + 2] << 16);
|
||||
|
||||
assert(tri == abc || tri == bca || tri == cba);
|
||||
}
|
||||
}
|
||||
|
||||
void fuzzRoundtripMeshletV(const uint8_t* data, size_t size)
|
||||
{
|
||||
size_t vertex_count = size / 4;
|
||||
if (vertex_count > 256)
|
||||
vertex_count = 256;
|
||||
|
||||
unsigned char tri[4] = {0, 1, 2};
|
||||
|
||||
unsigned char buf[4096];
|
||||
size_t enc = meshopt_encodeMeshlet(buf, sizeof(buf), reinterpret_cast<const uint32_t*>(data), vertex_count, tri, 1);
|
||||
assert(enc > 0);
|
||||
assert(enc <= meshopt_encodeMeshletBound(vertex_count, 1));
|
||||
|
||||
unsigned int rv4[256];
|
||||
int rc4 = meshopt_decodeMeshlet(rv4, vertex_count, tri, 1, buf, enc);
|
||||
assert(rc4 == 0);
|
||||
|
||||
for (size_t i = 0; i < vertex_count; ++i)
|
||||
assert(rv4[i] == reinterpret_cast<const uint32_t*>(data)[i]);
|
||||
|
||||
unsigned short rv2[256];
|
||||
int rc2 = meshopt_decodeMeshlet(rv2, vertex_count, tri, 1, buf, enc);
|
||||
assert(rc2 == 0);
|
||||
|
||||
for (size_t i = 0; i < vertex_count; ++i)
|
||||
assert(rv2[i] == uint16_t(reinterpret_cast<const uint32_t*>(data)[i]));
|
||||
}
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
// decodeIndexBuffer supports 2 and 4-byte indices
|
||||
fuzzDecoder(data, size, 2, meshopt_decodeIndexBuffer);
|
||||
fuzzDecoder(data, size, 4, meshopt_decodeIndexBuffer);
|
||||
|
||||
// decodeIndexSequence supports 2 and 4-byte indices
|
||||
fuzzDecoder(data, size, 2, meshopt_decodeIndexSequence);
|
||||
fuzzDecoder(data, size, 4, meshopt_decodeIndexSequence);
|
||||
|
||||
// decodeVertexBuffer supports any strides divisible by 4 in 4-256 interval
|
||||
// it's a waste of time to check all of them, so we'll just check a few with different alignment mod 16
|
||||
fuzzDecoder(data, size, 4, meshopt_decodeVertexBuffer);
|
||||
fuzzDecoder(data, size, 16, meshopt_decodeVertexBuffer);
|
||||
fuzzDecoder(data, size, 24, meshopt_decodeVertexBuffer);
|
||||
fuzzDecoder(data, size, 32, meshopt_decodeVertexBuffer);
|
||||
|
||||
// encodeVertexBuffer/decodeVertexBuffer should roundtrip for any stride, check a few with different alignment mod 16
|
||||
// this also checks memory safety properties of the encoder
|
||||
// to conserve time, we only check one version/level combination, biased towards version 1
|
||||
uint8_t data0 = size > 0 ? data[0] : 0;
|
||||
int level = data0 % 5;
|
||||
|
||||
meshopt_encodeVertexVersion(level < 4 ? 1 : 0);
|
||||
|
||||
fuzzRoundtrip(data, size, 4, level);
|
||||
fuzzRoundtrip(data, size, 16, level);
|
||||
fuzzRoundtrip(data, size, 24, level);
|
||||
fuzzRoundtrip(data, size, 32, level);
|
||||
|
||||
// validate that decodeMeshlet works on untrusted data and is memory safe within documented limits
|
||||
if (size > 2)
|
||||
fuzzDecodeMeshlet(data[0] + 1, data[1] + 1, reinterpret_cast<const unsigned char*>(data + 2), size - 2);
|
||||
|
||||
// validate that index data roundtrips in meshlet encoding modulo rotation
|
||||
fuzzRoundtripMeshlet(data, size);
|
||||
|
||||
// validate that vertex data roundtrips in meshlet encoding
|
||||
fuzzRoundtripMeshletV(data, size);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
#include "../src/meshoptimizer.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <fcntl.h>
|
||||
#include <io.h>
|
||||
#endif
|
||||
|
||||
size_t measure(const char* cmd, const std::vector<unsigned char>& data)
|
||||
{
|
||||
FILE* file = fopen("/tmp/codectest.in", "wb");
|
||||
if (!file)
|
||||
return 0;
|
||||
fwrite(data.data(), data.size(), 1, file);
|
||||
fclose(file);
|
||||
|
||||
char actualcmd[1024];
|
||||
snprintf(actualcmd, sizeof(actualcmd), cmd, "/tmp/codectest.in", "/tmp/codectest.out");
|
||||
|
||||
int rc = system(actualcmd);
|
||||
if (rc)
|
||||
return 0;
|
||||
|
||||
file = fopen("/tmp/codectest.out", "rb");
|
||||
if (!file)
|
||||
return 0;
|
||||
fseek(file, 0, SEEK_END);
|
||||
long result = ftell(file);
|
||||
fclose(file);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t measure_lz4(const std::vector<unsigned char>& data)
|
||||
{
|
||||
return measure("lz4 -f -q %s %s", data);
|
||||
}
|
||||
|
||||
size_t measure_zstd(const std::vector<unsigned char>& data)
|
||||
{
|
||||
return measure("zstd -f -q %s -o %s", data);
|
||||
}
|
||||
|
||||
struct Stats
|
||||
{
|
||||
bool testz;
|
||||
double count;
|
||||
double size;
|
||||
|
||||
double ratio;
|
||||
double ratio_lz4;
|
||||
double ratio_zstd;
|
||||
|
||||
double total;
|
||||
double total_lz4;
|
||||
double total_zstd;
|
||||
};
|
||||
|
||||
void testFile(FILE* file, size_t count, size_t stride, int level, Stats* stats = 0)
|
||||
{
|
||||
std::vector<unsigned char> input;
|
||||
unsigned char buffer[4096];
|
||||
size_t bytes_read;
|
||||
|
||||
while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0)
|
||||
input.insert(input.end(), buffer, buffer + bytes_read);
|
||||
|
||||
std::vector<unsigned char> decoded(count * stride);
|
||||
int res = meshopt_decodeVertexBuffer(&decoded[0], count, stride, &input[0], input.size());
|
||||
if (res != 0 && input.size() == decoded.size())
|
||||
{
|
||||
// some files are not encoded
|
||||
memcpy(decoded.data(), input.data(), decoded.size());
|
||||
}
|
||||
else if (res != 0)
|
||||
{
|
||||
printf(" error decoding input: %d", res);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<unsigned char> output(meshopt_encodeVertexBufferBound(count, stride));
|
||||
output.resize(meshopt_encodeVertexBufferLevel(output.data(), output.size(), decoded.data(), count, stride, level, -1));
|
||||
|
||||
printf(" raw %zu KB\t", decoded.size() / 1024);
|
||||
printf(" vtx %.3f", double(output.size()) / double(decoded.size()));
|
||||
|
||||
if (stats)
|
||||
{
|
||||
stats->count++;
|
||||
stats->size += double(decoded.size());
|
||||
|
||||
stats->total += double(output.size());
|
||||
stats->ratio += log(double(output.size()) / double(decoded.size()));
|
||||
}
|
||||
|
||||
if (stats && stats->testz)
|
||||
{
|
||||
size_t decoded_lz4 = measure_lz4(decoded);
|
||||
size_t output_lz4 = measure_lz4(output);
|
||||
size_t decoded_zstd = measure_zstd(decoded);
|
||||
size_t output_zstd = measure_zstd(output);
|
||||
|
||||
stats->total_lz4 += output_lz4;
|
||||
stats->total_zstd += output_zstd;
|
||||
|
||||
stats->ratio_lz4 += log(double(output_lz4) / double(decoded.size()));
|
||||
stats->ratio_zstd += log(double(output_zstd) / double(decoded.size()));
|
||||
|
||||
printf("\tlz4 %.3f", double(decoded_lz4) / double(decoded.size()));
|
||||
printf(" vtx+lz4 %.3f", double(output_lz4) / double(decoded.size()));
|
||||
|
||||
printf("\tzstd %.3f", double(decoded_zstd) / double(decoded.size()));
|
||||
printf(" vtx+zstd %.3f", double(output_zstd) / double(decoded.size()));
|
||||
}
|
||||
}
|
||||
|
||||
void testFile(const char* path, int level, Stats* stats = 0)
|
||||
{
|
||||
FILE* file = fopen(path, "rb");
|
||||
if (!file)
|
||||
return;
|
||||
|
||||
const char* name = strrchr(path, '/');
|
||||
name = name ? name + 1 : path;
|
||||
|
||||
int vcnt, vsz;
|
||||
int sr = sscanf(name, "v%d_s%d_", &vcnt, &vsz);
|
||||
assert(sr == 2);
|
||||
(void)sr;
|
||||
|
||||
const char* name0 = strchr(strchr(name, '_') + 1, '_') + 1;
|
||||
const char* name1 = strstr(name0, "_R");
|
||||
size_t namel = name1 ? name1 - name0 : strlen(name0);
|
||||
namel = namel > 25 ? 25 : namel;
|
||||
|
||||
#if TRACE
|
||||
printf("%.*s: %s\n", int(namel), name0, path);
|
||||
#else
|
||||
printf("%25.*s:", int(namel), name0);
|
||||
#endif
|
||||
|
||||
testFile(file, vcnt, vsz, level, stats);
|
||||
printf("\n");
|
||||
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
_setmode(_fileno(stdin), _O_BINARY);
|
||||
_setmode(_fileno(stdout), _O_BINARY);
|
||||
#endif
|
||||
|
||||
if (argc > 1 && (strcmp(argv[1], "-test") == 0 || strcmp(argv[1], "-testz") == 0))
|
||||
{
|
||||
Stats stats = {};
|
||||
stats.testz = strcmp(argv[1], "-testz") == 0;
|
||||
int level = -1;
|
||||
if (argc > 2 && argv[2][0] == '-' && argv[2][1] >= '0' && argv[2][1] <= '9')
|
||||
level = atoi(argv[2] + 1);
|
||||
for (int i = level < 0 ? 2 : 3; i < argc; ++i)
|
||||
testFile(argv[i], level < 0 ? 2 : level, &stats);
|
||||
|
||||
printf("---\n");
|
||||
printf("%d files: vtx %.3f", int(stats.count), exp(stats.ratio / stats.count));
|
||||
if (stats.testz)
|
||||
printf(", vtx+lz4 %.3f, vtx+zstd %.3f\n", exp(stats.ratio_lz4 / stats.count), exp(stats.ratio_zstd / stats.count));
|
||||
else
|
||||
printf("\n");
|
||||
|
||||
printf("total: %d files, raw %.2f MB, vtx %.2f MB",
|
||||
int(stats.count),
|
||||
stats.size / 1024 / 1024, stats.total / 1024 / 1024);
|
||||
if (stats.testz)
|
||||
printf(", vtx+lz4 %.2f MB, vtx+zstd %.2f MB\n", stats.total_lz4 / 1024 / 1024, stats.total_zstd / 1024 / 1024);
|
||||
else
|
||||
printf("\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (argc < 2 || argc > 3 || atoi(argv[1]) <= 0)
|
||||
{
|
||||
fprintf(stderr, "Usage: %s <stride> [<count>]\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t stride = atoi(argv[1]);
|
||||
|
||||
std::vector<unsigned char> input;
|
||||
unsigned char buffer[4096];
|
||||
size_t bytes_read;
|
||||
|
||||
while ((bytes_read = fread(buffer, 1, sizeof(buffer), stdin)) > 0)
|
||||
input.insert(input.end(), buffer, buffer + bytes_read);
|
||||
|
||||
if (argc == 3)
|
||||
{
|
||||
// if count is specified, we assume input is meshopt-encoded and decode it first
|
||||
size_t count = atoi(argv[2]);
|
||||
|
||||
std::vector<unsigned char> decoded(count * stride);
|
||||
int res = meshopt_decodeVertexBuffer(&decoded[0], count, stride, &input[0], input.size());
|
||||
if (res != 0)
|
||||
{
|
||||
fprintf(stderr, "Error decoding input: %d\n", res);
|
||||
return 2;
|
||||
}
|
||||
|
||||
fwrite(decoded.data(), 1, decoded.size(), stdout);
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t vertex_count = input.size() / stride;
|
||||
std::vector<unsigned char> output(meshopt_encodeVertexBufferBound(vertex_count, stride));
|
||||
size_t output_size = meshopt_encodeVertexBuffer(output.data(), output.size(), input.data(), vertex_count, stride);
|
||||
|
||||
#if TRACE
|
||||
printf("%d => %d\n", int(input.size()), int(output_size));
|
||||
#else
|
||||
fwrite(output.data(), 1, output_size, stdout);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import argparse
|
||||
import concurrent.futures
|
||||
import matplotlib.pyplot as plt
|
||||
import os
|
||||
import os.path
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
argp = argparse.ArgumentParser()
|
||||
argp.add_argument('--basisu', type=str, required=True)
|
||||
argp.add_argument('--graph', type=str, default="basisu.png")
|
||||
argp.add_argument('--etcbase', action='store_true')
|
||||
argp.add_argument('files', type=str, nargs='+')
|
||||
args = argp.parse_args()
|
||||
|
||||
def compress(path, flags):
|
||||
temp_path = "/dev/null" if os.name == "posix" else "NUL"
|
||||
output = subprocess.check_output([args.basisu, "-file", path, "-output_file", temp_path, "-stats", "-ktx2"] + flags)
|
||||
for line in output.splitlines():
|
||||
if m := re.match(r".*source image.*?(\d+)x(\d+)", line.decode()):
|
||||
pixels = int(m.group(1)) * int(m.group(2))
|
||||
elif m := re.match(r".*Compression succeeded.*size (\d+) bytes", line.decode()):
|
||||
bytes = int(m.group(1))
|
||||
elif m := re.match(r"\.basis RGB Avg:.*RMS: (\d+\.\d+) PSNR: (\d+\.\d+)", line.decode()):
|
||||
rms = float(m.group(1))
|
||||
psnr = float(m.group(2))
|
||||
|
||||
return {'path': path, 'bpp': bytes * 8 / pixels, 'rms': rms, 'psnr': psnr}
|
||||
|
||||
def stats(path):
|
||||
with concurrent.futures.ThreadPoolExecutor(16) as executor:
|
||||
futures = []
|
||||
for i in range(0, 26):
|
||||
rdo_l = i / 5
|
||||
flags = ["-uastc", "-uastc_level", "1", "-uastc_rdo_l", str(rdo_l), "-uastc_rdo_d", "1024"]
|
||||
futures.append((executor.submit(compress, path, flags), rdo_l))
|
||||
concurrent.futures.wait([f for (f, r) in futures])
|
||||
results = []
|
||||
bppbase = 0
|
||||
for future, rdo_l in futures:
|
||||
res = future.result()
|
||||
if rdo_l == 0:
|
||||
bppbase = res['bpp']
|
||||
res['rdo_l'] = rdo_l
|
||||
res['ratio'] = res['bpp'] / bppbase
|
||||
results.append(res)
|
||||
return results
|
||||
|
||||
fields = ['bpp', 'rms', 'psnr', 'ratio']
|
||||
fig, axs = plt.subplots(1, len(fields) + 1, layout='constrained')
|
||||
fig.set_figwidth(5 * (len(fields) + 1))
|
||||
lines = []
|
||||
|
||||
for path in args.files:
|
||||
print('Processing', path)
|
||||
results = stats(path)
|
||||
etcbase = compress(path, ["-q", "192"]) if args.etcbase else None
|
||||
|
||||
for idx, field in enumerate(fields):
|
||||
line, = axs[idx].plot([r['rdo_l'] for r in results], [r[field] for r in results])
|
||||
if etcbase and field in etcbase:
|
||||
axs[idx].axhline(etcbase[field], color=line.get_color(), linestyle='dotted')
|
||||
if idx == 0:
|
||||
lines.append(line)
|
||||
|
||||
axs[len(fields)].scatter([r['ratio'] for r in results], [r['psnr'] for r in results], color=line.get_color())
|
||||
|
||||
for idx, field in enumerate(fields):
|
||||
axs[idx].set_title(field)
|
||||
|
||||
axs[len(fields)].set_title('psnr vs ratio')
|
||||
|
||||
fig.legend(lines, [os.path.basename(path) for path in args.files], loc='outside right upper')
|
||||
|
||||
plt.savefig(args.graph)
|
||||
@@ -0,0 +1,6 @@
|
||||
#ifndef _CRT_SECURE_NO_WARNINGS
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#define FAST_OBJ_IMPLEMENTATION
|
||||
#include "../extern/fast_obj.h"
|
||||
@@ -0,0 +1,46 @@
|
||||
#include "../src/meshoptimizer.h"
|
||||
|
||||
#include <float.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* buffer, size_t size)
|
||||
{
|
||||
if (size == 0)
|
||||
return 0;
|
||||
|
||||
srand(buffer[0]);
|
||||
|
||||
float vb[100][4];
|
||||
|
||||
for (int i = 0; i < 100; ++i)
|
||||
{
|
||||
vb[i][0] = rand() % 10;
|
||||
vb[i][1] = rand() % 10;
|
||||
vb[i][2] = rand() % 10;
|
||||
vb[i][3] = rand() % 10;
|
||||
}
|
||||
|
||||
unsigned int ib[999];
|
||||
int indices = (size - 1) < 999 ? (size - 1) / 3 * 3 : 999;
|
||||
|
||||
for (int i = 0; i < indices; ++i)
|
||||
ib[i] = buffer[1 + i] % 100;
|
||||
|
||||
unsigned int res[999];
|
||||
|
||||
meshopt_simplify(res, ib, indices, vb[0], 100, sizeof(float) * 4, 0, 1e-1f, 0, NULL);
|
||||
meshopt_simplify(res, ib, indices, vb[0], 100, sizeof(float) * 4, 0, FLT_MAX, 0, NULL);
|
||||
meshopt_simplify(res, ib, indices, vb[0], 100, sizeof(float) * 4, 0, FLT_MAX, meshopt_SimplifyLockBorder, NULL);
|
||||
meshopt_simplify(res, ib, indices, vb[0], 100, sizeof(float) * 4, 0, FLT_MAX, meshopt_SimplifySparse, NULL);
|
||||
meshopt_simplify(res, ib, indices, vb[0], 100, sizeof(float) * 4, 0, FLT_MAX, meshopt_SimplifyPrune, NULL);
|
||||
meshopt_simplify(res, ib, indices, vb[0], 100, sizeof(float) * 4, 0, FLT_MAX, meshopt_SimplifyPermissive, NULL);
|
||||
|
||||
float aw = 1;
|
||||
meshopt_simplifyWithAttributes(res, ib, indices, vb[0], 100, sizeof(float) * 4, vb[0] + 3, sizeof(float) * 4, &aw, 1, NULL, 0, FLT_MAX, 0, NULL);
|
||||
|
||||
// must be last, as it modifies all inputs in place
|
||||
meshopt_simplifyWithUpdate(ib, indices, vb[0], 100, sizeof(float) * 4, vb[0] + 3, sizeof(float) * 4, &aw, 1, NULL, 0, FLT_MAX, 0, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,513 @@
|
||||
#ifdef _WIN32
|
||||
#include <assert.h>
|
||||
#include <d3d11.h>
|
||||
#include <d3dcompiler.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include "../extern/fast_obj.h"
|
||||
#include "../src/meshoptimizer.h"
|
||||
|
||||
#pragma comment(lib, "d3d11.lib")
|
||||
#pragma comment(lib, "d3dcompiler.lib")
|
||||
#pragma comment(lib, "dxgi.lib")
|
||||
|
||||
void stripGen(std::vector<unsigned int>& indices, int x0, int x1, int y0, int y1, int width, bool prefetch)
|
||||
{
|
||||
if (prefetch)
|
||||
{
|
||||
for (int x = x0; x < x1; x++)
|
||||
{
|
||||
indices.push_back(x + 0);
|
||||
indices.push_back(x + 0);
|
||||
indices.push_back(x + 1);
|
||||
}
|
||||
}
|
||||
|
||||
for (int y = y0; y < y1; y++)
|
||||
{
|
||||
for (int x = x0; x < x1; x++)
|
||||
{
|
||||
indices.push_back((width + 1) * (y + 0) + (x + 0));
|
||||
indices.push_back((width + 1) * (y + 1) + (x + 0));
|
||||
indices.push_back((width + 1) * (y + 0) + (x + 1));
|
||||
|
||||
indices.push_back((width + 1) * (y + 0) + (x + 1));
|
||||
indices.push_back((width + 1) * (y + 1) + (x + 0));
|
||||
indices.push_back((width + 1) * (y + 1) + (x + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void gridGen(std::vector<unsigned int>& indices, int x0, int x1, int y0, int y1, int width, int cacheSize, bool prefetch)
|
||||
{
|
||||
if (x1 - x0 + 1 < cacheSize)
|
||||
{
|
||||
bool prefetchStrip = 2 * (x1 - x0) + 1 > cacheSize && prefetch;
|
||||
|
||||
stripGen(indices, x0, x1, y0, y1, width, prefetchStrip);
|
||||
}
|
||||
else
|
||||
{
|
||||
int xm = x0 + cacheSize - 2;
|
||||
gridGen(indices, x0, xm, y0, y1, width, cacheSize, prefetch);
|
||||
gridGen(indices, xm, x1, y0, y1, width, cacheSize, prefetch);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int queryVSInvocations(ID3D11Device* device, ID3D11DeviceContext* context, const unsigned int* indices, size_t index_count)
|
||||
{
|
||||
if (index_count == 0)
|
||||
return 0;
|
||||
|
||||
ID3D11Buffer* ib = 0;
|
||||
|
||||
{
|
||||
D3D11_BUFFER_DESC bd = {};
|
||||
|
||||
bd.Usage = D3D11_USAGE_DYNAMIC;
|
||||
bd.ByteWidth = index_count * 4;
|
||||
bd.BindFlags = D3D11_BIND_INDEX_BUFFER;
|
||||
bd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
|
||||
|
||||
device->CreateBuffer(&bd, 0, &ib);
|
||||
|
||||
D3D11_MAPPED_SUBRESOURCE ms;
|
||||
context->Map(ib, 0, D3D11_MAP_WRITE_DISCARD, 0, &ms);
|
||||
memcpy(ms.pData, indices, index_count * 4);
|
||||
context->Unmap(ib, 0);
|
||||
}
|
||||
|
||||
context->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
|
||||
context->IASetIndexBuffer(ib, DXGI_FORMAT_R32_UINT, 0);
|
||||
|
||||
D3D11_QUERY_DESC qdesc = {D3D11_QUERY_PIPELINE_STATISTICS};
|
||||
ID3D11Query* query = 0;
|
||||
device->CreateQuery(&qdesc, &query);
|
||||
|
||||
context->Begin(query);
|
||||
context->DrawIndexed(index_count, 0, 0);
|
||||
context->End(query);
|
||||
|
||||
D3D11_QUERY_DATA_PIPELINE_STATISTICS stats = {};
|
||||
while (S_FALSE == context->GetData(query, &stats, sizeof(stats), 0))
|
||||
;
|
||||
|
||||
query->Release();
|
||||
ib->Release();
|
||||
|
||||
assert(stats.IAVertices == index_count);
|
||||
|
||||
return stats.VSInvocations;
|
||||
}
|
||||
|
||||
void setupShaders(ID3D11Device* device, ID3D11DeviceContext* context)
|
||||
{
|
||||
// load and compile the two shaders
|
||||
const char* shaders =
|
||||
"#define ATTRIBUTES 5\n"
|
||||
"struct Foo { float4 v[ATTRIBUTES]; };"
|
||||
"float4 VS(uint index: SV_VertexId, out Foo foo: FOO): SV_Position { uint i = index % 3; [unroll] for (int j = 0; j < ATTRIBUTES; j++) foo.v[j] = j; return float4(i != 0, i != 2, 0, 1); }"
|
||||
"float4 PS(Foo foo: FOO): SV_Target { float4 result = 0; [unroll] for (int j = 0; j < ATTRIBUTES; j++) result += foo.v[j]; return result; }";
|
||||
|
||||
ID3DBlob* vsblob = 0;
|
||||
ID3DBlob* psblob = 0;
|
||||
D3DCompile(shaders, strlen(shaders), 0, 0, 0, "VS", "vs_5_0", 0, 0, &vsblob, 0);
|
||||
D3DCompile(shaders, strlen(shaders), 0, 0, 0, "PS", "ps_5_0", 0, 0, &psblob, 0);
|
||||
|
||||
ID3D11VertexShader* vs = 0;
|
||||
ID3D11PixelShader* ps = 0;
|
||||
device->CreateVertexShader(vsblob->GetBufferPointer(), vsblob->GetBufferSize(), 0, &vs);
|
||||
device->CreatePixelShader(psblob->GetBufferPointer(), psblob->GetBufferSize(), 0, &ps);
|
||||
|
||||
context->VSSetShader(vs, 0, 0);
|
||||
context->PSSetShader(ps, 0, 0);
|
||||
}
|
||||
|
||||
template <typename Cache>
|
||||
void inspectCache(Cache cache)
|
||||
{
|
||||
unsigned int max_cache_size = 200;
|
||||
unsigned int grid_size = 100;
|
||||
|
||||
for (unsigned int cache_size = 3; cache_size <= max_cache_size; cache_size += 1)
|
||||
{
|
||||
std::vector<unsigned int> grid1;
|
||||
gridGen(grid1, 0, grid_size, 0, grid_size, grid_size, cache_size, true);
|
||||
|
||||
std::vector<unsigned int> grid2;
|
||||
gridGen(grid2, 0, grid_size, 0, grid_size, grid_size, cache_size, false);
|
||||
|
||||
std::vector<unsigned int> grid3;
|
||||
gridGen(grid3, 0, grid_size, 0, grid_size, grid_size, grid_size * 4, false); // this generates a simple indexed grid without striping/degenerate triangles
|
||||
meshopt_optimizeVertexCacheFifo(&grid3[0], &grid3[0], grid3.size(), (grid_size + 1) * (grid_size + 1), cache_size);
|
||||
|
||||
std::vector<unsigned int> grid4;
|
||||
gridGen(grid4, 0, grid_size, 0, grid_size, grid_size, grid_size * 4, false); // this generates a simple indexed grid without striping/degenerate triangles
|
||||
meshopt_optimizeVertexCache(&grid4[0], &grid4[0], grid4.size(), (grid_size + 1) * (grid_size + 1));
|
||||
|
||||
unsigned int invocations1 = cache(&grid1[0], grid1.size());
|
||||
unsigned int invocations2 = cache(&grid2[0], grid2.size());
|
||||
unsigned int invocations3 = cache(&grid3[0], grid3.size());
|
||||
unsigned int invocations4 = cache(&grid4[0], grid4.size());
|
||||
|
||||
unsigned int ideal_invocations = (grid_size + 1) * (grid_size + 1);
|
||||
|
||||
printf("%d, %f, %f, %f, %f\n", cache_size,
|
||||
double(invocations1) / double(ideal_invocations),
|
||||
double(invocations2) / double(ideal_invocations),
|
||||
double(invocations3) / double(ideal_invocations),
|
||||
double(invocations4) / double(ideal_invocations));
|
||||
}
|
||||
}
|
||||
|
||||
void testCache(IDXGIAdapter* adapter)
|
||||
{
|
||||
ID3D11Device* device = 0;
|
||||
ID3D11DeviceContext* context = 0;
|
||||
D3D11CreateDevice(adapter, D3D_DRIVER_TYPE_UNKNOWN, 0, 0, 0, 0, D3D11_SDK_VERSION, &device, 0, &context);
|
||||
|
||||
setupShaders(device, context);
|
||||
|
||||
inspectCache([&](const unsigned int* indices, size_t index_count)
|
||||
{ return queryVSInvocations(device, context, indices, index_count); });
|
||||
}
|
||||
|
||||
void testCacheSequence(IDXGIAdapter* adapter, int argc, char** argv)
|
||||
{
|
||||
ID3D11Device* device = 0;
|
||||
ID3D11DeviceContext* context = 0;
|
||||
D3D11CreateDevice(adapter, D3D_DRIVER_TYPE_UNKNOWN, 0, 0, 0, 0, D3D11_SDK_VERSION, &device, 0, &context);
|
||||
|
||||
setupShaders(device, context);
|
||||
|
||||
std::vector<unsigned int> ib;
|
||||
|
||||
for (int i = 2; i < argc; ++i)
|
||||
{
|
||||
char* end;
|
||||
int i0 = strtol(argv[i], &end, 10);
|
||||
|
||||
if (end[0] == '-')
|
||||
{
|
||||
int i1 = strtol(end + 1, &end, 10);
|
||||
|
||||
if (end[0] != 0)
|
||||
{
|
||||
printf("Unrecognized index range: %s\n", argv[i]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (i0 < i1)
|
||||
{
|
||||
for (int ii = i0; ii <= i1; ++ii)
|
||||
ib.push_back(ii);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int ii = i0; ii >= i1; --ii)
|
||||
ib.push_back(ii);
|
||||
}
|
||||
}
|
||||
else if (end[0] == '*')
|
||||
{
|
||||
int i1 = strtol(end + 1, &end, 10);
|
||||
|
||||
if (end[0] != 0 || i1 == 0)
|
||||
{
|
||||
printf("Unrecognized index range: %s\n", argv[i]);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int ii = 0; ii < i1; ++ii)
|
||||
ib.push_back(i0);
|
||||
}
|
||||
else if (end[0] == 'x')
|
||||
{
|
||||
int i1 = strtol(end + 1, &end, 10);
|
||||
|
||||
if (end[0] != 0)
|
||||
{
|
||||
printf("Unrecognized index range: %s\n", argv[i]);
|
||||
return;
|
||||
}
|
||||
|
||||
stripGen(ib, 0, i0, 0, i1, i0, true);
|
||||
}
|
||||
else if (end[0] == 0)
|
||||
{
|
||||
ib.push_back(i0);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("Unrecognized index range: %s\n", argv[i]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (ib.size() % 3)
|
||||
ib.resize(ib.size() - ib.size() % 3);
|
||||
|
||||
std::vector<bool> xformed(ib.size());
|
||||
|
||||
for (size_t i = 0; i < ib.size(); i += 3)
|
||||
{
|
||||
unsigned int inv0 = i == 0 ? 0 : queryVSInvocations(device, context, ib.data(), i);
|
||||
unsigned int inv1 = queryVSInvocations(device, context, ib.data(), i + 3);
|
||||
|
||||
assert(inv0 <= inv1);
|
||||
assert(inv0 + 3 >= inv1);
|
||||
|
||||
switch (inv1 - inv0)
|
||||
{
|
||||
case 0:
|
||||
xformed[i + 0] = xformed[i + 1] = xformed[i + 2] = false;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
xformed[i + 0] = xformed[i + 1] = xformed[i + 2] = true;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
case 2:
|
||||
{
|
||||
unsigned int a = ib[i + 0];
|
||||
unsigned int b = ib[i + 1];
|
||||
unsigned int c = ib[i + 2];
|
||||
|
||||
ib[i + 0] = ib[i + 1] = ib[i + 2] = a;
|
||||
unsigned int inva = queryVSInvocations(device, context, ib.data(), i + 3);
|
||||
|
||||
ib[i + 1] = ib[i + 2] = b;
|
||||
unsigned int invb = queryVSInvocations(device, context, ib.data(), i + 3);
|
||||
|
||||
ib[i + 2] = c;
|
||||
unsigned int invc = queryVSInvocations(device, context, ib.data(), i + 3);
|
||||
|
||||
assert(inv0 <= inva && inva <= inv1);
|
||||
assert(inv0 <= invb && invb <= inv1);
|
||||
assert(inv0 <= invc && invc <= inv1);
|
||||
|
||||
if (inv1 - inv0 == 1 && a == c && inva == inv1 && invb == inv0 && invc == inv1)
|
||||
{
|
||||
xformed[i + 0] = false;
|
||||
xformed[i + 1] = false;
|
||||
xformed[i + 2] = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(inva <= invb);
|
||||
assert(invb <= invc);
|
||||
|
||||
xformed[i + 0] = inva == inv0 + 1;
|
||||
xformed[i + 1] = invb == inva + 1;
|
||||
xformed[i + 2] = invc == invb + 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int xformed_total = 0;
|
||||
|
||||
for (size_t i = 0; i < ib.size(); ++i)
|
||||
xformed_total += xformed[i];
|
||||
|
||||
printf("// Sequence: %d indices", int(ib.size()));
|
||||
|
||||
for (size_t i = 0; i < ib.size(); ++i)
|
||||
{
|
||||
if (i % 12 == 0)
|
||||
{
|
||||
printf("\n// %3d*3:", int(i / 3));
|
||||
}
|
||||
|
||||
if (xformed[i])
|
||||
printf(" %3d*", ib[i]);
|
||||
else
|
||||
printf(" %3d ", ib[i]);
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
|
||||
std::vector<unsigned int> cached;
|
||||
|
||||
for (size_t i = 0; i < ib.size(); ++i)
|
||||
{
|
||||
unsigned int index = ib[i];
|
||||
unsigned int inv0 = queryVSInvocations(device, context, ib.data(), ib.size());
|
||||
|
||||
ib.push_back(index);
|
||||
ib.push_back(index);
|
||||
ib.push_back(index);
|
||||
|
||||
unsigned int inv1 = queryVSInvocations(device, context, ib.data(), ib.size());
|
||||
|
||||
ib.resize(ib.size() - 3);
|
||||
|
||||
if (inv1 == inv0)
|
||||
cached.push_back(index);
|
||||
}
|
||||
|
||||
std::sort(cached.begin(), cached.end());
|
||||
cached.erase(std::unique(cached.begin(), cached.end()), cached.end());
|
||||
|
||||
printf("// Cached :");
|
||||
|
||||
for (size_t i = 0; i < cached.size(); ++i)
|
||||
printf(" %d", cached[i]);
|
||||
|
||||
printf(" (%d)\n", int(cached.size()));
|
||||
|
||||
unsigned int invocations = queryVSInvocations(device, context, ib.data(), ib.size());
|
||||
|
||||
printf("// Invocations: %d\n", invocations);
|
||||
|
||||
assert(xformed_total == invocations);
|
||||
}
|
||||
|
||||
void testCacheMeshes(IDXGIAdapter* adapter, int argc, char** argv)
|
||||
{
|
||||
ID3D11Device* device = 0;
|
||||
ID3D11DeviceContext* context = 0;
|
||||
D3D11CreateDevice(adapter, D3D_DRIVER_TYPE_UNKNOWN, 0, 0, 0, 0, D3D11_SDK_VERSION, &device, 0, &context);
|
||||
|
||||
setupShaders(device, context);
|
||||
|
||||
bool stat = false;
|
||||
|
||||
double atvr_sum = 0;
|
||||
double atvr_count = 0;
|
||||
|
||||
unsigned int total_invocations = 0;
|
||||
unsigned int total_vertices = 0;
|
||||
|
||||
for (int i = 1; i < argc; ++i)
|
||||
{
|
||||
const char* path = argv[i];
|
||||
|
||||
if (strcmp(path, "--stat") == 0)
|
||||
{
|
||||
stat = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
fastObjMesh* obj = fast_obj_read(path);
|
||||
if (!obj)
|
||||
{
|
||||
printf("Error loading %s: file not found\n", path);
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<unsigned int> ib1;
|
||||
|
||||
size_t index_offset = 0;
|
||||
|
||||
for (unsigned int i = 0; i < obj->face_count; ++i)
|
||||
{
|
||||
if (obj->face_vertices[i] <= 2)
|
||||
continue;
|
||||
|
||||
for (unsigned int j = 0; j < obj->face_vertices[i]; ++j)
|
||||
{
|
||||
fastObjIndex gi = obj->indices[index_offset + j];
|
||||
|
||||
// triangulate polygon on the fly; offset-3 is always the first polygon vertex
|
||||
if (j >= 3)
|
||||
{
|
||||
unsigned int i0 = ib1[ib1.size() - 3];
|
||||
unsigned int i1 = ib1[ib1.size() - 1];
|
||||
|
||||
ib1.push_back(i0);
|
||||
ib1.push_back(i1);
|
||||
}
|
||||
|
||||
ib1.push_back(gi.p);
|
||||
}
|
||||
|
||||
index_offset += obj->face_vertices[i];
|
||||
}
|
||||
|
||||
unsigned int vertex_count = obj->position_count;
|
||||
unsigned int index_count = ib1.size();
|
||||
|
||||
unsigned int invocations1 = queryVSInvocations(device, context, ib1.data(), index_count);
|
||||
|
||||
if (stat)
|
||||
{
|
||||
std::vector<unsigned int> ib2(ib1.size());
|
||||
meshopt_optimizeVertexCache(&ib2[0], &ib1[0], ib1.size(), vertex_count);
|
||||
|
||||
unsigned int invocations = queryVSInvocations(device, context, ib2.data(), index_count);
|
||||
|
||||
atvr_sum += double(invocations) / double(vertex_count);
|
||||
atvr_count += 1;
|
||||
|
||||
total_invocations += invocations;
|
||||
total_vertices += vertex_count;
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("%s: baseline %f\n", path, double(invocations1) / double(vertex_count));
|
||||
|
||||
std::vector<unsigned int> ib3(ib1.size());
|
||||
meshopt_optimizeVertexCache(&ib3[0], &ib1[0], ib1.size(), vertex_count);
|
||||
|
||||
unsigned int invocations3 = queryVSInvocations(device, context, ib3.data(), index_count);
|
||||
|
||||
printf("%s: forsyth %f\n", path, double(invocations3) / double(vertex_count));
|
||||
|
||||
for (unsigned int cache_size = 12; cache_size <= 24; ++cache_size)
|
||||
{
|
||||
std::vector<unsigned int> ib2(ib1.size());
|
||||
meshopt_optimizeVertexCacheFifo(&ib2[0], &ib1[0], ib1.size(), vertex_count, cache_size);
|
||||
|
||||
unsigned int invocations2 = queryVSInvocations(device, context, ib2.data(), index_count);
|
||||
|
||||
printf("%s: tipsify(%d) %f\n", path, cache_size, double(invocations2) / double(vertex_count));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (stat)
|
||||
{
|
||||
printf("ATVR: average %f cumulative %f; %d vertices\n", atvr_sum / atvr_count, double(total_invocations) / double(total_vertices), total_vertices);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
IDXGIFactory1* factory = 0;
|
||||
CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&factory);
|
||||
|
||||
IDXGIAdapter* adapter = NULL;
|
||||
for (unsigned int index = 0; SUCCEEDED(factory->EnumAdapters(index, &adapter)); ++index)
|
||||
{
|
||||
DXGI_ADAPTER_DESC ad = {};
|
||||
adapter->GetDesc(&ad);
|
||||
|
||||
if (ad.VendorId == 0x1414 && ad.DeviceId == 0x8c)
|
||||
continue; // Skip Microsoft Basic Render Driver
|
||||
|
||||
printf("// GPU %d: %S (Vendor %04x Device %04x)\n", index, ad.Description, ad.VendorId, ad.DeviceId);
|
||||
|
||||
if (argc == 1)
|
||||
{
|
||||
testCache(adapter);
|
||||
}
|
||||
else if (argc > 1 && strcmp(argv[1], "--") == 0)
|
||||
{
|
||||
testCacheSequence(adapter, argc, argv);
|
||||
}
|
||||
else
|
||||
{
|
||||
testCacheMeshes(adapter, argc, argv);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,502 @@
|
||||
#include "../extern/fast_obj.h"
|
||||
#include "../src/meshoptimizer.h"
|
||||
|
||||
#define SDEFL_IMPLEMENTATION
|
||||
#include "../extern/sdefl.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
const int kCacheSizeMax = 16;
|
||||
const int kValenceMax = 8;
|
||||
|
||||
namespace meshopt
|
||||
{
|
||||
struct VertexScoreTable
|
||||
{
|
||||
float cache[1 + kCacheSizeMax];
|
||||
float live[1 + kValenceMax];
|
||||
};
|
||||
} // namespace meshopt
|
||||
|
||||
void meshopt_optimizeVertexCacheTable(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count, const meshopt::VertexScoreTable* table);
|
||||
|
||||
struct Profile
|
||||
{
|
||||
float weight;
|
||||
int cache, warp, triangle; // vcache tuning parameters
|
||||
int compression;
|
||||
};
|
||||
|
||||
Profile profiles[] = {
|
||||
{1.f, 0, 0, 0, 0}, // Compression
|
||||
{1.f, 0, 0, 0, 1}, // Compression w/deflate
|
||||
|
||||
// {1.f, 14, 64, 128}, // AMD GCN
|
||||
// {1.f, 32, 32, 32}, // NVidia Pascal
|
||||
// {1.f, 16, 32, 32}, // NVidia Kepler, Maxwell
|
||||
// {1.f, 128, 0, 0}, // Intel
|
||||
};
|
||||
|
||||
const int Profile_Count = sizeof(profiles) / sizeof(profiles[0]);
|
||||
|
||||
struct pcg32_random_t
|
||||
{
|
||||
uint64_t state;
|
||||
uint64_t inc;
|
||||
};
|
||||
|
||||
#define PCG32_INITIALIZER {0x853c49e6748fea9bULL, 0xda3e39cb94b95bdbULL}
|
||||
|
||||
uint32_t pcg32_random_r(pcg32_random_t* rng)
|
||||
{
|
||||
uint64_t oldstate = rng->state;
|
||||
// Advance internal state
|
||||
rng->state = oldstate * 6364136223846793005ULL + (rng->inc | 1);
|
||||
// Calculate output function (XSH RR), uses old state for max ILP
|
||||
uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u;
|
||||
uint32_t rot = oldstate >> 59u;
|
||||
return (xorshifted >> rot) | (xorshifted << ((-rot) & 31));
|
||||
}
|
||||
|
||||
pcg32_random_t rngstate = PCG32_INITIALIZER;
|
||||
|
||||
float rand01()
|
||||
{
|
||||
return pcg32_random_r(&rngstate) / float(1ull << 32);
|
||||
}
|
||||
|
||||
uint32_t rand32()
|
||||
{
|
||||
return pcg32_random_r(&rngstate);
|
||||
}
|
||||
|
||||
struct State
|
||||
{
|
||||
float cache[kCacheSizeMax];
|
||||
float live[kValenceMax];
|
||||
float fitness;
|
||||
};
|
||||
|
||||
struct Mesh
|
||||
{
|
||||
const char* name;
|
||||
|
||||
size_t vertex_count;
|
||||
std::vector<unsigned int> indices;
|
||||
|
||||
float metric_base[Profile_Count];
|
||||
};
|
||||
|
||||
Mesh gridmesh(unsigned int N)
|
||||
{
|
||||
Mesh result;
|
||||
|
||||
result.name = "grid";
|
||||
|
||||
result.vertex_count = (N + 1) * (N + 1);
|
||||
result.indices.reserve(N * N * 6);
|
||||
|
||||
for (unsigned int y = 0; y < N; ++y)
|
||||
for (unsigned int x = 0; x < N; ++x)
|
||||
{
|
||||
result.indices.push_back((y + 0) * (N + 1) + (x + 0));
|
||||
result.indices.push_back((y + 0) * (N + 1) + (x + 1));
|
||||
result.indices.push_back((y + 1) * (N + 1) + (x + 0));
|
||||
|
||||
result.indices.push_back((y + 1) * (N + 1) + (x + 0));
|
||||
result.indices.push_back((y + 0) * (N + 1) + (x + 1));
|
||||
result.indices.push_back((y + 1) * (N + 1) + (x + 1));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Mesh objmesh(const char* path)
|
||||
{
|
||||
fastObjMesh* obj = fast_obj_read(path);
|
||||
if (!obj)
|
||||
{
|
||||
printf("Error loading %s: file not found\n", path);
|
||||
return Mesh();
|
||||
}
|
||||
|
||||
size_t total_indices = 0;
|
||||
|
||||
for (unsigned int i = 0; i < obj->face_count; ++i)
|
||||
if (obj->face_vertices[i] > 2)
|
||||
total_indices += 3 * (obj->face_vertices[i] - 2);
|
||||
|
||||
struct Vertex
|
||||
{
|
||||
float px, py, pz;
|
||||
float nx, ny, nz;
|
||||
float tx, ty;
|
||||
};
|
||||
|
||||
std::vector<Vertex> vertices(total_indices);
|
||||
|
||||
size_t vertex_offset = 0;
|
||||
size_t index_offset = 0;
|
||||
|
||||
for (unsigned int i = 0; i < obj->face_count; ++i)
|
||||
{
|
||||
if (obj->face_vertices[i] <= 2)
|
||||
continue;
|
||||
|
||||
for (unsigned int j = 0; j < obj->face_vertices[i]; ++j)
|
||||
{
|
||||
fastObjIndex gi = obj->indices[index_offset + j];
|
||||
|
||||
Vertex v =
|
||||
{
|
||||
obj->positions[gi.p * 3 + 0],
|
||||
obj->positions[gi.p * 3 + 1],
|
||||
obj->positions[gi.p * 3 + 2],
|
||||
obj->normals[gi.n * 3 + 0],
|
||||
obj->normals[gi.n * 3 + 1],
|
||||
obj->normals[gi.n * 3 + 2],
|
||||
obj->texcoords[gi.t * 2 + 0],
|
||||
obj->texcoords[gi.t * 2 + 1],
|
||||
};
|
||||
|
||||
// triangulate polygon on the fly; offset-3 is always the first polygon vertex
|
||||
if (j >= 3)
|
||||
{
|
||||
vertices[vertex_offset + 0] = vertices[vertex_offset - 3];
|
||||
vertices[vertex_offset + 1] = vertices[vertex_offset - 1];
|
||||
vertex_offset += 2;
|
||||
}
|
||||
|
||||
vertices[vertex_offset] = v;
|
||||
vertex_offset++;
|
||||
}
|
||||
|
||||
index_offset += obj->face_vertices[i];
|
||||
}
|
||||
|
||||
fast_obj_destroy(obj);
|
||||
|
||||
Mesh result;
|
||||
|
||||
result.name = path;
|
||||
|
||||
std::vector<unsigned int> remap(total_indices);
|
||||
|
||||
size_t total_vertices = meshopt_generateVertexRemap(&remap[0], NULL, total_indices, &vertices[0], total_indices, sizeof(Vertex));
|
||||
|
||||
result.indices.resize(total_indices);
|
||||
meshopt_remapIndexBuffer(&result.indices[0], NULL, total_indices, &remap[0]);
|
||||
|
||||
result.vertex_count = total_vertices;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
size_t compress(const std::vector<T>& data, int level = SDEFL_LVL_DEF)
|
||||
{
|
||||
std::vector<unsigned char> cbuf(sdefl_bound(int(data.size() * sizeof(T))));
|
||||
sdefl s = {};
|
||||
return sdeflate(&s, &cbuf[0], reinterpret_cast<const unsigned char*>(&data[0]), int(data.size() * sizeof(T)), level);
|
||||
}
|
||||
|
||||
void compute_metric(const State* state, const Mesh& mesh, float result[Profile_Count])
|
||||
{
|
||||
std::vector<unsigned int> indices(mesh.indices.size());
|
||||
|
||||
if (state)
|
||||
{
|
||||
meshopt::VertexScoreTable table = {};
|
||||
memcpy(table.cache + 1, state->cache, kCacheSizeMax * sizeof(float));
|
||||
memcpy(table.live + 1, state->live, kValenceMax * sizeof(float));
|
||||
meshopt_optimizeVertexCacheTable(&indices[0], &mesh.indices[0], mesh.indices.size(), mesh.vertex_count, &table);
|
||||
}
|
||||
else
|
||||
{
|
||||
meshopt_optimizeVertexCache(&indices[0], &mesh.indices[0], mesh.indices.size(), mesh.vertex_count);
|
||||
}
|
||||
|
||||
meshopt_optimizeVertexFetch(NULL, &indices[0], indices.size(), NULL, mesh.vertex_count, 0);
|
||||
|
||||
std::vector<unsigned char> ibuf;
|
||||
|
||||
for (int profile = 0; profile < Profile_Count; ++profile)
|
||||
{
|
||||
if (profiles[profile].cache == 0)
|
||||
{
|
||||
ibuf.resize(meshopt_encodeIndexBufferBound(indices.size(), mesh.vertex_count));
|
||||
ibuf.resize(meshopt_encodeIndexBuffer(&ibuf[0], ibuf.size(), &indices[0], indices.size()));
|
||||
}
|
||||
}
|
||||
|
||||
for (int profile = 0; profile < Profile_Count; ++profile)
|
||||
{
|
||||
if (profiles[profile].cache)
|
||||
{
|
||||
meshopt_VertexCacheStatistics stats = meshopt_analyzeVertexCache(&indices[0], indices.size(), mesh.vertex_count, profiles[profile].cache, profiles[profile].warp, profiles[profile].triangle);
|
||||
result[profile] = stats.atvr;
|
||||
}
|
||||
else
|
||||
{
|
||||
// take into account both pre-deflate and post-deflate size but focus a bit more on post-deflate
|
||||
size_t csize = profiles[profile].compression ? compress(ibuf) : ibuf.size();
|
||||
|
||||
result[profile] = double(csize) / double(indices.size() / 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float fitness_score(const State& state, const std::vector<Mesh>& meshes)
|
||||
{
|
||||
float result = 0;
|
||||
float count = 0;
|
||||
|
||||
for (auto& mesh : meshes)
|
||||
{
|
||||
float metric[Profile_Count];
|
||||
compute_metric(&state, mesh, metric);
|
||||
|
||||
for (int profile = 0; profile < Profile_Count; ++profile)
|
||||
{
|
||||
result += mesh.metric_base[profile] / metric[profile] * profiles[profile].weight;
|
||||
count += profiles[profile].weight;
|
||||
}
|
||||
}
|
||||
|
||||
return result / count;
|
||||
}
|
||||
|
||||
std::vector<State> gen0(size_t count, const std::vector<Mesh>& meshes)
|
||||
{
|
||||
std::vector<State> result;
|
||||
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
{
|
||||
State state = {};
|
||||
|
||||
for (int j = 0; j < kCacheSizeMax; ++j)
|
||||
state.cache[j] = rand01();
|
||||
|
||||
for (int j = 0; j < kValenceMax; ++j)
|
||||
state.live[j] = rand01();
|
||||
|
||||
state.fitness = fitness_score(state, meshes);
|
||||
|
||||
result.push_back(state);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// https://en.wikipedia.org/wiki/Differential_evolution
|
||||
// Good Parameters for Differential Evolution. Magnus Erik Hvass Pedersen, 2010
|
||||
std::pair<State, float> genN(std::vector<State>& seed, const std::vector<Mesh>& meshes, float crossover = 0.8803f, float weight = 0.4717f)
|
||||
{
|
||||
std::vector<State> result(seed.size());
|
||||
|
||||
for (size_t i = 0; i < seed.size(); ++i)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
int a = rand32() % seed.size();
|
||||
int b = rand32() % seed.size();
|
||||
int c = rand32() % seed.size();
|
||||
|
||||
if (a == b || a == c || b == c || a == int(i) || b == int(i) || c == int(i))
|
||||
continue;
|
||||
|
||||
int rc = rand32() % kCacheSizeMax;
|
||||
int rl = rand32() % kValenceMax;
|
||||
|
||||
for (int j = 0; j < kCacheSizeMax; ++j)
|
||||
{
|
||||
float r = rand01();
|
||||
|
||||
if (r < crossover || j == rc)
|
||||
result[i].cache[j] = std::max(0.f, std::min(1.f, seed[a].cache[j] + weight * (seed[b].cache[j] - seed[c].cache[j])));
|
||||
else
|
||||
result[i].cache[j] = seed[i].cache[j];
|
||||
}
|
||||
|
||||
for (int j = 0; j < kValenceMax; ++j)
|
||||
{
|
||||
float r = rand01();
|
||||
|
||||
if (r < crossover || j == rl)
|
||||
result[i].live[j] = std::max(0.f, std::min(1.f, seed[a].live[j] + weight * (seed[b].live[j] - seed[c].live[j])));
|
||||
else
|
||||
result[i].live[j] = seed[i].live[j];
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma omp parallel for
|
||||
for (size_t i = 0; i < seed.size(); ++i)
|
||||
{
|
||||
result[i].fitness = fitness_score(result[i], meshes);
|
||||
}
|
||||
|
||||
State best = {};
|
||||
float bestfit = 0;
|
||||
|
||||
for (size_t i = 0; i < seed.size(); ++i)
|
||||
{
|
||||
if (result[i].fitness > seed[i].fitness)
|
||||
seed[i] = result[i];
|
||||
|
||||
if (seed[i].fitness > bestfit)
|
||||
{
|
||||
best = seed[i];
|
||||
bestfit = seed[i].fitness;
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_pair(best, bestfit);
|
||||
}
|
||||
|
||||
bool load_state(const char* path, std::vector<State>& result)
|
||||
{
|
||||
FILE* file = fopen(path, "rb");
|
||||
if (!file)
|
||||
return false;
|
||||
|
||||
State state;
|
||||
|
||||
result.clear();
|
||||
|
||||
while (fread(&state, sizeof(State), 1, file) == 1)
|
||||
result.push_back(state);
|
||||
|
||||
fclose(file);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool save_state(const char* path, const std::vector<State>& result)
|
||||
{
|
||||
FILE* file = fopen(path, "wb");
|
||||
if (!file)
|
||||
return false;
|
||||
|
||||
for (auto& state : result)
|
||||
{
|
||||
if (fwrite(&state, sizeof(State), 1, file) != 1)
|
||||
{
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return fclose(file) == 0;
|
||||
}
|
||||
|
||||
void dump_state(const State& state)
|
||||
{
|
||||
printf("cache:");
|
||||
for (int i = 0; i < kCacheSizeMax; ++i)
|
||||
{
|
||||
printf(" %.3f", state.cache[i]);
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
printf("live:");
|
||||
for (int i = 0; i < kValenceMax; ++i)
|
||||
{
|
||||
printf(" %.3f", state.live[i]);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
void dump_stats(const State& state, const std::vector<Mesh>& meshes)
|
||||
{
|
||||
float improvement[Profile_Count] = {};
|
||||
|
||||
for (size_t i = 0; i < meshes.size(); ++i)
|
||||
{
|
||||
float metric[Profile_Count];
|
||||
compute_metric(&state, meshes[i], metric);
|
||||
|
||||
printf(" %s", meshes[i].name);
|
||||
for (int profile = 0; profile < Profile_Count; ++profile)
|
||||
printf(" %f", metric[profile]);
|
||||
|
||||
for (int profile = 0; profile < Profile_Count; ++profile)
|
||||
improvement[profile] += meshes[i].metric_base[profile] / metric[profile];
|
||||
}
|
||||
|
||||
printf("; improvement");
|
||||
for (int profile = 0; profile < Profile_Count; ++profile)
|
||||
printf(" %f", improvement[profile] / float(meshes.size()));
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
meshopt_encodeIndexVersion(1);
|
||||
|
||||
std::vector<Mesh> meshes;
|
||||
|
||||
meshes.push_back(gridmesh(50));
|
||||
|
||||
for (int i = 1; i < argc; ++i)
|
||||
meshes.push_back(objmesh(argv[i]));
|
||||
|
||||
size_t total_triangles = 0;
|
||||
|
||||
for (auto& mesh : meshes)
|
||||
{
|
||||
compute_metric(nullptr, mesh, mesh.metric_base);
|
||||
|
||||
total_triangles += mesh.indices.size() / 3;
|
||||
}
|
||||
|
||||
std::vector<State> pop;
|
||||
size_t gen = 0;
|
||||
|
||||
if (load_state("mutator.state", pop))
|
||||
{
|
||||
printf("Loaded %d state vectors\n", int(pop.size()));
|
||||
}
|
||||
else
|
||||
{
|
||||
pop = gen0(95, meshes);
|
||||
}
|
||||
|
||||
printf("%d meshes, %.1fM triangles\n", int(meshes.size()), double(total_triangles) / 1e6);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
auto best = genN(pop, meshes);
|
||||
gen++;
|
||||
|
||||
if (gen % 10 == 0)
|
||||
{
|
||||
printf("%d: fitness %f;", int(gen), best.second);
|
||||
dump_stats(best.first, meshes);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("%d: fitness %f\n", int(gen), best.second);
|
||||
}
|
||||
|
||||
dump_state(best.first);
|
||||
|
||||
if (save_state("mutator.state-temp", pop) && rename("mutator.state-temp", "mutator.state") == 0)
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("ERROR: Can't save state\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import re
|
||||
import sys
|
||||
|
||||
# regenerate with wasmpack.py generate
|
||||
table = [32, 0, 65, 2, 1, 106, 34, 33, 3, 128, 11, 4, 13, 64, 6, 253, 10, 7, 15, 116, 127, 5, 8, 12, 40, 16, 19, 54, 20, 9, 27, 255, 113, 17, 42, 67, 24, 23, 146, 148, 18, 14, 22, 45, 70, 69, 56, 114, 101, 21, 25, 63, 75, 136, 108, 28, 118, 29, 73, 115]
|
||||
|
||||
palette = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789:;";
|
||||
|
||||
def encode(buffer):
|
||||
result = ''
|
||||
|
||||
for ch in buffer.read():
|
||||
if ch in table:
|
||||
index = table.index(ch)
|
||||
result += palette[index]
|
||||
else:
|
||||
result += palette[60 + ch // 64]
|
||||
result += palette[ch % 64]
|
||||
|
||||
return result
|
||||
|
||||
def stats(buffer):
|
||||
hist = [0] * 256
|
||||
for ch in buffer.read():
|
||||
hist[ch] += 1
|
||||
|
||||
result = [i for i in range(256)]
|
||||
result.sort(key=lambda i: hist[i], reverse=True)
|
||||
|
||||
return result
|
||||
|
||||
def patch(target, stem, code):
|
||||
with open(target, 'r') as f: content = f.read()
|
||||
|
||||
pattern = r'(["\']).*\1(;\s*//\s*embed! ' + stem + r')'
|
||||
result = re.sub(pattern, r'\1' + re.escape(code) + r'\1\2', content)
|
||||
|
||||
with open(target, 'w') as f: f.write(result)
|
||||
|
||||
if len(sys.argv) >= 2 and sys.argv[1] == 'generate':
|
||||
print(stats(sys.stdin.buffer)[:60])
|
||||
elif len(sys.argv) >= 2 and sys.argv[1] == 'patch':
|
||||
code = encode(sys.stdin.buffer)
|
||||
patch(sys.argv[2], sys.argv[3], code)
|
||||
else:
|
||||
print(encode(sys.stdin.buffer))
|
||||
@@ -0,0 +1,116 @@
|
||||
#ifndef __wasi__
|
||||
#error This file contains libc stubs for WASI SDK and should only be used in non-Emscripten WebAssembly builds
|
||||
#endif
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
extern unsigned char __heap_base;
|
||||
static intptr_t sbrkp = intptr_t(&__heap_base);
|
||||
|
||||
static const int WASM_PAGE_SIZE = 64 * 1024;
|
||||
|
||||
extern "C" void* sbrk(intptr_t increment)
|
||||
{
|
||||
intptr_t sbrko = sbrkp;
|
||||
|
||||
increment = (increment + 3) & ~3;
|
||||
sbrkp += increment;
|
||||
|
||||
size_t heap_size = __builtin_wasm_memory_size(0) * WASM_PAGE_SIZE;
|
||||
|
||||
if (size_t(sbrkp) > heap_size)
|
||||
{
|
||||
size_t diff = (sbrkp - heap_size + WASM_PAGE_SIZE - 1) / WASM_PAGE_SIZE;
|
||||
|
||||
if (__builtin_wasm_memory_grow(0, diff) == size_t(-1))
|
||||
return (void*)-1;
|
||||
}
|
||||
|
||||
return (void*)sbrko;
|
||||
}
|
||||
|
||||
extern "C" void* memcpy(void* destination, const void* source, size_t num)
|
||||
{
|
||||
char* d = (char*)destination;
|
||||
const char* s = (const char*)source;
|
||||
|
||||
if (((uintptr_t(d) | uintptr_t(s)) & 3) == 0)
|
||||
{
|
||||
while (num > 15)
|
||||
{
|
||||
((uint32_t*)d)[0] = ((uint32_t*)s)[0];
|
||||
((uint32_t*)d)[1] = ((uint32_t*)s)[1];
|
||||
((uint32_t*)d)[2] = ((uint32_t*)s)[2];
|
||||
((uint32_t*)d)[3] = ((uint32_t*)s)[3];
|
||||
d += 16;
|
||||
s += 16;
|
||||
num -= 16;
|
||||
}
|
||||
|
||||
while (num > 3)
|
||||
{
|
||||
((uint32_t*)d)[0] = ((uint32_t*)s)[0];
|
||||
d += 4;
|
||||
s += 4;
|
||||
num -= 4;
|
||||
}
|
||||
}
|
||||
|
||||
while (num > 0)
|
||||
{
|
||||
*d++ = *s++;
|
||||
num--;
|
||||
}
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
||||
extern "C" void* memset(void* ptr, int value, size_t num)
|
||||
{
|
||||
uint32_t v32 = ~0u / 255 * uint8_t(value);
|
||||
|
||||
char* d = (char*)ptr;
|
||||
|
||||
if ((uintptr_t(d) & 3) == 0)
|
||||
{
|
||||
while (num > 15)
|
||||
{
|
||||
((uint32_t*)d)[0] = v32;
|
||||
((uint32_t*)d)[1] = v32;
|
||||
((uint32_t*)d)[2] = v32;
|
||||
((uint32_t*)d)[3] = v32;
|
||||
d += 16;
|
||||
num -= 16;
|
||||
}
|
||||
|
||||
while (num > 3)
|
||||
{
|
||||
((uint32_t*)d)[0] = v32;
|
||||
d += 4;
|
||||
num -= 4;
|
||||
}
|
||||
}
|
||||
|
||||
while (num > 0)
|
||||
{
|
||||
*d++ = char(value);
|
||||
num--;
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void* operator new(size_t size)
|
||||
{
|
||||
return sbrk((size + 7) & ~7);
|
||||
}
|
||||
|
||||
void operator delete(void* ptr) throw()
|
||||
{
|
||||
void* brk = reinterpret_cast<void*>(sbrkp);
|
||||
assert(ptr <= brk);
|
||||
|
||||
sbrk((char*)ptr - (char*)brk);
|
||||
}
|
||||
Reference in New Issue
Block a user