というわけでSSE2編です。ちょっとコードが出てくるかな~というところです。
やっぱり鍵になるのは「除算をいかにして乗算に直すか」というものです。
MMX編ではあらかじめ逆数を計算しておく、という方法でテーブル処理ができたのですが、
SSE2の場合は計算幅が128bit =16bit x 8なので、64bitのテーブルを使おうとすると
- ca0を計算して64bitロード
- ca1を計算して64bitロード
- ca1側を64bitシフトして論理和で結合
という作業を踏む・・・・のもありですが、せっかくなので「SSE2」の利点を使ってみましょう。
この場合のSSE2の利点というのはSSEの機能を含んでいる、というところです。
つまり、一時的に浮動小数を使って処理をしてもかまわないということです。
さすがに、全部を浮動小数を使うとちょっと処理がもったいない(16bit x8が32bit float x4になるので効率が悪い)ので、
問題となっているcaの逆数計算にだけその処理を借りることにします。
SSEの環境で考えると、除算あるいは逆数を使う方法は以下の二つになります。
- divpsを使ってそのまんま除算を計算する
- rcppsを使って逆数値を計算して必要な値を乗算する
divpsの方は浮動小数ですがやはり遅いです。mulps(乗算命令)の4~5倍かかるのが弱点です。
rcppsの方はmulpsと同じくらいの速度で動くのですが、精度がちょっと悪くて11bit位しかありません。本当に除算の代わりにするなら
さらにニュートン法を使って精度を上げてやる必要があります。
今回の場合は最終の計算結果が8bitになるので、rcppsの精度でも十分と判断してこれを使って逆数を作ってみることにします。
この部分だけ簡単にコードを示してみます。
//rndalphaは 32bit integer x4 { 255, 255, 255, 255 } という値 //mulinvalphaは逆数の乗算を行うときの基準値がfloat x4となっている。(基準値を2^16とすると{ 65536.0f, 65536.0f, 65536.0f, 65536.0f }) __m128i xmm0,xmm1,xmm2,xmm3,xmm4; __m128 xmm7; xmm0 = _mm_loadu_si128((__m128i *)src); //srcは下のレイヤー xmm1 = _mm_loadu_si128((__m128i *)dest); //destは上のレイヤー xmm2 = xmm3 = rndalpha; xmm4 = _mm_setzero_si128(); //レイヤーの透明度を取得(ca = sa + (da * (1 - sa))) xmm0 = _mm_srli_epi32(xmm0,24); xmm1 = _mm_srli_epi32(xmm1,24); xmm3 = _mm_sub_epi32(xmm3,xmm0); xmm3 = _mm_mullo_epi16(xmm3,xmm1); xmm3 = _mm_add_epi32(xmm3,xmm2); xmm3 = _mm_srli_epi32(xmm3,8); xmm3 = _mm_add_epi32(xmm3,xmm0); //caが0の時にはca=65535として除算時の0除算を行わないようにする //0除算を行うとinvcaが誤った値を指してしまうのでそれを避ける処理 xmm4 = _mm_cmpeq_epi32(xmm4,xmm3); xmm4 = _mm_srli_epi32(xmm4,16); xmm3 = _mm_or_si128(xmm3,xmm4); //浮動小数に切り替えて逆数値(invca)を求める xmm7 = _mm_cvtepi32_ps(xmm3); xmm7 = _mm_rcp_ps(xmm7); xmm7 = _mm_mul_ps(xmm7,mulinvalpha); xmm3 = _mm_cvtps_epi32(xmm7);
これで逆数乗算値が計算できたのであとは必要な場所に持って行ってMMXと同じように計算を行えます。
今回の場合は4つのcaとinvcaをまとめて計算しているのが特徴です。MMXのように一つずつ扱っているのではありません。
あんまりアクロバティックでもないですが、SSE2が使えるという条件であるときは、基本的にSSEも使用可能であるので
その条件をうまく利用して一時的に浮動小数領域で処理をするということを行います。
なお、切り替えに使うcvtdq2psとcvtps2dqはSSE2の命令なのでSSEでは使えません。が、それほど遅い命令でもないので使用しすぎないレベルなら大丈夫です。
ということで、レイヤーの合成についてちょっと見てみました。
ただ、これで示したやり方が正解(最適)か?といわれると「たぶん違うんだろうな・・・」という回答を返すしかありません。
アセンブラよりは絶対に早いので、後は考え方(一種のアルゴリズム)の問題ですね。