OpenGL ES命令隊列及glFinish/glFlush

你們好,今天給你們介紹一下OpenGL ES的命令隊列及glFinish/glFlush多線程

咱們知道,咱們調用的OpenGL ES方法,都是在CPU上調用的,這些調用最終會被轉換成GPU驅動指令而在GPU上執行,而CPUGPU由於是兩個不一樣的處理器,它們之間天然是能夠並行地執行各自的指令,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()

感謝閱讀!

相關文章
相關標籤/搜索