利用MMX最佳化64K色Alpha混合演算法(轉)

post0發表於2007-08-12
利用MMX最佳化64K色Alpha混合演算法(轉)[@more@]

  開始使用MMX CPU後,一直在考慮如何用MMX技術加快Alpha混合的操作,尤其是針對目前常用的高彩模式。而早先在國外一個有關遊戲程式設計的郵件列表討論的結果是MMX不利於對16位色進行Alpha混合操作。讓我們先來看看MMX技術相對於普通指令集的更新,來了解一下這個論點的立論。

  

  MMX技術的優勢在於,它的暫存器是64位的,而提供了分組模式,可以將暫存器內的資料按8個位元組,或4個字,或2個雙字同時進行同一操作,方便了大資料量的資料處理。可以成組資料同時作比較操作,這為透明色點的批次判斷帶來好處。MMX的CPU擁有8個MMX暫存器,在一定程度上緩解了80x86CPU暫存器數量不足的缺陷。

  

  但是它也有諸多不足,比如算術指令不能對四位元組字操作。指令結構都不影響標誌位。不能對常數立即定址。MMX系統指令集的指令相當貧乏(連NOT操作也不能直接實現)。當顏色深度是24/32位時,RGB都佔8位,這樣可以巧妙的利用MMX裡的分組乘法指令達到做Alpha混合運算的效果(MMX的乘法相關指令只有對字操作的PMULHW/PMULLW兩條,分別是成組資料的乘後取高位和取低位)本文旨在探討16位色的快速Alpha混合運算,所以此處略去不提。而16位色,紅綠藍各佔5或6位,難以被分組分開,所以不利於運用MMX的這些特性。當然另外的解決方法是採用aRGB 4444的結構,其中4位是Alpha通道,每個色素佔半個位元組,再採用類似的方法。

  

  看過雲風去年提出的《16位Alpha混合最佳化演算法》的朋友,應該會聯想到這個演算法向MMX的引申,好了,也許你已經明白了大概,本文的理論基本點就在此,唯一的問題是,我們需要面對的是MMX指令集的種種缺陷,這些在實際的程式設計中會逐步的體現出來,下面,雲風將在介紹演算法的同時,附帶的提出一些運用MMX的技巧。先來看看上次的演算法有無可進一步最佳化的可能:

  16位下Alpha混合的關鍵在於如何將RGB分離,讓隨後的乘法結果不至於相互干擾。我提出的是將16位的“rrrrrggggggbbbbb”擴充套件到32位變形成“00000gggggg00000rrrrr000000bbbbb”,即將中間的綠色提到高16位,而使色素間隔都有5到6位,而對於5位的顏色,超過5位的Alpha級別是沒有意義的,所以只要設定Alpha值在0~31間,同時算這3個色素的乘法是不會因為進位造成干擾的。而這裡需要多操作一次移位擴充套件16位到32位,然後需要一次與操作, 將中間間隔位置0,而且結果需要同樣複雜的逆操作從32位還原到16位。

  

  改進的思路是直接將兩個點交錯分離,即“rrrrrggggggbbbbbRRRRRGGGGGGBBBBB”分離成“rrrrr000000bbbbb00000GGGGGG00000”及“00000gggggg00000RRRRR000000BBBBB”兩部分,前一部分右移5位後變成“00000rrrrr000000bbbbb00000GGGGGG”,兩個數字就都可以同時運算3個色素,其結果後一組右移5位後可以與前一組合並。這樣就省去了好幾次移位操作,並且資料可以4位元組讀入,和四位元組寫,粗看真的效率很高。但是在傳統的80x86上卻有兩點制約了它的運用:

  

  CPU 的暫存器不夠用,這個方法光儲存資料就需要4個32位的暫存器,雖然EAX、EBX、ECX、EDX剛夠用,但是這就使得Alpha混合函式不能直接寫在Blit操作裡面。必須單寫個子程式呼叫。(不過也值得寫嘗試一下,不是嗎?如果有朋友寫好了,希望能給我拜讀一下,我在風魂遊戲程式庫裡留了介面,並在註釋裡提到了函式的具體寫法。)

  

  

  2D遊戲中,一般都是利用Alpha混合繪製精靈而不是規則的矩形點陣圖,所以這裡面還存在著透明色的判斷,如果是雙點處理,這一步不易實現。(不過也不是沒有好的方法,就是程式碼的長度就長而複雜了)

  

  而MMX卻提供了8個暫存器,同時有分組比較的指令,正好彌補了這兩點不足,而且利用暫存器有64位的優勢可以同時運算4個點。所以我們暫且只用MMX來實現新的想法。(如果你對這個方法用在傳統指令集上有興趣,希望同時操作2個點進行Alpha混合,並寫出實際的程式碼,請和我聯絡,我非常希望看到風魂的非MMX Alpha混合版本能夠進一步最佳化)

  用MMX來做這項工作,原理差不多(相當簡單不是?),也是讀入源點和目標點後分離成4個資料放在4個暫存器中。兩對間進行Alpha混合,(這樣6個色素)最後就兩對資料混合的結果合併。不過從現在開始我們就要面對MMX 8個暫存器不夠用的困境了。MMX指令不能和64位立即常數一起使用,所以在進行分裂操作的時候用到的掩碼要常駐在暫存器內。

  

  如果暫存器足夠多的話,可以連掩碼的反值也放一個,可惜現在不能這麼浪費。處理透明色問題方面,可以先將點和透明色比較得到一個掩碼,我們再將混合後的點,及原來的目標圖上的點(這個點應當保留一個備份,哎,又去了一個暫存器)分別與掩碼邏輯運算合併得到最終的資料寫入目標圖。這裡,需要大量運用的NOT操作,Intel竟然沒有在MMX指令集中提供。我們只好用PANDN(取反再與操作)間接完成。(例:可以先用PCMPEQW mm0,mm0(自己和自己比較當然全相等了)生成常數FFFFFFFFFFFFFFFF,用PANDN mm1,mm0就可以將mm1取反。)這裡,不再可以利用MMX的分組乘法,(MMX不能對32位數進行乘法操作)所以我們應該用移位和加減法來實現。這樣,如果有幾級Alpha值,就應該寫幾個混合函式。最後建立一個函式指標陣列,將每級Alpha混合函式依次放入陣列。我們在呼叫時就可以根據需要的Alpha值來呼叫相應的函式了。

  

  在風魂0.07裡,Alpha混合又一次修改了演算法,(0.06使用的上述演算法,0.07 則沒有)這裡要感謝網友T&P的新思路。針對分級數比較少的Alpha混合,比如8級,可以用更簡單的方法。大家可以注意到,50%的 Alpha時,R=(r1+r2)/2,也可以近似的等於r1/2+r2/2。那麼RGB可以方便的同時運算。只需要在移位後做一次簡單的與操作即可(0RRRRRGGGGGGBBBB & 011110111101111 = 0RRRR0GGGGG0BBBB)然後,將兩個移位後的資料相加就完成了Alpha=50%的混合。這個方法避免了切分和還原資料,所以速度更快。風魂的早期版本,對50%的Alpha度就做了此種特殊處理。但是,它是有誤差的,誤差在於移位造成的每色素上1/32或1/64的偏差。

  

  下一步我們可以將50%的Alpha值推廣到25%、12.5%甚至更小。現在來看一下完成R1*25%+R2*75%,它等於R2+R1*25%-R2*25% = R2+R1/4+R2/4。這裡除4的操作和除2原理是一樣的即:(RRRRRGGGGGGBBBBB>>2)&0011100111100111。依次類推,X*37.5%+Y*62.5% = (X+Y)/2+Y/8-X/8等等。我們就只需要利用移位和加減法就可以同時完成N個色素的混合了。

  

  再來看看這個方法的缺陷。首先是誤差問題,每一組移位取與都會造成最大為1/32的誤差,而多次運算有可能使誤差累計,所以alpha級別不能分的太多。而且alpha級別分的太細後,使得運算步驟變的很多,不切分直接運算的優勢有可能損失掉。而且更致命的一點是,如果想用MMX加速,那麼通常AND運算用的掩碼應該放在暫存器中(如果放在記憶體,而MMX不能立即定址,間接定址取記憶體可能不能命中CACHE速度變慢,大規模的混合運算速度損失太多),MMX的暫存器卻只有8個。那麼多個掩碼會使明顯的感覺暫存器不夠用,但這不失為一種好的方法。風魂庫0.07中新的alpha精靈,這一步的演算法更改帶來了10%左右的速度提升,而畫質的損失卻幾乎沒有體現。

  

  最後對關於帶Alpha通道的點陣圖的做一點探討,這裡每一個點將帶有不同的Alpha值,我們應該合理的協調點陣圖的結構。將Alpha值和顏色資訊放在一起是不合算的。這樣不利於高速處理。我們可以將所有點的Alpha值提出來放在一起,對於16位的顏色,合理的Alpha級別應該在16級以下。這樣可以每一個位元組存放兩個Alpha值。用一個暫存器作為指向Alpha值區域的指標,讀入對應點的Alpha值,呼叫相應的混合函式運算。但是,這種點陣圖每個點都有可能是不同的alpha值,如此就不能多點同時運算,雲風找到了另外的加速方法,要知詳情,且看下文分解。

  

  後記

  本文提出的方法,都被雲風實踐證明可行,請參閱風魂遊戲程式庫的原始碼。你會發現速度相當的快。測試表明,MMX下帶Alpha混合的點陣圖操作,僅僅比普通的檢查透明色的點陣圖操作慢20%。比不用MMX逐點做Alpha混合快2.7倍。如果採用RLE壓縮掉透明色點,去掉對透明色的特殊處理,速度還會有很大的提高。(達到DirectDraw裡記憶體表面(Surface)間關鍵色檢查的位塊傳送(blit)操作的速度)這個演算法的意義在於,16位色下,軟體Alpha混合的速度已經足夠快,這使遊戲中大量運用光影效果不再有速度上的顧慮。

  

  

 

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/8225414/viewspace-952245/,如需轉載,請註明出處,否則將追究法律責任。

相關文章