SSE用いたアルファブレンドの高速化

Posted on Mon 14 September 2009 in ペイントツール

計測するアルファブレンド処理

  • SSEを使わない処理
  • 16バイトアライメントなし、SSEを使って1pixelごとに処理
  • 16バイトアライメントなし、SSEを使って2pixelごとに処理

計測方法

  • 1920x1200BGR画像を2枚読み込み、アルファ値を指定してアルファブレンドを行う。
  • 画像の保存、読み込みはopenCVを用いた。
  • アルファブレンド部分の処理のみ処理時間を計測した。
  • 処理時間の計測は以下のページにあるコードを使用した。

http://74.125.155.132/search?q=cache:lBJiOP0zi6MJ:clippingcutting.blogspot.com/2007_10_01_archive.html+QPCTimer+QueryPerformanceFrequency&cd=1&hl=ja&ct=clnk&gl=jp

計測に使用した環境

コード

#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

http://d.hatena.ne.jp/n7shi/20080323/1231278248