Appearance
降级的台阶
背景:帧率下跌时画面断崖式卡顿——降级是有的,但恢复时把用户原始设置弄丢了,或降级后永远回不来。 过程:四档降级(0-3)+ 快照保存/恢复 + _suppressSnapshotReset 防反馈循环 + custom 冻结为权威配置。
桌面壳把场景压到了极限。
四个模型,满分辨率,阴影全开,水面反射拉满——它知道帧率会掉,但它想看看到底能掉到哪。
帧率 30。25。20。
"卡了。" 它说。
帧率还在掉。18。15。12。
"降级呢?" 外交官问,"你的系统不是有自动降级吗?帧率低了就该降画质,画质降了帧率就该回来。"
"降级……没降。"
桌面壳打开性能监视器,看到降级级别一直停在 0——"未降级"。它明明写了降级逻辑:帧率低于阈值,就砍阴影、砍分辨率、砍反射。但此刻那个逻辑像睡着了。
"帧率在掉,降级没反应。" 它说,"它不知道自己的世界正在变卡。"
"它不知道,还是它不敢降?"
桌面壳愣了一下。
"我不敢降。" 它承认,"我写了降级,但我怕降级——怕降完回不来。"
一、台阶
桌面壳找到了降级系统。
DegradeLevel——四级台阶:
typescript
type DegradeLevel = 0 | 1 | 2 | 3;"零级是原画,三级是保底。" 它说,"每一级砍掉一批东西:一级砍阴影精度,二级砍分辨率,三级砍水面反射。台阶的意义是'渐进'——帧率掉一点,降一级;再掉,再降一级。不会从天堂直接跌进地狱。"
每一级对应一套完整配置:
typescript
const LEVEL_CONFIGS: Record<DegradeLevel, LevelConfig> = {
0: { light: { shadowEnabled: true }, render: { renderScale: 1.0 }, env: {...} },
1: { light: {...}, render: { renderScale: 0.8 }, env: {...} },
2: { ... },
3: { ... }, // 保底:能跑就行
};"台阶不是'少画一点',是'换一套画法'。" 桌面壳说,"每一级都是完整的状态:光照怎么调、渲染怎么调、环境怎么调。降级不是调一个参数,是整体切换到下一档世界观。"
"为什么不只调 renderScale?"
"因为只调一个参数,画面会变得'半吊子'——分辨率降了,阴影还是满的,帧率省不下来。整套配置一起换,每级都是自洽的:这一档该开什么、关什么,写死,不纠结。"
二、快照
但台阶会弄丢东西。
"降级是切配置,恢复是切回去。" 桌面壳说,"可'切回去'切到哪儿?用户原来把阴影开到 0.8、把反射开到 low——降级时我把它们改成 0.5、off。恢复时,我该恢复到什么?"
"恢复到用户的原始设置。"
"可原始设置被降级覆盖了。"
桌面壳找到了答案——快照。降级开始前,先把用户原始设置整个拍下来:
typescript
_snapshot = {
light: _bridgeGetLightState(), // 用户的光照设置
render: _bridgeGetRenderState(), // 用户的渲染设置
env: { qualityProfile, reflectionQuality, ... }, // 用户的环境设置
};"降级是暂时借走用户的设置,快照是借条。" 它说,"恢复时,照着借条一样一样还回去——阴影回到 0.8,反射回到 low,分毫不差。"
"如果快照没拍到呢?"
"那就回不去了。" 桌面壳说,"降级发生的时候,如果快照没拍下来——恢复时没有借条,就不知道该还什么。用户的原始设置永远丢了,画面停在降级档,再也上不去了。"
它想起之前的一个 bug:系统启动早期,用户手动切性能模式——那时渲染桥还没注册,快照整个被跳过。降级照常发生,恢复时没有借条,环境设置永久停留在降级档。
"降级可以发生在任何时候," 它说,"但快照必须无条件拍下。借条不能因为'还没开张'就不写——开没开张,借了就要写借条。"
三、循环的陷阱
快照解决了丢失,但引入了循环。
"降级调 setLightState——恢复调 setLightState——而 setLightState 内部又调用 resetPerformanceSnapshot,把快照恢复了。" 桌面壳说,"恢复快照 → 触发 setLightState → 触发 resetPerformanceSnapshot → 又恢复一次……这是'降级→恢复→再降级'的反馈循环。"
"怎么断?"
"加一个抑制标志。"
typescript
// 抑制标志:applyDegrade 调 setLightState/setRenderState 时置 true,
// 防止内部的 resetPerformanceSnapshot() 反向恢复快照,
// 形成「降级→恢复→再降级」的反馈循环。
_suppressSnapshotReset = true;
try {
_bridgeSetLightState(changes.light); // 降级中:抑制反向恢复
} finally {
_suppressSnapshotReset = false; // 恢复快照时:允许正常执行
}"降级期间,我告诉系统:'我正忙着降级,你别自作主张恢复快照。'降级完,恢复标志,世界回归正常。"
"为什么用 finally?"
"因为 setLightState 可能抛异常。" 桌面壳说,"异常了,抑制标志也要复位——否则标志永远卡在 true,以后所有的恢复都被压制,世界再也回不来了。finally 是'无论成败,都要还钥匙'。"
四、冻结的档位
最后是 custom 模式。
"自动降级之外,还有手动模式。" 桌面壳说,"用户自己调了渲染设置——那叫 custom。custom 的意思是什么?"
"用户说了算?"
"对。用户手动调了设置,系统不该再自动降级覆盖它。" 它说,"custom 模式:冻结当前渲染/光照状态为'权威配置',同时恢复自动降级遗留的快照——让降级彻底退场,把控制权还给用户。"
typescript
} else if (mode === 'custom') {
// Custom 模式:冻结当前渲染/光照状态为权威配置。
// 恢复自动降级遗留的快照(若有),使后续不再被降级覆盖;
// updatePerformance 已在 custom 模式下早返,不会再次降级。
resetPerformanceSnapshot();
}"自动降级是系统的手,custom 是用户的手。" 桌面壳说,"系统的手要松开——把手动设置冻结成权威,自动降级从此不碰它。用户调了什么,就是什么。"
"那自动降级不是没用了吗?"
"自动降级服务'默认'——用户不干预时,系统替用户守护帧率。用户一旦插手,系统立刻让位。降级是系统的体贴,custom 是用户的尊重——体贴要让位于尊重。"
尾声
桌面壳把场景压到了极限。
帧率 30。25。20。18。
降级动了——一级:阴影精度降。帧率回来一点。22。还在掉——二级:分辨率降。帧率回到 25。又掉——三级:反射关。帧率稳定在 30。
"它自己会降了。" 外交官说。
"而且它会回来。" 桌面壳说,"帧率恢复,台阶一级一级往上爬——借条还在,用户的设置分毫不差地还回去。"
它切到 custom,手动把阴影调高。自动降级退场,画面停在它调的样子。
"降级不是砍画质," 桌面壳说,"是画质的台阶——帧率掉,一级一级下;帧率回,一级一级上。每一步都记得来路,每一级都留了借条。"
"那最怕什么?"
"最怕忘记拍借条——降了,回不来。最怕反馈循环——降了,又被恢复顶回去。最怕 finally 没走——抑制标志卡死,世界永远停在降级档。"
它看着稳定的帧率:
"台阶不可怕。可怕的是下了台阶,忘了自己从哪级下来的。"
教训:降级是台阶不是悬崖——帧率掉一级级下,恢复时借条(快照)要分毫不差地还回去。
降级可以随时发生,快照必须无条件拍下;finally 里还钥匙,循环才断得开。