OpenGL 4.4 和 Android

jopen 10年前

手机硬件的更新是如此迅速以至于我们开始进入一个手机与高端PC功能无异的时代。它们的区别将只是芯片的功耗不同而已。

我不想让自己听起来像是在推广我们自己的芯片,但我相信Tegra K1是未来即将发生的事物的前瞻。不仅仅发生在我们身上,我希望其他行业也能加入进来,从而使开发者可以为从PC到手机安全地假设一个相对统一的功能集。K1只是首个示例,因为它的GPU核心是Kepler 。而且它不是什么营销手段,它实际上是与运转像GTX780这种神奇事物相同的微架构。几年的血汗与泪水使其成为了可能。并且限于尺寸,功率和热限制,它只有192个核心(而不是2304个),但它仍具有较强的性能竞争力。更重要的是在功能上它处于一个完全不同的阵营中,因为它支持完整的桌面级OpenGL4.4,包括几何着色器,镶嵌,计算等,还有像bindless这样的支持其他事物的扩展。

但是Android只支持OpenGL ES,对吗?实际上不是这样的。虽然现在在Android平台上,首选的官方支持的图形API是OpenGL ES,你仍然可以使用EGL在平台上创建一个"大"OpenGL 上下文,前提是你的设备支持它。这使得在不同的操作系统上传输数据很实用,Windows/Linux/Mac/streamOS 都有OpenGL的后台程序,可以快速启动并模拟裁剪平面(clip planes),alpha测试,或者spec中的少数差异。它同样可以作为一个不错的引用渲染后端程序,在ES渲染路径启动和运行之前验证你的端口。

不要担心,你可以在你的app中同时使用GLES和BigGL。实际上,这很简单。最重要的事情在EGL中,它是Android为创建OpenGL上下文而公开的API。Java或C/C++同样可以这样做,但是要注意需要EGL_SPEC.1.4以上版本。

首先,在创建上下文(或者调用任何一个EGL函数)之前,要知道哪个版本的GL最支持当前设备。可以通过简单地调用eglBindAPI(EGL_OPENGL_API)来完成。该API会将EGL切换到桌面OpenGL模式,如果当前设备不支持,则返回EGL_TRUE或者留下未改变的状态并返回EGL_FALSE。鉴于该函数可以切换全局状态,所以最安全的做法是让EGL首先调用它,之后不再调用。

创建完上下文之后,如果eglBindAPI()函数没有切换BigGL模式,你就要和你之前那样创建你的ES上下文。如果切换成功,你可选择性地创建BigGL上下文。幸运的是,EGL让它简单化。因为我们已经调用了eglBindAPI(EGL_OPENGL_API),EGL已经设置为BigGL模式,所以我们只需要在配置文件和上下文中调整一些属性即可。

首先,在传递给eglChooseConfig()的配置属性中,我们需要确保 EGL_RENDERABLE_TYPE 设置为 EGL_OPENGL_BIT ,而不是EGL_OPENGL_ES2_BIT.

其次, 传递给函数  eglCreateContext() 的属性也需要稍微调整。对于 ES上下文,通常把 EGL_CONTEXT_CLIENT_VERSION 设为 1, 2 或3, 这取决于你想搭建哪个版本的OpenGL ES.对于BigGL上下文,我们不使用这个属性,所以不要设置。事实上,你可以直接把BigGL的属性列表置空。

粗略地讲,这比较像下面的样本 (为简洁起见,忽视错误检查):

if(eglBindAPI(EGL_OPENGL_API))  {    // Create a BigGL context...    EGLDisplay display = eglGetDisplay(...);    eglInitialize(display, ...);    const EGLint configAttrs[] =    {      EGL_SURFACE_TYPE,    EGL_WINDOW_BIT,      EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,      // backbuffer attributes here...      EGL_NONE    };    EGLConfig config;    EGLint    numConfigs = 0;    eglChooseConfig(display, configAttrs, &config, 1, &numConfigs);    EGLint ctxAttrs[] =    {      EGL_NONE    };    eglCreateContext(display, config, EGL_NO_CONTEXT, ctxAttrs);  }  else  {    // TODO: Fallback to old ES context creation...  }

最后一点问题,也是我对于使用OpenGL或者OpenGL ES的开发人员的一些建议,不论他们基于怎样的平台进行开发。尤其重要的一点是像上面提到的那样在API之间进行切换。并且,不要隐式地链接到GL符号!就算是再多的功能,你都应当使用eglGetProcAddress(),并且当心不要在上下文中共享函数指针。如果你做了一下比较疯狂的事情,比如在同一个应用中创建了一个BigGL上下文和一个ES上下文,或者创建了像glDrawElement()这样的函数,在两者的API中都存在,但是可能指向了完全不同的实现方法。这就意味着你应当仅链接到libEGL,所以需要通过qglGetProcAddress()函数查询所有的符号。

编辑:应当指出的是,从技术的角度讲,通过eglGetProcAddress()可获得的“EGL_KHR_get_all_proc_addresses”应当是目前的核心功能,但是我坚信,既然目前它在Android通用EGL接口上进行了实现,也就意味着它可以单独驱动,这可能在旧版本的Android上无法正常工作。但是如果你正在考虑支持BigGL,你可能不希望旧设备成为你道路上的绊脚石。

特别强调:我强烈建议如果可能的话,任何正在开发的应用程序应该也有GLES渲染路径。BigGL对于开发是实用的,对于一些边缘特性,甚至会更实用,但是有GLES后端同样会帮助Android避免分段存储,并且帮助你的应用更多的曝光在尽可能多的用户面前。