你们好,这是个人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
,可能会观察到读出来的纹理是残缺不全的,渲染操做越是复杂,越是可能观察到,由于读的时候还来不及渲染完,在这个例子中,我作了一个模糊操做,让渲染操做稍微变得复杂。3d
下面是这个例子的效果:code
代码在我github
的OpenGLESPro
项目中,本文对应的是SampleFenceSync
,项目连接:github.com/kenneycode/…
感谢阅读!