• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            麒麟子

            ~~

            導航

            <2009年4月>
            2930311234
            567891011
            12131415161718
            19202122232425
            262728293012
            3456789

            統計

            常用鏈接

            留言簿(12)

            隨筆分類

            隨筆檔案

            Friends

            WebSites

            積分與排名

            最新隨筆

            最新評論

            閱讀排行榜

            評論排行榜

            OGRE3D 渲染系統線程化

            先前研究渲染系統線程化的時候翻到了這篇文章,于是一邊看一邊寫出了漢語。 文中寫多地方翻譯得很不通順,見諒。

             

            譯者序:偶然在網上看到這篇文章,自己很想仔細研究一下。但搜尋半天不見中文版。于是自己斗膽翻譯了一下。文中不免有漏洞百出,甚至可以說有些地方不及Google翻譯得好。但這樣總的來說是出了一個中文版,而我自己在翻譯過程中也會停下來仔細思考。

            OGRE這個線程化的文章很老了。因為OGRE目前已經支持多線程渲染。 這篇文章貌似是某些人研究出來的三個線程化方案,并給出了測試結果。以向OGRE社區證明線程化方案的可行性。 對于許多想研究渲染線程化的人來說,是一篇值得參考的文章。文中提出了許多在不同情況下線程化時遇到的問題,以及需要注意的問題。值得一讀/

            介紹

            OGRE3D(a.k.a. Ogre)是目前最常用的開放源代碼 3D 引擎之一。它是一款功能完善的通用 3D 引擎,可應用于從游戲到科學模擬等多種商用產品。該引擎由來自開放源代碼社區的數百名技術人員歷經五年時間而開發成功。如欲了解 Ogre 的詳細信息,請訪問網站 www.ogre3d.org 。

            然而,盡管 Ogre 功能強大,但是它卻在技術上存在一個重要缺憾,那就是它無法在系統中充分利用多個處理器的優勢。目前,英特爾已經有多款雙核產品上市,而超線程(HT)技術更是在多年前就已應用在英特爾&reg; 奔騰&reg; 4 處理器中。將 Ogre 線程化所實現的性能增益將絲毫不遜色于添加第二枚處理器所實現的性能增益。

            本文將向您介紹將 Ogre 渲染系統線程化的三種不同處理方法,并且我們將根據下文所描述的線程化目的,選擇一種方法進行完整實施。

            線程化目標

            當我們在對OGRE線程化時,需要實現以下幾個目標:

            ·通過改動最少的OGRE源碼以使OGRE社區接受。

            ·在雙核處理器上,相對于非線程化渲染的OGRE系統來說,提升25%的FPS,以平衡應用程序的CPU和GPU使用率

            ·在用戶不知道的情況下對SDK包中的DEMO進行必要的變動,而不讓用戶在畫面上有任何察覺。

            假設

            本文提及了OGRE中的許多類,以及使用了OGRE中的許多程序片段。所以,這里假設讀者都是對OGRE的源碼非常了解或者已經對源碼進行過快速的查閱,否則將很難理解這些OGRE的特性。同樣,讀者也應該對線程的概念有所了解。這里就不再介紹更多關于線程的內容。

            限制

            這里所討論的所有實現,都沒有在超過兩個處理器的機器上實驗過。因此,這些技術對于雙核處理器或雙核心系統最適合不過。以后的文章中將會討論如何在OGRE中創建一個線程隊列,使OGRE以及使用OGRE的應用程序都能夠在雙核處理器以及多核上得到性能的提升。

            線程化OGRE渲染系統使用到的技術

            OGRE中有很多地方都可以被線程化,但線程化最能在多核上提升性能的是渲染系統。渲染系統在OGRE占據了巨大的一塊,并且從某種意義上講,它能單獨地被外部程序訪問。下面介紹三種將OGRE渲染系統線程化的例子。

            1、 在OGRE中對于渲染的調用可以被放在他自己的線程中。

            2、 一個線程化層可以被放在OgreMain和渲染系統插件之間。

            3、 渲染系統插件可以單開一個線程來調用圖形API。

            上面這三種方案都有各自的優缺點,本文將會一一討論。

            線程化并不會對每個應用程序有利

            注意,線程化只對那種花費在圖形調用API和邏輯處理上的時間很接近的應用程序有好處。若其中一個較另一個差別很大,則看不到明顯的效果。

            技術方案1:線程化OgreMain(高級線程化手段)

            在OgreMain中進行線程化是一種最高級的線程化手段,也表示能獲得最高級的潛在性能。這是因為Ogre在做一次渲染的時候,需要做很多事情,并且不僅僅是提交某些命令或數據到顯卡。比如,決定哪個攝相機是活動攝相機,遍歷場景中所有可見物體,標志所有可見物體去渲染,等等。 下面的插圖展示了一個Ogre渲染過程(為了簡化起見,一些東西被忽略)。高級線程化將導致這里所有的過程被放置在他自己的線程里。

            clip_image002

            線程化問題

            采用這種方案有一個主要的問題就是,兩個線程中將會發生代碼重疊。打個比方,主線程和渲染線程都需要訪問場景中某個場景對象的相同數據。主線程要更新它的位置和方向,然而此時渲染線程要讀這些信息或者在渲染線程渲染前主線程修改了這些內容。從而導致渲染幀的內容與實際不符,會相差一幀。特別地,當渲染在渲染一個對象時主線程卻要把它刪除,就會出問題。如下圖所示:

            clip_image004

            除了剛剛提到的線程自身的問題外,同樣也存在處理器共享失敗的問題。當一個變量被一個線程更新的時候,這個變量是處于這個線程所在的CPU緩存行中的,而另外一個線程也會訪問這個緩存行中的內容的相同數據。由于它們共享緩存行,一個處理器需要清空整個緩存行,不管其它處理器是否做了修改。這就是這個高級線程方方案的問題所在。因為主線程和渲染線程使用同樣的類的實例。 由于類體變量被相繼地放置在內存中,因此他們要共享同樣的緩存行。關于更多緩存行共享失敗的問題??梢圆榭聪嚓P文章。

            避免問題

            為了避免上面提到的線程問題,這里提供了兩個可以安全訪問和更新對象的解決方案。

            1、 使用一個更新隊列。

            2、 復制對象

            更新隊列的方案通過維持一個對象的更新隊列來防止訪問重疊。見下圖。 更新將只發生一次,即當主線程準備讓渲染線程開始渲染的時候。當然,你需要等待渲染線程完成后才能進行第二次啟用。這個方案有一個缺點,就是單處理系統上的CPU反而會承受這個更新隊列的額外負擔,而享受不到這個更新隊列的好處。另一個缺點就是,當對硬件資源(如索引,頂點緩沖等)改變時。排隊改變這些資源將會很困難,因為這些數據都非常大。

            clip_image006

            復制對象的方案從本質上講,就是為一個經常變動的對象復制副本。在這種方案下,使用OGRE的應用程序將被要求復制一個經常需要更新的對象,因為只有它知道哪些對象是需要經常更新的。應用程序也不得不按照一定的方式來寫:對象的處理是在對象被顯示后的下一幀。(這點沒有太明白,貌似意思是說,對象的處理和渲染為兩個幀,一個幀拿來渲染,一個幀拿來處理,看到下面那圖應該是這個意思)。當然,如果你的應用程序并不每幀更新對象,這也是一個問題,在這種情況下,你的對象有可能是在幾幀后才被訪問,這樣就會導致沖突。也可以對其做一些優化,如只有對象中主線程和渲染線程要共享的數據才被復制,以此來減少負擔。在這樣的情況下對象將不再是一個復制品,但是將會有一個雙緩沖用于存放你復制的這些東西。

            在下圖中,注意那個object X 將保持一致性(假設更新速度大于30FPS)。但是object Y將不會,因為在前一幀進行了縮放,但是這個數據并沒有被體現在復制對象中。

            clip_image008

            圖里有許多關于同步的東西被我刪掉了,但是上面著實能夠反應這個技術的實現形式。

            上面說到的兩個技術中,并沒有任何一個技術被OGRE社區接受,它們都需要大量修改OGRE代碼,因此并未繼續。一個需要復制對象數據來控制數據修改的例子便是Frustum::updataView函數和_update函數,對于實現這個函數的所有類,都需要在渲染中被調用,以及OGRE的其它地方(渲染以外的地方)。

            在哪里進行線程化

            在OgreMain中進行線程化的一個理想的地方便是在Root::renderOneFrame中。這個函數調用了主渲染_Root::_updateAllRenderTargets,這個函數可以輕易地被封裝一次。

            下面是一些實現上面想法的示例代碼。



            _beginthreadex(
            0,0,Root::renderThreadFuc,0,0,0);



            /*static*/ unsigned int Root::renderThreadFunc( )



                
            while(1)

                
            {

                    waitForStartRendering( );

                    _setStartRendering(flase);

                    _updateAllRenderTargets( );

                    _setRenderingComplete(
            true);

                }


            }


            bool Root::renderOneFrame( )

            {

                
            if(!_fireFrameStarted( ) )

                    
            return false;

                
            if(mThreadedRendering)

                


                    _waitForRenderingComplete( );

                    _setRenderingComplete(
            false);

                    _setStartRendering(
            true);

                }


                
            else

                
            {

                    _updateAllRenderTargets( );

                }


            }


            _wait 和_set函數演示了操作系統依賴的同步函數調用,例如,WINDOWS版本的_waitForRenderingComplete將會包含一個WaitForSingleObject調用。注意當多線程開啟的時候,應該在真正渲染完成之前調用_fireFrameEnded函數。

            技術方案2:創建一個線程渲染系統層(中級線程化手段)

            創建一個線程化的渲染系統層對OGRE渲染系統來說是一個很不顯眼的方案。但是由于OGRE渲染系統的復雜性,它也是最困難的方案。渲染層從本質上講是對渲染系統插件(如D3D,OPENGL)的一個封裝。想要在OGRE中創建和集成一個這樣的附加渲染系統,只需要對OGRE進行較少的改動。但是創建創建一個線程化的渲染系統層,又是另一回事了。

            clip_image010

            為了創建一個線程化的渲染系統層,你至少需要在OgreMain中實現Ogre::RenderSystem 類和Ogre:RenderWindow類。這兩個類僅僅是界于Ogre和實際的渲染器插件之間的一個層。這個層的工作并不是簡單的將對插件的函數調用進行封裝。需要決定要做哪些什么,以便調用渲染器插件,因為這個方案的目標是將渲染的工作分離到另一個線程中。在實面這些類的函數時,有幾事情需要思考。

            ·習慣性地(如,所有函數調用僅僅是在開始渲染之前調用。)可以僅是對渲染器插件的直接調用,(相當于函數轉發)。也可以在包裝的同時進行一些必要的初始化。

            ·在調用渲染器插件進行創建操作時,將需要等待前一幀渲染完成。并且需要一個包裝類來包裝那個渲染器返回(提供一個已經存在的實例)的與創建相當的類。一個需要包裝類的好例子便是渲染器插件返回的RenderWindow類。這個類的實例通過RenderWindow:createRenderWindow創建并返回。

            ·某些函數需要訪問基類。RenderSystem和RenderWindow類需要調用一些基類方法來完成一些內部事情。在OGRE中不這樣做,會導致不正確的行為。

            ·渲染用的函數需要被排隊,以便享受到多線程的好處。渲染線程將會遍歷那個隊列,并按順序調用那些函數。 RenderWindow中的swapBuffers函數是一個例外。它的包裝函數既要加入渲染隊列管理,但它又是向渲染線程發出信號,執行渲染隊列中的函數的地方。

            上面提到的幾點描述了實現這個方案需要做的事情,也還有其它一些小問題需要考慮,并且一些問題需要在實現的時候處理。

            線程化的問題

            這個實現和“方案1:高級線程化方案”一樣,存在同樣的問題。除開這種情況,最好的解決方案就是復制需要共享的數據。因為這個中間層不擁有Ogre中的類。也有其它一些從“低級線程化方案”中借鑒而來的解決辦法來完成這個實現,從而對顯卡資源提供一個線程安全訪問手段。

            在哪里線程化

            正如先前提到的,這將是線程化Ogre渲染系統的一個最不顯眼的方案。所需要對Ogre做的輕微改變僅是對Ogre現有的渲染系統增加一個線程化的渲染系統層。像這樣

            void Root::AddRenderSystem(RenderSystem *newRender)

            {

                mRenderers.push_back(newRender);

            #ifdef __THEARDRENDERSYSTEM__

                
            if(mThreadRenderSystem)

                
            {

                    RenderSystem
            * newTRender = 

                        
            new ThreadedRenderSystem(newRender);

                    
            if(newTRender != NULL)

                        mRenderers.push_back( newTRender);

                }


            #endif

            }




            技術方案3:線程化渲染插件(低級線程化手段)

            線程化一個特定的渲染插件帶來了最低級的適應性,因為它和特定的技術(如D3D,OPENGL)綁定起來。但是它也使你能夠最大限度地操縱硬件資源。

            實現這個方案最干凈利落的辦法就是創建一個介于API和渲染插件之間的層,用來處理線程化。(如下圖)

            clip_image012

            在這種方法下,它僅僅是用你的層來替換API接口。并且每個一調用從渲染插件的調用都是線程安全的,因為你的這個層處理了所有的調用。為D3D做這個,僅僅是用你自己的包裝類和方法替換了IDirect3D的接口。對于OPENGL,你將移除所有的OPENGL頭文件,并用你自己包裝好后的頭文件替換掉它們。有可能你需要用一個命名空間來包裝OPENGL函數,以免出現名詞沖突。

            這個方案依然和“中級線程化手段”一樣需要考慮些線程化相關的東西。

            ·初始化不需要多線程

            ·創建函數以及一些get函數需要在渲染完一幀前等待。

            ·渲染命令,和伴隨參數將需要加入隊列中。

            關于索引和頂點緩沖的加鎖

            對于所有的方案,都存在一個使用硬件資源時的潛在線程問題。對索引頂點緩沖區的加鎖就是一個很大的挑戰,因為某些應用程序在執行的時候總是會反復對這些緩沖區進行加鎖和解鎖。比如對于動態緩沖區,總是會每幀都改變它的內容來實現畫面的變化。一些應用程序也會重用緩沖區以使多個對象每幀都能夠共享同樣的頂點和索引緩沖區。為了解決這個問題,我們需要緩沖這些緩沖區。然而,可以通過很多方法來實現,下面有兩種緩沖辦法:

            1、 部分緩沖區加鎖,這是一個類似于雙緩沖的緩沖技術。我們將在顯卡中創建兩個緩沖區,而不是一個。在這種情況下,當應用程序在寫一個緩沖區的時候,渲染線程將使用另一個緩沖區中的數據進行渲染。這個方案可以提升渲染速度但是有一個缺點,就是會消耗更多的顯存。并且不能處理應用程序在每一幀對同一緩沖區連續寫兩次的情況。

            clip_image014

            2、 完全緩沖區加鎖,這個技術保持一個對于緩沖區的所有鎖的副本。保留一個本地緩沖區并在上面進行所有的修改。當解鎖它的時候,那些數據就會被放入一個參數隊列,這樣就可以對每次加鎖/解鎖維護一個唯一的副本。雖然這樣可以對緩沖區數據進行精確的維護,但當某些應用程序在鎖較大的數據的時候效果會大打折扣。

            clip_image016

            為什么鎖表面的時候不用緩沖

            表面(如前臺緩沖,后臺緩沖,紋理等)通常消耗大量的存儲空間。 所以,在對表面加鎖時緩沖是無用的,也是不必要的。為什么沒有用呢,因為在上面的加鎖緩沖技術中,一個顯卡會瞬間消耗掉大量的存儲空間,因為這個時候我們需要創建兩個表面。 例如:一個1024X1024 32bpp的紋理通常會消耗掉4MB的顯存,但是如果采用了上面的緩沖鎖技術,那么將會占用8MB的顯存。這個技術將會導致紋理可用的顯存資源直接減半。而對于完全緩沖區鎖技術。則會消耗了大量的系統內存并且花費較多時間來拷貝數據。因為這個技術在系統內存中維護了一個數據的副本。所以它將會一次性地為這個表面分配大量的空間。當解鎖的時候,又會將數據拷貝到參數隊列里,并且參數隊列在必要的時候,也會分配較大的空間。在渲染時,渲染線程同樣需要將數據從參數隊列中取出,并拷貝到真正的表面上。所有的這些拷貝操作,都會導致加鎖和解鎖的速度大大降低。

            表面加鎖時緩沖不必要是因為應用程序不需要直接對表面(紋理除外)進行讀寫,為了取得更好的性能,可以使用顯卡直接對表面進行操作。紋理,從另一方面講,僅會被加鎖一次,然后從文件中加載信息到它的表面。所以,對于紋理的加鎖,可以讓主線程等待渲染線程返回,然后再進行加鎖,而不用對其進行緩沖。注意,等待渲染線程返回并加鎖有可能導致錯誤。因為有可能顯卡正在填充這個表面資源。記住,頻繁的對表面進行加鎖將會導致因為主線程的強制等待而使效率降低。

            在哪里進行線程化

            這個方案的實現只能是在包裝圖形API的那個包裝類中。每一個函數都會有不同的實現,因為它們有可能是直接調用圖形API,而有可能是要在調用圖形API前等待前一幀繪制完成。或者是將數據放入渲染線程的隊列,讓渲染線程去調用API。也可以對其進行一些優化,采用在包裝類中做一些緩存來緩存那些需要等待的調用(譯注:個人理解是因為每一幀需要等待的調用可能不止一個,將他們緩存起來。這樣當渲染線程返回時,一并調用這些函數,就能少了許多等待)。你還需要的就是修改渲染系統插件的代碼,用你的包裝類函數替換掉所有的圖形API調用。

            下面的代碼演示了如何進行IUnknown接口的包裝。

             


            D3D9WrapperUnknown:: D3D9WrapperUnknown

            D3D9WrapperDevice
            * pD3DDevice)

            :m_pD3DDevice(pD3DDevice)

            ,m_RefCount(
            1)

            {

            }


            D3D9WrapperUnknown:: 
            ~D3D9WrapperUnknown

            {

                
            if(m_RefCount>0)

                
            {

                    
            //輸出信息,因為錯了。

                }


            }


            ULONG D3D9WrapperUnknown::AddRef( )

            {

                
            return ++m_RefCount;

            }


            ULONG D3D9WrapperUnknown::Release( )

            {

                
            if(!(--m_RefCount))

                
            {

                    
            if(m_pUnknown != NULL)

                    
            {

                        m_pD3DDevice
            ->SubmitRelease(m_pUnKown);

                    }


                    delete 
            this;

                }


                
            return m_RefCount;

            }



            這個技術達到了本文介紹中的所有目標,并且也是最終的選擇方案。D3D9渲染系統被選擇來實現這個方案,伴隨本文的代碼可以從Intel Developer website上下載。

            OGRE分析以及結果

            這個部分我們將討論采用“低級線程化手段”時,運行OGRE的例子程序的情況。下面的數據是在2.4GHz Core 2 雙核處理器,NVIDIA 7800顯卡上測試的結果。這些數據顯示了兩個不同的緩沖加鎖方案,以及在沒有開啟多線程時DirectX包裝類的負載。綠色背景指明了使用這個方案時提升到了1.1倍,甚至更多。 黃色部分指明了運用該方案時,降低到了1.0倍以下,但是并無太大損耗。而紅色部分指明了動用此方案時,出現了沖突情況。

            clip_image018

            注意,這里有太多的1。0左右的元素,當然也有一些稍微低于1。0的。但是不會太多??梢钥吹剑@里也有許多綠色的元素,表明運行良好。但要注意,這些測試例子都是OGRE的DEMO,DEMO并不使用CPU處理太多的東西。只有兩個元素是紅的。都是處于“完全緩沖加鎖”方案中。因為這些DEMO經常更新很大的頂點緩沖。導致很多時間花費在等待復制完成,從而出現問題。

            需要特別說明的是“Shadow Demo”。有兩種陰影方案,模版和紋理。只有模版陰影在“部分緩沖加鎖”方案時會出現問題,而紋理陰影則工作良好。

            同時也要注意的時,一些例子在“完全緩沖加鎖”方案中運行的效果比“部分緩沖加鎖”方案要好。在這種情況下,應用程序取得一個可以讀寫的緩沖區。在這種情況下,主要是負載在于應用程序會將顯存中的數據復制到系統內存中。而“完全緩沖加鎖”方案通過將加鎖和解鎖放到渲染線程中,而將這個負擔消除了。

            結論:

            將一個3D渲染引擎線程化是一項具有挑戰性的工作,但事實證明,它是可行的。正如先前所展示的一樣,將OGRE渲染引擎線程化是很成功的,并且在某些DEMO上運行出了良好的效果。有三種線程化方案,但最終只選擇了“低級優化方案“來實現。因為其滿足了本文最初提出的目標。在OGRE社區的共同努力下,OGRE可以在CPU和GPU資源都密集型的應用程序中,運行出更多幀數和更平滑的顯示效果。

            posted on 2010-12-20 23:14 麒麟子 閱讀(2357) 評論(0)  編輯 收藏 引用 所屬分類: Game and Engine

            久久国产欧美日韩精品| 久久精品国产亚洲AV香蕉| 久久久SS麻豆欧美国产日韩| 亚洲AV日韩精品久久久久| 久久中文娱乐网| 久久亚洲美女精品国产精品| 亚洲国产成人精品女人久久久| 日韩va亚洲va欧美va久久| 2021国内久久精品| 久久五月精品中文字幕| 国产午夜精品久久久久九九电影| 国产精品99精品久久免费| 亚洲午夜久久久久久久久久| 久久精品视频一| 亚洲精品乱码久久久久久不卡| 久久午夜羞羞影院免费观看| 久久久久亚洲精品男人的天堂| 婷婷久久香蕉五月综合加勒比| 久久精品无码av| 婷婷久久综合| 久久免费小视频| 丰满少妇高潮惨叫久久久| 一级做a爰片久久毛片看看| 国产成人综合久久久久久| 国产农村妇女毛片精品久久| 亚洲中文久久精品无码| 欧美日韩精品久久久免费观看| 91精品国产91久久久久久青草| 精品国产婷婷久久久| 久久精品成人免费网站| 久久精品亚洲福利| 99久久久久| 国产亚洲精午夜久久久久久| 久久精品国产亚洲欧美| 777米奇久久最新地址| 精品久久久久久无码免费| 99精品伊人久久久大香线蕉| 热99re久久国超精品首页| a级毛片无码兔费真人久久| Xx性欧美肥妇精品久久久久久| 99久久国产免费福利|