Files
HelloVulkan/Lib/meshoptimizer/js/benchmark.js
T
atdunbg 5bdfb53566 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 抓的)。
2026-09-24 20:53:19 +08:00

151 lines
3.6 KiB
JavaScript

import { MeshoptEncoder as encoder } from './meshopt_encoder.js';
import { MeshoptDecoder as decoder } from './meshopt_decoder.mjs';
import { performance } from 'perf_hooks';
process.on('unhandledRejection', (error) => {
console.log('unhandledRejection', error);
process.exit(1);
});
function bytes(view) {
return new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
}
var tests = {
roundtripVertexBuffer: function () {
var N = 1024 * 1024;
var data = new Uint8Array(N * 16);
var lcg = 1;
for (var i = 0; i < N * 16; ++i) {
// mindstd_rand
lcg = (lcg * 48271) % 2147483647;
var k = i % 16;
if (k <= 8) data[i] = lcg & ((1 << k) - 1);
else data[i] = i & ((1 << (k - 8)) - 1);
}
var decoded = new Uint8Array(N * 16);
var t0 = performance.now();
var encoded = encoder.encodeVertexBuffer(data, N, 16);
var t1 = performance.now();
decoder.decodeVertexBuffer(decoded, N, 16, encoded);
var t2 = performance.now();
return { encodeVertex: t1 - t0, decodeVertex: t2 - t1, bytes: N * 16 };
},
roundtripIndexBuffer: function () {
var N = 1024 * 1024;
var data = new Uint32Array(N * 3);
for (var i = 0; i < N * 3; i += 6) {
var v = i / 6;
data[i + 0] = v;
data[i + 1] = v + 1;
data[i + 2] = v + 2;
data[i + 3] = v + 2;
data[i + 4] = v + 1;
data[i + 5] = v + 3;
}
var decoded = new Uint32Array(data.length);
var t0 = performance.now();
var encoded = encoder.encodeIndexBuffer(bytes(data), data.length, 4);
var t1 = performance.now();
decoder.decodeIndexBuffer(bytes(decoded), data.length, 4, encoded);
var t2 = performance.now();
return { encodeIndex: t1 - t0, decodeIndex: t2 - t1, bytes: N * 12 };
},
decodeGltf: function () {
var N = 1024 * 1024;
var data = new Uint8Array(N * 16);
for (var i = 0; i < N * 16; i += 4) {
data[i + 0] = 0;
data[i + 1] = (i % 16) * 1;
data[i + 2] = (i % 16) * 2;
data[i + 3] = (i % 16) * 8;
}
var decoded = new Uint8Array(N * 16);
var filters = [
{ name: 'none', filter: 'NONE', stride: 16 },
{ name: 'oct8', filter: 'OCTAHEDRAL', stride: 4 },
{ name: 'oct12', filter: 'OCTAHEDRAL', stride: 8 },
{ name: 'quat12', filter: 'QUATERNION', stride: 8 },
{ name: 'col8', filter: 'COLOR', stride: 4 },
{ name: 'col12', filter: 'COLOR', stride: 8 },
{ name: 'exp', filter: 'EXPONENTIAL', stride: 16 },
];
var results = { bytes: N * 16 };
for (var i = 0; i < filters.length; ++i) {
var f = filters[i];
var encoded = encoder.encodeVertexBuffer(data, (N * 16) / f.stride, f.stride);
var t0 = performance.now();
decoder.decodeGltfBuffer(decoded, (N * 16) / f.stride, f.stride, encoded, 'ATTRIBUTES', f.filter);
var t1 = performance.now();
results[f.name] = t1 - t0;
}
return results;
},
};
Promise.all([encoder.ready, decoder.ready]).then(() => {
var reps = 10;
var data = {};
for (var key in tests) {
data[key] = tests[key]();
}
for (var i = 1; i < reps; ++i) {
for (var key in tests) {
var nd = tests[key]();
var od = data[key];
for (var idx in nd) {
od[idx] = Math.min(od[idx], nd[idx]);
}
}
}
for (var key in tests) {
var rep = key;
rep += ':\n';
for (var idx in data[key]) {
if (idx != 'bytes') {
rep += idx;
rep += ' ';
rep += data[key][idx].toFixed(3);
rep += ' ms (';
rep += ((data[key].bytes / 1e9 / data[key][idx]) * 1000).toFixed(3);
rep += ' GB/s)';
if (key == 'decodeGltf' && idx != 'none') {
rep += '; filter ';
rep += ((data[key].bytes / 1e9 / (data[key][idx] - data[key]['none'])) * 1000).toFixed(3);
rep += ' GB/s';
}
rep += '\n';
}
}
console.log(rep);
}
});