OpenGL-ES 学习(5)---- GPU 基础知识

Arm GPU 架构说明

UtGard:
比较早的架构,支持到 OpenGL-ES 2.0,VertexShader 和 FragmentShader 是分离的
主要型号:mali-200, mali-400, mali-450, mali-470

MidGard:
比较早的架构,基于 128 bit 向量
主要型号:mali-T6xx, mali-T7xx, mali-T8xx

Bifrost:
对于Bifrost,例如G76,一个shader core可以同时运行几十个线程,
从 Mali 的资料显示,shader core 一般主要由三个部分组成,ALU,L/S Uni,TEXTURE, late-Z unit 几个主要模块
在G76上是 8-wide wrap 的,一般设置为 3 个 ALU
(其余的型号可能不一样,例如 G51/G72 是 4-wide wrap 的,G72同样是3个ALU;
G52 跟 G76一样,不过G52可配置成2个ALU的)
主要型号:mali-G31, mali-G51, mali-G71, mali-G52, mali-G72, mali-G76

ValHal:
2018 推出,基本都是超标量处理器
主要型号:Mali-G57,Mali-G77

移动系统的特点

桌面系统和移动系统的区别在于,桌面系统由 CPU, GPU+VRAM, DRAM 组成,功耗在 225~370 W 左右
并且辅以主动散热;而移动系统使用统一内存,一块芯片上集成了 CPU, GPU, DRAM,功耗只有 2~3 W,被动散热,
并且手机的塑料或玻璃外壳并不利于散热,还有手机壳会影响散热。
因此手机渲染的效率要求是比桌面高很多的。(注:这也是为什么同样的芯片,在平板上性能可能会比手机更好)

移动 GPU 和其它主要硬件一般通过总线相连。移动 CPU 通常采用大小核设计,大核考虑性能,小核考虑功耗,系统会自动为线程分配合适的核心。统一内存被所有芯片共享,访问内存的操作也是极其昂贵的,约 100 mW/GB/s。DPU 可以将多层画面合为一帧,包括图像变换,颜色转换,根据显示帧率实时运行,一些复杂的合成任务仍可能会交由 GPU。

总线上除了上述模块,还有像是基带芯片、VPU、ISP、NPU、广播 I/O、相机 I/O 等,同样共享 2~3 W 的功耗。
提到功耗,就不得不提处理器的功耗曲线,提升性能的同时,功耗大约是指数上升的。基准频率下,处理器的功耗和性能比较均衡。手机要根据 GPU 或 CPU 的预算,为他们分配合适的功耗,但总数依然不超过 2~3 W。CPU 和 GPU 通过大量的并行计算,可以以较低的功耗获得一致的性能。(注:这里的假设是并行 Overhead 不大,且所有过程都可以并行化,实际情况会有一个最合适的并行数量)

渲染管线

渲染管线简介

以绘制地球月球为例子,CPU 把绘制的 Shader、Buffer、贴图以及 Attachment 准备好,所有需要的数据写入显存,然后发送了两个 Draw Call D1, D2。

GPU 几何处理调用 Vertex_Shader S1,计算出顶点位置,像素处理调用 Pixel Shader S2 对每一个像素进行着色。Shader S1, S2 和 Vertex Buffer B1 是复用的,但是传的参数不同,贴图 T1, T2 不同,所以绘制出来的东西也不同。(注:渲染管线的定义有很多,建议参照《Real-Time Rendering》一书的定义,这里的管线应该更偏向 API)
render_pipeline
Compute 处理游离于渲染管线之外,它从显存(内存)中读取数据,然后又写回显存(内存)

几何处理

按顺序有 5 个阶段,输入是物体空间的顶点,输出是屏幕空间的图元:
顶点着色(可编程):执行 Vertex Shader(VS),一般计算骨骼动画、坐标转换
细分着色(可编程,可选):用于曲面细分增加细节,由 Controller Shader, Tessellator, Evaluation Shader 构成(Tesllation)
几何着色(可编程,可选):程序化生成和删除面片
图元组装:将顶点组装成点、线、三角面 (primitives)
图元剔除:剔除视锥体之外和背向 / 正向相机的图元 (clip & cull)

移动渲染通常不会使用细分着色,因为代价比较昂贵;
几何着色已经过时,一般可以用 Compute Shader 代替。
optimize Geometryy processing
图元剔除可以发生在更早的阶段来节省开销,Mali 的 Bifrost 系 GPU Compiler 可以拆分 Vertex Shader,将其它属性的计算 (varying shader) 放到了剔除阶段之后。(也就是图中的 vertex_shader和varying Shader) 尽管 GPU 可以这么做,最好的方式是 CPU 在软件处理时就已经做了部分剔除。(注:比如逐物体的视锥体裁剪和软件遮挡剔除,桌面端还会用上 Hi-Z)
这里要注意移动 GPU 在图元装配之后,光栅化之前就开始了 primitives culling
未优化的渲染管线是 图元装配之后进行图元剔除,优化后的管线在拆分Vertex Shader称为 Vertex Shader 和 Varying Shader, 优先执行的是 Vertex Shader,然后在 Varying Shader 之前执行 primitives)

像素处理

像素处理也有 5 个阶段,输入是屏幕空间图元,输出是像素颜色:
光栅化(rasterazation):从图元到像素点到 Quad,有一些还支持可变渲染率,
Quad 为每四个像素一组(不同的 GPU 可能有区别),用于计算 Mipmap 和各向异性过滤需要的梯度 (四个像素为一组的集合)

Early ZS(Z & Stencil)测试:不修改像素 Depth/Stencil 的片元在此阶段选择性剔除

片元着色(可编程):执行 Pixel Shader(ps),计算像素颜色

Late ZS 测试:其余未经过 Early ZS 测试的片元在此阶段选择性剔除

混合(blend):混合颜色,体现透明或不透明物体
optimize_pixelProcessing
渲染管线的三个大阶段可以并行,以最大化压榨硬件性能。需要注意的是 OpenGL ES 的一些同步函数,例如 glFinish 或者 Vulkan 的 Barrier 设置不合理,会导致并行性下降。并行渲染的性能也具有木桶效应,三者中最耗时的部分将成为渲染瓶颈。
paralle_processing

渲染管线的硬件

桌面GPU 硬件架构形式:几何处理 → 像素处理 → Framebuffer,通过一些 Buffer 来传递中间结果。可编程的 Shader Core 用于执行 Shader 代码,与 CPU 设计迥异。

现代 GPU 有两种常见的渲染模式:立即渲染(IMR)、Tile-Based 渲染(TBR)。

IMR(立即渲染)

立即渲染模式中,几何处理 → 像素处理使用的是简单的 FIFO Buffer,这块 Buffer 在显存中,Draw Call 以符合直觉的方式,顺序绘制到 Framebuffer 上。缺点是,GPU 读写内存中 Framebuffer 的带宽需求比较高。(注:尤其是延迟渲染,除非 Framebuffer 全部塞进显存里)
IMR

TBR(Tile Based Rendering)

Tile-Based 渲染中,Framebuffer 会被划分为一个个瓦片(Tile),每次仅渲染一个 Tile
几何处理 → 像素处理使用内存中的 Tile Lists 记录每个 Tile 需要的几何和渲染信息,处理完所有的三角面后,把每一个 Tile List 读入片上缓存,像素渲染在 GPU 中执行,最后将渲染结果传回内存。TBR 架构下,三角面片更昂贵,而渲染像素更高效。(可以看到是将 Geometry list 放到内存上了)
注意:TBR 和 TBDR 的区别在于几何处理的数据到像素处理数据的传递

TBR
由于统一内存读写非常昂贵,TBR 减少了大量带宽需求,提高了缓存的 Locality,使得 ZS 测试、MSAA、拷贝、面片剔除等操作的开销变得很小,并且可以实现完全的片上延迟渲染。缺点是 Tile Lists 位于内存中,三角面数、VS 的输出限制较高,无法高效执行细分着色和几何着色。

TBR 每个 Pass 三个阶段是严格顺序执行的,不同 Pass 之间可以并行。首先并行粒度就不是 Draw Call 而是 Pass,也容易遇到 API 的不并行问题。

Tile_base_render
移动 GPU 的 TBR 设计正是适应了高分辨率 2D 渲染的应用场景:面片不多,占屏比小,低功耗。
一些系统可能会采用介于 IMR 和 TBR 两者之间的渲染模式,取决与如何权衡顶点和像素开销。(注:猜想随着硬件和渲染 API 的发展,GPU 的最终目标是完全的高性能片上渲染)

渲染硬件的实现

CPU 和 GPU 的设计差别:

CPUGPU
低线程数极高线程数
注重单线程性能注重计算的整体吞吐率
对内存延迟敏感吞吐率取决于硬件能效
CPU

CPU 的一般的运行流水线是,取出指令 → 读寄存器 → 计算 → 写寄存器,其中 “读-算” 为一个 CPU 周期(clock),它需要花费多个时钟周期(常见的是 5~10 个 cycles),如果能减少每个 CPU 周期的时钟周期,就能在同样的频率下,获得更好的性能。前后指令之间经常有依赖关系,如果让流水线停下等待上一条指令执行完毕,会对性能造成影响。

CPU 处理器硬件优化:

  1. 结果转发(Result Forwarding):前一个指令的结果在写入寄存器前,即可作为后面指令的输入

  2. 分支预测:运行时等待会对性能造成影响,CPU 还可以猜测条件分支的走向,提早运行预测的路径,如果猜测正确,CPU 流水线的效率得到提高,若猜测错误则进行回滚

  3. 乱序执行:当不存在数据依赖时,很多指令能细粒度地并行,CPU 可以调用多组 ALU 来同时运行,前提是这些指令必须是连续的,因此高端处理器在取出指令后,会重新组合再做超标量(Super Scalar)分发

  4. 推测执行(Speculative Execution):在空闲的单元上,执行可能需要执行的指令,比如同时执行 if 和 else 指令

  5. 推测数据预读(Speculative Data Fetch):预测并提前在缓存中读入可能需要的内存数据

  6. 大容量缓存:比如 L2、L3 缓存,来应对内存延迟

如此多的组件只是为了让 CPU 的单线程性能提升,但它们同样会占据芯片空间,也会增加功耗。基于能效考虑,GPU 可以学习的是 CPU 中的 SIMD(单指令多数据流)的硬件设计。

GPU 设计

GPU 渲染核心的管线一样是,取出指令 → 读寄存器 → 计算 → 写寄存器,其特性在于向量化计算,同时会有多组 ALU 参与相同的计算。一个 “读-算-写” 的 GPU 周期会占据多个时钟周期,打个比方每过 10 个 cycles,GPU 将多个线程的指令和数据读入(注:多个线程执行的是相同的指令),然后同时计算。
render_every10_cycles
渲染核心硬件优化:
这样设计的管线,每个周期内只进行一次计算操作,对单个线程来说是变慢了,但是保证了一定的吞吐量,良好的并行性避免了和 CPU 一样的需要乱序执行和推测执行。GPU 周期之间没有重叠,可以保证指令之间满足依赖条件,不需要结果转发和分支预测。

并行设计虽然对单线程的延迟不那么敏感,但与内存传输数据依然需要 ~150 个 GPU 时钟 cycles,而缓存也无法容下所有的渲染资源。GPU 的优势就在于线程数量极多,一部分线程向内存请求数据的同时,另一部分被唤起执行。所以数据预读和大容量缓存,在 GPU 面前也可能是多余的。

对于需要处理 RGBA 颜色或是 XYZW 矢量的 GPU 来说,SIMD 一次计算四个分量看上去很高效,但是还有大量的计算不能打包成 vec4,因此相比使用硬件矢量计算单元,SIMT(单指令多线程)是一种更具性价比的方案。

GPU 中执行相同指令的一组线程有很多名称,比如 Warp(nvidia), Wavefront(AMD), Subgroup quad(Arm)等。使用 SIMT 的好处的一个 Warp 的向量容量可以是变化的,多个线程共同分摊控制面 (Control Plane)开销。
SIMT
坏处是同一个 Warp 在执行分支语句的时候,如果有一部分需要执行 if 而另一部分执行 else,所有的线程都需要运行两个分支,然后过滤掉不需要的部分结果。这叫 Warp Divergence,只能通过 Shader 代码去避免。(注:实际情况中动态分支如果能控制好 Divergence,可以达到一定的正面优化效果)

最后需要说明,GPU 有硬件加速单元,可以对插值、纹理解压、纹理过滤、颜色混合等固定操作进行加速,以此提升能效。

SIMT : 执行相同指令的一组线程;
SIMD : 单个指令可以执行多个数据的运算;

Mali Shadercore

一个 Mali GPU 的渲染核心大概长这样,渲染核心没有标准定义,不同厂商的设计也不尽相同。
bifrost_architecture
ShaderCore 的内部逻辑如下:
ShaderCore

重要补充

我在简书上的 GPU 知识博客:
GPU 基础知识

OpenGL ES 3.0 英文版 第1章——OpenGL ES 3.0简介   第1章简单介绍OpenGL ES,概述了OpenGL ES 3.0图形管线,讨论了OpenGL ES 3.0的设计理念和限制,最后介绍了OpenGL ES 3.0中使用的一些约定和类型。   第2章——你好,三角形:一个OpenGL ES 3.0示例   第2章介绍绘制三角形的一个简单OpenGL ES 3.0示例。我们的目的是说明OpenGL ES 3.0程序的样子,向读者介绍一些API概念,并说明如何构建和运行OpenGL ES 3.0示例程序。   第3章——EGL简介   第3章介绍EGL——为OpenGL ES 3.0创建表面和渲染上下文的API。我们说明与原生窗口系统通信、选择配置和创建EGL渲染上下文及表面的方法,传授足够多的EGL知识,你可以了解到启动OpenGL ES 3.0进行渲染所需的所有知识。   第4章——着色器和程序   着色器对象和程序对象是OpenGL ES 3.0中最基本的对象。第4章介绍创建着色器对象、编译着色器和检查编译错误的方法。这一章还说明如何创建程序对象、将着色器对象连接到程序对象以及链接最终程序对象的方法。我们讨论如何查询程序对象的信息以及加载统一变量(uniform)的方法。此外,你将学习有关源着色器和程序二进制代码之间的差别以及它们的使用方法。   第5章——OpenGL ES着色语言   第5章介绍编写着色器所需的着色语言的基础知识。这些着色语言基础知识包括变量和类型、构造器、结构、数组、统一变量、统一变量块(uniform block)和输入/输出变量。该章还描述着色语言的某些更细微的部分,例如精度限定符和不变性。   第6章——顶点属性、顶点数组和缓冲区对象   从第6章开始(到第11章为止),我们将详细介绍管线,教授设置和编程图形管线各个部分的方法。这一旅程从介绍几何形状输入图形管线的方法开始,包含了对顶点属性、顶点数组和缓冲区对象的讨论。   第7章——图元装配和光栅化   在前一章讨论几何形状输入图形管线的方法之后,第7章将讨论几何形状如何装配成图元,介绍OpenGL ES 3.0中所有可用的图元类型,包括点精灵、直线、三角形、三角形条带和三角扇形。此外,我们还说明了在顶点上进行坐标变换的方法,并简单介绍了OpenGL ES 3.0管线的光栅化阶段。   第8章——顶点着色器   我们所介绍的管线的下一部分是顶点着色器。第8章概述了顶点着色器如何融入管线以及OpenGL ES 着色语言中可用于顶点着色器的特殊变量,介绍了多个顶点着色器的示例,包括逐像素照明和蒙皮(skinning)。我们还给出了用顶点着色器实现OpenGL ES 1.0(和1.1)固定功能管线的示例。   第9章——纹理   第9章开始介绍片段着色器,描述OpenGL ES 3.0中所有可用的纹理功能。该章提供了创建纹理、加载纹理数据以及纹理渲染的细节,描述了纹理包装模式、纹理过滤、纹理格式、压缩纹理、采样器对象、不可变纹理、像素解包缓冲区对象和Mip贴图。该章介绍了OpenGL ES 3.0支持的所有纹理类型:2D纹理、立方图、2D纹理数组和3D纹理。   第10章——片段着色器   第9章的重点是如何在片段着色器中使用纹理,第10章介绍编写片段着色器所需知道的其他知识。该章概述了片段着色器和所有可用的特殊内建变量,还演示了用片段着色器实现OpenGL ES 1.1中所有固定功能技术的方法。多重纹理、雾化、Alpha测试和用户裁剪平面的例子都使用片段着色器实现。   第11章——片段操作   第11章讨论可以适用于整个帧缓冲区或者在OpenGL ES 3.0片段管线中执行片段着色器后适用于单个片段的操作。这些操作包括剪裁测试、模板测试、深度测试、多重采样、混合和抖动。本章介绍OpenGL ES 3.0图形管线的最后阶段。   第12章——帧缓冲区对象   第12章讨论使用帧缓冲区对象渲染屏幕外表面。帧缓冲区对象有多种用法,最常见的是渲染到一个纹理。本章提供API帧缓冲区对象部分的完整概述。理解帧缓冲区对象对于实现许多高级特效(如反射、阴影贴图和后处理)至关重要。   第13章——同步对象和栅栏   第13章概述同步对象和栅栏,它们是在OpenGL ES 3.0主机应用和GPU执行中同步的有效图元。我们讨论同步对象和栅栏的使用方法,并以一个示例作为结束。   第14章——OpenGL ES 3.0高级编程   第14章是核心章节,将本书介绍的许多主题串联在一起。我们已经选择了高级渲染技术的一个样本,并展示了实现这些功能的示例。该章包含使用法线贴图的逐像素照明、环境贴图、粒子系统、图像后处理、程序纹理、阴影贴图、地形渲染
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值