線程跨平臺遊戲開發

原文地址:http://www.intel.com/cd/ids/developer/apac/zho/325610.htm

 

計算機遊戲技術正在經歷重大的概念轉變:轉向多核處理器上運行的多線程引擎。多核處理器爲下一代個人電腦和遊戲控制檯提供動力,遊戲開發人員需要將更多平臺鎖定爲目標。遺憾的是,儘管線程執行和跨平臺支持至關重要,但許多開發人員發現很難將這些功能用於各自的代碼中。本文嘗試通過簡單的演示應用來研究這兩個特點,從而順利完成這一轉換。通過深入瞭解這些技術,遊戲開發人員可以增強對這些技術的理解,並將其實現於所負責的項目之中。

可以在此處找到本文的演示應用。該演示應用程序包括一個在 Windows 上生成和運行的 Microsoft Visual Studio* 2005 解決方案文件,以及一個在 Linux 上生成的文件。運行過程中,將打開一個窗口並繪製一個 OpenGL 場景(圖 1).該演示應用以及構成它的代碼將應用於本文中的所有示例。



圖 1:啓動中的演示應用程序

單擊此處查看放大圖
 

多線程軟件設計是業界長期進行深入研究的一個課題,其基本原理很容易理解。通常,單線程軟件以串行方式執行其所有代碼。對於遊戲,所有的遊戲任務都由一箇中心循環來執行(處理輸入、更新遊戲世界、渲染等),每次屏幕上只能渲染一個幀。對於單核處理器,這種串行執行模式已經足夠了,但多核處理器中有些處理資源並未完全使用。這些處理資源可以重新加以利用,將遊戲任務轉換成獨立的"線程",而這些線程可以在多核處理器的任何邏輯核上執行。這就是所謂的並行或線程執行模型。並行執行是在目前逐步向多核邁進的處理器上實現高性能遊戲體驗的關鍵所在。

該演示應用具有三個線程:一個線程運行基本的事件循環,一個線程更新遊戲對象的位置,而另一個線程將遊戲渲染到窗口上。窗口底部顯示的內容顯示了每個線程執行任務的頻率,以每秒調用的次數爲單位(圖 2)。這種單位等效於用於渲染任務的每秒幀數,但對重複執行任務的所有線程應用更爲常規的名稱。窗口底部的內容還可以顯示任務按照串行或者並行順序執行。按 Tab 鍵,即可切換至該設置,查看其對每項任務 CPS 的影響。

對於渲染任務和更新任務,可通過按不同的鍵(對渲染任務爲 Z 和 X 鍵,對更新任務爲"."(句點)和"/"(斜槓)鍵),以交互方式增加或減少其工作負荷。通過調整這些工作負荷,即可模擬那些運行速度受圖形或計算限制的遊戲了。

一些開發人員發現很難將所有任務的執行速率全部降低,我們是否需要以比遊戲更新更快的速度處理事件?這個問題與如何合理地確定遊戲所需的線程數量密切相關。但是,相對不同遊戲和不同處理環境,得到的答案大相徑庭。性能較高的遊戲可以充分利用可用的邏輯核,所以在單核處理器上運行時,不會出現速度過慢的情況。因此,在決定遊戲所需的線程數之前要檢測邏輯核數目。但在現代線程調度系統中運行時,這種優化功能的實際效果卻很難超出將處理操作分散爲獨立任務的模式。該演示應用通過允許用戶在一個線程中串行執行所有任務展示了這一點。在單核處理器上,由於開銷不足,無法使線程數比邏輯核數目更多。



圖 2:關於不斷增加的工作負荷的演示應用

實施細節
該演示程序包含四個 C++ 類以及某種將這些類聚集在一起的膠合代碼。這些類包括:

ThreadManager:此類用於管理線程池--它是程序開始時創建的一組線程,貫穿程序整個生命週期分配任務。一次性創建線程是爲了節省線程創建以及程序執行過程中屢次重建所需的開銷。該線程池將每個線程分配一個遊戲任務;啓動後重復調用同一個函數。應用這種便捷方案,可以方便地啓動或停止線程,也可以計算每秒調用次數。"未來工作"(Future Work)章節對其它的線程池管理方案進行了討論。

ThreadManager 類還爲線程相關任務提供了便捷方法。該類將每個線程分配到本地存儲區(由此可以將變量與線程本身聯繫起來),這樣就可以根據不同的執行線程運行不同的代碼。該演示應用通過該功能來確保,串行主線程上的其他任務或者更改渲染(在全屏之間來回轉換)時,渲染功能能夠繼續發揮作用。ThreadManager 類還定義了允許某線程在一段時間內處於休眠狀態或讓步於其他線程(如果這些線程正在等待執行)的方法。

該演示應用使用 ThreadManager 子類,稱爲 ThreadManager 系列(ThreadManagerSerial)。該子類還擁有演示應用在專用線程與主線程之間移動任務時所使用的其他方法。

CriticalSection:這是一個幫助類,在 ThreadManager 和演示應用中創建和管理關鍵代碼段,這些代碼段用於防止多個線程同時讀取或修改共享數據。

OpenGLWindow:該類使用 SDL(簡單直接媒體層)庫 1 提供跨平臺、固定分辨率渲染上下文。此類消除了使用 OpenGL 渲染時出現的一些不明顯的缺點。渲染可以在一個窗口中進行,或採用全屏模式。重新創建窗口時(例如,在轉至全屏模式時),OpenGL 無法渲染上下文。爲了對此進行控制,該類爲線程提供了確定是否有效渲染上下文的功能。這是必要的,因爲 OpenGL 要求每個渲染線程與單一渲染上下文相關聯,且如果其上下文無效,則不會自動提示線程。

World:此類專用於該演示應用。它可以管理靜態的三角形背景以及前景中由演示中的線程任務更新和渲染的一組移動點。背景三角形爲渲染任務提供工作負載。前景點用於建立"n 體"(n-body)問題的模型 - 展示萬有引力定律。假設每個點都是太空中的一顆行星或小行星,與其他天體互相吸引,一旦碰撞就會結合。太空中心還有一個不可見的黑洞,吸收與其碰撞的所有物質,但最終會"溢出"並釋放新的天體。n 體問題相當於更新任務的工作負載問題。
演示實驗
該演示可以模擬具備常見運行時特徵的遊戲的性能。有些遊戲要花費大部分運行時間來繪製複雜的場景。而另外一些則花費時間計算遊戲的複雜轉換。通過調整所演示任務的工作負載,可以模擬這些運行時情況,並且評估該情況下的線程執行優勢。

解釋演示應用程序輸出的關鍵在於它是否在多核處理器上運行。線程應用僅在單核處理器計算機上運行。但大多數情況下,如果沒有多核處理器,以並行方式運行線程任務的程序不會比以串行方式運行相同任務的程序更有效。這一通用規則有一種常見的例外情況,就是因大量網絡或磁盤 I/O 的阻擋作用產生的工作負荷。即使在單核處理器上,只要將這些任務線程化,就可以在檢索數據的同時執行其他計算。

下面的這些方案,將針對單核和多核執行分別列出預期結果。請注意,在單核中,更改任何任務的工作負荷都將影響到所有任務的 CPS。在多核中,更改某一任務的工作負荷很少甚至不影響其他任務的 CPS。

演示啓動時,背景顯示 10000,前景顯示 50 個點,並以窗口模式運行。下面的所有方案都將這種狀態假定爲起始狀態。按 Z 和 X 可以將三角形每次調整 10000,按 .(句點)和 /(斜槓)可以將物體每次調整 50。按 Tab 鍵將在串行與並行任務分配間切換。

方案 1:計算綁定執行。按 /(斜槓)鍵向 n 體問題中添加物體,調整更新任務。添加足夠的物體,使更新 CPS 明顯少於渲染 CPS,但不得低於 5(如果可能)。在單核計算機上,這很難實現,因爲增加更新任務負荷的同時會降低其他兩項任務的 CPS。但在多核計算機中,這種狀態很容易實現。在這種狀態下,物體看上去以波浪狀移動,這是對相同數據同時執行渲染和更新任務的負作用。從線程角度看,交互作用並不安全,所以渲染任務會顯示每一幀的部分更新。"未來工作"章節詳細介紹了線程安全的渲染方法。

請注意更新線程的 CPS,然後按 Tab 鍵切換到串行執行。在單核中,所有的任務都會將其 CPS 降至同一較低值(或許稍高一點)。在多核中,所有任務都會降至"甚至更低的"數字。這是爲什麼呢?使用單核時,是在同一個邏輯核上運行所有任務;切換到串行執行後,只會限制較快任務採用與較慢任務一樣的速度運行,因此該任務只需與很少的工作競爭。在多核中,緩慢的任務可能已經獨立佔據一個邏輯核(如無法確定,請參照"未來工作"章節),因此不存在與該核其他任務發生競爭的問題。但在實現任務的串行化時,緩慢任務可能要與其它工作在同一個核上運行,因此速度變得更慢。可以得出這樣的結論:在多核中,以線程方式執行遊戲可以加快所有任務的執行

方案 2:圖形綁定執行。按 X 鍵向背景中添加三角形,調整渲染任務。添加足夠多的三角形,使渲染 CPS 介於 5 到 10 之間,更新 CPS 相對更高(在單核上,可能不會很高)。問題是,雖然渲染非常緩慢,但模擬 n 體問題的更新任務被頻繁調用,因此可以保持較高的準確性。即便使用較低的渲染 CPS/FPS,也可以跟蹤每個物體的路徑。

按 Tab 鍵切換到串行執行。在單核和多核計算機上,渲染任務將使用相同的 CPS,但更新任務只有較低的 CPS。現在更新任務的調用並不頻繁,從而降低了模擬 n 體問題的準確性。結果是,即使應用程序每秒渲染相同數目的幀,屏幕上的操作也會更加混亂,更難執行。物體與黑洞的碰撞將加速,但不會被吸進去,而是通過"隧道"持續高速移動。該隧道移動行爲就是遊戲超緩慢更新的表現形式。在不同類型的遊戲中,此問題可能以快照形式出現,將敵人或專注於加固牆壁的玩家所在的陣營盡顯無餘。推論:圖形綁定遊戲也可以受益於更頻繁的世界/物理更新
結論
計算機遊戲始終是一項高性能事業。由於處理器技術發生了重大轉變,因此需要更好地實現遊戲線程處理,以便充分利用主機平臺的所有功能。此外,可以將目標鎖定在多個平臺上,從而拓展遊戲市場。由於可以對代碼的重要部分執行正確提取,跨平臺開發會相對簡單和直觀。線程處理和跨平臺開發技術爲遊戲(從高端遊戲到自制遊戲)市場提供了重大商機。此處介紹的技術可以應用於現有代碼庫,也可以用於啓動下一個遊戲的開發。
未來的工作
此演示跨越了作爲實驗工具和作爲利用現代技術開發遊戲的可行起點之間的界限。未來工作的着眼點將放在對其中一點的突出。

添加更多平臺:MacOS* 顯然是一個很好的選擇,而且比較容易添加。當然,還可以添加其他平臺(控制檯、手持設備等)。但平臺戰略越廣泛,就需要越多地考慮界面、控件等問題。

爲排序任務創建同步基元:在計算綁定項目中,沒有必要渲染重複的幀。渲染線程可以訪問由更新線程設置的條件變量(另一種普遍實現的線程 API 功能)。在 ThreadManager 類中提取此功能,使其始終具有跨平臺功能。

提供固定的遊戲世界更新頻率:n 體問題是一項敏感的物理模擬,其準確性受遊戲頻率長度的影響。選擇固定的遊戲任務更新頻率可以消除準確性偏差。完成遊戲更新後,該任務會等待一段時間。

雙向緩衝遊戲世界更新以便在增大 FPS 的同時實現線程安全渲染:如果項目與計算綁定,則可以將遊戲狀態平分爲靜態和動態。對於動態一半的兩個副本,遊戲世界線程可以更新一個,而渲染線程可以渲染另一個(以及靜態世界數據)。這就是許多商業遊戲在多核處理器上加快渲染速度的方法。

使用平臺特定的線程調度 API:我們可以將線程分配給任何可用的邏輯核,但不保證任何兩個線程一定會同時執行。每個平臺都有關於如何進行分配以及如何將總運行時間分佈到每個線程的策略。有些平臺會提供一個控制分配和調度策略的 API。我們可以將這些 API 簡化並應用於 ThreadManager 類中。

添加更多的線程任務:AI 循環、動態內容生成、音頻、網絡等都是遊戲項目中常用的功能。其他線程任務可以更好地利用未來將推出的擁有兩個以上邏輯核的多核處理器。除此之外,可以修改任務,以充分利用數據並行功能(使用線程將大型任務分解爲較小的並行任務)。數據並行功能是使任務數較少的遊戲充分利用多核處理器的一種途徑。同樣的,隨着多核技術的發展以及邏輯核數目逐漸控制可並行的任務的數目的趨勢,數據並行將成爲有效利用處理資源的關鍵所在。

放棄串行化,使用 ThreadManager 取代 ThreadManager 系列:這樣做的目的在於允許任務在等待滿足條件時能夠使用更多有效的阻塞調用。例如,在 Windows 上,runWindowLoop 函數可能會調用 WaitMessage,而不調用 PeekMessage,阻塞會一直持續到出現需要處理的事件爲止。渲染函數可能會調用 glFinish,而不會調用 glFlush,阻塞會持續到全部禎繪製完成。或者,可以修改 ThreadManager 使其具備一次性派送線程池策略(生產商-消費者隊列),將線程數與邏輯核數完美地匹配起來,在理論上實現絕對最低線程開銷。
附錄:構建演示應用程序
解包 TCPGD.zip 存檔文件,創建 TCPGD 目錄。

Windows:啓動 Microsoft Visual Studio* 2005 並打開 TCPGD 目錄中的 TCPGD.sln 解決方案文件。選擇發佈配置,並構建和運行解決方案。構建解決方案時,GLF 項目 2 可能會生成一些警告,但這不會影響應用程序的正常構建和運行。

Linux:進入 TCPGD 目錄。構建演示應用,必須單獨構建 GLF 和主項目。以下命令將構建和運行該程序:

cd GLF
make
cd ../Main
make
./main
致謝:
  1. http://www.libsdl.org* SDL - 簡單直接媒體層庫。SDL 用於以跨平臺方式創建窗口和 OpenGL 渲染上下文。
  2. http://www.forexseek.com/glf/* GLF - OpenGL 字體渲染庫。GLF 用於在演示應用中顯示文本。
  3. 《OpenGL 編程指南》(第五版),作者 Addison Wesley,出版時間 2005 年。該"紅寶書"對各級別的 OpenGL 開發都是無價的資源。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章