注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

Crayon

只想做自己

 
 
 

日志

 
 

ARM Mali GPU  

2015-07-26 10:30:23|  分类: 开发 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

Mali GPU: 抽象机器,第一部分 – 帧管线化


图形工作负载的优化对于许多现代移动应用程序而言往往必不可少,因为几乎所有渲染现在都直接或间接地由基于 OpenGL
ES 
的渲染后端负责处理。我的同事 Michael McGeagh 最近发表了一篇工作指南 [http://community.arm.com/docs/DOC-8055],介绍如何将 ARM?DS-5? Streamline? 性能分析工具用于Google Nexus 10,对利用Mali?-T604 GPU 的图形应用程序进行性能分析和优化。Streamline 是一款强大的工具,能够深入细致地洞悉整个系统的行为,但也需要驾驭它的工程师能够解读相关数据,识别问题区域,进而提出修复建议。


对于初涉图形优化的开发人员而言,起步阶段总会遇到一些困难,所以我写了新的系列博文,给开发人员提供必要的知识,以便他们能够成功地针对 Mali GPU进行优化。在整个系列博文中,我将阐述开发人员必须要考虑的基本宏观体系结构和行为、这些因素如何转化为能被内容触发的潜在问题,以及最终如何在
Streamline 
中找出这些问题。

 

抽象渲染机器


要想成功分析应用程序的图形性能,必须先掌握一个最基本的知识,也就是对 OpenGL ES API 底下系统运作方式建立一个心智模型,让工程师能够推断他们观察到的行为。


为避免让开发人员陷于驱动程序软件和硬件子系统的实施细节的沼泽之中(这些他们无法控制,因而价值有限),有必要定义一个简化的抽象机器,用作解读所观察到的行为的基础。这一机器包含三个有用部分,它们大体上是独立不相干的,所以我将在本系列博文的开头几篇中逐一介绍。不过,为了让你对它们有个初步印象,下面列出该模型的三个部分:

  • CPU-GPU 渲染管线
  • 基于区块的渲染
  • 着色器核心架构

在本篇博文中,我们将探讨第一个部分,即 CPU-GPU 渲染管线。

 

同步API,异步执行


务必要了解的一个基本知识是,OpenGL ES API 上应用程序函数调用和这些 API 调用所需渲染运算的执行之间的临时关系。从应用程序的角度而言,OpenGL ES API 被指定为同步 API。应用程序进行一系列的函数调用来设置其下一绘制任务所需的状态,然后调用 glDraw[1] 函数(通常称为绘制调用)触发实际的绘制运算。由于 API 是同步的,执行绘制调用后的所有 API 行为都被指定为要像渲染运算已经发生一样进行,但在几乎所有硬件加速的 OpenGL ES 实现上,这只是一种由驱动程序堆栈维持的美妙假象。

与绘制调用相似,驱动程序维持的第二个假象是帧末缓冲翻转。大多数头一次编写 OpenGL ES 应用程序的开发人员会告诉你,调用 eglSwapBuffers将交换其应用程序的前缓冲和后缓冲。虽然这在逻辑上是对的,但驱动程序再一次维持了同步性的假象;在几乎所有平台上,实际的缓冲交换可能会在很久之后才会发生。

 

管线化


正如你所想到的,需要创造这一假象的原因在于性能。如果我们强制渲染运算真正同步发生,你就会面临这样的尴尬:CPU 忙于创建下一绘制运算的状态时,GPU 会闲置;GPU 执行渲染时,CPU 会闲置。对于以性能为重的加速器而言,所有这些闲置时间都是绝然不可接受的。

gles-sync.png
为了去除这一闲置时间,我们使用 OpenGL ES 驱动程序来维持同步渲染行为的假象,而在面纱之后实际是以异步执行的方式处理渲染和帧交换。通过异步运行,我们可以建立一个小小的工作储备以允许创建一个管线,GPU 从管线的一端处理较旧的工作负载,而 CPU 则负责将新的工作推入另一端。这一方式的优势在于,只要管线装满,就始终有工作在 GPU 上运行,提供最佳的性能。


gles-async.png

 

Mali GPU 管线中的工作单元是以渲染目标为单位进行计划的,其中渲染目标可能是屏幕缓存或离屏缓存。单个渲染目标通过两步处理。首先,GPU 为渲染目标中的所有绘制调用处理顶点着色[2]然后,为整个渲染目标处理片段着色[3]因此,Mali 的逻辑渲染管线包含三个阶段:CPU 处理阶段、几何处理阶段,以及片段处理阶段。

 

gles-mali.png

 

管线节流

观察力敏锐的读者可能已注意到,上图中片段部分的工作是三个运算中最慢的,被 CPU 和几何处理阶段甩得越来越远。这种情形并不少见;大多数内容中要着色的片段远多于顶点,因此片段着色通常是占主导地位的处理运算。

在现实中,最好要尽可能缩短从 CPU 工作结束到帧被渲染之间的延时 – 对最终用户而言,最让人烦躁的莫过于在操作触控屏设备时,其触控事件输入和屏幕中数据显示之间出现数百毫秒的不同步– 所以,我们不希望等待片段处理阶段的工作储备变得过大。简而言之,我们需要某种机制来定期减慢 CPU 线程,当管线足够满、能够维持良好性能时停止把工作放入队列。


这种节流机制通常由主机窗口系统提供,而不是图形驱动程序本身。例如,在 Android 上,我们只有在知道缓冲方向时才能处理任何绘制运算,因为用户可能会旋转其设备,造成帧大小出现变化。SurfaceFlinger— Android 窗口表面管理器 – 可以通过一个简单方式控制管线深度:当管线中排队等待渲染的缓冲数量超过 N 个时,拒绝将缓冲返回到应用程序的图形堆栈。


如果出现这种情形,你就会看到:一旦每一帧达到“N” CPU 就会进入闲置状态,在内部阻止 EGL  OpenGL
ES API 
函数,直到显示屏消耗完一个待处理缓存,为新的渲染运算空出一个位置。

 

   gles-mali-throttle.png


如果图形堆栈的运行快于显示刷新率,同样的方案也可限制管线缓冲;在这一情形下,内容受到VSYNC限制并等待垂直空白(VSYNC同步)信号,该信号告诉显示控制器它可以切换到下一缓冲。如果 GPU 产生帧的速度快于显示屏显示帧的速度,那么
SurfaceFlinger 
将积累一定数量已经完成渲染但依然需要显示在屏幕上的缓冲;即使这些缓冲不再是 Mali 管线的一个部分,它们依然算在应用程序进程的 N 帧限制内。

gles-mali-vsync.png

正如上面的管线示意图所示,如果内容受到VSYNC同步限制,那么会经常出现 CPU  GPU 都完全闲置的时段。平台动态电压和频率调节 (DVFS) 通常会在此类情形中尝试降低当前的工作频率,以降低电压和功耗,但由于 DVFS 频率选择通常相对粗糙,所以可能会出现一定数量的闲置时间。


Mali GPU: 抽象机器,第二部分 – 基于区块的渲染

上一篇博文,我开始定义一台抽象机器,用于描述 Mali GPU和驱动程序软件对应用程序可见的行为。此机器的用意是为开发人员提供 OpenGL
ES API 
下有趣行为的一个心智模型,而这反过来也可用于解释影响其应用程序性能的问题。我在本系列后面几篇博文中继续使用这一模型,探讨开发人员在开发图形应用程序时常常遇到的一些性能缺口。

这篇博文将继续开发这台抽象机器,探讨 Mali GPU系列基于区块的渲染模型。你应该已经阅读了关于管线化的第一篇博文;如果还没有,建议你先读一下。


“传统”方式


在传统的主线驱动型桌面 GPU 架构中 — 通常称为直接模式架构 — 片段着色器按照顺序在每一绘制调用、每一原语上执行。每一原语渲染结束后再开始下一个,其利用类似于如下所示的算法:


1.         foreach( primitive ) 

2.              foreach( fragment ) 

3.                   render fragment 

由于流中的任何三角形可能会覆盖屏幕的任何部分,由这些渲染器维护的数据工作集将会很大;通常至少包含全屏尺寸颜色缓冲、深度缓冲,还可能包含模板缓冲。现代设备的典型工作集是 32 /像素 (bpp) 颜色,以及 32 bpp 封装的深度/模板。因此,1080p 显示屏拥有一个 16MB 工作集,而 4k2k 电视机则有一个 64MB 工作集。由于其大小原因,这些工作缓冲必须存储在芯片外的DRAM 中。

model-imr.png

每一次混合、深度测试和模板测试运算都需要从这一工作集中获取当前片段像素坐标的数据值。被着色的所有片段通常会接触到这一工作集,因此在高清显示中,置于这一内存上的带宽负载可能会特别高,每一片段也都有多个读--写运算,尽管缓存可能会稍稍缓减这一问题。这一对高带宽存取的需求反过来推动了对具备许多针脚的宽内存接口和专用高频率内存的需求,这两者都会造成能耗特别密集的外部内存访问。

Mali 方式

Mali GPU 系列采用非常不同的方式,通常称为基于区块的的渲染,其设计宗旨是竭力减少渲染期间所需的功耗巨大的外部内存访问。如本系列第一篇博文所述,Mali 对每一渲染目标使用独特的两步骤渲染算法。它首先执行全部的几何处理,然后执行所有的片段处理。在几何处理阶段中,Mali GPU 将屏幕分割为微小的 16x16 像素区块,并对每个区块中存在的渲染原语构建一份清单。GPU 片段着色步骤开始时,每一着色器核心一次处理一个 16x16 像素区块,将它渲染完后再开始下一区块。对于基于区块的架构,其算法相当于:

1.         foreach( tile )  
2.              foreach( primitive in tile ) 

3.                   foreach( fragment in primitive in tile )  
4.                         render fragment  

由于 16x16 区块仅仅是总屏幕面积的一小部分,所以有可能将整个区块的完整工作集(颜色、深度和模板)存放在和 GPU 着色器核心紧密耦合的快速 RAM 中。

model-tbr.png


这种基于区块的方式有诸多优势。它们大体上对开发人员透明,但也值得了解,尤其是在尝试了解你内容的带宽成本时:

对工作集的所有访问都属于本地访问,速度快、功耗低。读取或写入外部 DRAM 的功耗因系统设计而异,但对于提供的每 1GB/s 带宽,它很容易达到大约 120mW。与这相比,内部内存访问的功耗要大约少一个数量级,所以你会发现这真的大有关系。
混合不仅速度快,而且功耗低,因为许多混合方式需要的目标颜色数据都随时可用。
区块足够小,我们实际上可以在区块内存中本地存储足够数量的样本,实现 4 倍、倍和 16 多采样抗锯齿1。这可提供质量高、开销很低的抗锯齿。由于涉及的工作集大小(一般单一采样渲染目标的 4 16 倍;4k2k 显示面板的 16x MSAA需要巨大的 1GB 工作集数据),少数直接模式渲染器甚至将 MSAA 作为一项功能提供给开发人员,因为外部内存大小和带宽通常导致其成本过于高昂。
Mali 仅仅需要将单一区块的颜色数据写回到区块末尾的内存,此时我们便能知道其最终状态。我们可以通过 CRC 检查将块的颜色与主内存中的当前数据进行比较 — 这一过程叫做事务消除— 如果区块内容相同,则可完全跳过写出,从而节省了 SoC 功耗。我的同事 Tom Olson 针对这一技术写了一篇 优秀的博文文中还提供了事务消除的一个现实世界示例(某个名叫愤怒的小鸟的游戏;你或许听说过)。有关这一技术的详细信息还是由 Tom 的博文来介绍;不过,这儿也稍稍了解一下该技术的运用(仅多出的粉色区块由 GPU 写入 - 其他全被成功丢弃)。

    blogentry-107443-087661400+1345199231_thumb.png
我们可以采用快速的无损压缩方案 — ARM 帧缓冲压缩 (AFBC) — ,对逃过事务消除的区块的颜色数据进行压缩,从而进一步降低带宽和功耗。这一压缩可以应用到离屏 FBO 渲染目标,后者可在随后的渲染步骤中由 GPU 作为纹理读回;也可以应用到主窗口表面,只要系统中存在兼容 AFBC 的显示控制器,如 Mali-DP500
大多数内容拥有深度缓冲和模板缓冲,但帧渲染结束后就不必再保留其内容。如果开发人员告诉 Mali 驱动程序不需要保留深度缓冲和模板缓冲2— 理想方式是通过调用  glDiscardFramebufferEXT (OpenGL ES 2.0)  glInvalidateFramebuffer (OpenGLES 3.0),虽然在某些情形中可由驱动程序推断 — 那么区块的深度内容和模板内容也就彻底不用写回到主内存中。我们又大幅节省了带宽和功耗!

上表中可以清晰地看出,基于区块的渲染具有诸多优势,尤其是可以大幅降低与帧缓冲数据相关的带宽和功耗,而且还能够提供低成本的抗锯齿功能。那么,有些什么劣势呢?

任何基于区块的渲染方案的主要额外开销是从顶点着色器到片段着色器的交接点。几何处理阶段的输出、各顶点可变数和区块中间状态必须写出到主内存,再由片段处理阶段重新读取。因此,必须要在可变数据和区块状态消耗的额外带宽与帧缓冲数据节省的带宽之间取得平衡。

当今的现代消费类电子设备正大步向更高分辨率显示屏迈进;1080p 现在已是智能手机的常态,配备
Mali-T604 
 Google Nexus 10 等平板电脑以 WQXGA (2560x1600) 分辨率运行,而 4k2k 正逐渐成为电视机市场上新的不二之选。屏幕分辨率以及帧缓冲带宽正快速发展。在这一方面,Mali 确实表现出众,而且以对应用程序开发人员基本透明的方式实现 - 无需任何代价,就能获得所有这些好处,而且还不用更改应用程序!

在几何处理方面,Mali 也能处理好复杂度。许多高端基准测试正在接近每帧百万个三角形,其复杂度比 Android 应用商店中的热门游戏应用程序高出一个(或两个)数量级。然而,由于中间几何数据的确到达主内存,所以可以应用一些有用的技巧和诀窍,来优化 GPU 性能并充分发挥系统能力。这些技巧值得通过一篇博文来细谈,所以我们会在这一系列的后续博文中再予以介绍。

小结

在这篇博文中,我比较了桌面型直接模式渲染器与 Mali 所用的基于区块方式的异同,尤其探讨了两种方式对内存带宽的影响。
敬请期待下一篇博文。我将通过介绍 Mali 着色器核心本身的简单块模型,完成对这一抽象机器的定义。理解这部分内容后,我们就能继续介绍系列博文的其他有用部分:将这一模型应用到实践中,使其发挥实际作用,优化你在 Mali 上运行的应用程序。

注意: 本系列的下一篇博文已经发布: Mali GPU: 抽象机器,第3部分 – 着色器核心

与往常一样,欢迎提出任何意见和问题。

Pete

脚注

具体有哪些多采样选项可用要视 GPU 而定。最近推出的 Mali-T760 GPU 最高支持 16  MSAA
 EGL 窗口表面而言,深度丢弃与模板丢弃是自动执行的;但对于离屏渲染对象,它们可能会予以保留,供将来的渲染运算重新利用。

Mali GPU:抽象机器,第3部分 – 着色器核心

在本系列的前两篇博文中,我介绍了帧级别的管线化 [Mali GPU:抽象机器,第 1 部分 - 帧管线化 Mali GPU 所用的基于区块型渲染架构 [Mali GPU:抽象机器,第 2 部分 - 基于区块的渲染]。我的目标是开发一个心智模型,供开发人员用于在优化其应用程序性能时解释图形堆栈的行为。

 

 

 

 

在本篇博文中,我将组建最后一个组成部分,完成对这一抽象机器的建造:它就是 Mali GPU 本身。你应该已阅读了本系列中的前两篇博文,如果还没有的话,建议您先读那两篇。

 

 

 

 

GPU 架构

 

 

 

 

Mali GPU  Midgard 产品线(Mali-T600  Mali-T700 系列)使用一个统一着色器核心架构,即设计中仅存在一种类型的着色器核心。此单一核心可以执行所有类型的可编程着色器代码,包括顶点着色器、片段着色器和计算内核。

 

 

 

 

具体芯片中存在的着色器核心确切数量各不相同;我们的芯片合作伙伴可以根据其性能需求和芯片面积限制来选择实施的着色器核心数量。Mali-T760 GPU 可以从面向低端设备的单一核心升级到面向最高性能设计的 16 核心,但 4  8 个核心是最常见的实施。

 

mali-top-level.png  



 

 

GPU 的图形工作排在一双队列中,其一用于顶点/分块工作负载,其二用于片段工作负载,一个渲染目标的所有工作作为单一任务提交发送到各个队列中。两个队列中的工作负载可以由 GPU 同时处理,因而可以并行运行不同渲染目标的顶点处理和片段处理(有关这一管线化方法学的详细信息,请参见第一篇博文)。单一渲染目标的工作负载拆分为更小的部分,分散到 GPU 中的所有着色器核心;或者,对于分块工作负载(本系列的第二篇博文提供了分块方面的概述),则分散到固定函数分块单元中。

 

 

 

 

系统中的着色器核心共享一个二级 (L2) 缓存来改善性能,同时减少重复提取数据而造成的内存带宽。与核心数量一样,L2 的大小也可由我们的芯片合作伙伴配置,但 GPU 中每个着色器核心通常在 32-64KB 的范围内,具体取决于可用的芯片面积。此缓存与外部内存之间的内存端口数量和总线宽度也是可配置的,因此我们的合作伙伴可以调整其实施,来满足他们对性能、功耗和面积的需求。总体而言,我们的目标是达到每个核心每时钟写一个 32 位像素,因此可以合理地预计一个 8 核心设计拥有每个时钟周期总共 256 位的内存带宽(用于读取和写入)。

 

 

 

 

Mali GPU 着色器核心

 

 

 

 

Mali 着色器核心构建为多个固定函数硬件块围绕在一个可编程三管执行核心周围。固定函数单元执行着色器运算的设置 - 栅化三角形或执行深度测试 - 或者处理着色器后活动 - 如混合,或在渲染结束阶段写回相当于整个区块的数据。三管本身是一个负责执行着色器程序的可编程部分。

 

 

 

mali-top-core.png



 

 

 

 

三管

 

 

 

 

在三管设计中,有三个类别的执行管线:一个处理算术运算,一个处理内存加载/存储和可变数访问,另一个处理纹理访问。每个着色器有一个加载/存储管道和一个纹理管道,但算术管线的数量根据你使用的 GPU 而不同;如今出货的大多数芯片有两个算术管线,但也存在最多 4 个管线的 GPU 变体。

 

 

 

 

大批量多线程的机器

 

 

 

 

在传统的 CPU 架构中,通常一个核心一次只执行一个线程;三管设计则不同,它是一个大批量多线程处理引擎。三管中可能同时运行数百个硬件线程,被着色的每个顶点或片段都创建有一个线程。这么多数量的线程可以隐匿内存延迟;如果有一些线程停下来等待内存也没关系,只要至少有一个线程可以执行,那么我们就能维持高效的执行。

 

 

 

 

算术管线:矢量核心

 

 

 

 

算术管线 (A-pipe)  SIMD (单指令多数据)矢量处理引擎,拥有在 128 位四字寄存器上运算的算术单元。寄存器的访问很灵活,可以 2 x FP644 x FP328 x FP162 x int644 x int328 x int16,或者 16 x int8。因此,一个算术矢量任务可以在一次运算中运算 8 “mediump”值,而对于 OpenCL 内核,运算 8 度数据,以便每个时钟周期每个 SIMD 单元处理 16 个像素。

 

 

 

 

虽然我不能透露算术管线的内部架构,但我们各种 GPU 的公开性能数据可以提供一些有关可用算术单元数量的概念。例如,配有 16 个核心的 Mali-T760 额定为 326 FP32 GFLOPS(600MHz)。这为此着色器核心提供每个时钟周期 34 FP32 FLOPS;它拥有两个管线,所有每个管线每个时钟周期 17 FP32 FLOPS。运算方面的可用性能对于 FP16/int16/int8 数据类型会提高,而对于FP64/int64 数据类型则会降低。

 

 

 

 

纹理管线

 

 

 

 

纹理管线 (T-pipe) 负责与纹理相关的所有内存访问。纹理管线每个时钟可返回一个双线性过滤纹素;三线性过滤需要我们从内存中两个不同的纹理贴图加载样本,因此需要另一个时钟周期来完成。

 

 

 

 

加载/存储管线

 

 

 

 

加载/存储管线 (LS-pipe) 负责所有与纹理化不相关的内存访问。对于图形工作负载,这意味着在顶点着色期间读取属性和写入可变数,以及在片段着色期间读取可变数。总体而言,每一指令都是单个内存访问运算,尽管与算术管线相似,但它们是矢量运算,因而可以在单一指令中加载整个“highp”vec4 可变数。

 

 

 

 

前期 ZS 测试和后期 ZS 测试

 

 

 

 

 OpenGL ES 规格中,片段运算” - 包含深度测试和模板测试 - 在管线的末尾发生,即片段着色完成之后。这使得该规格非常简单,但也意味着你必须花费大量时间着色的某一对象,如果最后被ZS 测试消除,就会在帧末尾丢弃。为片段上色而后又丢弃它们,这会消耗大量性能,并且浪费能源;因此,在可能时,我们会执行前期 ZS 测试(即片段着色之前),只有在无法避免时(例如,片段上的某个依赖关系可能会调用“discard”等,因此具有不确定的深度状态,直到其退出三管为止)才会执行后期 ZS 测试(即片段着色之后)。

 

 

 

 

除了传统的 early-z 方案,我们也具有一些过度绘制剔除功能,阻止那些已栅化但对输出场景没有实际贡献的片段转入真正的渲染工作。我的同事 Sean Ellis 针对此技术写了一篇精彩的博文 -消除像素 - ARM Mali GPU 上着色优化新方式 所以,此处我就不再赘述了。

 

 

 

 

GPU 限制

 

 

 

 

根据这一模型,我们能够概述一些为 GPU 性能提供支撑的基本属性。

 

 

 

 

·        GPU能做到每个着色器核心每一个时钟发布一个顶点

 

 

·        GPU能做到每个着色器核心每一个时钟发布一个片段

 

 

·        GPU能做到每个着色器核心每一个时钟撤销一个像素

 

 

·        我们可以做到每个管道每个时钟发布一个指令,因此对于典型的着色器核心,如果可以运行的话,我们可以并行发布四个指令

 

 

o    我们可以做到,每个 A-pipe 17  FP32 运算

 

 

o    每个 LS-pipe 一个矢量加载、一个矢量存储,或者一个矢量可变

 

 

o    每个 T-pipe 一个双线性过滤纹素

 

 

·        GPU将通常具有每个核心每时钟 32  DDR 访问(读取和写入)[可配置]

 

 

 

 

如果我们将这放大到以 600MHz 运行的 Mali-T760 MP8,可以将理论峰值性能计算为:

 

 

 

 

·        填充率:

 

 

o    每时钟 8 像素 = 4.8 GPix/s

 

 

o   也就是每秒 2314 个完整 1080p 帧!

 

 

·        纹理速率:

 

 

o    每时钟 8 个双线性纹素 = 4.8 GTex/s

 

 

o   也就是 1080p 60 FPS 时每个像素 38 次双线性过滤纹理查找!

 

 

·        算术速率:

 

 

o    每管道每核心 17 FP32 FLOPS = 163 FP32 GFLOPS

 

 

o   也就是 1080p 60 FPS 时每个像素 1311 FLOPS

 

 

·        带宽:

 

 

o   每时钟 256 位内存访问 = 19.2GB/s 读写带宽1

 

 

o   也就是 1080p 60 FPS 时每个像素 154 个字节!

 

 

 

 

OpenCL 和计算

 

 

 

 

观察敏锐的读者可能已注意到,我在顶点和片段上谈了许多 - 图形工作的主角 - 但极少谈及 OpenCL  RenderScript 计算线程如何进入到核心之中。这两种类型的工作几乎都和顶点线程相同 - 你可以将对一组顶点运行顶点着色器看做是 1 维计算问题。所以顶点线程创建程序也会衍生计算线程,虽然更为准确地来说,计算线程创建程序也会衍生顶点

 

 

 

 

未完待续 ...

 

 

 

 

本篇博文是本系列第一章的完结篇,开发了定义基本行为的抽象机器,应用程序开发人员有望在 Midgard 产品线的 Mali GPU 中看到这些行为。在本系列的后续篇章中,我将把这一新知识运用到实践中,调查一些常见的应用程序开发错误,介绍有用的优化技巧,它们可以通过利用 Mali  ARM DS-5 Streamline 性能分析工具中的集成加以识别和调试。

 

 

 

 

与往常一样,欢迎提出任何意见和问题。

 

 

回头见,

 

 

Pete

 

 

 

 

脚注

 

 

 

 

1.     ... 19.2GB/s 取决于 GPU 外部的其余内存系统能否以这一速度为我们提供数据。与基于 ARM 的芯片的大多数功能类似,下游内存系统具有高度可配置性,允许不同的供应商根据其需求调节功率、性能和芯片面积。对于大多数 SoC 部件而言,系统的其余部分将在 GPU 耗尽请求数据的能力前对可用带宽进行节流。你肯定不希望长时间保持这样的带宽,但短期峰值性能很重要。

Mali 新技术减少重复渲染

不可见像素代价昂贵

 

 

 

 

给像素着色开销巨大,所以你一定不想花费时间和精力去给屏幕上那些不会真正出现的像素着色。为解决这一问题,ARM? Mali? GPU 开拓了一种全新的优化方式。

 

 

但是,在介绍解决方案之前,首先我们来了解为何会出现不可见像素?在探索像素可见与不可见之时,您或许也想阅读一下Ed Plowman 关于原理以及像素在与不在”.

 

 

屏幕上各个像素的颜色由着色器程序决定。每个对象通常有不同的程序与之关联,对象中的每个像素都会衍生一个执行线程。这些线程一旦启动,就必定要完成(除非它们执行一个丢弃指令而自我终止),然后它们将计算得出的颜色传递到混合单元,在那里与输出图像中的现有像素值合并。

 

 

此处的关键问题是过度绘制 - 较近的对象绘制在较远对象上,而将它们隐藏。在地平线上绘制翡翠之城时,如果前景中有一座山遮挡(隐藏)该城的话,那么就无需细致地绘制该城。如果在发现过度绘制之前已经花费时间和精力渲染了翡翠像素,那就浪费了性能、时间和电池续航时间,也可能浪费了气氛。

 

 

 

减轻负载

 

 

 

 

目前已有几种旨在减少过度绘制像素开销的方式。第一种是让应用程序使用其对场景的了解来避免将几何体发送至图形驱动程序。这对以室内为基础的封闭式游戏效果不错,但要求游戏引擎中具备额外的逻辑。对于常见的场景类别,确定哪些对象会遮挡住别的对象实际上也比较困难。

 

 

即使您消除了一些距离较远的几何体,在一些情形中依然会有一些绘制出的几何体处于隐藏状态。或许同一房间中有敌方玩家 – 您可以看到钢盔,但其余部分隐藏在木箱后面。这时只需为其帽子顶部着色就已足够,而不必为整个人物着色像素。

 

 

借助一个简单的深度缓存,加上早期深度测试,就可以在开始着色像素之前,确定较远对象的像素会被较近对象的像素隐藏。

 

 

按照距离排列对象,再首先绘制最近的对象,可能有助于完成处理过程并消除过度绘制图像中的大多数隐藏像素。反过来做显然不行,因为管线没有超能力,无法了解后面要绘制的内容... 不过,先等一下。

 

 

但是,从前到后的排序方式存在一些其他问题。

 

 

对于半透明对象,从前到后绘制它们恰好是错误的顺序,因为它们需要与其后面的对象混合。再说,排序对象也需要时间。更为糟糕的是,现代图形API OpenGL ES? Direct3D?)的结构其实并不包含场景中对象这一概念,所以你必须自行追踪并以可接受的顺序进行绘制。

 

 

另一种避免工作的方式是尽可能推迟着色:首先运行一个快速程序,仅计算深度并存储有关各个像素上哪一对象在前的数据;然后,在计算出所有像素后,再运行完整的照明计算。

 

 

这种方式效果非常好。至少它可以正常运作,直到您遇到打破规则的某些事物为止。或许是写入其自己深度的像素。又或是半透明对象。到那时,你必须回退到一个更加蛮力的运算模式,以便能跟踪额外的数据。这也不是软切换,因为一旦检测到特别情形性能就会显著下降,而且随着游戏引擎更加追求真实感,它们变得非常常见。

 

 

所以,在技术文章简介末尾难免要提出这样的问题:我们能够对此做些什么?

 

 

前像素终止

 

 

 

 

我们的答案是称为前像素终止 (FPK) 的一项专利技术,自
Mali-T62X 
 T678 起的 ARM Mali GPU 中已包含这项技术,如最近推出的Samsung
Exynos5420

 

 

在具备 FPK 功能的 GPU 中,为像素上色的线程并不是一旦启动就必定要完成。如果发现后续线程将向同一像素位置写入不透明数据,我们可以在任何时候终止已在进行的计算。由于完成每个线程的时间都是确定的,我们就有一个窗口期,可以用来消除管线中已存在的像素。实际上,我们利用管线的深度来模拟我早前提到的预见未来超能力

 

 

事实上,它可以做的不只如此。通过向管线的起点添加一个简单的FIFO 缓冲 ,我们可以扩展前像素终止区域,使其更有可能发现过度绘制,同时让管线线有机会在线程启动之前将它们除掉。

 

 

对于 ARM Mali GPU 等基于区块的渲染器来说,这种方式效果尤其好。即便是最温和的消除区域,也可以产生与从前到后绘制顺序一样好的效果,但不需要对场景排序(造成硅面积、功耗和内存带宽的开销)。所以,无需修改您的应用程序来添加排序算法。另外,由于按照与自然顺序相同的次序进行绘制,半透明内容也显示正常,从而避免代价昂贵的变通办法造成性能降级。

 

 

而且,最好的方面在于操作规程之间的过渡是软性的 – 更像是匀速调节而非换挡。帧速率不一致(有时称为闪烁)对用户而言特别烦人,所以任何大幅降低场景渲染时间中不确定性的技巧都会受到用户和开发人员欢迎。


  评论这张
 
阅读(300)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017