用多線程方法實如今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

相關文章
相關標籤/搜索