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:
2026-09-24 20:53:19 +08:00
parent 34b593f734
commit 5bdfb53566
125 changed files with 67608 additions and 29 deletions
+20
View File
@@ -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()))
+61
View File
@@ -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;
}
+453
View File
@@ -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);
}
}
+184
View File
@@ -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;
}
+233
View File
@@ -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;
}
}
+77
View File
@@ -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)
+6
View File
@@ -0,0 +1,6 @@
#ifndef _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#endif
#define FAST_OBJ_IMPLEMENTATION
#include "../extern/fast_obj.h"
+46
View File
@@ -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;
}
+513
View File
@@ -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
+502
View File
@@ -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");
}
}
}
+48
View File
@@ -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))
+116
View File
@@ -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);
}