Hatena::ブログ(Diary)

ゲームとかプログラムとかパソコンとか このページをアンテナに追加 RSSフィード

2012-01-08

[] 更新

  • コマンド用のDLLが正常に読みこまれていなかったのを修正
  • レイヤー平行移動を含んだ複数回のUndoをすると他のレイヤー編集のUndo結果がずれているのを修正
  • マスク編集を含んだ複数回のRedoをするとマスクが正常に効かないのを修正

http://key0note.ath.cx/hp/frayer_wiki/

2011-12-27

[] 更新

  • 設定ファイルがない場合にデフォルトの設定ファイルを作成するように変更
  • エフェクトがかからなかったのを修正

http://key0note.ath.cx/hp/frayer_wiki/

2011-12-26

[] 更新

  • OpenCV2.3に対応
  • ブラシテクスチャ実装
  • ナビゲータウィンドウに画面回転ボタン、画像反転ボタンを追加
  • レイヤー更新処理20%程度高速化
  • 新規ファイル作成時、規定サイズを選択できるように変更
  • レイヤー平行移動後、マスクを使って画像編集すると編集位置がずれるのを修正
  • レイヤー平行移動→Undo→画像編集→Undoで元に戻らないのを修正
  • ブラシツールを選択して、選択ブラシがかわるときにブラシウィンドウのスクロール位置がおかしくなるのを修正
  • psd形式の書き込みで間違ったデータが出力されることがあったのを修正
  • fyd形式の書き込みで間違ったデータが出力されることがあったのを修正
  • fydファイルレイヤーが画像サイズの外の範囲にあるときに読み込みエラーが出ていたのを修正
  • 選択範囲解除後のUndoがおかしかったのを修正
  • 選択範囲をカットして新規レイヤー作成後のUndoがおかしかったのを修正
  • レイヤーパネルを右クリック→レイヤー削除でヒストリに登録されなかったのを修正
  • レイヤー合成のハードライト、ソフトライト、オーバーレイの計算式がおかしかったのを修正
  • レイヤーを結合したときに画像がずれるのを修正
  • 透明なレイヤー同士を結合すると正しく結合されないのを修正
  • ブラシダイアログパラメータ変更後、ブラシプレビューが更新されないのを修正
  • 右クリックでブラシ選択されるように修正
  • エフェクトを掛けるときにマスクを点線表示にするように修正
  • マウスホイールでレイヤーウィンドウ・ブラシウィンドウ・パレットウィンドウがスクロールするように修正
  • Altを押してもメニューにフォーカスしないように修正
  • その他細かい修正

1年近くぶりの更新です。

ブラシテクスチャはこんな感じです。

f:id:hiroki0:20111226084240p:image

http://key0note.ath.cx/hp/frayer_wiki/

2011-12-03

[][][]SSE用いたアルファブレンドの高速化4 アライメントがずれている画像同士のアルファブレンド

↓の続き

http://d.hatena.ne.jp/hiroki0/20090914/1252931917

http://d.hatena.ne.jp/hiroki0/20100320/1269074009

http://d.hatena.ne.jp/hiroki0/20111128/1322504210

今回はアライメントをずらした場合での計測をします。

_mm_loadu_si128などアライメントをあわせる必要がないロード・ストア命令を使用する場合は、

前回と同じ様に何も考えずに読み込んでアルファブレンドを行い、書きこむという順で処理を行うことができます。

しかし、アライメントを合わせる必要があるロード・ストア命令を使用する場合は、

処理方法をアライメントのズレによって変える必要があります。

今回のアルファブレンド処理では、

  1. 入出力メモリともアライメントが揃っている場合
  2. 入出力メモリともアライメントから同じだけずれている場合
  3. 入力、出力メモリのアライメントのずれ量が違う場合

の3通りに分けてコードを実装しました。

1の場合は、前回と同様のコードで処理します。

2の場合は、ずれてる分を処理して入出力ともアライメントを合わせた状態にして、1と同じように処理します。

3の場合は、ずれてる分をシフト演算を使用してずれを合わせる処理をいれて、

ロード・ストア命令がアライメントが合った状態で実行されるようにします。

計測方法

32bit,64bit向けに2通りに実行ファイルを作成、それぞれについて

  1. SSEを使わない処理
  2. _mm_loadu_si128でメモリ読み込み、_mm_storeu_si128でメモリ書き込み
  3. _mm_load_si128でメモリ読み込み、_mm_store_si128でメモリ書き込み
  4. _mm_load_si128でメモリ読み込み、_mm_stream_si128でメモリ書き込み

の4通りの処理の処理速度を計測する。

前回までは間違えて浮動小数点用の_mm_loadu_ps等を使っていたので、今回は_mm_loadu_si128等を使います。

  • チャンネルサイズ8bit 5700x5700BGR画像を2枚読み込み、アルファ値を指定してアルファブレンドを行う。
  • 画像の保存、読み込みはopenCV 2.3を用いた。
  • アルファブレンド部分の処理のみ処理時間を計測した。
  • 与えるアルファチャンネルは、上下ともにアルファ値を均等に分布させる
  • アルファブレンドでの入力画像は上下でアライメントのずれ量が合った状態にする。

以下の3通りのように意図的にアライメントをずらして計測を行います。

  1. 入出力画像ともにずらさない
  2. 入出力画像とも2ピクセルずつずらす
  3. 入力画像を2ピクセル、出力画像を1ピクセルずらす

計測1

環境
32bit向け実行ファイル
  • 入出力画像ともにずらさない
SSE_mm_loadu_si128 + _mm_storeu_si128_mm_load_si128 + _mm_store_si128_mm_load_si128 + _mm_stream_si128
1回目 675.374 msec 203.678 msec 166 msec 166.106 msec
2回目 591.512 msec 181.441 msec 175.978 msec 207.662 msec
3回目 563.755 msec 170.575 msec 179.13 msec 165.016 msec
SSE_mm_loadu_si128 + _mm_storeu_si128_mm_load_si128 + _mm_store_si128_mm_load_si128 + _mm_stream_si128
1回目 584.079 msec 184.901 msec 163.356 msec 197.957 msec
2回目 601.959 msec 229.62 msec 169.618 msec 179.718 msec
3回目 576.596 msec 172.858 msec 172.926 msec 238.996 msec
SSE_mm_loadu_si128 + _mm_storeu_si128_mm_load_si128 + _mm_store_si128_mm_load_si128 + _mm_stream_si128
1回目 581.447 msec 215.271 msec 194.737 msec 183.298 msec
2回目 583.466 msec 168.634 msec 175.305 msec 204.374 msec
3回目 585.919 msec 170.421 msec 179.532 msec 177.077 msec
64bit向け実行ファイル
  • 入出力画像ともにずらさない
SSE_mm_loadu_si128 + _mm_storeu_si128_mm_load_si128 + _mm_store_si128_mm_load_si128 + _mm_stream_si128
1回目 321.546 msec 178.499 msec 181.309 msec 192.388 msec
2回目 370.826 msec 170.399 msec 159.175 msec 185.213 msec
3回目 311.765 msec 162.117 msec 166.067 msec 167.506 msec
SSE_mm_loadu_si128 + _mm_storeu_si128_mm_load_si128 + _mm_store_si128_mm_load_si128 + _mm_stream_si128
1回目 316.697 msec 172.909 msec 162.31 msec 167.428 msec
2回目 308.39 msec 169.355 msec 163.629 msec 159.495 msec
3回目 312.266 msec 165.965 msec 158.519 msec 173.99 msec
SSE_mm_loadu_si128 + _mm_storeu_si128_mm_load_si128 + _mm_store_si128_mm_load_si128 + _mm_stream_si128
1回目 303.656 msec 175.868 msec 172.008 msec 163.839 msec
2回目 329.698 msec 157.363 msec 171.484 msec 166.765 msec
3回目 297.241 msec 159.257 msec 164.473 msec 163.332 msec

計測2

環境
  • 入出力画像ともにずらさない
SSE_mm_loadu_si128 + _mm_storeu_si128_mm_load_si128 + _mm_store_si128_mm_load_si128 + _mm_stream_si128
1回目 1098.98 msec 217.239 msec 191.035 msec 197.02 msec
2回目 1103 msec 218.563 msec 190.894 msec 193.745 msec
3回目 1104.15 msec 218.304 msec 190.167 msec 196.236 msec
SSE_mm_loadu_si128 + _mm_storeu_si128_mm_load_si128 + _mm_store_si128_mm_load_si128 + _mm_stream_si128
1回目 1106.91 msec 218.914 msec 194.721 msec 199.865 msec
2回目 1109.58 msec 223.114 msec 196.515 msec 199.412 msec
3回目 1127.07 msec 216.637 msec 190.159 msec 196.093 msec
SSE_mm_loadu_si128 + _mm_storeu_si128_mm_load_si128 + _mm_store_si128_mm_load_si128 + _mm_stream_si128
1回目 1119.89 msec 255.483 msec 245.189 msec 243.91 msec
2回目 1086.53 msec 221.121 msec 210.191 msec 211.133 msec
3回目 1086.65 msec 221.38 msec 209.693 msec 221.721 msec

まとめ

Core i7 2600では、_mm_loadu_si128を使っても_mm_load_si128とバラツキがあるが同程度という結果だった。

32bit Core2 Duo E8400では、アライメントを合わせた効果がCorei7に比べてよく出ていて

入出力画像とも2ピクセルずつずらした場合には約10%、

入力画像を1ピクセル、出力画像を2ピクセルずらした場合には約5%速くなっていることがわかった。

使用したコード

SSEアルファブレンド関数はinlineを付けてもインライン展開されなかったのでマクロ関数で実装しています。

#include <iostream>
#include <opencv/cv.h>
#include <opencv/highgui.h>
#include "IETimer.h"

union UCvPixel32
{
	uint32_t value;
	struct
	{
		uint8_t b,g,r,a;
	};
};
typedef UCvPixel32 UCvPixel;

inline uint8_t* GetNextLineAddress(const IplImage* image, uint8_t* addr)
{
	return addr + image->widthStep;
}

inline uint8_t* GetPixelAddress(const IplImage* image, int x, int y)
{
	assert(image);
	assert(image->depth == IPL_DEPTH_8U);
	assert((0 <= x && x <= image->width-1) &&
		(0 <= y && y <= image->height-1));

	return (uint8_t*)(image->imageData + y*image->widthStep + x*image->nChannels);
}

inline UCvPixel32* GetNextLineAddress32(const IplImage* image, UCvPixel32* addr)
{
	return (UCvPixel32*)((uint8_t*)addr + image->widthStep);
}

inline UCvPixel32* GetPixelAddress32(const IplImage* image, int x, int y)
{
	assert(image);
	assert(image->depth == IPL_DEPTH_8U);
	assert((0 <= x && x <= image->width-1) &&
		(0 <= y && y <= image->height-1));

	return (UCvPixel32*)(image->imageData + y*image->widthStep + x*image->nChannels);
}

#define ALPHABLEND_SSE(dst, under, over)										\
{																				\
	__m128i __xmzero = _mm_setzero_si128();										\
	__m128i __xmff16 = _mm_set1_epi16(0xff);									\
																				\
	__m128i __xmo16, __xmu16;													\
	__m128i __xmlo, __xmhi;														\
	__m128i __xmover_alpha, __xmover_ralpha, __xmunder_alpha, __xmunder_ralpha, __xmalpha;\
	__m128 __xmf_lo, __xmf_hi, __xmf_alpha_lo, __xmf_alpha_hi;					\
																				\
	/*load lo double ward */													\
	__xmo16 = _mm_unpacklo_epi8(over, __xmzero);								\
	__xmu16 = _mm_unpacklo_epi8(under, __xmzero);								\
																				\
	int over_a1 = _mm_extract_epi16(__xmo16, 3);								\
	int over_a2 = _mm_extract_epi16(__xmo16, 7);								\
	if ((over_a1 == 255 && over_a2 == 255)) {									\
		__xmlo = __xmo16;														\
	} else {																	\
		int under_a1 = _mm_extract_epi16(__xmu16, 3);							\
		int under_a2 = _mm_extract_epi16(__xmu16, 7);							\
		if (under_a1 == 0 && under_a2 == 0) {									\
			__xmlo = __xmo16;													\
		} else if (under_a1 == 0 && under_a2 == 0) {							\
			__xmlo = __xmu16;													\
		} else if (under_a1 == 255 && under_a2 == 255) {						\
			__xmover_alpha = _mm_shufflelo_epi16(__xmo16, _MM_SHUFFLE(3,3,3,3));\
			__xmover_alpha = _mm_shufflehi_epi16(__xmover_alpha, _MM_SHUFFLE(3,3,3,3));\
			__xmover_ralpha = _mm_sub_epi16(__xmff16, __xmover_alpha);			\
																				\
			__xmu16 = _mm_mullo_epi16(__xmu16, __xmover_ralpha); /* under_pix * over_ralpha */\
			__xmo16 = _mm_mullo_epi16(__xmo16, __xmover_alpha); /* over_pix * over_alpha */\
			__xmo16 = _mm_add_epi16(__xmu16, __xmo16); /* (under_pix * over_ralpha + over_pix * over_alpha) */\
			__xmlo = _mm_srli_epi16(__xmo16, 8); /* (under_pix * over_ralpha + over_pix * over_alpha) >> 8 */\
			__xmlo = _mm_insert_epi16(__xmlo, 255, 3); /* write alpha */		\
			__xmlo = _mm_insert_epi16(__xmlo, 255, 7); /* write alpha */		\
		} else {																\
			__xmover_alpha = _mm_shufflelo_epi16(__xmo16, _MM_SHUFFLE(3,3,3,3));\
			__xmover_alpha = _mm_shufflehi_epi16(__xmover_alpha, _MM_SHUFFLE(3,3,3,3));\
			__xmover_ralpha = _mm_sub_epi16(__xmff16, __xmover_alpha);			\
			__xmunder_alpha = _mm_shufflelo_epi16(__xmu16, _MM_SHUFFLE(3,3,3,3));\
			__xmunder_alpha = _mm_shufflehi_epi16(__xmunder_alpha, _MM_SHUFFLE(3,3,3,3));\
			__xmunder_ralpha = _mm_sub_epi16(__xmff16, __xmunder_alpha);		\
			__xmalpha = _mm_sub_epi16(__xmff16, _mm_srli_epi16( _mm_mullo_epi16(__xmover_ralpha, __xmunder_ralpha), 8)); /* 255 - ((over_ralpha * under_ralpha) >> 8) */\
																				\
			__xmu16 = _mm_mullo_epi16( _mm_srli_epi16( _mm_mullo_epi16(__xmunder_alpha, __xmover_ralpha), 8), __xmu16); /* under_pix * ((under_alpha * over_ralpha)>>8) */\
			__xmo16 = _mm_mullo_epi16(__xmo16, __xmover_alpha); /* over_pix * over_alpha */\
			__xmo16 = _mm_add_epi16(__xmu16, __xmo16); /* over_pix * over_alpha + (under_pix * under_alpha * over_ralpha)>>8 */\
																				\
			/* calc pixel 1 */													\
			__xmf_lo = _mm_cvtepi32_ps( _mm_unpacklo_epi16(__xmo16, __xmzero) );\
			__xmf_alpha_lo = _mm_cvtepi32_ps( _mm_unpacklo_epi16(__xmalpha, __xmzero) );\
			__xmf_lo = _mm_mul_ps(__xmf_lo, _mm_rcp_ps(__xmf_alpha_lo));		\
																				\
			/* calc pixel 2 */													\
			__xmf_hi = _mm_cvtepi32_ps( _mm_unpackhi_epi16(__xmo16, __xmzero) );\
			__xmf_alpha_hi = _mm_cvtepi32_ps( _mm_unpackhi_epi16(__xmalpha, __xmzero) );\
			__xmf_hi = _mm_mul_ps(__xmf_hi, _mm_rcp_ps(__xmf_alpha_hi));		\
																				\
			/* pack to 16bit data */											\
			__xmlo = _mm_packs_epi32(_mm_cvtps_epi32(__xmf_lo), _mm_cvtps_epi32(__xmf_hi));\
			int a1 = _mm_extract_epi16(__xmalpha, 3);							\
			__xmlo = _mm_insert_epi16(__xmlo, a1, 3); /* write alpha */			\
			int a2 = _mm_extract_epi16(__xmalpha, 7);							\
			__xmlo = _mm_insert_epi16(__xmlo, a2, 7); /* write alpha */			\
		}																		\
	}																			\
																				\
	/* load hi double ward */													\
	__xmo16 = _mm_unpackhi_epi8(over, __xmzero);								\
	__xmu16 = _mm_unpackhi_epi8(under, __xmzero);								\
																				\
	over_a1 = _mm_extract_epi16(__xmo16, 3);									\
	over_a2 = _mm_extract_epi16(__xmo16, 7);									\
	if ((over_a1 == 255 && over_a2 == 255)) {									\
		__xmhi = __xmo16;														\
	} else {																	\
		int under_a1 = _mm_extract_epi16(__xmu16, 3);							\
		int under_a2 = _mm_extract_epi16(__xmu16, 7);							\
		if (under_a1 == 0 && under_a2 == 0) {									\
			__xmhi = __xmo16;													\
		} else if (under_a1 == 0 && under_a2 == 0) {							\
			__xmhi = __xmu16;													\
		} else if (under_a1 == 255 && under_a2 == 255) {						\
			__xmover_alpha = _mm_shufflelo_epi16(__xmo16, _MM_SHUFFLE(3,3,3,3));\
			__xmover_alpha = _mm_shufflehi_epi16(__xmover_alpha, _MM_SHUFFLE(3,3,3,3));\
			__xmover_ralpha = _mm_sub_epi16(__xmff16, __xmover_alpha);			\
																				\
			__xmu16 = _mm_mullo_epi16(__xmu16, __xmover_ralpha); /* under_pix * over_ralpha */\
			__xmo16 = _mm_mullo_epi16(__xmo16, __xmover_alpha); /* over_pix * over_alpha */\
			__xmo16 = _mm_add_epi16(__xmu16, __xmo16); /* (under_pix * over_ralpha + over_pix * over_alpha) */\
			__xmhi = _mm_srli_epi16(__xmo16, 8); /* (under_pix * over_ralpha + over_pix * over_alpha) >> 8 */\
			__xmhi = _mm_insert_epi16(__xmhi, 255, 3); /* write alpha */		\
			__xmhi = _mm_insert_epi16(__xmhi, 255, 7); /* write alpha */		\
		} else {																\
			__xmover_alpha = _mm_shufflelo_epi16(__xmo16, _MM_SHUFFLE(3,3,3,3));\
			__xmover_alpha = _mm_shufflehi_epi16(__xmover_alpha, _MM_SHUFFLE(3,3,3,3));\
			__xmover_ralpha = _mm_sub_epi16(__xmff16, __xmover_alpha);			\
			__xmunder_alpha = _mm_shufflelo_epi16(__xmu16, _MM_SHUFFLE(3,3,3,3));\
			__xmunder_alpha = _mm_shufflehi_epi16(__xmunder_alpha, _MM_SHUFFLE(3,3,3,3));\
			__xmunder_ralpha = _mm_sub_epi16(__xmff16, __xmunder_alpha);		\
			__xmalpha = _mm_sub_epi16(__xmff16, _mm_srli_epi16( _mm_mullo_epi16(__xmover_ralpha, __xmunder_ralpha), 8)); /* 255 - ((over_ralpha * under_ralpha) >> 8) */\
																				\
			__xmu16 = _mm_mullo_epi16( _mm_srli_epi16( _mm_mullo_epi16(__xmunder_alpha, __xmover_ralpha), 8), __xmu16); /* under_pix * ((under_alpha * over_ralpha)>>8) */\
			__xmo16 = _mm_mullo_epi16(__xmo16, __xmover_alpha); /* over_pix * over_alpha */\
			__xmo16 = _mm_add_epi16(__xmu16, __xmo16); /* over_pix * over_alpha + (under_pix * ((under_alpha * over_ralpha)>>8) */\
																				\
			/* calc pixel1 */													\
			__xmf_lo = _mm_cvtepi32_ps( _mm_unpacklo_epi16(__xmo16, __xmzero) );\
			__xmf_alpha_lo = _mm_cvtepi32_ps( _mm_unpacklo_epi16(__xmalpha, __xmzero) );\
			__xmf_lo = _mm_mul_ps(__xmf_lo, _mm_rcp_ps(__xmf_alpha_lo));		\
																				\
			/* calc pixel 2 */													\
			__xmf_hi = _mm_cvtepi32_ps( _mm_unpackhi_epi16(__xmo16, __xmzero) );\
			__xmf_alpha_hi = _mm_cvtepi32_ps( _mm_unpackhi_epi16(__xmalpha, __xmzero) );\
			__xmf_hi = _mm_mul_ps(__xmf_hi, _mm_rcp_ps(__xmf_alpha_hi));		\
																				\
			/* pack to 16bit data */											\
			__xmhi = _mm_packs_epi32(_mm_cvtps_epi32(__xmf_lo), _mm_cvtps_epi32(__xmf_hi));\
			int a3 = _mm_extract_epi16(__xmalpha, 3);							\
			__xmhi = _mm_insert_epi16(__xmhi, a3, 3); /* write alpha */			\
			int a4 = _mm_extract_epi16(__xmalpha, 7);							\
			__xmhi = _mm_insert_epi16(__xmhi, a4, 7); /* write alpha */			\
		}																		\
	}																			\
	dst =  _mm_packus_epi16(__xmlo, __xmhi);									\
}

inline void BltLineAlphaBlend(
	UCvPixel* dst,
	const UCvPixel* under,
	const UCvPixel* over,
	int blt_width)
{
	int x;
	for(x = 0; x < blt_width; x++){
		//alpha blend
		if ((over->a == 255) || (under->a == 0)) {
			dst->value = over->value;
		} else if (over->a == 0) {
			dst->value = under->value;
		} else if (under->a == 255) {
			uint8_t ra = ~over->a;
			dst->b = (over->b*over->a + under->b*ra)>>8;
			dst->g = (over->g*over->a + under->g*ra)>>8;
			dst->r = (over->r*over->a + under->r*ra)>>8;
			dst->a = 255;
		} else {
			uint8_t ra = ~over->a;
			uint8_t alpha = 255 - (ra*(255 - under->a)>>8);
			double inv_alpha = 1.0/alpha;
			dst->b = (over->b*over->a + ((under->b*under->a*ra)>>8))*inv_alpha;
			dst->g = (over->g*over->a + ((under->g*under->a*ra)>>8))*inv_alpha;
			dst->r = (over->r*over->a + ((under->r*under->a*ra)>>8))*inv_alpha;
			dst->a = alpha;
		}

		over++;
		under++;
		dst++;
	}
}

inline void BltLineAlphaBlend_SSE_unaligned(
	UCvPixel* dst,
	const UCvPixel* under,
	const UCvPixel* over,
	int blt_width)
{
	//4ピクセルずつ処理するときのループ回数を求める
	int nloop = blt_width >> 2; //blt_width / 4

	int x;
	for(x = 0; x < nloop; x++){
		__m128i xmunder = _mm_loadu_si128((const __m128i*)under);
		__m128i xmover = _mm_loadu_si128((const __m128i*)over);

		__m128i xmdst;
		ALPHABLEND_SSE(xmdst, xmunder, xmover);
		_mm_storeu_si128((__m128i*)dst, xmdst);

		over += 4;
		under += 4;
		dst += 4;
	}

	//波数を1ピクセルずつ処理
	int bn = blt_width & 0x3; //blt_width % 4
	BltLineAlphaBlend(
		dst,
		under,
		over,
		bn);
}

inline void SubBltLineAlphaBlend_SSE(
	UCvPixel* dst,
	const UCvPixel* under,
	const UCvPixel* over,
	int blt_width)
{
	assert (DIFF_16BYTE_ALIGNMENT(under) == DIFF_16BYTE_ALIGNMENT(over));
	assert (IS_16BYTE_ALIGNMENT(dst) == IS_16BYTE_ALIGNMENT(under));

	//4ピクセルずつ処理するときのループ回数を求める
	int nloop = blt_width >> 2; //blt_width / 4

	int x;
	for(x = 0; x < nloop; x++){
		__m128i xmunder = _mm_load_si128((const __m128i*)under);
		__m128i xmover = _mm_load_si128((const __m128i*)over);

		__m128i xmdst;
		ALPHABLEND_SSE(xmdst, xmunder, xmover);
		_mm_store_si128((__m128i*)dst, xmdst);

		over += 4;
		under += 4;
		dst += 4;
	}

	//1ピクセルずつ処理
	int bn = blt_width & 0x3; //blt_width % 4
	BltLineAlphaBlend(
		dst,
		under,
		over,
		bn);
	return;
}

template <int _SHIFT>
void ShiftSubBltLineAlphaBlend_SSE(
	UCvPixel* dst,
	const UCvPixel* under,
	const UCvPixel* over,
	int blt_width)
{
	UCvPixel* _dst = dst;
	const UCvPixel* _over = over;
	assert(DIFF_16BYTE_ALIGNMENT(under) == DIFF_16BYTE_ALIGNMENT(over));
	assert(IS_16BYTE_ALIGNMENT(under) && IS_16BYTE_ALIGNMENT(over));

	int nshift_pix = _SHIFT >> 2;
	int nloop = (blt_width >> 2) - 1; //(blt_width / 4) - 1

	__m128i xmunder, xmunder0, xmunder1, xmover, xmover0, xmover1;
	xmunder0 = _mm_load_si128((const __m128i*)under);
	xmover0 = _mm_load_si128((const __m128i*)over);

	//_SHIFT byte分処理
	//出力メモリを次回からアライメントを合わせて書き込めるようにする。
	BltLineAlphaBlend(dst, under, over, nshift_pix);
	dst = dst + nshift_pix;
	assert(IS_16BYTE_ALIGNMENT(dst));
	//
	xmunder0 = _mm_srli_si128(xmunder0, _SHIFT); //xmunder0 >> _SHIFT
	xmover0 = _mm_srli_si128(xmover0, _SHIFT); //xmover0 >> _SHIFT

	int x;
	for(x = 0; x < nloop; x++){
		//
		xmunder = _mm_load_si128((const __m128i*)(under + 4));
		xmover = _mm_load_si128((const __m128i*)(over + 4));
		//読み込んだ分をアライメントを合わせて書き込む時に
		//合うようにシフト演算でずらしてOR演算で前回の残りと組み合わせる。
		xmunder1 = _mm_slli_si128(xmunder, 16 - _SHIFT); //xmunder << (16 - _SHIFT)
		xmover1 = _mm_slli_si128(xmover, 16 - _SHIFT); //xmmover << (16 - _SHIFT)
		xmunder1 = _mm_or_si128(xmunder1, xmunder0);
		xmover1 = _mm_or_si128(xmover1, xmover0);
		//次回のループで使う分をずらして残しておく
		xmunder0 = _mm_srli_si128(xmunder, _SHIFT); //xmunder >> _SHIFT
		xmover0 = _mm_srli_si128(xmover, _SHIFT); //xmover >> _SHIFT

		__m128i xmdst;
		ALPHABLEND_SSE(xmdst, xmunder1, xmover1);
		_mm_store_si128((__m128i*)dst, xmdst);

		over += 4;
		under += 4;
		dst += 4;
	}

	//波数を1ピクセルずつ処理
	int bn = blt_width - (nloop << 2) - nshift_pix;
	BltLineAlphaBlend(
		dst,
		under + nshift_pix,
		over + nshift_pix,
		bn);

	return;
}

inline void BltLineAlphaBlend_SSE(
	UCvPixel* dst,
	const UCvPixel* under,
	const UCvPixel* over,
	int blt_width)
{
	assert (DIFF_16BYTE_ALIGNMENT(under) == DIFF_16BYTE_ALIGNMENT(over));

	if (IS_16BYTE_ALIGNMENT(dst) && IS_16BYTE_ALIGNMENT(under)) {
		//入出力ともアライメントがあってるならSSEで処理
		SubBltLineAlphaBlend_SSE(dst, under, over, blt_width);
		return;
	}

	int dif_d = DIFF_16BYTE_ALIGNMENT(dst); //アライメントのずれ量を求める
	int dif_s = DIFF_16BYTE_ALIGNMENT(under);
	assert((dif_d & 0x3) == 0); //1pixel、4byteなので4の倍数になるはず
	assert((dif_s & 0x3) == 0); //

	if (dif_d == dif_s) {
		int dif_npix = dif_s >> 2; //アライメントがあっていないピクセル数を得る
		//アライメントがあっていない分を1ピクセルずつしょり
		BltLineAlphaBlend(
			dst,
			under,
			over,
			dif_npix);
		//残りはSSEで
		SubBltLineAlphaBlend_SSE(
			dst + dif_npix,
			under + dif_npix,
			over + dif_npix,
			blt_width - dif_npix);
	} else {
		//読み込み元のアライメントを合わせる。
		int dif_npix = dif_s >> 2;
		BltLineAlphaBlend(
			dst,
			under,
			over,
			dif_npix);
		//書き込み先のアライメントのズレを再度計算
		dst += dif_npix;
		under += dif_npix;
		over += dif_npix;
		int nshift_byte = DIFF_16BYTE_ALIGNMENT(dst);
		assert((nshift_byte & 0x3) == 0);
		switch (nshift_byte) {
		case 4: //1pixelずれ
			ShiftSubBltLineAlphaBlend_SSE<4>(dst, under, over, blt_width);
			break;
		case 8: //2pixelずれ
			ShiftSubBltLineAlphaBlend_SSE<8>(dst, under, over, blt_width);
			break;
		case 12: //3pixelずれ
			ShiftSubBltLineAlphaBlend_SSE<12>(dst, under, over, blt_width);
			break;
		default:
			assert(0);
		}
	}
}

inline void SubBltLineAlphaBlend_SSE_nocache(
	UCvPixel* dst,
	const UCvPixel* under,
	const UCvPixel* over,
	int blt_width)
{
	assert (DIFF_16BYTE_ALIGNMENT(under) == DIFF_16BYTE_ALIGNMENT(over));
	assert (IS_16BYTE_ALIGNMENT(dst) == IS_16BYTE_ALIGNMENT(under));

	int nloop = blt_width >> 2; //blt_width / 4

	int x;
	for(x = 0; x < nloop; x++){
		__m128i xmunder = _mm_load_si128((const __m128i*)under);
		__m128i xmover = _mm_load_si128((const __m128i*)over);

		__m128i xmdst;
		ALPHABLEND_SSE(xmdst, xmunder, xmover);
		_mm_stream_si128((__m128i*)dst, xmdst);

		over += 4;
		under += 4;
		dst += 4;
	}

	int bn = blt_width & 0x3; //blt_width % 4
	BltLineAlphaBlend(
		dst,
		under,
		over,
		bn);
	return;
}

template <int _SHIFT>
void ShiftSubBltLineAlphaBlend_SSE_nocache(
	UCvPixel* dst,
	const UCvPixel* under,
	const UCvPixel* over,
	int blt_width)
{
	assert(DIFF_16BYTE_ALIGNMENT(under) == DIFF_16BYTE_ALIGNMENT(over));
	assert(IS_16BYTE_ALIGNMENT(under) && IS_16BYTE_ALIGNMENT(over));

	int nshift_pix = _SHIFT >> 2;
	int nloop = (blt_width >> 2) - 1; //(blt_width / 4) - 1

	__m128i xmunder, xmunder0, xmunder1, xmover, xmover0, xmover1;
	xmunder0 = _mm_load_si128((const __m128i*)under);
	xmover0 = _mm_load_si128((const __m128i*)over);

	//_SHIFT byte分処理
	BltLineAlphaBlend(dst, under, over, nshift_pix);
	dst = dst + nshift_pix;
	assert(IS_16BYTE_ALIGNMENT(dst));

	xmunder0 = _mm_srli_si128(xmunder0, _SHIFT); //xmunder0 >> _SHIFT
	xmover0 = _mm_srli_si128(xmover0, _SHIFT); //xmover0 >> _SHIFT

	int x;
	for(x = 0; x < nloop; x++){
		xmunder = _mm_load_si128((const __m128i*)(under + 4));
		xmover = _mm_load_si128((const __m128i*)(over + 4));
		xmunder1 = _mm_slli_si128(xmunder, 16 - _SHIFT); //xmunder << (16 - _SHIFT)
		xmover1 = _mm_slli_si128(xmover, 16 - _SHIFT); //xmmover << (16 - _SHIFT)
		xmunder1 = _mm_or_si128(xmunder1, xmunder0);
		xmover1 = _mm_or_si128(xmover1, xmover0);
		xmunder0 = _mm_srli_si128(xmunder, _SHIFT); //xmunder >> _SHIFT
		xmover0 = _mm_srli_si128(xmover, _SHIFT); //xmover >> _SHIFT

		__m128i xmdst;
		ALPHABLEND_SSE(xmdst, xmunder1, xmover1);
		_mm_store_si128((__m128i*)dst, xmdst);

		over += 4;
		under += 4;
		dst += 4;
	}

	int bn = blt_width - (nloop << 2) - nshift_pix;
	BltLineAlphaBlend(
		dst,
		under + nshift_pix,
		over + nshift_pix,
		bn);
	return;
}

inline void BltLineAlphaBlend_SSE_nocache(
	UCvPixel* dst,
	const UCvPixel* under,
	const UCvPixel* over,
	int blt_width)
{
	assert (DIFF_16BYTE_ALIGNMENT(under) == DIFF_16BYTE_ALIGNMENT(over));

	if (IS_16BYTE_ALIGNMENT(dst) && IS_16BYTE_ALIGNMENT(under)) {
		SubBltLineAlphaBlend_SSE(dst, under, over, blt_width);
		return;
	}

	int dif_d = DIFF_16BYTE_ALIGNMENT(dst);
	int dif_s = DIFF_16BYTE_ALIGNMENT(under);
	assert((dif_d & 0x3) == 0); //1pixel、4byteなので4の倍数になるはず
	assert((dif_s & 0x3) == 0); //

	if (dif_d == dif_s) {
		int dif_npix = dif_s >> 2; //アライメントがあっていないピクセル数を得る
		BltLineAlphaBlend(
			dst,
			under,
			over,
			dif_npix);
		SubBltLineAlphaBlend_SSE_nocache(
			dst + dif_npix,
			under + dif_npix,
			over + dif_npix,
			blt_width - dif_npix);
	} else {
		//読み込み元のアライメントを合わせる。
		int dif_npix = dif_s >> 2;
		BltLineAlphaBlend(
			dst,
			under,
			over,
			dif_npix);
		//
		dst += dif_npix;
		under += dif_npix;
		over += dif_npix;
		int nshift_byte = DIFF_16BYTE_ALIGNMENT(dst);
		assert((nshift_byte & 0x3) == 0);
		switch (nshift_byte) {
		case 4: //1pixelずれ
			ShiftSubBltLineAlphaBlend_SSE_nocache<4>(dst, under, over, blt_width);
			break;
		case 8: //2pixelずれ
			ShiftSubBltLineAlphaBlend_SSE_nocache<8>(dst, under, over, blt_width);
			break;
		case 12: //3pixelずれ
			ShiftSubBltLineAlphaBlend_SSE_nocache<12>(dst, under, over, blt_width);
			break;
		default:
			assert(0);
		}
	}
}

void BltAlphaBlend1(
	IplImage* dst_img,
	int dst_startX,
	int dst_startY,
	int blt_width,
	int blt_height,
	const IplImage* under_img,
	int under_startX,
	int under_startY,
	const IplImage* over_img,
	int over_startX,
	int over_startY)
{
	int y;
	UCvPixel *over_line = GetPixelAddress32(over_img, over_startX, over_startY);
	UCvPixel *under_line = GetPixelAddress32(under_img, under_startX, under_startY);
	UCvPixel *dst_line = GetPixelAddress32(dst_img, dst_startX, dst_startY);
	for(y = 0; y < blt_height; y++){

		BltLineAlphaBlend(dst_line, under_line, over_line, blt_width);

		over_line = GetNextLineAddress32(over_img, over_line);
		under_line = GetNextLineAddress32(under_img, under_line);
		dst_line = GetNextLineAddress32(dst_img, dst_line);
	}
}

void BltAlphaBlend2(
	IplImage* dst_img,
	int dst_startX,
	int dst_startY,
	int blt_width,
	int blt_height,
	const IplImage* under_img,
	int under_startX,
	int under_startY,
	const IplImage* over_img,
	int over_startX,
	int over_startY)
{
	int y;
	UCvPixel *over_line = GetPixelAddress32(over_img, over_startX, over_startY);
	UCvPixel *under_line = GetPixelAddress32(under_img, under_startX, under_startY);
	UCvPixel *dst_line = GetPixelAddress32(dst_img, dst_startX, dst_startY);
	for(y = 0; y < blt_height; y++){

		BltLineAlphaBlend_SSE_unaligned(
			dst_line,
			under_line,
			over_line,
			blt_width);


		over_line = GetNextLineAddress32(over_img, over_line);
		under_line = GetNextLineAddress32(under_img, under_line);
		dst_line = GetNextLineAddress32(dst_img, dst_line);
	}
}

void BltAlphaBlend3(
	IplImage* dst_img,
	int dst_startX,
	int dst_startY,
	int blt_width,
	int blt_height,
	const IplImage* under_img,
	int under_startX,
	int under_startY,
	const IplImage* over_img,
	int over_startX,
	int over_startY)
{
	int y;
	UCvPixel *over_line = GetPixelAddress32(over_img, over_startX, over_startY);
	UCvPixel *under_line = GetPixelAddress32(under_img, under_startX, under_startY);
	UCvPixel *dst_line = GetPixelAddress32(dst_img, dst_startX, dst_startY);
	for(y = 0; y < blt_height; y++){

		BltLineAlphaBlend_SSE(
			dst_line,
			under_line,
			over_line,
			blt_width);


		over_line = GetNextLineAddress32(over_img, over_line);
		under_line = GetNextLineAddress32(under_img, under_line);
		dst_line = GetNextLineAddress32(dst_img, dst_line);
	}
}

void BltAlphaBlend4(
	IplImage* dst_img,
	int dst_startX,
	int dst_startY,
	int blt_width,
	int blt_height,
	const IplImage* under_img,
	int under_startX,
	int under_startY,
	const IplImage* over_img,
	int over_startX,
	int over_startY)
{
	int y;
	UCvPixel *over_line = GetPixelAddress32(over_img, over_startX, over_startY);
	UCvPixel *under_line = GetPixelAddress32(under_img, under_startX, under_startY);
	UCvPixel *dst_line = GetPixelAddress32(dst_img, dst_startX, dst_startY);
	for(y = 0; y < blt_height; y++){

		BltLineAlphaBlend_SSE_nocache(
			dst_line,
			under_line,
			over_line,
			blt_width);


		over_line = GetNextLineAddress32(over_img, over_line);
		under_line = GetNextLineAddress32(under_img, under_line);
		dst_line = GetNextLineAddress32(dst_img, dst_line);
	}
}

//sseを使った4ch同士のalphablend 
int main(void)
{
	//cvCreateImageで帰ってくる画像データのアドレスは先頭が16byteアライメントに合わせてある
	//これをわざとずらす。
	int dst_dif = 1; //この数分、正方向にずらす
	int src_dif = 2; //この数分、正方向にずらす

	int x,y;

	int r,g,b;
	int r1, g1, b1;
	int r2, g2, b2;

	UCvPixel *over_pix;
	UCvPixel *under_pix;
	UCvPixel *dst_pix;
	UCvPixel *over_line;
	UCvPixel *under_line;
	UCvPixel *dst_line;

	IplImage* test1 = cvLoadImage("test1.bmp");
	IplImage* test2 = cvLoadImage("test2.bmp");
	IplImage* b_ch = cvCreateImage(cvGetSize(test1), IPL_DEPTH_8U, 1);
	IplImage* g_ch = cvCreateImage(cvGetSize(test1), IPL_DEPTH_8U, 1);
	IplImage* r_ch = cvCreateImage(cvGetSize(test1), IPL_DEPTH_8U, 1);
	IplImage* a_ch1 = cvCreateImage(cvGetSize(test1), IPL_DEPTH_8U, 1);
	IplImage* a_ch2 = cvCreateImage(cvGetSize(test1), IPL_DEPTH_8U, 1);
	IplImage* img1 = cvCreateImage(cvGetSize(test1), IPL_DEPTH_8U, 4);
	IplImage* img2 = cvCreateImage(cvGetSize(test1), IPL_DEPTH_8U, 4);
	IplImage* img3 = cvCreateImage(cvGetSize(test1), IPL_DEPTH_8U, 4);
	IplImage* img4 = cvCreateImage(cvGetSize(test1), IPL_DEPTH_8U, 4);
	IplImage* img5 = cvCreateImage(cvGetSize(test1), IPL_DEPTH_8U, 4);
	IplImage* img6 = cvCreateImage(cvGetSize(test1), IPL_DEPTH_8U, 4);
	
	//上になる画像のアルファ値を均等に分布させる
	uint8_t* line1 = GetPixelAddress(a_ch1, 0, 0);
	for (y = 0; y < a_ch1->height; y++) {
		uint8_t* pix = line1;
		for (x = 0; x < a_ch1->width; x++) {
			(*pix++) = ((double)x / a_ch1->width) * 255;
		}
		line1 = GetNextLineAddress(a_ch1, line1);
	}

	//下になる画像のアルファ値を均等に分布させる
	uint8_t* line2 = GetPixelAddress(a_ch2, 0, 0);
	for (y = 0; y < a_ch2->height; y++) {
		uint8_t* pix = line2;
		for (x = 0; x < a_ch2->width; x++) {
			(*pix++) = ((double)y / a_ch2->height) * 255;
		}
		line2 = GetNextLineAddress(a_ch2, line2);
	}

	cvSplit(test1, b_ch, g_ch, r_ch, NULL);
	cvMerge(b_ch, g_ch, r_ch, a_ch1, img1);
	cvSplit(test2, b_ch, g_ch, r_ch, NULL);
	cvMerge(b_ch, g_ch, r_ch, a_ch2, img2);

	//ずれがある分、書き込み幅を減らす。
	int write_width = img1->width - max(dst_dif, src_dif);

	//非SSE
	IETimer timer;
	BltAlphaBlend1(
		img3,
		dst_dif, 0,
		write_width, img3->height,
		img2,
		src_dif, 0,
		img1,
		src_dif, 0);
	std::cout << timer.elapsed() << " msec" << std::endl;
	cvSaveImage("test3.bmp", img3);

	//SSE: _mm_loadu_ps + _mm_storeu_ps
	timer.restart();
	BltAlphaBlend2(
		img4,
		dst_dif, 0,
		write_width, img4->height,
		img2,
		src_dif, 0,
		img1,
		src_dif, 0);
	std::cout << timer.elapsed() << " msec" << std::endl;
	cvSaveImage("test4.bmp", img4);

	//SSE: _mm_load_ps + _mm_store_ps			
	timer.restart();
	BltAlphaBlend3(
		img5,
		dst_dif, 0,
		write_width, img5->height,
		img2,
		src_dif, 0,
		img1,
		src_dif, 0);
	std::cout << timer.elapsed() << " msec" << std::endl;
	cvSaveImage("test5.bmp", img5);

	//SSE: _mm_load_ps + _mm_stream_si128
	timer.restart();
	BltAlphaBlend4(
		img6,
		dst_dif, 0,
		write_width, img6->height,
		img2,
		src_dif, 0,
		img1,
		src_dif, 0);
	std::cout << timer.elapsed() << " msec" << std::endl;
	cvSaveImage("test6.bmp", img6);


	cvReleaseImage(&b_ch);
	cvReleaseImage(&g_ch);
	cvReleaseImage(&r_ch);
	cvReleaseImage(&a_ch1);
	cvReleaseImage(&a_ch2);
	cvReleaseImage(&img1);
	cvReleaseImage(&img2);
	cvReleaseImage(&img3);
	cvReleaseImage(&img4);
	cvReleaseImage(&img5);
	cvReleaseImage(&img6);
}

2011-11-28

[][][]SSE用いたアルファブレンドの高速化3 アルファチャンネルをもつ画像同士のアルファブレンド

↓の続き

http://d.hatena.ne.jp/hiroki0/20090914/1252931917

http://d.hatena.ne.jp/hiroki0/20100320/1269074009

使用するアルファブレンド計算式

チャンネルサイズ8bitとして

a_{dst} = 255 - (255 - a_{over})(255 - a_{under}) / 255

d_{dst} = (d_{over} a_{over} + (d_{under} a_{under} (255 - a_{over}) / 255) / a_{dst}

ここで、aはアルファ値、dは画素値である。


この計算式をコードにすると以下のようになる。

255による除算は、高速化のために256の除算に変更しシフト演算で処理している。

また、計算量を減らすためにアルファ値により分岐を行なっている。

今回は、この処理をSSEにより実装して計測を行う。

if ((over_pix->a == 255) || (under_pix->a == 0)) {
	dst_pix->value = over_pix->value;
} else if (over_pix->a == 0) {
	dst_pix->value = under_pix->value;
} else if (under_pix->a == 255) {
	uint8_t ra = ~over_pix->a;
	dst_pix->b = (over_pix->b*over_pix->a + under_pix->b*ra)>>8;
	dst_pix->g = (over_pix->g*over_pix->a + under_pix->g*ra)>>8;
	dst_pix->r = (over_pix->r*over_pix->a + under_pix->r*ra)>>8;
	dst_pix->a = 255;
} else {
	uint8_t ra = ~over_pix->a;
	uint8_t alpha = 255 - (ra*(255 - under_pix->a)>>8);
	double inv_alpha = 1.0/alpha;
	dst_pix->b = (over_pix->b*over_pix->a + ((under_pix->b*under_pix->a*ra)>>8))*inv_alpha;
	dst_pix->g = (over_pix->g*over_pix->a + ((under_pix->g*under_pix->a*ra)>>8))*inv_alpha;
	dst_pix->r = (over_pix->r*over_pix->a + ((under_pix->r*under_pix->a*ra)>>8))*inv_alpha;
	dst_pix->a = alpha;
}

計測方法

32bit,64bit向けに2通りに実行ファイルを作成、それぞれについて

  1. SSEを使わない処理
  2. _mm_loadu_psでメモリ読み込み、_mm_storeu_psでメモリ書き込み
  3. _mm_load_psでメモリ読み込み、_mm_store_psでメモリ書き込み
  4. _mm_load_psでメモリ読み込み、_mm_stream_si128でメモリ書き込み

の4通りの処理の処理速度を計測する。

  • チャンネルサイズ8bit 5700x5700BGR画像を2枚読み込み、アルファ値を指定してアルファブレンドを行う。
  • 画像の保存、読み込みはopenCV 2.3を用いた。
  • アルファブレンド部分の処理のみ処理時間を計測した。
  • アライメントは常に合っている状態で処理を行う。

アルファブレンドの処理は、アルファ値により分岐を入れているので

ただコピーを行う部分、アルファ値1つによるアルファブレンドを行う部分、

アルファ値2つでアルファブレンドを行う部分の3箇所の計測を行いたい。

なので、与えるアルファチャンネルは、

  1. 上下ともにアルファ値255
  2. 下になる画像全てアルファ値255、上になる画像はアルファ値を均等に分布
  3. 上下ともにアルファ値を均等に分布

の三通りとした。

計測に使用した環境

計測結果

上下ともにアルファ値255
  • 32bit向け実行ファイル
SSE_mm_loadu_ps + _mm_storeu_ps_mm_load_ps + _mm_store_ps_mm_load_ps + _mm_stream_si128
1回目 111.002 msec 95.006 msec 90.9281 msec 90.853 msec
2回目 87.0272 msec 70.3019 msec 64.3998 msec 77.862 msec
3回目 86.3182 msec 66.67 msec 72.2693 msec 62.0172 msec
  • 64bit向け実行ファイル
SSE_mm_loadu_ps + _mm_storeu_ps_mm_load_ps + _mm_store_ps_mm_load_ps + _mm_stream_si128
1回目 85.2114 msec 82.4653 msec 85.074 msec 83.51 msec
2回目 56.3726 msec 63.0728 msec 71.2231 msec 56.5069 msec
3回目 55.2358 msec 62.6271 msec 62.9312 msec 58.8877 msec

このアルファ値では、分岐処理により上の画像の画素を出力メモリにコピーする処理だけになる。

SSEのロード・ストア命令による違いはバラツキがあり、殆ど無いと思われる。

32bitでは、SSEを使ったほうが1.1倍程度速くなっている。

32bitと64bitの結果を比較して、SSEを使わない処理では64bitの方が早くなっている。

下になる画像全てアルファ値255、上になる画像はアルファ値を均等に分布
  • 32bit向け実行ファイル
SSE_mm_loadu_ps + _mm_storeu_ps_mm_load_ps + _mm_store_ps_mm_load_ps + _mm_stream_si128
1回目 193.727 msec 114.325 msec 94.4707 msec 101.514 msec
2回目 187.955 msec 93.6102 msec 93.4529 msec 91.6304 msec
3回目 190.177 msec 104.997 msec 94.9949 msec 91.032 msec
  • 64bit向け実行ファイル
SSE_mm_loadu_ps + _mm_storeu_ps_mm_load_ps + _mm_store_ps_mm_load_ps + _mm_stream_si128
1回目 167.373 msec 99.5402 msec 89.3116 msec 100.947 msec
2回目 198.225 msec 91.6833 msec 99.7168 msec 89.8282 msec
3回目 186.376 msec 87.6821 msec 98.8608 msec 97.6576 msec

このアルファ値では、分岐処理により主に一つのアルファ値によるアルファブレンドを行う処理になる。

SSEのロード・ストア命令による違いはバラツキがあり、殆ど無いと思われる。

32bitと64bitによる違いは殆ど見られない。

また、SSEにより1.6倍〜2.0倍程度速くなっていることがわかる。

上下ともにアルファ値を均等に分布
  • 32bit向け実行ファイル
SSE_mm_loadu_ps + _mm_storeu_ps_mm_load_ps + _mm_store_ps_mm_load_ps + _mm_stream_si128
1回目 615.202 msec 182.197 msec 208.116 msec 176.766 msec
2回目 576.132 msec 182.21 msec 196.25 msec 172.828 msec
3回目 586.831 msec 176.329 msec 179.473 msec 171.598 msec
  • 64bit向け実行ファイル
SSE_mm_loadu_ps + _mm_storeu_ps_mm_load_ps + _mm_store_ps_mm_load_ps + _mm_stream_si128
1回目 327.867 msec 208.067 msec 204.649 msec 170.796 msec
2回目 328.621 msec 173.355 msec 176.605 msec 160.269 msec
3回目 336.624 msec 167.226 msec 168.419 msec 170.916 msec

このアルファ値では、分岐処理により主に2つのアルファ値によるアルファブレンドを行う処理になる。

SSEのロード・ストア命令による違いはバラツキがあり、殆ど無いと思われる。

SSEの処理の32bitの方は64bitに対して1.8倍程度遅くなっており、かなり時間がかかっていることがわかる。

SSEによる速度の向上は32bitで約3倍64bitで約1.8倍である。

まとめ

測定条件のいずれの場合もSSEのロード・ストア命令による違いは見られなかった。

今回は、アライメントは常に合っている状態で計測を行ったのでアライメントをずらいた状態では違いが見られるかもしれない。

コード

SSEでの実装方針は、4ピクセル分のデータをロード、SSEレジスタに16bitで8つのデータを持ち2ピクセルづつ処理し、

4ピクセル分ストアするようにする。

2つのアルファ値をもつアルファブレンドの処理では途中、除算処理が入る。

SSEの命令には整数除算が無いので、[16bit整数]→[32bit整数]→[32bit浮動小数]と変換して

_mm_rcp_ps()で逆数を求めて除算を行う。

_mm_rcp_ps()を用いることで_mm_div_ps()よりも高速に計算することができる。

_mm_rcp_ps()は11bit程度しか精度がないが、最後は8bit整数にするので問題ないはず。

#include <iostream>
#include <opencv/cv.h>
#include <opencv/highgui.h>
#include "IETimer.h"

union UCvPixel32
{
	uint32_t value;
	struct
	{
		uint8_t b,g,r,a;
	};
};
typedef UCvPixel32 UCvPixel;

inline uint8_t* GetNextLineAddress(const IplImage* image, uint8_t* addr)
{
	return addr + image->widthStep;
}

inline uint8_t* GetPixelAddress(const IplImage* image, int x, int y)
{
	assert(image);
	assert(image->depth == IPL_DEPTH_8U);
	assert((0 <= x && x <= image->width-1) &&
		(0 <= y && y <= image->height-1));

	return (uint8_t*)(image->imageData + y*image->widthStep + x*image->nChannels);
}

inline UCvPixel32* GetNextLineAddress32(const IplImage* image, UCvPixel32* addr)
{
	return (UCvPixel32*)((uint8_t*)addr + image->widthStep);
}

inline UCvPixel32* GetPixelAddress32(const IplImage* image, int x, int y)
{
	assert(image);
	assert(image->depth == IPL_DEPTH_8U);
	assert((0 <= x && x <= image->width-1) &&
		(0 <= y && y <= image->height-1));

	return (UCvPixel32*)(image->imageData + y*image->widthStep + x*image->nChannels);
}

int main(void)
{
	int x,y;

	int r,g,b;
	int r1, g1, b1;
	int r2, g2, b2;

	UCvPixel *over_pix;
	UCvPixel *under_pix;
	UCvPixel *dst_pix;
	UCvPixel *over_line;
	UCvPixel *under_line;
	UCvPixel *dst_line;

	IplImage* test1 = cvLoadImage("test1.bmp");
	IplImage* test2 = cvLoadImage("test2.bmp");
	IplImage* b_ch = cvCreateImage(cvGetSize(test1), IPL_DEPTH_8U, 1);
	IplImage* g_ch = cvCreateImage(cvGetSize(test1), IPL_DEPTH_8U, 1);
	IplImage* r_ch = cvCreateImage(cvGetSize(test1), IPL_DEPTH_8U, 1);
	IplImage* a_ch1 = cvCreateImage(cvGetSize(test1), IPL_DEPTH_8U, 1);
	IplImage* a_ch2 = cvCreateImage(cvGetSize(test1), IPL_DEPTH_8U, 1);
	IplImage* img1 = cvCreateImage(cvGetSize(test1), IPL_DEPTH_8U, 4);
	IplImage* img2 = cvCreateImage(cvGetSize(test1), IPL_DEPTH_8U, 4);
	IplImage* img3 = cvCreateImage(cvGetSize(test1), IPL_DEPTH_8U, 4);
	IplImage* img4 = cvCreateImage(cvGetSize(test1), IPL_DEPTH_8U, 4);
	IplImage* img5 = cvCreateImage(cvGetSize(test1), IPL_DEPTH_8U, 4);
	IplImage* img6 = cvCreateImage(cvGetSize(test1), IPL_DEPTH_8U, 4);

	//上になる画像のアルファ値をすべて255にする
	cvSet(a_ch1, cvScalar(255));
	//下になる画像のアルファ値をすべて255にする
	cvSet(a_ch2, cvScalar(255));
	
	//上になる画像のアルファ値を均等に分布させる
	//uint8_t* line1 = GetPixelAddress(a_ch1, 0, 0);
	//for (y = 0; y < a_ch1->height; y++) {
	//	uint8_t* pix = line1;
	//	for (x = 0; x < a_ch1->width; x++) {
	//		(*pix++) = ((double)x / a_ch1->width) * 255;
	//	}
	//	line1 = GetNextLineAddress(a_ch1, line1);
	//}

	//下になる画像のアルファ値を均等に分布させる
	//uint8_t* line2 = GetPixelAddress(a_ch2, 0, 0);
	//for (y = 0; y < a_ch2->height; y++) {
	//	uint8_t* pix = line2;
	//	for (x = 0; x < a_ch2->width; x++) {
	//		(*pix++) = ((double)y / a_ch2->height) * 255;
	//	}
	//	line2 = GetNextLineAddress(a_ch2, line2);
	//}

	cvSplit(test1, b_ch, g_ch, r_ch, NULL);
	cvMerge(b_ch, g_ch, r_ch, a_ch1, img1);
	cvSplit(test2, b_ch, g_ch, r_ch, NULL);
	cvMerge(b_ch, g_ch, r_ch, a_ch2, img2);

	//非SSE
	IETimer timer;
	over_line = GetPixelAddress32(img1, 0, 0);
	under_line = GetPixelAddress32(img2, 0, 0);
	dst_line = GetPixelAddress32(img3, 0, 0);
	for(y=0; y<img1->height; y++){
		over_pix = over_line;
		under_pix = under_line;
		dst_pix = dst_line;
		for(x=0; x<img1->width; x++){

			//alpha blend
			if ((over_pix->a == 255) || (under_pix->a == 0)) {
				dst_pix->value = over_pix->value;
			} else if (over_pix->a == 0) {
				dst_pix->value = under_pix->value;
			} else if (under_pix->a == 255) {
				uint8_t ra = ~over_pix->a;
				dst_pix->b = (over_pix->b*over_pix->a + under_pix->b*ra)>>8;
				dst_pix->g = (over_pix->g*over_pix->a + under_pix->g*ra)>>8;
				dst_pix->r = (over_pix->r*over_pix->a + under_pix->r*ra)>>8;
				dst_pix->a = 255;
			} else {
				uint8_t ra = ~over_pix->a;
				uint8_t alpha = 255 - (ra*(255 - under_pix->a)>>8);
				double inv_alpha = 1.0/alpha;
				dst_pix->b = (over_pix->b*over_pix->a + ((under_pix->b*under_pix->a*ra)>>8))*inv_alpha;
				dst_pix->g = (over_pix->g*over_pix->a + ((under_pix->g*under_pix->a*ra)>>8))*inv_alpha;
				dst_pix->r = (over_pix->r*over_pix->a + ((under_pix->r*under_pix->a*ra)>>8))*inv_alpha;
				dst_pix->a = alpha;
			}

			over_pix++;
			under_pix++;
			dst_pix++;
		}
		over_line = GetNextLineAddress32(img1, over_line);
		under_line = GetNextLineAddress32(img2, under_line);
		dst_line = GetNextLineAddress32(img3, dst_line);
	}
	std::cout << timer.elapsed() << " msec" << std::endl;
	cvSaveImage("test3.bmp", img3);

	//SSE: _mm_loadu_ps + _mm_storeu_ps
	timer.restart();
	{
		over_line = GetPixelAddress32(img1, 0, 0);
		under_line = GetPixelAddress32(img2, 0, 0);
		dst_line = GetPixelAddress32(img4, 0, 0);
		__m128i xmzero = _mm_setzero_si128();
		__m128i xmff16 = _mm_set1_epi16(0xff);
		for(y=0; y<img1->height; y++){
			over_pix = over_line;
			under_pix = under_line;
			dst_pix = dst_line;
			for(x=0; x<img1->width; x+=4){
				//4ピクセルロード
				__m128i under = _mm_castps_si128( _mm_loadu_ps((float*)under_pix) );
				__m128i over = _mm_castps_si128( _mm_loadu_ps((float*)over_pix) );

				__m128i xmo16, xmu16;
				__m128i xmlo, xmhi;
				__m128i xmover_alpha, xmover_ralpha, xmunder_alpha, xmunder_ralpha, xmalpha;
				__m128 xmf_lo, xmf_hi, xmf_alpha_lo, xmf_alpha_hi;

				//load lo double ward
				xmo16 = _mm_unpacklo_epi8(over, xmzero);
				xmu16 = _mm_unpacklo_epi8(under, xmzero);

				//アルファ値を各個読み出し比較し分岐へ
				int over_a1 = _mm_extract_epi16(xmo16, 3);
				int over_a2 = _mm_extract_epi16(xmo16, 7);
				int under_a1 = _mm_extract_epi16(xmu16, 3);
				int under_a2 = _mm_extract_epi16(xmu16, 7);
				if (((over_a1 == 255 && over_a2 == 255)) || (under_a1 == 0 && under_a2 == 0)) {
					xmlo = xmo16;
				} else if (under_a1 == 0 && under_a2 == 0) {
					xmlo = xmu16;
				} else if (under_a1 == 255 && under_a2 == 255) {
					//16bitのアルファ値8個をシャッフルをつかってならべ、
					//[a1,a1,a1,a1,a2,a2,a2]というようにする。
					xmover_alpha = _mm_shufflelo_epi16(xmo16, _MM_SHUFFLE(3,3,3,3));
					xmover_alpha = _mm_shufflehi_epi16(xmover_alpha, _MM_SHUFFLE(3,3,3,3));
					xmover_ralpha = _mm_sub_epi16(xmff16, xmover_alpha); //255 - over_alpha

					//2ピクセル同時にアルファブレンドの計算
					xmu16 = _mm_mullo_epi16(xmu16, xmover_ralpha); // under_pix * over_ralpha
					xmo16 = _mm_mullo_epi16(xmo16, xmover_alpha); // over_pix * over_alpha
					xmo16 = _mm_add_epi16(xmu16, xmo16); // (under_pix * over_ralpha + over_pix * over_alpha)
					xmlo = _mm_srli_epi16(xmo16, 8); // (under_pix * over_ralpha + over_pix * over_alpha) >> 8
					//アルファ値255を出力用のレジスタに各個書き込み
					xmlo = _mm_insert_epi16(xmlo, 255, 3); //write alpha
					xmlo = _mm_insert_epi16(xmlo, 255, 7); //write alpha
				} else {
					//16bitのアルファ値8個をシャッフルをつかってならべ
					//[a1,a1,a1,a1,a2,a2,a2]というようにする。
					//上になる画像と下になる画像の2つぶんを行なっておく
					xmover_alpha = _mm_shufflelo_epi16(xmo16, _MM_SHUFFLE(3,3,3,3));
					xmover_alpha = _mm_shufflehi_epi16(xmover_alpha, _MM_SHUFFLE(3,3,3,3));
					xmover_ralpha = _mm_sub_epi16(xmff16, xmover_alpha); // 255 - over_alpha
					xmunder_alpha = _mm_shufflelo_epi16(xmu16, _MM_SHUFFLE(3,3,3,3));
					xmunder_alpha = _mm_shufflehi_epi16(xmunder_alpha, _MM_SHUFFLE(3,3,3,3));
					xmunder_ralpha = _mm_sub_epi16(xmff16, xmunder_alpha); // 255 - under_alpha
					//新しくアルファ値になる値の計算
					//ここのアルファ値も[a1,a1,a1,a1,a2,a2,a2]となるようにしている
					xmalpha = _mm_sub_epi16(xmff16, _mm_srli_epi16( _mm_mullo_epi16(xmover_ralpha, xmunder_ralpha), 8)); //255 - ((over_ralpha * under_ralpha) >> 8)
					//16bit幅での2ピクセル同時処理
					//ここで 16bit乗算 → 8bit右シフト → 16bit乗算という順で計算しているので誤差が出る。
					xmu16 = _mm_mullo_epi16( _mm_srli_epi16( _mm_mullo_epi16(xmunder_alpha, xmover_ralpha), 8), xmu16); //under_pix * ((under_alpha * over_ralpha)>>8)
					xmo16 = _mm_mullo_epi16(xmo16, xmover_alpha); // over_pix * over_alpha
					xmo16 = _mm_add_epi16(xmu16, xmo16); // over_pix * over_alpha + (under_pix * under_alpha * over_ralpha)>>8

					//32bit浮動小数に変換してピクセル1の除算の計算
					xmf_lo = _mm_cvtepi32_ps( _mm_unpacklo_epi16(xmo16, xmzero) );
					xmf_alpha_lo = _mm_cvtepi32_ps( _mm_unpacklo_epi16(xmalpha, xmzero) );
					xmf_lo = _mm_mul_ps(xmf_lo, _mm_rcp_ps(xmf_alpha_lo));

					////32bit浮動小数に変換してピクセル2の除算の計算
					xmf_hi = _mm_cvtepi32_ps( _mm_unpackhi_epi16(xmo16, xmzero) );
					xmf_alpha_hi = _mm_cvtepi32_ps( _mm_unpackhi_epi16(xmalpha, xmzero) );
					xmf_hi = _mm_mul_ps(xmf_hi, _mm_rcp_ps(xmf_alpha_hi));

					//16bit整数に戻してレジスタに書き込み
					xmlo = _mm_packs_epi32(_mm_cvtps_epi32(xmf_lo), _mm_cvtps_epi32(xmf_hi));
					//新しくアルファ値になる値を取り出して各個書き込み
					int a1 = _mm_extract_epi16(xmalpha, 3);
					xmlo = _mm_insert_epi16(xmlo, a1, 3); //write alpha
					int a2 = _mm_extract_epi16(xmalpha, 7);
					xmlo = _mm_insert_epi16(xmlo, a2, 7); //write alpha
				}

				//load hi double ward
				xmo16 = _mm_unpackhi_epi8(over, xmzero);
				xmu16 = _mm_unpackhi_epi8(under, xmzero);

				over_a1 = _mm_extract_epi16(xmo16, 3);
				over_a2 = _mm_extract_epi16(xmo16, 7);
				under_a1 = _mm_extract_epi16(xmu16, 3);
				under_a2 = _mm_extract_epi16(xmu16, 7);
				if (((over_a1 == 255 && over_a2 == 255)) || (under_a1 == 0 && under_a2 == 0)) {
					xmhi = xmo16;
				} else if (under_a1 == 0 && under_a2 == 0) {
					xmhi = xmu16;
				} else if (under_a1 == 255 && under_a2 == 255) {
					xmover_alpha = _mm_shufflelo_epi16(xmo16, _MM_SHUFFLE(3,3,3,3));
					xmover_alpha = _mm_shufflehi_epi16(xmover_alpha, _MM_SHUFFLE(3,3,3,3));
					xmover_ralpha = _mm_sub_epi16(xmff16, xmover_alpha);

					xmu16 = _mm_mullo_epi16(xmu16, xmover_ralpha); // under_pix * over_ralpha
					xmo16 = _mm_mullo_epi16(xmo16, xmover_alpha); // over_pix * over_alpha
					xmo16 = _mm_add_epi16(xmu16, xmo16); // (under_pix * over_ralpha + over_pix * over_alpha)
					xmhi = _mm_srli_epi16(xmo16, 8); // (under_pix * over_ralpha + over_pix * over_alpha) >> 8
					xmhi = _mm_insert_epi16(xmhi, 255, 3); //write alpha
					xmhi = _mm_insert_epi16(xmhi, 255, 7); //write alpha
				} else {
					//
					xmover_alpha = _mm_shufflelo_epi16(xmo16, _MM_SHUFFLE(3,3,3,3));
					xmover_alpha = _mm_shufflehi_epi16(xmover_alpha, _MM_SHUFFLE(3,3,3,3));
					xmover_ralpha = _mm_sub_epi16(xmff16, xmover_alpha);
					xmunder_alpha = _mm_shufflelo_epi16(xmu16, _MM_SHUFFLE(3,3,3,3));
					xmunder_alpha = _mm_shufflehi_epi16(xmunder_alpha, _MM_SHUFFLE(3,3,3,3));
					xmunder_ralpha = _mm_sub_epi16(xmff16, xmunder_alpha);
					xmalpha = _mm_sub_epi16(xmff16, _mm_srli_epi16( _mm_mullo_epi16(xmover_ralpha, xmunder_ralpha), 8)); //255 - ((over_ralpha * under_ralpha) >> 8)

					xmu16 = _mm_mullo_epi16( _mm_srli_epi16( _mm_mullo_epi16(xmunder_alpha, xmover_ralpha), 8), xmu16); //under_pix * ((under_alpha * over_ralpha)>>8)
					xmo16 = _mm_mullo_epi16(xmo16, xmover_alpha); // over_pix * over_alpha
					xmo16 = _mm_add_epi16(xmu16, xmo16); // over_pix * over_alpha + (under_pix * ((under_alpha * over_ralpha)>>8)

					//calc pixel1
					xmf_lo = _mm_cvtepi32_ps( _mm_unpacklo_epi16(xmo16, xmzero) );
					xmf_alpha_lo = _mm_cvtepi32_ps( _mm_unpacklo_epi16(xmalpha, xmzero) );
					xmf_lo = _mm_mul_ps(xmf_lo, _mm_rcp_ps(xmf_alpha_lo));

					//calc pixel 2
					xmf_hi = _mm_cvtepi32_ps( _mm_unpackhi_epi16(xmo16, xmzero) );
					xmf_alpha_hi = _mm_cvtepi32_ps( _mm_unpackhi_epi16(xmalpha, xmzero) );
					xmf_hi = _mm_mul_ps(xmf_hi, _mm_rcp_ps(xmf_alpha_hi));

					//pack to 16bit data
					xmhi = _mm_packs_epi32(_mm_cvtps_epi32(xmf_lo), _mm_cvtps_epi32(xmf_hi));
					int a3 = _mm_extract_epi16(xmalpha, 3);
					xmhi = _mm_insert_epi16(xmhi, a3, 3); //write alpha
					int a4 = _mm_extract_epi16(xmalpha, 7);
					xmhi = _mm_insert_epi16(xmhi, a4, 7); //write alpha
				}

				//16bit幅、2ピクセルのデータが入った2つのレジスタを合わせて8bitへ
				//4ピクセルのデータをメモリへストア
				_mm_storeu_ps((float*)dst_pix,
					_mm_castsi128_ps( _mm_packus_epi16(xmlo, xmhi)) );

				//
				over_pix += 4;
				under_pix += 4;
				dst_pix += 4;
			}
			over_line = GetNextLineAddress32(img1, over_line);
			under_line = GetNextLineAddress32(img2, under_line);
			dst_line = GetNextLineAddress32(img4, dst_line);
		}
	}
	std::cout << timer.elapsed() << " msec" << std::endl;
	cvSaveImage("test4.bmp", img4);

	//SSE: _mm_load_ps + _mm_store_ps			
	timer.restart();
	{
		over_line = GetPixelAddress32(img1, 0, 0);
		under_line = GetPixelAddress32(img2, 0, 0);
		dst_line = GetPixelAddress32(img5, 0, 0);
		__m128i xmzero = _mm_setzero_si128();
		__m128i xmff16 = _mm_set1_epi16(0xff);
		for(y=0; y<img1->height; y++){
			over_pix = over_line;
			under_pix = under_line;
			dst_pix = dst_line;
			for(x=0; x<img1->width; x+=4){

				__m128i under = _mm_castps_si128( _mm_load_ps((float*)under_pix) );
				__m128i over = _mm_castps_si128( _mm_load_ps((float*)over_pix) );

				__m128i xmo16, xmu16;
				__m128i xmlo, xmhi;
				__m128i xmover_alpha, xmover_ralpha, xmunder_alpha, xmunder_ralpha, xmalpha;
				__m128 xmf_lo, xmf_hi, xmf_alpha_lo, xmf_alpha_hi;

				//load lo double ward
				xmo16 = _mm_unpacklo_epi8(over, xmzero);
				xmu16 = _mm_unpacklo_epi8(under, xmzero);

				int over_a1 = _mm_extract_epi16(xmo16, 3);
				int over_a2 = _mm_extract_epi16(xmo16, 7);
				int under_a1 = _mm_extract_epi16(xmu16, 3);
				int under_a2 = _mm_extract_epi16(xmu16, 7);
				if (((over_a1 == 255 && over_a2 == 255)) || (under_a1 == 0 && under_a2 == 0)) {
					xmlo = xmo16;
				} else if (under_a1 == 0 && under_a2 == 0) {
					xmlo = xmu16;
				} else if (under_a1 == 255 && under_a2 == 255) {
					xmover_alpha = _mm_shufflelo_epi16(xmo16, _MM_SHUFFLE(3,3,3,3));
					xmover_alpha = _mm_shufflehi_epi16(xmover_alpha, _MM_SHUFFLE(3,3,3,3));
					xmover_ralpha = _mm_sub_epi16(xmff16, xmover_alpha);

					xmu16 = _mm_mullo_epi16(xmu16, xmover_ralpha); // under_pix * over_ralpha
					xmo16 = _mm_mullo_epi16(xmo16, xmover_alpha); // over_pix * over_alpha
					xmo16 = _mm_add_epi16(xmu16, xmo16); // (under_pix * over_ralpha + over_pix * over_alpha)
					xmlo = _mm_srli_epi16(xmo16, 8); // (under_pix * over_ralpha + over_pix * over_alpha) >> 8
					xmlo = _mm_insert_epi16(xmlo, 255, 3); //write alpha
					xmlo = _mm_insert_epi16(xmlo, 255, 7); //write alpha
				} else {
					//
					xmover_alpha = _mm_shufflelo_epi16(xmo16, _MM_SHUFFLE(3,3,3,3));
					xmover_alpha = _mm_shufflehi_epi16(xmover_alpha, _MM_SHUFFLE(3,3,3,3));
					xmover_ralpha = _mm_sub_epi16(xmff16, xmover_alpha);
					xmunder_alpha = _mm_shufflelo_epi16(xmu16, _MM_SHUFFLE(3,3,3,3));
					xmunder_alpha = _mm_shufflehi_epi16(xmunder_alpha, _MM_SHUFFLE(3,3,3,3));
					xmunder_ralpha = _mm_sub_epi16(xmff16, xmunder_alpha);
					xmalpha = _mm_sub_epi16(xmff16, _mm_srli_epi16( _mm_mullo_epi16(xmover_ralpha, xmunder_ralpha), 8)); //255 - ((over_ralpha * under_ralpha) >> 8)

					xmu16 = _mm_mullo_epi16( _mm_srli_epi16( _mm_mullo_epi16(xmunder_alpha, xmover_ralpha), 8), xmu16); //under_pix * ((under_alpha * over_ralpha)>>8)
					xmo16 = _mm_mullo_epi16(xmo16, xmover_alpha); // over_pix * over_alpha
					xmo16 = _mm_add_epi16(xmu16, xmo16); // over_pix * over_alpha + (under_pix * under_alpha * over_ralpha)>>8

					//calc pixel 1
					xmf_lo = _mm_cvtepi32_ps( _mm_unpacklo_epi16(xmo16, xmzero) );
					xmf_alpha_lo = _mm_cvtepi32_ps( _mm_unpacklo_epi16(xmalpha, xmzero) );
					xmf_lo = _mm_mul_ps(xmf_lo, _mm_rcp_ps(xmf_alpha_lo));

					//calc pixel 2
					xmf_hi = _mm_cvtepi32_ps( _mm_unpackhi_epi16(xmo16, xmzero) );
					xmf_alpha_hi = _mm_cvtepi32_ps( _mm_unpackhi_epi16(xmalpha, xmzero) );
					xmf_hi = _mm_mul_ps(xmf_hi, _mm_rcp_ps(xmf_alpha_hi));

					//pack to 16bit data
					xmlo = _mm_packs_epi32(_mm_cvtps_epi32(xmf_lo), _mm_cvtps_epi32(xmf_hi));
					int a1 = _mm_extract_epi16(xmalpha, 3);
					xmlo = _mm_insert_epi16(xmlo, a1, 3); //write alpha
					int a2 = _mm_extract_epi16(xmalpha, 7);
					xmlo = _mm_insert_epi16(xmlo, a2, 7); //write alpha
				}

				//load hi double ward
				xmo16 = _mm_unpackhi_epi8(over, xmzero);
				xmu16 = _mm_unpackhi_epi8(under, xmzero);

				over_a1 = _mm_extract_epi16(xmo16, 3);
				over_a2 = _mm_extract_epi16(xmo16, 7);
				under_a1 = _mm_extract_epi16(xmu16, 3);
				under_a2 = _mm_extract_epi16(xmu16, 7);
				if (((over_a1 == 255 && over_a2 == 255)) || (under_a1 == 0 && under_a2 == 0)) {
					xmhi = xmo16;
				} else if (under_a1 == 0 && under_a2 == 0) {
					xmhi = xmu16;
				} else if (under_a1 == 255 && under_a2 == 255) {
					xmover_alpha = _mm_shufflelo_epi16(xmo16, _MM_SHUFFLE(3,3,3,3));
					xmover_alpha = _mm_shufflehi_epi16(xmover_alpha, _MM_SHUFFLE(3,3,3,3));
					xmover_ralpha = _mm_sub_epi16(xmff16, xmover_alpha);

					xmu16 = _mm_mullo_epi16(xmu16, xmover_ralpha); // under_pix * over_ralpha
					xmo16 = _mm_mullo_epi16(xmo16, xmover_alpha); // over_pix * over_alpha
					xmo16 = _mm_add_epi16(xmu16, xmo16); // (under_pix * over_ralpha + over_pix * over_alpha)
					xmhi = _mm_srli_epi16(xmo16, 8); // (under_pix * over_ralpha + over_pix * over_alpha) >> 8
					xmhi = _mm_insert_epi16(xmhi, 255, 3); //write alpha
					xmhi = _mm_insert_epi16(xmhi, 255, 7); //write alpha
				} else {
					//
					xmover_alpha = _mm_shufflelo_epi16(xmo16, _MM_SHUFFLE(3,3,3,3));
					xmover_alpha = _mm_shufflehi_epi16(xmover_alpha, _MM_SHUFFLE(3,3,3,3));
					xmover_ralpha = _mm_sub_epi16(xmff16, xmover_alpha);
					xmunder_alpha = _mm_shufflelo_epi16(xmu16, _MM_SHUFFLE(3,3,3,3));
					xmunder_alpha = _mm_shufflehi_epi16(xmunder_alpha, _MM_SHUFFLE(3,3,3,3));
					xmunder_ralpha = _mm_sub_epi16(xmff16, xmunder_alpha);
					xmalpha = _mm_sub_epi16(xmff16, _mm_srli_epi16( _mm_mullo_epi16(xmover_ralpha, xmunder_ralpha), 8)); //255 - ((over_ralpha * under_ralpha) >> 8)

					xmu16 = _mm_mullo_epi16( _mm_srli_epi16( _mm_mullo_epi16(xmunder_alpha, xmover_ralpha), 8), xmu16); //under_pix * ((under_alpha * over_ralpha)>>8)
					xmo16 = _mm_mullo_epi16(xmo16, xmover_alpha); // over_pix * over_alpha
					xmo16 = _mm_add_epi16(xmu16, xmo16); // over_pix * over_alpha + (under_pix * ((under_alpha * over_ralpha)>>8)

					//calc pixel1
					xmf_lo = _mm_cvtepi32_ps( _mm_unpacklo_epi16(xmo16, xmzero) );
					xmf_alpha_lo = _mm_cvtepi32_ps( _mm_unpacklo_epi16(xmalpha, xmzero) );
					xmf_lo = _mm_mul_ps(xmf_lo, _mm_rcp_ps(xmf_alpha_lo));

					//calc pixel 2
					xmf_hi = _mm_cvtepi32_ps( _mm_unpackhi_epi16(xmo16, xmzero) );
					xmf_alpha_hi = _mm_cvtepi32_ps( _mm_unpackhi_epi16(xmalpha, xmzero) );
					xmf_hi = _mm_mul_ps(xmf_hi, _mm_rcp_ps(xmf_alpha_hi));

					//pack to 16bit data
					xmhi = _mm_packs_epi32(_mm_cvtps_epi32(xmf_lo), _mm_cvtps_epi32(xmf_hi));
					int a3 = _mm_extract_epi16(xmalpha, 3);
					xmhi = _mm_insert_epi16(xmhi, a3, 3); //write alpha
					int a4 = _mm_extract_epi16(xmalpha, 7);
					xmhi = _mm_insert_epi16(xmhi, a4, 7); //write alpha
				}

				//store to memory
				_mm_store_ps((float*)dst_pix,
					_mm_castsi128_ps( _mm_packus_epi16(xmlo, xmhi)) );

				//
				over_pix += 4;
				under_pix += 4;
				dst_pix += 4;
			}
			over_line = GetNextLineAddress32(img1, over_line);
			under_line = GetNextLineAddress32(img2, under_line);
			dst_line = GetNextLineAddress32(img5	, dst_line);
		}
	}
	std::cout << timer.elapsed() << " msec" << std::endl;
	cvSaveImage("test5.bmp", img5);

	//SSE: _mm_load_ps + _mm_stream_si128
	timer.restart();
	{
		over_line = GetPixelAddress32(img1, 0, 0);
		under_line = GetPixelAddress32(img2, 0, 0);
		dst_line = GetPixelAddress32(img6, 0, 0);
		__m128i xmzero = _mm_setzero_si128();
		__m128i xmff16 = _mm_set1_epi16(0xff);
		for(y=0; y<img1->height; y++){
			over_pix = over_line;
			under_pix = under_line;
			dst_pix = dst_line;
			for(x=0; x<img1->width; x+=4){

				__m128i under = _mm_castps_si128( _mm_load_ps((float*)under_pix) );
				__m128i over = _mm_castps_si128( _mm_load_ps((float*)over_pix) );

				__m128i xmo16, xmu16;
				__m128i xmlo, xmhi;
				__m128i xmover_alpha, xmover_ralpha, xmunder_alpha, xmunder_ralpha, xmalpha;
				__m128 xmf_lo, xmf_hi, xmf_alpha_lo, xmf_alpha_hi;

				//load lo double ward
				xmo16 = _mm_unpacklo_epi8(over, xmzero);
				xmu16 = _mm_unpacklo_epi8(under, xmzero);

				int over_a1 = _mm_extract_epi16(xmo16, 3);
				int over_a2 = _mm_extract_epi16(xmo16, 7);
				int under_a1 = _mm_extract_epi16(xmu16, 3);
				int under_a2 = _mm_extract_epi16(xmu16, 7);
				if (((over_a1 == 255 && over_a2 == 255)) || (under_a1 == 0 && under_a2 == 0)) {
					xmlo = xmo16;
				} else if (under_a1 == 0 && under_a2 == 0) {
					xmlo = xmu16;
				} else if (under_a1 == 255 && under_a2 == 255) {
					xmover_alpha = _mm_shufflelo_epi16(xmo16, _MM_SHUFFLE(3,3,3,3));
					xmover_alpha = _mm_shufflehi_epi16(xmover_alpha, _MM_SHUFFLE(3,3,3,3));
					xmover_ralpha = _mm_sub_epi16(xmff16, xmover_alpha);

					xmu16 = _mm_mullo_epi16(xmu16, xmover_ralpha); // under_pix * over_ralpha
					xmo16 = _mm_mullo_epi16(xmo16, xmover_alpha); // over_pix * over_alpha
					xmo16 = _mm_add_epi16(xmu16, xmo16); // (under_pix * over_ralpha + over_pix * over_alpha)
					xmlo = _mm_srli_epi16(xmo16, 8); // (under_pix * over_ralpha + over_pix * over_alpha) >> 8
					xmlo = _mm_insert_epi16(xmlo, 255, 3); //write alpha
					xmlo = _mm_insert_epi16(xmlo, 255, 7); //write alpha
				} else {
					//
					xmover_alpha = _mm_shufflelo_epi16(xmo16, _MM_SHUFFLE(3,3,3,3));
					xmover_alpha = _mm_shufflehi_epi16(xmover_alpha, _MM_SHUFFLE(3,3,3,3));
					xmover_ralpha = _mm_sub_epi16(xmff16, xmover_alpha);
					xmunder_alpha = _mm_shufflelo_epi16(xmu16, _MM_SHUFFLE(3,3,3,3));
					xmunder_alpha = _mm_shufflehi_epi16(xmunder_alpha, _MM_SHUFFLE(3,3,3,3));
					xmunder_ralpha = _mm_sub_epi16(xmff16, xmunder_alpha);
					xmalpha = _mm_sub_epi16(xmff16, _mm_srli_epi16( _mm_mullo_epi16(xmover_ralpha, xmunder_ralpha), 8)); //255 - ((over_ralpha * under_ralpha) >> 8)

					xmu16 = _mm_mullo_epi16( _mm_srli_epi16( _mm_mullo_epi16(xmunder_alpha, xmover_ralpha), 8), xmu16); //under_pix * ((under_alpha * over_ralpha)>>8)
					xmo16 = _mm_mullo_epi16(xmo16, xmover_alpha); // over_pix * over_alpha
					xmo16 = _mm_add_epi16(xmu16, xmo16); // over_pix * over_alpha + (under_pix * under_alpha * over_ralpha)>>8

					//calc pixel 1
					xmf_lo = _mm_cvtepi32_ps( _mm_unpacklo_epi16(xmo16, xmzero) );
					xmf_alpha_lo = _mm_cvtepi32_ps( _mm_unpacklo_epi16(xmalpha, xmzero) );
					xmf_lo = _mm_mul_ps(xmf_lo, _mm_rcp_ps(xmf_alpha_lo));

					//calc pixel 2
					xmf_hi = _mm_cvtepi32_ps( _mm_unpackhi_epi16(xmo16, xmzero) );
					xmf_alpha_hi = _mm_cvtepi32_ps( _mm_unpackhi_epi16(xmalpha, xmzero) );
					xmf_hi = _mm_mul_ps(xmf_hi, _mm_rcp_ps(xmf_alpha_hi));

					//pack to 16bit data
					xmlo = _mm_packs_epi32(_mm_cvtps_epi32(xmf_lo), _mm_cvtps_epi32(xmf_hi));

					//write alpha
					int a1 = _mm_extract_epi16(xmalpha, 3);
					xmlo = _mm_insert_epi16(xmlo, a1, 3); //write alpha
					int a2 = _mm_extract_epi16(xmalpha, 7);
					xmlo = _mm_insert_epi16(xmlo, a2, 7); //write alpha
				}

				//load hi double ward
				xmo16 = _mm_unpackhi_epi8(over, xmzero);
				xmu16 = _mm_unpackhi_epi8(under, xmzero);

				over_a1 = _mm_extract_epi16(xmo16, 3);
				over_a2 = _mm_extract_epi16(xmo16, 7);
				under_a1 = _mm_extract_epi16(xmu16, 3);
				under_a2 = _mm_extract_epi16(xmu16, 7);
				if (((over_a1 == 255 && over_a2 == 255)) || (under_a1 == 0 && under_a2 == 0)) {
					xmhi = xmo16;
				} else if (under_a1 == 0 && under_a2 == 0) {
					xmhi = xmu16;
				} else if (under_a1 == 255 && under_a2 == 255) {
					xmover_alpha = _mm_shufflelo_epi16(xmo16, _MM_SHUFFLE(3,3,3,3));
					xmover_alpha = _mm_shufflehi_epi16(xmover_alpha, _MM_SHUFFLE(3,3,3,3));
					xmover_ralpha = _mm_sub_epi16(xmff16, xmover_alpha);

					xmu16 = _mm_mullo_epi16(xmu16, xmover_ralpha); // under_pix * over_ralpha
					xmo16 = _mm_mullo_epi16(xmo16, xmover_alpha); // over_pix * over_alpha
					xmo16 = _mm_add_epi16(xmu16, xmo16); // (under_pix * over_ralpha + over_pix * over_alpha)
					xmhi = _mm_srli_epi16(xmo16, 8); // (under_pix * over_ralpha + over_pix * over_alpha) >> 8
					xmhi = _mm_insert_epi16(xmhi, 255, 3); //write alpha
					xmhi = _mm_insert_epi16(xmhi, 255, 7); //write alpha
				} else {
					//
					xmover_alpha = _mm_shufflelo_epi16(xmo16, _MM_SHUFFLE(3,3,3,3));
					xmover_alpha = _mm_shufflehi_epi16(xmover_alpha, _MM_SHUFFLE(3,3,3,3));
					xmover_ralpha = _mm_sub_epi16(xmff16, xmover_alpha);
					xmunder_alpha = _mm_shufflelo_epi16(xmu16, _MM_SHUFFLE(3,3,3,3));
					xmunder_alpha = _mm_shufflehi_epi16(xmunder_alpha, _MM_SHUFFLE(3,3,3,3));
					xmunder_ralpha = _mm_sub_epi16(xmff16, xmunder_alpha);
					xmalpha = _mm_sub_epi16(xmff16, _mm_srli_epi16( _mm_mullo_epi16(xmover_ralpha, xmunder_ralpha), 8)); //255 - ((over_ralpha * under_ralpha) >> 8)

					xmu16 = _mm_mullo_epi16( _mm_srli_epi16( _mm_mullo_epi16(xmunder_alpha, xmover_ralpha), 8), xmu16); //under_pix * ((under_alpha * over_ralpha)>>8)
					xmo16 = _mm_mullo_epi16(xmo16, xmover_alpha); // over_pix * over_alpha
					xmo16 = _mm_add_epi16(xmu16, xmo16); // over_pix * over_alpha + (under_pix * ((under_alpha * over_ralpha)>>8)

					//calc pixel1
					xmf_lo = _mm_cvtepi32_ps( _mm_unpacklo_epi16(xmo16, xmzero) );
					xmf_alpha_lo = _mm_cvtepi32_ps( _mm_unpacklo_epi16(xmalpha, xmzero) );
					xmf_lo = _mm_mul_ps(xmf_lo, _mm_rcp_ps(xmf_alpha_lo));

					//calc pixel 2
					xmf_hi = _mm_cvtepi32_ps( _mm_unpackhi_epi16(xmo16, xmzero) );
					xmf_alpha_hi = _mm_cvtepi32_ps( _mm_unpackhi_epi16(xmalpha, xmzero) );
					xmf_hi = _mm_mul_ps(xmf_hi, _mm_rcp_ps(xmf_alpha_hi));

					//pack to 16bit data
					xmhi = _mm_packs_epi32(_mm_cvtps_epi32(xmf_lo), _mm_cvtps_epi32(xmf_hi));
					int a3 = _mm_extract_epi16(xmalpha, 3);
					xmhi = _mm_insert_epi16(xmhi, a3, 3); //write alpha
					int a4 = _mm_extract_epi16(xmalpha, 7);
					xmhi = _mm_insert_epi16(xmhi, a4, 7); //write alpha
				}

				//store to memory
				_mm_stream_si128((__m128i*)dst_pix, _mm_packus_epi16(xmlo, xmhi));

				//
				over_pix += 4;
				under_pix += 4;
				dst_pix += 4;
			}
			over_line = GetNextLineAddress32(img1, over_line);
			under_line = GetNextLineAddress32(img2, under_line);
			dst_line = GetNextLineAddress32(img6, dst_line);
		}
	}
	std::cout << timer.elapsed() << " msec" << std::endl;
	cvSaveImage("test6.bmp", img6);


	cvReleaseImage(&b_ch);
	cvReleaseImage(&g_ch);
	cvReleaseImage(&r_ch);
	cvReleaseImage(&a_ch1);
	cvReleaseImage(&a_ch2);
	cvReleaseImage(&img1);
	cvReleaseImage(&img2);
	cvReleaseImage(&img3);
	cvReleaseImage(&img4);
	cvReleaseImage(&img5);
	cvReleaseImage(&img6);
}