並行計算之路——CUDA與紋理映射

紋理存儲器是GPU常用的優化方法。

舉個慄

參考《GPGPU編程技術——從GLSL、CUDA到OpenCL》代碼6-2(離散卷積程序)。因爲CUDA版本的不同,所以本人對原書的代碼進行修改,修改後的代碼如下。

#include "cuda_runtime.h"
#include "device_launch_parameters.h"

#include <iostream>

using namespace std;

// Block 的尺寸爲[BLOCK_X * BLOCK_Y]
#define BLOCK_X   16u
#define BLOCK_Y   16u

// 定義一個四維向量相加的宏
#define VectorAdd(a, b) a.x += b.x; a.y += b.y; a.z += b.z; a.w += b.w;

// 定義一個四維向量與標量除法的宏
#define VectorScalarDev(a, b, c) a.x = b.x / c; a.y = b.y / c; a.z = b.z / c; a.w = b.w / c;

// 紋理相關全局變量
texture<float4, 2, cudaReadModeElementType> refTex;
cudaArray* cuArray;

__global__ void convolution(int nWidth, int nHeight, int nRadius, float4* pfResult)
{
    // 計算線程號
    const int idxX = blockIdx.x * blockDim.x + threadIdx.x;
    const int idxY = blockIdx.y * blockDim.y + threadIdx.y;

    // 將線程號映射到全局存儲器
    const int idxResult = idxY * nHeight + idxX;

    // 近鄰元素的數量
    int nTotal = 0;
    // 近鄰元素之和
    float4 f4Sum = { 0.0f, 0.0f, 0.0f, 0.0f };
    // 計算結果:輸出四元向量
    float4 f4Result = { 0.0f, 0.0f, 0.0f, 0.0f };
    float4 f4Temp = { 0.0f, 0.0f, 0.0f, 0.0f };

    // 近鄰元素求和
    for (int ii = idxX - nRadius; ii < idxX + nRadius; ii++)
    {
        for (int jj = idxY - nRadius; jj <= idxY + nRadius; jj++)
        {
            if (ii >= 0 && jj >= 0 && ii < nWidth && jj < nHeight)
            {
                f4Temp = tex2D(refTex, ii, jj);
                VectorAdd(f4Sum, f4Temp);
                nTotal++;
            }
        }
    }
    VectorScalarDev(f4Result, f4Sum, (float)nTotal);
    *(pfResult + idxResult) = f4Result;
}

int main()
{
    unsigned unWidth = 1024u;
    unsigned unHeight = 1024u;
    unsigned unSizeData = unWidth * unHeight;
    unsigned unRadius = 2u;

    // 準備輸入數據
    unsigned unData = 0;
    float4* pf4Sampler;
    cudaMallocHost((void **)&pf4Sampler, unSizeData * sizeof(float4));

    for (unsigned i = 0; i < unSizeData; i++)
    {
        pf4Sampler[i].x = (float)(unData++);
        pf4Sampler[i].y = (float)(unData++);
        pf4Sampler[i].z = (float)(unData++);
        pf4Sampler[i].w = (float)(unData++);
    }

    // 準備紋理
    cudaChannelFormatDesc cuDesc = cudaCreateChannelDesc<float4>();
    cudaMallocArray(&cuArray, &cuDesc, unWidth, unHeight);
    cudaMemcpyToArray(cuArray, 0, 0, pf4Sampler, unSizeData * sizeof(float4), cudaMemcpyHostToDevice);

    // 分配全局存儲器
    float4* pfResult;
    cudaMalloc((void **)&pfResult, unSizeData * sizeof(float4));

    // 配置線程並調用內核
    dim3 block(BLOCK_X, BLOCK_Y);
    dim3 grid(ceil((float)unWidth / BLOCK_X), ceil((float)unHeight / BLOCK_Y));
    convolution <<< grid, block >>>(unWidth, unHeight, unRadius, pfResult);

    // 結果
    cudaMemcpy(pf4Sampler, pfResult, unSizeData * sizeof(float4), cudaMemcpyDeviceToHost);

    cudaUnbindTexture(refTex);
    cudaFreeHost(pf4Sampler);
    cudaFreeArray(cuArray);
    cudaFree(pfResult);

    return 0;
}

砸開喫

一般使用紋理存儲器處理圖形(樹上是這麼說的),至於有什麼好處,樹上說了一大堆。天書啊( ╯□╰ )看不懂(* ̄︶ ̄)y。不過好歹有個栗子可以砸開喫。

  1. 聲明紋理參照系 refTex以及CUDA數組cuArray
  2. 準備輸入數據 pf4Sampler
  3. (cudaArray類型本身並非模板,因此在使用cudaMallocArray() 分配存儲器空間時調用) cudaChannelFormatDesc() 設置數組的數據類型。cudaMemcpyToArray() 初始化CUDA數組。
  4. 紋理綁定cudaBindTextureToArray()
  5. 紋理拾取tex2D
  6. 數據操作。
  7. 解除綁定。
  8. 釋放CUDA數組。
    經過8道工序就個享受紋理的美味了。

在CUDA的Samples中也提供了一個關於紋理的例子simpleTexture.cu。

參考:
《GPGPU編程技術——從GLSL、CUDA到OpenCL》♥♥♥♥♥
《數字圖像處理高級應用——基於MATLAB與CUDA的實現》♥♥♥
《基於CUDA的並行程序設計》♥♥♥
《CUDA專家手冊》♥♥♥♥♥
《高性能CUDA應用設計與開發》♥♥♥♥

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章