« クロスフェード | メイン | クロスフェードが重いのは・・・ »

2005年02月10日

吉里吉里2/KAG3ムービー拡張日誌:: アルファブレンドのコードを追う

    

Cとアセンブラ(MMX)で記述されたコードがあり、対応する環境によってコールされる関数が切り替えられるようになっている模様。
まずはCのコードを読もうと思ったが、見たことのない記述の仕方・・・
次のような構造をしていた。

int lu_n = (len + (4-1)) / 4;
switch(len % 4)
{
  case 0:
    do {
      { // アルファブレンド処理 } ;
      case 3:
        { // アルファブレンド処理 } ;
      case 2:
        { // アルファブレンド処理 } ;
      case 1:
        { // アルファブレンド処理 } ;
    } while(-- lu_n);
}

初めはどのように動作するのかまったくわからなかったが、デバッガでトレースしてみて納得。
goto文を使って似たような動作をするように書いてみればわかりやすい。

int lu_n = (len + (4-1)) / 4;
if( (len%4) == 0 ) goto case_0;
else if( (len%4) == 3 ) goto case_3;
else if( (len%4) == 2 ) goto case_2;
else if( (len%4) == 1 ) goto case_1;
case_0:
do {
  // アルファブレンド処理
case_3:
  // アルファブレンド処理
case_2:
  // アルファブレンド処理
case_1:
  // アルファブレンド処理
} while( --lu_n );

4pixel文のループを展開して、幅が4の倍数でない場合にきちんと合うようにループ開始位置を変えているようだ。
また、アルファブレンド式は、単なる数式の変換と2色同時演算によって効率化されていた。
MMXを使ったアセンブラのコードは追う気がしなかったので、ほったらかし。

で、計測するために上記のコードを使った簡単なプログラムを作ることにした。
まずは、もっとも単純な実装。

#define MASK_0(x) ((x)&0xFF000000)
#define MASK_1(x) (((x)&0xFF0000)>>16)
#define MASK_2(x) (((x)&0xFF00)>>8)
#define MASK_3(x) ((x)&0xFF)
// 以下をループ
dest[i] = MASK_0(src1[i]);
ret= ((MASK_1(src1[i]) * alpha ) + (MASK_1(src2[i]) * (256-alpha) ))/256;
ret = (ret > 255) ? 255 : ret;
dest[i] |= ret<<16;
ret= ((MASK_2(src1[i]) * alpha ) + (MASK_2(src2[i]) * (256-alpha) ))/256;
ret = (ret > 255) ? 255 : ret;
dest[i] |= ret<<8;
ret= ((MASK_3(src1[i]) * alpha ) + (MASK_3(src2[i]) * (256-alpha) ))/256;
ret = (ret > 255) ? 255 : ret;
dest[i] |= ret;

ほとんど何も考えてません。
そのまんま。
次に吉里吉里のコード。
これは、4pixelのループを展開するよりも、1pixelずつやった方が早かったので、ループ展開はなくした。
で、最後にMMXを使って記述。

__m64 mblend = _mm_set1_pi16(alpha);
__m64 zero = _mm_setzero_si64();
// 以下をループ
__m64 ms1 = *(__m64*)src1;
__m64 ms2 = *(__m64*)src2;

__m64 ms = _mm_unpacklo_pi8( ms1, zero );
__m64 md = _mm_unpacklo_pi8( ms2, zero_ );
ms = _mm_sub_pi16( ms, md );
ms = _mm_mullo_pi16( ms, mblend );
ms = _mm_srai_pi16( ms, 8 );
__m64 mlo = _mm_add_pi16( ms, md );

ms = _mm_unpackhi_pi8( ms1, zero_ );
md = _mm_unpackhi_pi8( ms2, zero_ );
ms = _mm_sub_pi16( ms, md );
ms = _mm_mullo_pi16( ms, alpha_ );
ms = _mm_srai_pi16( ms, 8 );
__m64 mhi = _mm_add_pi16( ms, md );
*((__m64*)dest) = _mm_packs_pu16( mlo, mhi );

src2+=2;
src1+=2;
dest+=2;

多分、あっているはず。
元画像は乱数で生成した上に、出力結果は見ていないので、正しいかどうかは不明。
なので、このコードは参考にしない方が良いと思われます。
 ※間違っていたので修正。別エントリーでも同じようなことを書いたのでそちらを参照したほうが良い。
で、上記コードを640*480サイズの32bitカラーの乱数で生成した画像に適用。
各30回実行してその時間を計測した。
結果は次の通り。

べた実装 : 75 (msec)
吉里吉里C : 53 (msec)
適当MMX : 42 (msec)

30FPSとしたら、べた実装でも7.5%しか負荷がない計算になる。
おかしい。
実際に吉里吉里に計測コードを埋め込むが、やはり1回の処理にはほとんどかかっていない様子。(吉里吉里では1枚の画像を何回かに分割して処理していた)
どうやら、アルファブレンドの処理が重いわけではないようだ。



投稿者 Takenori : 2005年02月10日 20:35




comments powered by Disqus
Total : Today : Yesterday : なかのひと