perf: BuildFrame 收集段优化——实体级粗剔除 + 数组化 batch 查找 + 合并矩阵写
定位数据(100 背包 + SunTemple,RTX 5060):GPU 全帧仅 ~3ms (Forward 1.91 + Shadow 0.82),CPU BuildFrame 收集段 5.94ms 才是 帧率瓶颈。三段细分测量:pre 0.2 / each+cull 5.0 / flatten 0.4。 优化(collect 5.94 → 4.6ms,-23%): 1. Mesh 全局连续槽位(GetSlot);ViewScratch batch 查找 hash → 数组寻址(slot × kMeshLodCount 槽) 2. Model 级联合包围球(各 mesh bounds 并集):实体在四视图全部 不可见 → 跳过整个 mesh 循环(多 mesh 模型的粗剔除) 3. worlds/prevWorlds 合并为 stride-2 InstanceMatrices 单次写 (每实例每视图 2 次随机堆访问 → 1 次,cache miss 减半) 遥测:Renderer::GetCollectMs 暴露收集段耗时。剩余 4.6ms 的进一步 压缩(并行收集 / GPU-driven culling)单独立项。
This commit is contained in:
@@ -291,6 +291,23 @@ namespace Engine
|
||||
glm::max(glm::length(glm::vec3(t.World()[1])),
|
||||
glm::length(glm::vec3(t.World()[2]))));
|
||||
|
||||
// ---- 实体级粗剔除(联合包围球):四视图全部看不到 →
|
||||
// 跳过整个 mesh 循环(多 mesh 模型的大头优化)----
|
||||
const Mesh::Bounds& mb = model->GetBounds();
|
||||
const glm::vec3 modelCenter =
|
||||
glm::vec3(t.World() * glm::vec4(mb.center, 1.0f));
|
||||
const f32 modelRadius = mb.radius * worldScale;
|
||||
const bool mainVis =
|
||||
mainFrustum.IntersectsSphere(modelCenter, modelRadius);
|
||||
bool cascVis[kMaxCascades];
|
||||
bool anyVis = mainVis;
|
||||
for (u32 c = 0; c < kMaxCascades; ++c) {
|
||||
cascVis[c] =
|
||||
cascFrustum[c].IntersectsSphere(modelCenter, modelRadius);
|
||||
anyVis |= cascVis[c];
|
||||
}
|
||||
if (!anyVis) return;
|
||||
|
||||
for (auto& mesh : model->GetMeshes()) {
|
||||
const auto& bounds = mesh->GetBounds();
|
||||
const glm::vec3 worldCenter =
|
||||
@@ -317,31 +334,32 @@ namespace Engine
|
||||
instanceTruncated = true;
|
||||
return;
|
||||
}
|
||||
// lookup 值 = batches 索引(此前误存 worlds 索引:
|
||||
// 单实体时两序号恰好恒等,多实体共享 mesh 后分叉
|
||||
// → batches[worldsIdx] 越界)。batch.first(GPU
|
||||
// firstInstance 基址)才是 worlds 索引,语义别混
|
||||
const u64 key =
|
||||
(reinterpret_cast<u64>(mesh.get()) << 8) | lod;
|
||||
const auto [it, inserted] = s.lookup.emplace(
|
||||
key, static_cast<u32>(s.result.batches.size()));
|
||||
if (inserted) {
|
||||
// batch 查找 = 数组寻址(Mesh::GetSlot 槽位 + LOD 槽)
|
||||
// batchIdx 存储 +1 偏移(0 = 该槽位尚无 batch)
|
||||
const u32 slot = mesh->GetSlot();
|
||||
if (slot >= s.lookup.size()) {
|
||||
s.lookup.resize(slot + 1);
|
||||
}
|
||||
auto& lodSlots = s.lookup[slot];
|
||||
u32 batchIdx = lodSlots[lod];
|
||||
if (batchIdx == 0) {
|
||||
batchIdx = static_cast<u32>(s.result.batches.size()) + 1;
|
||||
lodSlots[lod] = batchIdx;
|
||||
s.result.batches.push_back({mesh.get(), 0, 0, lod});
|
||||
s.batchWorlds.emplace_back();
|
||||
s.batchPrevWorlds.emplace_back();
|
||||
s.batchMats.emplace_back();
|
||||
}
|
||||
// 实例归入自己的 batch(mesh 分组),保证同 batch
|
||||
// 实例在扁平化后连续——firstInstance 语义的前提
|
||||
const u32 batchIdx = it->second;
|
||||
s.result.batches[batchIdx].count++;
|
||||
s.batchWorlds[batchIdx].push_back(t.World());
|
||||
s.batchPrevWorlds[batchIdx].push_back(prevWorld);
|
||||
const u32 bi = batchIdx - 1;
|
||||
s.result.batches[bi].count++;
|
||||
s.batchMats[bi].push_back({t.World(), prevWorld});
|
||||
s.result.instanceCount++;
|
||||
++totalInstances;
|
||||
};
|
||||
|
||||
tryAppend(m_MainScratch, mainFrustum, mainLod);
|
||||
for (u32 c = 0; c < kMaxCascades; ++c) {
|
||||
if (!cascVis[c]) continue; // 粗剔除已判不可见
|
||||
// 级联降级:越远的级联覆盖范围越大,用越低的 LOD
|
||||
//(阴影本来就是低频信息,误差可接受)
|
||||
tryAppend(m_CascScratch[c], cascFrustum[c],
|
||||
@@ -360,13 +378,10 @@ namespace Engine
|
||||
b.first = base; // 扁平化后的连续区间基址
|
||||
base += b.count;
|
||||
dst.batches.push_back(b);
|
||||
m_Frame.instanceModels.insert(m_Frame.instanceModels.end(),
|
||||
s.batchWorlds[i].begin(),
|
||||
s.batchWorlds[i].end());
|
||||
m_Frame.prevInstanceModels.insert(
|
||||
m_Frame.prevInstanceModels.end(),
|
||||
s.batchPrevWorlds[i].begin(),
|
||||
s.batchPrevWorlds[i].end());
|
||||
for (const auto& m : s.batchMats[i]) {
|
||||
m_Frame.instanceModels.push_back(m.cur);
|
||||
m_Frame.prevInstanceModels.push_back(m.prev);
|
||||
}
|
||||
}
|
||||
dst.instanceCount = s.result.instanceCount;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user