というわけで、さらにテストで関数丸ごとで出してみます。
MMXを使って画像を線形拡大する
BOOL StretchBitmapMMX(void *lpDest,long lDestWidth,long lDestHeight,long lDestPitch,const void *lpSrc,long lSrcWidth,long lSrcHeight,long lSrcPitch)
{
__m64 mm0,mm1,mm2,mm3,mm4,mm5,mm6,mm7;
unsigned char *dst,*src,*buf,*spn; int *ptr1,*ptr2;
long sx,sy,sw,sh,dx,dy; long x,y,ddx,ddy;
MMVAL _mmalpha; MMVAL _mmalphac(0x0100010001000100ull);
dst = (unsigned char *)lpDest; src = (unsigned char *)lpSrc;
ddx = (lSrcWidth << 8) / lDestWidth; ddy = (lSrcHeight << 8) / lDestHeight;
sw = lSrcWidth - 1; sh = lSrcHeight - 1;
mm6 = _mm_cvtsi32_si64(8); mm7 = _mm_setzero_si64();
for(sy = y = 0,dy = lDestHeight;dy > 0;dy–,sy += ddy,dst += lDestPitch){
buf = dst; src = (unsigned char *)lpSrc + lSrcPitch * y;
if(y < sh){ spn = src + lSrcPitch; _mmalpha.setu16((unsigned short)(sy & 0x000000ff)); }
else{ spn = src; _mmalpha.setu16(0x0000); }
mm4.m64_u64 = _mmalphac.u64; mm5.m64_u64 = _mmalpha.u64; mm4 = _mm_sub_pi16(mm4,mm5);
for(sx = 0,dx = lDestWidth;dx > 0;dx–,sx += ddx,buf += sizeof(unsigned long)){
x = sx >> 8; _mmalpha.setu16((unsigned short)(sx & 0x000000ff));
ptr1 = (int *)(src + sizeof(unsigned long) * x);
ptr2 = (int *)(spn + sizeof(unsigned long) * x);
mm0 = _mm_cvtsi32_si64(*ptr1);
mm2 = _mm_cvtsi32_si64(*ptr2);
if(x < sw){
mm1 = _mm_cvtsi32_si64(*(ptr1 + 1));
mm3 = _mm_cvtsi32_si64(*(ptr2 + 1));
}
else{
mm1 = mm0;
mm3 = mm2;
}
mm0 = _mm_unpacklo_pi8(mm0,mm7); mm1 = _mm_unpacklo_pi8(mm1,mm7);
mm2 = _mm_unpacklo_pi8(mm2,mm7); mm3 = _mm_unpacklo_pi8(mm3,mm7);
mm0 = _mm_mullo_pi16(mm0,mm4); mm1 = _mm_mullo_pi16(mm1,mm4);
mm2 = _mm_mullo_pi16(mm2,mm5); mm3 = _mm_mullo_pi16(mm3,mm5);
mm0 = _mm_add_pi16(mm0,mm2); mm1 = _mm_add_pi16(mm1,mm3);
mm0 = _mm_srl_pi16(mm0,mm6); mm1 = _mm_srl_pi16(mm1,mm6);
mm2.m64_u64 = _mmalphac.u64; mm3.m64_u64 = _mmalpha.u64; mm2 = _mm_sub_pi16(mm2,mm3);
mm0 = _mm_mullo_pi16(mm0,mm2);
mm1 = _mm_mullo_pi16(mm1,mm3);
mm1 = _mm_add_pi16(mm0,mm1);
mm0 = _mm_srl_pi16(mm1,mm6);
mm0 = _mm_packs_pu16(mm0,mm7);
*(int *)buf = _mm_cvtsi64_si32(mm0);
}
}
_mm_empty();
return TRUE;
}
[/cpp]
さっと解説すると、sxとsyは24.8の固定小数点でSourceの位置、dyとdyはDestのループカウンタ、ddxとddyはDest1PixelあたりのSourceの移動量です。
mm0~mm7はMMXのレジスタを模しています。MMVALは64bitの構造体の変数で、各ワード(16bit)や各ダブルワード(32bit)に同じ値を設定できるようにしたヘルパ構造体です。
引数やらそれ以外は意味に合うように推察してください。一応名前そのまんまのつもりです。
元々は全部をx86のアセンブラコードで組もうかと思っていたコードでしたが、ループ制御の部分がややこしすぎたのでmmintrinでの実装に切り替えました。
計算のやり方はほかの線形補間のページで解説されているとおりのことしかやっていない(1Pixelの幅を256分割してそれをα乗算することで4点の補間処理を行っている)です。
一応MMX命令を使用しているのである程度の高速化の効果が期待できるルーチンになっています。
もちろん、このままでは無駄がいろいろとありますので、実際に実装しているものはもう少し高速化を図ってあります。
目安としては、
- 256分割ではなく、16分割にしてPixel一回分のx乗算+シフトの演算を削除する
- 計算が不要なコードを削る(終端部のポインタが一致する部分など)
があります。
さて、このコードは私が作成したプログラムに実装されていますが・・・。
それはとりあえず明日の話ということで。それでは。
・・・やっぱりはてなの方がプログラムコードの表示がきれいですね。
しばらく使ってみて乗り換えか併用か考えてみたいと思います。