OpenGL ES 框架详细解析(四) —— 配置OpenGL ES的上下文

版本记录

版本号 时间
V1.0 2017.09.28

前言

OpenGL ES是一个强大的图形库,是跨平台的图形API,属于OpenGL的一个简化版本。iOS系统可以利用OpenGL ES将图像数据直接送入到GPU进行渲染,这样避免了从CPU进行计算再送到显卡渲染带来的性能的高消耗,能带来来更好的视频效果和用户体验。接下来几篇就介绍下iOS 系统的 OpenGL ES框架。感兴趣的可以看上面几篇。
1. OpenGL ES 框架详细解析(一) —— 基本概览
2. OpenGL ES 框架详细解析(二) —— 关于OpenGL ES
3. OpenGL ES 框架详细解析(三) —— 构建用于iOS的OpenGL ES应用程序的清单

配置OpenGL ES的上下文

OpenGL ES的每个实现都提供了一种创建渲染上下文来管理OpenGL ES规范所需的状态的方法。 通过将这种状态置于上下文中,多个应用程序可以轻松共享图形硬件,而不会干扰对方的状态。

下面详细介绍如何在iOS上创建和配置上下文。


EAGL Is the iOS Implementation of an OpenGL ES Rendering Context - EAGL是OpenGL ES渲染上下文的iOS实现

在您的应用程序可以调用任何OpenGL ES函数之前,必须初始化一个EAGLContext对象。 EAGLContext类还提供了用于将OpenGL ES内容与Core Animation集成的方法。


The Current Context Is the Target for OpenGL ES Function Calls - 当前上下文是OpenGL ES函数调用的目标

iOS应用程序中的每个线程都具有当前上下文; 当您调用OpenGL ES函数时,这是通过调用更改其状态的上下文。

要设置线程的当前上下文,请在该线程上执行时调用EAGLContext类方法setCurrentContext:

[EAGLContext setCurrentContext: myContext];

调用EAGLContext类方法currentContext来检索线程的当前上下文。

注意:如果您的应用程序主动在同一线程上的两个或多个上下文之间切换,请在将新上下文设置为当前上下文之前调用glFlush函数。 这样可以确保先前提交的命令及时传递给图形硬件。

OpenGL ES对与当前上下文相对应的EAGLContext对象有很强的引用。 (如果使用手动引用计数,OpenGL ES保留此对象。)当调用setCurrentContext:方法来更改当前上下文时,OpenGL ES不再引用上一个上下文。 (如果您使用手动引用计数,OpenGL ES会释放EAGLContext对象。)为防止当不在当前上下文时EAGLContext对象被释放,您的应用程序必须保持强制引用(或保留)这些对象。


Every Context Targets a Specific Version of OpenGL ES - 每个上下文都针对特定版本的OpenGL ES

EAGLContext对象只支持一个版本的OpenGL ES。 例如,为OpenGL ES 1.1编写的代码与OpenGL ES 2.03.0上下文不兼容。 使用核心OpenGL ES 2.0功能的代码与OpenGL ES 3.0上下文兼容,为OpenGL ES 2.0扩展设计的代码通常可以在OpenGL ES 3.0上下文中进行微小更改。 许多新的OpenGL ES 3.0功能和增加的硬件功能需要OpenGL ES 3.0上下文。

您的应用程序决定在创建和初始化EAGLContext对象时要支持的OpenGL ES版本。 如果设备不支持所需版本的OpenGL ES,initWithAPI:方法返回nil。 您的应用程序必须进行测试,以确保在使用上下文之前初始化成功。

为了支持多种版本的OpenGL ES作为应用程序中的渲染选项,您应该首先尝试初始化要定位的最新版本的渲染上下文。 如果返回的对象为nil,则会初始化旧版本的上下文。 下面代码演示了如何做到这一点。

//Supporting multiple versions of OpenGL ES in the same app

EAGLContext* CreateBestEAGLContext()
{
   EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
   if (context == nil) {
      context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
   }
   return context;
}

上下文的API属性指出上下文支持的OpenGL ES版本。 您的应用程序应测试上下文的API属性,并使用它来选择正确的渲染路径。 实现此行为的常见模式是为每个渲染路径创建一个类。 您的应用程序在初始化时测试上下文并创建一次渲染器。


An EAGL Sharegroup Manages OpenGL ES Objects for the Context - EAGL Sharegroup 管理上下文的OpenGL ES对象

虽然上下文保持OpenGL ES状态,但它并不直接管理OpenGL ES对象。 相反,OpenGL ES对象由EAGLSharegroup对象创建和维护。 每个上下文都包含一个EAGLSharegroup对象。

当两个或多个上下文引用相同的共享组时,共享组的优点变得明显,如下图所示。 当多个上下文连接到公共共享组时,任何上下文创建的OpenGL ES对象都可用于所有上下文; 如果绑定到另一个上下文上与创建它相同的对象标识符,则引用同一个OpenGL ES对象。 移动设备上的资源通常很少; 在多个上下文中创建相同内容的多个副本是浪费的。 共享共享资源可以更好地利用设备上可用的图形资源。

一个共享组是一个不透明的对象; 它没有您的应用可以调用的方法或属性。 使用sharegroup对象的上下文保持强有力的引用。

Two contexts sharing OpenGL ES objects

在两种具体情况下,sharegroups最有用:

  • 当上下文之间共享的大部分资源是不变的。
  • 当您希望您的应用程序能够在除渲染器的主线程之外的线程上创建新的OpenGL ES对象。 在这种情况下,第二个上下文在单独的线程上运行,专门用于获取数据和创建资源。 在资源被加载之后,第一个上下文可以绑定到对象并立即使用它。 GLKTextureLoader类使用此模式提供异步纹理加载。

要创建引用同一个sharegroup的多个上下文,首先通过调用initWithAPI: 将为上下文自动创建一个共享组。 通过调用initWithAPI:sharegroup:方法,将第二个和更晚的上下文初始化为使用第一个上下文的共享组。 下面的代码显示了如何工作。 第一个上下文使用上面代码中定义的方便函数创建。 第二个上下文通过从第一个上下文中提取API版本和共享组来创建。

重要提示:与同一共享组相关联的所有上下文都必须使用与初始上下文相同的OpenGL ES API版本。

// Creating two contexts with a common sharegroup

EAGLContext* firstContext = CreateBestEAGLContext();
EAGLContext* secondContext = [[EAGLContext alloc] initWithAPI:[firstContext API] sharegroup: [firstContext sharegroup]];

当共享组由多个上下文共享时,应用程序有责任管理OpenGL ES对象的状态更改。 这是规则:

  • 如果对象未被修改,您的应用程序可以同时访问多个上下文中的对象。
  • 当通过发送到上下文的命令修改对象时,不得在任何其他上下文中读取或修改该对象。
  • 修改对象后,所有上下文都必须重新绑定对象以查看更改。 如果上下文在绑定之前引用它,则该对象的内容是未定义的。

以下是应用程序应该更新OpenGL ES对象的步骤:

  • 在可能使用对象的每个上下文上调用glFlush
  • 在要修改对象的上下文中,调用一个或多个OpenGL ES函数来更改对象。
  • 在接收到状态修改命令的上下文中调用glFlush。
  • 在其他上下文中,重新绑定对象标识符。

注意:共享对象的另一种方法是使用单个渲染上下文,但是使用多个目标帧缓冲区。 在渲染时,您的应用程序会绑定相应的帧缓冲区,并根据需要呈现其帧。 因为所有OpenGL ES对象都是从单个上下文引用的,所以它们会看到相同的OpenGL ES数据。 此模式使用的资源较少,但仅适用于可以仔细控制上下文状态的单线程应用程序。

后记

未完,待续~~~

推荐阅读更多精彩内容