cuda學習筆記(4)

上篇文章介紹cuda常量內存的特點及使用方法。本篇記錄cuda紋理內存( texture )的特點及使用方法。

1. 紋理內存

cuda編程經常要對二維或者三維數據進行操作,爲了加速內存讀寫,需要使用紋理內存。紋理內存不能單獨使用,必讀綁定到全局內存上,紋理內存( texture memory )實質上是全局內存的一個特殊形態,全局內存被綁定爲紋理內存,對其的讀寫操作將通過專門的 texture cache(紋理緩存)進行,其實稱爲紋理緩存更加貼切。

紋理緩存的優勢:紋理緩存具備硬件插值特性,可以實現最近鄰插值和線性插值。紋理緩存針對二維空間的局部性訪問進行了優化,所以通過紋理緩存訪問二維矩陣的鄰域會獲得加速。紋理緩存不需要滿足全局內存的合併訪問條件。此外,紋理內存訪問會自動處理邊界條件,對於越界有自動處理機制

紋理可以是一段連續的設備內存,也可以是一個CUDA數組。但是CUDA數組對局部尋址有優化,稱爲“塊線性”,原理是將鄰域元素緩存在同一條cache線上,這將加快鄰域內的尋址,但是對於設備內存,並沒有“塊線性”。所以,選擇採用CUDA數組,還是設備內存,需要根據實際情況決定,將數據copy至CUDA數組是很耗時的。紋理的一個元素稱爲texels。紋理最多支持三維,width,x方向的維數;height,y方向的維數;depth,z方向的維數。

紋理的創建通過texture object或者texture reference,注意只有計算能力3.0以上的設備支持texture object。texture object在運行時創建,texture reference在編譯期創建,運行時綁定到實際的紋理。texture reference必須定義爲全局變量,不能作爲函數的參數。

2. 紋理內存的使用

紋理內存使用流程如下:

a. 聲明紋理參考系(注:紋理參照系必須定義在所有函數體外)
texture<uchar4,cudaTextureType2D,cudaReadModeNormalizedFloat> tex;
b.設置紋理通道格式等,並以此申請global內存
cudaChannelFormatDesc channelDesc=cudaCreateChannelDesc<uchar4>();
cudaArray *cuArray;
cudaMallocArray(&cuArray,&channelDesc,width,height);
c.初始化申請的cuda global內存
cudaMemcpyToArray(cuArray,0,0,src.data,size,cudaMemcpyHostToDevice);
d.設置紋理參考系的屬性,並綁定到cuda global 內存上

紋理參考系的結構體如下:

struct textureReference
{
    int normalized;
    enum cudaTextureFilterMode filterMode;
    enum cudaTextureAddressMode addressMode[3];
    struct cudaChannelFormatDesc channelDesc;
}

normalized設置讀取模式:是否對紋理座標歸一化
1)歸一化浮點模式,fetch將返回【0.0,1.0】的歸一化之後的數值,輸入數據類型爲8位、16位整型或浮點數;(2)元素類型模式,fetch返回原始數值。 紋理支持歸一化的浮點座標,計算方式如下:[0.0,1-1/N],N爲當前維度的維數。歸一化座標在某些情況下非常方便,如放大縮小操作。

filterMode用於設置紋理的濾波模式
定義了fetch返回結果的計算方式。有兩種模式:cudaFilterModePoint or cudaFilterModeLinear 。 cudaFilterModePoint:點模式,返回最接近的一個點,即最近鄰插值。插值公式 tex(x) = T(i),i=floor(x),注意是對座標向下取整,所以一般對輸入座標值+0.5,避免無法精確表示的某些數值出現錯誤取值,如 x=3,實際是2.99999,此時實際獲取的是x=2的元素。 cudaFilterModeLinear:線性模式,即線性插值,對於一維紋理,兩點插值;對於二維紋理,四點插值;對於三維紋理,八點插值。線性模式只有在fetch返回浮點類型數據(注意並非指read mode的歸一化浮點模式)下才有效。

addressMode說明了尋址方式
尋找模式,定義超出座標範圍的取值(越界情況)。一共四種模式:cudaAddressModeBorder, cudaAddressModeClamp(默認模式), cudaAddressModeWrap, and cudaAddressModeMirror,後兩個只能在歸一化座標時使用。addressing mode定義爲一個三維向量,分別代表紋理各個方向上的尋址模式。
cudaAddressModeClamp:超出範圍就用邊界值代替,示意: AA | ABCDE | EE
cudaAddressModeBorder:超出範圍就用零代替,示意: 00 | ABCDE | 00
cudaAddressModeWrap:重疊模式(循環),示意: DE | ABCDE || AB
cudaAddressModeMirror:鏡像模式,示意: BA | ABCDE | ED

綁定示例:

tex.addressMode[0]=cudaAddressModeWrap;
tex.addressMode[1]=cudaAddressModeWrap;
tex.filterMode = cudaFilterModeLinear;
tex.normalized =false;
cudaBindTextureToArray(tex,cuArray,channelDesc)
e.使用內存(紋理拾取)
tex2D(tex,x,y);
f.解開綁定並釋放cuda global內存。
cudaUnbindTexture(tex);
cudaFree(cuArray);

下面用一個展示CUDA二維紋理內存+OpenCV圖像濾波示例(注意:由於紋理內存使用浮點型4字節,對於opencv讀取RGB三通道,應使用 cv::cvtColor(src, src, CV_BGR2BGRA) 轉換爲RGBA三通道格):

#include "cuda.h"
#include "cuda_runtime.h"
#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/opencv.hpp"
#include "stdio.h"
using namespace std;
using namespace cv;

texture<uchar4,cudaTextureType2D,cudaReadModeNormalizedFloat> tex;
//cudaReadModeNormalizedFloat 爲了讓tex2D讀取,格式可轉換。

__global__ void smooth_kernel(char *img,int width,int heigth,int channels)
{
    unsigned int x = blockIdx.x*blockDim.x + threadIdx.x;
    unsigned int y = blockIdx.y*blockDim.y + threadIdx.y;
    unsigned int offset = x + y*blockDim.x+gridDim.x;

    //若使用歸一化
    float u = x/(float)width;
    float v = y/(float)heigth;

    //如果使用cudaReadModeElementType,則讀取uchar4不能轉爲float
    //圖像邊界越界自動處理!!
    float4 pixel    = tex2D(tex,x,y);
    float4 left     = tex2D(tex,x-1,y);
    float4 right    = tex2D(tex,x+1,y);
    float4 top      = tex2D(tex,x,y-1);
    float4 botton   = tex2D(tex,x,y+1);

    img[(y*width+x)*channels+0] = (left.x+right.x+top.x+botton.x)/4*255;
    img[(y*width+x)*channels+1] = (left.y+right.y+top.y+botton.y)/4*255;
    img[(y*width+x)*channels+2] = (left.z+right.z+top.z+botton.z)/4*255;
    img[(y*width+x)*channels+3] = 0;
}


#define IMAGE_DIR "1.jpg"

int main(int argc,char **argv)
{
        Mat src = imread(IMAGE_DIR,IMREAD_COLOR);

    //注意:紋理內存綁定限制每行應該爲256字節,也有非256字節掉對齊方法
    //  這裏爲了方便,我們將圖片resize位256*256大小
        resize(src, src, Size(256, 256));

        //爲了使用float的紋理,將RGB三字節的格式改爲BGRA四字節掉存儲方式
        cvtColor(src, src, CV_BGR2BGRA);

        int rows=src.rows;
        int cols=src.cols;
        int channels=src.channels();
        int width=cols,height=rows,size=rows*cols*channels;

        cudaChannelFormatDesc channelDesc=cudaCreateChannelDesc<uchar4>();
        cudaArray *cuArray;
        cudaMallocArray(&cuArray,&channelDesc,width,height);
        cudaMemcpyToArray(cuArray,0,0,src.data,size,cudaMemcpyHostToDevice);

        tex.addressMode[0]=cudaAddressModeWrap; 
        tex.addressMode[1]=cudaAddressModeWrap;
        tex.filterMode = cudaFilterModeLinear;  
        tex.normalized =false;          //不使用歸一化

        cudaBindTextureToArray(tex,cuArray,channelDesc);


        Mat out=Mat::zeros(width, height, CV_8UC4);
        char *dev_out=NULL;
        cudaMalloc((void**)&dev_out, size);

        dim3 dimBlock(16, 16);
        dim3 dimGrid((width + dimBlock.x - 1) / dimBlock.x, (height + dimBlock.y - 1) / dimBlock.y);
        smooth_kernel<<<dimGrid,dimBlock,0>>>(dev_out,width,height,channels);

        cudaMemcpy(out.data,dev_out,size,cudaMemcpyDeviceToHost);

        imshow("orignal",src);
        imshow("smooth_image",out);
        waitKey(0);

        cudaFree(dev_out);
        cudaFree(cuArray);
        cudaUnbindTexture(tex);
        return 0;

}

引用

[1] : https://blog.csdn.net/kelvin_yan/article/details/54019017
[2] : https://www.cnblogs.com/traceorigin/archive/2013/04/11/3015755.html
[3] : https://www.jianshu.com/p/6e2c50c5e0a6
[4] : https://www.cnblogs.com/riddick/p/7892663.html

發佈了27 篇原創文章 · 獲贊 2 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章