最近在帧缓冲区对象这里卡了一下,不过前面已经了解了相关的OpenGL ES的知识,现在再去了解就感觉轻松多了。现在就进行总结。
基础知识
我们知道,在应用程序调用任何的OpenGL ES命令之前,需要首先创建一个渲染上下文和绘图表面,并使之成为现行上下文和表面,之前在渲染的时候,其实一直使用的是原生窗口系统(比如EAGL,GLFW)提供的渲染上下文和绘图表面(即帧缓冲区)。
一般情况下,我们只需要系统提供的帧缓冲区作为绘图表面,但是又有些特殊情况,比如阴影贴图、动态反射、处理后特效等需要渲染到纹理(Render To Texture/RTT)操作的,如果使用系统提供的帧缓冲区,效率会比较低低下。
对于系统提供的帧缓冲区,如果要实现RTT,有下面两种方式:
- 直接将帧缓冲区的对应区域赋值到纹理来实现RTT。借助glCopyTexImage2D/glCopyTexSubImage2D来从帧缓冲区复制颜色数据到纹理缓冲区,因为需要复制数据,所以操作比较慢,而且受限于EGLSurface的宽高尺寸,纹理的尺寸只能小于等于帧缓冲区尺寸大小。
- 使用连接到纹理的pbuffer来实现RTT。窗口系统提供的EGLSurface必须连接到EGLContext,但是pbuffer和EGLSurface可能需要不同的EGLContext,这样实现可能效率低。而且,在窗口系统提供的EGLSurface之间转换可能需要清除所有切换之前渲染的图像,这会造成CPU的闲置,这种情况下建议不要使用,因为EGLSurface和EGLContext切换相关的开销很大。
如果应用程序只在屏幕上的表面绘图,则窗口系统提供的帧缓冲区通常很高效。但是很多应用程序需要你渲染到纹理,使用窗口提供的不太理想,因此需要自定义自己的帧缓冲区。
帧缓冲区对象API支持以下操作:
- 仅使用OpenGL ES命令创建帧缓冲区对象。
- 在单一EGL上下文中创建和使用多个帧缓冲区对象。也就是说,不需要每个帧缓冲区都有一个渲染上下文。
- 创建屏幕外颜色,深度或者模版渲染缓冲区和纹理,并将它们连接到帧缓冲区对象。
- 在多个帧缓冲区之间共享颜色、深度或者模版缓冲区。
- 将纹理直接连接到帧缓冲区作为颜色或者深度,从而避免了进行复制操作的必要。
- 在帧缓冲区之间复制并使帧缓冲区内容失效。
帧缓冲区对象是一组颜色、深度和模版纹理或者渲染目标,和默认的帧缓冲区一样,自定义的帧缓冲区也包括颜色缓冲区、深度和模版缓冲区,这些逻辑上的缓冲区在FBO中称之为可附加的图像,它们是可以附加到FBO的二维像素数组。
FBO包含两种类型的附加图像,纹理图像和渲染缓冲区图像。附加纹理时,OpenGL渲染到这个纹理图像,在着色器中可以访问到这个纹理对象;附加到渲染缓冲区时,OpenGL执行离屏渲染。
但是需要注意的地方是,FBO可以附加多个缓冲区,而且可以灵活的在缓冲区进行切换,FBO中包含一个以上的颜色附加点,各种2D图像可以连接到帧缓冲区对象中的颜色附加点,但是只有一个深度和模版附加点。如下图:
使用帧缓冲区对象
创建的过程和创建VBO是类似的。
void glGenFramebuffers(GLsizei n, GLuint* ids)
void glDeleteFramebuffers(GLsizei n, const GLuint* ids)
void glBindFramebuffer(GLenum target, GLuint id)
这里的对象类型是指与附着点相关的对象的类型,如果要连接一个渲染缓冲区对象,则类型可以是GL_RENDERBUFFER,如果连接的是一个纹理对象,那么类型可以是GL_TEXTURE,但是默认值是GL_NONE。
使用渲染缓冲区对象
void glGenRenderbuffers(GLsizei n, GLuint* ids)
void glDeleteRenderbuffers(GLsizei n, const Gluint* ids)
void glBindRenderbuffer(GLenum target, GLuint id)
一旦绑定渲染缓冲区对象,就可以指定保存在渲染缓冲区对象中的图像大小和格式。
void glRenderbufferStorage(GLenum target,
GLenum internalFormat, GLsizei width, GLsizei height)
void glGetRenderbufferParameteriv(GLenum target, GLenum param, GLint* value)
附着
其中颜色附着可以分为渲染缓冲区附着或者纹理附着。
glFramebufferTexture2D(GLenum target,GLenum attachmentPoint,GLenum textureTarget,GLuint textureId, GLint level)
void glFramebufferRenderbuffer(GLenum target,GLenum attachmentPoint, GLenum renderbufferTarget, GLuint renderbufferId)
其实还可以连接3D纹理的一个图像作为帧缓冲区附着。
void glFramebufferTextureLayer(GLenum target,GLenum attachment, GLuint texture, GLint level, GLint layer);
检查帧缓冲区完整性
帧缓冲区对象必须定义为完整的才能够用作渲染目标。
其中帧缓冲区对象被视为完整的规则如下:
- 确保颜色、深度和模版附着有效;帧缓冲区至少有一个有效的附着,如果没有任何附着,那么帧缓冲区则是不完整的,因为没有可以绘制或者读取的区域。
- 与帧缓冲区对象相关的有效附着必须有相同的宽度和高度。
- 如果存在深度和模版附着,则它们必须是相同的宽度和高度。
- 所有渲染缓冲区附着的GL_RENDERBUFFER_SAMPLES值都相同。如果附着是渲染缓冲区和纹理的结合,则GL_RENDERBUFFER_SAMPLES的值为0。
glCheckFramebufferStatus命令可用于验证帧缓冲区对象是否完整,其返回的是一个枚举,是否完整。
GLenum glCheckFramebufferStatus( GLenum target);
GLenum glCheckNamedFramebufferStatus(GLuint framebuffer,GLenum target);
帧缓冲区位块传送
帧缓冲区位块传送可以高效的将一个矩形区域的像素值从一个帧缓冲区(读帧缓冲区)复制到另一个帧缓冲区(绘图帧缓冲区)。帧缓冲区位块传送的关键应用之一是将一个多重采样渲染缓冲区解析为一个纹理(用一个帧缓冲区对象,纹理绑定为它的颜色附着)。
void glBlitFramebuffer( GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
这个函数是从es 3.0才开始支持。
帧缓冲区失效
帧缓冲区失效为应用程序提供了一个通知驱动程序不再需要帧缓冲区内容的机制。因为可以通知无效,所以这可以使得驱动程序采取多种优化步骤:1.跳过在块状渲染(TBR)架构中为了进一步渲染到帧缓冲区而做的不必要的图块内容修复;2.跳过多GPU系统中GPU之间不必要的数据复制;3.跳过某些实现中为了改进性能而对特定缓存的刷新。这种功能对于许多应用程序中实现峰值性能很重要,特别是那些执行大量屏幕外渲染的应用。
这个函数在es 3.0中才会实现。
我们需要先了解TBR GPU中的设计,才能知道帧缓冲区失效对于GPU的重要性。TBR GPU常常部署在移动设备上,以最小化GPU和系统内存之间数据传输量,从而减少最大的电力消耗者之一,内存带宽;这通过添加能够保存少量像素数据的芯片内建快速存储器来实现,然后,帧缓冲区被区分为许多个图块,对于每个图块,图元被渲染到芯片内建的存储器中,然后结果在完成时被复制到系统内存。因为每个像素的最少量数据(最终像素结果)被复制到系统内存,所以这种方法节约了GPU和系统内存之间的内存带宽。
有了帧缓冲区失效机制,GPU就可以删除不再需要的帧缓冲区内容,以减少每个帧保留的内容数量,此外,如果图块数据不再有效,GPU还可以消除从芯片内建存储器到系统内存不必要的数据传输,因为GPU和系统内存之间内存带宽需求明显降低,所以电力消耗随之下降,性能则得到改善。
其中glInvalidateFramebuffer和glInvalidateSubFramebuffer命令用于使整个帧缓冲区或者帧缓冲区的像素子区域失效。
void glInvalidateFramebuffer( GLenum target, GLsizei numAttachments, const GLenum *attachments);
void glInvalidateSubFramebuffer( GLenum target, GLsizei numAttachments, const GLenum * attachments, GLint x, GLint y, GLint width, GLint height);
性能提示和技巧
在使用帧缓冲区对象时应该要认真考虑的性能提示:
- 避免频繁地在渲染到窗口系统提供的帧缓冲区和渲染到帧缓冲区对象之间切换。
- 不要逐帧创建和删除帧缓冲区和帧缓冲区对象(或者任何其他大型数据对象)。
- 尝试避免修改用作渲染图标的帧缓冲区对象附着的纹理,也就是说当指定纹理附着到帧缓冲区对象上以后,不要再进行修改(使用glTexImage2D、glTexSubImage2D、glCopyTexImage2D等)。
- 如果整个纹理图像将被渲染,则将glTexImage2D和glTexImage3D中的pixel参数设置为NULL,因为原始数据不会被使用。如果你希望图像包含任何预先定义的像素值,那么在绘制到纹理之前使用glInvalidateFramebuffer清除纹理图像。
- 尽可能共享帧缓冲区对象使用的用作附着的深度和模版渲染缓冲区,以保证内存占用需求最小,但是需要注意的是,因为帧缓冲区的宽度和高度必须相同。