核弹厂为了卖显卡真是无所不用其极。当然人家号称Geforce就是用来玩游戏的,Quadro才适合更高端的工作。可是如果我只是用来做三维场景绘制,你Quadro比起Geforce也没见得有很大优势,一块K4000顶得上三块GTX760的价格,直接吓得不省人事。幸好CAD&CG实验室有钱,让我借得一块K4000玩玩。
之前就说过插了4块GTX却只有一块在跑的凄惨经历。多番打听之后基本确凿了一件事情:单纯GTX是别想了。那么咱咬咬牙,用Quadro总是可以的。但是,如果没钱,只买得起一块Quadro,还有希望吗?又请出Google,寻遍中外论坛,未见有如我之奇葩要求者。算了,求人不如求己,自己试试吧。
要实现多显卡协同工作,首先需要有一块Quadro显卡,什么型号无所谓。然后,其他显卡,则Quadro、Telsa、Geforce都可以,多少张也都无所谓。
由于个人编程能力有限,窗口编写方面就借用大名鼎鼎的NeHe教程的框架,也就是用Win32 Api编写的窗口框架。当然,还得对框架进行一些修改。为什么不用freeglut之类的库?因为freeglut不让我碰它建好的窗口的RC,而它的代码我不会改……(计算机系大神们改起来是分分钟的事情)
然后,要用到nVidia家的WGL_NV_gpu_affinity特性。要使用这个功能,除了glew之外,还需要wglew:
#include "GL/glew.h"
#include "GL/wglew.h"
首先是注册窗口类。NeHe教程里注册窗口类和创建窗口是写在一起的。但是我们要创建多个同一个类的窗口,却不能把同样的类注册多次,所以先把RegisterClass的部分单独抽出来,只注册一次。
然后就是CreateGLWindow函数。在这里,我加了一个传入参数:RCNum,用于标识窗口绑定到哪块显卡上。在wglMakeCurrent(hDC, hRC)一句出现后,初始化GL参数的InitGL()出现前,加入:
glewInit(); //glew初始化必须放在NV枚举之前/创建一个Affinity RC并替代原有的RC///HGPUNV hGPU = NULL;wglEnumGpusNV(RCnum, &hGPU); //枚举第RCnum号GPUHGPUNV GpuMask[2] = { hGPU, NULL }; //创建GPUMaskHDC hDCNV = NULL;hDCNV = wglCreateAffinityDCNV(GpuMask); //得到Affinity DCint pf = ChoosePixelFormat(hDCNV, &pfd); //根据Affinity DC读取像素格式SetPixelFormat(hDCNV, pf, &pfd);DescribePixelFormat(hDCNV, pf, sizeof(PIXELFORMATDESCRIPTOR),&pfd);hRC = wglCreateContext(hDCNV); //根据Affinity DC创建Affinity RC,并用它代替掉原来的RCprintf("hGPU: 0x%p hDCNV: 0x%p\n", hGPU, hDCNV); //显示一下地址信息。不为null就算是成功了。wglMakeCurrent(hDC,hRC); //记得重新绑定一下窗口DC
GpuMask的作用就是把需要工作的显卡标识出来,则之后的Affinity DC和Affinity RC都会跟GpuMask指定的显卡绑定。还有一点就是glewInit()函数必须在操作多显卡的代码之前,否则会出错。具体的工作原理倒是没搞清楚。
然后就可以用CreateGLWindow函数来创建一个用于OpenGL绘制的窗口了。此处有一点也是很重要的,就是创建出来的窗口的位置,必须跟当前窗口所在的显示器对应的显卡一致。也就是说,假如RCNum参数填了2,就应该把窗口创建在2号显卡(从0开始算)所连接的显示器里,并且窗口不要越界。假如RCNum传了2,而把窗口开到了1号显卡连接的显示器上,则绘制时工作的是2号显卡,但是绘制完成的内容则要通过内存转到1号显卡里给显示器显示,造成显卡之间的数据交换,极其影响帧数。
实际上如果只是这么建几个窗口的话,最后会发现显卡的负荷是间歇性的。原因是单线程的程序只能等待一块显卡SwapBuffer结束之后,才能进行下一块显卡的Swapbuffer,导致实际上同时还是只有一块显卡工作。因此程序还需要应用多线程技术,创建对应数量的线程,在每个线程内部创建窗口。线程之间互相不受影响,也不需要调用wglMakeCurrent(hDC, hRC)来切换窗口。如果需要窗口之间的同步,还需要使用Event来控制线程之间的进度(WGL_NV_gpu_affinity的说明里还提到了叫SwapGroup的特性,可以针对这种情况自动管理同步,但是我一直没有成功,还请指教)。这就涉及多线程的管理,不在本文细说。
如此就可以实现多显卡协同工作。如果几个窗口需要绘制同样的场景,记得每个窗口都要把场景所需要的顶点、纹理等传入GPU的数据初始化一遍。还是那句话,避免显卡之间的数据交换。
参考:
https://www.opengl.org/registry/specs/NV/gpu_affinity.txt
NeHe的OpenGL教程