1. khronos 简介
khronos 是由 170 家企业组成的开源、非盈利的成员驱动型组织,主要负责开发、发布和维护免税版权标准,其范围涉及到 3D图像学、虚拟现实、增强现实、并行计算、视觉加速和机器学习。该组织制定的标准及其相关的适应性测试提高了软件和中间软件的创作,而且加速了动态多媒体的在跨平台设备上的回放。
khronos 是 OpenGL ES 标准的制定者、维护者,有什么疑问直接去官方找资料即可:
https://www.khronos.org/registry/OpenGL/specs
2. OpenGL 和 OpenGL ES
OpenGL ES 是 OpenGL 的子集,专门为嵌入式设备而设计,如手机端,另外 Play Station 也有使用;
嵌入式设备的因为诸多限制,比如能耗,所以 GPU 和 CPU 性能一般不如 PC 端,所以 OpenGL ES 的设计相对 OpenGL 就很精简、高效,比较典型的区别如下:
- 在OpenGL ES的世界里,没有四边形、多边形,无论多复杂的图形都是由点、线和三角形组成的;
- 没有glBegin/glEnd/glVertex,只能用glDrawArrays/glDraw;
- 没有double型数据类型,但加入了高性能的定点小数数据类型;
- 没有实时将非压缩图片数据转成压缩贴图(纹理)的功能,程序必须直接提供压缩好的贴图;
- 删除了很多功能:显示列表、求值器、索引色模式等等;
- glDrawArrays等函数中数据必须紧密排列,即间隔为 0;
7.各种数据的堆栈深度较低;
3. iOS 中 OpenGL ES 的版本
在学习时,一般参考 OpenGL 的官方教程:
https://learnopengl-cn.github.io/
但是需要注意的是,OpenGL 和 OpenGL ES 还是有很大的不同,比如 #version
的声明,所以一些图形学的概念可以参考 openGL 中的教程,但是涉及到具体的 Api 差异或者版本问题,最好直接看 OpenGL ES 的官方文档;
这里是 openGL 和 openGL ES 相关文档的归档:
https://www.khronos.org/registry/OpenGL/specs/es/
3. version
按照 LearnOpenGL 中的示例代码,编译着色器时如果使用到了#version 330 core
,跑在 iOS 设备上,必定是会报错的,报错信息大概如下:
ERROR: 0:1: '' : version '330' is not supported
ERROR: 0:1: '' : syntax error: #version
那为什么会报错呢?原因是因为 OpenGL ES 中的 GLSL 最高版本为 320,且使用 GLSL ES 时,需要这样:
#version number es
而 OpenGL 中,3.3 版本发生了重大更新,所以一般使用 OpenGL 3.3 版本,对应的 GLSL 为 330,具体可以上官网查询。而 OpenGL 中版本声明一般这么写:
#version number core
关键就在于版本号和 es 、core 的区别;
来看看官方文档中的描述吧~~~
这里是 3.1.0 的版本:GLSL_ES_3.1.0
最新版本为 3.2.0,iOS 中支持的 GLSL ES 最高版本为 3.0.0,但是 version 部分基本没什么变化,所以直接那 3.1.0 中的文档来看了;
关于 #version
的描述如下:
比较重要的几个结论:
- 必须声明在第一行,且必须在所有预处理命令之前,且必须在注释之前;
- 如果没有声明 version,则默认为 1.0.0 版本;
- 和 OpenGL 中的 GLSL 不同, #version number 后面必须加 es,以此来表明该 GLSL 为 ES 版本;
再来看看 iOS 中的 OpenGL ES,iOS 支持 openGL ES 3.0,相关文档如下:
这里其实有待商榷,Apple 官方文档中没有找到 OpenGL ES 的具体版本,只知道是 OpenGL ES 3.0 以上的。可以做下测试,如果着色器代码中声明 #version 320 es
被支持,也就是最新版本的 GLSL ES,是不是可以理解为:iOS 的 OpenGL ES 虽然被遗弃了,但是仍然是最新的?
测试 #version 320 es
和 #version 310 es
结果如下:
测试 #version 300 es
,结果如下:
也就是说,iOS 中的 GLSL ES 版本为 3.0.0。所以,iOS 对应的 OpenGL ES 也为 3.0.0,并不是 2019 年修订的 3.2 版本。
4. Apple 抛弃 OpenGL
iOS 从 iphone7 + iOS12 开始就已经抛弃了 OpenGL ES,开始主推自研的 Metal:
这里有个疑问:
已经有现成的 openGL ES 了,得罪开发者,而且自己也需要额外的工作量来开发新的 Metal 框架,同时还需要底层 GPU 支持自己的 Metal 框架,这么吃力不讨好的事情, Apple 为什么这么做?
其实这里可以拆解成几个问题:
- OpenGL ES 是否已经不能满足 Apple 的需求了?
- 是否真的得罪开发者?
- Apple 是否在为自己的独立 GPU 步道?
首先第一个问题,查阅资料之后可以确定,openGL ES 已经无法满足 Apple 了。因为:
- OpenGL ES 由 khronos 制定、发布、更新,但是该组织由很多公司组成,各公司为了自己的利益在 openGL/openGL ES 中增加扩展,需要得到的部分公司同意之后才能发布到 release 版本,所以效率极慢;
- openGL 和 openGL ES 已经很久没有更新了,而且官方已经使用 Vulken 来替代 openGL 了。嵌入式设备端, Vulken 已经支持嵌入式设备,并在安卓和 Flutter 上应用,只是 Apple 有了 Metal,所以官方并没有对其进行支持。如果想在 MacOS/iOS 上使用 Vulken,只能使用 khronos 开发的扩展来进行支持,本质上是对 Metal 的封装;
- OpenGL 拥有很多历史包袱且支持跨平台,那么性能必定上无法满足 Apple 对高性能的追求。加上更新缓慢,Apple 研发自己的图形 Api 势在必行;
所以,对于第一个问题可以做出解答:
OpenGL/OpenGL ES 已经有点更不上现在 GPU 的发展,在性能和、更新周期、Api 设计上皆无法满足 Apple 的需求了,所以 Metal 势在必行;
第二个问题,Apple 是否对开发者不友好?这个要从几点来说明:
- Apple 从来都是以自身的发展方向作为技术选型的主要考虑,包括后面的自研芯片,因特尔的芯片因为无法跟上自己的需求被 Apple 抛弃转向自研 M1 芯片,Metal 同样如此;
- DirectX 和 Metal 一样都是属于面向 GPU 的图形 Api,windows 早就这么做了,为什么现在 Apple 这么做就不行?
- OpenGL/OpenGL ES 官方都已经抛弃并转向 Vulken 了,这个时候怪 Apple 对开发者不友好?谁让你在 Metal 都已经武装完成之后才出来 Vulken,难不成 Apple 还要一直等你或者受制于 khronos?
第三个问题,其实已经不用回答了,从 M1 芯片的推出,Apple 几乎已经大通了硬件+软件的全链路,自成一派了,自身的发展完全由自己掌握,包括后续的电车、增强现实、虚拟现实、机器学习、Aiot 框架等等,都可以由自己来支持。
5. 图形学框架常见名词和分层
常见的名词有:
- Unity 3D、Cocos3D 、UE4;
- sika;
- Core Animation、Core Graphics
- FlutterUI、MaterialUI、UIKit;
这里以 Flutter 的图形架构图来展开:
从上图可以看出,基本上可以这样分层:
- 图形 Api
该层是面向 GPU 的图形 Api,也就是我们上文说到的 Vulken 、DirectX 、Metal、 OpenGL/OpenGL ES。OpenGL 已经被抛弃,所以主流的图形 Api 也就剩下三个了。
从上文可以看到,Flutter 使用 OpenGL ES 对低端机器进行支持,使用 Metal 支持 iOS 平台,使用 Vulken 支持 Android 平台。因为 DirectX 主要用于 PC 端,自然不在其框架内。
- 中间层
为什么需要中间层?
封装的主要原因是满足不同级别的抽象。面向 GPU 的图形学 Api 不可能直接提供给所有的开发者,所以系统需要为开发者提供 UI 框架。但是在开发这个框架时,也不太可能直接使用图形 Api,因为这样会有大量重复工作。
另外,有很多业务是不需要所有的上层开发者知道的,学习成本越低,那么框架的普及就越高效,所以就需要封装很多流程,比如渲染机制的封装。
所以中间层诞生了,这个主要供系统开发人员使用,同时也会部分提供给业务开发者;
如上图,Core Animation 主要是封装了渲染管线、CPU 和 GPU 的交互。苹果的员工在开发 UIKit 时,不需要自己去生成和计算顶点数据、着色器代码,也不需要去调用很多 openGLES/Metal 的接口就可以完成渲染操作。
skia 作为 Flutter 的核心渲染框架,也是同理。因为 Flutter 是跨平台的,所以 Skia 内部相对于 Core Animation,必定会多出很多跨平台判断、兼容的代码;
- UI 层
这一层就是系统 UI 框架的最上层,面向普通的开发者。比如 iOS 中的 UIKit,MacOS 中的 AppKit,Android 中的 View、Group 等,还有 Flutter 的 Flutter UI;
开发者可以使用该层架构提供的 Api 来快速完成 UI 相关的业务,就算是新手也可以使用几行代码就可以展示一个 Label、Button 或者是列表;
Unity 3D 没有使用过,个人理解,Unity 3D 类似于 UIKit 和 AppKit 框架,或者说是介于 UIKit 和 OpenGL/Metal 中间的一个框架。开发者可以使用 Unity 3D 提供的 Api 做出很多酷炫的动画和图形,从而简化游戏开发或者是重度绘制 App 的开发流程。
个人更偏向于把 Unity3D 放在和 Core Animation/Skia 平级,也就是直接操作图形 Api。Unity3D 作为跨平台游戏引擎,自然也是完成了对 OpenGL、Metal、Vulken 的封装。
上文中写道,不可能面向所有的开发者提供图形 Api,关键在于这个所有人的区分。工具人还是工程师,取决于你对原理了解的多少,对本质触及的多少,这些才最终决定了你是哪一类人。
6. Tips
总结:
- 图形 Api(graphics Api)是针对 GPU 的,由 GPU 厂商进行支持;GPU 厂商支持的越多,你的标准就更容易流行并被开发者接纳,亦或是像苹果一样,依靠自身的生态链自成一派;
- 现在的图形 Api 只有三种:Metal、OpenGL、Vulken;
- 图形 Api 之上还有中间层,也就是对图形 Api 的封装和渲染流程的封装,有:skia(flutter)、Core Animation/Core Graphic、Canvans??类似的,游戏开发中的游戏引擎也是处于这一部分,比如 Unity3D、UE4 或 cocos2dx 都是给予对图形 Api 的封装而实现的;
- 在中间层之上,还有 UI Framework ,比如 UIKit 就是使用 Core Animation 或者 Core Graphic 实现了一套 UI。类似的还有 Android 和 Flutter 上层的 UI Framework;
- openGL 是最初的图形 Api,在最早也是所有图形绘制框架的基础架构,跨平台支持很好。但是随着嵌入式设备的普及而嵌入式的 GPU 又没有 PC 端那么强悍,所以只能在 openGL 上拉出一个子集来支持,也就是 openGL ES;
- openGL 虽然历史悠久,跨平台支持很好,但是存在着很多历史包袱,另外因为跨平台,if else 自然就会消耗性能,最后,因为 khronos 是有很多公司组成的一个组织,大家都会为了子集的利益各自为战,这就导致 openGL 更新缓慢,适应不了一些公司的发展,所以就出现了 DirectX 和 Metal;
- Vulken 本质上是为了 替换 openGL,所以一开始就是跨平台的。但是 Apple 已经有了自己的 Metal 框架,所以暂时没有对 Vulken 进行支持,这也就让 Vulken 的跨平台意义大打折扣,也就让 Vulken 和 DirectX 成为了竞争对手。
- 当前除了 Apple 在 iOS/MacOS 上已经完全抛弃了 openGL,其他平台暂时还处于一种比较纠结和共存的状态,但是大部分 GPU 厂商已经支持 Vulken 了,这算是一个比较好的地基;
- Windows PC 端,大部分游戏应用都是给予 DirectX 开发,少部分已经支持 Valken,比如 Dota2;
- 安卓端,仍然是 openGL ES 为主,Flutter 已经是 Vulken 和 Metal 为主了?openGL 用于支持低端机型?
- vulkan 相对于 openGL,Api 上更偏向于底层,这意味着可以使用 vulkan 更加自由的操作 GPU 以此来为上层提供更丰富和搞笑的功能。于此同时,开发者也需要做更多的工作,所以也就出现了图形 Api 入门程序中,绘制三角形时,vulkan 的代码远超过 openGL/openGL ES;
7. iOS 和 OpenGL ES
几个结论:
- iOS 上仍然可以使用 OpenGL ES 进行开发,只不过版本最高只能使用 OpenGL ES 3.0;
- iOS 上如果实在对图形 Api 有需求,趁早转 Metal;
- iOS 中做一些渲染优化时,最好直接参考 Apple 的官方文档。因为 Metal 和 OpenGL ES 的流程、设计思路都不一样了;
- 要深刻认识封装、封装颗粒、Api 通用性设计的重要性;
比如如果 App 中有图形 Api 的需求,就应该基于 OpenGL ES 进行封装,产生一套自有的中间框架,而且 Api 应当尽量屏蔽硬件层的逻辑。如果这样做了,除非 Metal 不支持,后续更换 Metal,只需要将 OpenGL ES 的调用替换成 Metal 即可,相对而言会轻松一些;