D-20261102-02|比賽視口背景層移除與像素自適應¶
背景與驅動力¶
issue 001182:第一階段固定削減(D-20261102-01)落地後,內顯機器 (AMD Radeon D3D11、DPR 1.5)的比賽畫面仍每秒約 3 個 200~300 ms 長幀、rAF 17 fps。乾淨環境 (影片分頁暫停、分頁可見)的開關表定位到兩層成本,而場景本身極輕(每幀 76~96 個 draw call、9 萬三角形、 5 個 shader、無陰影、無貼圖上傳、無同步讀回、render() 提交 0.6 ms):
| 開關 | rAF fps | 長幀 ≥100 ms/≥200 ms(40 秒) | rAF p90 |
|---|---|---|---|
| 基線:3.5M 像素+4× MSAA | 17 | 127/118 | 200 ms |
| 反鋸齒關 | 18 | 124/112 | 196 ms |
| +背景層只拿掉 CSS filter | 23 | 116/103 | 167 ms |
| +背景層整層不掛 | 28 | 59/30 | 38 ms |
| +像素再降到 0.87M | 42 | 10/1 | 29 ms |
比賽頁的 o4-game-scene 背景層是一個全幅、帶 CSS filter(saturate/contrast)與 18 秒 transform 呼吸動畫的
圖層,位於已經不透明且全覆蓋的比賽 canvas 之下;瀏覽器合成器仍每幀為它做 filter 與動畫合成,與 WebGL 幀爭
內顯的 3D 引擎。去掉背景層後剩餘成本按像素走;MSAA 與 effectsQuality 都不是槓桿。
考慮過的選項¶
- A(採納):兩步都做——(1) 比賽場景不掛背景層(確定性、零視覺差異,canvas 全覆蓋);(2) 像素單軸自適應 算圖倍率,階梯只有像素一軸,不含反鋸齒與 effectsQuality 階(實測無效)。
- B:只做背景層移除。內顯仍 28 fps、p99 175 ms,未達可玩。
- C:不做執行期自適應,比賽像素預算由 3.5M 再降到約 1.5M。高階機也失去清晰度,且不同內顯的臨界點不同。
決定¶
O4GameScene新增 surface modecanvas:不掛背景層、保留裝飾層與內容;比賽頁以surfaceMode="canvas"使用。展示車(透明 canvas、背景透出)與其他頁維持scene。- 新增純邏輯
AdaptiveRenderScale(src/viewport/adaptive-render-scale.ts):階梯ADAPTIVE_RENDER_SCALE_LADDER = [100, 80, 60, 45](百分比);訊號=rAF 間隔在 2 秒滾動窗的 P90; P90 超過幀預算(1000÷60 ms)的 2 倍持續 2 秒降一階,低於 0.8 倍持續 10 秒升一階;換階後靜置 2 秒讓窗 重新填滿;非有限或倒退的時間戳忽略;間隔達 500 ms 視為暫停(背景分頁 rAF 1 Hz、視窗被遮)——不計樣本、 持續時間從頭算,分頁隱藏時 viewport 也不餵樣本。 O4Viewport只在race場景餵時間戳;其他場景與 renderer 更換時清樣本並把已發布的倍率同步回 100;銷毀時回報 100。 交給 stage 的renderScalePercent改為round(玩家設定 × 自適應 ÷ 100)——玩家設定是上限,自適應只往下。 倍率只在 session 內生效、不持久化、不進 SavedState 或 replay hash。viewport 回報給設定頁的實際比例 (reportEffectiveRenderScale)走純函式hardwareRenderScalePercent:只有 stage 實際值低於要求值超過 floor 容差 1 才算 DPR/像素預算截斷;像素預算與 maxTextureSize 是對 pixelRatio 的絕對上限、不隨要求值等比, 截斷時 stage 的實際值本身就是硬體上限占 native 的百分比,照實回報(以玩家設定封頂);無截斷則回報玩家設定 本身。設定頁的「受裝置 GPU 上限限制」只反映硬體截斷,不把 session 內的自適應降階當硬體上限(直接除回 自適應會因取整少 1 而誤標;等比折算會高估上限)。自適應把要求值壓到上限以下時 stage 觀測不到上限, 回報玩家設定=不顯示提示,是接受的取捨。PresentationSettingsRuntime新增adaptiveRenderScalePercent(100=未降階);賽中 HUD 的效能警告橫幅 在「模擬層 perfWarning 或 呈現層降階中」任一成立時顯示,文案與顯示設定入口不變。- 守門:
game-scene.spec(canvas mode 無背景層、有裝飾層)、race-page.spec(比賽頁用 canvas 表面; 呈現層降階時顯示橫幅、回到 100 時隱藏)、adaptive-render-scale.spec(8 案:階梯與參數、穩定不換階、 持續過載逐階降到 45、單一尖峰不降、遲滯帶維持、持續餘裕升階、暫停幀不計、reset 與無效時間戳)、viewport.spec(自適應降階不算硬體上限;比賽中 renderer 更換倍率與 HUD 回報一併回 100)、race-render-budget.spec(hardwareRenderScalePercent:滑桿 50–100 × 階梯全枚舉無截斷時回報玩家設定本身;截斷回報實際值本身)。
後果與影響¶
- 內顯機器的比賽畫面從 17 fps 升到可玩區間(實測 28 fps 不掛背景層;再降階後 42 fps、p99 33 ms)。 落地後實機(同一顆內顯、分頁在前景、track-02):起跑後 3.2/7.6/11.5 秒依序降到 80/60/45(1.31M 像素), 效能警告橫幅 5.4 秒出現;階 45 穩態 35 fps、p90 29 ms、30 秒內長幀 ≥100 ms 3 個、≥200 ms 0 個 (修前 17 fps、127/118)。離開比賽倍率回 100,再進比賽從 100 重新判定。
- 高階機不受影響:P90 不超過兩幀預算就不降階,畫面維持玩家設定與 3.5M 預算的清晰度。
- 不改物理、共識、SavedState、replay hash;效能警告橫幅的語意由「模擬層撐不住」擴為「本機撐不住(模擬或呈現)」。
- 現況規格:程式架構/ui-frontend.md renderer/context 預算段、 程式架構/settings.md 的 drawing-buffer ratio 說明、 程式架構/network-sync.md 第 8 節效能警告的呈現層口徑。
- 與既有決策的關係:amends D-20261102-01(第一階段的固定削減保留, 本決策補上第二階段)。
定案紀錄¶
2026-11-02 使用者裁決「先診斷」→ 乾淨開關表 → 「兩步都做」。