Qt OpenGL 的一些使用問題


這一篇,算是自己點單紀錄一下,目前使用 Qt 5 來開發 OpenGL 程式時,碰到的一些問題吧…

基本上,Heresy 對於整個 Qt OpenGL 的使用架構不算非常熟,也是邊寫邊摸,所以本文也不盡然正確,很大的機會應該有不少地方是使用錯誤…不過,基本上就算記錄下來,給自己以後參考用了。

首先,Qt 5 基本上應該算是已經深度整合 OpenGL/OpenGL ES 了~預設下載預先編譯好的版本,都有支援 OpenGL、而不用另外自行編譯了。

而他的主要文件,應該可以從《OpenGL and OpenGL ES Integration》這邊開始看。

在使用上,一般寫 Qt5 的 OpenGL 程式,是會去使用 QOpenGLWidgetQOpenGLWindow 來做基礎、進行開發。

而實際上,這兩個類別都是 Qt 預先包好的、讓使用者可以省略很多管理部分程式的類別;回到底最基本的,應該是 QOpenGLContext 和 QOpenGLFunctions_XXX 這些底層的類別。

也由於這些類別的存在,所以如果要使用 Qt OpenGL 作為繪製的核心的話,在開發物件導向的程式時,OpenGL 相關的模組,似乎無可避免地、會受到嚴重的污染,必須保有一堆 Qt OpenGL 的相關物件…


OpenGL Context

其中,QOpenGLContext 就是代表 OpenGL Context 這個資源、狀態管理的類別(官方說明);如果是以前使用 glut + glew 作為 OpenGL 開發環境的話,對於這東西應該相當地陌生,因為那時候基本上都是以單一 context 作為環境,沒辦法管理、切換。

對於要使用 Qt 來開發 OpenGL 的人來說,context 的管理應該會是一個很大的問題,老實說,個人也沒有很清楚 context 的切換狀況,以及一些使用的設定。

再加上 Qt 本身又有多執行序,所以如果沒處理好,很有可能已經被切換到其他 context 而不自覺;這時候去呼叫 OpenGL 的相關函式,就可能會發生各種靈異現象了。

而要確保繪製的正確,就很有可能需要在很多地方,加上 makeCurrent() 這行命令才行。這也導致了當要把 3D 物體拆成各個類別,來進行模組化時,可能會需要保存一份 QOpenGLContext、以及目標的 surface 指標才行…

也就是說,如果自行訂一個 3D 的 OpenGL 物件,想讓他能在 Qt OpenGL 的環境繪製的話,在繪製的程式的部分,必然要保有大量的 Qt OpenGL 專用的物件、函式,讓整個類別被 Qt 綁死,沒辦法簡單地切換回單純的 OpenGL(glew)環境。


QOpenGLFunctions_XXX

而 QOpenGLFunctions_XXX 這東西,某方面來說也是一件麻煩事。如果使用傳統 OpenGL 與 glew(官網)的話,所有的 OpenGL 函式都是全域的,到處都可以呼叫。

但是在 Qt OpenGL 的物件導向架構下,所有的 OpenGL 函式,都被根據不同的版本,包成 QOpenGLFunctions_XXX 這樣的類別。
(不過最基本 Windows SDK 內建的 OpenGL 函式倒是還是可以直接當作全域函式使用,有限制的主要是需要透過 glew 擴充的部分。)

像如果是要使用 OpenGL 4.4 Core Profile 的函式的話,就是要去呼叫 QOpenGLFunctions_4_4_Core 的成員函式來做操作。

而也由於所有的 OpenGL 函式都是該類別物件的成員函式,所以當要在一個類別中、呼叫 OpenGL 的函式的時候,這個物件就勢必要有對應的 QOpenGLFunctions_XXX 物件了。

不過,由於這些函式物件也可以透過 QOpenGLContext::versionFunctions<>() 這個函式來取得,而且也有一定程度的向下相容性(這算是 OpenGL 本來的定義),所以只要物件本身有紀錄 QOpenGLContext 的物件,那就還不算太麻煩。
(話說,到底可不可以直接建立一個新的 QOpenGLFunctions_XXX 來用呢?)

但是在操作上,由於所有函式都從全域函式變成成員函式了,所以對於已經寫好的既有的物件來說,要轉移到 Qt OpenGL 環境也還是會增加一些麻煩,而且同樣無法切換回單純的 glew 環境了。

而有看過的一種作法,是讓自己的類別去繼承所需要的 OpenGL 版本的函式物件(例如 QOpenGLFunctions_4_4_Core),這樣在呼叫 OpenGL 函式的部分,就可以「好像」和使用 glew 時一樣了,某方面來說,應該也是程式碼改動最少的方案了。

下面就算一個簡單的例子:

class CTest : public QOpenGLFunctions_4_1_Compatibility
{
public:
	void initial()
	{
		GLuint m_glVertArray;
		glGenVertexArrays(1, &m_glVertArray);
	}
};

但是,因為 Qt 是使採用不同的類別,來區隔不同版本的 OpenGL 函式,所以如果繼承不同版本、要修改的話…這部分可能會因為版本不同而增加一些困擾。


與 glew 混用的問題

如果本來已經有一些使用 glew 開發的 OpenGL 物件的話,其實某方面來說,是可以和 Qt OpenGL 混用的;至少,Heresy 這邊的經驗是可以的。

但是,由於 glew 的 context 管理…基本上就只有一個 glewInit() 是用來建立 OpenGL context 的,也沒有其他的管理功能,所以要怎麼和 Qt OpenGL 的 context 整合,就成為一個很大的問題了。

而以 Heresy 自己的經驗來說,只要在執行 glewInit() 前,先呼叫 Qt OpenGL 的 makeCurrent(),似乎就可以讓他們共用同一份 context 了?至於之後的操作的,看來也是需要大量的 makeCurrent(),才能確保操作的 OpenGL 資源是正確的了。

這樣的限制,也代表了本來為了物件導向的設計而拆開到不同類別的程式,到了要執行的時候,都還得先回到一個可以執行 makeCurrent() 操作的物件,才有辦法執行;某方面來說,在架構上會變得相當複雜。

例如,想透過一個按鈕、去更新一個 3D 物件(classA)的貼圖的話,那這個按鈕的 slot 函式不能直接直接去呼叫 classA 的 updateTexture() 函式,而是必須先想辦法先呼叫過 makeCurrent()、再去呼叫這個函式才行…

此外,如果程式碼中有一個類別是透過繼承 QOpenGLFunctions_XXX 的形式來呼叫 OpenGL 的函式的話,那如果在這個原始碼檔案裏面又去引入 glew.h 的話,就會導致 Qt OpenGL 和 glew 的函式庫衝突,導致這個檔案雖然可以編譯、但是無法連結的狀況…

所以,如果有要同時使用 Qt OpenGL 和 glew 的話,比較好的做法,可能是得把原始碼的部分完全拆開來成不同的檔案了。


整體來說,Heresy 自己目前使用 Qt 來開發 OpenGL 的程式,主要問題大概可以是下面幾點:

  • 對於 OpenGL Context 概念極度不熟系
  • 使用 Qt OpenGL 架構後,整個原始碼的大範圍汙染
  • Qt OpenGL 與 glew 混用的各種問題

在 Heresy 來看,如果想要寫一個自己的 OpenGL 3D 框架/引擎,並使用 Qt 來做圖形介面的話…某方面來說,大概就得寫成 Qt 專用的了吧… orz

不過,Qt 的 OpenGL 也有一些方便的地方,像是 QOpenGLDebugLogger 就是一個感覺還滿實用的 OpenGL debug 輔助工具~他可以把訊息都記錄下來,或是在有錯誤時,馬上觸發事件來做處理,而不需要到處加 glGetError() 來找錯,算是相當好用的。

而由於現在要有比較好看的跨平台圖形介面,看來 Qt 還是一個比較好的選擇,所以大概只能看看到底要怎麼去適應它了吧…

對「Qt OpenGL 的一些使用問題」的想法

  1. 謝謝您的建議~~
    我的狀況是這樣的:我封裝了一個繼承自QOpenGLWidget 的 class, GLView。 在 main window 放置一個,另一個在 dialog window(有需要才會開),平常會一直餵資料給 main window 的 GLView,並且會用 glCallList 的方式來增進效能。因為這兩個 window 用的 GLView 都是相同的程式碼,如果沒有把 glCallList 的位址錯開,就會發生渲染異常的問題。即使將位址錯開了,顯示也正常了,但只要開第二個 dialog 出來,main window 不斷更新 GLView 的動作就會被中斷,直到關閉 dialog 後才會繼續。我想這應該也是互相干擾造成的。
    後來我發現如果將 dialog->exec() 改成 dialog->show() 這樣就可以解決更新中斷的問題(暫時解決了~~)

    qt5.10 中有個 qopenglwidget 的範例,可以同時使用很多個 QOpenGLWidget ,不過這個要搭配 modern opengl 的語法才行,從我現在的程式(classic opengl) 要轉過去有難度,要重新學 modern opengl…

    請問如果要在 modern opengl 上顯示文字,並且要能跟著視圖旋轉的話,您有建議的方式嗎?我本來是用 freeglut 的 glutStrokeCharacter 來做,但在 modern opengl 上是不行用的。
    謝謝您這麼迅速的回應與建議~~~

    • 1. 繪製內容的問題,個人還是覺得應該是你沒有做好 context switch 的問題。
      在和 OpenGL 有關的部分,很多地方都必須透過 makeCurrent 來做 context switch,否則有可能會去更新到另一個 context 的內容。

      2. QDialog::exec() 的設計本來應該就是那樣

      3. Qt 沒有提供比較方便的 3D 文字繪製方式。如果只是要簡單的 2D 文字的話,其實直接把文字先畫道圖上,當成 texture 來用會比較簡單。
      不然就是要去找其他現成的函式庫來用。

      • 好的,謝謝您的建議,我會就您提的部份再研究測試
        感謝您的指導,謝謝您~~

  2. 您好:
    我有一個畫面要同時顯示兩個 QOpenGLWidget (搭配 opengl 2.x 的語法),顯示的內容是不一樣的物體。於是我放了兩個 QOpenGLWidget 在畫面上,但是我發現顯示的內容會錯亂,我要渲染至窗口1的,會跑到窗口2。這個似乎跟您提到的 OpenGL Context 有關,請問您有遇過類似的問題嗎?有建議的解決方式嗎?
    謝謝您

發表留言

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料