[原] GLES在iOS和Android上的不同

时间:2023-12-09 14:44:49

本来GLES提供了与native platform的接口 EGL,

然而iOS没有使用EGL接口, 而是自己搞了一套,叫做EAGL的类似东西, 虽然说大同小异,但是在做跨平台的时候还是很恶心.

elgMakeCurrent: 默认的EGL是需要surface和display的, iOS的EAGL实现, 对于用户(程序猿)来说, 没有surface和display这些东西.

iOS 下需要使用FBO + RBO, 然后直接通过native API: EAGLContext - presentRenderbuffer,将RBO的内容填充到目标窗口表面.

然而Android的GLES2.0下面, 如果使用了FBO + RBO, 那么只能用于离线渲染,离线读写, 没有任何copy和swap/present接口可以将RBO swap到窗口表面.

http://www.khronos.org/opengles/sdk/docs/man/xhtml/glBindFramebuffer.xml:

Application created framebuffer objects (i.e. those with a non-zero name) differ from the default window-system-provided framebuffer in a few important ways. First, they have modifiable attachment points for a color buffer, a depth buffer, and a stencil buffer to which framebuffer attachable images may be attached and detached. Second, the size and format of the attached images are controlled entirely within the GL and are not affected by window-system events, such as pixel format selection, window resizes, and display mode changes. Third, when rendering to or reading from an application created framebuffer object, the pixel ownership test always succeeds (i.e. they own all their pixels). Fourth, there are no visible color buffer bitplanes, only a single "off-screen" color image attachment, so there is no sense of front and back buffers or swapping. Finally, there is no multisample buffer, so the value of the implementation-dependent state variables GL_SAMPLES and GL_SAMPLE_BUFFERS are both zero for application created framebuffer objects.

虽然GLES3.0 有glBlitFramebuffer(),可以直接将自己的FBO复制到context的默认FBO( fboID = 0)上,然后swap, 但是显然目前的目标平台是GLES2.0. 所以现在对于安卓有两个方案: 1.使用默认FBO,放弃自定义的FBO和RBO. 2.使用自定义FBO+RenderTexture, 最后使用full screen quad把这张texture绘制到默认FBO上,然后swap.

很显然从效率上说,第一种好,因为第二种需要额外的内存开销,并且有多余的draw call,还多了一次纹理采样.但是最后可能要综合兼容性/适配性等其他因素, 选择最终方案.

On iOS, we use FBO and RBO to draw to the RBO, and use presentBuffer to swap the content of RBO to window surface(or layer).

but on Android GLES 2.0, still you can attach a RBO to your own FBO and render onto it, but there's no way that you can copy/swap your RBO to window surface.

although in GLES3.0 the glBlitFramebuffer is finally available, which can copy your own FBO to the default FBO( fboID = 0), and then yiou can swap it, that turns out not a sound solution since we're now targeting the GLES 2.0 platform.
Now we have 2 solutions: 1.to use the default FBO (0), and no other custom FBOs. 2. use own FBO, and RenderTexture instead of RBO, and finally draw the texture onto the default FBO via a full screen quad, and then swap it.
I believe the former one is better in performance because the latter one takes more memory and, uses extra draw call & texture sampling. But the compatiblity & adaptability also needs to be considered.


补充:

GL Extensions:  iOS每一代的硬件是固定的, 所以extension基本是固定的. 但是Android上由于显示芯片可以随厂家来配, 所以比较繁琐. 所以整体的开发上, iOS有点像console平台,比如XBOX, 而Android更像PC平台,需要考虑各种CPU/GPU/等等问题.

对于前面FBO的问题, 由于不使用FBO的话,灵活性就大大降低.而且等于关闭了RenderToTexture的特性, 当然可以只有在RenderToTexture的时候才绑定FBO, 正常渲染使用默认FBO, 但是这样看起来不够elegant(也许还好吧), 而且对于现有的架构有一定的改动. 所以目前暂用的方案是使用FBO, 用RenderTexture作为backbuffer, 最后再绘制到默认FBO上,已经测试通过.
如果以后有时间做后续优化的话,可能这么做: 用默认FBO, RenderToTexture的时候再绑定自己的FBO.


FBO不适合动态改变attachment, 如果有多个rendertarget, 那么就用多个FBO, 挂接到render target上.

动态attach buffer的效率相对要低.

2014/12/20 更新一些细节:

在iOS上, 即使OpenGLES 2.0的设备和API, 也支持一些3.0才有的特性, 2.0上或许有扩展, 但是都是各个厂商各自为战, 非常不方便.

1.Multisample (https://www.khronos.org/registry/gles/extensions/APPLE/APPLE_framebuffer_multisample.txt)

虽然它是Extension, 但是在iOS上是直接可以用的. 即RenderbufferStorageMultisampleAPPLE.

在android GLES 2.0上的Imagenation的Extension:

https://www.khronos.org/registry/gles/extensions/IMG/IMG_multisampled_render_to_texture.txt

因为是Imagenation(IMG)的扩展, 估计只能在PowerVR的GPU上才有.

2.MAXMIPLEVEL

https://www.khronos.org/registry/gles/extensions/APPLE/APPLE_texture_max_level.txt

这个GLES 2.0上只有扩展, 3.0才有. 但是iOS上也是固有的,即TEXTURE_MAX_LEVEL_APPLE, 其他平台就不一定了.

另外工作中发现某设备, 搭载的是GLES 3.0 capable的GPU, 但是是2.0的driver, 发现mipmap chain如果不完整(0-n)的话, 贴图采样会变成黑色, 这个是2.0的通病, 但是iOS上可以正常显示, 应该是2.0的spec不够清晰.

通过hack: 强制使用OpengGL ES 3.0的GL_TEXTURE_MAX_LEVEL / GL_TEXTURE_BASE_LEVEL, 竟然也能成功, 贴图就不黑了...
还有就是在这种设备上强制使用ETC2 (2.0的header, ETC2用的是3.0header里面ETC贴图对应的数字), 竟然也是可以的.

3.iOS上可以说有固定的贴图格式: PVRTC. 因为他用的全部都是PVR(PowerVR)的GPU. 而android的GLES 2.0上各种压缩贴图格式ATITC, ASTC, ETC1, PVRTC, S3TC...

虽然ETC1支持的最广泛, 但是ETC1压缩不支持alpha通道....需要单独加一张alpha贴图, 或者其他处理方式:
http://malideveloper.arm.com/cn/develop-for-mali/sample-code/etcv1-texture-compression-and-alpha-channels/

上面的前两个问题虽然恶心, 但是花时间总能找到渲染错误的原因, 而且有相对简单的解决方案. 但这个压缩纹理的问题才是是最头疼的问题.

所以目前我们的产品(大型3D游戏 :P)暂时只支持OpenGL ES 3.0的设备, 因为3.0对于压缩贴图格式,有了统一的标准: ETC2. 它支持alpha通道. 还有单通道和双通道压缩格式(specular/normal map等).

另外一种方案是使用in-game-download, 根据设备的特性下载对应的资源包, 需要生成n种资源包.
我们没有这么做是因为除了贴图的问题以外, OpenGLES 2.0上shader的bug也非常多, 这个不知道是compiler的bug, 还是渲染的问题:

同样的shader在iOS上没有问题, 但在android设备上, 总会遇到问题, 比如屏幕上有黑块, 或者某个shader渲染不对, 稍微改成其他等价的方式就好了, 比如之前贴过的bug:

 //GLES2.0 fragment shader

 float   factor   = ( dir >=  ) ?  : ;   //doesn't work! artifacts!
float factor = step(, dir); //f*******k! it works! the hell why?

改用3.0以后问题全好了. 所以说OpenGL ES 2.0 不适合做大型3D游戏. 至少是android上的不适合, 至少是目前不适合.

其他类似的问题应该还有很多, 比如iOS上固有glDiscardFramebufferEXT, 但是android仍然是Extension(https://www.khronos.org/registry/gles/extensions/EXT/EXT_discard_framebuffer.txt)

其他的暂时想不起来了.