MLAA:将抗锯齿从 GPU 高效地转移到 CPU





0/5 (0投票)
高效的抗锯齿技术是高质量、实时渲染的重要工具。英特尔一项名为形态抗锯齿(MLAA)的新技术解决了标准技术MSAA的局限性。本文讨论了MLAA的实现。包含代码示例和免费下载。
由 Intel® Visual Computing Developer Community 赞助
引言
高效的抗锯齿技术是高质量、实时渲染的重要工具。MSAA(多重采样抗锯齿)是当前的标准技术,但存在一些严重的缺点
- 与延迟着色不兼容,后者在实时渲染中应用越来越广泛;
- 高内存和处理成本,这使得它在一些广泛可用的平台上(如索尼PlayStation* PS3* [Perthuis 2010])无法使用。此成本也与渲染场景的复杂性直接相关;
- 除非与 alpha 到 coverage 结合使用,否则无法平滑非几何边缘。
英特尔实验室开发的一项新技术,称为形态抗锯齿(MLAA)[Reshetov 2009],解决了这些局限性。MLAA是一种基于图像的后期处理滤波技术,它识别不连续模式,并对这些模式周围的颜色进行混合,以实现有效的抗锯齿。它是新一代实时抗锯齿技术的先驱,可与MSAA相媲美[Jimenez et al., 2011] [SIGGRAPH 2011]。
此示例基于 Reshetov 提供的原始、基于 CPU 的 MLAA 实现,并进行了改进以大大提高性能。改进包括
- 集成了基于 Intel® Threading Building Blocks (Intel® TBB) 的新型、高效、易于使用的任务系统。
- 集成了用于 CPU 卸载图形任务的新型、高效、易于使用的流水线系统。
- 通过新的转置传递改进了数据访问模式。
- 增加了 Intel® SSE 指令的使用,以优化不连续检测和颜色混合。
MLAA 算法
本节介绍 MLAA 算法的工作原理概述;有关详细解释,请参阅 [Reshetov 2009]。概念上,MLAA 分三个步骤处理图像缓冲区
- 在给定图像中查找像素之间的不连续点。
- 识别 U 形、Z 形、L 形图案。
- 混合这些图案周围的颜色。
第一步(查找不连续点)通过将每个像素与其邻居进行比较来实现。水平不连续点通过将像素与其下邻居进行比较来识别,垂直不连续点通过与其右邻居进行比较来识别。在我们的实现中,我们比较颜色值,但任何其他适合应用程序特定性的方法都是完全有效的。
在第一步结束时,如果检测到水平不连续标志和/或垂直不连续标志,则每个像素都会被标记。在下一步中,我们“遍历”标记的像素以识别不连续线(由具有相同不连续标志的连续像素组成的序列),并确定它们如何组合形成 L 形图案,如下图所示
第三步,也是最后一步,是对每个已识别的 L 形图案执行混合。
基本思路是将 L 形的主段(图中绿色水平线)的中间点连接到次段(绿色垂直线)的中间点(连接线为红色)。连接线将每个像素分成两个梯形;对于每个像素,相应梯形的面积决定了混合权重。例如,在下图所示中,我们看到像素 c5 的梯形面积为 1/3;因此,c5 的新颜色将计算为 2/3 *(c5 的颜色)+ 1/3 *(c5 的下邻居的颜色)。
实际上,为了确保平滑的轮廓外观,我们需要最小化连续 L 形连接点处的颜色差异。为此,我们根据连接点周围像素的颜色,在 L 形段的连接点上进行微调。
示例用法
可以通过拖放鼠标在场景周围移动相机,并可以使用鼠标滚轮进行放大或缩小。此外,样本窗口的右侧有三个控件块
第一个控件块控制场景的渲染
- 暂停场景 切换场景动画的开关;
- 显示缩放框 切换缩放框功能的开关,该功能允许用户近距离查看像素区域,以便更好地比较抗锯齿技术。可以通过右键单击新区域来更改感兴趣的区域;
- 场景复杂度 通过使用重叠绘制来模拟增加场景复杂度的效果。该值(在 1 到 100 之间,由滑块调整)表示每帧渲染场景的次数(带有重叠绘制)。
第二个控件块选择要应用的抗锯齿技术:MLAA、MSAA (4x) 或无抗锯齿。这当然是为了能够比较技术的性能和质量(默认选择是“MLAA”);
最后一个控件块仅在使用的抗锯齿技术是 MLAA 时可用,并控制算法的运行方式
- 仅复制/映射/取消映射 将颜色缓冲区从 GPU 内存复制到 CPU 内存再返回,但在两次复制操作之间不执行任何 MLAA 处理。这允许测量复制操作对整个算法总体性能的影响;
- CPU 任务流水线 切换 CPU 卸载图形任务的流水线系统(默认为开启),以便轻松查看流水线的好处;
- 显示找到的边缘 运行算法的第一部分(查找像素之间的不连续点),但混合传递被调试传递替换,其中像素被
- 更改为纯绿色,如果找到了与其邻居之间的水平不连续点;
- 更改为纯蓝色,如果找到了与其邻居之间的垂直不连续点;
- 更改为纯红色,如果找到了与其邻居之间的水平和垂直不连续点;
- 如果未找到不连续点,则保持不变。
样本架构
如果不进行流水线优化,每一帧的事件顺序是
动画并渲染测试场景
MLAA 阶段(如果启用了 MLAA)
- 将渲染场景的颜色缓冲区复制到暂存缓冲区
- 映射暂存缓冲区以供 CPU 访问。
- MLAA 后处理(暂存缓冲区既是输入也是输出)
- 取消映射暂存缓冲区
- 将暂存缓冲区复制回 GPU 端颜色缓冲区
- 渲染缩放框(如果适用)
渲染样本的 UI,呈现帧
除了“执行 MLAA 后处理工作”步骤(暂时不考虑流水线)之外,所有这些步骤都是使用标准的 Microsoft DirectX* 方法实现的。
任务 API
MLAA 算法本质上易于并行化。对于不连续检测和混合传递,我们可以处理独立的颜色缓冲区块(连续行或列的块)。MLAA 可以使用基于任务的解决方案实现,该解决方案可以自动充分利用所有可用的 CPU 核心,同时保持代码与核心数量无关
此示例使用一个简单的 C 语言任务 API,该 API 基于 Intel® Threading Building Blocks (Intel® TBB) 调度器实现。创建此包装器 API 的目的是简化该技术在已公开类似任务 API 的现有代码库(例如跨平台游戏引擎)中的集成。另一个好处是提高了主源文件MLAA.cpp的可读性。
包装器 API 的两个重要函数是
并且
回调函数具有以下签名
.MLAA 需要一个由三个连续任务集组成的依赖图
- 第一个任务集查找颜色缓冲区中像素之间的不连续点;
- 第二个任务集执行水平混合传递,并依赖于第一个任务集的不连续信息;
- 第三个任务集执行垂直混合传递,它依赖于第一个任务集的断点信息,还依赖于第二个任务集的转置优化(有关详细信息,请参阅相应部分)。
此依赖图在MLAA.cpp中表示为对CreateTaskSet
的三个连续调用;任务集回调函数(实现在MLAAPostProcess.cpp中)分别为MLAAFindDiscontinuitiesTask
、MLAABlendHTask
和MLAABlendVTask
。
如果未启用流水线,我们将通过调用最后一个任务集的句柄的WaitForSet
来等待最后一个任务集完成。当调用返回时,帧的 MLAA 工作就完成了。使用流水线时,情况会稍微复杂一些。
CPU/GPU 流水线
为了从我们的实现中获得最大性能,我们必须使 CPU 和 GPU 两侧都得到充分利用。由于任务集之间存在数据依赖关系,因此可以通过交错处理多个帧来实现完全利用,如下面的图所示
换句话说,我们必须构建一个流水线系统,其中流水线是 CPU 阶段(工作负载)和 GPU 阶段的序列,并且能够同时运行该流水线的多个实例。
在我们的案例中,流水线的每个实例对应于一帧的处理。我们有三个步骤
步骤 1(GPU 阶段)
- 动画并渲染测试场景;
- 将颜色缓冲区复制到暂存缓冲区(使用异步 GPU 端 CopyResource)。
步骤 2(CPU 阶段)
- 映射暂存缓冲区;
- 执行 MLAA 后处理工作(暂存缓冲区既是输入也是输出)。
步骤 3(GPU 阶段)
- 取消映射暂存缓冲区并将其复制回 GPU 端颜色缓冲区;
- 完成帧渲染(缩放框、UI)并呈现。
为了实现这个概念,我们设计了一个简单的 Pipeline 类。每个阶段都由一个 PipelineFunction
结构表示,该结构指定要调用的函数和阶段类型。回调函数必须具有以下签名
其中 uInstance
表示正在调用函数的是流水线的哪个实例。GPU 阶段使用 DirectX* 查询(类型为 D3D11_QUERY_EVENT
)来发出其完成信号,而 CPU 阶段必须显式调用 Pipeline 类的 CompleteCPUWait 方法来发出完成信号
创建流水线实例需要调用一次 Init
方法。在我们的案例中,代码如下
其中 g_NumPipelineInstances
是要创建的流水线实例的数量(在此案例中为 3)。
为了运行流水线,我们每帧调用一次 Start 方法
由于 Start 方法返回的是已完成的流水线实例的索引,因此最后一个步骤不必通过 Pipeline
类调用。最后一个步骤的代码在调用 Start 方法后立即执行,并使用 Start 返回的索引对数据结构进行索引。
流水线不依赖于任务 API 的 WaitForSet 调用,因为它会阻塞,因此不允许发生流水线。解决方案是使用*完成任务集*——即,一个依赖于所有 MLAA 任务完成的任务,其唯一工作将是调用 CompleteCPUWait 方法。
Intel® SSE 优化
MLAA 算法的第一遍查找像素之间的不连续点。概念上,每个像素会检查其颜色并将其与其下邻居(在查找水平不连续点时)或右邻居(在查找垂直不连续点时)进行比较 [Reshetov09, section 2.2.1]。
在此示例中,我们保留了参考实现中简单的不连续检测内核。如果两个像素的至少一个 RGB 颜色分量相差至少 16(在 RGBA8 格式的 0-255 范围内),则它们之间存在不连续点。
此定义在此示例中效果很好,并且允许非常紧凑高效的 SIMD 实现。然而,更复杂的方法是可能的,并且有时是必需的,以获得最佳可能的结果。例如,像素的亮度可以代替直接比较颜色值,并且可以每帧重新计算可变阈值,以考虑场景的整体亮度和对比度 [Luminance]。可以使用深度值来辅助边缘检测或任何自定义数据,以将颜色缓冲区的特定区域排除在处理之外。
由于此步骤独立于算法的其余部分,并且我们在 CPU 上运行,因此程序中的任何数据都可以直接用作输入数据。检测算法的唯一限制是
- 算法必须为每个像素输出一个位,指示是否检测到不连续点;
- 性能与质量/复杂性之间的权衡;
- 程序员的想象力。
与原始参考实现 [Reshetov09, section 2.4] 一样,我们使用 RGBA8 渲染目标格式,MLAA 计算的“垂直不连续”和“水平不连续”位标志会就地存储在像素数据的两个高位(即,alpha 分量的两个高位,MLAA 混合操作不受影响)。这保持了实现的简单性,同时有助于减少内存占用,并允许在算法的后续步骤中进行优化(参见下面的“转置优化”部分)。
由于每个像素是 32 位数据,并且在此步骤中每个像素都可以独立于其他像素进行处理,因此我们可以使用 Intel® SSE 内在函数一次处理 4 个像素。检测代码非常简短。
参考实现然后继续更新每个像素的 alpha 分量,但使用了效率低下的串行代码。我们可以使用以下 Intel® SSE 代码来优化此序列
我们还替换了 MixColors 函数(它计算两种颜色的线性插值),使其使用完整的 Intel® SSE 实现。
转置优化
算法的下一个任务是查找“不连续线”,即在遍历颜色缓冲区的行和列时,标记有相同不连续标志(水平混合传递为水平标志,垂直混合传递为垂直标志)的连续像素序列 [Reshetov09, section 2.1]。
由于不连续线往往很短,直觉表明(并且性能分析数据证实)此操作中最昂贵的部分是扫描不连续线*之间*的区域,即扫描没有设置不连续标志的大面积连续像素。
好消息是,当满足以下条件时,我们可以使用 _mm_movemask_ps Intel® SSE 内在函数一次检查 4 个像素
- 我们正在扫描存储在连续地址中的 4 个像素;
- 第一个像素的地址是“Intel® SSE 对齐”(16 字节对齐);
- 不连续标志存储为 32 位像素数据的最高位。
在水平混合传递期间,(1) 为真(我们在表示为二维像素数据线性数组的颜色缓冲区中扫描像素的水平行);(2) 几乎所有时候都为真(请记住,16 字节对齐相当于“缓冲区中起始像素的索引是 4 的倍数”,因为缓冲区已正确对齐);(3) 为真,因为我们选择位 31 来表示水平不连续标志。
如果所有条件都为真,我们计算标志
根据 HFlags 的值,可能有五种结果
- 0(迄今为止最常见的情况):此 4 个像素组中没有设置不连续标志,移至下一组 4 个像素。
- 设置了位 0:不连续线从该组的第一个像素开始。
- 设置了位 1:不连续线从该组的第二个像素开始。
- 设置了位 2:不连续线从该组的第三个像素开始。
- 设置了位 3:不连续线从该组的第四个像素开始。
此优化是参考实现的一部分,对于水平混合传递效果很好,但对于垂直混合传递则无法应用。由于代码是垂直扫描缓冲区,(1) 为假(列中的相邻像素未存储在连续地址中),(3) 也为假(垂直不连续标志存储在像素数据的位 30 中)。
通过引入简单的“左移一位”操作来处理(3)的问题,如果我们正在处理垂直标志,则将代码转换为
但是 (1) 仍然是一个问题。此外,垂直扫描对缓存非常不友好。由于这两个问题,在参考实现中,垂直混合传递的成本是水平混合传递的 3 倍(300%!)(如性能分析数据所示)。
我们解决此问题的办法是使垂直混合传递使用水平混合传递的缓存和 Intel® SSE 友好的数据访问模式,方法是将颜色缓冲区视为像素矩阵,并在传递之间对其进行转置
- 执行水平混合传递
- 转置(水平混合的)颜色缓冲区
- 执行垂直混合传递
- 将颜色缓冲区转置回
这样,两个混合传递的代码就完全相同了(增加了简单性/可读性的优势),唯一的区别是扫描哪个标志,并且我们受益于水平传递的所有优化和缓存友好性。
实际上,转置操作不是作为单独的传递/任务集实现的,而是作为各自混合传递的最后一部分。这使我们能够受益于缓存的“预热”。
这两个转置操作并非免费。成本是额外的代码执行时间、一个额外的工作缓冲区以及水平和垂直传递之间的一个同步点(我们必须等待*所有*水平任务完成,然后才能开始*任何*垂直任务,因为在开始垂直传递工作之前,我们必须等待颜色缓冲区完全转置)。
即使有这些额外成本,总体性能也明显优于先前的方法。正如预期的那样,性能分析数据显示,两种混合传递都具有同等的性能。
性能结果
MLAA 性能在以下两种配置上进行了测量
- 代号“Huron River”:Intel® Core™ i7-2820QM 处理器(Intel® 微架构代号 Sandy Bridge,4 核 8 线程 @2.30 GHz),配 GT2 处理器图形,4 GB RAM,Windows 7 Ultimate 64 位 Service Pack 1
- 代号“Kings Creek”:Intel® Core™ i5-660 处理器(代号“Clarkdale”,2 核 4 线程 @3.33 GHz),配 GMA HD 图形(代号“Ironlake”),2 GB RAM,Windows 7 Ultimate 64 位 Service Pack 1
我们测量了样本的平均帧渲染时间与场景复杂度以及不同抗锯齿设置的关系。我们还测量了仅复制/映射/取消映射模式的渲染时间,以突出颜色缓冲区复制操作对算法总体性能的影响。
结果
数据显示,对于 Huron River 机器,MSAA 4x 的额外成本随场景复杂度线性增加(实际上,对于所有分辨率,使用 MSAA 4x 渲染我们测试场景时的帧时间大约是没有抗锯齿时的两倍)。相比之下,MLAA 的成本似乎或多或少恒定(在 1280x800 分辨率下约为 4 ms/帧)。这与我们的预期一致,因为与 MSAA 4x 不同,MLAA 每帧只执行一次,与场景复杂度/调用次数无关。
我们还发现,除了非常低的复杂度值(即小于约 5)之外,MLAA 始终优于 MSAA 4x,无论分辨率如何,并且性能差异随复杂度增加而增大(因为如上所述,MSAA 4x 的成本随复杂度线性增长,而 MLAA 的成本则不然)。
在 Kings Creek 机器上,我们无法比较 MLAA 与 MSAA 4x 的成本,因为后者不被 Ironlake 硬件提供。目标是确定 MLAA 是否能提供具有可接受性能的软件抗锯齿替代方案。我们在 1280x800 分辨率下的测量结果表明,MLAA 的成本在很大程度上与场景复杂度无关,平均值为约 7.5 ms(剔除了复杂度 = 100 时的异常数据点)。
有趣的是,如果我们将其结果与一个性能特征与 Huron River 机器相似的假设 MSAA 4x 实现(帧时间 MSAA 4x 约等于没有抗锯齿时帧时间的 2 倍)进行比较,我们会发现 MLAA 在几乎所有复杂度值(在此情况下 ≥4)下都优于 MSAA 4x。
参考文献
[Reshetov 2009] RESHETOV, A. 2009. “Morphological Antialiasing”
[Perthuis 2010] PERTHUIS, C. 2010. MLAA in God of War 3. Sony Computer Entertainment America, PS3 Devcon, Santa Clara, July 2010.
[Jimenez et al., 2011] JIMENEZ, J., MASIA, B., ECHEVARRIA, J., NAVARRO, F. and GUTIERREZ, D. 2011. Practical Morphological Anti-Aliasing. In Wolfgang Engel, ed., GPU Pro 2. AK Peters Ltd.
[SIGGRAPH 2011] JIMENEZ, J., GUTIERREZ D., YANG, J., RESHETOV, A., DEMOREUILLE, P., BERGHOFF, T., PERTHUIS, C., YU, H., MCGUIRE, M., LOTTES, T., MALAN, H., PERSSON, E., ANDREEV, D. and SOUSA T. 2011. Filtering Approaches for Real-Time Anti-Aliasing. In ACM SIGGRAPH 2011 Courses.
[Luminance] Definition of luminance for CRT-like devices
INTERNATIONAL COMMISSION ON ILLUMINATION. 1971. Recommendations on Uniform Color Spaces, Color Difference Equations, Psychometric Color Terms. Supplement No.2 to CIE publication No. 15 (E.-1.3.1), TC-1.3, 1971.
And for LCDs
ITU-R Rec. BT.709-5. 2008. Page 18, items 1.3 and 1.4