上篇文章介紹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