65.9K
CodeProject 正在变化。 阅读更多。
Home

学习如何使用 CUDA 进行 Alpha 混合

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.76/5 (14投票s)

2009 年 9 月 1 日

CPOL

2分钟阅读

viewsIcon

52243

downloadIcon

873

使用 CUDA 的爆发性能进行图像处理

AlphaBlending

引言

本文将向您介绍使用 CUDA™ 进行 Alpha 混合的方法。什么是 CUDA?简而言之,它是由 NVIDIA® 开发的并行计算架构,是 NVIDIA 图形处理单元中的计算引擎,软件开发人员可以通过编程语言访问它。

背景

对于大图像,Alpha 混合需要更多的 CPU 时间来处理。从 NVIDIA 学习,CUDA 可以加速图像处理性能。嗯,您需要试一试,看看它们之间的差异有多大。此 Alpha 混合代码在 Windows 7 和 NVIDIA GeForce G210M 上进行了测试。

传统方式的 Alpha 混合

AlphaBlending_Host() 是我用来进行 Alpha 混合的传统例程。我认为它已经是速度最快的方式,并且也具有良好的性能。

参数

  • PULONG pulFore [in]:前景图像缓冲区。一般来说,前景图像在背景图像之上。
  • PULONG pulBack [in]:背景图像缓冲区。
  • PULONG pulResult [out]:混合后的图像缓冲区。在传递之前,必须先分配此图像缓冲区。
  • DWORD dwSize [in]:图像尺寸大小。
void   AlphaBlending_Host(PULONG pulFore , PULONG pulBack, PULONG pulResult, DWORD dwSize)
{
    ULONG   ulResult = 0L ;
    ULONG   ulAlpha = 0L ;
    ULONG   ulVal = 0L ;
    ULONG   ulFore = 0L ;
    ULONG   ulBack = 0L ;

    if ((NULL == pulFore) || (NULL == pulBack) || (NULL == pulResult) || (0L == dwSize))
    {
        return;
    }

    _asm
    {
    FromBeginning:
    //--- get foreground pixel and move buffer forward by 1 ---
    MOV     EAX,pulFore
    MOV     ECX,DWORD PTR [EAX]
    MOV     ulFore,ECX

    MOV     EDX,pulFore
    ADD     EDX,4
    MOV     pulFore,EDX

    //--- get background pixel and move buffer forward by 1 ---
    MOV     EAX,pulBack
    MOV     ECX,DWORD PTR [EAX]
    MOV     ulBack,ECX

    MOV     EDX,pulBack
    ADD     EDX,4
    MOV     pulBack,EDX

    // blend foreground color (F) to a background color (B),
    // using alpha channel value of F
    // Result Z = (Fa * Frgb) + ((1 - Fa) * Brgb)
    // EAX <- Foreground
    // EDX <- Background
    MOV     EAX,ulFore
    MOV     EDX,ulBack
            
    // Test Fa = 255 ? operation of subtraction
    CMP     EAX,0xFF000000      // Fa = 255 ? => Result = EAX
    JNC     ReturnForePixel
    // Test Fa = 0 ? operation of and
    TEST    EAX,0xFF000000      // Fa = 0 ?   => Result = EDX
    JZ      ReturnBackPixel

    JMP     EntryProcess

    ReturnForePixel:
    MOV     ulVal,EAX
    JMP     ProcessFinished

    ReturnBackPixel:
    MOV     ulVal,EDX
    JMP     ProcessFinished

    //--- entry ---
    EntryProcess:

    // Get weight W = Fa * M
    MOV     ECX,EAX             // ECX  <-  Fa Fr Fg Fb
    SHR     ECX,24              // ECX  <-  00 00 00 Fa

    PUSH    EBX

    // P = W * F
    MOV     EBX,EAX             // EBX  <-  Fa Fr Fg Fb
    AND     EAX,0x00FF00FF      // EAX  <-  00 Fr 00 Fb
    AND     EBX,0xFF00FF00      // EBX  <-  Fa 00 Fg 00
    IMUL    EAX,ECX             // EAX  <-  Pr ** Pb **
    SHR     EBX,8               // EBX  <-  00 Fa 00 Fg
    IMUL    EBX,ECX             // EBX  <-  Pa ** Pg **
    ADD     EAX,0x00800080
    AND     EAX,0xFF00FF00      // EAX  <-  Pr 00 Pb 00
    SHR     EAX,8               // EAX  <-  00 Pr ** Pb
    ADD     EBX,0x00800080
    AND     EBX,0xFF00FF00      // EBX  <-  Pa 00 Pg 00
    OR      EAX,EBX             // EAX  <-  Pa Pr Pg Pb

    // W = (1 - W) ; Q = W * B
    XOR     ECX,0x000000FF      // ECX  <-  1 - ECX

    MOV     EBX,EDX             // EBX  <-  Ba Br Bg Bb
    AND     EDX,0x00FF00FF      // EDX  <-  00 Br 00 Bb
    AND     EBX,0xFF00FF00      // EBX  <-  Ba 00 Bg 00
    IMUL    EDX,ECX             // EDX  <-  Qr ** Qb **
    SHR     EBX,8               // EBX  <-  00 Ba 00 Bg
    IMUL    EBX,ECX             // EBX  <-  Qa ** Qg **
    ADD     EDX,0x00800080
    AND     EDX,0xFF00FF00      // EDX  <-  Qr 00 Qb 00
    SHR     EDX,8               // EDX  <-  00 Qr ** Qb
    ADD     EBX,0x00800080
    AND     EBX,0xFF00FF00      // EBX  <-  Qa 00 Qg 00
    OR      EBX,EDX             // EBX  <-  Qa Qr Qg Qb
    // Z = P + Q (assuming no overflow at each byte)
    ADD     EAX,EBX             // EAX  <-  Za Zr Zg Zb

    POP     EBX

    MOV     ulVal,EAX           // new blended RGB color

    ProcessFinished:
    //--- saved to result buffer and move buffer forward by 1 ---
    MOV     EAX,pulResult
    MOV     ECX,ulVal
    MOV     DWORD PTR [EAX],ECX

    MOV     EDX,pulResult
    ADD     EDX,4
    MOV     pulResult,EDX

    //--- check next pixel until the final one ---
    DEC     dwSize
    JNZ     FromBeginning
    }
}

host.png

在这种情况下,在 CPU 上的处理时间为 0.006027 (ms)。

使用 CUDA 进行 Alpha 混合

DilutePixel() 用于使用特定的 alpha 通道值混合像素,然后返回混合后的像素。请注意,此函数有一个 __device__ 关键字作为前缀。它由 CUDA __global__ 函数调用。

参数

  • unsigned long ulPixel [in]:源设备像素。
  • unsigned long ulAlpha [in]:用于混合像素的 alpha 通道值。

该函数返回混合后的像素。

__device__ 
unsigned long DilutePixel(unsigned long ulPixel, unsigned long ulAlpha)
{
    unsigned long nResult = 0;
    
    nResult   = ulPixel;
    ulPixel  &= 0x00ff00ff;
    nResult  &= 0xff00ff00;
    ulPixel  *= ulAlpha;
    nResult >>= 8;
    nResult  *= ulAlpha;
    ulPixel  += 0x00800080;
    ulPixel  &= 0xff00ff00;
    ulPixel >>= 8;
    nResult  += 0x00800080;
    nResult  &= 0xff00ff00;
    nResult  |= ulPixel;

    return( nResult );
}

AlphaBlending_Texture() 是执行 Alpha 混合的线程例程,它处理前景和背景图像缓冲区的每个像素。请注意,此函数有一个 __global__ 关键字作为前缀。

参数

  • unsigned long* pResult [out]:混合后的设备图像缓冲区。
  • unsigned nSize [in]:图像尺寸大小。
__global__ 
void AlphaBlending_Texture(unsigned long* pResult, unsigned nSize)
{
    unsigned nIndex = (__umul24(blockIdx.x, blockDim.x) + threadIdx.x);
    unsigned long ulPixelF = 0L;
    unsigned long ulPixelB = 0L;
    unsigned long ulAlphaF = 0L;

    if (nIndex >= nSize)
    {
        return;
    }
    ulPixelF = tex1Dfetch(texForegnd, nIndex);
    ulPixelB = tex1Dfetch(texBackgnd, nIndex);
    ulAlphaF = (ulPixelF >> 24L);

    if (ulAlphaF == 0xffL)
    {
        *(pResult + nIndex) = ulPixelF;
    }
    else if (ulAlphaF == 0L)
    {
        *(pResult + nIndex) = ulPixelB;
    }
    else
    {
        ulPixelF = DilutePixel(ulPixelF, ulAlphaF);
        ulPixelB = DilutePixel(ulPixelB, (0xffL ^ ulAlphaF));

        *(pResult + nIndex) = (ulPixelF + ulPixelB);
    }
}

AlphaBlending_Device() 是处理两个图像的 Alpha 混合的入口进程。

参数

  • unsigned long* pMemA [in]:主机前景图像缓冲区。
  • unsigned long* pMemB [in]:主机背景图像缓冲区。
  • unsigned long* pResult [out]:主机混合图像缓冲区。
  • unsigned nWidth [in]:图像宽度。
  • unsigned nHeight [in]:图像高度。
extern "C"
void AlphaBlending_Device(unsigned long* pMemA, unsigned long* pMemB, 
			unsigned long* pMemResult,
                          unsigned nWidth, unsigned nHeight)
{
    unsigned nDimen = (nWidth * nHeight);
    unsigned nSize = (nDimen << 2);
    unsigned char *pDevA = NULL, *pDevB = NULL, *pDevResult = NULL;

    cudaMalloc((void**)&pDevA, nSize);
    cudaMalloc((void**)&pDevB, nSize);

    if (false == gm_bMapHostMemory)
    {
        cudaMalloc((void**)&pDevResult, nSize);
    }
    else
    {
        cudaHostGetDevicePointer((void**)&pDevResult, (void*)pMemResult, 0);
    }

    cudaMemcpy(pDevA, pMemA, nSize, cudaMemcpyHostToDevice);
    cudaMemcpy(pDevB, pMemB, nSize, cudaMemcpyHostToDevice);

    cudaBindTexture(0, texForegnd, pDevA);
    cudaBindTexture(0, texBackgnd, pDevB);

    AlphaBlending_Texture<<<::ceil((float)nDimen / 
	(float)BLOCK_DIM), BLOCK_DIM>>>((unsigned long*)pDevResult, nDimen);

    if (false == gm_bMapHostMemory)
    {
        cudaMemcpy(pMemResult, pDevResult, nSize, cudaMemcpyDeviceToHost);
    }

    cudaUnbindTexture( texForegnd );
    cudaUnbindTexture( texBackgnd );

    cudaFree( pDevA );
    cudaFree( pDevB );
    if (false == gm_bMapHostMemory)
    {
        cudaFree( pDevResult );
    }
}

host.png

在这种情况下,在 GPU 上的处理时间仅为 0.000067 (ms)。

关注点

结果非常棒。使用 CUDA 的处理时间比传统方式快近 100 倍。我想尝试另一种方法;OpenCL,它也是一种并行计算语言,看看它们之间的差距有多大。

修订

  • 版本:1.0 初始发布
© . All rights reserved.