← All posts English version →

不碰遊戲怎麼數 FPS?我們 overlay 背後的 ETW 技術

你正在打遊戲,角落的 overlay(疊在遊戲畫面上的小面板)顯示現在是幾 FPS(每秒幾張畫面)。你可能會想:它是怎麼知道的?你沒在遊戲裡裝過幀數計數器,也沒有往遊戲裡加入任何程式。這個數字是從哪裡來的?

這個 overlay 沒有碰遊戲,也不能碰。如果工具進入線上遊戲內部去數幀,防作弊系統就可能偵測到有外來的程式,而最壞的結果不是當機,是你的帳號被標記。所以我們從遊戲外面數。Windows 本來就會記錄畫面送出的次數,我們讀的是這份紀錄。

這篇講三件事:負責數幀的作業系統功能、為什麼這個 overlay 可以跟排位賽一起開,還有一個 bug。這個 bug 曾經讓我們的計數器在遊戲只有約 200 FPS 時,顯示成 500 到 600 FPS。

前提:絕不碰遊戲本體

量 FPS 常見的做法是「進到遊戲裡面量」:RTSS 這類工具會把 DLL(外加的程式模組)注入遊戲程序,並攔截(hook)遊戲的 Present 呼叫。遊戲每送出一張畫面,就會呼叫一次 Present,所以攔到一次就算一幀。這個方法效果很好,但也是防作弊系統主要偵測的對象。對一個要跟線上遊戲同時執行的工具來說,注入不是選項:最壞情況不是當機,是使用者的帳號被標記。

所以需求變成:完全不碰目標程序,數別人的幀。

Windows 已經記錄了每一幀

答案是 ETW(Event Tracing for Windows)。這是 Windows 內建的機制,會把系統各處發生的事件記錄下來。負責顯示畫面的系統元件本身就會留下紀錄:Microsoft-Windows-DXGI 這個事件提供者(provider),會在程序送出一幀時發出事件(Event 42,Present::Start)。我們的程序訂閱這個事件串流,用前景遊戲的 PID(程序編號)篩選,再每秒計算收到幾個事件,得到的就是 FPS。整個過程都在遊戲外面進行。這和 Intel 的 PresentMon 用的是同一個資料來源。舊的 DirectX 9 遊戲走另一條路徑,所以我們同時訂閱 Microsoft-Windows-D3D9 的 Event 1,並用旗標避免同一幀從兩邊各算一次。

想像一棟有大廳警衛室的住宅大樓。要算某位住戶進出大門幾次,你不會闖進他家,也不會在他家門口偷裝攝影機。你只要調大廳的監視器畫面,因為從正門經過的人本來就都被錄下來了。ETW 就是這套全大樓的中央監視系統:Windows 早就把每一次「有一幀送出門」都記下來了,所以我們只要看錄影,不必硬闖進遊戲裡。

那個把 200 FPS 算成 600 的 bug

第一版上線後,玩家回報:遊戲實際大約 200 幀,overlay 卻顯示 500–600。不是偶爾跳動,而是穩定地顯示成實際數字的三倍左右。

事後看,原因很明顯:不少遊戲不是只有一條畫面來源。很多引擎內嵌 Chromium 瀏覽器來做 UI(選單、商城、地圖介面),這個瀏覽器有自己的 swapchain(把畫好的畫面交給螢幕的緩衝區),會自己送出畫面。我們的計數器把該 PID 的所有 Present 事件加總:主畫面約 200 幀,加上一個很忙的網頁 UI,報出的就是兩者的總和。修法和 PresentMon 的做法一樣:按 swapchain 分組,只回報事件最多的那一組(主畫面),網頁 UI 的事件自成一組,不再混進主畫面的數字。

我們的計數器當初像在數高速公路的車流:站在交流道口,把每條車道的車一次全算進去,包括主線,也包括一條從大賣場匯進來的繁忙匝道,然後報出一個超大的數字。難怪會變成 600。修法是分車道各算各的,只報主線。遊戲的選單和商城畫面就是那條賣場匝道:它有自己穩定的一股「車流」,跟遊戲主畫面的快慢沒有直接關係。

接著又出現一個問題:事件附帶的資料(payload)裡,指向 swapchain 的欄位,在不同 Windows 版本叫不同名字(有的叫 pIDXGISwapChain,有的是類似的寫法)。如果讀錯欄位名,所有事件會全部歸到同一組,等於悄悄重現原本的 bug。最後穩定下來的版本,改成不依賴欄位名稱的掃描方式:找出 payload 裡名字含「swap」的指標欄位,並且每秒把各個 swapchain 的分組明細寫進診斷紀錄(log)。如果哪台機器的欄位又換了新名字,我們看診斷紀錄就能知道,不用猜。

這個做法做不到的事

從程序外面量,有這幾個限制:

  • 跟著前景視窗走。前景視窗就是你正在操作的視窗。用 Alt-Tab 切到別的視窗,overlay 就會改量你切過去的那個程式。這是設計如此,使用多螢幕時要留意。
  • 數的是 present 次數,不是每幀耗時。FPS 數字本身可靠,但沒有每幀耗時的百分位數(frametime percentile),也沒有 1% low(最慢那 1% 畫面的幀率)。要深入分析畫面停頓,建議用 PresentMon 或 CapFrameX,這類分析它們做得更好。
  • ETW 追蹤工作階段(session)需要管理員權限。PowerDoze 在背景執行的程式本來就以系統管理員身分管理電源,所以 overlay 不必另外取得權限。

電源工具為什麼需要這個數字

這個 overlay 存在的目的,是讓你一眼回答一個問題:我是不是在為看不到的幀付電費?165 Hz 的螢幕一秒最多顯示 165 張畫面,選單卻以 400 FPS 運算,多出來的畫面看不到,只會增加發熱和耗電。FPS 和 Hz 並排顯示。你降低顯示卡(GPU)的功耗上限(power limit)時,效能通常幾乎不受影響,但仍然可以直接看數字確認 FPS 有沒有下降,不必憑感覺猜。

從 Microsoft Store 取得 PowerDoze從 Microsoft Store 取得 PowerDoze

Nisonxi

我是 Nisonxi,PowerDoze 的開發者。當初純粹因為我自己受夠了 Windows 桌機整天全速發呆吃電、卻找不到一個能「看懂情況、自動切換」的工具,乾脆自己做。這個部落格就是我一路上的筆記。

關於作者與 PowerDoze →