MGFX 性能模型
MGFX 的性能目标不是“消灭 immediate”,而是在 immediate 心智模型下尽量减少 Lua 侧无意义工作、材质参数上传、临时表和临时 Color。GMod UI 常见问题不是 draw call 数量单独过高,而是每帧为了调度、归类、转换和上传参数付出过多 Lua/Source API 成本。
当前方向
- Shape 和 widget 保持 immediate shader/fallback path。
- Public
NameEx(..., style)仍然接受表,但 renderer 内部必须在 API 边界展开为直接参数,不能一层层传 style 表再重复解析。 - 只保留经过实测的专用 fused shader,不恢复通用 data-texture batch scheduler。
- 常规 shape 参数优先通过
$viewprojmat/ pixel shaderc11上传。 $invviewprojmat/ pixel shaderc15作为辅助 16-float 参数页,$c0..$c3只保留给兼容和诊断。- Pattern 在 shader 中数学化生成,不拆成大量
LineEx或几何段。 - Scoreboard、表格、聊天和普通 label 文本优先走原生 GMod text。
当前实测基线
当前代表性压力场景是复杂 shop UI:大量 rounded box、gradient、stroke、shadow、glow、image 和少量 text 混合绘制。关闭诊断后,实测结果为:
满商品列表 稳定 130+ FPS
商品较少分类 稳定 160+ FPS这次提升来自减少 Lua 侧准备工作和无意义 pass,而不是恢复通用 batch scheduler。
Prepared 绘制层
外部 API 可以继续使用 style table,因为这是 GLua/Lux 调用方最容易读写的形式。但内部绘制层不应该继续传递这些表。
正确链路:
public API style table
-> API 边界解析一次
-> prepared fill/stroke/effect scalar 参数
-> shader/fallback draw应避免的链路:
style table
-> helper table/spec
-> 转发 style table
-> 再次 normalization
-> shader/fallback drawRoundedBox、Chamfer、Progress/Segment、Image round/chamfer、line-backed rect、poly/chamfer fallback 现在都走 prepared 参数。这样 public API 仍然易用,但 inner loop 不再重复调用 shadowRaw、outerGlowRaw、innerGlowRaw、backdropStyle、patternStyle、fillFromStyle 和 fillVisible。
参数上传
本地 GMod benchmark 结论:
SetFloat x16 ~3.6-3.9 us/iter
SetUnpacked + SetMatrix ~0.5-0.6 us/iter因此 hot shape path 的 16 个常规 float 参数统一打包到 Matrix():SetUnpacked(...),再用:
mat:SetMatrix("$viewprojmat", matrix)HLSL 侧读取:
const float4x4 MGFXExtraParams : register(c11);注意 Source/GMod 的矩阵索引按列抵达 HLSL。Lua 端打包必须使用 MGFX 的统一 helper,不要在调用点自己猜行列顺序。
辅助参数走 $invviewprojmat:
const float4x4 MGFXAuxParams : register(c15);mat:SetMatrix("$invviewprojmat", matrix)SetFloat("$cN_x", ...) 仍然存在,但它是备用/诊断手段。只有已经确认 matrix 参数页无法满足需求,并且有实测理由时,才考虑逐 float 上传。
Pattern 数学化
Pattern 不应该在调用层展开成许多 line、box 或 polygon。StripePattern、SmokePattern 和 WornPattern 是 paint slot,应该由对应 shape shader 根据当前 shape local space 直接采样。
正确方向:
MGFX.RoundedBoxEx(x, y, w, h, {
radius = 8,
fill = Color(8, 18, 24, 220),
pattern = MGFX.StripePattern({
color = Color(255, 255, 255, 22),
spacing = 12,
width = 2,
angle = 135,
}),
})错误方向:
for i = 1, 40 do
MGFX.LineEx(...)
end如果某个界面需要大面积斜线、烟雾、扫描线或噪声底纹,优先补 shader pattern 能力,而不是在 UI 层堆 primitive。
融合 Shader 策略
专用 shader 是可行的,但必须复刻原始分层结果。允许的前提:
- 输入参数布局清晰,主参数走
MGFXExtraParams。 - 需要额外参数时只用
$c0..$c3辅助页。 - 抗锯齿、stroke、透明渐变、pattern、glow、backdrop 和 blend order 与原路径一致。
- 不能为了少一个 pass 改变 source-over 视觉结果。
当前保留的 fused 路径:
roundrect_fx fill/stroke + innerGlow
chamfer fill/stroke + optional innerGlow
ring_fx fill/stroke + optional innerGlow
roundrect_shadow_outer shadow + outerGlow
chamfer_shadow_outer shadow + outerGlow
ring_shadow_outer shadow + outerGlow
image_mask_shadow_outer shadow + outerGlowshadow 和 outerGlow 在 API 语义上仍然分离;合成 pass 只用于能保持 CSS-like 结果一致的 shape。backdrop、convex poly 的 shadow/glow 和部分 pattern 仍可能是独立 pass,因为它们的 draw bounds、framebuffer read、参数页压力或 blend order 是可见行为。
RoundedBox 还支持 shadow = { {...}, {...} } 多层阴影。它用于替代“为了多个阴影叠多次完整 RoundedBoxEx”的写法:style 只解析一次,每一层只走 shadow-only path,主体和其它效果仍然只画一次。
Backdrop 共享模糊
Backdrop blur 按“引擎渲染帧 + backdrop.level 整数层”共享,不再由每个 控件各自捕获 framebuffer。每层第一个非零 blur 捕获当时的 framebuffer, 并完成横向、纵向两个全屏 pass;同层同强度的 shape 随后只做一次带遮罩采样。
如果本帧有 N 个模糊 backdrop,且都没有显式 recapture,统计应为:
captures 1
reuses N - 1
blur passes 2同层切换 blur 强度只会从该层的原始捕获重新执行两个模糊 pass,不再次复制 framebuffer。提高 backdrop.level 会在该位置重新捕获,使高层能包含已经画出的 低层 UI。只有同一 level 内也必须强制获取更新源时才设置 backdrop.recapture = true;不要给每个卡片或列表行都设置 recapture。
分配规则
Hot paint path 中避免这些写法:
MGFX.RoundedBoxEx(x, y, w, h, {
fill = Color(20, 24, 32, 220),
})
local c = colorAlpha(baseColor, alpha)第一种每帧创建 style table 和 Color。第二种如果返回新 Color,也会制造分配压力。MGFX 会尽量在内部复用 scratch record,但调用方仍然应该缓存稳定 style、pattern、gradient 和颜色对象。
更好的方向:
local panelFill = Color(20, 24, 32, 220)
local panelStyle = {
radius = 8,
fill = panelFill,
}
function PANEL:Paint(w, h)
MGFX.RoundedBoxEx(0, 0, w, h, panelStyle)
end如果颜色每帧变化,优先复用已有 Color 对象并改字段,或者让上层 UI 状态缓存少量离散状态。不要在行级 scoreboard、列表或 HUD 表格中为每个 cell 每帧创建临时对象。
文本成本模型
Plain text 走原生 GMod text。MGFX text composer 只适合需要 shader 效果的文字:gradient face、soft outline、glow、surface polish、weight bias 等。
不需要特效的高频文本应该直接使用原生绘制,尤其是:
- scoreboard rows
- player names
- dense tables
- chat/log
- rapidly changing counters
如果要用 FX text,尽量让字符串集合稳定,并使用 MGFX.PrewarmText 预热。
性能分析
先安装 mgfx/devtools.lua 或 @lux/mgfx/devtools。开发时:
mgfx_profile 1
mgfx_draw_counts 1
mgfx_status真实 FPS 检查时关闭诊断:
mgfx_profile 0
mgfx_draw_counts 0诊断本身会产生额外计数和文本输出成本。最终判断要看关闭诊断后的体感和帧率。