OpenGL ES 编程指南

关于 OpenGL ES

重要 OpenGL ES 在 iOS 12 中已弃用。要在 GPU 上创建高性能代码,请改用 Metal 框架。见Metal

开放图形库 (OpenGL) 用于可视化 2D 和 3D 数据。它是一个多用途的开放标准图形库,支持 2D 和 3D 数字内容创建、机械和建筑设计、虚拟原型、飞行模拟、视频游戏等应用程序。您使用 OpenGL 配置 3D 图形管道并向其提交数据。顶点被变换和照亮,组装成图元,然后光栅化以创建 2D 图像。OpenGL 旨在将函数调用转换为可以发送到底层图形硬件的图形命令。因为这个底层硬件专门用于处理图形命令,所以 OpenGL 绘图通常非常快。

用于嵌入式系统的 OpenGL (OpenGL ES) 是 OpenGL 的简化版本,它消除了冗余功能,提供了一个更易于学习且更易于在移动图形硬件中实现的库。

image.png

乍看上去

OpenGL ES 允许应用程序利用底层图形处理器的功能。iOS 设备上的 GPU 可以执行复杂的 2D 和 3D 绘图,以及对最终图像中的每个像素进行复杂的着色计算。如果您的应用程序的设计要求要求对 GPU 硬件进行最直接和全面的访问,您应该使用 OpenGL ES。OpenGL ES 的典型客户端包括呈现 3D 图形的视频游戏和模拟。

OpenGL ES 是一个低级的、以硬件为中心的 API。虽然它提供了最强大和最灵活的图形处理工具,但它也有一个陡峭的学习曲线,并且对你的应用程序的整体设计有重大影响。对于需要高性能图形以实现更专业用途的应用程序,iOS 提供了几个更高级别的框架:

  • Sprite Kit 框架提供了一个硬件加速动画系统,针对创建 2D 游戏进行了优化。(请参阅*SpriteKit 编程指南*。)
  • Core Image 框架为静态和视频图像提供实时过滤和分析。(请参阅*核心映像编程指南*。)
  • Core Animation 为所有 iOS 应用程序提供了硬件加速的图形渲染和动画基础设施,以及一个简单的声明式编程模型,使实现复杂的用户界面动画变得简单。(请参阅*核心动画编程指南*。)
  • 您可以使用 UIKit 框架中的功能将动画、基于物理的动力学和其他特殊效果添加到 Cocoa Touch 用户界面。

OpenGL ES 是在 iOS 中实现的平台中立 API

因为 OpenGL ES 是一个基于 C 的 API,它具有极强的可移植性和广泛的支持。作为一个 C API,它与 Objective-C Cocoa Touch 应用程序无缝集成。OpenGL ES 规范没有定义窗口层;相反,宿主操作系统必须提供函数来创建一个 OpenGL ES渲染上下文,它接受命令,以及一个帧缓冲区,任何绘图命令的结果都被写入其中。在 iOS 上使用 OpenGL ES 需要使用 iOS 类来设置和呈现绘图表面,并使用平台中立的 API 来呈现其内容。

GLKit 提供绘图表面和动画支持

由 UIKit 框架定义的视图和视图控制器控制 iOS 上视觉内容的呈现。GLKit 框架提供了这些类的 OpenGL ES 感知版本。当您开发 OpenGL ES 应用程序时,您使用一个GLKView对象来渲染您的 OpenGL ES 内容。您还可以使用GLKViewController对象来管理视图并支持对其内容进行动画处理。

iOS 支持替代渲染目标

除了绘制内容以填充整个屏幕或视图层次结构的一部分之外,您还可以将 OpenGL ES 帧缓冲区对象用于其他渲染策略。iOS 实现了标准的 OpenGL ES 帧缓冲区对象,您可以使用这些对象渲染到屏幕外缓冲区或纹理,以便在 OpenGL ES 场景的其他地方使用。此外,iOS 上的 OpenGL ES 支持渲染到 Core Animation 层(CAEAGLLayer类),然后您可以将其与其他层组合以构建应用程序的用户界面或其他视觉显示。

应用程序需要额外的性能调整

图形处理器是针对图形操作优化的并行设备。为了在您的应用程序中获得出色的性能,您必须仔细设计您的应用程序以向 OpenGL ES 提供数据和命令,以便图形硬件与您的应用程序并行运行。调整不佳的应用程序会强制 CPU 或 GPU 等待对方完成处理命令。

您应该设计您的应用程序以有效地使用 OpenGL ES API。完成构建应用程序后,使用 Instruments 微调应用程序的性能。如果您的应用程序在 OpenGL ES 中遇到瓶颈,请使用本指南中提供的信息来优化您的应用程序的性能。

Xcode 提供了一些工具来帮助您提高 OpenGL ES 应用程序的性能。

OpenGL ES 可能无法在后台应用程序中使用

在后台运行的应用程序可能不会调用 OpenGL ES 函数。如果您的应用程序在后台访问图形处理器,它会被 iOS 自动终止。为避免这种情况,您的应用应在移入后台之前刷新之前提交给 OpenGL ES 的所有待处理命令,并避免调用 OpenGL ES,直到将其移回前台。

OpenGL ES 对多线程应用程序施加了额外的限制

设计应用程序以利用并发性有助于提高应用程序的性能。如果您打算向 OpenGL ES 应用程序添加并发性,您必须确保它不会同时从两个不同的线程访问相同的上下文。

为 iOS 构建 OpenGL ES 应用程序的清单

OpenGL ES 规范定义了一个平台中立的 API,用于使用 GPU 硬件来渲染图形。实现 OpenGL ES 的平台提供了用于执行 OpenGL ES 命令的渲染上下文、用于保存渲染结果的帧缓冲区以及一个或多个呈现帧缓冲区内容以供显示的渲染目标。在 iOS 中,EAGLContext该类实现了一个渲染上下文。iOS 仅提供一种类型的帧缓冲区,即 OpenGL ES 帧缓冲区对象,并且GLKViewCAEAGLLayer类实现渲染目标。

在 iOS 中构建 OpenGL ES 应用程序需要考虑几个因素,其中一些是 OpenGL ES 编程通用的,而一些是特定于 iOS 的。请按照此清单和以下详细部分开始:

  1. 确定哪些 OpenGL ES 版本具有适合您的应用程序的功能集,并创建 OpenGL ES 上下文。
  2. 在运行时验证设备是否支持您要使用的 OpenGL ES 功能。
  3. 选择渲染 OpenGL ES 内容的位置。
  4. 确保您的应用在 iOS 中正确运行。
  5. 实现你的渲染引擎。
  6. 使用 Xcode 和 Instruments 调试您的 OpenGL ES 应用程序并调整它以获得最佳性能。

选择要支持的 OpenGL ES 版本

确定您的应用程序是否应支持 OpenGL ES 3.0、OpenGL ES 2.0、OpenGL ES 1.1 或多个版本。

  • OpenGL ES 3.0 是 iOS 7 中的新功能。它添加了许多新功能,可实现更高的性能、通用 GPU 计算技术以及更复杂的视觉效果,而这些视觉效果以前只能在桌面级硬件和游戏控制台上实现。
  • OpenGL ES 2.0 是 iOS 设备的基准配置文件,具有基于可编程着色器的可配置图形管道。
  • OpenGL ES 1.1 仅提供基本的固定功能图形管道,并且在 iOS 中可用主要是为了向后兼容。

您应该针对支持与您的应用程序最相关的功能和设备的一个或多个 OpenGL ES 版本。要了解有关 iOS 设备的 OpenGL ES 功能的更多信息,请阅读*iOS 设备兼容性参考*。

验证 OpenGL ES 功能

*iOS 设备兼容性参考*总结了发货 iOS 设备上可用的功能和扩展。但是,为了让您的应用程序能够在尽可能多的设备和 iOS 版本上运行,您的应用程序应始终在运行时查询 OpenGL ES 实现的功能。

要确定实现特定的限制,例如最大纹理大小或最大顶点属性数,请使用其数据类型的适当函数查找相应标记的值(例如,MAX_TEXTURE_SIZEMAX_VERTEX_ATTRIBS在标头中找到)。gl.h``glGet

要检查 OpenGL ES 3.0 扩展,请使用glGetIntegervglGetStringi函数,如以下代码示例所示:

BOOL CheckForExtension(NSString *searchName)
{
    // Create a set containing all extension names.
    // (For better performance, create the set only once and cache it for future use.)
    int max = 0;
    glGetIntegerv(GL_NUM_EXTENSIONS, &max);
    NSMutableSet *extensions = [NSMutableSet set];
    for (int i = 0; i < max; i++) {
        [extensions addObject: @( (char *)glGetStringi(GL_EXTENSIONS, i) )];
    }
    return [extensions containsObject: searchName];
}

要检查 OpenGL ES 1.1 和 2.0 扩展,请调用glGetString(GL_EXTENSIONS)以获取所有扩展名称的空格分隔列表。

选择渲染目标

在 iOS 中,帧缓冲区对象存储绘图命令的结果。(iOS 不实现窗口系统提供的帧缓冲区。)您可以通过多种方式使用帧缓冲区对象的内容:

  • GLKit 框架提供了一个绘制 OpenGL ES 内容并管理其自己的帧缓冲区对象的视图,以及一个支持动画 OpenGL ES 内容的视图控制器。使用这些类来创建全屏视图或将 OpenGL ES 内容适合 UIKit 视图层次结构。
  • 该类CAEAGLLayer提供了一种将 OpenGL ES 内容作为核心动画层组合的一部分进行绘制的方法。使用此类时,您必须创建自己的帧缓冲区对象。
  • 与任何 OpenGL ES 实现一样,您还可以使用帧缓冲区进行离屏图形处理或渲染到纹理以在图形管道的其他地方使用。使用 OpenGL ES 3.0,屏幕外缓冲区可用于使用多个渲染目标的渲染算法。

与 iOS 集成

iOS 应用程序默认支持多任务处理,但在 OpenGL ES 应用程序中正确处理此功能需要额外考虑。OpenGL ES 使用不当可能会导致您的应用在后台被系统杀死。

许多 iOS 设备都包含高分辨率显示器,因此您的应用应支持多种显示器尺寸和分辨率。

实现渲染引擎

设计您的 OpenGL ES 绘图代码有许多可能的策略,其全部细节超出了本文档的范围。渲染引擎设计的许多方面对于 OpenGL 和 OpenGL ES 的所有实现都是通用的。

调试和分析

Xcode 和 Instruments 提供了许多工具来跟踪渲染问题和分析应用程序中的 OpenGL ES 性能。

配置 OpenGL ES 上下文

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

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

EAGL 是 OpenGL ES 渲染上下文的 iOS 实现

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

当前上下文是 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对象在不是当前上下文时被释放,您的应用程序必须保持对(或保留)这些对象的强引用。

每个上下文都针对特定版本的 OpenGL ES

一个EAGLContext对象只支持一个版本的 OpenGL ES。例如,为 OpenGL ES 1.1 编写的代码与 OpenGL ES 2.0 或 3.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,则改为初始化旧版本的上下文。清单 2-1演示了如何做到这一点。

清单 2-1  在同一个应用程序中支持多个版本的 OpenGL ES

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

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

EAGL 共享组管理上下文的 OpenGL ES 对象

尽管上下文持有 OpenGL ES 状态,但它并不直接管理 OpenGL ES 对象。相反,OpenGL ES 对象由对象创建和维护EAGLSharegroup。每个上下文都包含一个EAGLSharegroup将对象创建委托给的对象。

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

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

图 2-1  共享 OpenGL ES 对象的两个上下文

基于核心动画的渲染缓冲区

共享组在两种特定情况下最有用:

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

要创建引用同一共享组的多个上下文,第一个上下文通过调用初始化initWithAPI:;为上下文自动创建一个共享组。第二个和以后的上下文通过调用该initWithAPI:sharegroup:方法来初始化以使用第一个上下文的共享组。清单 2-2展示了它是如何工作的。第一个上下文是使用清单 2-1中定义的便利函数创建的。第二个上下文是通过从第一个上下文中提取 API 版本和共享组来创建的。

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

清单 2-2  使用公共共享组创建两个上下文

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

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

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

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

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

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

猜你喜欢

转载自juejin.im/post/7118200721576558629