青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

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

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

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

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

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

為什么要有Shader Compiler

如果是固定管線(xiàn)的話(huà),那么SALVIA做到這些特性也就足夠了。但是從SALVIA一開(kāi)始,我就希望讓它成為一個(gè)Pure Shader的管線(xiàn),固定管線(xiàn)的那些狀態(tài)實(shí)在太煩人了。本來(lái)Cpp實(shí)現(xiàn)的Shading language能滿(mǎn)足絕大部分的需要了,但是有一個(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問(wèn)題,起碼有兩種方案:
    • 自己搞一個(gè)Fiber Manager,直接控制代碼的棧的Switch。每個(gè)pixel都有一個(gè)Fiber,到了DDX/DDY就換到下一個(gè)Fiber執(zhí)行,直到所有的Fiber都執(zhí)行完畢后,計(jì)算ddx,寫(xiě)入棧變量,再繼續(xù)執(zhí)行;
    • 直接用線(xiàn)程,Join,計(jì)算,然后繼續(xù)執(zhí)行。
  2. 對(duì)于棧變量的地址問(wèn)題,也有辦法:
    • 在切換線(xiàn)程的時(shí)候直接保存臨時(shí)變量的地址。

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

所以我最終橫下一條心:要為它做Shading Language Compiler。然后開(kāi)始了漫長(zhǎng)的Compiler開(kāi)發(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月份開(kāi)始設(shè)計(jì)Shader的EBNF。設(shè)計(jì)語(yǔ)言,不外乎是三個(gè)方面:應(yīng)用場(chǎng)景、語(yǔ)法和庫(kù)的支持。盡管有現(xiàn)成的HLSL和GLSL作參考,但對(duì)于我從0開(kāi)始設(shè)計(jì)語(yǔ)言來(lái)說(shuō),這些語(yǔ)言的語(yǔ)法和語(yǔ)義都過(guò)于復(fù)雜了。我需要讓語(yǔ)言特性慢慢的添加進(jìn)來(lái)。

考慮到HLSL和C比較接近,C的文法參考資料又很多,于是我選擇了從C開(kāi)始裁剪語(yǔ)法。但是文法這個(gè)東西,并不簡(jiǎn)簡(jiǎn)單單是樹(shù)狀的結(jié)構(gòu),樹(shù)上的任何一個(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寫(xiě)出來(lái),再稍微規(guī)范一下,一門(mén)不那么復(fù)雜的語(yǔ)言就成了。當(dāng)然像C++這種變態(tài)語(yǔ)言,這樣做是做不出來(lái)的。但當(dāng)時(shí)我顯然不具備那樣的能力。從七月份開(kāi)始就磕磕絆絆地裁剪了一些語(yǔ)法特性之后的語(yǔ)言,到了八月份才出了個(gè)千瘡百孔的方案。

神:Boost.Spirit

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

Boost.Spirit是個(gè)很奇葩的東西。它想在C++里面提供一個(gè)類(lèi)似于EBNF、可以定義語(yǔ)法分析規(guī)則的方言。要讓C++看起來(lái)像一個(gè)方言,當(dāng)然是要使用神出鬼沒(méi)的操作符重載。當(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)完全滿(mǎn)足一個(gè)DSL的要求了:直觀的面向解決方案。

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

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

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

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

后端與前端

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

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

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

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

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

可沒(méi)想著就這么掉坑里面去了。

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

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

主體大人真是神,五個(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è)開(kāi)發(fā)進(jìn)度變得龜速。

所以整個(gè)一年中,SALVIA的開(kāi)發(fā)就是寫(xiě)寫(xiě)停停,停停寫(xiě)寫(xiě)。可以說(shuō)08年初的銳氣,已經(jīng)消磨的差不多了。到了8月份的時(shí)候,我畢業(yè)了,新工作也基本上確定和熟悉了,我就和minmin說(shuō),從現(xiàn)在開(kāi)始我寫(xiě)半年報(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)開(kāi)始慢慢的醞釀和發(fā)酵,敏捷也逐漸成為了我開(kāi)發(fā)過(guò)程中的主要指南。

基本上,那個(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的硬盤(pán);
  • Mangling name輕松超過(guò)4K字符的限制;
  • 輕易撐爆obj文件的symbol table,需要用/bigobj才能夠編譯通過(guò);
  • 甚至在編譯的時(shí)候會(huì)輕易的讓32位的MSVC CL out of memory。

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

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

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

Shader的階段性成果

到了四月份的時(shí)候,Shading Language Semantic/System Value已經(jīng)在語(yǔ)法上支持了,語(yǔ)義上也能分析出哪些變量是System Value,哪些變量是Uniform的。并且通過(guò)生成特殊的函數(shù)簽名,Shader滿(mǎn)足了以下幾個(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全線(xiàn)貫通。雖然很多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也有了,我在部門(mén)內(nèi)部做的一些Introduction也幫助我梳理了思路。于是從4月份開(kāi)始,我就籌備著要把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ā)布后,我開(kāi)始做Pixel Shader的特性。本以為半年之內(nèi)就能搞定,發(fā)個(gè)1.0揚(yáng)眉吐氣一下。但是實(shí)踐證明,我真是他媽的太盲目樂(lè)觀了。

我先來(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ō)過(guò),我要讓指令看起來(lái)是四個(gè)像素同一時(shí)刻執(zhí)行的,那么顯然我生成的代碼就會(huì)類(lèi)似于這樣:

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)化與問(wèn)題

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

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

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

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

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

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

  • 那要不要把每個(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)重的問(wè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ù)都通過(guò)指針來(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í)變量,那么它只要通過(guò)ESP - 0x10*n就能獲得一個(gè)對(duì)齊的地址。但是在VC中,x86下完全沒(méi)有這樣的限制(除非函數(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è)問(wèn)題就更加變本加厲了。在x86上,我還可以通過(guò)嵌入?yún)R編,來(lái)強(qiáng)制調(diào)整棧幀。但是在x64上,又啟動(dòng)了AVX的情況下,我就徹底沒(méi)有辦法了。這個(gè)問(wèn)題一直延續(xù)到現(xiàn)在,如果我不動(dòng)手去Debug LLVM的話(huà),就只能等他們什么時(shí)候想起來(lái)修復(fù)這個(gè)問(wèn)題了。

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

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

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

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

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

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

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

坂道のTest

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

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

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

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

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

  1. 測(cè)試一定要選擇盡可能低的面,這樣牽涉的代碼就盡可能少;
  2. 在縱向上,粒度要細(xì)。除了單個(gè)API的Test,還要有適度的交叉,不過(guò)太綜合的測(cè)試,請(qǐng)讓集成測(cè)試用例來(lái)完成;
  3. 要重視代碼覆蓋率;
  4. 測(cè)試面向的API要穩(wěn)定。天天變得API會(huì)讓你徹底失去寫(xiě)Test的信心。API越穩(wěn)定,在它上面出現(xiàn)問(wèn)題的機(jī)會(huì)就越多,你寫(xiě)的測(cè)試性?xún)r(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ì)開(kāi)發(fā)者自信心的增強(qiáng),都需要有短平快的開(kāi)發(fā)周期。11年也正好是Autodesk推行敏捷的一年。同事里面有很多的人反應(yīng)說(shuō)敏捷會(huì)導(dǎo)致軟件品質(zhì)的下降,短期目標(biāo)會(huì)導(dǎo)致過(guò)于追逐眼前利益。

但是從我的經(jīng)驗(yàn)來(lái)看,對(duì)于個(gè)人,敏捷要短平快。但對(duì)于團(tuán)隊(duì),敏捷要從長(zhǎng)計(jì)議。不是所有的iteration都需要開(kāi)發(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月份開(kāi)始到現(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化的管線(xiàn)、對(duì)性能有要求的新前端、瞄準(zhǔn)DX11以上Shader Model Features、JIT的調(diào)試符號(hào),這些一定會(huì)給我?guī)?lái)許多絞盡腦汁想不明白的問(wèn)題,但同時(shí)我也會(huì)學(xué)習(xí)到、實(shí)踐到許多新的知識(shí)。

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

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

評(píng)論

# re: 開(kāi)源光柵化渲染器SALVIA的漫長(zhǎng)五年(準(zhǔn)&middot;干貨) 2013-01-13 05:12 Angel.Cheung
坐個(gè)沙發(fā)~~顯然完全看不懂~~  回復(fù)  更多評(píng)論
  

# re: 開(kāi)源光柵化渲染器SALVIA的漫長(zhǎng)五年(準(zhǔn)&middot;干貨) 2013-01-13 09:18 Scan
在最初卡ddx問(wèn)題的時(shí)候,比起fiber-manager方案,可否考慮從這個(gè)方向入手:

ps的輸入都是c++的PSVector<n>類(lèi)型,至于PSXXX<n>相對(duì)XXX<n>的區(qū)別在于,前者包含了4個(gè)XXX<n>,在之后進(jìn)行各種計(jì)算的時(shí)候,都是對(duì)4個(gè)XXX<n>同時(shí)進(jìn)行的,類(lèi)似于XXX<n>和它內(nèi)部的每個(gè)分量的關(guān)系。于是用c++編寫(xiě)的靜態(tài)ps中的計(jì)算都是基于PSXXX<n>的,ddx也就是在PSXXX<n>內(nèi)部各個(gè)分量之間交互。這種寫(xiě)法c++編譯器能自動(dòng)進(jìn)行sse優(yōu)化。
當(dāng)然,這遇上了后面的動(dòng)態(tài)分支必然會(huì)是個(gè)死。

空明巨巨的經(jīng)歷實(shí)在是很贊的,并且居然cpu的shader涉及那么復(fù)雜的問(wèn)題,可以讓人在上面浸淫那么久學(xué)到那么多!

RFX的確是神技啊!!

最后那個(gè)“6. 2011年7月及以后:現(xiàn)在與未來(lái)”該叫“7.2012年7月xxx”吧?  回復(fù)  更多評(píng)論
  

# re: 開(kāi)源光柵化渲染器SALVIA的漫長(zhǎng)五年(準(zhǔn)&middot;干貨) 2013-01-13 10:10 穿越模式
雖然看不懂,頂一下空明大神!  回復(fù)  更多評(píng)論
  

# re: 開(kāi)源光柵化渲染器SALVIA的漫長(zhǎng)五年(準(zhǔn)&middot;干貨) 2013-01-13 11:57 airtrack
膜拜空明巨巨!  回復(fù)  更多評(píng)論
  

# re: 開(kāi)源光柵化渲染器SALVIA的漫長(zhǎng)五年(準(zhǔn)&middot;干貨) 2013-01-13 17:14 Richard Wei
膜拜并學(xué)習(xí)  回復(fù)  更多評(píng)論
  

# re: 開(kāi)源光柵化渲染器SALVIA的漫長(zhǎng)五年(準(zhǔn)&middot;干貨) 2013-01-13 17:47 Edwin
弱弱的問(wèn)一句:什麼是RFX ?  回復(fù)  更多評(píng)論
  

# re: 開(kāi)源光柵化渲染器SALVIA的漫長(zhǎng)五年(準(zhǔn)&middot;干貨) 2013-01-13 17:50 空明流轉(zhuǎn)
@Edwin

人名,一個(gè)大牛
  回復(fù)  更多評(píng)論
  

# re: 開(kāi)源光柵化渲染器SALVIA的漫長(zhǎng)五年(準(zhǔn)&middot;干貨)[未登錄](méi) 2013-01-14 09:51 春秋十二月
長(zhǎng)風(fēng)破浪會(huì)有時(shí) 直掛云帆濟(jì)滄海 加油  回復(fù)  更多評(píng)論
  

# re: 開(kāi)源光柵化渲染器SALVIA的漫長(zhǎng)五年(準(zhǔn)&middot;干貨) 2013-01-14 21:43 Zblc(邱震鈺)
MARK,支持~~~~~~~~~  回復(fù)  更多評(píng)論
  

# re: 開(kāi)源光柵化渲染器SALVIA的漫長(zhǎng)五年(準(zhǔn)&middot;干貨)[未登錄](méi) 2013-01-17 15:10 Hunter
大神,再過(guò)幾年,你對(duì)V/P/C Shader知行合一,融會(huì)貫通,國(guó)內(nèi)是容不下你了。  回復(fù)  更多評(píng)論
  

# re: 開(kāi)源光柵化渲染器SALVIA的漫長(zhǎng)五年(準(zhǔn)&middot;干貨) 2013-03-18 11:23 hustruan
最近也在做光柵化的圖形學(xué)作業(yè),C++版的Shader實(shí)現(xiàn),確實(shí)不好做ddx,ddy這種東西,不過(guò)應(yīng)該可以自己定義個(gè)包含4個(gè)Float4的ShaderFloat4,然后就可以計(jì)算各種偏導(dǎo)數(shù)了。  回復(fù)  更多評(píng)論
  

# re: 開(kāi)源光柵化渲染器SALVIA的漫長(zhǎng)五年(準(zhǔn)&middot;干貨) 2016-04-08 23:42 bitzhuwei
博主你好,我現(xiàn)在在做一個(gè)GLSL的編譯器(前端),但是我找到的GLSL文法是個(gè)病態(tài)的文法,我對(duì)GLSL本身用的還不夠多,沒(méi)辦法自己修改出一個(gè)正常的文法。求博主提供一個(gè)GLSL的文法?(bitzhuwei@qq.com)  回復(fù)  更多評(píng)論
  


只有注冊(cè)用戶(hù)登錄后才能發(fā)表評(píng)論。
網(wǎng)站導(dǎo)航: 博客園   IT新聞   BlogJava   博問(wèn)   Chat2DB   管理


青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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>
            亚洲综合激情| 亚洲日本aⅴ片在线观看香蕉| 亚洲国产另类久久精品| 欧美+日本+国产+在线a∨观看| 永久久久久久| 亚洲国产精品小视频| 欧美日本国产在线| 午夜精品免费在线| 欧美一级午夜免费电影| 在线精品国精品国产尤物884a| 欧美不卡高清| 欧美日韩亚洲一区二区三区在线| 午夜免费日韩视频| 久久久久久久综合日本| 99精品视频免费观看视频| 亚洲欧美韩国| 亚洲三级影片| 午夜视频一区| 日韩午夜精品视频| 亚洲影音一区| 亚洲欧洲一区二区天堂久久 | 亚洲一区二区三区精品动漫| 亚洲免费网站| 亚洲精品中文字幕有码专区| 亚洲视频精品在线| 亚洲二区三区四区| 亚洲一区三区视频在线观看| 亚洲激情网站| 性色一区二区三区| 亚洲手机视频| 欧美a级在线| 久久久国产一区二区三区| 欧美电影免费观看大全| 久久av老司机精品网站导航| 欧美国产专区| 你懂的国产精品| 国产精品一区免费视频| 亚洲国产你懂的| 国内外成人免费激情在线视频| 最近看过的日韩成人| 激情国产一区二区| 亚洲综合大片69999| 亚洲免费高清视频| 久久精品首页| 香蕉久久夜色精品| 欧美日韩一区二区三区在线视频 | 亚洲综合成人在线| 亚洲社区在线观看| 欧美福利专区| 亚洲电影在线播放| 亚洲成色www8888| 亚洲在线网站| 欧美精品一区二区三区视频| 亚洲视频在线免费观看| 免费在线观看精品| 免费在线视频一区| 国内外成人免费激情在线视频| 亚洲性夜色噜噜噜7777| 亚洲一区二区在线播放| 欧美另类视频在线| 亚洲精品在线免费观看视频| 亚洲免费成人av| 欧美国产日韩精品| 亚洲国产欧美在线人成| 亚洲国产精品精华液2区45 | 亚洲人午夜精品免费| 亚洲精品视频在线播放| 欧美国产乱视频| 亚洲激情成人网| 一区二区三区回区在观看免费视频| 欧美wwwwww| 亚洲美女av黄| 亚洲综合国产精品| 国产裸体写真av一区二区| 亚洲欧美日本伦理| 久久久久久97三级| 亚洲大胆视频| 欧美日韩精品一本二本三本| 一区二区三区精品国产| 午夜老司机精品| 国产亚洲成人一区| 久久综合九色综合欧美就去吻| 欧美成人蜜桃| 亚洲天堂成人在线视频| 国产欧美精品在线观看| 久久精品女人| 亚洲精品在线看| 欧美影院视频| 亚洲欧洲精品一区二区三区不卡| 欧美区二区三区| 亚洲欧美日韩国产中文| 免费影视亚洲| 亚洲一区制服诱惑| 精品成人一区二区| 欧美日韩免费一区二区三区| 午夜精品一区二区三区电影天堂| 老司机免费视频久久| 一区二区三区日韩| 国产一区在线视频| 欧美日韩精品二区| 久久精品国产欧美激情| 亚洲伦理一区| 另类成人小视频在线| 亚洲先锋成人| 亚洲国产成人av好男人在线观看| 欧美三级电影大全| 美日韩免费视频| 午夜视频一区| 一级成人国产| 亚洲盗摄视频| 久久蜜桃av一区精品变态类天堂| 中文日韩在线视频| 亚洲大胆在线| 国产午夜亚洲精品理论片色戒| 欧美国产视频在线| 久久一本综合频道| 欧美中文在线免费| 亚洲午夜高清视频| 亚洲人线精品午夜| 欧美大片一区| 久久综合99re88久久爱| 性色av一区二区三区| 中文亚洲免费| 一本高清dvd不卡在线观看| 久久久久国产免费免费| 老色批av在线精品| 久久不射中文字幕| 亚洲欧美日韩国产一区二区| 一个人看的www久久| 亚洲国产精品第一区二区三区| 国产日韩精品一区| 国产啪精品视频| 国产毛片久久| 国产精品你懂的在线欣赏| 欧美日韩一区成人| 欧美日韩在线播放一区| 欧美日韩大片| 欧美日韩黄视频| 欧美日韩免费区域视频在线观看| 久久婷婷丁香| 免费日韩av电影| 免费一级欧美片在线观看| 久久中文字幕一区| 久久资源在线| 欧美成人免费全部| 欧美激情久久久久久| 欧美精品一区二区精品网 | 亚洲视频高清| 西西裸体人体做爰大胆久久久| 亚洲在线视频一区| 午夜精品视频| 久久九九免费| 欧美激情一区二区三区成人| 欧美韩日一区| 欧美午夜不卡视频| 国产精品五月天| 国产一区二区三区丝袜| 亚洲第一久久影院| 亚洲精品一区二区三区婷婷月| 一区二区三区视频在线观看| 午夜在线a亚洲v天堂网2018| 久久久www| 亚洲福利国产| 中文国产亚洲喷潮| 欧美一进一出视频| 模特精品裸拍一区| 欧美视频中文字幕| 国产女优一区| 91久久久亚洲精品| 亚洲五月六月| 老牛嫩草一区二区三区日本| 亚洲精华国产欧美| 亚洲一区日韩在线| 久久综合精品一区| 国产精品国产自产拍高清av王其 | 亚洲乱码国产乱码精品精 | 极品少妇一区二区| 在线亚洲国产精品网站| 久久久精品2019中文字幕神马| 欧美肥婆在线| 香蕉av福利精品导航| 牛人盗摄一区二区三区视频| 国产精品成人午夜| 亚洲成人在线视频播放| 亚洲欧美一级二级三级| 欧美韩日一区二区| 欧美一级免费视频| 欧美日韩国产美| 黄色影院成人| 午夜精品久久久久久99热| 亚洲电影视频在线| 久久精品成人一区二区三区| 欧美视频网站| 日韩亚洲欧美成人| 狂野欧美一区| 欧美一区二区三区免费视频| 欧美日韩亚洲综合在线| 亚洲欧洲日产国产综合网| 欧美中文在线字幕| 中日韩男男gay无套|