• <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>

            歲月流轉(zhuǎn),往昔空明

            C++博客 首頁(yè) 新隨筆 聯(lián)系 聚合 管理
              118 Posts :: 3 Stories :: 413 Comments :: 0 Trackbacks

            置頂隨筆 #

            SALVIA是一款光柵化的軟件渲染器,設(shè)計(jì)目標(biāo)是達(dá)到Direct3D 10/11的核心功能的實(shí)現(xiàn)。我們的設(shè)計(jì)目的主要包括以下幾點(diǎn)

            • 一個(gè)高度可移植的光柵化圖形管線的軟件實(shí)現(xiàn)
            • 圖形硬件工作原理的展現(xiàn)和教學(xué)
            • 為下一代Many Core處理器架構(gòu)的計(jì)算設(shè)備提供高性能的圖形繪制能力
            • 提供在GPU一類的流處理器上難以實(shí)現(xiàn),但在Many Core架構(gòu)的設(shè)備上有著顯著優(yōu)勢(shì)的Features
            • 比圖形API更加易于使用的接口
            • 與復(fù)雜的渲染技術(shù)(如輻射度和光線追蹤等)相結(jié)合的可伸縮的渲染體系,研究可以提供速度-質(zhì)量相均衡的渲染架構(gòu)


            SALVIA的接口重點(diǎn)參照了DX10的設(shè)計(jì)。
            以流水線劃分Stage;每個(gè)Stage及其相關(guān)設(shè)施的接口,均采用了Object-Oriented的設(shè)計(jì)風(fēng)格。
            這種設(shè)計(jì)與D3D9和OGL的狀態(tài)機(jī)風(fēng)格的設(shè)計(jì)相比更易于使用,同時(shí)也降低了流水線前后級(jí)的耦合,對(duì)于優(yōu)化或擴(kuò)展都是有利的。

            目前,SALVIA已經(jīng)具有了完整的D3D9的流水線級(jí),并有了基本的Demo。
            在未來(lái),SALVIA將在維持內(nèi)核穩(wěn)定的同時(shí),通過擴(kuò)展提供先進(jìn)的圖形技術(shù)支撐。
            同時(shí),我們還將嘗試著將一些不易在GPU上實(shí)現(xiàn)的算法,以擴(kuò)展的形式在SALVIA中實(shí)現(xiàn)出來(lái),以期提供高于圖形API的表現(xiàn)和特性。

            SALVIA在近階段的主要工作包括:

            • Rasterizer的優(yōu)化
            • SALVIA Shading Language語(yǔ)言特性設(shè)計(jì)及編譯器實(shí)現(xiàn),為SALVIA提供文本化的Shader
            • MSAA,并提供可定制的Sampling Pattern(2x 和 4x,目前尚有Bug)
            • EWA-based Anistropic Filtering
            • 以擴(kuò)展形式提供的Geometry Shader,Hull Shader和Tesselassion Shader
            • 并行優(yōu)化(持續(xù)優(yōu)化中)
            • Intel SCC的移植
            • 特性及性能的演示用例
            • 文檔撰寫 (已經(jīng)有成員負(fù)責(zé)此事)


            目前,SALVIA已經(jīng)作為一個(gè)開源項(xiàng)目發(fā)布在http://code.google.com/p/softart上,最新的代碼在Mercurial中。
            所有代碼除特殊聲明外,均為GPL 2協(xié)議,您可以在協(xié)議許可的范圍內(nèi)自由下載或使用。

            如果發(fā)現(xiàn)了軟件的缺陷,或者有任何好的意見和建議,您可以在項(xiàng)目管理頁(yè)面上留言,或者聯(lián)系作者
            wuye9036@gmail.com
            minmin.gong@gmail.com
            我謹(jǐn)代表項(xiàng)目全體成員及用戶,對(duì)您為本項(xiàng)目的發(fā)展做出的獨(dú)一無(wú)二的貢獻(xiàn)表示敬意和感謝!


            作為一款基于GPL2協(xié)議的開源光柵化渲染器,SALVIA的目的當(dāng)然不僅僅是軟件產(chǎn)品那么簡(jiǎn)單。
            我們也希望以SALVIA為基礎(chǔ),建設(shè)一個(gè)充滿智慧與活力的社區(qū)。
            這個(gè)社區(qū)里,每一個(gè)智慧的閃光,都能夠給其他人以啟迪;每一個(gè)智慧的閃光,都能夠使SALVIA向更好的方向邁出一步。

            隨著SALVIA框架的完成,SALVIA復(fù)雜而有挑戰(zhàn)性的特性擴(kuò)充工作已經(jīng)擺在面前。
            無(wú)論你

            • 是喜歡Irregular Z Buffer一類不走尋常路的硬件架構(gòu)技術(shù),期望實(shí)現(xiàn)自己的硬件架構(gòu);
            • 還是癡迷于運(yùn)用最新的圖形學(xué)理論,制作讓人眼花繚亂,嘆為觀止的Demo;
            • 還是希望將SALVIA與商業(yè)產(chǎn)品相結(jié)合,使其想用戶所想,為用戶所不能為;

            我們都以100%的熱忱歡迎您。

            為了維持SALVIA核心框架的穩(wěn)定性,保證代碼質(zhì)量,我們計(jì)劃將全部的Project Members分為核心組開發(fā)者組兩部分。

            核心組
            暫時(shí)由 空明流轉(zhuǎn)(wuye9036@gmail.com) 和 Minmin.Gong(minmin.gong@gmail.com) 組成,主要負(fù)責(zé)架構(gòu)設(shè)計(jì),Shading Language語(yǔ)言標(biāo)準(zhǔn)的制定,SALVIA內(nèi)核的開發(fā),設(shè)計(jì)文檔和接口約定的撰寫,以及主分支的維護(hù)工作。

            開發(fā)者組將按照工作內(nèi)容大致分為三種:

            • 文檔組:主要負(fù)責(zé)注釋和文檔的撰寫工作等
            • 編譯器組:負(fù)責(zé)編譯器Host特性和Language Bridge的設(shè)計(jì)和擴(kuò)充,編譯器維護(hù),性能調(diào)優(yōu)等
            • 擴(kuò)展組:撰寫設(shè)備或輔助庫(kù)擴(kuò)展,如Geometry Shader的Host代碼,數(shù)學(xué)庫(kù)等

            現(xiàn)有開發(fā)組成員均具有6-12年不等的開發(fā)經(jīng)驗(yàn),多數(shù)在業(yè)內(nèi)著名企業(yè)擔(dān)任主要開發(fā)人員或技術(shù)負(fù)責(zé)人的職位。

            我們對(duì)開發(fā)組成員充分信任,開發(fā)組成員將在各自的分支上完成開發(fā)工作,在您工作的分支上,您享有完全的寫權(quán)限。
            我們將按期進(jìn)行所有分支修改的Review工作,并邀請(qǐng)您參與到Review中來(lái),您既是分支的作者,也是其他分支的審閱者。
            如果您的修改通過了Review并采納到主分支中,我們希望能在您的協(xié)助下,將您對(duì)SALVIA的所思,所想,所為,原原本本的融入到SALVIA主分支中,令它如您所想般的成長(zhǎng)。
            同時(shí),核心組將會(huì)視情況,組織線上或線下的技術(shù)交流活動(dòng),與大家一起交流技術(shù)心得、分享管理經(jīng)驗(yàn)。當(dāng)然,也會(huì)分享快樂的人生。

            如果您希望加入我們這個(gè)團(tuán)隊(duì)當(dāng)中,為我們的團(tuán)隊(duì),為SALVIA提供您寶貴的支持,請(qǐng)您準(zhǔn)備好您的以下資料

            • ID:常用的ID,最好包括真實(shí)姓名
            • Google Account:如果沒有,可以申請(qǐng)一個(gè)。因?yàn)槲覀兊腟VN Repository是建立在Google Code上的)
            • 聯(lián)系方式:IM(QQ,MSN,GTALK)和Email,有手機(jī)最好
            • 自我介紹:包括擅長(zhǎng)的技術(shù)啦,項(xiàng)目經(jīng)驗(yàn)啦,閑扯也可,呵呵
            • 希望參與的工作
            • 其他要求:唔。。。隨便什么要求


            發(fā)送至郵箱 wuye9036@gmail.com,或在此站點(diǎn)以站內(nèi)信的方式發(fā)送與我。我將盡可能的與您聯(lián)系并面議。


            我們真誠(chéng)歡迎您的參與,并對(duì)您的加盟,表示真心的感謝和由衷的期待!

            posted @ 2009-12-07 10:31 空明流轉(zhuǎn) 閱讀(3359) | 評(píng)論 (15)編輯 收藏

            2014年1月14日 #

            以前的時(shí)候話多,能寫很長(zhǎng)。工作以后人越來(lái)越懶,也越來(lái)越不能寫了。 總體來(lái)說(shuō)13年干掉的事情比較多,但是得到的成長(zhǎng)比較少。 最重要的當(dāng)然是把結(jié)婚證領(lǐng)了。這個(gè)本來(lái)就在計(jì)劃內(nèi),沒啥好說(shuō)的。 然后就是來(lái)了X康。雖然去年底就把面試什么的走完了,但是二月底我才拿到offer,三月初才入職, 然后9月份就忙著領(lǐng)證,十月份開始就是辦簽證,準(zhǔn)備搬家,諸如此類日常瑣碎的事務(wù)。 十二月份整個(gè)月都耽誤在Relocation上。 自己的項(xiàng)目方面,就是想明白一些事情后,緩慢更新SALVIA。開了一些坑,比如CppTemplateTutorial。但是沒有一個(gè)坑是填平了的。 估摸著,2014年大概和今年的狀態(tài)接近。買車,把老婆弄過來(lái),婚禮,省錢準(zhǔn)備接下來(lái)的生活,有時(shí)間的話就填坑(你們有什么C++ Template的問題都來(lái)問吧。。。)。
            posted @ 2014-01-14 10:07 空明流轉(zhuǎn) 閱讀(3372) | 評(píng)論 (6)編輯 收藏

            2013年2月11日 #

            梗概

            SALVIA 0.5.2 的優(yōu)化經(jīng)歷是一個(gè)“跌宕起伏”的過程。這個(gè)過程的結(jié)果很簡(jiǎn)單:

            在Core 2 Duo T5800(2.0GHz x 2)上,Sponza的性能提升了60%,ComplexMesh性能提升了26%。

             

            背景

            SALVIA的整個(gè)渲染流程主要是以下幾部分:

            • 根據(jù)Index Buffer獲得需要進(jìn)行變換的頂點(diǎn);
            • 將頂點(diǎn)利用Vertex Shader進(jìn)行變換;
            • 將變換后的頂點(diǎn),輸出成若干個(gè)float4;
            • 將三角形光柵化。SALVIA的光柵化是將三角形拆分成4x4的像素塊若干,不滿的塊有掩碼來(lái)處理;
            • 將像素進(jìn)行插值;
            • 插完值后把像素送到Pixel Shader中處理一趟;
            • 處理完的結(jié)果用Blend Shader塞到Back buffer里面去。

            用于測(cè)試的場(chǎng)景:

            • Sponza 26萬(wàn)個(gè)面,20個(gè)左右的Diffuse紋理(1024x1024);
            • PartOfSponza 約200個(gè)面,4個(gè)Diffuse紋理(1024x1024);
            • ComplexMesh 兩萬(wàn)個(gè)面,無(wú)紋理,有個(gè)能量保守的光照。

            最初的版本(V1231)中,性能的主要瓶頸在插值階段,各種耗時(shí)林林總總占了一半以上(50% - 70%)。

            相比之下其他階段對(duì)性能的影響要么有限,要么沒有多少優(yōu)化空間。所以最近一周的優(yōu)化,就都集中在了“插值”上。

             

            插值算法

            線性的插值算法常見的實(shí)現(xiàn)有兩種,

            第一種是拿UV插值,第二種是用ddx和ddy累積。

            UV是先計(jì)算像素的u和v(基本方法是用面積比,不記得就復(fù)習(xí)一下中學(xué)幾何吧),然后用插值公式:

            pixel = v0 * u + v1 * v + v2 * (1-u-v)

            后者的步驟是選一個(gè)主頂點(diǎn),然后計(jì)算這個(gè)頂點(diǎn)的ddx和ddy,最后用

            pixel = v0 + ddx * offset_x + ddy * offset_y

            計(jì)算出相應(yīng)頂點(diǎn)。

            但是在圖形學(xué)中,我們還需要對(duì)插值進(jìn)行透視修正,獲得在3D空間中線性的插值結(jié)果。

            我們將步驟修正到透視空間

            先將v0,v1,v2弄到透視空間中,變成projected_v0, projected_v1, projected_v2

            對(duì)于UV的插值是

            pixel = ( projected_v0*u + projected_v1*v + projected_v2 * (1-u-v) ) / pixel_w

            對(duì)于用ddx和ddy的累積公式是:

            pixel = ( projected_v0 + projected_ddx * offset_x + projected_ddy * offset_y ) / pixel_w

             

            插值算法的選擇

            何詠(Graphixer)大神之前也寫了一個(gè)渲染器,比我快許多(大概是4-6倍),用的是UV;

            gameKnife大神兩個(gè)禮拜寫成的渲染器,速度比我用五年寫出來(lái)的半成品要快7倍,用的辦法是Lerp到Scanline上,再Lerp到像素。

            SALVIA采用了累積法:

            struct transformed_vertex { float4 attributes[MAX_ATTRIBUTE_COUNT]; };
            transformed_vertex projected_corner;
            
            // 計(jì)算角點(diǎn)的坐標(biāo)
            projected_scanline_start = projected_v0 + projected_ddx * offset_x + projected_ddy * offset_y; 
            
            // 像素的透視修正值
            float inv_w; 
            
            // 最終輸出的4x4個(gè)像素
            pixel_input px_in[4][4];
            
            for(int i = 0; i < 4; ++i)
            {
              projected_pixel = projected_scanline_start;
              for(int j = 0; j < 4; ++j)
              {
                  // 透視空間轉(zhuǎn)換到線性空間并輸出到px_in中
                  px_in[i][j] = unproject( projected_pixel );
                 // 累加x方向上的值(透視空間)
                  projected_pixel += projected_ddx;
              }
              // 累加y方向上的值(透視空間)
              projected_scanline_start += projected_ddy;
            }

             

            本輪優(yōu)化之前對(duì)插值算法的優(yōu)化嘗試

            注意那個(gè)MAX_ATTRIBUTE_COUNT,這個(gè)值通常比較大,在v1231中,它是32。

            不過,顯然我們不需要對(duì)所有的屬性進(jìn)行計(jì)算。敏敏在這里運(yùn)用了一點(diǎn)小小的技巧進(jìn)行了優(yōu)化:只計(jì)算必要的屬性。同時(shí),為了減少分支的使用,他甚至用

            template <int N>
            void sub_n(out, v0, v1 )
            {
                for(int i = 0; i < N; ++i) {
                   out.attributes[i] = v0.attributes[i] – v1.attributes[i];
                }
            }

            并配合函數(shù)指針的方法,以促使編譯器展開循環(huán),減少分支。

            不過從實(shí)際生成的匯編來(lái)看,這個(gè)部分并沒有被展開到期望的形式,可能是編譯器認(rèn)為x86的Branch Predication性能已經(jīng)足夠高了吧。

            這個(gè)“優(yōu)化”在v1231中就已經(jīng)具備了。

             

            首輪優(yōu)化:unproject函數(shù),operator += 與 operator =

            第一個(gè)Profiling是用BenchmarkPartOfSponza和Sponza跑的;unproject,operator +=和operator = 加在一起大約占用了15-20%的時(shí)間。單獨(dú)的unproject

            最初的實(shí)現(xiàn)就是普通的標(biāo)量。既不要求對(duì)齊,也沒有使用SIMD。

            所以當(dāng)然會(huì)以為用了SIMD后,優(yōu)化效果會(huì)很好。于是在v1232中,中間頂點(diǎn)和像素輸入的分配都以16字節(jié)對(duì)齊,unproj,+=和=也都使用了SSE進(jìn)行了重寫。

            從跑分來(lái)看,PartOfSponza性能提升了20%。但是,在測(cè)試ComplexMesh和Sponza時(shí),并未發(fā)現(xiàn)幀率有顯著提升。

            其實(shí)在進(jìn)行優(yōu)化之前,何詠就告誡過我,因?yàn)楝F(xiàn)代CPU的一些技術(shù),比方說(shuō)超標(biāo)量啥的,四個(gè)數(shù)據(jù)寬度的SSE和標(biāo)量運(yùn)算相比,就只有50%的性能差距。

            并且這些函數(shù)的指令已經(jīng)極為簡(jiǎn)單,瓶頸也很明確的落在計(jì)算指令上。例如Unproject優(yōu)化后,性能焦點(diǎn)就落在_mm_mul_ps上(3.7%),幾無(wú)優(yōu)化余地。

             

            二輪優(yōu)化:插值算法的調(diào)整

            在進(jìn)行第二輪優(yōu)化之前同樣運(yùn)行了一次Profiling。因?yàn)閷?duì)PartOfSponza性能基本滿意,因此這次優(yōu)化的目標(biāo)主要在Sponza上。

            排名前幾位的小函數(shù),分別是sub_n,unproj,+= 和tex2D。對(duì)sub_n例行優(yōu)化后,性能沒什么變化。當(dāng)然,這也是意料之中的事情了。

            因此,第二輪優(yōu)化便著重考慮在插值算法本身上。

            在優(yōu)化之前,我嘗試對(duì)代碼成本做個(gè)粗略的評(píng)估:

            在現(xiàn)有算法下,假設(shè)每個(gè)像素有N個(gè)需要插值的屬性,則平均每個(gè)像素有

            (corner)3N/16個(gè)讀 + 2N/16個(gè)乘法 + 2N/16個(gè)加法 + N/16個(gè)寫

            (x:+=)2N個(gè)讀 + N個(gè)加法 + N個(gè)寫

            (x:*)  N個(gè)讀 + 1個(gè)標(biāo)量除法 + N個(gè)乘法 + N個(gè)寫

            (y:+=)2N/4個(gè)讀 + N/4個(gè)加法 + N/4個(gè)寫

            (y:=) N/4個(gè)讀 + N/4個(gè)寫

            因?yàn)槊總€(gè)都是函數(shù)指針,所以這些都是優(yōu)化不掉的。因此首先將一些操作合并了一下,比如把+= 和*合并以減少一下讀寫操作。只可惜效果也不是很明顯。

             

            第二刀就砍到算法的頭上。因?yàn)槔奂颖旧硎菫榱藴p少乘法的運(yùn)用,但是這可能帶來(lái)了多余的存取開銷。

            因此直接套用公式:

            pixel = ( projected_v0 + projected_ddx * offset_x + projected_ddy * offset_y ) / pixel_w

            這樣就有:3N讀,2N乘法,2N加法,N個(gè)乘法和N個(gè)寫(假設(shè)寄存器夠用的話)。不算Corner的計(jì)算成本,這樣比較一下,就等于是3N/4個(gè)讀,N/2+N個(gè)寫,N/4個(gè)加法來(lái)?yè)Q取2N個(gè)乘法的時(shí)間。本來(lái)以為作為IO瓶頸的應(yīng)用,這樣可以提高一些性能。不過結(jié)果證實(shí)這個(gè)買賣實(shí)在是很不劃算,整體性能不增反減。

             

            三輪優(yōu)化:減少內(nèi)存占用,柳暗花明

            雖然所有的操作只針對(duì)已使用的屬性,但是空間上還是浪費(fèi)了許多。

            考慮到內(nèi)存占用較大也會(huì)導(dǎo)致一些性能損失,于是將MAX_ATTRIBUTE_COUNT從32下調(diào)到了8。

            結(jié)果令人大跌眼鏡。性能瞬間提升了20-30%之多。

            再加上SSE也不知道為什么開始發(fā)力了,使用上之后性能大約又有了10-15%的提升。

            我猜測(cè)可能是因?yàn)閾Q頁(yè)頻率下降,以及Cache的命中率提升。不過手上沒有VTune這種工具,所以也不太好驗(yàn)證。

             

            四輪優(yōu)化:精度敏感性下降的額外紅利

            在這輪優(yōu)化之后,PartOfSponza出現(xiàn)了精度問題。因?yàn)橐曞F體的上下左右四個(gè)面都沒有Clip,所以可能會(huì)出現(xiàn)非常大的三角形。這樣累積的時(shí)候一旦起始點(diǎn)選擇的不好,就會(huì)出現(xiàn)比較大的誤差。在之前版本中,使用/fp: precise來(lái)減少這一問題出現(xiàn)的機(jī)會(huì)。但是因?yàn)槭褂昧薙SE,也讓這個(gè)問題再難解決。因此我選用了一些辦法,來(lái)改善精度問題。在大問題都修正以后,換用/fp: fast來(lái)編譯整個(gè)SALVIA,最終也獲得了0-10%左右的性能收益。

             

            結(jié)論

            對(duì)于運(yùn)算和IO都密集的程序來(lái)說(shuō),優(yōu)化真可能是牽一發(fā)而動(dòng)全身的問題。比如在我的例子中,所有猜測(cè)是性能瓶頸的地方,都沒有得到預(yù)想中的改善。

            倒是在內(nèi)存占用這個(gè)地方無(wú)心插柳,才得以柳暗花明,而且還讓別的優(yōu)化方案體現(xiàn)了價(jià)值。所以如果你不像qiaojie大牛那樣對(duì)x86了如指掌,還是要習(xí)慣于從多方面猜測(cè),例如內(nèi)存占用,對(duì)齊或緊縮,計(jì)算強(qiáng)度,訪存密度,并行度等多個(gè)角度進(jìn)行設(shè)想并用實(shí)踐去驗(yàn)證。盡管可能會(huì)遇到很多挫折,但是,只要是直覺上有優(yōu)化的余地,一般都可以找到合適的方案。

            posted @ 2013-02-11 20:09 空明流轉(zhuǎn) 閱讀(2809) | 評(píng)論 (2)編輯 收藏

            2013年1月13日 #

            SALVIA是從07年底開始開發(fā)的。歷經(jīng)五年,無(wú)論是設(shè)計(jì)目標(biāo),還是使用到的一些方法,都和最初差別很大。

            謹(jǐn)以此文,紀(jì)念我在五年中作出來(lái)的各種傻逼決定。

             

            1. 2007年9月 - 2007年12月:可笑的動(dòng)機(jī),可笑的雛形

            動(dòng)機(jī)與原型

            SALVIA出現(xiàn)的原因其實(shí)很可笑。07年底的時(shí)候我正在寫一篇paper,講GP-GPU的。那個(gè)時(shí)候還沒有CUDA一類的東西,一切都要靠Shader來(lái)。本來(lái)我手上的顯卡是一塊9550的SDRAM的簡(jiǎn)版。但是論文快結(jié)束的時(shí)候,突然這卡的風(fēng)扇就罷工了。然后我降頻用了大概一個(gè)多月,卡也廢掉了。因?yàn)闆]錢買新顯卡,我就打算寫一個(gè)比D3D REF快的軟件渲染器。

            07年底的時(shí)候,實(shí)現(xiàn)了第一版的SALVIA,當(dāng)時(shí)還叫SoftArt。第一版的SALVIA其實(shí)還算不錯(cuò),流水線的完整程度到現(xiàn)在都還沒超過,包括Cpp的Vertex Shader和Pixel Shader、紋理采樣、光照什么的一應(yīng)俱全。在開發(fā)過程中,主要參考GL 2.0的Specification,也閱讀了一些同類型軟件的代碼,例如Muli3D和Mesa。

            一些對(duì)管線至關(guān)重要的概念,例如透視修正、固定管線上紋理采樣的LoD Level、Clip都是借助于Spec和這些實(shí)現(xiàn)建立的。

            為什么要有Shader Compiler

            如果是固定管線的話,那么SALVIA做到這些特性也就足夠了。但是從SALVIA一開始,我就希望讓它成為一個(gè)Pure Shader的管線,固定管線的那些狀態(tài)實(shí)在太煩人了。本來(lái)Cpp實(shí)現(xiàn)的Shading language能滿足絕大部分的需要了,但是有一個(gè)特性徹底難倒了我:Pixel Shader的差分函數(shù)ddx/ddy。

            這個(gè)東西的工作原理是這樣的:

            比方說(shuō)我有一段shader函數(shù):

            float shading_pixel( ... ): COLOR0
            {
                float x;
                // Expression for calculating x
                return ddx(x);
            }

            在Pixel Shader運(yùn)行的時(shí)候,它一次性執(zhí)行2x2的一個(gè)小塊,所有的指令對(duì)于整個(gè)塊內(nèi)都是同步執(zhí)行的。遇到ddx(x)后,四個(gè)像素都正好執(zhí)行到這里,然后把x方向上的相鄰兩個(gè)像素的局部變量x求個(gè)差,就可以得出ddx了。

            這個(gè)要求在C++中很難實(shí)現(xiàn)。

            1. 不好讓C++的四個(gè)函數(shù)都在同一個(gè)地方Join;
            2. 我不好去獲得相鄰函數(shù)的棧上的值。

            其實(shí)如果要較真,當(dāng)然還是有辦法的:

            1. 對(duì)于Join問題,起碼有兩種方案:
              • 自己搞一個(gè)Fiber Manager,直接控制代碼的棧的Switch。每個(gè)pixel都有一個(gè)Fiber,到了DDX/DDY就換到下一個(gè)Fiber執(zhí)行,直到所有的Fiber都執(zhí)行完畢后,計(jì)算ddx,寫入棧變量,再繼續(xù)執(zhí)行;
              • 直接用線程,Join,計(jì)算,然后繼續(xù)執(zhí)行。
            2. 對(duì)于棧變量的地址問題,也有辦法:
              • 在切換線程的時(shí)候直接保存臨時(shí)變量的地址。

            但是這些實(shí)現(xiàn),要么因?yàn)榍袚Q上下文而變得奇慢無(wú)比;要么就是完全沒有平臺(tái)移植性。想來(lái)想去,還是要讓代碼按照硬件的方式SIMD執(zhí)行。

            所以我最終橫下一條心:要為它做Shading Language Compiler。然后開始了漫長(zhǎng)的Compiler開發(fā)。后來(lái)我看團(tuán)長(zhǎng)那個(gè)《漫無(wú)止境的八月》的時(shí)候,簡(jiǎn)直就是對(duì)著鏡子照自己的傻逼。所以我才更黑團(tuán)長(zhǎng)。

            2. 2008年初 - 2009年12月:黎明前的黑暗

            Shader的文法

            08年到09年我都在外面實(shí)習(xí),一周上六天班,一天得干上十個(gè)多小時(shí)。從2008年初到7月份,我都一直在看編譯原理和成熟的語(yǔ)法庫(kù)。底子薄,看起來(lái)很吃力。到了8月份開始設(shè)計(jì)Shader的EBNF。設(shè)計(jì)語(yǔ)言,不外乎是三個(gè)方面:應(yīng)用場(chǎng)景、語(yǔ)法和庫(kù)的支持。盡管有現(xiàn)成的HLSL和GLSL作參考,但對(duì)于我從0開始設(shè)計(jì)語(yǔ)言來(lái)說(shuō),這些語(yǔ)言的語(yǔ)法和語(yǔ)義都過于復(fù)雜了。我需要讓語(yǔ)言特性慢慢的添加進(jìn)來(lái)。

            考慮到HLSL和C比較接近,C的文法參考資料又很多,于是我選擇了從C開始裁剪語(yǔ)法。但是文法這個(gè)東西,并不簡(jiǎn)簡(jiǎn)單單是樹狀的結(jié)構(gòu),樹上的任何一個(gè)語(yǔ)法節(jié)點(diǎn),都可能會(huì)引用到其它的文法規(guī)則。因此修改了一條規(guī)則后,你會(huì)發(fā)現(xiàn)它可能會(huì)和其它規(guī)則沖突了,二義了。于是裁剪計(jì)劃完蛋了。

            當(dāng)然,如果我現(xiàn)在來(lái)設(shè)計(jì)語(yǔ)法,肯定會(huì)和陳漢子一樣,直接從Use Case就能把EBNF寫出來(lái),再稍微規(guī)范一下,一門不那么復(fù)雜的語(yǔ)言就成了。當(dāng)然像C++這種變態(tài)語(yǔ)言,這樣做是做不出來(lái)的。但當(dāng)時(shí)我顯然不具備那樣的能力。從七月份開始就磕磕絆絆地裁剪了一些語(yǔ)法特性之后的語(yǔ)言,到了八月份才出了個(gè)千瘡百孔的方案。

            神:Boost.Spirit

            作為完全不懂編譯器的矬貨,設(shè)計(jì)語(yǔ)言一定要和編譯器的開發(fā)放在一起才能有點(diǎn)收獲。我用過Flex/Bison,用過ANTLR。但是當(dāng)時(shí)我對(duì)編譯器特別的陌生,組織Build的能力也比較弱,因此它們?cè)谑褂蒙戏爆嵑碗y于調(diào)試給我?guī)?lái)了很大的困擾。不過那時(shí)我對(duì)模板、元編程和Boost就已經(jīng)相當(dāng)熟悉了,無(wú)論是開發(fā)、閱讀代碼還是Debug都能輕松應(yīng)付,所以我挑了半天,選了Boost.Spirit。

            Boost.Spirit是個(gè)很奇葩的東西。它想在C++里面提供一個(gè)類似于EBNF、可以定義語(yǔ)法分析規(guī)則的方言。要讓C++看起來(lái)像一個(gè)方言,當(dāng)然是要使用神出鬼沒的操作符重載。當(dāng)然,即便是修飾后的語(yǔ)法,看起來(lái)也還是會(huì)有點(diǎn)怪怪的。EBNF中的規(guī)則

            Rule ::= Token SubRule0 [OptionalSubRule1]

            在Cpp中最簡(jiǎn)單可以表示成

            rule = token >> subrule0 >> optional(OptionalSubRule1)

            雖然看起來(lái)有點(diǎn)丑陋,但是它已經(jīng)完全滿足一個(gè)DSL的要求了:直觀的面向解決方案。

            不過如果牽涉到實(shí)現(xiàn)細(xì)節(jié),在C++里面要寫一個(gè)又簡(jiǎn)單、又可用Parser Generator,那幾乎是不可能完成的任務(wù)。起碼對(duì)于Combinator-based Parser來(lái)說(shuō),它夠簡(jiǎn)單,但是沒有CPS的支持會(huì)令錯(cuò)誤恢復(fù)這一類的周遭設(shè)計(jì)變得極為可怕;如果Rule只是grammar definition,不牽涉到任何Parser的構(gòu)造,那解析這個(gè)definition的復(fù)雜度和調(diào)試難度又不亞于ANTLR或者Yacc這樣有單獨(dú)腳本的工具。所以這項(xiàng)工作,還是交給Haskell這樣的語(yǔ)言來(lái)完成吧。

            通過使用Spirit、設(shè)計(jì)編譯器、折騰文法,讓我對(duì)Compiler和Cpp的理解都遞進(jìn)了一大步。再加上08年全年都在做GUI相關(guān)的東西,也讓我對(duì)編譯器的理解有所加深。

            09年下半年我一直都比較動(dòng)蕩,不過到年底總算是安定了下來(lái)。

            3. 2009年12月—2010年2月:長(zhǎng)征的開始

            后端與前端

            09年12月份的時(shí)候,Boost升級(jí)了,Spirit也到了V2。到了2月份,我費(fèi)了點(diǎn)功夫,把V2的Spirit折騰到SALVIA的前端上。Parser也有所變化:前一版的Parser還比較草率,這一版的Parser我?guī)缀跏峭耆凑誗pirit的Demo中的方案進(jìn)行的。此時(shí)我也開始嘗試著撰寫語(yǔ)義分析。怎么做函數(shù)重載都是在那個(gè)時(shí)候開始點(diǎn)的技能樹,雖然在現(xiàn)在看來(lái)都是歪的。為了執(zhí)行生成的代碼,我設(shè)計(jì)了半個(gè)虛擬機(jī),然后還準(zhǔn)備寫點(diǎn)教程。但是我思前想后,對(duì)于Shader這樣一秒鐘要調(diào)用10M次的函數(shù),無(wú)論如何虛擬機(jī)都是不合適的。

            所以我就開始籌備自己的后端。要求就是一個(gè)字:快。那個(gè)時(shí)候,陳漢子正在學(xué)怎么寫x86的JIT。但是我的語(yǔ)言到x86有很長(zhǎng)的路要走。怎么去分配寄存器,怎么把類型轉(zhuǎn)換到x86的Native,怎么選擇指令,我都是一知半解的。憑我當(dāng)時(shí)的知識(shí),這一定是不可能完成的。

            于是在閱讀完Intel Architecture手冊(cè)和優(yōu)化指南后,我決定去找一個(gè)合用的后端。考慮過很多可選的辦法,例如生成C++的Code然后編譯成DLL;使用Tiny C(TCC);或者是JIT。但是它們?nèi)秉c(diǎn)都是很明顯的。編譯成DLL必須要自己裁剪一個(gè)GCC出來(lái);Tiny C的效率并不是很好;JIT很復(fù)雜(起碼在那個(gè)時(shí)候是這樣)。不過2月份的時(shí)候,敏敏還是誰(shuí)指點(diǎn)了我一下,說(shuō)你可以去看看LLVM。然后我去一看,牛逼,就是我要的東西!然后我就開始學(xué)LLVM。LLVM的IR很好學(xué),一個(gè)下午就搞了個(gè)Hello world。

            這個(gè)時(shí)候,minmin也在SALVIA上實(shí)現(xiàn)了Half-Space的光柵化算法。

            那個(gè)時(shí)候我躊躇滿志,意氣風(fēng)發(fā),三月趕英,五月超美。

            可沒想著就這么掉坑里面去了。

            4. 2010年2月—2011年新年:苦難的行軍

            苦難:復(fù)雜的問題

            主體大人真是神,五個(gè)字就概括了我2010年一年的努力。

            • minmin做的SALVIA的Half-Space算法并不比我樸素的Top-Bottom的光柵化強(qiáng);
            • 紋理上的優(yōu)化盡管使用了SSE但是仍然改進(jìn)有限;
            • Shader編譯器本身的編譯時(shí)間由于Spirit的存在而實(shí)在漫長(zhǎng);
            • Shader編譯器和Pipeline如何關(guān)聯(lián)又無(wú)從下手;
            • LLVM的集成也因?yàn)榍岸硕兴R,另外因?yàn)楦鞣N錯(cuò)誤層出不窮,讓整個(gè)開發(fā)進(jìn)度變得龜速。

            所以整個(gè)一年中,SALVIA的開發(fā)就是寫寫停停,停停寫寫。可以說(shuō)08年初的銳氣,已經(jīng)消磨的差不多了。到了8月份的時(shí)候,我畢業(yè)了,新工作也基本上確定和熟悉了,我就和minmin說(shuō),從現(xiàn)在開始我寫半年報(bào)吧,講述一下半年來(lái)的進(jìn)展。于是便有了第一篇項(xiàng)目簡(jiǎn)報(bào)。

            行軍:些微的進(jìn)展

            也正是從那個(gè)時(shí)候,我決定要把SALVIA作為一款實(shí)驗(yàn)品來(lái)對(duì)待,用上所有我不會(huì)的或者新學(xué)的東西。單元測(cè)試,CMake工具鏈,為Shader設(shè)計(jì)的Pipeline,語(yǔ)義分析和后端的原型都在那一年加入了SALVIA。雖然從實(shí)現(xiàn)上它們已經(jīng)與現(xiàn)在相距甚遠(yuǎn),但是起碼一切都還是往好的方向發(fā)展。

            另外,08年到09年期間在實(shí)習(xí)的時(shí)候積累的教訓(xùn)開始慢慢的醞釀和發(fā)酵,敏捷也逐漸成為了我開發(fā)過程中的主要指南。

            基本上,那個(gè)時(shí)候積累了很多必要的經(jīng)驗(yàn)和教訓(xùn)。當(dāng)然絕大多數(shù)是教訓(xùn)。

            5. 2011年2月—2011年6月:新Shader的起點(diǎn)

            坑神:Boost.Spirit的滅亡

            在11年的春節(jié)期間,我終于無(wú)法忍受Spirit的麻煩了:

            • 一段400行不到的代碼,在我的機(jī)器上需要編譯30分鐘;
            • Object File需要占用1.9G的硬盤;
            • Mangling name輕松超過4K字符的限制;
            • 輕易撐爆obj文件的symbol table,需要用/bigobj才能夠編譯通過;
            • 甚至在編譯的時(shí)候會(huì)輕易的讓32位的MSVC CL out of memory。

            要知道,以上這些還是應(yīng)用了Spirit指南中的編譯速度優(yōu)化方案之后的結(jié)果。

            這一切原因,都是因?yàn)锽oost.Spirit對(duì)于Parser Tree,是用了完全靜態(tài)的分析樹結(jié)構(gòu)。每條規(guī)則的返回值都會(huì)是完全不同的類型。這直接導(dǎo)致類型數(shù)量極為龐大,代碼膨脹的厲害。

            于是11年的寒假我花了5天的時(shí)間重新山寨了一個(gè)文法分析器的產(chǎn)生器,并做到DSL幾乎完全和Spirit一致。只不過Parser Tree不再是靜態(tài)類型;模板的用量也減輕了很多。

            Shader的階段性成果

            到了四月份的時(shí)候,Shading Language Semantic/System Value已經(jīng)在語(yǔ)法上支持了,語(yǔ)義上也能分析出哪些變量是System Value,哪些變量是Uniform的。并且通過生成特殊的函數(shù)簽名,Shader滿足了以下幾個(gè)需求:

            1. Shader要返回一個(gè)函數(shù);
            2. 這個(gè)函數(shù)是可重入的(因?yàn)橐l(fā));
            3. 數(shù)據(jù)能正確的從Pipeline傳入到Shader的函數(shù)中,也能正確的返回;
            4. Shader中對(duì)于Pipeline數(shù)據(jù)引用要能正確的生成地址。

            到了11年6月份的時(shí)候,終于把Shader全線貫通。雖然很多Operator和Instrinsic還不支持,但是起碼有了個(gè)可以看的Demo。

            第一個(gè)版本與發(fā)布前的完善工作

            LLVM用上了;VS完整了,PS也有了個(gè)雛形;預(yù)處理器什么的都有了。

            Unit Test也有了原型。我為每個(gè)Stage都做了Unit test:Parser,Semantic,CodeGen和JIT。

            某種意義上來(lái)說(shuō),這幾個(gè)月來(lái)在后端上順利進(jìn)展,讓我多少有點(diǎn)得意忘形。再加上梁總的幫助,SoftArt這個(gè)名字改成SALVIA,LOGO也有了,我在部門內(nèi)部做的一些Introduction也幫助我梳理了思路。于是從4月份開始,我就籌備著要把SALVIA正式發(fā)布出去。

            11年6月1號(hào),SALVIA Milestone 1.0 發(fā)布。有Change Log,有Binary Demo,有Snapshot。

            三周后,發(fā)布了第一個(gè)有Vertex Shader的Demo

            6. 2011年7月—2012年1月:坂道の1.0

            Pixel Shader:需求與設(shè)計(jì)

            在Milestone 1.0發(fā)布后,我開始做Pixel Shader的特性。本以為半年之內(nèi)就能搞定,發(fā)個(gè)1.0揚(yáng)眉吐氣一下。但是實(shí)踐證明,我真是他媽的太盲目樂觀了。

            我先來(lái)說(shuō)一說(shuō)Pixel Shader的特點(diǎn)和需求。比方說(shuō)我有四個(gè)pixel,每個(gè)pixel都是一個(gè)float。

            struct pixel_input
            {
              float data;
            };
            
            pixel_input pixel_block[4];

            然后我要計(jì)算一下,這個(gè)data加上1.0之后是多少。我前面說(shuō)過,我要讓指令看起來(lái)是四個(gè)像素同一時(shí)刻執(zhí)行的,那么顯然我生成的代碼就會(huì)類似于這樣:

            struct pixel_input
            {
              float data;
            };
            
            struct pixel_output
            {
              float data;
            };
            
            void shading_pixel(pixel_input* in_data, pixel_output* out_data)
            {
                 // TMP = IN_DATA.DATA + 1.0
                 float tmp0 = in_data[0].data + 1.0;
                 float tmp1 = in_data[1].data + 1.0;
                 float tmp2 = in_data[2].data + 1.0;
                 float tmp3 = in_data[3].data + 1.0;
            
                // OUT_DATA.DATA = TMP
                out_data[0].data = tmp0;
                out_data[1].data = tmp1;
                out_data[2].data = tmp2;
                out_data[3].data = tmp3;
            }

            Pixel Shader:優(yōu)化與問題

            顯然這里是可以優(yōu)化的:將四條指令并作一條SIMD指令。

            那么這個(gè)時(shí)候,有兩個(gè)需求是要滿足的:

            1. 同樣的struct member一定要是鄰接在一起。
            2. 得根據(jù)SIMD的要求數(shù)據(jù)對(duì)齊。

            只有一個(gè)域當(dāng)然好辦。如果struct很復(fù)雜呢,比方說(shuō)下面這樣:

            struct
            {
               float;
               float2;
               int3;
               struct 
               {
                   float2[3];
                   float;
               };
            };

            那就會(huì)衍生出各種問題:

            • 那要不要把每個(gè)域都展平呢?
            • 展平到什么程度?
            • 讓每個(gè)Builtin Type Member相鄰,還是讓每個(gè)Float/Int相鄰?
            • 那遇到動(dòng)態(tài)尋址,怎么辦?
            • 展平后的代碼,與VS中的代碼能通用嗎?

            每個(gè)方案都一定能完成,每個(gè)方案都有明顯的缺陷。最初我是想嘗試四個(gè)像素完全獨(dú)立的辦法,這樣實(shí)現(xiàn)起來(lái)最方便。但是出于對(duì)性能的追求,我又想做展平的。展平的方案做到一半,發(fā)現(xiàn)太復(fù)雜了。

            坑神II:LLVM

            此外,還有幾個(gè)非常嚴(yán)重的問題,發(fā)生在LLVM上。

            一個(gè)是ABI。一個(gè)符合C Calling Convention的LLVM函數(shù),它對(duì)堆棧的理解與VS完全不同,特別是參數(shù)傳入或者返回Struct的時(shí)候。這樣,直接用LLVM的函數(shù)Export出來(lái)后,讓VC去Call它就一定會(huì)失敗。為了解決它,我花了近兩周的時(shí)間,設(shè)計(jì)了一個(gè)Proxy,讓函數(shù)避免用Struct來(lái)傳遞,一切數(shù)據(jù),除了和寄存器同樣大小的float和int,其余數(shù)據(jù)都通過指針來(lái)做。同時(shí),我需要將一些函數(shù)注入到LLVM中,比方說(shuō)紋理采樣,此時(shí)ABI同樣是個(gè)禍患。為了讓Code Gen正確的識(shí)別函數(shù)是LLVM的調(diào)用協(xié)議還是我自己定制的調(diào)用協(xié)議,并產(chǎn)生正確的代碼。我做了各種奇葩和傻逼的方案。有一些方案被廢棄了,但是主要的Idea,仍然沿用到現(xiàn)在。

            一個(gè)是臨時(shí)變量(包括Spiller)的對(duì)齊。在Linux/GCC上,棧頂和棧基指針一定是16字節(jié)對(duì)齊的。如果編譯器需要分配一個(gè)臨時(shí)變量,那么它只要通過ESP - 0x10*n就能獲得一個(gè)對(duì)齊的地址。但是在VC中,x86下完全沒有這樣的限制(除非函數(shù)中使用了__m128,這個(gè)時(shí)候在進(jìn)入Frame之后會(huì)有一個(gè)SUB/AND的指令把棧頂搞到16字節(jié)對(duì)齊。)。但LLVM生成的所有代碼,又是基于GCC的假設(shè)。SALVIA生成的局部變量,還可以控制地址,但是對(duì)于編譯器臨時(shí)生成的變量來(lái)說(shuō),就完全不可控了。在3.1之后因?yàn)橐肓薃VX,需要32字節(jié)對(duì)齊,這個(gè)問題就更加變本加厲了。在x86上,我還可以通過嵌入?yún)R編,來(lái)強(qiáng)制調(diào)整棧幀。但是在x64上,又啟動(dòng)了AVX的情況下,我就徹底沒有辦法了。這個(gè)問題一直延續(xù)到現(xiàn)在,如果我不動(dòng)手去Debug LLVM的話,就只能等他們什么時(shí)候想起來(lái)修復(fù)這個(gè)問題了。

            SIMD執(zhí)行模型下分支的處理

            Pixel Shader的執(zhí)行模型是SIMD的,這要求每個(gè)像素上同一時(shí)刻都執(zhí)行相同的指令。如果沒有分支,那自然是簡(jiǎn)單無(wú)比。一旦有了分支就打破了這個(gè)約定。在DX9.0b及之前,這當(dāng)然沒問題。

            但是Shader Model 3.0正式支持Dynamic Branch開始,這個(gè)問題就凸現(xiàn)出來(lái)了:分支要怎么處理?

            對(duì)于Pixel Shader來(lái)說(shuō),會(huì)面臨三種分支:靜態(tài)分支,準(zhǔn)靜態(tài)分支(這個(gè)名字是我瞎起的)和動(dòng)態(tài)分支。

            float branches( uniform float udata, float vdata: POSITION): COLOR0
            {
               const float zero = 0.0;
               if(zero < 1.0)
               {
                 // Static branch
               }
            
               if(udata)
               {
                  // Semi-Static Branch (我自己造的)
               }
              
               if(vdata)
               {
                 // Dynamic Branch
               }
            } 

            我們來(lái)分情況討論一下:

            • 對(duì)于靜態(tài)分支來(lái)說(shuō),因?yàn)榇_定分支的是一個(gè)常量,那么顯然在編譯階段就能夠知道分支執(zhí)行與否,直接生成對(duì)應(yīng)的代碼就可以了。
            • 對(duì)于uniform作為判斷條件的分支來(lái)說(shuō),在shader編譯的時(shí)候,并不知道這個(gè)分支是否會(huì)執(zhí)行。但是呢,Uniform會(huì)在Shader執(zhí)行前設(shè)置,和代碼執(zhí)行相比,Uniform設(shè)置的比例非常低。這個(gè)時(shí)候我們可以先講代碼編譯成中間表達(dá),這個(gè)中間表達(dá)會(huì)知道一個(gè)變量是不是Uniform的。在Uniform設(shè)置好后,Shader真正執(zhí)行前,把Uniform替換成那個(gè)值,也就是把Uniform當(dāng)做常量,對(duì)Shader再編譯一次,得到真正的執(zhí)行指令。所以在指令執(zhí)行的時(shí)候,準(zhǔn)靜態(tài)分支就和靜態(tài)分支完全相同了。
            • 最后一個(gè),動(dòng)態(tài)分支。如果判斷條件就是動(dòng)態(tài)的,那沒辦法,如果要支持SM3.0,就必須要能支持它。同時(shí)對(duì)于不同的Pixel,都可能有不同的分支。這對(duì)于SIMD來(lái)說(shuō),才是真正的難題。

            實(shí)際上,我們真正要解決的,就是動(dòng)態(tài)分支。

            對(duì)于SIMD模型來(lái)說(shuō),動(dòng)態(tài)分支有三種處理辦法。

            1. 跳轉(zhuǎn)執(zhí)行。像CUDA 2.0以上那樣的指令集具備有一定的跳轉(zhuǎn)執(zhí)行能力。編譯器可以把SIMD拆開,按照標(biāo)量執(zhí)行。每個(gè)都執(zhí)行完了后,再繼續(xù)按照SIMD執(zhí)行其他的代碼。
            2. 條件執(zhí)行。這也是圖形硬件上最常見的執(zhí)行模式。通過一個(gè)位,就可以決定GPU中的執(zhí)行單元是否執(zhí)行一段代碼。舉個(gè)不準(zhǔn)確的例子,如果是個(gè)4并發(fā)的執(zhí)行器,那么四個(gè)并發(fā)執(zhí)行器的執(zhí)行條件可以設(shè)置為1100,這樣就只有前兩個(gè)單元的數(shù)據(jù)執(zhí)行,后兩個(gè)不執(zhí)行了。
            3. 寫掩碼。這個(gè)辦法是沒有辦法的辦法。它的基本理念就是:只要不寫到內(nèi)存中的執(zhí)行結(jié)果,就可以認(rèn)為它沒執(zhí)行過。但是寫掩碼總是浪費(fèi)了指令。不過好歹它還是避免了跳轉(zhuǎn)的。所以對(duì)于早期的ARM這樣沒有分支預(yù)測(cè)的精簡(jiǎn)體系來(lái)說(shuō),一旦有分支執(zhí)行起來(lái)就是死翹翹。所以它有類似于Select-Store這樣的指令,盡可能的避免分支的出現(xiàn)。

            對(duì)于SAVLIA來(lái)說(shuō),跳轉(zhuǎn)執(zhí)行和寫掩碼是兩個(gè)可能的選擇。因?yàn)閷懷诖a的代碼生成起來(lái)更加輕松一些,所以目前的SALVIA的實(shí)現(xiàn)是寫掩碼的。在x86/x64平臺(tái)上,對(duì)于AVX以上的指令,還可以用blend。但是對(duì)于其他指令而言,基本上只能是通過跳轉(zhuǎn)實(shí)現(xiàn)寫掩碼。所以這部分的開銷其實(shí)很大。等到造出了自己的SSA之后,再來(lái)考慮分支執(zhí)行的事情吧。

            對(duì)于寫掩碼的掩碼要怎么計(jì)算,一開始我心里挺沒譜的。特別是有了,Continue和Break之后,情況就會(huì)變得復(fù)雜起來(lái)。一開始我沒法確信自己的方案是正確的。后來(lái)看了MESA的Gallinum以后,看見了Continue Mask和Break Mask兩個(gè)變量,瞬間就明白了。

            具體怎么思考的不多說(shuō)了,這里寫下幾個(gè)結(jié)論:

            1. 語(yǔ)言不能有Goto(有Goto會(huì)讓代碼變得非常復(fù)雜,甚至不可解);
            2. 所需要的掩碼的數(shù)量會(huì)隨著循環(huán)的嵌套層數(shù)的增加而增加;
            3. 每個(gè)循環(huán)最多有三個(gè)掩碼:Break,Continue和Mask;
            4. 程序是固定的話,掩碼的數(shù)量就一定是個(gè)常量。(要不然硬件就沒法做了)
            5. 寫掩碼的位數(shù)只和執(zhí)行單元的數(shù)量有關(guān),和嵌套深度無(wú)關(guān)。

            坂道のTest

            盡管遇到了各種難處,但是很多方案還是順利的做出來(lái)了。方案和方案之間差異很大,要想順利移植,必須要有Test。

            之前也說(shuō)過,一開始我的Test是按照Parser,Semantic,Code Gen,JIT分開做的。但是呢,這樣一來(lái),不同Stage之間的Test復(fù)用性非常高。而且因?yàn)镾tage經(jīng)常變化,包括Stage的接口。這時(shí)候Test就完蛋了。Test本身也很枯燥(變量名都不好起),所以Test重寫起來(lái)難過的要死。

            于是我重新審視了一下需求。發(fā)現(xiàn)我最終只關(guān)心JIT編譯出來(lái)的函數(shù)的運(yùn)行結(jié)果,其實(shí)并不關(guān)心中間的過程。而且隨著我對(duì)編譯過程理解的逐步變化,Compiler Stages幾乎每隔兩個(gè)月就要進(jìn)行比較大的修正。測(cè)試的量稍微大一點(diǎn),就沒有辦法維護(hù)Test Case了。并且,對(duì)于單條語(yǔ)句或者非常短的函數(shù)來(lái)說(shuō),從詞法到最終JIT出來(lái)的函數(shù)所覆蓋的編譯器代碼非常之少,可能3-4個(gè)函數(shù),代碼就出來(lái)了。即便有問題,對(duì)比過去的版本輕松就能分析出來(lái)。再加上大量的Assertion,診斷起來(lái)更加容易。

            因此,在這幾個(gè)月中我完全重寫了Test Case:讓JIT的測(cè)試粒度更低,測(cè)試更豐富;取消所有的中間Level的測(cè)試。新的測(cè)試回歸起來(lái)非常容易,出了問題也很好找到。在Test Case寫完后,正好看到Martin Fowler噴過度TDD的問題,真是感同身受。

            測(cè)試需要嗎?當(dāng)然需要。但是選擇合適的Level,做合適的測(cè)試是非常重要的。結(jié)合之前實(shí)習(xí)的時(shí)候的Unit Test經(jīng)驗(yàn),有以下幾點(diǎn)感受:

            1. 測(cè)試一定要選擇盡可能低的面,這樣牽涉的代碼就盡可能少;
            2. 在縱向上,粒度要細(xì)。除了單個(gè)API的Test,還要有適度的交叉,不過太綜合的測(cè)試,請(qǐng)讓集成測(cè)試用例來(lái)完成;
            3. 要重視代碼覆蓋率;
            4. 測(cè)試面向的API要穩(wěn)定。天天變得API會(huì)讓你徹底失去寫Test的信心。API越穩(wěn)定,在它上面出現(xiàn)問題的機(jī)會(huì)就越多,你寫的測(cè)試性價(jià)比也越高。

            坡長(zhǎng)路遠(yuǎn),小步快走

            在完成了Test的改造后,終于有了一個(gè)合適的發(fā)布前評(píng)估。所以到了11年11月后,發(fā)布的速度就明顯變快了許多。快速的發(fā)布對(duì)于做一個(gè)長(zhǎng)期項(xiàng)目來(lái)說(shuō)非常重要。這也和敏捷的想法不謀而合。不管是從品質(zhì)控制上、還是進(jìn)度追蹤上,或者是說(shuō)對(duì)開發(fā)者自信心的增強(qiáng),都需要有短平快的開發(fā)周期。11年也正好是Autodesk推行敏捷的一年。同事里面有很多的人反應(yīng)說(shuō)敏捷會(huì)導(dǎo)致軟件品質(zhì)的下降,短期目標(biāo)會(huì)導(dǎo)致過于追逐眼前利益。

            但是從我的經(jīng)驗(yàn)來(lái)看,對(duì)于個(gè)人,敏捷要短平快。但對(duì)于團(tuán)隊(duì),敏捷要從長(zhǎng)計(jì)議。不是所有的iteration都需要開發(fā)新特性,必須要保留足夠的iteration來(lái)完成重構(gòu)、整理、設(shè)計(jì)方案的反省和討論。對(duì)于以年為單位的長(zhǎng)周期產(chǎn)品來(lái)說(shuō),可以每個(gè)季度有3-5天的時(shí)間,每個(gè)人都提出對(duì)框架的改進(jìn)計(jì)劃;每年有兩周的時(shí)間,完成框架的重構(gòu)和修正。更小的重構(gòu),可以安排的更加短小的時(shí)間。

            6. 2012年1月及以后:現(xiàn)在與未來(lái)

            新特性,新思考

            從11年7月份開始到現(xiàn)在,就一直在做Demo、優(yōu)化、特性的完善;以及一些新特性的思考。

            總的來(lái)說(shuō),這一年半的時(shí)間里面,很多工作已經(jīng)不像早先幾年做的那么吃力,但是仍然在很多的點(diǎn)上有所斬獲。

            • 整個(gè)編譯器后端,包括基本的分析和優(yōu)化都已經(jīng)有所了解,LLVM也熟悉了許多;
            • 對(duì)Shader相關(guān)的API的了解也不再懵懵懂懂;
            • 對(duì)于語(yǔ)言機(jī)制的研究,加上陳漢子時(shí)不時(shí)拋來(lái)的一些思維發(fā)散題令我對(duì)語(yǔ)言有了更深入的認(rèn)識(shí);
            • 認(rèn)識(shí)了RFX,在短短幾周就幫助我在閱讀V8和LLVM時(shí)積累的一些知識(shí)轉(zhuǎn)化成了有用的理解。

            在2012年底為SALVIA進(jìn)行了局部的重新設(shè)計(jì),也是“學(xué)”與“習(xí)”的新一輪“習(xí)”。新的SSA及Shader優(yōu)化、JIT化的管線、對(duì)性能有要求的新前端、瞄準(zhǔn)DX11以上Shader Model Features、JIT的調(diào)試符號(hào),這些一定會(huì)給我?guī)?lái)許多絞盡腦汁想不明白的問題,但同時(shí)我也會(huì)學(xué)習(xí)到、實(shí)踐到許多新的知識(shí)。

            我相信時(shí)間會(huì)教給我們一切。

            posted @ 2013-01-13 05:00 空明流轉(zhuǎn) 閱讀(6070) | 評(píng)論 (12)編輯 收藏

            2012年3月8日 #

            1. Diagnostic需要提供哪些數(shù)據(jù)

            出錯(cuò)處理和錯(cuò)誤提示,是編譯器開發(fā)過程中重要而繁瑣的部分。

            診斷信息的格式因編譯器和IDE而不同。

            SALVIA將采用Visual Studio的格式,即 文件 + 行列 + 類別(等級(jí)) + 編號(hào) + 出錯(cuò)信息。例如:

            d:\programming\salvia\sasl\test\cgllvm_test\function_test_basic.cpp(16): error C2061: syntax error : identifier 'te'

            因此在出錯(cuò)分析的時(shí)候,也需要提供如上的一些信息。


            2. 診斷信息Diagnostic Item

            在以上信息中,文件名和行列號(hào)可以在詞法分析的時(shí)候獲得,我們將它作為屬性附加在Token中。

            類別和編號(hào),對(duì)于同一個(gè)編譯器而言是相對(duì)固定的,盡管我們可以用ID來(lái)表示,但是它并不直觀,編譯器檢查也較少。與參數(shù)匹配時(shí),也比較容易出錯(cuò)。

            SASL中的診斷信息將每個(gè)錯(cuò)誤都使用一個(gè)類型來(lái)表達(dá):

            class diagnostic_item
            {
            };
            
            
            class unrecognized_identifier: public diagnostic_item
            {
            public:
                unrecognized_identifier& token( token_t tok );
                
            private:
                static int level;
                static int id;
                static std::string description_template;
                
            private:
                std::string ident;
                size_t      row, col;
                // Other properties
            };

            這樣的好處在于可以用Combinator的風(fēng)格來(lái)撰寫錯(cuò)誤信息。例如這樣:

            diagnostic_chat.report<unrecognized_identifier>().token( err_tok );

            并且由于編譯器的保證也比較不容易寫錯(cuò)。

             

            但是這種寫法也有一個(gè)很關(guān)鍵的問題,需要為每個(gè)錯(cuò)誤都定義一個(gè)類,工作量很大。SASL對(duì)這一問題的處理,自然是傳統(tǒng)的大殺器:運(yùn)用腳本進(jìn)行生成。

            Clang使用了它內(nèi)置的代碼生成工具td來(lái)完成生成的工作。

             

            3. 診斷信息管理器Diagnostic Chat

            Chat是診斷信息的管理工具。它主要要完成以下需求:添加和清理診斷信息,以及在診斷信息的添加清理時(shí)提供回調(diào)操作。

            后者是很有用的,尤其是在調(diào)試編譯器的時(shí)候。你得分清楚究竟是真正的程序錯(cuò)誤呢,還是編譯器出了錯(cuò)。

            Diagnostic Chat的原型如下:

            class diagnostic_chat
            {
            public:
                template <typename T> T& report();
                void add_report_diagnostic_handler( DiagnosticHandlerT handler );
            };

            同時(shí),我們也將Treat Warning As Error,Error Count,Disable Warning,Stop compiling when error occurs等狀態(tài)和功能所需要的支持添加到Chat中。

            所以,Chat除了提供管理之外,也要具有相應(yīng)的診斷信息的統(tǒng)計(jì)功能。

             

            4. 過濾器Diagnostic Filter

            Filter主要配合IDE使用,從Chat中取出符合條件的診斷信息。Error Count和Disable Warnings等功能也可以通過它來(lái)完成。

             

            5. Formatter

            Formatter用于將DiagnosticItems中的信息轉(zhuǎn)換成人可讀的字符串。目前SASL只打算支持Visual Studio的格式,但是相信支持GCC的格式以更好的和Eclipse等第三方IDE集成并不困難。

            在C#里面,我們可以用“We need ‘{0}’ not ‘{1}’.”這樣的方式來(lái)分離description template并延期的產(chǎn)生格式化的字符串。但是在C++中,這種做法并不容易。C的sprintf很難具有延期、漸增的綁定模板的特定,對(duì)自定義類型的字符串化的支持也不足,類型安全也比較差;而stream的話,也會(huì)面臨著將好端端的格式化字符串割裂的問題。SASL使用了boost.format,從一定程度上搞定了這兩個(gè)問題,從而像C#一樣,使用格式化字符串的功能。

            posted @ 2012-03-08 21:25 空明流轉(zhuǎn) 閱讀(2045) | 評(píng)論 (0)編輯 收藏

            2012年3月6日 #

            SALVIA 0.3 出爐了!

            這是自2010年8月份以來(lái), SALVIA Milestone 1.0之后最重要的發(fā)布!

            0.3開始,SALVIA正式支持了Pixel Shader。

            自2008年中開始的Shader設(shè)計(jì)與實(shí)現(xiàn)工作基本完成。

            未來(lái)一年內(nèi),SALVIA的工作將集中以下幾點(diǎn):

            1. 在Shader編譯器的完善上,比如友善的語(yǔ)法和語(yǔ)義錯(cuò)誤提示。
            2. 提升與管線的集成度,進(jìn)而充分提升性能。
            3. 編譯器和編譯器生成代碼的優(yōu)化,提高Shader的編譯速度和運(yùn)行速度。
            4. 新的圖形特性,如各向異性過濾等。

            隨著SALVIA整體的逐漸成熟,我們也希望有其他的朋友能來(lái)參與和支持這個(gè)項(xiàng)目,一同進(jìn)步。

            如果您希望參與到這個(gè)項(xiàng)目中,請(qǐng)mail聯(lián)系我:wuye9036 __at__ gmail dota com.

             

            tex2D

            posted @ 2012-03-06 17:41 空明流轉(zhuǎn) 閱讀(1931) | 評(píng)論 (0)編輯 收藏

            2012年2月24日 #

            SALVIA 0.2.5 發(fā)布!

            項(xiàng)目主頁(yè): http://code.google.com/p/softart/

            更新列表如下:


            0.2.2 - 0.2.5 (Feb 24, 2012)

            • 版本名稱發(fā)生變化,從原先的Milestone X fix Y的命名方式正式更新為與主版本相同的版本號(hào)序列。
            • 取消了對(duì)DirectX的強(qiáng)制依賴。
            • 添加了對(duì)Visual C++ Express的支持
            • Pixel Shader 進(jìn)一步增強(qiáng):
                      1. 提供了對(duì)分支語(yǔ)句 if 的支持  
                      2. 支持 for, while, do-while 循環(huán)
                      3. 添加了新的內(nèi)建函數(shù):ddx ddy dot cross sqrt
                      4. 添加了紋理取樣函數(shù)tex2D的原型
            • 對(duì)Rasterizer進(jìn)行了進(jìn)一步的優(yōu)化
            • 修正了以下問題:
                      1. 在x86上執(zhí)行vertex shader時(shí)可能會(huì)Crash的問題
                      2. 不能再build配置文件中指定CMake路徑的問題
                      3. 一些表達(dá)式調(diào)用時(shí)報(bào)告函數(shù)重載錯(cuò)誤的問題
            posted @ 2012-02-24 16:56 空明流轉(zhuǎn) 閱讀(2560) | 評(píng)論 (1)編輯 收藏

            2011年12月26日 #

            SALVIA在Milestone 1.2 Fix 2中,正式提供了一站式編譯腳本的支持。

            項(xiàng)目主頁(yè):http://code.google.com/p/softart/

            新的編譯步驟如下:

            1. 下載最新的CMake并安裝。

            2. 下載Python 2.7并安裝。

            3. 下載boost 1.44或更新的版本,解壓到某個(gè)目錄下。

            4. Clone或下載SALVIA代碼包,執(zhí)行根目錄下的build_all.py文件。第一次運(yùn)行的時(shí)候會(huì)生成一個(gè)project.py,編輯project.py設(shè)置相應(yīng)屬性,包括boost代碼目錄,編譯器,configuration等。

            5. 再次運(yùn)行build_all.py,編譯程序。

            posted @ 2011-12-26 21:09 空明流轉(zhuǎn) 閱讀(2615) | 評(píng)論 (5)編輯 收藏

            2011年11月22日 #

            1. LLVM在x86和x64下都和Microsft C++ ABI的吻合程度不夠。目前已知在以下情況下會(huì)出錯(cuò):
            • 參數(shù)為結(jié)構(gòu)體的
            • 返回值為結(jié)構(gòu)體
            以下情況我沒有完整測(cè)試過:
            • 返回值為單個(gè)浮點(diǎn)
            • 返回值為向量(_m128 / <4 x float>)
            • 參數(shù)為向量(_m128 / <4xfloat>)
            所以建議大家統(tǒng)一將是結(jié)構(gòu)體的返回值和參數(shù)以引用/指針的形式傳遞。
            對(duì)于大小為4個(gè)或者8個(gè)字節(jié)的結(jié)構(gòu)體如果希望按值傳遞,那么需要在LLVM函數(shù)的簽名上使用i32/i64作為參數(shù)類型,并使用bit cast在函數(shù)體內(nèi)強(qiáng)制轉(zhuǎn)換成結(jié)構(gòu)體。

            2. LLVM提供了很多的Intrinsics,例如SSE指令集。它在Module上提供了一個(gè)getOrCreateTargetIntrinsic,但實(shí)際上這個(gè)函數(shù)是坑爹的。有兩個(gè)方法可以正確的創(chuàng)建并獲取指令集:
            • 使用Module::getOrInsertFunction( intrinsic_name, intrinsic_function_type )。它會(huì)自動(dòng)識(shí)別intrinsic的名稱并創(chuàng)建function或者是intrinsic。指令需要使用全名。例如 llvm.x86.sse.sqrt.ps.
            • 或者使用Ilvm::Intrinsic::getDeclaration( id ) 來(lái)創(chuàng)建。這個(gè)id可以在intrinsics.gen中找到。
            因?yàn)長(zhǎng)LVM生成的Intrinsic是全平臺(tái)的,所以可以在x86上指定ARM匯編的生成,反之亦然。

            3. 默認(rèn)情況下,LLVM的JIT是不會(huì)啟用InliningPass的,Optimization Level指定為Aggressive也不會(huì)。這意味著inlinehint和alwaysinline都是失效的。如果需要inlining得自己修改JIT的源代碼。

            4. UndefValue是個(gè)好東西。這個(gè)常量可以使生成的匯編少一條初始化指令。比方說(shuō)用0初始化,可能對(duì)應(yīng)的匯編就是 xor reg, reg。如果用了Undef,那這條指令就沒了。

            5.
            TypeBuilder很好用,只是不能生成struct等復(fù)雜的類型。不過你可以對(duì)它做一些修改以讓它支持struct和vector。這個(gè)時(shí)候Boost.MPL就能派上用場(chǎng)了。不過要當(dāng)心MPL帶來(lái)漫長(zhǎng)的編譯時(shí)間。
            posted @ 2011-11-22 21:04 空明流轉(zhuǎn) 閱讀(2918) | 評(píng)論 (0)編輯 收藏

            2011年8月17日 #

            在設(shè)計(jì)一門語(yǔ)言與其他語(yǔ)言交互的API與ABI(Application Binary Interface,二進(jìn)制接口)時(shí),調(diào)用協(xié)議和內(nèi)存對(duì)齊是兩個(gè)無(wú)從回避的問題。

            本文將討論如何在LLVM上生成正確的內(nèi)存對(duì)齊和調(diào)用協(xié)議的代碼。

            在這里為了方便和標(biāo)準(zhǔn)起見,假定應(yīng)用LLVM的語(yǔ)言的Extending和Embedding的對(duì)象都是C。

            調(diào)用協(xié)議

            先來(lái)討論調(diào)用協(xié)議。調(diào)用協(xié)議用于保證調(diào)用方和被調(diào)用方在二進(jìn)制/匯編一級(jí)上是相容的。合適的調(diào)用協(xié)議可以幫助構(gòu)造出以下代碼:

            // Callee Signature of LLVM code
            void __cdecl foo( int a, float b, float4 c);
            
            // C caller
            typedef void (__cdecl* fn_ptr)(int, float, float4)
            fn_ptr p = static_cast<fn_ptr>( get_jit_function("foo") );
            p(1, 1.0, vec);
            

            一般來(lái)說(shuō)調(diào)用協(xié)議包括參數(shù)傳遞和返回值傳遞和堆棧平衡三個(gè)部分。在x86平臺(tái)上的C/C++編譯器中常見的調(diào)用協(xié)議有cdecl, fastcall和stdcall。具體的協(xié)議內(nèi)容請(qǐng)參見MSDN。

            在C++中還有一類特殊的調(diào)用協(xié)議thiscall,用于調(diào)用對(duì)象的成員函數(shù)。但是這一類調(diào)用協(xié)議不同的平臺(tái),不同的編譯器實(shí)現(xiàn)皆有不同,既無(wú)書面標(biāo)準(zhǔn),也無(wú)事實(shí)標(biāo)準(zhǔn),再加上virtual call等復(fù)雜的情況存在,并不適合用于做跨語(yǔ)言的調(diào)用。

            對(duì)于x64平臺(tái)而言,在windows下和linux下分別有兩種調(diào)用協(xié)議。

            先來(lái)看x86。由于x86在cdecl和fastcall上是有著跨平臺(tái)的標(biāo)準(zhǔn)的,因此LLVM對(duì)它的支持是比較完整的。程序只要在創(chuàng)建Function的時(shí)候指定Call Convention即可。

            但是對(duì)于x64,LLVM的支持便不是那么完善。以windows為例,windows的x64調(diào)用協(xié)議要求以rcx,rdx,r8,r9寄存器傳遞前四個(gè)不大于64bit的參數(shù),其余參數(shù)放在棧上。如果參數(shù)大于64bit,則要求傳遞它的指針。浮點(diǎn)使用xmm0-3來(lái)傳遞。但是對(duì)于LLVM而言,一旦參數(shù)大于64bit,它便會(huì)將整個(gè)對(duì)象而不是指針壓到棧上傳遞。因此在遇到x64時(shí),需要小心處理API部分的調(diào)用協(xié)議。

            在這里,我們需要將所有超過64bit的結(jié)構(gòu)體處理成指針(或者拷貝后處理成指針)傳遞。

            同時(shí),LLVM提供了readonly和byval兩個(gè)參數(shù)屬性(Attribute)來(lái)確保參數(shù)的值語(yǔ)義。前者意味著傳入的指針?biāo)赶虻闹凳遣槐恍薷牡模愃朴赥 const*),而后者會(huì)對(duì)傳入的指針做一份內(nèi)存拷貝,確保寫值不被傳遞出函數(shù)(類似于值拷貝)。這樣,LLVM生成的函數(shù)便可以MSVC生成的x64代碼正確調(diào)用了。

            內(nèi)存對(duì)齊

            與移動(dòng)平臺(tái)的體系結(jié)構(gòu)相比,x86對(duì)內(nèi)存對(duì)齊的條件算是相當(dāng)寬松的了。大部分的指令對(duì)內(nèi)存對(duì)齊基本上是沒有特殊要求的。只有一些SIMD的指令會(huì)對(duì)內(nèi)存對(duì)齊有所限定,例如movaps。

            為了方便后端生成SIMD代碼,LLVM提供了vector類型,例如vector<float, 1>。在代碼生成的時(shí)候,vector會(huì)編譯成最有可能的SIMD類型。因此在x86平臺(tái)上,vector<float, 1-4>都被處理成類似于__m128的類型,更長(zhǎng)的vector則被拆分成多個(gè)__m128類型。

            這實(shí)際上意味著,所有的vector都應(yīng)該遵循16Bytes對(duì)齊的原則。

            考慮到我們的需求,類似于struct{ float[3]; }這樣的結(jié)構(gòu),如果能表示為vector<float, 3>顯然適合一些數(shù)學(xué)運(yùn)算,例如shuffle,逐元素的add,sub,mul,同時(shí)LLVM指令的選擇也更加靈活。但是顯然,這個(gè)結(jié)構(gòu)體有兩個(gè)條件是不滿足的:16字節(jié)對(duì)齊和16字節(jié)的大小(movups和movaps都是一次取16字節(jié))。這會(huì)造成邊界下讀寫的內(nèi)存越界。因此非常可惜,這些數(shù)據(jù)必須表示為struct{ float ,float, float }。在讀取的時(shí)候,也會(huì)生成正確的指令:movss。

            那么,對(duì)于一般的非對(duì)齊的vec4應(yīng)用vector<float,4>行不行呢?

            答案是,很困難。對(duì)于LLVM而言,他們?cè)谠O(shè)計(jì)的時(shí)候就沒有過多的考慮vector在非對(duì)齊時(shí)候的應(yīng)用。盡管load和store都能夠指定alignment以生成非對(duì)齊的內(nèi)存操作(例如movups)并且確實(shí)會(huì)起效,但是由于代碼優(yōu)化、臨時(shí)存取等特性的存在,導(dǎo)致一些非load和store的內(nèi)存操作仍然是要求對(duì)齊的(例如生成了addaps xmm, [addr])。此時(shí)仍然有可能為非對(duì)齊的數(shù)據(jù)生成了內(nèi)存對(duì)齊的指令。

            因此綜合權(quán)衡,SASL在API界面上使用了struct{float x,y,z,w;} 這樣的ABI來(lái)表示數(shù)據(jù),在代碼生成時(shí),會(huì)首先將struct的數(shù)據(jù)轉(zhuǎn)換成vector,然后再執(zhí)行其它的操作,兼顧ABI與SIMD;同時(shí)對(duì)于Intrinsic,由于并不暴露給Host,所以它們?nèi)匀槐M可能使用Vector,便于LLVM進(jìn)行優(yōu)化。

            posted @ 2011-08-17 13:58 空明流轉(zhuǎn) 閱讀(3435) | 評(píng)論 (3)編輯 收藏

            2011年7月17日 #

            項(xiàng)目主頁(yè):

            源碼下載地址:

            版本:
            • Milestone 1.1 Fix 1
            更新記錄:
            • 添加了了新的Demo:Sponza
            • Wavefront Obj添加了32位索引的支持。
            • 修復(fù)了 Wavefront Obj 錯(cuò)誤的頂點(diǎn)共享的問題。
            • 修復(fù)了 Mip-map 計(jì)算錯(cuò)誤的問題。
            • 修復(fù)了對(duì)多邊形錯(cuò)誤剔除的問題。
            Demo下載:

            Demo截圖

            image

            image

            image

            posted @ 2011-07-17 17:46 空明流轉(zhuǎn) 閱讀(3097) | 評(píng)論 (0)編輯 收藏

            僅列出標(biāo)題  下一頁(yè)
            久久精品国产亚洲7777| 久久久久久亚洲精品成人| 久久久久AV综合网成人| 亚洲色婷婷综合久久| 久久这里只有精品首页| 婷婷久久综合九色综合绿巨人| 久久国产成人午夜aⅴ影院| 久久99免费视频| 久久精品国产69国产精品亚洲| 久久国产乱子伦精品免费强| 国产V综合V亚洲欧美久久| 国产欧美久久一区二区| 精品国产福利久久久| 国产精品日韩深夜福利久久 | 热久久这里只有精品| 国产欧美久久一区二区| 国产精品亚洲美女久久久| 久久91这里精品国产2020| 久久人人爽人人爽AV片| 欧美国产成人久久精品| 久久久无码精品亚洲日韩按摩| 国产精品一久久香蕉国产线看观看| 国产精品国色综合久久| 中文字幕一区二区三区久久网站| 久久久久国产| 97久久国产综合精品女不卡| 久久精品aⅴ无码中文字字幕不卡| 99久久精品毛片免费播放| 久久影视综合亚洲| 国内精品久久久人妻中文字幕| 青青草国产精品久久久久| 欧美亚洲国产精品久久高清| 国产亚洲精品美女久久久| 久久精品国产只有精品66| 欧美va久久久噜噜噜久久| 国产综合免费精品久久久| 久久久久亚洲AV无码永不| 伊人色综合久久天天网| 99久久精品国产综合一区| 亚洲AV无码久久精品成人| 日本久久久久久久久久|