跨平臺渲染引擎之路:框架與核心模組

遊資網發表於2019-10-25
前言

說好的保持一月一更再次食言了,再也不敢隨便立Flag了,這段時間只能在業餘時間進行 GPUImage-X 的設計和第一版的落地,除了時間因素之外,還有就是想要把第一版的整體框架和基礎能力都能以更編碼規範、考慮更充分地落地下來,不僅僅是一個小玩具更是一個可以被使用的渲染庫,因此在過程中就會出現返工重複調整等情況。

目前 GPUImage-X 的整體框架中各個層級的主要模組已基本落地,整體設計上在個人來看符合我們上一篇中的部分目標:以遊戲引擎的效果和特性,支援圖片/視訊等場景下的跨平臺3D渲染庫。在功能上,該庫落地了圖層、濾鏡鏈、變換、包括轉場、遮罩、疊加在內的混合模式等基本功能,同時不管是更多樣的圖層元素還是粒子、光照等更廣泛的效果都也已具備了對應擴充套件性。在專案裡配套了一個可調節上述能力引數效果的Demo,不過目前只實現了Mac上的版本,其他平臺的在下一步規劃裡會優先安排上,確保跨平臺可用這一點。

那麼接下來這篇文章主要會介紹這個過程中自己的一些想法還有踩過的一些坑,有存在問題的地方期待看的大佬們交流糾正~

站在bgfx的肩膀上

首先簡單說一下為什麼一開始先基於bgfx來搭建這個專案,一方面是在於時間成本上,自己從頭開始擼一個渲染API封裝的框架的話是很耗時的,會導致看到效果的週期被不斷拉長,難以快速得到反饋和驗證,另一方面就是對於各個平臺的渲染API目前還並未完全掌握,即使去寫也肯定是需要借鑑甚至照搬bgfx的設計,很難有所超越。因此在目前的階段先選擇基於bgfx之上來進行開發和學習,重點去理解bgfx的設計理念和實現思路,記錄下踩過的坑和自己感覺使用不便、可以完善優化的一些地方,作為後續自己開發渲染框架的基礎,這部分後續在開始落地時也會再有文章來進行記錄和分享。

要依賴bgfx來進行二次開發還是有一定難度的,光配置依賴關係就折騰了一會。目前是借鑑於 bgfx.cmake 的思路,將其配置好的CMakeList 以及 .cmake 檔案遷移到了 GPUImage-X 的專案中並且做了一些定製化修改(當然會不斷同步更新的),比如 bgfx.cmake 中是自己搞了一個App類來作為Demo執行基類, GPUImage-X 則直接使用了 bgfx 的entry以及自帶的工具,避免重複的編碼工作。具體的依賴配置可以在執行專案還有根據各個層級下的 cmake 配置內容檢視。

解決完依賴問題後,在開發過程中也是踩了bgfx的各種坑,當然其中很大部分原因是因為自己對bgfx的使用方式、內部邏輯原理等不夠熟悉導致的,比如view的265最大數限制(這個View的機制導致了目前渲染還需要有一個全域性的計數器來控制每次submit的index),同一個uniform在一輪渲染中不得重複設定,多紋理的設定等等。

設計思路

接下來介紹一下目前 GPUImage-X 專案中的整體設計思路以及各個核心模組。

整體設計

首先貼一下一個簡要的層級和模組設計圖:

跨平臺渲染引擎之路:框架與核心模組

首先訪問層就沒什麼好說的,展示層指的是比如Android上的GLSurfaceView之類的承載我們渲染結果的檢視,目前bgfx有一套建立跨平臺渲染環境和Surface的程式碼,但是看網上還是挺多人在詢問移動端如何使用和接入的,因此對這套程式碼在移動端上的應用情況還是存在顧慮(可用但是希望能夠易用),因此如果下一步在做Android/iOS雙端的接入時發現的確存在問題,那麼就會先封裝對應平臺原生的展示控制元件,然後等功能驗證完畢後進一步自己實現跨平臺的渲染環境和Surface建立(感覺又是一個可以學習分享的大坑-_-)。

使用層是使用者可直接感知的一些模組,目前主要的就是圖層和效果這兩個模組。處理層是根據使用者設定的圖層以及其上的疊加效果等內容,內部自行封裝的各個業務邏輯模組,因為現在專案整體的架構和程式碼量都還是很輕量的,因此將和效果處理相關的內容都統一放到這一層,後續再根據情況考慮是否進一步拆分。

除了架構設計,在專案結構上也劃分了以下幾個目錄結構:

  • thirdparty:存放第三方依賴專案以及相關配置,包括bgfx、glfw等
  • examples:存放各種Demo
  • shaders:存放著色器原始碼
  • source:原始碼存放路徑
  • include:對外標頭檔案存放路徑
  • tools:存放一些如快速編譯著色器等工具

在外部接入使用上,還有封裝了一個 XImage 來作為對外統一新增圖層、控制渲染流程的門面,後續在進一步降低使用成本與風險裡面,應該會將其作用範圍進一步擴大。

幀 XFrameBuffer

在 GPUImage-X 專案裡有一個 XFrameBuffer 類,該類表示一幀的渲染結果,被設計用於封裝所有的輸入和輸出,比如圖片/視訊紋理除了載入成Texture之外還會被進一步載入到一個FBO中封裝成 XFrameBuffer ,而我們每一個效果的渲染結果也都必將渲染到一個自身的 XFrameBuffer 裡,這樣做的好處是統一了各個模組的入口和出口,同時在我們做快取池的時候也會更簡單一些。

目前專案裡有一個 XFrameBufferPool 的快取池,專案中所有 XFrameBuffer 的獲取和回收都需要經過該快取池,目前的邏輯比較簡單,只具備簡單的快取和回收能力,後續會進一步完善。

圖層 XLayer

首先回到我們之前說的該專案主要面向的場景是圖片/視訊下的,因此需要針對該型別場景有一個總結性的提煉,這樣更能有所針對性地進行設計。

在視訊/圖片型別場景下,類比PS/AE之類的設計工具會發現,裡面如圖片、文字等元素都是以圖層的概念存在,所有圖層有其公共和特有屬性,在圖層之上我們可以再去額外疊加一系列的效果,圖層和圖層之間也會產生相互影響。借鑑於此,我們抽象出圖層 XLayer 來作為外部操作控制的物件,該基類負責如渲染區域、渲染層級以及效果疊加等通用屬性和業務邏輯,而如圖片/視訊幀資料則可以封裝成 XFrameLayer 這樣的子類,持有其特有屬性如圖片路徑、畫素資料等,還有其特有邏輯如載入圖片到 XFrameBuffer 中等,再進一步預想比如後面還有其他型別圖層,文字、向量等也可以通過這樣策略模式的方式來進行實現。

在 XLayer 裡,每一個圖層都首先必須有一個自身的輸出,如 XFrameLayer 就是圖片/視訊的載入結果,在這些源輸出的基礎上才會去疊加各類效果,進行各項處理。而圖層和圖層之間是可以互相影響的,這裡借鑑於AE,圖層可以設定自身的圖層內遮罩 Matte ,Matte 也是一個個的圖層,只是這些圖層只能用作對應圖層的遮罩,不參與全域性的圖層混合,並且 Matte 目前出於簡化邏輯考慮,暫時不可再 Matte 圖層上疊加 Matte 也不可新增額外的效果,只能進行基本的繪製和動畫(下一階段實現的內容之一)。除了圖層內遮罩 Matte 之外,還可以對圖層設定圖層間遮罩 Mask ,Mask 除了與對應圖層進行摳圖混合之外,還可以選擇是否參與最終的全域性混合。

效果 XEffect

在專案裡我們將各類效果都封裝成是基類 XEffect 的各個子類,XEffect 就代表了可疊加在各類圖層上的各種效果,如濾鏡、變換、粒子、混合等等,XEffect 除了提供公共介面外目前沒有太多實現內容,主要的邏輯都由不同的效果子類來實現,如 XFilter 代表了所有的濾鏡效果,XMixer 繼承自 XFilter 但是主要負責混合相關的多輸入效果,後續如粒子效果則會相比這兩者更復雜一些,因為它需要考慮將自身的粒子效果與上一次的渲染結果進行疊加之類等額外邏輯。

而各個效果都會有專用或者共用的處理器,目前暫時都為公用的,一個是 XEffectProcessor ,負責處理濾鏡等單輸入著色器,一個是 XTwoInputEffectProcessor ,則是負責處理混合等雙輸入著色器,當然這一層對於使用者來說是不可感知的。

輸入/輸出 XInput/XOutput

輸入輸出模組對於使用層來說也是不可感知的模組,相比效果處理器還要更底下一層,該模組是所有鏈式結構的基礎。輸出的基類 XOuput ,繼承自該類的所有子類都需要確保自身可以產出一個儲存在XFrameBuffer中的輸出結果,而輸入的基類為 XInput ,繼承自該類的所有子類都可以接收一個外部傳入的 XFrameBuffer 作為輸入,同時自身的處理邏輯是基於這個輸入來進行的。

輸入輸出模組是借鑑與 GPUImage 中的輸入輸出模組的,輸出是整個效果鏈條的開端,它可以是一個圖片輸出也可以是一個視訊輸出(這在後面可以讓我們在視訊輸出中加入 FFmpeg 解碼相關的內容),而輸入是效果鏈條的結束,它對外部傳入的輸入幀進行效果處理,而當我們同時繼承自XInput/XOuput時,那麼如濾鏡等效果就可以既處理外部傳入的幀資料(圖片、上一次渲染結果等),也可以將自身的渲染結果儲存下來作為下一輪渲染的輸入。

在 GPUImage 的設計中,每一個 Output 可以通過 addTarget(Input) 來新增一系列的處理該 Output 輸出結果的輸入物件,但是通過研究其內部實現,會發現這些Target都是處於並行狀態的,也就是說不斷通過 addTarget 新增的輸入物件,他們都是在處理同一個輸入(即 Output 的輸出)而不是處理前一個被 add 的 Input 的結果。這個也是很容易理解的,因為畢竟這個介面宣告的是 Input 物件,而 Input 物件是沒有 addTarget 這樣的介面的。因此如果需要形成鏈式結果需要外部自己進行額外的 addTarget 等操作,但是這種方式的擴充套件性其實是更強的,一個輸出可以產生多個處理鏈條。這裡提一下是因為目前專案中也是借鑑這樣的設計思路的,避免產生理解上的歧義(至少本人在一開始研究GPUImage的時候就理解錯了的-_-)。

範例 Examples

目前專案裡已經有一個 X-Image 的Demo,該Demo主要是提供專案中已有的各項能力的展示以及引數調節,引數調節介面是藉助於 imgui 實現的,目前用下來感覺還比較順手,在Demo目錄下面也針對各個能力的引數設定、介面展示都做了封裝,可以通過這些封裝後的類的實現來參考各個效果如何設定和引數規則。

後續還會針對一些效果如粒子、動畫系統等搭建額外的Demo進行展示,避免一個Demo裡承載太多內容,失去參考意義。

記憶體管理

目前在記憶體管理上秉承的原則就是誰建立誰管理,比如 XLayer、XEffect 這些是由使用者建立和設定的,那麼就由使用者來進行管理,而 XMixer 等物件建立是內部進行,則由內部來統一管理,涉及到記憶體管理相關的都會在對應的地方新增註釋和宣告由誰負責其生命週期等內容,後續會考慮如何通過只設定一些引數,所有物件建立和回收都由內部進行,降低使用成本和風險。

下一步

目前的規劃主要分三個方面:能力的不斷擴充,框架的不斷完善以及文件的補充,現在主要還是自己給自己提需求,如果大家有不同的訴求的話,歡迎在評論或者專案issue裡提出。


能力擴充:

  • GPUImage 中以及目前流行濾鏡效果
  • 粒子系統
  • 光照
  • 文字圖層
  • 多邊形圖層
  • 更多3D效果

框架完善:

  • Android/iOS 平臺支援(aar/pod)
  • 進一步降低使用成本與風險
  • 動畫系統
  • 手勢處理

文件補充:

  • 使用手冊
  • 其他

最近發現還有很多放著積了很久灰的書要看,但是還是會盡量保證算上週末平均每天2-3小時的時間來進行編碼(這算Flag嗎-_-),並且在一個階段一個階段比如某些較複雜效果、較大的框架落地等節點上更新自己的一些經驗和踩坑記錄,至少避免出現長時間的斷更,目標是希望能在今年內把上面的規劃都落地下來。


來源:遊戲蠻牛
原文:https://mp.weixin.qq.com/s/cydxq2zBc1LTt0xfxh6oyA

相關文章