用多线程方法实如今MFC/WIN32中调用OpenGL函数并建立OpenGL窗口

OpenGL相关的工具库中的OpenGL程序每每都是在C函数main中初始化和建立的,使用控制台来完成显示和控制颇为不便。若是可以在MFC中OpenGL函数并建立OpenGL窗口,而且能够将控制参数传入给OpenGL则能够获得很好的交互性能。本身查找不少文献资料,貌似都是说要在MFC中显示OpenGL都是经过微软的wgl扩展来完成,可是wgl很早就中止更新了的而且本身写的wgl运行框架尽管有些时候可使用但在本身的电脑上却老是发现有运行有内存泄漏的问题而且加载opengl程序也很是慢,也许是显卡驱动的问题?但本身装的英伟达的GTX560显卡而且是最新的显卡驱动,也仍是存在这个问题,看来应该是本身写的和参考的wgl框架有bug。原本是想用glut的却发现也是好久就中止更新了,只有freeglut不错,今年初还出了更新的。因而决定使用glew+freeglut来实现这个想法。 html

不过有一个问题就是glut的初始化函数每每都是写的glutInit(&argc,argv);其中argcargv两个参数是从控制台下的C函数的main(int argc,char *argv[])中传过来的,argc记录的是命令行中输入参数的数目,argv是一个拥有argc个元素的字符串数组,每一个元素保存一个命令行中输入的参数。可是在MFC中默认是不会生成控制台窗口的,而实际上若是蹦出个控制台来控制opengl也不太好(这是能够实现的,能够参考《GUI程序也能使用控制台窗口》等文章http://www.cppblog.com/wish/articles/23642.html)偶然看到pfan论坛的eastcowboy的一个帖子《OpenGL入门学习——写给想用计算机画图的朋友》中提到能够在WinMain中初始化而且建立OpenGL窗口,经过自定义argcargv参数的值达到了欺骗glutInit完成初始化而且不依赖与命令行/控制台窗口。我的以为既然能够自定义初始化参数,那么就实际上能够在MFC中的任何地方初始化和建立OpenGL窗口了。可是为了避免影响MFC正常工做、完成与用户交互即UI的功能,能够经过多线程来完成,便可以将OpenGL的运行封装在一个工做线程的run函数中(这实际上真是有趣:这个名义上的工做线程倒是能够建立窗口显示的)。 windows

这里给出一个MFC对话框实现的例子: 数组

第一步就是建立一个MFC对话框应用程序,我这里取名MFCwithOpenGLWindow。第二步添加一个启动OpenGL按钮IDC_STARTOPENGL及其对应的消息响应函数OnStartOpengl(). 安全

第三步就是添加相应的函数。在对话框实现MFCwithOpenGLWindowDlg.cpp文件顶部添加两个全局函数声明(由于是子线程函数,必须是全局的)
多线程

int OpenGLThread(LPVOID lpv);//线程run函数
void DisplayGLView(void);//被线程函数调用以在OpenGL窗口中绘图显示的函数

接下来在对话框实现MFCwithOpenGLWindowDlg.cpp文件中给出线程函数的实现(这里借鉴了《OpenGL入门学习——写给想用计算机画图的朋友》帖子中的程序,只不过添加了显示绘图的颜色为红色) 框架

void DisplayGLView()//被线程函数调用以在OpenGL窗口中绘图显示的函数
{
      glClear(GL_COLOR_BUFFER_BIT);//清空颜色缓冲
      glColor3f(1.0,0.0,0.0);//设置绘图为红色
      glRectf(-0.5f,-0.5f,0.5f,0.5f);//绘制一个正方形
      glFlush();//刷新显示缓冲完成显示
}
int OpenGLThread(LPVOID lpv)//线程run函数
{
      //得到线程建立时对话框传入的参数,能够经过这里传递给opengl一些控制参数
      CMFCwithOpenGLWindowDlg* pTaskMain = (CMFCwithOpenGLWindowDlg *) lpv;
      int argc=1;
      char *argv[]={"OpenGL Thread "};
      glutInit(&argc,argv);//初始化glut
      glutInitDisplayMode(GLUT_RGB|GLUT_SINGLE);//设置显示模式
      glutInitWindowPosition(100,100);//设置opengl窗口显示位置
      glutInitWindowSize(400,400);//设置opengl窗口大小
      glutCreateWindow("OpenGL Thread Window");//设置opengl窗口标题
      glutDisplayFunc(&DisplayGLView);//调用显示回调函数绘图
      //调用freeglut中的函数设置opengl窗口被关闭和结束返回时能够继续执行glutMainLoop后面的部分
      glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_GLUTMAINLOOP_RETURNS);
      glutMainLoop();//进入glut循环
      //AfxMessageBox("it end");
      AfxEndThread(0);//结束线程返回,以避免出现意外结束
      return 1;//结束时返回
}



    红色部分很是重要,首先经过调用glutSetOption设置了opengl窗口被关闭时或者其余结束响应事件发生以后能够继续执行glutMainLoop死循环后面的语句,一方面能够正确地调用AfxEndThread函数结束子线程而不致于opengl对话框关闭时之间调用exit(0)MFC主对话框也给关闭了即结束了主线程,还能够完成一些清理工做,好比相应的内存释放等以避免形成内存泄漏事件。该函数须要freeglut的支持,在glut中是没有这个函数的,由此能够看到freeglutglut的完善和补充,,使得程序更为可靠和安全了。 ssh

最后就是在启动OpenGL按钮对应的消息响应函数OnStartOpengl()中建立opengl线程,将opengl窗口显示出来,这里比较简单只有一句: 函数

      CWinThread *TEST1=AfxBeginThread((AFX_THREADPROC)OpenGLThread,this);//建立opengl线程 工具

如此,编译程序并运行,点击启动OpenGL按钮就能够获得一个绘制了一个红色正方形的opengl窗口,以下图所示 oop

为了使程序编译经过,须要下载和安装glewfreeglut库,而且在MFCwithOpenGLWindowDlg.cpp文件中须要引用对应的头文件,在VC6.0或者VS平台中设置工程的头文件,库函数以及可执行dll文件的目录,这里为了方便使用已将相关的库的文件放置在了工程目录下,仅须要在MFCwithOpenGLWindowDlg.cpp文件中设置以下:

#include "./include/GL/glew.h"
#include "./include/GL/freeglut.h"
#pragma comment(lib,"glew32.lib")
#pragma comment(lib,"freeglut.lib")

    为了验证能够经过MFCOpenGL窗口绘图进行控制,这里进一步实现了一个旋转前面绘制出来的红色正方形。

首先在前面的对话框中再添加一个按钮IDC_ROTATEGLVIEW用于旋转GL绘图。

而后添加该按钮的消息响应函数OnRotateGLview(),实现以下:

   

csfortangle.Lock();       //临界区加锁
      rtangle=rtangle+5.0f;        //增长旋转角度
      if (rtangle>180.0f)       //处理旋转角度范围
      {
             rtangle=0;
      }
      csfortangle.Unlock();    //临界区开锁

其中csfortangle是rtangle角度对应的临界区对象用于实现线程之间对rtangle变量的访问控制使得每时刻仅有一个线程对其操做,在对话框实现文件的顶部定义的:

GLfloat rtangle=0;                        //旋转角度

CCriticalSection csfortangle;  //控制旋转角度线程之间访问同步的临界区对象

同时要使用临界区对象,必须加上对应的头文件:

#include "Afxmt.h"            //引入MFC中的线程/进程同步类,这里使用临界区对象。

为了实现真正的旋转绘制的图形,在显示回调函数DisplayGLView中修改以下,即增长红色部分:

      glClear(GL_COLOR_BUFFER_BIT);//清空颜色缓冲
      glLoadIdentity();                             //复位当前模型视角矩阵
      csfortangle.Lock();                                //临界区加锁
      glRotatef(rtangle,0.0f,0.0f,1.0f);   //绕Z轴旋转绘制的图形
      csfortangle.Unlock();                     //临界区开锁
      glColor3f(1.0,0.0,0.0);//设置绘图为红色
      glRectf(-0.5f,-0.5f,0.5f,0.5f);//绘制一个正方形
      glFlush();//刷新显示缓冲完成显示最后编译运行。



  
  测试时建立 opengl 窗口后首先点击对话框中的“旋转 GL 视图”按钮增长旋转的角度,而后鼠标切换到 opengl 窗口点击一下(为了产生一个点击事件使得 glut 可以调用显示回调函数 DisplayGLView 旋转图形并刷新显示,也能够经过定时等事件来达到相应的目的)就能够看到旋转后的图形。

若是不想手动去切换,能够经过glut中的定时事件来触发显示函数回调。

在前面程序基础上实现以下:

      首先添加一个glut定时timerEvent回调函数,至关于MFC中的ontimer定时器响应函数:

void timerEvent(int value)//GLUT定时回调函数
{
   glutPostRedisplay();//发送从新显示消息,刷新显示
   glutTimerFunc(REFRESH_DELAY, timerEvent, 0);//重置定时器
}



而后修改OpenGLThread函数以下:

int OpenGLThread(LPVOID lpv)//线程run函数

{

      //得到线程建立时对话框传入的参数,能够经过这里传递给opengl一些控制参数

      CMFCwithOpenGLWindowDlg* pTaskMain = (CMFCwithOpenGLWindowDlg *) lpv;

 

      int argc=1;

      char *argv[]={"OpenGL Thread "};

      glutInit(&argc,argv);//初始化glut

      glutInitDisplayMode(GLUT_RGB|GLUT_SINGLE);//设置显示模式

      glutInitWindowPosition(100,100);//设置opengl窗口显示位置

      glutInitWindowSize(400,400);//设置opengl窗口大小

      glutCreateWindow("OpenGL Thread Window");//设置opengl窗口标题

      glutDisplayFunc(&DisplayGLView);//注册显示回调函数绘图

      glutTimerFunc(REFRESH_DELAY, timerEvent, 0);//注册定时回调函数

      //调用freeglut中的函数设置opengl窗口被关闭和结束返回时能够继续执行glutMainLoop后面的部分

      glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_GLUTMAINLOOP_RETURNS);

      glutMainLoop();//进入glut循环,处理响应事件

      //AfxMessageBox("it end");

      AfxEndThread(0);//结束线程返回,以避免出现意外结束
      return 1;//结束时返回
}



其中REFRESH_DELAY为文件顶部的定时时间ms数值宏定义,这里取200ms,从新编译并运行程序就能够实如今MFC中点击“旋转GL视图”便可连续地旋转绘制的正方形了。

为了验证glut窗口中也可以经过按钮实现正方形旋转的控制,能够进一步添加一个全局按钮控制回调函数:

void keyboard(unsigned char key, int /*x*/, int /*y*/)//GLUT按键回调函数
{
             switch(key)
             {
                    case '+'://+号实现旋转的逆时针角度增长
                           {
                                  csfortangle.Lock();        //临界区加锁
                                  rtangle=rtangle+5.0f;          //增长旋转角度
                                  if (rtangle>180.0f)        //处理旋转角度范围
                                  {
                                         rtangle=0;
                                  }
                                  csfortangle.Unlock();     //临界区开锁
                           }
                           break;
                    case '-'://-号实现旋转的顺时针角度增长
                           {
                                  csfortangle.Lock();        //临界区加锁
                                  rtangle=rtangle-5.0f;    //增长旋转角度
                                  if (rtangle<-180.0f)        //处理旋转角度范围
                                  {
                                         rtangle=0;
                                  }
                                  csfortangle.Unlock();     //临界区开锁
                           }
                           break;
                    default:
                           break;
             }
}



而且在建立线程时将函数注册到glut中:

      glutKeyboardFunc(keyboard);//注册键盘按钮控制回调函数

编译以后运行就能够看到既能够经过glut的窗口中经过键盘按钮控制正方形旋转,也能够经过MFC对话框按钮实现。

 

补充:

若是要经过主线程如UI线程来结束本身建立的opengl线程,则能够在本身定义的显示函数或者定时器处理函数中加上一个条件变量如Boolean型的变量,当该变量条件为真时就调用freeglut的退出循环函数glutLeaveMainLoop(),它将直接跳出glutMainLoop,接着执行AfxEndThread()结束线程返回,从而实现提早退出和结束线程。

其实经过阅读freeglut的源程序能够发现windows平台上它实际上对窗口的绘制对事件的响应都是对windows API的封装,尤为对于窗口和图形的绘制也是对微软wgl的封装,如fgSetWindow函数中封装了wglMakeCurrent函数和ReleaseDC函数,fgWindowProc函数则更是专门封装了对应win32窗口处理的函数,其中封装了wglCreateContextwglGetCurrentContext等等函数。

 

最后给出本文中的示例源代码下载地址:

http://download.csdn.net/detail/menglongbor/4268748

参考文献:

GUI程序也能使用控制台窗口,http://www.cppblog.com/wish/articles/23642.html

OpenGL入门学习——写给想用计算机画图的朋友,http://bbs.pfan.cn/post-184355.html

OpenGL内存泄漏之主循环函数glutMainLoop()

http://blog.csdn.net/ronggang175/article/details/6068854

坑爹的sshglutMainLoop

http://hi.baidu.com/tyxxybd/blog/item/64f6040f698402306159f337.html

相关文章
相关标签/搜索