CUDA总结:纹理内存

来源:《CUDA_Runtime_API》、《CUDA_C_Programming_Guide-V8.0》

纹理内存和表面内存(surface memory)实质上是全局内存的一个特殊形态,全局内存被绑定为纹理内存(表面内存),对其的读(写)操作将通过专门的texture cache(纹理缓存)进行,其实称为纹理缓存更加贴切。
纹理缓存的优势:纹理缓存具备硬件插值特性,可以实现最近邻插值和线性插值。纹理缓存针对二维空间的局部性访问进行了优化,所以通过纹理缓存访问二维矩阵的邻域会获得加速。纹理缓存不需要满足全局内存的合并访问条件

纹理的创建通过texture object或者texture reference,注意只有计算能力3.0以上的设备支持texture object

texture object在运行时创建,texture reference在编译期创建,运行时绑定到实际的纹理

texture reference必须定义为全局变量,不能作为函数的参数

纹理可以是一段连续的设备内存,也可以是一个CUDA数组。但是CUDA数组对局部寻址有优化,称为“块线性”,原理是将邻域元素缓存在同一条cache线上,这将加快邻域内的寻址,但是对于设备内存,并没有“块线性”。所以,选择采用CUDA数组,还是设备内存,需要根据实际情况决定,将数据copy至CUDA数组是很耗时的。

纹理的一个元素称为texels

纹理最多支持三维,width,x方向的维数;height,y方向的维数;depth,z方向的维数

read mode:(1)归一化浮点模式,fetch将返回【0.0,1.0】的归一化之后的数值,输入数据类型为8位、16位整型时,会根据数据范围映射至[0.0,1.0]或[-1.0,1.0],例如,unsigned char输入255输出1.0,又如signed char输入-127输出-1.0;(2)元素类型模式,fetch返回原始数值

纹理支持归一化的浮点坐标,计算方式如下:[0.0,1-1/N],N为当前维度的维数。归一化坐标在某些情况下非常方便,如放大缩小操作


addressing mode:寻找模式,定义超出坐标范围的取值(越界情况)。一共四种模式:cudaAddressModeBorder, cudaAddressModeClamp(默认模式), cudaAddressModeWrap, and cudaAddressModeMirror,后两个只能在归一化坐标时使用。addressing mode定义为一个三维向量,分别代表纹理各个方向上的寻址模式

cudaAddressModeClamp:超出范围就用边界值代替,示意: AA | ABCDE | EE
cudaAddressModeBorder:超出范围就用零代替,示意: 00 | ABCDE | 00
cudaAddressModeWrap:重叠模式(循环),示意: DE | ABCDE || AB
cudaAddressModeMirror:镜像模式,示意: BA | ABCDE | ED


filtering mode:滤波模式,定义了fetch返回结果的计算方式。有两种模式:cudaFilterModePoint or cudaFilterModeLinear

cudaFilterModePoint:点模式,返回最接近的一个点,即最近邻插值。插值公式 tex(x) = T(i),i=floor(x),注意是对坐标向下取整,所以一般对输入坐标值+0.5,避免无法精确表示的某些数值出现错误取值,如 x=3,实际是2.99999,此时实际获取的是x=2的元素
cudaFilterModeLinear:线性模式,即线性插值,对于一维纹理,两点插值;对于二维纹理,四点插值;对于三维纹理,八点插值。线性模式只有在fetch返回浮点类型数据(注意并非指read mode的归一化浮点模式)下才有效

最近邻插值

线性插值

线性插值公式:
tex(x)=(1−α)T[i]+αT[i+1]
tex(x,y)=(1−α)(1−β)T[i,j]+α(1−β)T[i+1,j]+(1−α)βT[i,j+1]+αβT[i+1,j+1]
tex(x,y,z) =
(1−α)(1−β)(1−γ)T[i,j,k]+α(1−β)(1−γ)T[i+1,j,k]+
(1−α)β(1−γ)T[i,j+1,k]+αβ(1−γ)T[i+1,j+1,k]+
(1−α)(1−β)γT[i,j,k+1]+α(1−β)γT[i+1,j,k+1]+
(1−α)βγT[i,j+1,k+1]+αβγT[i+1,j+1,k+1]
其中
i=floor(xB), α=frac(xB), xB=x-0.5
j=floor(yB), β=frac(yB), yB=y-0.5
k=floor(zB), γ=frac(zB), zB= z-0.5
x、y、z分别是三个方向上的实际坐标值,在其基础上再减去0.5才是访问纹理时的坐标。
如,坐标(3,7)的像素,执行tex2D(TexSrc, 3, 7),将对(2.5,6.5)的坐标进行插值,此时参与插值计算的四个点分别为(2,6)、(2,7)、(3,7)、(3,6),按照上面的公式:
tex(3,7)=(1-0.5)(1-0.5)TexSrc[2,6] + 0.5(1-0.5)TexSrc[3,6] + (1-0.5)0.5TexSrc[2,6] + 0.50.5TexSrc[3,7]
所以,对二维数组中的某个点进行插值fetch,就是对其左上方的四个点(包括该点)进行插值。


Texture Object API
纹理对象

创建纹理对象的函数:

__host__cudaError_t cudaCreateTextureObject
(cudaTextureObject_t *pTexObject, const
cudaResourceDesc *pResDesc, const cudaTextureDesc
*pTexDesc, const cudaResourceViewDesc
*pResViewDesc)

第二个参数cudaResourceDesc的定义如下:

//封装输入数据
struct cudaResourceDesc {
	enum cudaResourceType
		resType;
	union {
		
		struct {
			cudaArray_t array;
		} array;  //cuda数组
		
		struct {
			cudaMipmappedArray_t mipmap;
		} mipmap;  //mipmap数组

		struct {
			void *devPtr;  //!设备指针,必须符合cudaDeviceProp::textureAlignment的对齐要求
			struct cudaChannelFormatDesc desc;  //texels的属性
			size_t sizeInBytes;  //数组的字节长度
		} linear;   //一维数组
		
		struct {
			void *devPtr;  //!设备指针,必须符合cudaDeviceProp::textureAlignment的对齐要求
			struct cudaChannelFormatDesc desc; //texels的属性
			size_t width; //宽,以数组元素为单位
			size_t height; //高,以数组元素为单位
			size_t pitchInBytes;  //每一行的字节长度
		} pitch2D;  //二维数组
	
	} res;
};

其中,cudaResourceType 定义如下:

enum cudaResourceType {
	cudaResourceTypeArray = 0x00,
	cudaResourceTypeMipmappedArray = 0x01,
	cudaResourceTypeLinear = 0x02,
	cudaResourceTypePitch2D = 0x03
};

cudaChannelFormatDesc定义如下:

struct __device_builtin__ cudaChannelFormatDesc
{
    int                        x; /**< x,通道0的数据位深度(比特数) */
    int                        y; /**< y,通道1的数据位深度 */
    int                        z; /**< z,通道2的数据位深度 */
    int                        w; /**< w */
    enum cudaChannelFormatKind f; /**< Channel format kind */
};

cudaChannelFormatKind 定义如下:

enum __device_builtin__ cudaChannelFormatKind
{
    cudaChannelFormatKindSigned           =   0,      /**< Signed channel format */
    cudaChannelFormatKindUnsigned         =   1,      /**< Unsigned channel format */
    cudaChannelFormatKindFloat            =   2,      /**< Float channel format */
    cudaChannelFormatKindNone             =   3       /**< No channel format */
};

第三个参数cudaTextureDesc的定义如下:

struct cudaTextureDesc {
	enum cudaTextureAddressMode addressMode[3]; //!在cudaResourceDesc::resType为cudaResourceTypeLinear时无效
	enum cudaTextureFilterMode filterMode; //!在cudaResourceDesc::resType为cudaResourceTypeLinear时无效
	enum cudaTextureReadMode readMode;
	int sRGB;
	float borderColor[4];
	int normalizedCoords;  //指定是否归一化坐标
	unsigned int maxAnisotropy;
	enum cudaTextureFilterMode mipmapFilterMode;
	float mipmapLevelBias;
	float minMipmapLevelClamp;
	float maxMipmapLevelClamp;
};

其中,cudaTextureAddressMode的定义如下:

enum cudaTextureAddressMode {
	cudaAddressModeWrap = 0,
	cudaAddressModeClamp = 1,
	cudaAddressModeMirror = 2,
	cudaAddressModeBorder = 3
};

cudaTextureFilterMode 的定义如下:

enum cudaTextureFilterMode {
	cudaFilterModePoint = 0,
	cudaFilterModeLinear = 1
};

cudaTextureReadMode的定义如下:

enum cudaTextureReadMode {
	cudaReadModeElementType = 0,
	cudaReadModeNormalizedFloat = 1
};

纹理对象需要显式销毁(类似于new和delete),销毁纹理对象的函数:

__host__cudaError_t cudaDestroyTextureObject
(cudaTextureObject_t texObject)

纹理对象本身定义:

typedef __device_builtin__ unsigned long long cudaTextureObject_t;

纹理对象对外只是一个透明的数值,指示当前纹理的序号,如,依次创建了5个纹理,那么第一个创建的纹理对象值为1,第二个为2…。纹理对象创建之后,纹理属性等信息被保存在设备的某个地方,注意纹理对象的生命期是全局的!也就是通过纹理的序号,可以在其它函数体中访问,只要该纹理没有被销毁!设备管理着一个纹理列表,用户每次创建一个纹理,将会在纹理列表中第一个空位插入,如,现有纹理1、2、3、4、5,销毁纹理2和纹理5,那么下一次创建的纹理将在第2个位置插入,再创建一个纹理将在第5个位置插入。
纹理对象的管理很方便,只需要保存纹理对象本身,即纹理的序号。

对纹理对象进行访问、操作需要通过CUDA API,API就是通过纹理的序号识别不通的纹理,所以,纹理对象实质上是一个全局变量,在kernel执行过程中不能修改纹理所对应的设备内存,否则会引起data race。
当前设备允许每个kernel拥有的最大纹理数为256,也就是同一时刻仅允许存在256个纹理。

获取纹理对象的元素属性:

__host__cudaError_t
cudaGetTextureObjectResourceDesc (cudaResourceDesc
*pResDesc, cudaTextureObject_t texObject)

获取纹理对象的纹理属性:

__host__cudaError_t cudaGetTextureObjectTextureDesc
(cudaTextureDesc *pTexDesc, cudaTextureObject_t
texObject)

纹理对象的创建和销毁是否耗时?将设备内存绑定为纹理,仅仅是指明了该段内存的读取是通过纹理缓存进行,并插入设备内建的纹理列表中。并没有任何实质性的操作,所以并不耗时。同理,纹理的销毁也基本不会耗时,仅仅是从设备的纹理列表中清除该纹理。

某段内存绑定为纹理,我们可以对该段内存进行写操作,修改其中的内容,此时再通过纹理获取相应的内存,将是改变后的内容。所以,使用纹理时,千万要注意数据竞争问题。


Texture Reference API
纹理引用

绑定设备内存为纹理

C风格用法:

texture<float, cudaTextureType2D,
	cudaReadModeElementType> texRef;
textureReference* texRefPtr;
cudaGetTextureReference(&texRefPtr, &texRef);  //获取纹理引用指针
cudaChannelFormatDesc channelDesc =
	cudaCreateChannelDesc<float>();
size_t offset;
cudaBindTexture2D(&offset, texRefPtr, devPtr, &channelDesc,
	width, height, pitch);   //texRef、channelDesc传递的是指针

C++风格用法:

texture<float, cudaTextureType2D,
	cudaReadModeElementType> texRef;
cudaChannelFormatDesc channelDesc =
	cudaCreateChannelDesc<float>();
size_t offset;
cudaBindTexture2D(&offset, texRef, devPtr, channelDesc,
width, height, pitch);  //直接传递对象,texRef、channelDesc

可以通过纹理引用设置纹理属性,纹理引用定义如下:

struct textureReference {
	int normalized;
	enum cudaTextureFilterMode filterMode;
	enum cudaTextureAddressMode addressMode[3];
	struct cudaChannelFormatDesc channelDesc;
	int sRGB;
	unsigned int maxAnisotropy;
	enum cudaTextureFilterMode mipmapFilterMode;
	float mipmapLevelBias;
	float minMipmapLevelClamp;
	float maxMipmapLevelClamp;
}

示例如下:

// Set texture reference parameters
texRef.addressMode[0] = cudaAddressModeWrap;
texRef.addressMode[1] = cudaAddressModeWrap;
texRef.filterMode = cudaFilterModeLinear;
texRef.normalized = true;

绑定CUDA数组为纹理

C风格:

texture<float, cudaTextureType2D,
	cudaReadModeElementType> texRef;
textureReference* texRefPtr;
cudaGetTextureReference(&texRefPtr, &texRef);
cudaChannelFormatDesc channelDesc;
cudaGetChannelDesc(&channelDesc, cuArray);
cudaBindTextureToArray(texRef, cuArray, &channelDesc);

C++风格:

texture<float, cudaTextureType2D,
cudaReadModeElementType> texRef;
cudaBindTextureToArray(texRef, cuArray);

TEXTURE FETCHING
纹理的访问

一维纹理的维数限制
CUDA数组:65536
线性内存:2^27

二维纹理的维数限制
(wh)
CUDA数组:65536
65535
线性内存:65000*65000

纹理对象用法:

访问一维数组

template<class T>
T tex1Dfetch(cudaTextureObject_t texObj, int x);  //非归一化坐标,用于访问由设备内存创建的纹理。索引只能是整型数值,不进行任何的插值,允许border和clamp越界处理。
template<class T>
T tex1D(cudaTextureObject_t texObj, float x);  //归一化坐标,用于访问由CUDA数组创建的纹理

访问二维数组

template<class T>
T tex2D(cudaTextureObject_t texObj, float x, float y);

纹理引用用法:

template<class DataType>
Type tex1Dfetch(
	texture<DataType, cudaTextureType1D,
	cudaReadModeElementType> texRef,
	int x);
template<class DataType, enum cudaTextureReadMode readMode>
Type tex1D(texture<DataType, cudaTextureType1D, readMode> texRef,
	float x);
template<class DataType, enum cudaTextureReadMode readMode>
Type tex2D(texture<DataType, cudaTextureType2D, readMode> texRef,
	float x, float y);

纹理引用与纹理对象的区别,在fetch时不需要输入模板参数,因为在texture定义时就决定了

  • 17
    点赞
  • 52
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值