天天看點

設計并行化遊戲引擎的架構

作者: Jeff Andrews

譯者:百年孤寂

設計一個功能可分解的、資料可分解的系統可以提供大規模的并行化執行,同時保證發揮多核處理器的性能。

随着多核心處理器的降臨,對可并行計算遊戲引擎的需求已經變得越來越重要了。盡管僅僅依靠GPU和單線程遊戲引擎依然是可行的,但是在一個系統上使用多核處理器所具有的優勢會給使用者帶來更深刻的體驗。譬如,使用多核CPU一個遊戲可以增加更多的實體剛體對象來提升效果,或者開發出更加智慧的類人化的AI。

并行化遊戲引擎架構,或者稱為多線程引擎,目的是在開發平台上利用所有的處理器來提升性能。(引擎)通過并行化處理,各個功能子產品可以利用所有可用的處理器。當然,說比做要容易,畢竟在遊戲引擎中很多東西是互相交叉的,這通常會引起線程錯誤。是以,需要設計一套系統來合适地處理資料同步問題,同時避免被同步鎖所限制。此外,也需要一套方法來保證在并行方式下處理資料同步時使串行處理消耗盡可能小。本文要求讀者需要對現代計算機遊戲發展以及遊戲引擎線程程式設計有很好的了解和工作經驗。

2.并行處理态

并行處理态的概念對于一個高效的具有多線程運作時态的引擎來說是非常重要的。引擎如果要實作真正意義上的并行處理——即盡可能少的同步損耗,則需要引擎内部各個系統在運作時坐到盡量少的互動。盡管資料需要共享,但是現在每個系統都應該有自己的一份資料拷貝,而不是通過一個公共的方式來通路資料。這樣各個系統之間将不再有資料依賴關系。任何一個共享資料的變化都會被送到一個狀态管理器那裡,并且被加入一個變化隊列,不妨稱作消息提示隊列。一旦各個系統完成處理任務,他們将會被提示改變自己的狀态,同時更新各自内部的資料結構(作為消息隊列的一部分)。使用這一機制将會大大減少同步損耗,使得各個系統能更加獨立地工作。

2.1執行模式

當各個系統同步運作時(即各系統的操作被限制在同一個時鐘内),對于執行狀态的管理将會達到最優。這個時鐘的頻率可以等于幀速率,當然這并不是絕對的。這個時鐘的頻率甚至可以不是一個固定的值,然而若使這個跨度等于處理一幀所需要的時間——無論這一幀有多麼長,我們就可以完全不考慮頻率了。你對執行态的管理的實作将會決定這個時鐘跨度。圖示1描繪了不同系統在使用自由的時鐘步進時的狀态,這種狀态下這些系統并非在同一個時鐘内完成執行。除此之外,圖示2描繪了所有系統在同一個鎖定的時鐘下是如何執行的。

設計并行化遊戲引擎的架構

圖示1. 自由步進模式下的執行态

2.1.1 自由步進模式

在這一模式下系統的運作時間取決于任務所需要的時間。這裡的自由并非指系統在完成任務之前是不自由的,而是指系統可以自由選擇需要使用的時鐘數。

在這個方式下,一個普通的對于狀态變化的提示對于狀态管理器來說是不夠的,相關的資料也需要被包含在該提示中。這是因為當一個系統修改了共享資料時它仍有可能還在執行,而這時别的系統也需要更新這些資料。這就需要越來越多的記憶體做備份,這種方式顯然不是最理想的。

2.1.2 鎖定步進模式

這一模式要求所有的系統在同一個跨度内完成各自的處理。這樣既易于實作同時又不需要将資料附加在提示中,因為系統的狀态發生變化時可以在運作周期的結尾簡單地通過通路别的系統來擷取資料。

鎖定步進模式可以通過在多個步驟中進行交叉執行來實作一個假的自由步進模式。譬如當AI在第一個時鐘計算出它初始的“宏觀視角”下的目标後,在下一個時鐘内它可以在宏觀目标下關注更具體的目标,而不僅僅是重複上一個宏觀目标。

設計并行化遊戲引擎的架構

圖示2. 鎖定步進下的執行态

2.2 資料同步

基于多個系統可以對同一個共享資料做出改變,那麼就需要确定在這些變化中到底那個值才是正确且可以使用的。有兩種機制來解決這個問題:

l         時間,最後一個做出變化的系統的值是正确的。

l         權限,具有更高權限的系統的值是正确的。當多個系統擁有相同權限時可以與時間機制結合使用。

在這兩種機制下,那些被認為是舊的資料将會被覆寫或者從提示隊列中抛棄掉。

因為資料是共享的,那麼在給資料賦相對值時可能因為這些資料是沒有順序的而變得難以掌握。為了消除這一障礙,當系統更新資料時使用絕對值來指派以達到新舊交替。絕對值和相對值的結合使用是比較理想的,但是這也要根據情況而定。譬如,像位置,朝向這種公共資料,應該用絕對值來辨別,這是因為在建立一個變換矩陣時需要考慮接收資料的順序。然而,一個建立粒子的系統,在完全擁有粒子資訊的情況下,可以隻做相對值的更新。

3.引擎

設計引擎時應關注結構的彈性,以使得在擴充功能時更加簡便。基于此,引擎在各種受到限制(譬如記憶體)的平台上應用時可以很好地做出調整。

引擎由兩部分組成,一部分是架構,另一部分是管理器。架構(章節3.1)包含了遊戲中會重複出現的擁有多個執行個體的那些部分,同時也包含那些出現在主循環的東西。管理器(章節3.2)作為單件存在并且獨立于遊戲邏輯。

下面的圖描述了組成引擎的各個部分:

設計并行化遊戲引擎的架構

圖示 3:引擎的進階架構

值得注意的是,處理遊戲的功能,即某個系統,是與引擎差別對待的。基于子產品化的目的,将引擎作為一種“膠水”将各個功能聯結起來。子產品化使得系統可以按照需要進行加載或者解除安裝。

接口是引擎和系統之間進行通信的途徑。在系統實作了接口之後引擎就可以使用系統的功能了,相反在引擎實作了接口之後系統也可以通路引擎中的管理器。

附錄A對這一概念做出了更加清晰的解釋,“引擎示例圖”。正如章節2所言,“并行執行态”的概念使得系統在本質上是離散的。這樣系統在并行運作時就不會互相幹擾。然而這種并行在系統之間需要通信時無法保證資料的穩定。系統間通信的理由有兩個:

l         通知另一個系統共享資料已經發生了變化。(譬如位置,朝向)

l         請求一些自身并不包含的功能。(譬如AI系統要求地形/實體系統執行一次射線碰撞檢測)

第一個通信問題通過實作前一章所述的狀态管理器來解決。狀态管理器将在章節3.2.3“狀态管理器”進行更詳細的讨論。

要解決第二個問題,需要在系統中加入一個用來給不同系統提供服務的機制。章節3.2.3“服務管理器”将會進行深入的解釋。

3.1 架構

架構的作用是把引擎中不同的部分聯結起來。引擎的初始化将在架構内完成,但是管理器的初始化是全局的,不受架構影響。場景的資訊同樣也儲存在架構内部。基于彈性的考量,場景,或者稱為通用場景,等于僅僅作為容器組成整個場景的通用對象。章節3.1.2對此提供了更詳細的資訊。

遊戲循環同樣在架構内執行,下面是遊戲循環的流程:

設計并行化遊戲引擎的架構

圖示 4:遊戲主循環

由于引擎運作在一個視窗環境,那麼遊戲循環的第一步就是處理來自作業系統的視窗消息。如果這些消息沒有被處理那麼引擎也不需要做額外的工作。下一步是由排程器向任務管理器釋出系統的任務。這一部分将在章節3.1.1進行更詳細的讨論。接下來,由狀态管理器(章節3.2.2)跟蹤的消息被分發給需要做出響應的部分。最後,由架構來确認執行的狀态并決定引擎是否退出,還是繼續執行其他的任務,譬如進入下一個場景。引擎的執行态由環境管理器負責,這一部分将在章節3.2.4進行讨論。

3.1.1 排程器

排程器管理主時鐘供執行時使用,主時鐘頻率應該是事先設定好的。時鐘頻率也可以是沒有限制的,譬如在基準測試模式下需要時鐘可以在運作結束前就停止。排程器通過任務管理器在一個時鐘長度内将系統進行注冊。在自由步進模式下(章節2.1.1)排程器和系統進行通信來決定系統完成執行所需要的時鐘數,以及哪些系統做好了執行的準備或者在某一個時鐘後完成執行。鎖定步進模式(章節2.1.2)下所有的系統的起始和結束都分别在同一個時鐘内,是以排程器隻需要等待系統完成執行即可。

3.1.2 通用場景和對象

通用場景和對象作為某些功能的容器存在于系統之中。通用場景和對象自身并不擁有任何功能,除了與引擎進行互動的功能。然而它們可以被擴充成包含系統功能的容器。由此這些容器可以在松耦合關系下接管可用系統的屬性,而不必與某個特定的系統進行粘合。松耦合這一特點使得系統之間可以互相獨立,進而使得并行執行成為可能。下面的圖示描述了通用場景和對象在系統内的擴充:

設計并行化遊戲引擎的架構

圖示 5:通用場景和對象的擴充

擴充的工作執行個體如下:一個通用場景被擴充成可以包含圖形、實體,以及其他屬性的容器。圖形場景擴充用來初始化螢幕和其他渲染對象,實體場景擴充用來設定剛體世界,譬如重力等等。場景包含對象,是以,一個通用場景會擁有若幹通用對象。一個通用場景也可以被擴充成為包含圖形、實體,以及其他屬性的容器。圖形對象擴充用來具體渲染螢幕上的某一對象,實體對象擴充用來進行剛體之間的碰撞互動。引擎與系統之間的進一步的關系可以在附錄B的圖示“引擎與系統關系圖”中檢視。

另一點需要指出的是,通用場景和通用對象需要将各自的擴充通過狀态管理器進行注冊,以此來響應其他擴充(譬如系統)造成的由變化産生的提示。譬如,某個圖形擴充在注冊後,可以捕獲由實體擴充造成的位置和朝向的變化所産生的提示。

更多的關于系統元件的資訊可以在章節5.2“系統元件”找到。

繼續閱讀