对于计算力高于2.0的设备,使用cudaArraySurfaceLoadStore标志创建的CUDA数组, 可以通过surface object.或者surface reference进行读写。不同的设备,所支持的表面内存的大小是不一样的。
表面对象的描述可以使用struct cudaResourceDesc来表示并通过调用cudaCreateSrufaceObject()来进行创建。
下示代码演示了对纹理进行简单变换的核函数的使用:
// Simple copy kernel
__global__ void copyKernel(cudaSurfaceObject_t inputSurfObj,
cudaSurfaceObject_t outputSurfObj,
int width, int height)
{
// Calculate surface coordinates
unsigned int x = blockIdx.x * blockDim.x + threadIdx.x;
unsigned int y = blockIdx.y * blockDim.y + threadIdx.y;
if (x < width && y < height) {
uchar4 data;
// Read from input surface
surf2Dread(&data, inputSurfObj, x * 4, y);
// Write to output surface
surf2Dwrite(data, outputSurfObj, x * 4, y);
}
}
// Host code
int main()
{
// Allocate CUDA arrays in device memory
cudaChannelFormatDesc channelDesc =
cudaCreateChannelDesc(8, 8, 8, 8,
cudaChannelFormatKindUnsigned);
cudaArray* cuInputArray;
cudaMallocArray(&cuInputArray, &channelDesc, width, height,
cudaArraySurfaceLoadStore);
cudaArray* cuOutputArray;
cudaMallocArray(&cuOutputArray, &channelDesc, width, height,
cudaArraySurfaceLoadStore);
// Copy to device memory some data located at address h_data
// in host memory
cudaMemcpyToArray(cuInputArray, 0, 0, h_data, size,
cudaMemcpyHostToDevice);
// Specify surface
struct cudaResourceDesc resDesc;
memset(&resDesc, 0, sizeof(resDesc));
resDesc.resType = cudaResourceTypeArray;
// Create the surface objects
resDesc.res.array.array = cuInputArray;
cudaSurfaceObject_t inputSurfObj = 0;
cudaCreateSurfaceObject(&inputSurfObj, &resDesc);
resDesc.res.array.array = cuOutputArray;
cudaSurfaceObject_t outputSurfObj = 0;
cudaCreateSurfaceObject(&outputSurfObj, &resDesc);
// Invoke kernel
dim3 dimBlock(16, 16);
dim3 dimGrid((width + dimBlock.x - 1) / dimBlock.x,
(height + dimBlock.y - 1) / dimBlock.y);
copyKernel<<>>(inputSurfObj,
outputSurfObj,
width, height);
// Destroy surface objects
cudaDestroySurfaceObject(inputSurfObj);
cudaDestroySurfaceObject(outputSurfObj);
// Free device memory
cudaFreeArray(cuInputArray);
cudaFreeArray(cuOutputArray);
return 0;
}
表面引用的声明的作用范围只是在文件 内:
surface surfRef;
其中,type指明了表面引用的类型,它可以是cudaSurgaceType1D、cudaSurgaceType2D、cudaSurgaceType3D、cudaSurgaceTypeCubemap、cudaSurgaceType1DLayered、cudaSurgaceType2DLayered、cudaSurgaceTypeCubemapLayered。该参数的默认值是cudaSurgaceType1D。一个表面纹理引用只能够被声明为全局静态变量,且不能够作为参数传递给函数。
在核函数使用表面引用来访存CUDA 数组前, 表面引用必须使用cudaBindSurfaceToArray()来与相应的数组进行绑定。
下面的代码演示了如何进行绑定:
// low-level API
surface surfRef;
surfaceReference* surfRefPtr;
cudaGetSurfaceReference(&surfRefPtr, "surfRef");
cudaChannelFormatDesc channelDesc;
cudaGetChannelDesc(&channelDesc, cuArray);
cudaBindSurfaceToArray(surfRef, cuArray, &channelDesc);
// high-level API
surface surfRef;
cudaBindSurfaceToArray(surfRef, cuArray);
一个CUDA 数组使用表面引用来进行读写时,维度和数据类型必须相匹配,否则是未定义的行为。不像纹理内存,表面内存使用的是字节寻址。也就是说,通过纹理函数来获取 x轴方向的纹理元的时后直接使用就可以了,而在表面函数需要乘上元素对应的字节大小。例如, 一维的浮点型CUDA 数组被绑定至纹理引用texRef时使用tex1d(texRef,x)来进行获取,而当绑定至surfRef时需要使用**surf1Dread(surfRef, 4*x)**来进行获取。
下示代码是对纹理使用了一些简单的变换。
// 2D surfaces
surface inputSurfRef;
surface outputSurfRef;
// Simple copy kernel
__global__ void copyKernel(int width, int height)
{
// Calculate surface coordinates
unsigned int x = blockIdx.x * blockDim.x + threadIdx.x;
unsigned int y = blockIdx.y * blockDim.y + threadIdx.y;
if (x < width && y < height) {
uchar4 data;
// Read from input surface
surf2Dread(&data, inputSurfRef, x * 4, y);
// Write to output surface
surf2Dwrite(data, outputSurfRef, x * 4, y);
}
}
// Host code
int main()
{
// Allocate CUDA arrays in device memory
cudaChannelFormatDesc channelDesc =
cudaCreateChannelDesc(8, 8, 8, 8,
cudaChannelFormatKindUnsigned);
cudaArray* cuInputArray;
cudaMallocArray(&cuInputArray, &channelDesc, width, height,
cudaArraySurfaceLoadStore);
cudaArray* cuOutputArray;
cudaMallocArray(&cuOutputArray, &channelDesc, width, height,
cudaArraySurfaceLoadStore);
// Copy to device memory some data located at address h_data
// in host memory
cudaMemcpyToArray(cuInputArray, 0, 0, h_data, size,
cudaMemcpyHostToDevice);
// Bind the arrays to the surface references
cudaBindSurfaceToArray(inputSurfRef, cuInputArray);
cudaBindSurfaceToArray(outputSurfRef, cuOutputArray);
// Invoke kernel
dim3 dimBlock(16, 16);
dim3 dimGrid((width + dimBlock.x - 1) / dimBlock.x,
(height + dimBlock.y - 1) / dimBlock.y);
copyKernel<<>>(width, height);
// Free device memory
cudaFreeArray(cuInputArray);
cudaFreeArray(cuOutputArray);
return 0;
}
cubemap表面的访存使用surfCubemapread()和surfCubemapwrite()来实现。它通常被当做二维layered表面,使用一个整数索引来标记一个面,两个浮点数来寻址该面对应层内的纹理元。面的存储顺序可以通过查阅相关资料得知。
cubemap表面的访存使用surfCubemapLayeredread()和surfCubemapLayeredwrite()来实现。它通常被当做二维layered表面,使用一个整数索引来标记一个cubemap中的一个面,两个浮点数来寻址该面对应层内的纹理元。面的存储顺序可以通过查阅相关资料得知。
CUDA 数组为了方便纹理的fetching, 其内存排布是经过了优化的,因此对外是不透明的。它可以是一维的、二维的、三维的或者是多种元素的组合。核函数对CUDA 数组的访存只能通过纹理的fetching或者表面的读写。
纹理和表面内存是被缓存的,在同一个核函数调用时, 缓存并不会与全局内存中保持一致。也就是说,一个线程只会在前一个核函数调用更新后后者内存拷贝之后才可以安全地读纹理或表面。如果是被核函数内的同一个线程或者其他线程更新再进行读取是不安全的。