GPU 硬件架构与渲染管线概述
GPU 是并行计算密集型硬件,在移动SoC中负责加速图形渲染。其硬件模块包括:
- 算术逻辑单元(ALU)或统一着色器单元(执行顶点/片元着色器)
- 光栅化单元(将图元转换为像素/片元)
- 纹理采样单元(纹理映射与滤波)
- 像素合成单元(ROP/Output Merger),将像素结果写入帧缓冲,进行混合、深度测试等操作
- Z/Stencil测试单元,执行早期深度剔除、模板测试等以减少不必要的计算
- 缓存层次结构(如顶点缓存、纹理缓存、片元缓存、片元缓存(Tile Cache)、L1/L2 Cache等,用于减少主内存访问延迟)
- 指令调度器/任务调度器, 管理线程分派与波前(wavefront/thread group)调度
这些模块在渲染管线各阶段协同工作:顶点处理模块执行模型变换、视锥剔除和光栅化设置;裁剪/剔除阶段剔除不可见图元;光栅化单元将可见三角形转换为片元;片元着色器在统一着色器中执行逐像素计算;最后深度测试、混合和写回操作将最终颜色写入帧缓冲。例如,GeForce6架构的GPU在栅格化阶段使用专门的Z-cull模块快速丢弃被更近几何遮挡的像素;在片元处理阶段,纹理单元从内存中加载并过滤纹理数据,再由片元着色器执行并隐藏纹理访问延迟;最终,片元在Z测试和混合单元处理后写回帧缓冲。现代GPU支持统一着色器架构,即顶点、片元(和可选的几何、计算等)共用同一组ALU流水线,可动态分配着色器工作,以提高利用率。缓存方面,GPU通常具有专用的顶点/几何缓存和纹理缓存,以及可供片元处理访问的小容量帧缓冲缓存,辅以更大容量的L2缓存,尽量减少对主内存的访问。
在渲染管线各阶段中,各硬件模块配合如下:输入顶点首先经由顶点着色器(在统一着色器中)计算变换和属性,然后通过裁剪和图元组装模块形成三角形。可见的三角形传递给光栅化单元,该单元使用硬件加速的扫描转换算法计算覆盖的片元坐标,并生成深度测试数据。每个生成的片元会经由片元着色器(片段着色器)执行逐像素光照、纹理采样等计算,其中纹理单元负责高效访问和滤波纹理数据。GPU调度单元则负责管理大量并行线程(或称“线程束”/“波前”),动态分配就绪线程到ALU单元执行。在多线程环境下,GPU采用SIMD方式并行处理一组片元(通常称为“像素四元组”或更宽),同时可遮挡剔除(深度测试)并行进行,以避免过度计算。最终的深度和颜色测试、模板测试、混合等操作在固定功能单元(ROP)中完成,结果写入帧缓冲或渲染目标。需要注意的是,在移动SoC中,GPU往往与CPU共享统一内存,片元缓存和深度缓存多使用片上高速存储,以降低高带宽DDR访问次数。
GPU 架构类型:IMR、TBR、TBDR
不同GPU供应商采用不同渲染架构来平衡性能与功耗。**即刻模式渲染(IMR, Immediate-Mode Rendering)**传统地按照API提交顺序“流水线”地处理三角形:顶点着色→裁剪→光栅化→逐个片元着色,整个帧缓冲作为活跃工作集,着色过程中对帧缓冲进行随机访问。IMR的优势是实现简单,对流水线无特殊需求,但缺点是片元着色会频繁跳跃访问帧缓冲,工作集往往是全屏大小(高分辨率时工作集可达数十MB),需要大量外部带宽,且着色操作可能对位置无序的像素进行冗余计算。典型代表为传统桌面GPU或早期移动GPU,例如高通Adreno系列长期以来基本采用近似IMR模型(或称“简化瓦片渲染”),依赖复杂的帧缓冲缓存和早期深度测试减少工作量。有资料指出Adreno在必要时可退化到真IMR模式。
瓦片基渲染(TBR, Tile-Based Rendering)将帧缓冲划分为若干小瓦片(如16×16像素),渲染分两阶段完成:首先在几何阶段对所有图元执行顶点变换并进行分桶(Binning),将每个图元的列表按照会影响的瓦片进行记录;然后按照每个瓦片顺序进行光栅化,每次仅处理一个瓦片内的图元,并将瓦片颜色/深度数据加载到片上高速缓存中,处理完毕后再写回内存。这样做的优势在于:瓦片工作集很小,色深Stencil可全部驻留在片上快速存储,减少了对外部内存的随机访问。由于只需将最终瓦片数据一次性写出,TBR大幅降低了内存带宽开销(尤其是在移动设备有限带宽下优势明显)。缺点是TBR需要额外的几何分桶硬件,以及整个渲染必须等待几何阶段全部完成后才执行片元阶段(增加了延迟和硬件复杂度),同时分桶缓冲区需要占用片上或片外内存,若几何非常复杂时可能形成瓶颈。典型的TBR架构GPU包括ARM Mali系列和Imagination PowerVR系列(虽PowerVR进一步延伸为TBDR),它们通常使用16×16或更小的瓦片。
延迟式瓦片渲染(TBDR, Tile-Based Deferred Rendering)是PowerVR等架构引入的瓦片渲染变体,其核心思想是尽可能推迟昂贵的纹理采样与片元着色,先执行完全的可见性判断(HSR)再对可见像素进行着色。TBDR同样对场景分割瓦片,首先收集并划分几何,随后在每瓦片内利用深度信息确定可见表面,最后只对可见像素运行片元着色(推迟过度绘制),且可对透明对象额外处理。PowerVR的TBDR架构在每个瓦片内常被比作类似光线投射的过程,以确定最近表面。这种架构的优势是原理上消除了不可见像素的处理,降低了过绘(overdraw)带来的多余计算,非常适合片上片外带宽受限的移动平台;同时,每个瓦片数据可完整驻留片上内存中,一次输出。劣势是实现复杂,需要跟踪每个像素的最前表面ID,并在遇半透明时回写已着色像素,还需要较大片上内存(PowerVR最早代每像素只跟踪深度及ID)。典型代表是Apple的GPU(采用TBDR方案)和Imagination PowerVR(Series 1/2)。对比来看,IMR适用于对延迟敏感、流水线推理简单的场景;TBR适合几何复杂度中等且需节省带宽的场合;TBDR在带宽极其紧张或过绘消除要求高时最优。例如Apple在WWDC提到其移动GPU为TBDR,并强调通过HSR彻底降低了过绘;ARM文档也指出瓦片渲染显著降低内存带宽需求。各家厂商在此基础上发展出不同混合模式:有资料称Adreno采用灵活的瓦片大小和模式(“FlexRender”),可以在一些场景下退化为IMR;Mali也在新一代架构(Valhall)中改进线程和调度,但核心仍为TBR。
硬件层面优化技术
现代GPU通过多种硬件技术减少冗余工作和带宽消耗:
隐藏表面移除(Hidden Surface Removal, HSR):在任何片元着色前,通过深度信息剔除被遮挡的像素。Apple GPU在TBDR阶段使用可渲染瓦片的片上深度缓存,在运行片元着色前先计算每像素的前端可见三角形,仅对可见像素最终着色,理论上消除所有不透明像素的过绘。Mali新一代GPU也引入了**片元预处理(Fragment Pre-pass)**机制,硬件自动执行一次隐式的Z预通道(类似软件的Z pre-pass)来筛选可见像素,再进行实际着色。HSR能够显著降低过绘次数和片元着色次数(Apple称即使提交顺序不按深度,也能保持像素级的最前表面),但需付出额外前处理复杂度和资源。当存在半透明时,GPU可能回退到正常着色流程,对透明对象执行“flush”操作。应用层面可配合HSR:按渲染顺序绘制不透明物体优先,再绘制可丢弃片元(alpha-test)物体,最后绘制透明物体,以最大化HSR效率。
深度剔除(Z-culling / Early-Z):在片元进入片元着色器前先做深度测试,快速丢弃远端像素。NVIDIA等桌面GPU中常见专门的Z-cull硬件,可在像素进入计算单元前根据深度值屏蔽掉不可见片元。移动GPU也普遍支持早期深度测试,例如在多级深度缓存(Hierarchical Z)中快速判断一整块像素是否全被遮挡,只对剩余像素执行细粒度深度测试,减少后续流水线负担。ARM Mali的新机制则是将这种思想延伸到更主动的预通道,通过硬件第一次剔除绝大多数不可见片元,从而避免了传统的“深度测试+丢弃”流程中的多余片元计算。
瓦片缓存(Tile Cache / Tile Memory):瓦片渲染架构在片元阶段使用的本地片上RAM。每个瓦片通常可以同时存储该区域所有像素的颜色、深度和模板数据。例如,据开源驱动透露,老款iOS设备每像素可在片上存储128位数据(颜色+深度+模板),新设备可达512位。类似地,ARM Mali一块16×16瓦片需要4KB本地RAM来保存深度和颜色数据。这些片上缓存使得全帧写回带宽大幅降低;清除操作可以直接在片内完成,无需读写全局内存。另外,瓦片缓存天然适合多重采样抗锯齿(MSAA)实现:GPU仅在本地存储多重采样像素,分辨率降低操作(resolve)在写出前于片上完成,大幅节省带宽。
着色器指令重排序:一些新GPU架构(如NVIDIA Ada)支持运行时动态重排序着色器片段,以提高ALU利用率。移动GPU更多依赖编译器在编译阶段进行指令调度,以减少流水线气泡。例如ARM新一代Valhall架构取消了固定发射组,将硬件进行更多出序调度,从而提高指令执行并行度。同时,应用开发上应尽量避免使片元着色器中包含阻塞指令(如动态分支、大量纹理Fetch),以便硬件更有效利用多线程并行。
内存带宽优化:移动GPU常用各种压缩技术减少内存访问。ARM提出的**帧缓冲压缩(AFBC)是一种块压缩方案,对渲染输出纹理进行无损压缩,显著降低读写带宽。AFBC在GPU写回帧缓冲时压缩数据,随后显示或后续读取时解压缩,能在写出和读回两端节省带宽。此外,TBDR架构引入可编程混合(Programmable Blending)和无内存渲染目标(Memoryless Render Targets)**等特性:前者允许在片上直接读取当前瓦片的像素数据,合并多个渲染通道为一步,减少跨通道读写;后者让开发者声明某些中间附件为“无内存”模式,只在片上保留数据,不分配主存,从而节省资源。这些技术共同作用,有效压缩和省略了不必要的内存传输。
典型移动GPU平台分析
高通 Snapdragon (Adreno GPU):Adreno系列传统上采用近似即时模式渲染,依赖较大片上缓存(称为GMEM)和强大的硬件裁剪来减少带宽。最新研究表明,Adreno GPU实际上实现了可配置的瓦片渲染:例如Adreno 540的GMEM约1MB,可以支持128×128的大瓦片;并且通过“FlexRender”可动态调整瓦片大小,在需要时退化为真正的IMR模式。调度上,Adreno以SIMD多线程方式运行,每条指令横跨多个片元并发执行(类似“warp”)。硬件支持早期深度测试和粗粒度的HSR,但不像TBDR那样完全推迟着色。驱动方面,Qualcomm提供Vulkan/OpenGL ES驱动优化指导,建议减少绘制调用数量、避免无用状态切换,并充分利用实例化和批处理等特性。整体而言,Adreno对几何带宽敏感,建议提前进行丢弃(例如使用剔除技术)、合理组织顶点索引和内存访问,以配合其混合的渲染模式。
Apple M 系列 (Apple GPU):Apple自主设计的GPU为TBDR架构,与其统一内存架构(UMA)紧密耦合。GPU没有独立显存,CPU/GPU共享片上内存,但GPU拥有专用的高速瓦片内存。根据开源分析,M1 GPU可同时运行24个线程组(threadgroup),每组最多1024个线程,总共可并发执行24576个线程;每个线程组有约208KB寄存器文件。硬件采用标量ALU和向量I/O,支持16位浮点和高效的指令发射。Apple GPU完全采用TBDR流程:先将几何着色和分桶完成到片上缓冲,然后对每瓦片执行HSR,再仅对最终可见像素运行片元着色。加载/存储操作(Load/Store Actions)由开发者通过Metal API明确指定,以控制瓦片内存的使用。Apple GPU在可见表面确定后才着色,默认取消传统的顶点属性读硬件,所有状态由着色器取代,使硬件更为简洁。由于UMA特性,CPU与GPU访问同一内存,减少了额外复制和一致性操作(Metal在驱动层负责资源驻留管理)。综合来看,Apple GPU利用TBDR和大量线程并行,在移动平台上实现了优秀的带宽使用效率和高吞吐。
ARM Mali (Immortalis/Mali 系列):早期Mali(Utgard/Midgard)架构以片上16×16瓦片为渲染单元,采用TBR流程。现代Mali(Bifrost/Valhall)继续使用TBR,但在线程调度上已进化:早期Mali每个片元流水线很窄(如4宽SIMD),新Valhall已经增加到16宽warp。Mali GPU拥有可配置核心数量,每核含统一着色引擎、多级纹理单元和深度缓存。驱动通过ARM提供的资源(如OpenCL/Vulkan指导)优化几何带宽使用,尽量将可丢弃几何在分桶阶段剔除。Mali也支持早期深度测试和多重采样抗锯齿。在缓存一致性方面,Mali与CPU共享内存需手动同步(如用API内存屏障);ARM平台通常通过驱动保证CPU/GPU可以安全协作。异构计算方面,Mali及其平台经常与big.LITTLE CPU配合,使用OpenCL/Vulkan或厂商中间件实现CPU-GPU协同任务(如基于GPU的后处理、神经网络加速等)。
总体对比:Adreno偏向灵活的“半瓦片”IMR模式以降低复杂度,强调几何带宽;Apple GPU完全采用TBDR以最大化过绘剔除和带宽节约;Mali则典型地以TBR折中,依赖分桶高效利用片上缓存。在调度上,三者均为统一着色器多线程执行,但Warp/ThreadGroup宽度和调度策略不同(Adreno和Mali类似于NVIDIA的warp,Apple则采用Metal定义的线程组概念)。缓存一致性方面,Apple UMA架构优势明显;Adreno/Mali依赖系统内存,通信延迟稍大。异构计算上,三者都支持通用计算API,但Apple更倾向Metal+Metal Performance Shaders生态,Snapdragon/Mali则依赖Vulkan/OpenCL等跨平台API。
GPU 驱动与图形API协作
在图形API层面,驱动程序负责将CPU提交的渲染命令高效地转换为GPU执行单元的工作负载,并优化资源和状态使用。对于Vulkan,开发者通过命令缓冲(Command Buffer)明确记录绘制和计算指令,驱动在提交时逐条下发给GPU。Vulkan强调多线程并行命令录制和显存管理:建议将命令缓冲构建并行化、避免过度分散的小提交、重用管线对象和缓冲池。驱动会对图形管线状态做缓存(Pipeline Cache)和编译优化,减少绑定流水线(vkCmdBindPipeline)的次数;同时利用渲染通道(Render Pass)和亚通道(Subpass)机制,帮助GPU按瓦片更高效地利用片上内存。在数据传输方面,Vulkan驱动管理主机侧和显存侧的资源;通过内存屏障和同步原语(比如vkQueueSubmit和vkPipelineBarrier)协调CPU/GPU访问。当GPU执行绘制或计算任务时,驱动确保所需资源已经驻留,并且在必要时执行异步数据上传。高级优化包括使用描述符集(Descriptor Set)来绑定大量纹理和缓冲,使用推送常量和动态UAV减少驱动开销,以及在渲染路径中尽可能减少状态切换(例如一次绑定多个帧缓冲附件,一并绘制)等。
对于Metal(Apple专有API),驱动利用硬件的TBDR特性,开发者明确指定每个渲染目标的加载/存储操作(Load/Store Action),以控制瓦片内存的使用和清空。例如,Metal要求在每个渲染通道开始时声明是否需要加载之前的颜色/深度数据或清除它们,这直接映射到Apple GPU的载入/存储操作。Metal还提供了资源堆(Heap)和Argument Buffer等功能,允许更灵活地管理纹理和缓冲,并通过预编译的**图形管线状态对象(PSO)**减少运行时开销。由于Apple硬件和Metal深度协同优化,Metal驱动会尽量复用片上资源,对纹理数据和帧缓冲实现自动压缩。Metal中的多线程渲染(如MTLCommandQueue和MTLCommandBuffer)可以在CPU多核上并行填充命令,而GPU异步消费。值得一提的是,Metal API设计充分考虑了TBDR特点,比如推荐先绘制不透明再透明、使用Memoryless模式、尽量减少跨帧读回等(均可极大提升TBDR效率)。
在执行图形任务时,驱动和API还负责GPU资源调度:比如智能地调度提交队列、在多个硬件上下文(graphics/compute)间分配执行资源,以及应用后端合并(batching)和前端剔除技术来减少CPU与GPU之间的同步等待。高级技术如异步计算队列允许图形和计算任务并行执行,从而提高硬件利用率(需要驱动管理好依赖)。无论是Vulkan还是Metal,优化原则包括:并行化命令生成、多用批量提交、避免不必要的管线切换和状态绑定、使用内存屏障确保一致性,以及充分利用硬件压缩与内存连续性等。例如NVIDIA建议尽可能减少vkCmdBindPipeline调用,因为每次绑定代价高昂;对于Metal,则建议利用可编程混合和Memoryless模式合并多通道操作、减小带宽开销。
游戏开发者与渲染器设计优化建议
减少过绘:尽量按不透明→可剪裁→透明顺序绘制对象,以利用HSR/Early-Z尽早丢弃被遮挡像素。对于不可见面做剔除、深度预通道等策略可提高效率。尽可能避免在片元着色器中使用会修改深度的操作(如
discard
或在深度反馈后再绘制),因这些操作会让早期深度测试失效。优化填充率(Fill Rate):利用多边形细分和LOD(层次细节)减少三角形数量,避免在低分辨率下绘制极多小三角形。开启MSAA时可利用TBR架构优势,仅在片上缓存中进行采样融合,无须写出多份帧缓冲。
纹理和内存带宽优化:采用压缩纹理(如ASTC、RGBA8等),使用GPU支持的帧缓冲压缩(AFBC)格式存储渲染目标,减少读写流量。对于中间中间纹理,可考虑Memoryless存储模式(不分配片外内存)。尽量在片上执行可能的计算,减少来回交换数据次数。
管线状态管理:批量提交绘制调用、复用管线和资源。使用现代API的管线缓存(Vulkan pipeline cache、Metal PSO)预编译着色器,避免运行时重新编译。将共享状态(如渲染目标格式、混合模式)相同的绘制合并到同一管线调用中,减少
vkCmdBindPipeline
、vkCmdBindDescriptorSets
等指令次数。使用动态偏移量或推送常量来替代频繁的缓冲绑定切换。命令缓冲录制并行化:在Vulkan中,将命令缓冲的录制任务分散到多线程,以降低CPU提交瓶颈。合理规划每帧命令结构,避免太多小提交导致驱动负担。尽量重用命令缓冲和内存池,以减少分配/销毁开销。
利用硬件特性:针对TBDR GPU,应明确使用API提供的加载/存储选项(load/store actions),只加载必要的缓冲数据(无需清除时直接clear),以充分利用片上缓存效率。对于支持可编程混合的GPU,可合并多通道后处理pass为一次绘制,以减少中间帧缓冲写读。同时,根据GPU架构特性调整工作负载:例如在Adreno上尽量避免Geometry Shader或其他可能破坏分桶的特性;在Mali上注意几何带宽,尽量使用Instance Rendering减少顶点负载。
Shader优化技巧
- 避免使用高计算成本的数学函数
- 方法:减少使用如 pow、exp、log、sqrt、sin、cos 等复杂数学函数。可以用近似算法或预先查表的方式替代。
- 原理:这些函数通常没有直接的硬件支持,需要分解成多条基础计算指令,增加运算负担。
- 减少 if 语句和 discard 的使用
- 方法:使用像 step()、lerp()、saturate() 这样的数学函数来代替 if 语句,并尽量避免用 discard。
- 原理:GPU 使用并行的 SIMD 模型,if 会导致线程不同步,影响性能;discard 会让像素被跳过 Early-Z 流程,降低渲染效率。
- 控制纹理采样次数
- 方法:合并重复使用的通道、共享计算结果,或提前在顶点阶段进行处理。
- 原理:纹理采样是显存读取操作,成本较高,频繁采样会增加延迟。
- 使用较低精度的数据类型
- 方法:如果变量不需要高精度(如颜色、UV 坐标、法线等),可以用 half、fixed 或 lowp 来替代 float。
- 原理:低精度类型可以减少内存占用,使 GPU 能执行更多线程,提高效率。
- 把计算尽可能放到顶点着色器中
- 方法:可预测的计算先在 vertex shader 中处理,再通过 varying 传给 fragment shader。
- 原理:顶点着色器每个顶点执行一次,而片元着色器每个像素执行一次,把计算前移能显著降低总体负担。
- 移除无用的 varying 变量和多渲染目标(MRT)
- 方法:检查并删除未使用的输出变量和渲染目标,避免资源浪费。
- 原理:这些多余的数据会增加显存传输和写入成本,对性能不利。
- 合理使用 Early-Z 和 ZWrite
- 方法:优先绘制不透明物体并开启深度写入,可以利用 GPU 的 Early-Z 优化。
- 原理:Early-Z 可以在执行片元着色器前丢弃被遮挡的像素,节省计算。
- 在一张纹理中打包多个属性
- 方法:将 AO(环境光遮蔽)、光泽度、粗糙度、高光强度等属性分别存储在 RGBA 四个通道中。
- 原理:打包可以减少纹理数量,从而减少采样次数和显存使用。
- 压缩小数据并使用 bit mask 操作
- 方法:将多个布尔值或小范围整数压缩成一个字节或使用位操作管理。
- 原理:压缩后的数据能有效减少内存和带宽占用,常用于延迟渲染等场景。
- 减少像素重绘(Overdraw)
- 方法:通过 Z-Prepass(预先写入深度)优先绘制前景物体,避免重复绘制被遮挡部分。
- 原理:如果一个像素被多次绘制,会让 fragment shader 重复执行,浪费性能。