GPU相对CPU,可以更好地并行处理数据,因此可以利用GPU,来进行可以并行的计算,比如图像处理中,若每个像素的处理都独立于其他像素,则就可以使用GPU来加速。
GPGPU的一个比较一般而通用的核心方法就是将待处理的图片或者其他数据转成比如说OpenGL的纹理,然后用OpenGL绘制一个矩形,并使用GLSL、Cg、HLSL等着色语言编写着色程序实现针对每个数据的算法,OpenGL绘制的东西放在用于离屏渲染的帧缓存对象FBO里,每个输入数据对应一组输出,运算完后从FBO中获得结果。
GPGPU的实现方法可以参考如下文章:
http://www.mathematik.uni-dortmund.de/~goeddeke/gpgpu/tutorial.html
翻译版:
http://blog.csdn.net/huawenguang/archive/2007/03/15/1530547.aspx
其中的细节不多说,文章讲得比较详细,也有源码提供。下面我写一下我总结的一些问题
一、硬件支持
要想GPGPU有较好效果,显卡不能太烂……我之前使用一个笔记本的集成显卡,结果综合起来还没有用CPU跑的快。
对于OpenGL的纹理,较烂的显卡只能支持浮点纹理,只有Geforce8以上才能支持整型纹理。虽然似乎显卡处理浮点数速度挺快,但整型纹理的好处是,对于Qt来说,图像像素信息是以整数形式存储的,这样导入导出GPU时不需要转换。
另外,对于N卡和A卡,OpenGL纹理创建的时候的格式是有区别的,虽然也有通用的格式,不过也不是全都覆盖的,还是会有一些型号的显卡无法运行,需要使用更针对性的格式(参考文章中的相关表格)。因此,这里需要识别出当前系统的显卡型号,并以此选择不同的格式。
其他平台我不知道,Windows下,各个硬件的信息都放在了注册表里,因此我们要做的就是找到这个地方,并找到显卡的位置,然后读取相关的信息,并根据信息来设置参数
硬件信息的注册表地址:HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Class
- void SetTextureParams()
- {
- SP_DEVINFO_DATA DeviceInfoData;
- DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
- char buffer[MAX_PATH];
- ZeroMemory(buffer, MAX_PATH);
- HKEY hCurKey;
- std::string strConstKey;
- HKEY hRootKey = HKEY_LOCAL_MACHINE;
- OSVERSIONINFO osvi;
- BOOL bIsWindowsXPorLater;
- ZeroMemory(&osvi, sizeof(OSVERSIONINFO));
- osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
- GetVersionEx(&osvi);
- // 获得操作系统版本
- bIsWindowsXPorLater =
- ( (osvi.dwMajorVersion > 5) ||
- ( (osvi.dwMajorVersion == 5) && (osvi.dwMinorVersion >= 1) ));
- if (bIsWindowsXPorLater)
- strConstKey = "SYSTEM//CurrentControlSet//Control//Class//";
- else
- strConstKey = "SYSTEM//CurrentControlSet//Services//Class//";
- HDEVINFO hDevInfo;
- // Create a HDEVINFO with all present devices.
- hDevInfo = SetupDiGetClassDevs(NULL,
- 0, // Enumerator
- 0,
- DIGCF_PRESENT | DIGCF_ALLCLASSES );
- if (hDevInfo == INVALID_HANDLE_VALUE)
- {
- // Insert error handling here.
- return;
- }
- char szValueData[100];
- DWORD dwVDataSize=100;
- for (DWORD i=0;SetupDiEnumDeviceInfo(hDevInfo,i,&DeviceInfoData);i++)
- {
- SetupDiGetDeviceRegistryPropertyA(hDevInfo, &DeviceInfoData,
- SPDRP_DRIVER, NULL, (PBYTE)buffer, MAX_PATH, NULL);
- std::string strKey = strConstKey;
- strKey.append(buffer);
- char dpath[100]={0}, purepath[100]={0};
- int strlength=strlen(strKey.c_str());
- memcpy(dpath, strKey.c_str(), strlength+1);
- if(dpath[strlength-1]!='}'){
- int temp=strcspn(dpath, "}");
- memcpy(purepath, dpath, temp+1);
- }
- else{
- memcpy(purepath, dpath, strlength+1);
- }
- if (ERROR_SUCCESS != ::RegOpenKeyExA(hRootKey, (LPCSTR)purepath, 0, KEY_READ, &hCurKey))
- {
- // TRACE( "%d ", GetLastError());
- return;
- }
- // Get the driver description
- DWORD dwType = REG_SZ;
- // 根据Class的名称,找到显卡的对应项,即Display
- if (ERROR_SUCCESS == ::RegQueryValueExA(hCurKey, (LPCSTR)"Class", NULL, &dwType, (LPBYTE)szValueData, &dwVDataSize)) {
- dwVDataSize=100;
- if(strcmp((char*)szValueData, "Display")!=0){
- continue;
- }
- if (ERROR_SUCCESS != ::RegOpenKeyExA(hRootKey, (LPCSTR)dpath, 0, KEY_READ, &hCurKey)){
- return;
- }
- // 找到显卡后,我这里是简单地根据厂商,即ProviderName来判断;DriverDesc提供了具体的型号描述,也可发掘其他有用信息
- if (ERROR_SUCCESS == ::RegQueryValueExA(hCurKey, (LPCSTR)"ProviderName", NULL, &dwType, (LPBYTE)szValueData, &dwVDataSize)) {
- dwVDataSize=100;
- if(strcmp((char*)szValueData, "NVIDIA")==0){
- // 设置N卡的参数
- }
- else{
- // 设置其他卡的参数
- }
- return;
- }
- }
- }
- }
参考文献:http://topic.csdn.net/t/20051027/13/4353990.html
二、纹理坐标
OpenGL的纹理有两种tex2D和texRect。前者必须是正方形纹理,纹理坐标范围为[0, 1];后者可以是任意形状,纹理坐标范围为[0.5, length-0.5],而不是[0, length-1]。
三、GLSL
GLSL的输出颜色变量:gl_FragColor
这个变量似乎不能读,只能写。如果先对它赋值,然后再读取它,在很多情况下会编译不通过。我之前写了个简单的程序没啥问题。后来程序里加了个循环语句,就死也编译不了了。因此,gl_FragColor最好在程序的最后赋值,如果需要对输出结果反复修改,先放在一个临时变量里。