GItHub地址
簡介
本倉庫實現了一個在QML框架中,顯示QImage數據的QML控件,取名為JQImageItem
本控件針對的場合是需要顯示并且頻繁修改QImage的場景,例如視頻顯示。
提供了2個實現版本,一個是基于QQuickFramebufferObject(FBO)的JQImageItem,一個是基于QQuickPaintedItem的JQImageItem2。
在絕大多數場合下,FBO的實現性能要明顯好于其他版本,因此如果你需要使用本倉庫的控件,請使用這個版本。
代碼基于純Qt+OpenGL實現,開發時測試了2個Qt版本,分別是5.15.2和6.5.2,理論上其他Qt版本也能正常使用。
如何使用
在QML中,直接實例化控件,然后需要把控件的指針傳回到C++,然后在C++端進行數據更新。實例化代碼如下:
JQImageItem {id: imageItemanchors.fill: parent
}
如果要調整圖片顯示的大小,位置等屬性,那么直接調整這個Item即可。
在C++端拿到指針后,調用setImage接口,可以把QImage數據設置到控件內,然后控件會顯示這個QImage圖片數據,代碼如下:
imageItem->setImage( QImage( "C:/your/image/xxx.png" ) );
這個setImage接口是線程安全的,因此你可以在任何線程調用這個接口
為什么快?
要理解為什么本倉庫的實現塊,我們需要先理解3個常見的QImage顯示方法
一般來說,在QML中顯示一個QImage,有3個方法實現,他們分別如下:
-
QQuickImageProvider
這是目前網上最多的實現方法,大致原理是把QImage圖片數據通過圖片顯示的邏輯,傳給QML。
這個方法理論上是最慢的,因為這個邏輯下,需要把一個圖片,完整的經過Qt的圖片資源處理邏輯,才能顯示到QML中。
在Qt的設計初衷上,這個類根本不是給這個場景用的,因此大多數人的實現,都需要在QML端給url賦值隨機數,或者一個自增變量,來避免URL相同而引起的圖片不刷新問題。
并且,這個邏輯下,會嚴重破壞QML的圖片緩存邏輯(如果你不設置cache為false的話)。
因此在頻繁修改圖片的需求下,這個實現方式,我是完全不推薦使用的。
-
QQuickPaintedItem
這個類的實現,更加的貼近我們的使用場景,也不存在url這些對實現需求完全無用的中間參數變量等。
這個類大致的流程是在C++端,通過QPainter繪制一個圖片到QImage中,然后Qt會把這個QImage圖片顯示到QML上
這個類的速度其實已經很快了,但是他還是存在一個額外的開銷,就是QPainter的步驟。即使在分辨率,格式完全相同的情況下,QPainter繪制一個圖片到QQuickPaintedItem內部的緩沖區,會存在至少一次的拷貝開銷。
不過這個類本身也有FBO的實現方式,可以說如果你的需求是QPainter繪制圖表,或者其他內容的話,這就是最合適的實現。
-
QQuickFramebufferObject(FBO)
分析我們的需求,目前已經有了一個QImage,QImage有完整封裝好的圖片數據,我們希望他可以直接傳給OpenGL實現,沒有其他的開銷。那么此時,直接把這個圖片數據,上傳到OpenGL的紋理對象(QOpenGLTexture),然后直接繪制,那么這就是最快的方法。
基于FBO顯示圖片的話,會比較復雜,大致流程是:
-
創建自己的shader,只保留圖片渲染需要的部分
-
創建和圖片渲染相關的VBO,VAO
-
把圖片根據需求,更新到紋理中
-
在OpenGL的繪制回調中,調用所有的相關對象,完成圖片繪制
也就是說基于FBO的實現,雖然邏輯更復雜,代碼更多,但是更精準的控制了相關資源的使用、創建和釋放邏輯。因此獲得了一定的性能優勢。并且在分辨率和格式相同的情況下,紋理不需要重新分配顯存空間,可以復用。
換句話說,就是特化了QImage顯示的場景,降低了控件的通用性以換取性能。
另外這里還有一個重要的性能優勢,就是圖片分辨率小于控件分辨率時,不在C++端進行縮放,而是把小圖片直接上傳到紋理,然后由QpenGL在顯示的時候完成縮放邏輯。
基于FBO也有圖片附件的顯示方法,代碼量更少。但是實測下來性能開銷也很大,因此我自己重寫了shader,沒有使用圖片附件的方法。
-
注1:圖片附件的代碼如下,有興趣的小伙伴可以自行搜索下文檔:
glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0 );
注2:我們暫時不討論AnimatedImage和VideoOutput,因為這2個控件有其他更精準的使用場景
關于測試代碼
測試代碼分為2部分,一個是QML部分,分別實例化了JQImageItem和JQImageItem2,鼠標點擊切換,開發者們可以觀察切換后,CPU和GPU占用情況的變化。
C++的Helper類,會起一個線程,以大約60FPS的速度在設置圖片給JQImageItem,以模擬圖片變化的場合。
測試數據會有一個縮放的效果,這個涉及到的圖片序列已經在初始化的時候生成完畢,并且統一了分辨率。這樣在測試代碼運行過程中的開銷,基本就只有渲染開銷。
心得體會
就如同之前所說,特化代碼,換取了性能優勢。因此這個方法需要更多的代碼量,以及OpenGL相關的知識,大部分開發者不會做到這一步。
對我而言,很久以前就有這個QImage的顯示需求,我都是拿QQuickPaintedItem實現,即使知道了FBO可以更快,但是無奈沒有相關的知識儲備,一直沒有做實現,一直到現在才把這個實現做了出來。
實現的代碼量其實不大,前后加起來就是300行不到。但是我覺得這個功能真的很重要,因此直接開源,方便其他開發者們取用。