[性能] 方案 D:静态几何预烘焙进不变 VBO —— A→C 落地后实测只剩 2.8 µs/帧可省,请先裁决是否还做 #17

Closed
opened 2026-09-15 01:37:10 +00:00 by troytt · 1 comment
Owner

拆自 #16 的方案 D。开这个 issue 主要是为了把它关掉或降级:A→C 落地(4cc49a0)之后,重新测了一遍,D 的收益空间已经基本不存在了。

方案原意

地面/墙/屋顶的顶点在整个关卡生命周期里一个 float 都不会变(相机是 uniform,不进顶点)。所以可以在关卡加载时把它们烘进 STATIC_DRAW 的 VBO,每帧只按剔除结果调整 drawElements 的 offset/count,动态缓冲里只剩角色和少量动画对象,把每帧的 CPU 顶点写入降到接近 0。

为什么现在不值了

写 #16 的时候,每帧要写 2 849 个四边形(最差 14 400),顶点写入是实打实的热点。视口剔除之后每帧只剩 204 个(最差 437),把当前渲染器里那段展开后的顶点写入单独拎出来跑基准(Node 22,5 000 帧取平均):

场景 四边形/帧 顶点写入 顶点数据
改造前 平均 2 849 40.3 µs 400.6 KB
改造前 最差 14 400 203.4 µs 2 025.0 KB
A→C 后 平均 204 2.8 µs 28.7 KB
A→C 后 最差 437 5.8 µs 61.5 KB

也就是说 D 的理论收益上限是 2.8 µs/帧(最差 5.8 µs),占 16 ms 预算的 0.02%。

代价

不便宜。要维持画家序,静态 VBO 必须按「地面 / 墙+对象归并 / 屋顶」三段各自连续存放,而剔除后留下的是不连续的下标集合,一次 drawElements 只能画一段连续区间。要么:

  • 每帧把可见区间切成多段,发多次 drawElements —— 直接把刚压到 1 的 draw call 又打回去;
  • 要么每帧重建索引缓冲 —— 那就只是把"写顶点"换成"写索引",省不下什么;
  • 要么放弃精确剔除、改成按块整批画 —— 又把刚省下的 88% 四边形吐回去一部分。

墙和对象还是两路按深度归并出来的,顺序依赖运行时的玩家深度,本身就不是静态的。

建议

关掉,或者标成 P3 挂着。 除非以后实机 profile 里顶点写入重新变成热点(比如取消剔除、或者视口大幅变大),否则不建议花这个复杂度。

请裁决:关闭 / 降级挂起 / 仍然要做。

(复现口径见 #16 的评论;基准脚本是把 SpriteRenderer.quad() 展开后的写入循环单独隔离出来跑的。)

拆自 #16 的方案 D。**开这个 issue 主要是为了把它关掉或降级**:A→C 落地(`4cc49a0`)之后,重新测了一遍,D 的收益空间已经基本不存在了。 ## 方案原意 地面/墙/屋顶的顶点在整个关卡生命周期里一个 float 都不会变(相机是 uniform,不进顶点)。所以可以在关卡加载时把它们烘进 `STATIC_DRAW` 的 VBO,每帧只按剔除结果调整 `drawElements` 的 offset/count,动态缓冲里只剩角色和少量动画对象,把每帧的 CPU 顶点写入降到接近 0。 ## 为什么现在不值了 写 #16 的时候,每帧要写 2 849 个四边形(最差 14 400),顶点写入是实打实的热点。视口剔除之后每帧只剩 **204 个**(最差 437),把当前渲染器里那段展开后的顶点写入单独拎出来跑基准(Node 22,5 000 帧取平均): | 场景 | 四边形/帧 | 顶点写入 | 顶点数据 | | --- | --- | --- | --- | | 改造前 平均 | 2 849 | 40.3 µs | 400.6 KB | | 改造前 最差 | 14 400 | 203.4 µs | 2 025.0 KB | | **A→C 后 平均** | **204** | **2.8 µs** | **28.7 KB** | | **A→C 后 最差** | **437** | **5.8 µs** | **61.5 KB** | 也就是说 D 的**理论收益上限是 2.8 µs/帧**(最差 5.8 µs),占 16 ms 预算的 **0.02%**。 ## 代价 不便宜。要维持画家序,静态 VBO 必须按「地面 / 墙+对象归并 / 屋顶」三段各自连续存放,而剔除后留下的是**不连续**的下标集合,一次 `drawElements` 只能画一段连续区间。要么: - 每帧把可见区间切成多段,发多次 `drawElements` —— 直接把刚压到 1 的 draw call 又打回去; - 要么每帧重建索引缓冲 —— 那就只是把"写顶点"换成"写索引",省不下什么; - 要么放弃精确剔除、改成按块整批画 —— 又把刚省下的 88% 四边形吐回去一部分。 墙和对象还是两路按深度归并出来的,顺序依赖运行时的玩家深度,本身就不是静态的。 ## 建议 **关掉,或者标成 P3 挂着。** 除非以后实机 profile 里顶点写入重新变成热点(比如取消剔除、或者视口大幅变大),否则不建议花这个复杂度。 请裁决:关闭 / 降级挂起 / 仍然要做。 (复现口径见 #16 的评论;基准脚本是把 `SpriteRenderer.quad()` 展开后的写入循环单独隔离出来跑的。)
Author
Owner

按实测关闭:视口剔除落地后,顶点写入只剩 2.8 µs/帧(最差 5.8 µs),占 16 ms 预算的 0.02%,而静态 VBO 为了保画家序只能画连续区间、剔除后留下的却是不连续下标,绕开的代价会把刚压到 1 的 draw call 又打回去 —— 收益与复杂度完全不成比例。

如果以后实机 profile 里顶点写入重新成为热点(取消剔除、视口大幅变大、或每帧四边形数量级回升),再重开。

按实测关闭:视口剔除落地后,顶点写入只剩 **2.8 µs/帧**(最差 5.8 µs),占 16 ms 预算的 0.02%,而静态 VBO 为了保画家序只能画连续区间、剔除后留下的却是不连续下标,绕开的代价会把刚压到 1 的 draw call 又打回去 —— 收益与复杂度完全不成比例。 如果以后实机 profile 里顶点写入重新成为热点(取消剔除、视口大幅变大、或每帧四边形数量级回升),再重开。
Sign in to join this conversation.
No Label
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Reference: troytt/diablo2-web#17
No description provided.