元宇宙時(shí)代的來臨對(duì)實(shí)時(shí)3D引擎提出了諸多要求,Unity作為游戲行業(yè)應(yīng)用最廣泛的3D實(shí)時(shí)內(nèi)容創(chuàng)作引擎,為應(yīng)對(duì)這些新挑戰(zhàn),提出了Unity云原生分布式運(yùn)行時(shí)的解決方案。LiveVideoStack 2023上海站邀請(qǐng)到Unity中國(guó)的解決方案工程師舒潤(rùn)萱,和大家分享該方案的實(shí)踐案例、面臨的問題、解決方式,并介紹了Unity目前對(duì)其他方案的構(gòu)想。
文/舒潤(rùn)萱
大家好,我叫舒潤(rùn)萱,現(xiàn)在在Unity中國(guó)擔(dān)任解決方案工程師,主要負(fù)責(zé)開發(fā)的項(xiàng)目是Unity云原生分布式運(yùn)行時(shí)。
首先介紹一下Unity。Unity是游戲行業(yè)應(yīng)用最廣泛的3D實(shí)時(shí)內(nèi)容創(chuàng)作引擎。截止2021年第4季度,70%以上移動(dòng)平臺(tái)的游戲是使用Unity開發(fā)的。

但Unity不止是一個(gè)游戲引擎,Unity的業(yè)務(wù)目前涉及到汽車行業(yè)、建筑行業(yè)、航空航天行業(yè)、能源行業(yè)等等各行各業(yè)。
Unity的業(yè)務(wù)在全球都有開展,在18個(gè)國(guó)家有54個(gè)辦公室。在中國(guó),在上海、北京、廣州都有辦公室,在臨港也開了一間新的辦公室。
Unity覆蓋的平臺(tái)是最廣泛的,它支持超過20個(gè)主流平臺(tái),率先支持了Apple新發(fā)布的vision OS。

我今天的分享將從這六個(gè)方面進(jìn)行。
-01-
元宇宙時(shí)代的挑戰(zhàn)
首先,實(shí)時(shí)3D引擎在元宇宙時(shí)代會(huì)遇到什么挑戰(zhàn)?

Unity認(rèn)為元宇宙會(huì)是下一代的互聯(lián)網(wǎng),它將是實(shí)時(shí)的3D的、交互的、社交性的,并且是持久的。

元宇宙會(huì)是一個(gè)規(guī)模龐大的虛擬世界,這個(gè)虛擬世界里有很多參與者,它將會(huì)是一個(gè)大量用戶實(shí)時(shí)交互的場(chǎng)景。同時(shí),元宇宙它必須是一個(gè)持續(xù)穩(wěn)定的虛擬世界。
參與者在這個(gè)虛擬世界里對(duì)它進(jìn)行的改變將會(huì)隨著時(shí)間保留下來,并且它是穩(wěn)定的狀態(tài)。這就對(duì)實(shí)時(shí)3D交互造成了很多挑戰(zhàn)。

首先,因?yàn)檫@個(gè)虛擬世界將會(huì)是一個(gè)大規(guī)模的高清世界,里面將會(huì)有數(shù)量龐大的動(dòng)態(tài)元素、靜態(tài)元素,所以它對(duì)實(shí)時(shí)3D引擎提出了渲染的挑戰(zhàn)。
其次,它伴隨著大量的網(wǎng)絡(luò)傳輸,對(duì)引擎的可擴(kuò)展性和可伸縮性提出了很高要求,所以對(duì)運(yùn)行平臺(tái)來說也是一個(gè)不小的挑戰(zhàn)。
最后,因?yàn)檫@個(gè)虛擬世界會(huì)有超大規(guī)模的物理仿真,用戶將會(huì)在其中進(jìn)行大量的實(shí)時(shí)交互,所以這對(duì)運(yùn)算資源也是一個(gè)巨大的挑戰(zhàn)。
-02-
Unity分布式運(yùn)行時(shí)
為了應(yīng)對(duì)這些挑戰(zhàn),Unity提出了分布式運(yùn)行時(shí)的解決方案。

這個(gè)方案由兩部分組成,第一個(gè)部分是Unity云原生的分布式渲染,第二個(gè)部分是Unity云原生分布式計(jì)算。

首先介紹分布式渲染。要做一個(gè)多人聯(lián)網(wǎng)的體驗(yàn),可以從最簡(jiǎn)單的Server-Client架構(gòu)入手。如圖所示,在這個(gè)架構(gòu)中有一個(gè)中心化的Server,它可以服務(wù)于多個(gè)Client。這張圖上畫了兩個(gè)Client,這是最簡(jiǎn)單的架構(gòu)。

為了對(duì)應(yīng)大規(guī)模的渲染壓力,Unity把用戶的Client端拆成了Merger和Renderer。在這里面,一個(gè)Merger對(duì)應(yīng)多個(gè)Renderer,這些Renderer將會(huì)負(fù)責(zé)虛擬世界的渲染。我們把一幀的渲染任務(wù)拆分成很多個(gè)子任務(wù),分別交給這些Render進(jìn)行。Merger負(fù)責(zé)把Renderer渲染的畫面組合成最終畫面,之后通過WebRTC推流的方案推給用戶的客戶端。要注意,這里除了用戶的客戶端以外,所有的環(huán)境都是運(yùn)行在云上的。

圖示為一個(gè)最簡(jiǎn)單的屏幕空間拆分,即把一個(gè)完整的畫面在屏幕空間上拆成幾個(gè)部分。除此之外還有其他拆分方式,例如通過時(shí)間拆分:假設(shè)有4個(gè)Renderer,第一個(gè)Renderer渲染第一幀,第二個(gè)Renderer渲染第二幀,第三個(gè)Renderer渲染第三幀……以此類推,再把它們組合成一個(gè)完整的序列。
Unity現(xiàn)在也在實(shí)驗(yàn)一個(gè)新的拆分方式:在Merger上渲染一個(gè)比較高清的近景,再把遠(yuǎn)景拆分到幾個(gè)Renderer上。Renderer的幾個(gè)畫面可以組成一個(gè)天空盒推到Merger上,這樣Merger就可以同時(shí)擁有細(xì)節(jié)比較豐富的近景和遠(yuǎn)景。Unity的架構(gòu)允許開發(fā)者根據(jù)自己的業(yè)務(wù)需求定義自己畫面的拆分方式。

回到這個(gè)架構(gòu)圖,想象在一個(gè)大型的虛擬世界里,有成千上萬用戶連入運(yùn)行時(shí),通過分布式渲染,把原本一個(gè)進(jìn)程服務(wù)對(duì)應(yīng)一個(gè)用戶的場(chǎng)景,拆分成好幾個(gè)進(jìn)程服務(wù)于一個(gè)用戶,因此這個(gè)場(chǎng)景就會(huì)對(duì)Server提出巨大的挑戰(zhàn)。為了應(yīng)對(duì)這個(gè)挑戰(zhàn),Unity提出了分布式計(jì)算。

這種分布式計(jì)算把Server拆分成了Server和Remote,用 Remote分擔(dān)Server的一些運(yùn)算壓力。

簡(jiǎn)要概述一下Unity是怎樣把計(jì)算任務(wù)分配給Remote的。Unity游戲的業(yè)務(wù)流程可以簡(jiǎn)單拆分成數(shù)據(jù)和數(shù)據(jù)處理器,數(shù)據(jù)叫做Component,數(shù)據(jù)處理器實(shí)際上就是業(yè)務(wù)邏輯,代碼叫System。每個(gè)System有自己感興趣的數(shù)據(jù),它會(huì)讀取這個(gè)感興趣的數(shù)據(jù),通過邏輯進(jìn)行數(shù)據(jù)更新。


Unity把這個(gè)System跑在遠(yuǎn)程的機(jī)器上,而不是本地,之后把它感興趣的數(shù)據(jù)通過網(wǎng)絡(luò)發(fā)送給遠(yuǎn)程的機(jī)器,它就可以像本地處理一樣處理這個(gè)數(shù)據(jù),再把更新的數(shù)據(jù)通過網(wǎng)絡(luò)發(fā)回給Server。
視頻編解碼與實(shí)時(shí)渲染
下面進(jìn)入今天的正題,視頻編解碼與實(shí)時(shí)渲染。

為什么要在分布式渲染中引入視頻編解碼?
可以看這個(gè)簡(jiǎn)化的分布式渲染架構(gòu)圖。Merger和Renderer之間的通信是通過網(wǎng)絡(luò)TCP傳輸?shù)?,即圖像是通過TCP傳輸?shù)摹?/p>

這個(gè)做法有兩個(gè)問題:
一是會(huì)遇到網(wǎng)絡(luò)帶寬瓶頸,在實(shí)時(shí)3D交互的場(chǎng)景下,一般至少要Target到1080p60幀。而如果傳的是8 bits RGBA的圖像格式,需要的帶寬是4Gbps,遠(yuǎn)遠(yuǎn)超出了常見的千兆帶寬1 Gbps,很多機(jī)房沒有辦法接受。即使對(duì)Raw Data進(jìn)行YUV420的簡(jiǎn)單壓縮,它也要占用約1.6 Gbps的帶寬,超過了千兆。
其次是性能問題。因?yàn)閁nity的畫面是從GPU渲染出來的,為了通過網(wǎng)絡(luò)傳輸這個(gè)畫面,要先把GPU顯存上的圖像數(shù)據(jù)先回傳到CPU端。這個(gè)回讀就會(huì)導(dǎo)致需要暫停渲染的流水線,因?yàn)槟壳癠nity優(yōu)化較好的實(shí)時(shí)3D引擎的渲染都是流水線形式的,即CPU產(chǎn)生場(chǎng)景數(shù)據(jù),再把這個(gè)場(chǎng)景數(shù)據(jù)交給GPU渲染,同時(shí)CPU可以進(jìn)行下一幀的計(jì)算。但是如果數(shù)據(jù)是反過來從GPU回到CPU,意味著需要把流水線暫停,包括GPU、CPU上的所有任務(wù)都要等待回讀完成之后才能繼續(xù)工作。
這里截取了一張Unity的回讀指令,它消耗的時(shí)間是7.22毫秒,在實(shí)時(shí)交互的場(chǎng)景下,希望做到的是60幀的體驗(yàn),即16毫秒,因此用7.22毫秒的時(shí)間把這張圖片讀回來是無法接受的。

為了解決這兩個(gè)問題,Unity為分布式渲染的方案開發(fā)了Unity Native Render Plugin。Unity的目標(biāo)運(yùn)行環(huán)境是云端,Linux和Vulkan的圖形API。云端一般配備的是NVIDIA GPU,希望采用NVIDIA GPU進(jìn)行硬件的視頻編解碼。在這個(gè)方案下面,Unity還是負(fù)責(zé)虛擬世界的渲染和邏輯。FFmpeg負(fù)責(zé)視頻的編解碼以及把Unity的圖像在顯存內(nèi)部拷貝給NVIDIA的Video Codec SDK,Unity的插件就是連接這兩個(gè)部分以實(shí)現(xiàn)目標(biāo)。

這幾個(gè)流程圖展示了Unity一定要硬件編解碼的原因。橙色的框?yàn)轱@存上的數(shù)據(jù),藍(lán)色的框?yàn)镃PU端內(nèi)存上的數(shù)據(jù)。
最左邊的流程圖是最簡(jiǎn)單的。如果要傳輸一個(gè)Raw Data的方案,在GPU上把圖像渲染完成之后,要通過回讀把它讀到CPU端,這是非常耗時(shí)的操作。在讀完這個(gè)圖像之后,把它通過TCP發(fā)送給Merger,占用帶寬非常大,這是不可接受的。
如果引入的是一個(gè)軟件編解碼的場(chǎng)景,需要圖像的數(shù)據(jù)在CPU端存在。所以回讀還是無法避免,操作仍然十分耗時(shí)。在軟件編解碼時(shí)進(jìn)行的編碼和解碼操作,同樣非常耗時(shí)。雖然這對(duì)視頻來說沒問題,但是對(duì)于60幀的實(shí)時(shí)場(chǎng)景是不太能接受的。但是這個(gè)方案的帶寬其實(shí)優(yōu)化了很多,因?yàn)橐曨l碼率至少比4 Gbps、1.5 Gbps好很多。這是軟件面板的情況,它最多只是優(yōu)化了帶寬。
最后是硬件編解碼。硬件編解碼是在渲染完成之后,通過顯存間的拷貝,把渲染的結(jié)果拷貝到Media的CUDA端,然后做硬件編碼和硬件解碼。雖然是一次拷貝,但是因?yàn)槭窃谝粡堬@卡的顯存上,所以拷貝過程非???。硬件編碼完成之后,視頻幀數(shù)據(jù)會(huì)自動(dòng)回到CPU端。雖然其本質(zhì)上也是一次回讀,但是它和流水線無關(guān),相當(dāng)于是在另一條線上完成的,所以這個(gè)回讀消耗的只有從PCIE到CPU到內(nèi)存的帶寬的速度,回讀的不是一個(gè)完整的圖像,而是壓縮好的視頻幀,它的數(shù)據(jù)量也大大減小,所以回讀非??臁V笸ㄟ^TCP發(fā)送視頻幀,帶寬非常小,在Merger端解碼上傳的Buffer也比原來小,解碼也很快。最后把解碼完成后的Image,Copy 到Vulkan Texture,這也是在同一塊GPU內(nèi)顯存里的操作。
可以看到,硬件編解碼的流程優(yōu)化了很多,每一步都能得到很高的提升。

介紹一下Unity是怎樣配置FFmpeg的CodecContext的。Unity使用了兩個(gè)CodecContext,Video Context是真正用來做視頻編解碼的Context,它是一個(gè)TYPE CUDA的Hwdevice。第二個(gè)CodecContext用的是Vulkan的Context,這個(gè)Context的作用是
和Unity的Vulkan Context進(jìn)行交互,所以在初始化時(shí)不會(huì)使用到它默認(rèn)的API,而是從Unity的Vulkan Instance里面取出了所有必要的信息,把它交給FFmpeg的Vulkan Context,最后通過這種方式,就可以讓FFmpeg的環(huán)境和Unity的環(huán)境存在于同一個(gè)Vulkan的運(yùn)行環(huán)境中。

Video Context做初始化時(shí),沒有用到它默認(rèn)的Hwdevice Context create的API,用到的是create derived的API。這個(gè)API需要傳入另一個(gè)Hwdevice Context,它能保證做初始化時(shí)和另一個(gè)Hwdevice Context使用的是同一個(gè)物理GPU,這樣才能真正地做顯存間拷貝。這里選取的邊界碼的格式是H.264,8 bit,NV 12。 為什么要選用這個(gè)格式呢?其實(shí)是受硬件限制,因?yàn)槟壳癗VIDIA硬件不支持10 bit的H.264的解碼。H.265則耗時(shí)較大,不太滿足60幀的需求,所以不得不選取這個(gè)格式。同時(shí)Unity是Zero Latency的配置,GOP的Size是0,保證視頻每一幀都是Intra Frame。這有兩個(gè)好處,一是它的延遲非常低。其次,因?yàn)榉植际戒秩驹诠芾淼臅r(shí)候可能會(huì)隨機(jī)丟棄幀,如果有預(yù)測(cè)幀則不好丟棄,在全是I幀的情況下可以隨機(jī)丟棄。
-05-
畫質(zhì)與性能優(yōu)化
接下介紹Unity在畫質(zhì)方面和性能方面對(duì)插件進(jìn)行的一些優(yōu)化。

首先是Tone-Mapping。Unity之所以這樣做是因?yàn)橛龅搅薈olor-Banding問題,這個(gè)問題由兩個(gè)因素導(dǎo)致:
首先,傳輸?shù)膱D像不是一個(gè)普通的SDR圖像,而是HDR圖像;
其次,選取的格式只有8 bit,它導(dǎo)致了顏色精度和范圍都是受限的。
為了理解為什么Unity傳輸?shù)氖荋DR圖像,我給大家簡(jiǎn)單介紹一下渲染里面的一幀是怎么生成的。圖示為簡(jiǎn)化的渲染過程,真正的渲染過程要比這復(fù)雜很多。
渲染過程可以簡(jiǎn)單分為三部分:
第一個(gè)部分叫Pre-Pass,它的作用是生成后續(xù)渲染階段所需要的Buffer,在這一階段里會(huì)預(yù)先生成Depth,即預(yù)先生成深度緩沖。深度緩沖預(yù)先生成最主要的好處就是可以減少Over-Draw,很多被遮擋的物體直接被剔除掉,后續(xù)步驟中就可以不用畫。
第二步是渲染中最主要的一個(gè)步驟,我把它叫做Lighting Pass,主要的作用是計(jì)算光照,生成物體的最終顏色。這種Pass的實(shí)現(xiàn)方式有很多種,例如前向渲染、延遲渲染等。無論是使用哪種實(shí)現(xiàn)方式,它的最終目的都是生成一張Color Buffer,即顏色數(shù)據(jù)。
最后一個(gè)步驟一般成為后處理Post-Processing。這個(gè)步驟的主要作用是對(duì)輸出的Color Buffer進(jìn)行圖像處理。經(jīng)過Post-Processing產(chǎn)生的圖像,顏色比單純的Color Buffer自然很多。Post-Processing里面的效果大部分都是屏幕空間效果,例如要做一個(gè)輝光的效果,畫面中有一盞燈非常亮,在它的邊緣會(huì)有一些柔和的光效溢出,這個(gè)效果基本上就是通過Post-Processing實(shí)現(xiàn)的。
屏幕空間效果會(huì)有什么問題?在分布式渲染中,渲染任務(wù)是被拆分開的,所以真正做渲染的Render的機(jī)器,實(shí)際上是沒有全屏信息的,它沒有辦法做后處理。
對(duì)此Unity的解決方案是將后處理交由Merger進(jìn)行。Renderer渲染到Color Buffer生成之后,就把Color Buffer傳給Merger,Merger先把這個(gè)Color Buffer合起來,再總體做一次后處理。
那么,為什么這一過程中傳的Colour Buffer是HDR的?因?yàn)樵阡秩局?,光照模型一般都是物理真?shí)的,所以為了之后做后處理,Colour Buffer本身是HDR的。
如圖所示,這是把Black Point和White Point分成設(shè)置成0和1的效果,但是實(shí)際上這張圖最高的值可以到90以上。如果是室外的場(chǎng)景,最高值可以到一萬多,所以這張圖的動(dòng)態(tài)范圍是非常高的。

同時(shí),因?yàn)閭鞯氖莿?dòng)態(tài)很高的HDR圖像,加上使用的是8bit的編碼,所以必須找到一個(gè)方法把HDR的Corlor Buffer映射為SDR的Buffer。對(duì)于這一映射也有一些要求,其一,它需要可逆;其二,它需要保留更大的表示范圍,并且盡量減少精度損失。
Unity在這方面采用的是AMD提出的Fast Reversible Tone-Mapper,它有許多好處: 首先它是Unity SRP原生支持的,是Unity的可編程的渲染管線;其次它是可逆的,如圖是它的公式;再次,它保留的數(shù)字范圍更大,精度損失更小。如圖上這一圖像,y=x可以理解為原始顏色,經(jīng)過Tone-Mapper處理后,它的取值范圍永遠(yuǎn)在0~1之間,同時(shí)當(dāng)它的值越小時(shí),斜率越大,代表它能表示的數(shù)字越多,可保留的精度越高。有時(shí)在渲染中會(huì)遇到顏色值非常高的情況,但這種情況少之又少,更多的還是保留在0~1的范圍區(qū)間內(nèi)。因此Tone-Mapper能夠幫助我們?cè)?~1這個(gè)區(qū)間范圍內(nèi)保留更多的精度;最后它非??欤贏MD的GCN架構(gòu)的顯卡下,MAX RGB會(huì)被編譯為一個(gè)指令,整個(gè)運(yùn)算中只有3個(gè)指令,Max、加法和除法,所以它是非常快的。

如圖,可以對(duì)比使用Tone-Mapping前后的效果。最左邊是未經(jīng)任何處理的原始圖像;中間是不使用任何Tone-Mapping,經(jīng)過8 bit的編解碼、網(wǎng)絡(luò)傳輸,再解碼回來的情況;最右邊是應(yīng)用了Fast Reversible Tone-Mapping的情況??梢钥吹嚼锩娴谋尘坝泻芏嗉?xì)節(jié)紋理,代表其中高頻的信息比較多。在高頻信息較多的場(chǎng)景下,應(yīng)用了Fast Reversible Tone-Mapping之后的效果和原始圖像的效果對(duì)比,已經(jīng)看不出什么區(qū)別了。
但是如果場(chǎng)景里低頻的信息較多,例如漸變較多,即使運(yùn)用了Tone-Mapping,也沒有辦法完全解決這個(gè)問題??梢钥吹?,原始圖像上的漸變非常柔和,在無Tone-Mapping的情況下,色帶肉眼可見。但是即使引入了Fast Reversible Tone-Mapping,也只能減緩色帶問題,比起原始圖像而言還是差了很多,目前沒有更好的辦法解決這一問題。

Unity對(duì)插件進(jìn)行性能優(yōu)化的另一個(gè)方式是Vulkan同步。因?yàn)樯婕癎PU內(nèi)部的顯存拷貝,而且它是Vulkan拷貝到CUDA的操作,所以它是一個(gè)GPU-GPU的異步操作。異步操作要求開發(fā)人員對(duì)于操作做好同步,即在編碼時(shí),要保證Unity渲染完成之后才進(jìn)行編碼,解碼時(shí)要保證解碼完成之后,才能把這張圖像Copy給Unity,讓Unity把它顯示在屏幕上。

Unity Vulkan Native Plugin Interface提供了一種同步方式。這里主要關(guān)注框出的兩個(gè)Flag:
上面的Flush Command Buffer,指在Unity調(diào)用自定義渲染事件時(shí),Unity會(huì)先把它已經(jīng)錄制好的渲染指令提交到GPU上,這時(shí)GPU就可以開始執(zhí)行這些指令了。
下面的Sync Worker Threads,指在Unity調(diào)用自己定義的Plugin Render Event時(shí),會(huì)等待GPU上所有的工作全部完成之后才會(huì)調(diào)用。

這兩個(gè)方式組合起來確實(shí)能滿足需求,確實(shí)可以做到同步,但是這種方式也打斷了渲染流水線。使用這種同步方式,在調(diào)用Plugin Event之前,所有程序要全部停下,等待GPU完成操作。因此這個(gè)方式雖然能實(shí)現(xiàn)同步,但非常耗時(shí)。
如圖展示的就是在這個(gè)同步方式下的情況。在簡(jiǎn)單的場(chǎng)景下,它耗時(shí)5.6毫秒。所以這種方式雖然能同步,但是性能非常差。

Unity只提供了上述的同步方式,因此我們只能轉(zhuǎn)向Vulkan自帶的同步原語。Timeline Semaphore是Vulkan在1.2版本的SDK里提出的新型的Semaphore,它非常靈活,而且是FFmpeg原生支持的。這里是FFmpeg的Vulkan Context的Frame,它通過Timeline Semaphore同步。在FFmpeg里,它主要被用于GPU-CPU同步,但它也可以用于GPU-GPU同步。
Unity提供的同步方式只有上述兩個(gè)Flag,它無法直接使用底層的同步原語,但是它允許我們Hook Vulkan的任何一個(gè)API,即在 Hook之后,Unity在調(diào)用Vulkan的API時(shí),它其實(shí)調(diào)用的是我們自己定義的Hook的版本,因此我們使用了Vulkan Hook介入U(xiǎn)nity的渲染,在Unity把一幀的渲染提交給GPU之前,通過Hook提交的API,把FFmpeg的Semaphore 塞到提交里面,就可以保證在渲染完成之后會(huì)通知Timeline Semaphore,F(xiàn)Fmpeg會(huì)等Semaphore被通知之后再執(zhí)行。通過這種方式,這個(gè)同步也能達(dá)成目的,而且不會(huì)打斷渲染流水線。

如圖所示,上面為5.6毫秒耗時(shí)的情況,下面則完全把這一耗時(shí)消除了。因?yàn)樵谶@個(gè)情況下,不需要在調(diào)用Render Event之前就提交渲染指令,而是在GPU上通過Timeline Semaphore和FFmpeg的Command進(jìn)行同步,因此把這一部分完全省去了。在這個(gè)簡(jiǎn)單場(chǎng)景中大概有5.6毫秒左右的提升,經(jīng)過測(cè)試,在復(fù)雜場(chǎng)景中則會(huì)有10~20毫秒不等的提升。

另一個(gè)性能優(yōu)化方案是多重緩沖,這是渲染中非常常用的技巧。在渲染中,常常會(huì)用多重緩沖來減少畫面的卡頓、撕裂等情況,三重緩沖還能夠提高幀率的穩(wěn)定性、提高渲染性能。
多重緩沖的引入會(huì)把渲染變成Single Producer, Single Consumer的流水線模型,即渲染流水線。正是因?yàn)橛卸嘀鼐彌_,才能形成流水線,讓GPU往一個(gè)緩沖中寫入時(shí),CPU可以開始準(zhǔn)備下一個(gè)緩沖,GPU可以同時(shí)往另一緩沖區(qū)寫入下一幀數(shù)據(jù)。
在編解碼的插件中,Unity也引入了多重緩沖來提高性能,使用硬件編解碼代替圖像上屏操作。渲染的圖像沒有顯示在屏幕上,而是通過網(wǎng)絡(luò)發(fā)走,這是通過多重緩沖的方式實(shí)現(xiàn)的,和渲染有同樣的效果。在引入多重緩沖后,Unity的渲染和編解碼器會(huì)分別作為Producer和Consumer進(jìn)行渲染和編解碼的流水線。
-06-
總結(jié)與未來展望

在分布式渲染的解決方案中會(huì)遇到網(wǎng)絡(luò)帶寬和性能問題。
首先,通過引入視頻編解碼可以解決了網(wǎng)絡(luò)帶寬的問題,采取硬件編解碼避免GPU-CPU的回讀,避免打斷渲染的流水線。為了實(shí)現(xiàn)這個(gè)目標(biāo),Unity開發(fā)了Unity Native Rendering Plugin來對(duì)接Unity和FFmpeg底層的Vulkan和NVIDIA Codec 的SDK。
因?yàn)檫x取的編解碼格式,方案中還遇到了色帶問題,因此在方案中我們引入Tone-Mapping優(yōu)化畫質(zhì),通過FFmpeg自帶的Timeline Semaphore,把Unity的渲染指令和FFmpeg的拷貝指令和編解碼指令進(jìn)行同步,保證編解碼結(jié)果正確。
最后,Unity通過引入多重緩沖提升性能,減少幀率不穩(wěn)的情況。
目前Unity還在探索一些其他的方案。
首先,Unity希望嘗試Vulkan自己推出的Vulkan Vider Extensions。它在2023年1月左右才真正進(jìn)入Vulkan的SDK,成為一個(gè)正式的功能。這個(gè)Extension非常新,所以到目前為止Unity還沒有機(jī)會(huì)進(jìn)行嘗試,但一直在關(guān)注。如果使用這一Extension,就可以完全避免前文所述得到顯存間拷貝、同步等問題。因?yàn)檫@一應(yīng)用程序沒有引入別的GPU端的運(yùn)行環(huán)境,完全在Vulkan內(nèi)部運(yùn)行,因此我們不需要拷貝,直接使用Unity的結(jié)果即可。
其次,Unity在對(duì)接其他GPU的硬件廠商,嘗試其他硬件編碼。Unity目前正在和一些國(guó)產(chǎn)的GPU廠商對(duì)接,他們表示他們的硬件編解碼能力會(huì)有所提升,支持的格式不再限制于8 bits了。
最后,Unity還希望嘗試一些要使用GPUDirect RDMA、CXL共享內(nèi)存等特殊硬件的方案。GPUDirect RDMA允許直接把GPU顯存里的東西直接通過網(wǎng)絡(luò)發(fā)走,能夠減少回讀。CXL共享內(nèi)存,顧名思義是個(gè)共享內(nèi)存,相當(dāng)于很多臺(tái)機(jī)器共享一個(gè)遠(yuǎn)程的內(nèi)存池,因此它的帶寬和延遲都是內(nèi)存級(jí)別。這一方案至少允許我們?cè)诜植际戒秩镜沫h(huán)境下不進(jìn)行視頻編解碼,可以使用Raw Data的方式,把Raw Data存在遠(yuǎn)程內(nèi)存中。
編輯:黃飛
電子發(fā)燒友App
















評(píng)論