CUDA C编程手册: 编程接口(七)

本文介绍CUDA如何与OpenGL、Direct3D交互,实现图形资源的共享和处理,包括资源注册、映射、核函数调用等关键步骤。同时,讨论了多GPU系统中SLI配置的注意事项,以及CUDA版本兼容性和计算模式的设定。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

运行时

图形交互

一些来自其他软件的资源, 如OpenGL, Direct3D 可以映射至CUDA的地址空间。 这样CUDA 就可读取由其他程序所写的数据, 同时也能产出可以供它们使用的数据。

在将资源映射至CUDA的地址空间之前,它必须使用一些API中的函数来先进行注册。这些函数会返回一个指向CUDA图形资源struct cudaGraphicsResource的指针。注册资源会有很高的overhead,因此每个资源最好只注册一次。CUDA图形资源也可以使用cudaGraphicsUnregistrerResource()来使之变成未注册。每个CUDA context试图使用的资源都必须对它进行单独注册。

被注册至CUDA的资源,在必要的时候它可以通过APIcudaGraphicsMapResource()cudaGraphicsUnmapResource()映射或者取消映射多次。cudaGraphicsResourceSetMapFlags()可以用来指定一些标志(只读、只写), 这样有利于CUDA 驱动来有优化资源的管理。核函数可以对 被映射的资源进行读写,它需要借助cudaGraphicsResourcdeGetMappedPointer()获取的buffer的指针; cudaGraphicsSubResourcdeGetMappedArray()获取CUDA数组。

OpenGL 交互

OpenGL的资源可以映射至CUDA地址空间的资源有:buffertexture_和_renderbuffer 对象。buffer__对象可以使用cudaGraphicsGLRegisterBuffer()来进行注册。在CUDA中,它表现的和一个设备指针一样, 因此可以通过cudaMemcpy()来进行读写。texture 和_renderbuffer 对象需要使用cudaGraphicsGLRegisterImage()来进行注册。在CUDA中,它表现的像CUDA 数组。核函数可以对绑定至纹理引用或者表面引用的“数组”进行读取,也可以通过表面函数对在注册时使用了cudaGraohicwRegistrerFlagsSurfaceLoadStore标志的资源进行写操作。这些数组也可以通过cudaMemcpy2D来进行读写操作。cudaGraphicsGLRegisterImage()支持所有的纹理格式(1, 2,4分量或者内置类型,归一化整数,非归一化整数)。

共享资源的OpenGL上下文必须是进行任何OpenGL互操作API调用的主机线程的最新上下文。需要注意的是,当一个OpenGL的纹理被设置成不可绑定时,它就不能被CUDA注册。

下示代码是利用一个核函数来动态的修改一个存储于顶点buffer的2D网格对象:

GLuint positionsVBO;
struct cudaGraphicsResource* positionsVBO_CUDA;
int main()
{
	// Initialize OpenGL and GLUT for device 0
	// and make the OpenGL context current
	...
	glutDisplayFunc(display);
	// Explicitly set device 0
	cudaSetDevice(0);
	// Create buffer object and register it with CUDA
	glGenBuffers(1, &positionsVBO);
	glBindBuffer(GL_ARRAY_BUFFER, positionsVBO);
	unsigned int size = width * height * 4 * sizeof(float);
	glBufferData(GL_ARRAY_BUFFER, size, 0, GL_DYNAMIC_DRAW);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	cudaGraphicsGLRegisterBuffer(&positionsVBO_CUDA,
	positionsVBO,
	cudaGraphicsMapFlagsWriteDiscard);
	// Launch rendering loop
	glutMainLoop();
	...
}

void display()
{
	// Map buffer object for writing from CUDA
	float4* positions;
	cudaGraphicsMapResources(1, &positionsVBO_CUDA, 0);
	size_t num_bytes;
	cudaGraphicsResourceGetMappedPointer((void**)&positions,
	&num_bytes,
	positionsVBO_CUDA));
	// Execute kernel
	dim3 dimBlock(16, 16, 1);
	dim3 dimGrid(width / dimBlock.x, height / dimBlock.y, 1);
	createVertices<<<dimGrid, dimBlock>>>(positions, time,
	width, height);
	// Unmap buffer object
	cudaGraphicsUnmapResources(1, &positionsVBO_CUDA, 0);
	// Render from buffer object
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glBindBuffer(GL_ARRAY_BUFFER, positionsVBO);
	glVertexPointer(4, GL_FLOAT, 0, 0);
	glEnableClientState(GL_VERTEX_ARRAY);
	glDrawArrays(GL_POINTS, 0, width * height);
	glDisableClientState(GL_VERTEX_ARRAY);
	// Swap buffers
	glutSwapBuffers();
	glutPostRedisplay();
}

void deleteVBO()
{
	cudaGraphicsUnregisterResource(positionsVBO_CUDA);
	glDeleteBuffers(1, &positionsVBO);
}
__global__ void createVertices(float4* positions, float time,
unsigned int width, unsigned int height)
{
	unsigned int x = blockIdx.x * blockDim.x + threadIdx.x;
	unsigned int y = blockIdx.y * blockDim.y + threadIdx.y;
	// Calculate uv coordinates
	float u = x / (float)width;
	float v = y / (float)height;
	u = u * 2.0f - 1.0f;
	v = v * 2.0f - 1.0f;
	// calculate simple sine wave pattern
	float freq = 4.0f;
	float w = sinf(u * freq + time)
	* cosf(v * freq + time) * 0.5f;
	// Write positions
	positions[y * width + x] = make_float4(u, w, v, 1.0f);
}

Direct3D交互

Direct3D 交互支持 Direct3D 9Ex, Direct3D 10 以及Direct3D 11。CUDA 上下文可以交互的Direct3D 设备必须有一些设置: Direct3D 9Ex 设备必须在创建时将DeivceType设置为D3DDEVTYPE_HAL, BehaviorFlags设置为D3DCREATE_HARDWARE_VERTESPROCESSING; Direct3D 10 和 Direct3D 11 的设备在创建时需将DeivceType设置为D3D_DRIVER_TYPE_HARDWARE

Direct3D 可以映射至CUDA空间的资源包括: Direct3D buffertextures, surface。这些资源可以通过cudaGraphicsD3D9RegisterResource()cudaGraphicsD3D10RegisterResource()cudaGraphicsD3D11RegisterResource()

下示代码是利用一个核函数来动态的修改一个存储于顶点buffer的2D网格对象:

// Direct3D 9 Version
IDirect3D9* D3D;
IDirect3DDevice9* device;
struct CUSTOMVERTEX {
	FLOAT x, y, z;
	DWORD color;
};
IDirect3DVertexBuffer9* positionsVB;
struct cudaGraphicsResource* positionsVB_CUDA;
int main()
{
	int dev;
	// Initialize Direct3D
	D3D = Direct3DCreate9Ex(D3D_SDK_VERSION);
	// Get a CUDA-enabled adapter
	unsigned int adapter = 0;
	for (; adapter < g_pD3D->GetAdapterCount(); adapter++) {
	D3DADAPTER_IDENTIFIER9 adapterId;
	g_pD3D->GetAdapterIdentifier(adapter, 0, &adapterId);
	if (cudaD3D9GetDevice(&dev, adapterId.DeviceName)
	== cudaSuccess)
	break;
}
// Create device
...
D3D->CreateDeviceEx(adapter, D3DDEVTYPE_HAL, hWnd,
D3DCREATE_HARDWARE_VERTEXPROCESSING,
&params, NULL, &device);
// Use the same device
cudaSetDevice(dev);
// Create vertex buffer and register it with CUDA
unsigned int size = width * height * sizeof(CUSTOMVERTEX);
device->CreateVertexBuffer(size, 0, D3DFVF_CUSTOMVERTEX,
D3DPOOL_DEFAULT, &positionsVB, 0);
cudaGraphicsD3D9RegisterResource(&positionsVB_CUDA,
positionsVB,
cudaGraphicsRegisterFlagsNone);
cudaGraphicsResourceSetMapFlags(positionsVB_CUDA,
cudaGraphicsMapFlagsWriteDiscard);
// Launch rendering loop
while (...) {
	...
	Render();
	...
}
...
}

void Render()
{
	// Map vertex buffer for writing from CUDA
	float4* positions;
	cudaGraphicsMapResources(1, &positionsVB_CUDA, 0);
	size_t num_bytes;
	cudaGraphicsResourceGetMappedPointer((void**)&positions,
	&num_bytes,
	positionsVB_CUDA));
	// Execute kernel
	dim3 dimBlock(16, 16, 1);
	dim3 dimGrid(width / dimBlock.x, height / dimBlock.y, 1);
	createVertices<<<dimGrid, dimBlock>>>(positions, time,
	width, height);
	// Unmap vertex buffer
	cudaGraphicsUnmapResources(1, &positionsVB_CUDA, 0);
	// Draw and present
	...
}
void releaseVB()
{
	cudaGraphicsUnregisterResource(positionsVB_CUDA);
	positionsVB->Release();
}
__global__ void createVertices(float4* positions, float time,
unsigned int width, unsigned int height)
{
	unsigned int x = blockIdx.x * blockDim.x + threadIdx.x;
	unsigned int y = blockIdx.y * blockDim.y + threadIdx.y;
	// Calculate uv coordinates
	float u = x / (float)width;
	float v = y / (float)height;
	u = u * 2.0f - 1.0f;
	v = v * 2.0f - 1.0f;
	// Calculate simple sine wave pattern
	float freq = 4.0f;
	float w = sinf(u * freq + time)
	* cosf(v * freq + time) * 0.5f;
	// Write positions
	positions[y * width + x] =
	make_float4(u, w, v, __int_as_float(0xff00ff00));
}

SLI 交互

在一个多GPU的系统中,所有的可以使用CUDA的GPU都可以通过CUDA 驱动进行访问。但是当系统在SLI模式下,有一些特殊问题需要考虑。

第一:在一个GPU上进行内存的分配时会在其他的GPU上也会消耗内存, 会被消耗内存的GPU是通过Direct3D 或者 OpenGL设备进行SLI 配置过的。因此,有时候内存的分配会失败。

第二:应用程序需要创建多个CUDA 上下文,每个SLI配置中的GOU都需要有一个。尽管这并不是非常必须的需求,但是它能避免在设备间发生不必要的数据迁移。应用程序可以对于Direct3D使用cudaD3D[9|10|11]GetDevice()和OpenGL使用cudaGLGetDevices() 来识别正在执行渲染的CUDA设备的句柄。应用程序会使用这些给定的信息来选择合适的设备来映射Direct3D 或 OpenGL 资源至CUDA 设备空间。

版本与兼容性

在开发CUDA应用程序的时候,需要关注两个版本数字:计算力版本和CUDA 驱动API 版本。前者描述了计算设备的通用说明和特性;后者描述了驱动API 和运行时所支持的特性。

驱动API的版本CUDA_VERSION定义在驱动的头文件中。它允许开发者去对其进行核对。驱动API是向后兼容的,这意味着应用程序、插件、库在之前的驱动上进行编译后仍旧可以在最新的驱动下进行使用。但是驱动API并不会进行前向兼容。
API后向兼容
值得注意的是,一些混合匹配的版本是得到支持的:

  • 因为在一个系统中只能安装一份CUDA 驱动,因此安装的驱动版本必须不低于系统中任何应用程序所需的最低驱动版本。
    -应用程序所使用的所有的插件或者库必须使用相同版本的CUDA 运行时,除非它们静态的连接运行时,此时就会有多个版本的运行时同时存在同一个程序空间中。如果使用NVCC来链接应用程序, 默认会使用静态版本的CUDA 运行时,且所有的CUDA Toolkit 库都会静态的链接CUDA 运行时。
  • 应用程序所使用的所有的插件或者库 所使用的其他库必须使用相同版本的CUDA 运行时, 除非进行静态链接。

计算模式

关于运行在Windows Server 2008及其之后的系统或linux下的Tesla 解决方案, 用户可以通过NVIDIA 的系统管理接口nvidia-smi设置系统的任意设备计算模式。 计算模式主要分为三种:

-_default_计算模式。主机端的多线程可以同时使用同一设备。
-Exclusive-process 计算模式。 只会创建一个CUDA 上下文,设备上的所有进程共享一个上下文。上下文可以是创建该上下文的进程中所需的任意多个线程的当前上下文。
-Prohibited 计算模式。不会在设备上创建CUDA context。

也就是说, 当设备0倍设置成_Prohibited_模式或者_exclusive_process_时,主机端线程在调用运行时API时不显式的调用cudaSetDevice()可能会关联到一个其他的设备上。cudaSetValidDevices()可以用来设定设备的优先级。

第一章导论 1 1.1 从图形处理到通用并行计算 1 1.2 CUDATM:一种通用并行计算架构 3 1.3 一种可扩展的编程模型 3 1.4 文档结构 4 第二章编程模型 7 2.1 内核 7 2.2 线程层次 8 2.3 存储器层次 11 2.4 异构编程 11 2.5 计算能力 11 第三章编程接口 15 3.1 用nvcc编译 15 3.1.1 编译流程 16 3.1.1.1 离线编译 16 3.1.1.2 即时编译 16 3.1.2 二进制兼容性 17 3.1.3 PTX兼容性 17 3.1.4 应用兼容性 18 3.1.5 C/C++兼容性 19 3.1.6 64位兼容性 19 3.2 CUDA C运行时 3.2.1 初始化 20 3.2.2 设备存储器 20 3.2.3 共享存储器 24 3.2.4 分页锁定主机存储器 32 3.2.4.1 可分享存储器(portable memory) 34 3.2.4.2 写结合存储器 34 3.2.4.3 被映射存储器 34 3.2.5 异步并发执行 35 3.2.5.1 主机和设备间异步执行 35 3.2.5.2 数据传输和内核执行重叠 36 3.2.5.3 并发内核执行 36 3.2.5.4 并发数据传输 36 3.2.5.5 流 37 3.2.5.6 事件 41 3.2.5.7 同步调用 42 3.2.6 多设备系统 42 3.2.6.1 枚举设备 42 3.2.6.2 设备指定 42 3.2.6.3 流和事件行为 43 3.2.6.4 p2p存储器访问 44 3.2.6.5 p2p存储器复制 45 3.2.6.6 统一虚拟地址空间 45 3.2.6.7 错误检查 46 3.2.7 调用栈 47 3.2.8 纹理和表面存储器 47 3.2.8.1 纹理存储器 47 3.2.8.2 表面存储器(surface) 60 3.2.8.3 CUDA 数组 65 目录iii 3.2.8.4 读写一致性 66 3.2.9 图形学互操作性 66 3.2.9.1 OpenGL互操作性 67 3.2.9.2 Direct3D互操作性 70 3.2.9.3 SLI(速力)互操作性 82 3.3 版本和兼容性 82 3.4 计算模式 83 3.5 模式切换 84 3.6 Windows上的Tesla计算集群模式 85 第四章硬件实现 87 4.1 SIMT 架构 87 4.2 硬件多线程 88 第五章性能指南 91 5.1 总体性能优化策略 91 5.2 最大化利用率 91 5.2.1 应用层次 91 5.2.2 设备层次 92 5.2.3 多处理器层次 92 5.3 最大化存储器吞吐量 94 5.3.1 主机和设备的数据传输 95 5.3.2 设备存储器访问 96 5.3.2.1 全局存储器 96 5.3.2.2 本地存储器 98 5.3.2.3 共享存储器 99 5.3.2.4 常量存储器 100 5.3.2.5 纹理和表面存储器 100 5.4 最大化指令吞吐量 100 iv CUDA编程指南5.0中文版 5.4.1 算术指令 101 5.4.2 控制流指令 104 5.4.3 同步指令 105 附录A 支持CUDA的GPU 107 附录B C语言扩展 109 B.1 函数类型限定符 109 B.1.1 device 109 B.1.2 global 109 B.1.3 host 109 B.1.4 noinline 和forceinline 110 B.2 变量类型限定符 110 B.2.1 device 111 B.2.2 constant 111 B.2.3 shared 112 B.2.4 restrict 113 B.3 内置变量类型 115 B.3.1 char1、uchar1、char2、uchar2、char3、uchar3、char4、 uchar4、short1、ushort1、short2、ushort2、short3、ushort3、 short4、ushort4、int1、uint1、int2、uint2、int3、uint3、 int4、uint4、long1、ulong1、long2、ulong2、long3、ulong3、 long4、ulong4、float1、float2、float3、float4、double2 115 B.3.2 dim3类型 115 B.4 内置变量 115 B.4.1 gridDim 115 B.4.2 blockIdx 115 B.4.3 blockDim 117 B.4.4 threadIdx 117 B.4.5 warpSize 117 目录v B.5 存储器栅栏函数 117 B.6 同步函数 119 B.7 数学函数 120 B.8 纹理函数 120 B.8.1 纹理对象函数 120 B.8.1.1 tex1Dfetch() 120 B.8.1.2 tex1D() 121 B.8.1.3 tex2D() 121 B.8.1.4 tex3D() 121 B.8.1.5 tex1DLayered() 121 B.8.1.6 tex2DLayered() 122 B.8.1.7 texCubemap() 122 B.8.1.8 texCubemapLayered() 122 B.8.1.9 tex2Dgather() 123 B.8.2 纹理参考函数 123 B.8.2.1 tex1Dfetch() 123 B.8.2.2 tex1D() 124 B.8.2.3 tex2D() 124 B.8.2.4 tex3D() 125 B.8.2.5 tex1DLayered() 125 B.8.2.6 tex2DLayered() 125 B.8.2.7 texCubemap() 125 B.8.2.8 texCubemapLayered() 126 B.8.2.9 tex2Dgather() 126 B.9 表面函数(surface) 126 B.9.1 表面对象函数 127 B.9.1.1 surf1Dread() 127 B.9.1.2 surf1Dwrite() 127 vi CUDA编程指南5.0中文版 B.9.1.3 surf2Dread() 127 B.9.1.4 surf2Dwrite() 128 B.9.1.5 surf3Dread() 128 B.9.1.6 surf3Dwrite() 128 B.9.1.7 surf1DLayeredread() 129 B.9.1.8 surf1DLayeredwrite() 129 B.9.1.9 surf2DLayeredread() 129 B.9.1.10 surf2DLayeredwrite() 130 B.9.1.11 surfCubemapread() 130 B.9.1.12 surfCubemapwrite() 131 B.9.1.13 surfCubemapLayeredread() 131 B.9.1.14 surfCubemapLayeredwrite() 131 B.9.2 表面引用API 132 B.9.2.1 surf1Dread() 132 B.9.2.2 surf1Dwrite() 132 B.9.2.3 surf2Dread() 132 B.9.2.4 surf2Dwrite() 133 B.9.2.5 surf3Dread() 133 B.9.2.6 surf3Dwrite() 133 B.9.2.7 surf1DLayeredread() 134 B.9.2.8 surf1DLayeredwrite() 134 B.9.2.9 surf2DLayeredread() 135 B.9.2.10 surf2DLayeredwrite() 135 B.9.2.11 surfCubemapread() 135 B.9.2.12 surfCubemapwrite() 136 B.9.2.13 surfCubemapLayeredread() 136 B.9.2.14 surfCubemapLayeredwrite() 137 B.10 时间函数 137 目录vii B.11 原子函数 137 B.11.1 数学函数 138 B.11.1.1 atomicAdd() 138 B.11.1.2 atomicSub() 139 B.11.1.3 atomicExch() 139 B.11.1.4 atomicMin() 140 B.11.1.5 atomicMax() 140 B.11.1.6 atomicInc() 140 B.11.1.7 atomicDec() 141 B.11.1.8 atomicCAS() 141 B.11.2 位逻辑函数 141 B.11.2.1 atomicAnd() 141 B.11.2.2 atomicOr() 142 B.11.2.3 atomicXor() 142 B.12 束表决(warp vote)函数 142 B.13 束洗牌函数 143 B.13.1 概览 143 B.13.2 在束内广播一个值 144 B.13.3 计算8个线程的前缀和 145 B.13.4 束内求和 146 B.14 取样计数器函数 146 B.15 断言 147 B.16 格式化输出 148 B.16.1 格式化符号 149 B.16.2 限制 149 B.16.3 相关的主机端API 150 B.16.4 例程 151 B.17 动态全局存储器分配 152 viii CUDA编程指南5.0中文版 B.17.1 堆存储器分配 153 B.17.2 与设备存储器API的互操作 154 B.17.3 例程 154 B.17.3.1 每个线程的分配 154 B.17.3.2 每个线程块的分配 155 B.17.3.3 在内核启动之间持久的分配 156 B.18 执行配置 159 B.19 启动绑定 160 B.20 #pragma unroll 162 B.21 SIMD 视频指令 163 附录C 数学函数 165 C.1 标准函数 165 C.1.1 单精度浮点函数 165 C.1.2 双精度浮点函数 168 C.2 内置函数 171 C.2.1 单精度浮点函数 172 C.2.2 双精度浮点函数 172 附录D C++语言支持 175 D.1 代码例子 175 D.1.1 数据类 175 D.1.2 派生类 176 D.1.3 类模板 177 D.1.4 函数模板 178 D.1.5 函子类 178 D.2 限制 180 D.2.1 预处理符号 180 D.2.2 限定符 180 目录ix D.2.2.1 设备存储器限定符 180 D.2.2.2 Volatile限定符 182 D.2.3 指针 182 D.2.4 运算符 183 D.2.4.1 赋值运算符 183 D.2.4.2 地址运算符 183 D.2.5 函数 183 D.2.5.1 编译器生成的函数 183 D.2.5.2 函数参数 184 D.2.5.3 函数内静态变量 184 D.2.5.4 函数指针 184 D.2.5.5 函数递归 185 D.2.6 类 185 D.2.6.1 数据成员 185 D.2.6.2 函数成员 185 D.2.6.3 虚函数 185 D.2.6.4 虚基类 185 D.2.6.5 Windows相关 185 D.2.7 模板 186 附录E 纹理获取 187 E.1 最近点取样 187 E.2 线性滤波 187 E.3 查找表 189 附录F 计算能力 191 F.1 特性和技术规范 191 F.2 浮点标准 195 F.3 计算能力1.x 198 x CUDA编程指南5.0中文版 F.3.1 架构 198 F.3.2 全局存储器 199 F.3.2.1 计算能力1.0和1.1的设备 199 F.3.2.2 计算能力1.2和1.3的设备 199 F.3.3 共享存储器 201 F.3.3.1 32位步长访问 201 F.3.3.2 32位广播访问 202 F.3.3.3 8位和16位访问 205 F.3.3.4 大于32位访问 205 F.4 计算能力2.x 206 F.4.1 架构 206 F.4.2 全局存储器 208 F.4.3 共享存储器 209 F.4.3.1 32位步长访问 209 F.4.3.2 大于32位访问 210 F.4.4 常量存储器 211 F.5 计算能力3.x 211 F.5.1 架构 211 F.5.2 全局存储器访问 212 F.5.3 共享存储器 213 F.5.3.1 64位模式 213 F.5.3.2 32位模式 213 附录G 驱动API 215 G.1 上下文 218 G.2 模块 219 G.3 内核执行 220 G.4 运行时API和驱动API的互操作性 222 G.5 注意 223
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值