你們好,這是個人OpenGL ES
高級進階系列文章,在個人github
上有一個與本系列文章對應的項目,歡迎關注,連接:github.com/kenneycode/…git
今天給你們介紹OpenGL ES 3.0
的一個特性fence
,它能夠用來同步OpenGL
命令,在多線程編程時頗有用,我以前的一篇文章《OpenGL ES 命令隊列及glFlush/glFinish》中有說到,OpenGL
命令的執行是在GPU
上的,咱們調用OpenGL
方法其實是往OpenGL
的命令隊列裏面插入命令,GPU
從命令中取出命令執行。github
所以,通常狀況下咱們調用OpenGL
方法後,並非立刻有效果的,若是要某處確保以前的OpenGL
執行完,就須要用到glFinish
,但glFinish
只能保證本線程對應的命令隊列中的命令執行完,這就意味着不能在一個線程中等待另外一個線程的OpenGL
命令執行完,這就有很大的限制,回想咱們在CPU
上的同步操做,例如咱們在一個線程中wait,在另外一個線程中notify,這很容易實如今一個線程中等待另外一個線程的指定任務執行完成,這也是咱們很經常使用的操做,但在對於GPU
,沒法用glFinish
來實現相似的邏輯,實際上,在OpenGL ES 3.0
之前,是沒法實現的。編程
到了OpenGL ES 3.0
,咱們能夠用fence
實現,使用越來也很簡單,就是在一個線程中插入一個fence
,而後在另外一個線程中就能夠去等待這個fence
,我畫了一張圖:多線程
例如咱們有這樣一種邏輯,在GLThread 0
中渲染一個紋理,在另外一個線程GLThread 1
中將這個紋理拿去使用,那就須要確保在GLThread 1
使用這個紋理時,GLThread 0
對這個紋理的渲染已經完成,有了fence
後,咱們能夠在GLThread 0
渲染操做以後插入一個fence
,而後在GLThread 1
要使用這個紋理時去等這個fence
。ide
咱們來看看代碼,先看看插入fence
的代碼:post
val fenceSyncObject = GLES30.glFenceSync(GLES30.GL_SYNC_GPU_COMMANDS_COMPLETE, 0)
複製代碼
這個方法調用後會往當前線程的命令隊列中插入一個fence
並返回一個long
型變量來代碼這個fence
同步對象,以便於其它地方去等待它,有2個方法能夠用於等待,glWaitSync
和glClientWaitSync
,它們的差異是glWaitSync
是在GPU
上等待,glClientWaitSync
是在CPU
上等待。spa
來看看咱們有例子代碼,在這個例子中,咱們在一個線程中渲染一張圖到一個紋理同時到屏幕上,在另外一個線程中將這個紋理讀出來顯示到屏幕的右下角的一個ImageView
上:線程
override fun onDrawFrame(gl: GL10?) {
...
// 渲染到紋理上
// Render to texture
GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, textureCoordinateData.size / VERTEX_COMPONENT_COUNT)
// 向OpenGL的Command Buffer中插入一個fence
// Insert a fence into the OpenGL command buffer
val fenceSyncObject = GLES30.glFenceSync(GLES30.GL_SYNC_GPU_COMMANDS_COMPLETE, 0)
// 在另外一個線程中讀取當前線程的渲染結果
// Read the render result in the other thread
otherThreadHandler.post {
if (!flag) {
// 等待fence前的OpenGL命令執行完畢
// Waiting for completion of the OpenGL commands before our fence
GLES30.glWaitSync(fenceSyncObject, 0, GLES30.GL_TIMEOUT_IGNORED)
// 刪除fence同步對象
// Delete the fence sync object
GLES30.glDeleteSync(fenceSyncObject)
val frameBuffers = IntArray(1)
GLES30.glGenFramebuffers(frameBuffers.size, frameBuffers, 0)
GLES30.glActiveTexture(GLES30.GL_TEXTURE1)
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, sharedTexture)
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, frameBuffers[0])
GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, sharedTexture, 0)
val buffer = ByteBuffer.wrap(ByteArray(glSurfaceViewWidth * glSurfaceViewHeight * 4))
GLES30.glReadPixels(0, 0, glSurfaceViewWidth, glSurfaceViewHeight, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, buffer)
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0)
val bitmap = Bitmap.createBitmap(glSurfaceViewWidth, glSurfaceViewHeight, Bitmap.Config.ARGB_8888)
buffer.position(0)
bitmap.copyPixelsFromBuffer(buffer)
flag = true
// 將讀取到的渲染結果顯示到一個ImageView上
// Display the read render result on a ImageView
imageView.post {
imageView.setImageBitmap(bitmap)
}
}
}
// 將frame buffer綁回0號,將渲染結果同時也顯示到屏幕上
// Bind frame buffer to 0# and also render the result on screen
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0)
GLES30.glActiveTexture(GLES30.GL_TEXTURE0)
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, imageTexture)
GLES30.glClearColor(0.9f, 0.9f, 0.9f, 1f)
// 清屏
// Clear the screen
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)
// 設置視口,這裏設置爲整個GLSurfaceView區域
// Set the viewport to the full GLSurfaceView
GLES30.glViewport(0, 0, glSurfaceViewWidth, glSurfaceViewHeight)
GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, vertexData.size / VERTEX_COMPONENT_COUNT)
}
複製代碼
加了fence
後就能保證在另外一個線程中讀出的時候,圖片紋理已經渲染完成了,若是不加fence
,可能會觀察到讀出來的紋理是殘缺不全的,渲染操做越是複雜,越是可能觀察到,由於讀的時候還來不及渲染完,在這個例子中,我作了一個模糊操做,讓渲染操做稍微變得複雜。code
下面是這個例子的效果:cdn
代碼在我github
的OpenGLESPro
項目中,本文對應的是SampleFenceSync
,項目連接:github.com/kenneycode/…
感謝閱讀!