来源:《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数组:6553665535
线性内存: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定义时就决定了