你們好,今天給你們介紹一下OpenGL ES
的命令隊列及glFinish/glFlush
。多線程
咱們知道,咱們調用的OpenGL ES
方法,都是在CPU
上調用的,這些調用最終會被轉換成GPU
驅動指令而在GPU
上執行,而CPU
和GPU
由於是兩個不一樣的處理器,它們之間天然是能夠並行地執行各自的指令,OpenGL ES
有一個命令隊列用於暫存還未發送到GPU
的命令,實際上咱們調用的絕大多數OpenGL ES
方法,只是往命令隊列時插入命令而已,並不會在CPU
等命令執行完,所以若是你們去測耗時,會發現OpenGL ES
大多數方法,基本都不耗時,不管渲染的東西多麼複雜。post
我畫了一個圖來表示:性能
這裏注意一個細節,這個命令隊列並非全部的線程都對應同一個,命令隊列是和EGL Context
對應的,而一個線程又只能同時綁定到一個EGL Context
(關於EGL、GL線程、線程共享EGL Context,能夠參見個人另外一篇文章《OpenGL ES 高級進階:EGL及GL線程》),所以,能夠理解爲命令隊列是和綁定的EGL Context
的線程對應的。優化
有時咱們又但願在CPU
上等待OpenGL ES
命令執行完成,例如咱們有時但願作多線程優化,在兩個共享EGL Context
的線程中,在一個線程中渲染,在另外一個線程中用渲染好的紋理作其它操做等,那麼在這種狀況下,咱們是不能像在CPU
上作同步那樣的,來看一段僞代碼:spa
// thread0:
fun run() {
...
// 調用glDrawXXX()渲染到texture上
lock.notify()
...
}
// thread1:
fun run() {
...
lock.wait()
// 將texture拿去用
...
}
複製代碼
代碼中,咱們但願在thread0
完成渲染後,在thread1
中將它讀到bitmap
中,這樣會讀到什麼結果?基於前面的討論,能夠知道這樣讀到的結果是不肯定的,由於thread0
執行glDrawXXX()
以後,並不會等待GPU
真正執行了渲染,因此thread1
在使用texture
時,它的內容是不肯定的,有可能還沒開始渲染,也有可能渲染到了一半,或者是已經渲染完了。要獲得正確的結果,在OpenGL ES 2.0中
咱們可使用glFinsh()
,在OpenGL ES 3.0
中可使用fence
,後面我會寫文章介紹fence
,如今咱們使用glFinish()
,它的做用是在CPU
上等待當前線程對應的命令隊列裏的命令執行完成,加上glFinish()
後,咱們就必定能獲得正確的結果:線程
// thread0:
fun run() {
...
// 調用glDrawXXX()渲染到texture上
glFinish()
lock.notify()
...
}
// thread1:
fun run() {
...
lock.wait()
// 將texture拿去用
...
}
複製代碼
我畫了個圖來直觀的展現:code
因爲glFinish()
要在CPU
上等待,所以會對性能形成必定的影響,若是thread0
是一個主渲染線程,那就會對幀率產生影響,所以把等待放到比較次要的thread1
中會比較好,可是咱們把glFinish()
放到thread1
能夠嗎?來看下面這張圖:cdn
前面提到過,命令隊列是每一個綁定了EGL Context
的線程各自有各自的,glFinish()
只會等待當前線程的命令隊列中的命令執行完成,也就是等待thread1
的命令隊列中的命令執行完成,所以是沒有咱們指望的效果的,在OpenGL ES 2.0
中,是沒有辦法作到在一個線程中等待另外一個線程的OpenGL
命令的,在OpenGL ES 3.0
中能夠用fence
實現。blog
前面說到絕大多數OpenGL ES
方法是不會等待的,那麼什麼方法會等待呢?剛纔的glFinish()
就是一個,此外,還有將texture
讀取出來的方法glReadPixels()
也會等待,另外還有eglSwapBuffers()
,實際這2個方法會隱式調用glFinish()
,因此有時候咱們經常發現,glReadPixels()
會耗時,因而有些人會認爲,把texture
讀出來的操做很耗時,實際上這個讀操做並無多耗時,耗時是在等待命令隊列中的全部命令執行完成。隊列
你們能夠試一下,在glReadPixels()
前若是先調glFinish()
把命令隊列清空,再執行glReadPixels()
,會發現glReadPixels()
沒有想像中的那麼耗時。
glReadPixels()
爲何會隱式調用glFinish()
?你們能夠這樣理解,由於glReadPixels()
是要將texture
讀出來,若是不保證以前的渲染命令執行完,那麼讀出來的結果就是不肯定的,而eglSwapBuffers()
爲何也會隱式調用glFinish()
?能夠相似地這樣理解,由於eglSwapBuffers()
是將雙buffer
進行交換從而讓正在接受渲染的back buffer
能顯示出來,若是不保證全部渲染命令執行完,是否是有可能顯示出來是殘缺不全的?
說到這,順便提一下OpenGL ES
的耗時測量,因爲OpenGL ES
大多數方法只是往命令隊列裏插入命令而不等待執行完成,所以要測量一段OpenGL ES
操做的代碼真正的耗時,須要在先後加上glFinish()
:
glFinish()
val startTime = System.currentTimeMillis()
// 一頓OpenGL ES操做
glFinish()
val duration = System.currentTimeMillis() - startTime
複製代碼
在前面也加glFinish()
是爲了將以前的命令先執行完,不要干擾咱們的測量。
與glFinish()
相似的還有一個方法是glFlush()
,它的做用是將命令隊列中的命令所有刷到GPU
,但並不等它執行完成,所以有一些操做但願它能快些執行,但又不是特別急切到立刻等它執行完成,這時候就能夠用glFlush()
。
感謝閱讀!