第 14 章 图像和 UI:原理

节选自《iOS和macOS性能优化》

这一部分的文章是我早年间参与iOS和macOS性能优化这本书翻译时的原稿, 当时的水平有限, 翻译的可能会有一些纰漏, 还请多多指教. 将文章同步到个人博客主要是为了同步和备份.

在之前的章节中,我们已经学习了不少底层系统性能方面的知识,例如 CPU、内存和 I/O ,现在我们可以将学到的知识应用到搭建一个高性能的用户界面中。这意味着应用程序既能快速绘制用户界面,又能高效响应用户请求。

响应能力

当提及高性能的用户界面(high-performance user interface)时,通常也意味着它是一个响应式的界面,也就是说它能快速响应用户的操作。这里的快速响应到底得多快呢?一般说来,就是能多快就多快,不过我们还是设定了一些阈值来区分用户的感知程度。具体的数值请见表 14.1。

下表中最值得关注的两个阈值分别为响应用户操作的 100 毫秒和保证动画流畅所需的 60 Hz/16(2/3)毫秒,如果你不想让用户在向计算机发出指令后等待一段时间才获得反馈,而是希望让用户有一种在屏幕直接操控物体的感觉,那么这两个阈值就显得及其重要。下表提及的 25 Hz(也有说是 30 Hz)也是一个常见的阈值,举个例子来说,它可以应用在模拟电影(analog film,用模拟胶片拍摄的电影,需要进行数字化后才能在电子设备上播放)的重制过程中,如果想让整个电影画面平稳过渡的话,它还依赖模拟媒介提供的集成效果,例如运动模糊等。而对于数字屏幕上的动画,60 Hz 将成为我们的目标。

表 14.1 响应时间阈值表

响应阈值 效果
10 秒 能够引起用户的注意
1 秒 能够保证用户持续关注
100 毫秒 能够造成直接操作对象的感觉
40 毫秒(25 Hz) 能够将多个帧合成动画
16(2/3)毫秒(60 Hz) 能够让动画变得平缓细腻
1 毫秒 能够追踪到快速的手势操作

请见http://www.youtube.com/watch?v=vOvQCPLkPt4. 。

相比之前看到的阈值而言,60-Hz,即 16.67 毫秒,可不是一个多么长的时间。对于一些基本的计算密集型(CPU-bound)操作来说,这个时间是没问题的,但如果你还需要在磁盘搜索上花费 7ms,那你就不得不考虑下花费如何使用剩下的时间来做点其他的事儿,更何况图形系统还要占用一些剩余的时间。

所以 60 HZ 是一个极限值了么?尽管表 14.1 的最后一项不是 60 Hz,但受限于当前的软硬件水平,60 HZ 的确是极限了。虽然 60 HZ 能够保证动画非常流畅,但实际上仍然有提升的可能性。我们举个例子,当你用光笔(pen or stylus)在屏幕上进行绘制或者写作的时候,假设笔尖移动的速度大约是 5 cm/s(大约 2 英寸/每秒),而屏幕在 60 HZ 的刷新率下,暂且认为它会以每 16.7 ms 移动 1 mm 的速度延伸,屏幕渲染出来的“印记”和光笔的笔尖之间有着明显的距离差,这会产生一种“滞后”的感觉。当你在屏幕上拖拽物体的时候,也会产生同样的效果。假如你快速地移动指针设备(手指,鼠标等设备),系统实际上是无法做到精准追踪的效果。

幸运的是,对于鼠标的光标这一类物体实在是太小了,小到以至于它快速移动时,人眼根本无法精准定位,因此它带来的滞后感不易被察觉。但是要拖拽一个比较大的物体时,人眼就能察觉到这种滞后感。在当下科技水平无法从本质上解决这一实际问题时,充分利用人类感知的极限是一种保持响应能力的常见技术手段。

软件和 API

在 Mac OS X 和 iOS 中,大部分的人机交互都由高层级的框架完成,如 iOS 上的 UIKit,Mac OS X 上的 AppKit。这些框架读取用户的输入并将其转换成事件传递到程序中,同时它们还负责绘制用户界面。图 14.1 从一个较高的视角对图形相关的 API 进行了分层。

这些高层级框架提供了大量现成的控件,如按钮(buttons)、复选框(checkboxes)、表格(tables)和文本编辑器(text editors)。除个别控件外,它们中的绝大多数不光完备性强,还拥有快速响应的能力,所以它们的性能并没有什么问题。

这些控件基于 OS X 的 NSView 类和 iOS 的 UIView,它允许用户扩展视图的层次结构。一个视图(view)表示屏幕上的一个矩形区域,可用drawRect:方法渲染,可处理用户的输入事件。这些高层级框架主要通过 Core Graphic 的 API 进行绘制方面的工作,Core Graphic 有时候也被叫做 Quartz。

另外一个与图形相关的主要 API 是 OpenGL,它主要用于 3D 图形应用中,例如一些 3D 游戏,由于这套 API 拥有对图形加速硬件的访问权限,也常常被其他的 API 所使用。除此之外,还有一些处理图片或者视频的 API,如 Core Image 和 Core Video,以及视频播放相关的 AVFoundation 或 Quick Time X,最新的框架还包括了用于 2D 游戏开发的框架 SpriteKit 和用于处理 3D 图形的高级框架 SceneKit。

在显示列表(display list)被图像系统持有且直接驱动显像管(CRT)成像的时代,保留模式(retained-mode)即时模式(immediate-mode)的意义在于它们区分了两种不同风格的图形 API。在即时模式中,程序通过主动调用绘图 API 的方式进行渲染。而在保留模式中,程序不会主动调用绘图相关的 API ,而是通过更新 API 创建的对象这种方式来实现渲染的效果。

图 14.2 以三个几何图形为例来阐述二者之间的不同。图表上方是保留模式风格的 API,程序首先生成三个图形,这些图形会以某种数据结构或着数据库的形式被 API 所持有。如果想要改变粉色矩形的坐标,程序必须要记住该矩形,并且告诉 API 要移动该矩形。接着 API 就会担负起刷新屏幕并渲染该矩形到新的位置上的工作。图表下方是即时模式风格的 API,它没有保留模式那么复杂,它只需绘制两遍场景(scence)即可,粉色矩形一次是在旧的坐标上,一次是新的坐标上。

Quartz 和 OpenGL 这两个重要的图形 API 都是即时模式风格的 API,绘图命令可以直接发起并立即执行。虽然 OpenGL 将自己内部的 API 划分为了即时模式和保留模式,但从广义上来说,OpenGL 的 API 应该都是即时模式风格的 API。SceneKit 和 SpriteKit 属于保留模式风格的 API;你需要创建 nodes(节点)并将其添加到场景中,之后的过程中,我们只需要对节点进行操作即可。

乍一看,保留模式风格的 API 似乎更简单一些,尤其在图形对象的层级关系满足应用需求的时候。因此在许多场景中,一个简易的图形编辑器可以是这些 API 轻度封装后的产物。然而,现实中的大多数应用都有特定的应用场景和特定的数据模型,这也就是说通过业务对象模型(domain-model)和相关算法得到的图元也会缺乏共性。在这种情况下,我们可以选择继续抽象相关算法以便它能够处理不同类型的数据模型,也可以选择放弃保留模式,而投入到即时模式的怀抱中。

UIKIt/AppKit 是个混合体:视图本身是保留模式风格的 API,但是绘制的时候使用的是即时模式风格的 API, 想想 drawRect: 方法。在这种混合模式下,你可以定义视图的相关特性并在发出重新绘制视图的命令后,把视图的移动,缩放和相关变换的工作交给系统来完成,但你同样保留了灵活修改视图内容的能力。图 14.2 中的三个几何图形,既可以用三个独立的视图对象表示(保留模式),也可以用 drawRect: 方法统一绘制(即时模式)。

Core Animation 和上面的情况有点相似,我们会在后面用另外一种方式来讨论它。

Quartz 和 PostScript 图像模型

Quartz 看起来很像当今计算机环境下的产物,但它的前身可以追溯到施乐公司 Palo Alto 研究中心(PARC)的 Bravo 系统,后来面向打字机的 PostScript 页面描述语言(page description language)和 Quartz 走的很近,借助 NeXT 公司开发的视窗和绘图系统( windowing and drawing system) ,Adobe 公司的 PDF,以及阉割掉编程功能的 PostScript,Quartz 最终演变成了 Mac OS X 的 Quartz 框架。

PDF 和 Quartz 是基于 PostScript 的产物,而 PostScript 这门页面描述语言的一个特性就是它拥有精确定义的图像模型。PostScript 语言操作的对象会被定义成栅格图像(raster image),我们根据其来源将这些被操作的对象分成三种类型的图元:

  1. 栅格图像
  2. 路径(path),filled 类型或 stroked 类型
  3. 文本

这三种图元可以进行仿射变换,任意裁剪以及着色,在之后的版本中,还可以使用渐变色或其他非常量类型的着色方式。早期图像模型的构成方式决定了在栅格化之后用画家算法(Painter’s Algorithm)着色的结果,这意味着对于某个具体的像素点而言,后添加的图像模型会最终决定其着色方式,不论这个像素上之前的图像模型是什么样子的。Quartz 和当前版本下的 PDF 使用了新的图像构成方式,这种方式具备了 alpha 混合(alpha-blending)的能力,这意味着先添加的图像模型和后添加的图像模型会共同影响最终的生成结果。抗锯齿(anti-aliasing)技术可以看做是 alpha 混合的一种变体,它是将原始图像模型的边缘和其相对应的背景色进行了混合,从而产生抗锯齿的效果。

最重要的是在这种设计模式下,每个像素的信息都可以通过图像模型获取,而不依赖具体的实现方式,苹果公司非常认同并坚持这种方式。

在图像模型中,所有的对象都会被转换为 filled 类型的路径。文本字符通过编码映射转换成字形(glyph),然后再通过字体程序变成 filled 类型的路径。stroked 类型的路径可以理解为由自身轮廓线围成的 filled 类型的路径;路径的顶点样式(cap)和拐点样式(line)也会以几何图形的形式添加进路径中。所有曲线在高分辨率下会被转换成一个个连接着的直线段,之后就是栅格处理了。

平整的栅格图像是由矩形网格组成的,所以栅格图像本身也可以像其他矩形一样进行缩放和旋转。举个例子,我们可以绘制一个编号从 0 到 255 号, 256 像素宽、1 像素高的栅格图形,它的效果与我们绘制一个灰度值介于 0 到 1 之间,共有 256 个矩形的效果一样。事实上,将图像进行栅格化就是一个渐变过程,这种渐变和我们常说的渐变有一些不同:用一个路径(clip path)描绘出需要绘制的形状,将对应的图像绘制到这个形状中。

图 14.3 用 Times Roman 字体的小写字母 a 来演示上面的内容。

注意这个转换步骤的每一步都会导致数据量的增加,有时候增长的非常显著。字母本身可以用一个字节表示,例如 ASCII 码中 97 就代表小写字母 a。字形也可以是表明某个特定字体的数字或名称。然而,字母 a 的轮廓有 3 个直线段和 34 个曲线段组成,总计 210 个浮点坐标,将曲线拟合成直线会成倍地增加坐标数量,当然这主要取决于分辨率(在当前的测试环境下会生成 484 个坐标点)。最终,网格化后的字形有 24 point 的尺寸,在 retina 屏幕下会产生大约 2 KB 的黑白栅格数据,或者带有 alpha 信息的 8 KB 的全彩色栅格数据。如果你还想使用 super-sampling 技术进行抗锯齿处理,那么栅格属性还会继续增大。

在现实生活中我们完全可以不这样做,也不应该这样做,否则文本和光栅图形的处理会非常慢。虽然精准的图像模型能够更好的还原图像本身,但这也意味着我们需要能够保存更多信息的图像模型。

OpenGL

Mac OS X 平台上的第二个基础图形 API 就是 3D OpenGL API(iOS 上也叫 OpenGL ES)。OpenGL 最早是由 Silicon Graphics 开发出来的,它是一种与语言无关的、跨平台的 3D 图形 API,但是在后来它变成了一个开源标准。远远超越了现在的 PHIGS 开源标准,而其中的原因就是 OpenGL 与 Postscript,Quartz 一样,都是即时模式风格的图形框架,相比较与 PHIGS 这种保留模式风格的图形框架,OpenGL 的使用更加简单。

OpenGL 和 Quartz 支持的图形模式和图元大不相同,OpenGL 可以通过顶点数组(vertex array)实现对多边形(polygon meshe)、折线(polyline)或点云(point cloud)使用。在 OpenGL 中,不能直接使用图像(image)进行渲染,但可以通过在物体表面覆盖纹理贴图(texture)的方式达到同样的效果。另外 OpenGL 不支持文本类型,所以它会将文本转换成多边形或者位图。

尽管绘制图像本身是即时模式的,但 OpenGL 也可以将纹理贴图或顶点数组上传到图形硬件中,以便图形硬件在之后可以多次使用。在 OpenGL 的领域内,这就是保留模式。这将保留模式与另外一种数据被逐步指定的模式区分开来。

OpenGL 有这样一个问题,它自身提供的是一种过程式(procedural)/返回式(call return)的 API,这使得 OpenGL 与更加面向批量处理的现代化图形硬件格格不入。这种格格不入体现在,看起来非常“自然”的 API 并不高效,而高效的 API 看起来非常别扭且容易出错。

Metal

针对 OpenGL 与现代化图形硬件格格不入的情况,苹果公司给出了自己的解决方案。那就是 Metal API。与 Khronos 组织的 Vulcan API 非常相似, Metal 是一个面向底层的 API。

在 Metal 的世界中,应用程序不仅管理着命令缓冲区(comman buffer),也负责将缓冲区中的指令发送到 GPU 中,这与让程序直接调用 API 中去更新状态或者绘制视图的方式不太一样。

图形硬件加速

尽管计算机图形学中的一些术语,如 “Display List(显示列表)”会让我们回想曾经还使用过向量显示器(vector display)(光束在 CRT 的作用下可以绘制出特定的线段),但事实上,现在所有的显示器都是栅格显示器。栅格显示器定义由图像元素(像素)组成的矩形网格,这就像栅格图像和原始图像的关系。

虽然现代固态显示器经历了诸如 LCD、OLED、等离子屏幕等技术的发展,但最终显示的像素矩阵仍然是由硬件预设:这也就是说在硬件上的每一个独立元素会对应着帧缓冲区(frame buffer)的每一个元素。

针对前面所说的情况,即使考虑到视网膜(retina)屏幕的存在。我们需要提供的像素数量也可以说是相对稳定的,iPhone 屏幕的像素数量在七十万到二百万之间,iPad 屏幕需要的像素数量接近三百万,iPad Pro 或者笔记本电脑的像素数量接近五百万。因此无论应用程序多么复杂,只要在每次刷新的过程中能够处理这么多的像素数,就代表你可以改变整个屏幕的内容。在大 O 表示法中,它的复杂度是 O(k)。

虽然像素的数量是一个常量,我们从算法复杂度的角度上来说可以忽略它的存在,但由于像素的数量非常大,在实际的计算过程中我们还是无法完全忽视它。大多数情况下,绘制图形是当今非服务器型(non-server)计算机中计算量最大的任务。

随着时间的推移,市面上出现了许多能够增强图形计算的硬件架构,最常见的几种如图 14.4 所示。架构(1)中没有任何额外的硬件支持,CPU 在内存中进行绘制,而且内存还会被当做用于刷新屏幕的帧缓冲器。

CPU 绘图模型(CPU-drawing model)的优点是简单通用。从 Xerox Alto 到 Apple II,以及早期的 Macintosh 和 NeXT 机器,都使用了这个模型。Quartz 就是使用 CPU 绘图模型的一个例子 ,即 Quartz 操作 CPU 在内存中进行绘制。

从几何计算到绘制像素中,图形处理器(graphics processing units, GPUs)可以在绘图的各个过程中发挥作用。在架构(2)中,独立出来的 GPU 和 CPU 一样,都可以直接访问内存,这种架构在二十世纪八九十年代的家用电脑中非常普遍,不过现在已被弃用。

如今,图形硬件的架构大体可以分为两类,一种是架构(3)中的独立显卡模式,GPU 配有独立的显存(VRAM);另一种是架构(4)中的集成显卡模式,GPU 和 CPU 在同一个芯片上,通过公用总接线口访问同样的 RAM。

特定硬件协助通用硬件更好的完成工作,这种模式似乎看起来有点像历史的重演,但随着这种特定硬件的普及,现在的 GPU 还可以用在 OpenCL 的计算中。例如英特尔的 “Larrabee” 显卡由大量的通用 x86 内核组成。最终,集成显卡的架构会越来越像最初的 CPU 配置,可以看做是前者的一种变体。

尽管这些架构看起来大同小异,但在性能上还是存在不小的差异。举个例子,如图 14.5 所示,我在 2D 平面下绘制了许多三角形进行测试,测试设备是一台 13 英寸、拥有视网膜显示屏、英特尔 HD Graphics 4000 集成显卡的 MacBook Pro。

首先我尝试用 OpenGL 进行实验,显而易见,我们将用到图形硬件,详情见示例 14.1

示例 14.1 用 OpenGL 进行三角形绘制的基准测试代码

-(void)drawRect:(NSRect)dirtyRect
{
int iterations=10000;
glClearColor(1.0, 0.5, 0.5, 0.5);
glEnable (GL_BLEND);
glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glPushMatrix();
glScalef(0.4, 0.4, 1.0);
for (int i=0;i<iterations;i++) {
float a = -0.5 + (float)i / (float)iterations;
glColor4f(1.0f, 0.85f, 0.35f + a,0.4);
glBegin(GL_POLYGON);
{
a*=2;
glVertex3f( 0.0 + a, 0.6, 0.0);
glVertex3f( -0.2 + a, -0.3, 0.0);
glVertex3f( 0.2 + a , -0.3 ,0.0);
glVertex3f( 0.0 + a, 0.6, 0.0);
}
glEnd(); }
glPopMatrix();
glFinish();
}

在执行效率上,示例 14.1 里 OpenGL 的代码比示例 14.2 里 Quartz 的代码快了十多倍,我在一台 2007 年产,拥有独立显卡的 Mac Pro 上看到了明显的对比结果。

示例 14.2 用 Quartz 进行三角形绘制的基准测试代码

-(void)drawRect:(NSRect)dirtyRect
{
int iterations=10000;
CGContextRef context=[[NSGraphicsContext currentContext]
graphicsPort];
CGContextSetRGBFillColor(context, 1.0,0.5,0.5,1.0 );
CGContextAddRect( context, dirtyRect);
CGContextFillPath( context );
CGContextScaleCTM( context, [self frame].size.width,
[self frame].size.height );
CGContextTranslateCTM( context, 0.25 , 0.5);
CGContextScaleCTM( context,0.2, 0.2);
for (int i=0;i<iterations;i++) {
float a = (float)i / (float)iterations;
CGContextSetRGBFillColor(context, 1.0f, 0.85f, 0.35f + a,0.4);
a*=2;
CGContextMoveToPoint( context, 0.0 + a, 0.6);
CGContextAddLineToPoint( context, -0.2 + a, -0.3);
CGContextAddLineToPoint( context, 0.2 + a , -0.3);
CGContextClosePath(context);
CGContextFillPath( context );
}
}

在使用 Quartz 时,CPU 已经达到了百分之百的利用率,而在使用 OpenGL 时,将对 GPU 操作的指令传递到命令缓冲器(command buffer)后,CPU 就空闲下来,等待这些命令执行完毕。上面两种情况都符合低功耗 CPU 的标准。

GPU 不仅在架构上拥有一定的优势,它还可以在图形计算中充分发挥自身并行计算的能力,这是以芯片上有大量的晶体管为前提做保障的。虽然 CPU 也有相似的晶体管数量,但是 CPU 却并不具备相同的并行能力。

所以,相比于 CPU 设计者需要采取更加精密的方案才能让 CPU 在执行大量串性指令的时候得到小幅的性能提升,而利用额外的硬件资源就会让整体性能的提升近似于线性增长,例如让 GPU 分担一部分的计算量。GPU 和 CPU 在处理串行指令上的区别是造成它两性能差距的主要原因之一(在适当的计算负载下),但是 GPU 的性能提升曲线更为陡峭,意味着 CPU 和 GPU 的性能差距每年都会扩大。

从 Quartz 到 Core Animation

Mac OS X 和 iOS 的图像 API 最开始只有基于 CPU 渲染的 Quartz,随后 Quartz Extreme,Quartz GL,以及 Core Animation 等图像框架逐渐加入到这个大家庭中。驱动这些 API 演变的不只是基于 OpenGL 的游戏,还有充分利用现代 GPU 潜能进行系统级别图形渲染的目标。

让 Quartz 利用 GPU 进行绘制的最直接方式就是将 Quartz 的图元映射为 GPU 的命令。这个方案的问题在于图形硬件实现了与 OpenGL 兼容的图元,而 OpenGL 的图元与 Quartz 的图元并不兼容。OpenGL 并没有 filled path 类型的图元;它只有三角形或四边形组成的多边形网络,并没有曲线的概念,与 stroke path 类型的图元相对应的是折线。尽管这种映射是可行的,例如我们将 filled path 转换为多边形,但这种转换的代价相当大。另外,OpenGL 对渲染模型的定义非常松散(loosely),也就是说,这意味着硬件在解释命令方面有很大的回旋余地。这种松散的特性不仅与 Quartz 严格定义的成像模型冲突,也和苹果公司对图形质量的要求相冲突。

然而,有一个图元可以很容易的进行转换,那就是栅格图像(raster image)。有一个名为窗口管理者(Window Mnanager)的隐藏子系统会处理栅格图像,它允许不同进程所拥有的窗口对象可以与单个物理屏幕进行映射,这样每个进程可以只关注自己的窗口对象,无需关注其他进程的操作。

由于窗口管理者的存在,不管是即时模式还是保留模式,Mac OS X 上的每个图形 API 最后都变成了保留模式的 API,具有窗口位图和保留状态,具备保留模式 API 的所有优势:窗口可以随意移动,移动的过程可视化,完全由 Window Manager 控制。这和之前的视窗系统(windowing systems)不同,也不像是 AppKit 或 UIKit 中的视图层级结构,无需客户端代码来重绘显示的部分,因此即使进程不响应,窗口的操作总是非常平滑的。

在 Mac OS X 10.2 引入了 Quartz Extreme 库,这个库为系统添加了一些图形硬件加速的能力。系统中的每个窗口都变成了 OpenGL 里的矩形,窗口的内容(由 Quartz 或其他图形 API 提供)都变成了 OpenGL 的纹理,这些纹理被映射到了相应的矩形中。每个窗口的内容都由合适的 API 进行绘制,然后再使用 OpenGL 和图形硬件将内容整合到位图窗口(bitmap window)中。

这种改变不仅更好地利用了显卡的性能,对于许多操作而言,也完全消除了 CPU 的负载:为了移动窗口,视窗管理者(window server)只需改变矩形的坐标,想要将窗口置前或置后,只需调整矩形的 z 值。

Core Animation 将这种架构从 Window Server 引入到客户端程序中,每个 CALayer 不再是绘制到一个共享的后台存储(shared backing store)里,而是维护自己的栅格图像,然后通过硬件支持的单独进程(iOS 上的渲染服务器)将这些图像组合在一起。其实,每个 CALayer 就像是 Quartz Extreme 中的窗口,我们可以改变 CALayer 的位置、透明度和旋转等属性,通过操作 CALayer 映射的 OpenGL 图元的几何形状,我们也可以将实际的位图组合加载到图形硬件中。

图 14.6 展示了从 Window Server,扩展了 Quartz Extreme 的 Window Server,到 Core Animation 的发展过程。如您所见,硬件加速的增长和图形架构中后置缓冲区(backing buffer)的增长相对应。在图形资源极端的情况下,除了解码这些资源外,根本就没有软件渲染;整个管道的其余部分都是硬件加速的。所谓的开销就是内存,资源的分辨率,以及图片资源解码所需的时间,这可能也是最重要的。

就像独立 Window Server 进程能够让窗口的操作平滑过渡一样,这种架构也可以在任意的客户端达到动画运行平滑的效果。一旦设置完所有的图层,动画就会独立于调用程序而运行,它会在一个单独的进程中执行,并且能够得到硬件的支持。

这是一种极其不明显的性能优化方法,因为该方法的基础是处理最宝贵的图元,即栅格图像。栅格图像使用了大量的内存和内存带宽,并用于最后的图像合成。在其他条件相同的情况下,Core Animation 的架构会导致速度变慢,但是实际上其他的条件不可能相同,因为这些操作可以从图形硬件中获得不少好处。

另外,我们在简单性和相应的可预测性上获得了收益,使得操作可以单独执行,只需给图形硬件发送指令流即可。

大大减少了 CPU 的工作,将不同的层复合在一起,为这些组合创建动画空间。实际上动画由独立的进程控制,在高性能的 GPU 上执行,这意味着比起客户端程序,该系统可以保证更高的平滑度和性能。

性能保证了动画可以成为用户体验的核心,除了能够让用户体验更加“立体”和真实之外,动画还能极大地提高感知响应性。

如果用户的操作触发了一个动画,即使系统没有给出最终的结果,用户也会认为这个应用是具有响应性的。只要找到合适的动画效果,这个动画所花费的时间都可用于处理用户的操作。

总结

在本章中,我们主要了解了应用程序响应用户操作的基本心理学,学习了图形编程的特点和需要权衡的问题,接着我们还了解了图形硬件和 Mac OS X 以及之后 iOS 的图像 API 的协同进化史。

这种协同进化给我们留下了许多的关于图形编程的方法和需要权衡的地方,我们将在后面的章节进行详细的探讨。