這篇基本上是之前《使用 QGraphicsScene 繪製 widget、產生 OpenGL Texture》一文的後續。
在當時,Heresy 有提過,如果真的用 QOpenGLPaintDevice 來給 QGraphicsScene 繪製到 Frame Buffer Object 上的話,會造成文字的破碎、無法識別的狀況;而 Heresy 當時的解法,是暫時放棄 OpenGL 繪圖,而讓 QGraphicsScene 先畫到 QImage 上(raster)、然後再把它傳到顯示當作為 OpenGL 的 texture。
理論上,這個方法的確是可行的,但是實際上,在後來使用的時候,卻發現:
當介面稍微多一些東西的時候,用 QGraphicsScene 把圖形介面畫在 QImage 上要花超過 150ms… orz
如果是一般視窗環境,或許還算勉強可以接受,但是在 VR 環境下,這樣連 10FPS 都到不了的速度,是完全無法被當作「堪用」的…
而且,這個時間還不包含從系統記憶體送到顯示卡記憶體的時間呢…
後來,在參考《Qt Graphics and Performance – An Overview》一文後,試著把 QImage 的格式從 RGBA8888 改成 RGB32,發先可以把快 150+ms 壓到 40ms 以下,算是快了相當地多~
(比較奇怪是,這樣在設定 OpenGL Texture 的時候,格式得改成 GL_BGRA…)
但是儘管如此,不到 30FPS 的更新率,基本上還是不夠快啊…不過,這或許也是沒有使用硬體加速的極限了?
而如果改用 QOpenGLPaintDevice、用 OpenGL 畫在 frame buffer object 上的話,文字會爛掉,但是時間可以壓到 20ms、甚至大部分是在 15ms 以下。而且,由於他直接是畫在顯示卡記憶體上了,所以也不需要後續的傳輸時間。
不過,考量到 VR 環境所需的更新綠,大概還是得看看怎麼解決使用 OpenGL 來繪製 UI 時,文字會產生的問題了…
本來有在考慮,不知道是不是因為 OpenGL stack 是和 3D 場景繪製共用,所以導致狀態混亂?所以也試著加上了 QPainter::beginNativePainting()(參考),但是似乎沒有用。
最後,則是發現:
如果另外建立一個 OpenGL context 專門拿來畫圖形介面,似乎就可以解決大部分問題!
但是…這樣下來,就變成一個程式會有兩個 OpenGL Context,需要使用 context sharing 來做管理,整個架構會變得相對複雜啊…
基本上,就是得另外建立一個 QOpenGLContext,並在呼叫 create() 之前,先透過他的提供的 setShareContext() 的函式,可以設定要和繪製 3D 場景的 context 共用資源;然後,則是在這個 context 中,建立需要的 OpenGL 物件(FBO、texture、vertex buffer object…)。
而由於這樣的架構下,會有多個 OpenGL conext,所以就得到處放 makeCurrent() 了…
另外,文字的問題,似乎也不能算是完全解決。
在使用獨立 context 後,雖然大部分的圖形介面、文字都可以正常顯示了,但是卻還是有部分的文字會出問題;而這次的問題,則是整個變黑框…
像右圖中,可以看到大部分的文字都可以正確顯示(不清楚是解析度問題);但是黑色方框的部分,則是 Tab 的文字,這邊就因為不明原因,無法正確顯示了。而錯誤的方式,也和之前使用同一份 context 時明顯不同。
後來又研究了好一陣子,最後發現,感覺這種文字的錯誤,似乎是因為 Heresy 有把整個圖形介面放大造成的?
在經過測試後,發現似乎是把介面放大兩倍以內,都不會有問題;但是如果到三倍以上,就很容易出現這樣的問題?像上面的錯誤狀況是放大四倍時造成的,而如果把它改成只放大兩倍,就會是正確的(下圖)…
所以感覺上,這個問題似乎變成是 Qt 繪製圖形介面的程式,在需要處理放大時,文字的部分有問題? orz
不過,儘管如此已經解決了大部分的問題,其實還是有小部份的問題存在;像是上面的圖片如果仔細看的話,就會發現,「Translation」背後的 group 的淺灰色框線,其實沒有正確地斷開,而是連續的…
只是基本上,這個相對起來真的只是小問題就是了。
總之,現在算是勉強做出一個堪用的、使用 Qt 做出 VR 環境的圖形介面的框架了;由於影像也變小了,所以現在繪製的時間大致上是可以壓在 10ms 左右,速度也算讓人滿意了。
雖然看來應該還是有些小問題,但是就等真的碰到嚴重的狀況再說吧。
之後可能再考慮整理一下程式碼,把片段的程式碼放出來吧。
附註:
-
在稍微研究一下後,也發現一個可能是效能瓶頸的問題,那就是是用 QGraphicsScene 畫 widget 的時候,他的 changed 事件傳過來的更新區域根本是全部、沒辦法局部更新啊! orz
-
使用 OpenGL 繪製的話,由於座標系統定義不同,所以上下會是顛倒的。
這部分可以透過 QOpenGLPaintDevice::setPaintFlipped() 這個函式來做調整。 -
理論上這邊也可以不需要 QGraphicsScene、直接用 QWidget 的 render() 這個函式(參考)來畫;不過,由於 QWidget 本身沒有 signal 可以用來觸發畫面更新的事件,所以真的要拿來處理畫面更新的重繪,必須要去繼承 QWidget、重新實作 paintEvent(),整個會很麻煩…
拿 QGraphicsScene 來做變動偵測,應該還是比較簡單的。 -
這樣畫的時候,QWidget 不會去處理縮放的問題,所以要透過 QPainter 或 QPaintDevice 的函式來做縮放的設定。