SSE用いたアルファブレンドの高速化
Posted on Mon 14 September 2009 in ペイントツール
計測するアルファブレンド処理
- SSEを使わない処理
- 16バイトアライメントなし、SSEを使って1pixelごとに処理
- 16バイトアライメントなし、SSEを使って2pixelごとに処理
計測方法
- 1920x1200BGR画像を2枚読み込み、アルファ値を指定してアルファブレンドを行う。
- 画像の保存、読み込みはopenCVを用いた。
- アルファブレンド部分の処理のみ処理時間を計測した。
- 処理時間の計測は以下のページにあるコードを使用した。
計測に使用した環境
- CPU: core2duo E8400
- OS: windows 7 64bit
- コンパイラ: Visual Studio 2008
- 最適化オプション: 実行速度 (/O2)
コード
#include <iostream>
#include <cv.h>
#include <highgui.h>
#include "QPCTimer.h"
typedef unsigned char uint8_t;
typedef unsigned char int8_t;
typedef unsigned short uint16_t;
typedef short int16_t;
typedef unsigned long uint32_t;
typedef long int32_t;
union UCvPixel32
{
uint32_t value;
struct
{
uint8_t b,g,r,a;
};
};
typedef UCvPixel32 UCvPixel;
/////////////////////////////////////////////
/*!
指定位置のRGB値を取得する
@param[in] image 画像
@param[in] x X座標
@param[in] y Y座標
@param[out] dst 取得した値
*/
inline void GetPixelFromBGR(const IplImage *image, int x, int y, UCvPixel *dst)
{
assert(image);
assert(image->nChannels == 3);
assert((0 <= x && x <= image->width-1) && (0 <= y && y <= image->height-1));
memcpy(&(dst->value), image->imageData + y*image->widthStep + x*image->nChannels, sizeof(uint8_t)*3);
}
//////////////////////////////////////////////
/*!
指定位置にRGB値をセットする
@param[in] image 画像
@param[in] x X座標
@param[in] y Y座標
@param[in] src セットする値
*/
inline void SetPixelToBGR(
IplImage *image,
int x,
int y,
const UCvPixel* src)
{
assert(image);
assert(image->nChannels == 3);
assert((0 <= x && x <= image->width-1) && (0 <= y && y <= image->height-1));
memcpy(image->imageData + y*image->widthStep + x*image->nChannels, &(src->value), sizeof(uint8_t)*3);
}
int main(void)
{
int x,y;
int alpha = 150;
int r,g,b;
int r1, g1, b1;
int r2, g2, b2;
int ra = 255 - alpha;
UCvPixel *buf1;
UCvPixel *buf2;
UCvPixel *buf3;
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_ch = 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);
cvSet(a_ch, cvScalar(0));
cvSplit(test1, b_ch, g_ch, r_ch, NULL);
cvMerge(b_ch, g_ch, r_ch, a_ch, img1);
cvSplit(test2, b_ch, g_ch, r_ch, NULL);
cvMerge(b_ch, g_ch, r_ch, a_ch, img2);
QPCTimer timer;
for(y=0; y<img1->height; y++){
for(x=0; x<img1->width; x++){
buf1 = GetPixelAddress32(img1, x, y);
buf2 = GetPixelAddress32(img2, x, y);
buf3 = GetPixelAddress32(img3, x, y);
buf3->b = (buf1->b*ra + buf2->b * alpha) >> 8;
buf3->g = (buf1->g*ra + buf2->g * alpha) >> 8;
buf3->r = (buf1->r*ra + buf2->r * alpha) >> 8;
}
}
std::cout << timer.elapsed() << " msec" << std::endl;
cvSaveImage("test3.bmp", img3);
timer.restart();
for(y=0; y<img1->height; y++){
for(x=0; x<img1->width; x++){
buf1 = GetPixelAddress32(img1, x, y);
buf2 = GetPixelAddress32(img2, x, y);
buf3 = GetPixelAddress32(img4, x, y);
__asm{
pslld xmm6, 32; // xmm6 [ 0, 0, 0, 0]
mov eax, buf1; //
movdqu xmm0, [eax]; // xmm0 [ *, *, *, a1r1g1b1]
punpcklbw xmm0, xmm6; // xmm0 [ *, *, a1r1, g1b1]
mov eax, buf2;
movdqu xmm1, [eax]; // xmm1 [ *, *, *, a2r2g2b2]
punpcklbw xmm1, xmm6; // xmm1 [ *, *, a2r2, g2b2]
mov eax, alpha;
movd xmm3, eax; // xmm3 [ *, *, *, *a2]
pshuflw xmm3, xmm3, 0x00; // xmm3 [ *, *, a2a2, a2a2]
mov eax, ra;
movd xmm4, eax; // xmm4 [ *, *, *, *0xff]
pshuflw xmm4, xmm4, 0x00; // xmm4 [ *, *, 0xff0xff, 0xff0xff]
pmullw xmm0, xmm4; // xmm0 = data1 * a1
pmullw xmm3, xmm1; // xmm3 = data2 * a2
paddw xmm3, xmm0; // xmm3 = data1 * a1 + data2 * a2
psrlw xmm3, 8; // xmm3 = (data1 * a1 + data2 * a2) >> 8
packuswb xmm3, xmm6; // xmm3 [ *, *, *, abgr,]
mov eax, buf3;
movd [eax], xmm3; //
}
}
}
std::cout << timer.elapsed() << " msec" << std::endl;
cvSaveImage("test4.bmp", img4);
timer.restart();
for(y=0; y<img1->height; y++){
for(x=0; x<img1->width; x+=2){
buf1 = GetPixelAddress32(img1, x, y);
buf2 = GetPixelAddress32(img2, x, y);
buf3 = GetPixelAddress32(img5, x, y);
__asm{
pslld xmm6, 32; // xmm6 [ 0, 0, 0, 0]
mov eax, buf1; //
movdqu xmm0, [eax]; // xmm0 [ *, *, *, a11r11g11b11]
punpcklbw xmm0, xmm6; // xmm0 [ *, *, a11r11, g11b11]
mov eax, buf2;
movdqu xmm1, [eax]; // xmm1 [ *, *, *, a21r21g21b21]
punpcklbw xmm1, xmm6; // xmm1 [ a22r22, g22b22, a21r21, g21b21]
mov eax, alpha;
movd xmm3, eax; // xmm3 [ *, *, *, *a2]
pshuflw xmm3, xmm3, 0x00; // xmm3 [ *, *, a2a2, a2a2]
pshufd xmm3, xmm3, 0x00; // xmm3 [ a2a2, a2a2, a2a2, a2a2]
mov eax, ra;
movd xmm4, eax; // xmm4 [ *, *, *, *0xff]
pshuflw xmm4, xmm4, 0x00; // xmm4 [ *, *, 0xff0xff, 0xff0xff]
pshufd xmm4, xmm4, 0x00; // xmm4 [ 0xff0xff, 0xff0xff, 0xff0xff, 0xff0xff]
pmullw xmm0, xmm4; // xmm0 = data1 * a1
pmullw xmm3, xmm1; // xmm3 = data2 * a2
paddw xmm3, xmm0; // xmm3 = data1 * a1 + data2 * a2
psrlw xmm3, 8; // xmm3 = (data1 * a1 + data2 * a2) >> 8
packuswb xmm3, xmm6; // xmm3 [ *, *, *, abgr,]
mov eax, buf3;
movlpd [eax], xmm3; //
}
}
}
std::cout << timer.elapsed() << " msec" << std::endl;
cvSaveImage("test5.bmp", img5);
cvReleaseImage(&b_ch);
cvReleaseImage(&g_ch);
cvReleaseImage(&r_ch);
cvReleaseImage(&a_ch);
cvReleaseImage(&img1);
cvReleaseImage(&img2);
cvReleaseImage(&img3);
cvReleaseImage(&img4);
cvReleaseImage(&img5);
}
計測結果
32.2164 msec
30.2538 msec
18.262 msec
32.1765 msec
30.4334 msec
17.9203 msec
32.3969 msec
32.8726 msec
19.3482 msec
非SSEとSSEの1pixelごとに処理する方ではほとんど速度差が見られなかった。
1pixelごとに処理する方と比較して、2pixelごとに処理する方は約1.7倍の処理速度の向上が見られた。
16バイト境界にアライメントしてmovdqa命令でデータの転送を行えばもう少し早くなるはず。
↓続きです。
http://d.hatena.ne.jp/hiroki0/20100320
参考URL