Skip to content

perf(glsm): fix per-draw client-array re-uploads collapsing GUI/F3 frame rate - #116

Merged
DHJComical merged 3 commits into
mainfrom
perf/f3-glsm-per-call-overhead
Sep 4, 2026
Merged

perf(glsm): fix per-draw client-array re-uploads collapsing GUI/F3 frame rate#116
DHJComical merged 3 commits into
mainfrom
perf/f3-glsm-per-call-overhead

Conversation

@DHJComical

Copy link
Copy Markdown
Owner

问题

0.0.5 → 0.0.7 之间的性能回归:打开 F3 调试界面帧率从 140+ 暴跌至 15(帧时间 +60ms),背包、视频设置等 GUI 界面也明显卡顿。无光影同样复现。

根因

perfDebug 日志定位(gl.clientArrayUpload[count=708-750/s, avgUs≈1150µs],F3 下约 50 次/帧 ≈ 57ms/帧):

  1. 2f54db7 为修 HBM-CE 兼容,将 VertexAttribState 捕获的 client 指针扩到整个底层分配(HBM 复用 BufferBuilder 分配、set pointer 后改 limit)——正确性修复,语义必须保留;
  2. GLStateManager.uploadClientArraysToVBO()每次 draw(只要存在启用的 client-side 顶点属性,HBM-CE 的 client-state 泄漏使其常态启用)都把捕获的整个分配(实测 4.2MB,bufferData orphan + bufferSubData,约 1.1ms/次,与 PCIe 上传耗时吻合)重新上传;
  3. GUI/F3 的文字与矩形 draw 风暴把它放大为帧级卡顿;普通帧约 5 次/帧的全量上传也吃掉了大部分帧时间。

改动(3 个提交)

  • bcd0c67 perf(glsm):client-array 上传按 draw 顶点区间裁剪(主修复)。glDrawArrays(含 QUADS 经 QuadConverter 转换的路径,其共享 EBO 恰好引用 [first, first+count))的读取区间已知,逐属性将上传裁剪到该区间实际读取的字节(4.2MB → 几十~几百字节);最大索引未知的裸 glDrawElements 系列保守保持全量上传。新增 VertexAttribState.computeUploadLength 纯函数 + 10 个单元测试;GLSM perf 周期日志新增 clientArray.stackSample 调用方归因。逃逸通道:-Dactinium.glsmFullClientArrayUpload=true
  • 20e6e73 perf(glsm):无 deferred pipeline 时跳过 blend/program 快照与事件 postIrisGLSMBridge 启动时无条件注册监听器导致 hasListeners() 恒真,2f54db7 引入的每次 tryBlendFunc/glUseProgram 快照+比较在无光影时全部空转。新增 GLSMHooks.consumersActive 门禁,由 PipelineManager 在 pipeline 生命周期维护(仅 DeferredWorldRenderingPipeline 活跃时为 true);有光影时行为逐字节不变。逃逸通道:-Dactinium.glsmHooksAlwaysActive=true
  • f499c63 perf(terrain):缓存 vanilla missing-model 引用,消除 chunk 构建期每方块的 4 级 getter 链(MissingModelCompat,资源重载时经监听器失效;桥 renderer 的第三方 Mixin 绑定契约未触碰)。

验证

  • ./gradlew build --no-daemon 全绿:根项目 140 个测试套件 0 失败,含 CeleritasCompatBridgeJarTest 7/7(桥契约)、新增 VertexAttribUploadLengthTest 10/10、GLSMHooksConsumersGateTest
  • 生产整合包实测(含 HBM-CE、DynamicSurroundings 等):修复前 F3 开启 140+ → 15 fps;部署本分支构建后 F3/GUI 卡顿消失,用户已确认。
  • 有光影路径:门禁仅在 deferred pipeline 活跃时放行,光影开启时快照/事件行为与修复前一致;本次未改动光影渲染语义,compatibility-matrix 无需更新。

备注

  • glDrawElements 路径仍是全量上传;若后续日志显示该路径也热,可再加索引扫描求上界的优化。
  • 排查过程中另发现 a3ad50d 的 BiomeColorNoise 存在最多 60 倍的平面噪声冗余计算(仅 worker 线程构建期、与本次症状无关),将另行评估处理,不在本 PR 范围。

…ne consumes them

IrisGLSMBridge registers its GLSM hook listeners unconditionally at
startup, so VANILLA_BLEND_CHANGE/PROGRAM_CHANGE always had listeners and
the per-call snapshot+compare work introduced in 2f54db7 ran on every
tryBlendFunc/glUseProgram even with no shader pack loaded, while every
listener is a no-op without a DeferredWorldRenderingPipeline. GUI-heavy
frames (notably the F3 debug overlay, ~100 drawString + ~55 drawRect per
frame) multiply this fixed per-call cost into a sharp FPS drop.

Add a cheap GLSMHooks.consumersActive gate maintained by PipelineManager
(true only while a DeferredWorldRenderingPipeline is active) and consult
it before the snapshot/compare/post work. With a deferred pipeline the
gate is always true and behavior is unchanged. Escape hatch:
-Dactinium.glsmHooksAlwaysActive=true.
… renders

MissingModelCompat.isMissingModel walked a four-deep getter chain
(Minecraft -> BlockRendererDispatcher -> BlockModelShapes -> ModelManager)
for every block render during chunk mesh building. The reference only
changes on resource reload, so cache it (volatile, worker-thread safe) and
clear the cache from a reload listener registered in Actinium.onInit.
The one-argument isMissingModel signature is kept so the compat-bridge
renderer bytecode contract is untouched.
… reads

Root cause of the F3/GUI FPS collapse (140+ -> 15 fps with the debug
overlay open, sluggish inventory/video-settings screens): 2f54db7 widened
captured client pointers to the whole underlying allocation to stay
correct when HBM-CE mutates the BufferBuilder limit after setting a
pointer. uploadClientArraysToVBO then re-uploaded that entire allocation
(>4MB observed, ~1.1ms per upload on PCIe) on EVERY draw issued while a
mod leaves client-state attribs enabled. Text/rect-heavy GUI frames issue
hundreds of draws, multiplying this into ~57ms/frame.

glDrawArrays (and the QUADS path through QuadConverter, whose shared EBO
references exactly [first, first+count)) knows the drawn vertex range, so
per-attrib uploads are now clamped to the bytes that range can read via
VertexAttribState.computeUploadLength; indexed draws with an unknown max
index keep the full-allocation upload. Escape hatch:
-Dactinium.glsmFullClientArrayUpload=true.

Also adds clientArray.stackSample caller attribution to the periodic GLSM
perf report to identify which mod drives large uploads.
@DHJComical
DHJComical merged commit 7e0128b into main Sep 4, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant