▶ 表面內存使用
● 創建 cuda 數組時使用標志 cudaArraySurfaceLoadStore 來創建表面內存,可以用表面對象(surface object)或表面引用(surface reference)來對其進行讀寫。
● 使用 Surface Object API
■ 涉及的結構定義、接口函數。
1 // vector_types.h 2 struct __device_builtin__ __align__(4) uchar4 3 { 4 unsigned char x, y, z, w; 5 }; 6 7 // surface_types.h 8 typedef __device_builtin__ unsigned long long cudaSurfaceObject_t;
■ 完整的測試代碼,使用表面內存進行簡單的讀寫。
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <malloc.h> 4 #include <cuda_runtime_api.h> 5 #include "device_launch_parameters.h" 6 7 #define CEIL(x,y) (((x) + (y) - 1) / (y) + 1) 8 9 __global__ void myKernel(cudaSurfaceObject_t inputSurfObj, cudaSurfaceObject_t outputSurfObj, int width, int height) 10 { 11 unsigned int idx = blockIdx.x * blockDim.x + threadIdx.x; 12 unsigned int idy = blockIdx.y * blockDim.y + threadIdx.y; 13 if (idx < width && idy < height) 14 { 15 uchar4 data; 16 // 簡單的表面內存讀寫,使用了字節地址,而不是簡單的線程編號 17 surf2Dread(&data, inputSurfObj, sizeof(float) * idx, idy); 18 surf2Dwrite(data, outputSurfObj, sizeof(float) * idx, idy); 19 } 20 cudaBindSurfaceToArray(); 21 } 22 23 int main() 24 { 25 // 基本數據 26 int i; 27 float *h_data, *d_data; 28 int width = 32; 29 int height = 32; 30 31 int size = sizeof(float)*width*height; 32 h_data = (float *)malloc(size); 33 cudaMalloc((void **)&d_data, size); 34 35 for (i = 0; i < width*height; i++) 36 h_data[i] = (float)i; 37 38 printf("\n\n"); 39 for (i = 0; i < width*height; i++) 40 { 41 printf("%6.1f ", h_data[i]); 42 if ((i + 1) % width == 0) 43 printf("\n"); 44 } 45 46 // 申請 cuda 數組 47 cudaChannelFormatDesc channelDesc = cudaCreateChannelDesc(8, 8, 8, 8, cudaChannelFormatKindUnsigned); 48 cudaArray* cuInputArray; 49 cudaMallocArray(&cuInputArray, &channelDesc, width, height, cudaArraySurfaceLoadStore); 50 cudaArray* cuOutputArray; 51 cudaMallocArray(&cuOutputArray, &channelDesc, width, height, cudaArraySurfaceLoadStore); 52 cudaMemcpyToArray(cuInputArray, 0, 0, h_data, size,cudaMemcpyHostToDevice); 53 54 // 指定表面內存 55 struct cudaResourceDesc resDesc; 56 memset(&resDesc, 0, sizeof(resDesc)); 57 resDesc.resType = cudaResourceTypeArray; 58 59 // 創建表面對象 60 resDesc.res.array.array = cuInputArray; 61 cudaSurfaceObject_t inputSurfObj = 0; 62 cudaCreateSurfaceObject(&inputSurfObj, &resDesc); 63 resDesc.res.array.array = cuOutputArray; 64 cudaSurfaceObject_t outputSurfObj = 0; 65 cudaCreateSurfaceObject(&outputSurfObj, &resDesc); 66 67 // 運行核函數 68 dim3 dimBlock(16, 16); 69 dim3 dimGrid(CEIL(width, dimBlock.x), CEIL(height, dimBlock.y)); 70 myKernel << <dimGrid, dimBlock >> > (inputSurfObj, outputSurfObj, width, height); 71 72 // 結果回收和檢查結果 73 memset(h_data,0,size);// 刷掉原來的 h_data,再用 cuOutputArray 的數據寫入 74 cudaMemcpyFromArray(h_data, cuOutputArray, 0, 0, size, cudaMemcpyDeviceToHost); 75 76 printf("\n\n"); 77 for (i = 0; i < width*height; i++) 78 { 79 printf("%6.1f ", h_data[i]); 80 if ((i + 1) % width == 0) 81 printf("\n"); 82 } 83 84 // 回收工作 85 cudaDestroySurfaceObject(inputSurfObj); 86 cudaDestroySurfaceObject(outputSurfObj); 87 cudaFreeArray(cuInputArray); 88 cudaFreeArray(cuOutputArray); 89 90 getchar(); 91 return 0; 92 }
● 使用 Surface Reference API。
■ 表面引用的一些只讀屬性需要在聲明的時候指定,以便編譯時提前確定,只能在全局作用域內靜態指定,不能作為參數傳遞給函數。使用 surface 指定紋理引用屬性,Datatype 為數據類型,Type 為紋理引用類型,有 7 種,默認 cudaSurfaceType1D。
1 surface<void, Type> surfRef; 2 3 // cuda_texture_types.h 4 template<class T, int dim = 1> 5 struct __device_builtin_surface_type__ surface : public surfaceReference 6 { 7 #if !defined(__CUDACC_RTC__) 8 __host__ surface(void) 9 { 10 channelDesc = cudaCreateChannelDesc<T>(); 11 } 12 13 __host__ surface(struct cudaChannelFormatDesc desc) 14 { 15 channelDesc = desc; 16 } 17 #endif /* !__CUDACC_RTC__ */ 18 }; 19 20 //surface_types.h 21 #define cudaSurfaceType1D 0x01 22 #define cudaSurfaceType2D 0x02 23 #define cudaSurfaceType3D 0x03 24 #define cudaSurfaceTypeCubemap 0x0C 25 #define cudaSurfaceType1DLayered 0xF1 26 #define cudaSurfaceType2DLayered 0xF2 27 #define cudaSurfaceTypeCubemapLayered 0xFC 28 29 // 訪問邊界模式 30 enum __device_builtin__ cudaSurfaceBoundaryMode 31 { 32 cudaBoundaryModeZero = 0, // 0 邊界模式 33 cudaBoundaryModeClamp = 1, // 擠壓模式 34 cudaBoundaryModeTrap = 2 // 陷阱模式 35 }; 36 37 // ?表面格式模式 38 enum __device_builtin__ cudaSurfaceFormatMode 39 { 40 cudaFormatModeForced = 0, // 強制模式 41 cudaFormatModeAuto = 1 // 自動模式 42 }; 43 44 // 表面引用的通道描述 45 struct __device_builtin__ surfaceReference 46 { 47 struct cudaChannelFormatDesc channelDesc; 48 }; 49 50 // cuda_runtime_api.h 51 extern __host__ cudaError_t CUDARTAPI cudaBindSurfaceToArray(const struct surfaceReference *surfref, cudaArray_const_t array, const struct cudaChannelFormatDesc *desc);
■ 表面引用使用字節地址來定位訪問(而不是像紋理那樣使用 fetch 函數),如以上代碼中 surf1Dread(surfRef, sizeof(float) * idx) 或是 surf1Dread(surfRef, sizeof(float) * idx) 。
■ 表面引用必須用函數 cudaBindSurfaceToArray() 綁定到 cuda 數組上才能使用,要求表面引用的維度、數據類型與該數組匹配,否則操作時未定義的,使用完后不需要特殊函數來解除綁定。
■ 將表面引用綁定到 cuda 數組上的范例代碼。
1 // 准備工作 2 surface<void, Type>surfRef; 3 4 ... 5 6 int width, height; 7 size_t pitch; 8 float *d_data; 9 cudaMallocPitch((void **)&d_data, &pitch, sizeof(float)*width, height); 10 11 // 第一種方法,低層 API 12 surfaceReference* surfRefPtr; 13 cudaGetSurfaceReference(&surfRefPtr, "surfRef"); 14 cudaChannelFormatDesc channelDesc; 15 cudaGetChannelDesc(&channelDesc, cuArray); 16 cudaBindSurfaceToArray(surfRef, cuArray, &channelDesc); 17 18 // 第二種方法,高層 API 19 cudaBindSurfaceToArray(surfRef, cuArray);
■ 完整的應用樣例代碼。與前面表面對象代碼的功能相同。
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <malloc.h> 4 #include <cuda_runtime_api.h> 5 #include "device_launch_parameters.h" 6 7 #define CEIL(x,y) (((x) + (y) - 1) / (y) + 1) 8 9 // 聲明表面引用 10 surface<void, 2> inputSurfRef; 11 surface<void, 2> outputSurfRef; 12 13 __global__ void myKernel(int width, int height) 14 { 15 unsigned int idx = blockIdx.x * blockDim.x + threadIdx.x; 16 unsigned int idy = blockIdx.y * blockDim.y + threadIdx.y; 17 if (idx < width && idy < height) 18 { 19 uchar4 data; 20 // 簡單的表面內存讀寫,使用了字節地址,而不是簡單的線程編號 21 surf2Dread(&data, inputSurfRef, sizeof(float) * idx, idy); 22 surf2Dwrite(data, outputSurfRef, sizeof(float) * idx, idy); 23 } 24 } 25 26 int main() 27 { 28 // 基本數據 29 int i; 30 float *h_data, *d_data; 31 int width = 32; 32 int height = 32; 33 34 int size = sizeof(float)*width*height; 35 h_data = (float *)malloc(size); 36 cudaMalloc((void **)&d_data, size); 37 38 for (i = 0; i < width*height; i++) 39 h_data[i] = (float)i; 40 41 printf("\n\n"); 42 for (i = 0; i < width*height; i++) 43 { 44 printf("%6.1f ", h_data[i]); 45 if ((i + 1) % width == 0) 46 printf("\n"); 47 } 48 49 // 申請 cuda 數組 50 cudaChannelFormatDesc channelDesc = cudaCreateChannelDesc(8, 8, 8, 8, cudaChannelFormatKindUnsigned); 51 cudaArray* cuInputArray; 52 cudaMallocArray(&cuInputArray, &channelDesc, width, height, cudaArraySurfaceLoadStore); 53 cudaArray* cuOutputArray; 54 cudaMallocArray(&cuOutputArray, &channelDesc, width, height, cudaArraySurfaceLoadStore); 55 cudaMemcpyToArray(cuInputArray, 0, 0, h_data, size,cudaMemcpyHostToDevice); 56 57 // 綁定表面引用,注意與表面對象的使用不一樣 58 cudaBindSurfaceToArray(inputSurfRef, cuInputArray); 59 cudaBindSurfaceToArray(outputSurfRef, cuOutputArray); 60 61 // 運行核函數 62 dim3 dimBlock(16, 16); 63 dim3 dimGrid(CEIL(width, dimBlock.x), CEIL(height, dimBlock.y)); 64 myKernel << <dimGrid, dimBlock >> > (width, height); 65 66 // 結果回收和檢查結果 67 memset(h_data,0,size);// 刷掉原來的 h_data,再用 cuOutputArray 的數據寫入 68 cudaMemcpyFromArray(h_data, cuOutputArray, 0, 0, size, cudaMemcpyDeviceToHost); 69 70 printf("\n\n"); 71 for (i = 0; i < width*height; i++) 72 { 73 printf("%6.1f ", h_data[i]); 74 if ((i + 1) % width == 0) 75 printf("\n"); 76 } 77 78 // 回收工作 79 cudaFreeArray(cuInputArray); 80 cudaFreeArray(cuOutputArray); 81 82 getchar(); 83 return 0; 84 }
▶ 立方體表面 Cubemap Surface 。 (想象成一個正方體的外表面)
● 一種特殊的二維分層表面。函數 surfCubemapread() 和函數 surfCubemapwrite() 來對其進行讀寫,使用一個整數下標和兩個浮點數有序組來定義層號和表面坐標。
▶ 分層立方體表面 Cubemap Layered Surfaces 。(想象成一個多層的正方體的各外表面)
● 一種特殊的二維分層表面。函數 surfCubemapread() 和函數 surfCubemapwrite() 來對齊進行讀寫。使用一個整數下標和兩個浮點數有序組來定義層號和表面坐標。
● 分層立方體貼圖紋理只能使用函數 cudaMAlloc3DArray() 加上 cudaArrayLayered 和 cudaArrayCubemap 標志來聲明,使用函數 texCubemapLayered() 來進行訪問濾波只在同一層內部進行,不會跨層執行。
▶ cuda 數組。
● cuda 優化的數組類型,可以有一維或二維或三維,每個元素可以有 1 個或 2 個或 4 個分量,各分量可以是 1 B 或 2 B 或 4 B 尺寸的有符號或無符號整數,或 2 B 或 4 B 尺寸的浮點數。cuda 數組只能用紋理訪問函數來訪問,或表面函數來進行讀寫。
● 紋理內存和表面內存都是可緩存的,且不能保證緩存和內存的一致性。同一個核函數中,用紋理訪問或表面訪問來讀取“已經全局寫入或表面寫入的內存”是未定義的。
▶ 壓縮版的 surface_types.h
1 #if !defined(__SURFACE_TYPES_H__) 2 #define __SURFACE_TYPES_H__ 3 4 #include "driver_types.h" 5 6 #define cudaSurfaceType1D 0x01 7 #define cudaSurfaceType2D 0x02 8 #define cudaSurfaceType3D 0x03 9 #define cudaSurfaceTypeCubemap 0x0C 10 #define cudaSurfaceType1DLayered 0xF1 11 #define cudaSurfaceType2DLayered 0xF2 12 #define cudaSurfaceTypeCubemapLayered 0xFC 13 14 //CUDA Surface boundary modes 15 enum __device_builtin__ cudaSurfaceBoundaryMode 16 { 17 cudaBoundaryModeZero = 0, // Zero boundary mode */ 18 cudaBoundaryModeClamp = 1, // Clamp boundary mode */ 19 cudaBoundaryModeTrap = 2 // Trap boundary mode */ 20 }; 21 22 //CUDA Surface format modes 23 enum __device_builtin__ cudaSurfaceFormatMode 24 { 25 cudaFormatModeForced = 0, // Forced format mode */ 26 cudaFormatModeAuto = 1 // Auto format mode */ 27 }; 28 29 //CUDA Surface reference 30 struct __device_builtin__ surfaceReference 31 { 32 // Channel descriptor for surface reference 33 struct cudaChannelFormatDesc channelDesc; 34 }; 35 36 //An opaque value that represents a CUDA Surface object 37 typedef __device_builtin__ unsigned long long cudaSurfaceObject_t; 38 39 40 #endif