3.9_CUDA数组与纹理操作
3.9 CUDA数组与纹理操作
CUDA数组与设备内存从相同的物理内存池中分配,但是前者拥有一个细节不明的布局:为2D和3D局部性做了优化。图形驱动程序使用这些布局保存纹理,从地址中分离出数组索引,硬件可以在2D或3D元素块上操作,取代1D寻址。对展示出稀疏访问模式的应用程序,特别是有维度局部性的程序(例如,计算机视觉领域的应用程序),CUDA数组无疑会胜出;对拥有常规访问模式的应用程序,尤其是那些没有重用或那些重用可以被应用程序在共享内存上显式管理的,设备内存指针是显而易见的选择。
有些应用程序,像图像处理程序,会处在选择设备指针还是CUDA数组的两难处境中。如果所有的因素都相同,设备内存会是更好的选择,但是如下的几点考虑可能会对选择产生影响:
CUDA数组不消耗CUDA地址空间。
·在WDDM驱动程序中,系统可以自动管理CUDA数组的驻留。
· CUDA可以只在设备内存上驻留,GPU在总线上传输数据时在这两种形式之间转化。对于某些应用程序,在主机内存上保持一个等宽形式内存,在设备内存上建立CUDA数组是最好的处理办法。
3.9.1 纹理引用
纹理引用是CUDA用来设置纹理硬件解释实际内存内容的对象[1]。这个间接层存在的一部分原因是:这会使多个纹理引用使用不同的属性引用相同的内存变得有效。
纹理引用的属性可能是不可变的,也可能是可变的。前者在编译时间指定,不会导致应用程序表现的不正确或不定,后者中的应用程序以编译器不可见的方式改变纹理行为(见表3-5)。例如,纹理维数(1D、2D和3D)是不可变的,这必须由编译器预先获取并使用正确数量的输入参数,发出正确的机器指令。相对之下,滤波与寻址模式是可变的,它们隐式改变应用程序的行为,而编译器毫不知情。
表3-5 可变的与不可变纹理属性
CUDA运行时(语言集成特性)和CUDA驱动程序API处理纹理引用的行为十分不同。对双方来说,纹理引用都需要调用一个名为texture的模板来声明:
texture>Type, Dimension, ReadMode> Name;
Type指的是纹理所要在内存中读取的对象类型,Dimension是纹理的维度(1、2或3),ReadMode指定了在纹理读取时整数纹理类型是否转化为归一化浮点数。
纹理引用必须在使用前绑定在实际内存上。硬件从CUDA数组绑定纹理会更有优势,但在下述情况下,应用程序可以从设备内存绑定纹理中获利:
·作为带宽聚合器支持纹理缓存。
·使应用程序满足合并访问限制。
对那些最好通过设备内存写入的应用,可以避免从内存读取时的多余副本。例如,一个视频解码器或许希望发送给设备内存一个帧,而通过纹理方式读取它们。
一旦纹理引用与实际内存绑定,CUDA内核便可以调用tex*内建函数读取内存,例如表3-6中的tex1D()函数。
表3-6 纹理内建函数

注意 在这里对通过全局加载/存储或表面加载/存储执行的
纹理读和写之间没有一致性保证。因此,CUDA内核当心不要从那些可以使用其他方式访问的内存读取纹理。
1. CUDA运行时
为了绑定内存到纹理上,应用程序必须调用表3-7中的一个函数。CUDA运行时应用程序可以通过直接指定结构体成员修改可变的纹理引用属性。
texture(float, 1,udaReadMode ElementType> tex1;
...
tex1.filterMode =udaFilterModeLinear; // enable linear filtering
tex1normalized = true; // texture coordinates will be normalized表3-7 绑定设备内存到纹理的函数
指定这些结构体成员会立即生效,且不用重新绑定。
2. 驱动程序API
由于在使用驱动程序API时,CPU代码和GPU代码之间存在严格的分区,任何CUDA模块中声明的纹理引用都必须通过cuModuleGetTexRef()查询,这个函数传回一个Cutexref。不同于CUDA运行时,纹理引用必须使用所有正确的属性初始化——可变的和不变的——因为编译器不会编码纹理引用的不可变属性到CUDA模块中。表3-8总结了驱动程序API函数——在绑定纹理引用与内存中使用。
表3-8 绑定内存到纹理的驱动程序API函数
3.9.2 表面引用
表面引用可以使CUDA内核使用表面加载/存储内建函数读写CUDA数组。它是最近添加到CUDA中的,在特斯拉架构GPU上不可用。设置表面引用的初衷是使CUDA内核可以直接写入数组。在表面加载/存储函数实现之前,内核必须先写入设备内存,再执行一次从设备到数组的内存复制并转化输出为CUDA数组。
纹理引用与表面引用对比明显。前者可以把输入的任何坐标转化到设置的输出格式,后者则暴露了一个常规的逐位操作接口来读写 CUDA数组内容。
你可能会想,为什么CUDA没有实现表面加载/存储内建函数来直接在CUDA数组上操作(像OpenGL一样)。原因是想要表面操作与时俱进,能够以更加高级的方式使图形加载/存储操作转化为底层表达,例如将采样使用分数坐标放入CUDA数组,或与抗锯齿图形表面互操作。目前,CUDA开发者将需要通过软件来完成这些操作。
[1] 在CUDA 3.2前,纹理引用是除了显式的内存复制之外读取CUDA数组的唯一方式。