图形处理单元 (chapter 3 The Graphic Processing Unit)(1-4)
Real-Time Rendering 4th 中文翻译 chapter 3目录
3.1 数据并行架构(Data-Parallel)
3.2 图形管线概述(GPU Pipeline Overview)
3.3 可编程的着色阶段(The Programmable Shader Stage)
3.4 可编程着色和API的发展(The Evolution of Programmable Shading and APIs)
3.5 顶点着色器(The Vertex Shader)
3.6 细分阶段(The Tessellation Stage)
3.7 几何着色器(The Geometry Shader)
3.8 像素着色器(The Pixel Shader)
3.9 合并阶段(The Merge Shader)
3.10 计算着色器 (The compute Shader)
从历史上看,图形加速开始于在与三角形重叠的每个像素扫描线上插值颜色,然后显示这些值。 包括访问图像数据的能力允许将纹理应用于表面。 添加用于插值和测试 z 深度的硬件提供了内置的可见性检查。 由于它们的频繁使用,此类进程致力于专用硬件以提高性能。 渲染管线的更多部分以及每个部分的更多功能在连续几代中被添加。专用图形硬件相对于 CPU 的唯一计算优势是速度,但速度至关重要。
在过去的二十年中,图形硬件经历了令人难以置信的转变。 第一个包含硬件顶点处理的消费类图形芯片(NVIDIA 的 GeForce256)于 1999 年面世。NVIDIA 创造了图形处理单元 (GPU,Graphics Processing Unit) 一词以将 GeForce 256 与以前可用的仅光栅化芯片区分开来,并且它一直流行。 在接下来的几年中,GPU 从复杂的固定功能管线的可配置实现发展为高度可编程的白板,开发人员可以在其中实现自己的算法。 各种可编程着色器是控制 GPU 的主要方式。 为了提高效率,管线的某些部分仍然是可配置的,而不是可编程的,但趋势是朝着可编程性和灵活性方向发展
GPU 通过专注于一小部分高度可并行化的任务来获得极快的速度。 例如,他们拥有专门用于实现 z 缓冲区、快速访问纹理图像和其他缓冲区以及查找哪些像素被三角形覆盖的定制芯片。 第 23 章介绍了这些元素如何执行其功能。更重要的是尽早了解 GPU 如何为其可编程着色器实现并行性。
3.3 节解释了着色器是如何工作的。 现在,您需要知道的是着色器核心是一个小型处理器,它执行一些相对独立的任务,例如将顶点从其在世界中的位置转换为屏幕坐标,或计算被着色器覆盖的像素的颜色 三角形。 每帧都有数千或数百万个三角形被发送到屏幕,每秒可能有数十亿次着色器调用(shader invocations),即运行着色器程序的单独实例。
首先,延迟(latency)是所有处理器都面临的一个问题。 访问数据需要一些时间。 考虑延迟的一个基本方法是信息离处理器越远,等待的时间就越长。 第 23.3 节更详细地介绍了延迟。 存储在存储芯片中的信息比本地寄存器中的信息需要更长的时间来访问。 18.4.1 节更深入地讨论了内存访问。 关键是等待数据被检索意味着处理器停滞,这会降低性能。
3.1 数据并行架构(Data-Parallel)
不同的处理器架构使用各种策略来避免停顿。 CPU 经过优化以处理各种数据结构和大型代码库。 CPU 可以有多个处理器,但每个处理器都以串行方式运行代码,有限的 SIMD (Single Instruction Multiple Data) 向量处理是一个小例外。 为了最大限度地减少延迟的影响,CPU 的大部分芯片都由快速本地缓存组成,内存中充满了接下来可能需要的数据。 CPU 还通过使用分支预测(branch prediction)、指令重新排序(instruction reordering)、寄存器重命名(register renaming)和缓存预取(cache prefetching )等巧妙技术来避免停顿.
注:SIMD(Single Instruction Multiple Data)是单指令多数据,在GPU的ALU单元内,一条指令可以处理多维向量(一般是4D)的数据。特指一个Thread在一个Croe中ALU可以进行向量计算。或者说一条指令控制多个线程\寄存器进行相同的操作,对于GPU来说就是:单个控制器控制多个平行的处理微元。
Image
比如,有以下shader指令:
float4 c = a + b; // a, b都是float4类型
对于没有SIMD的处理单元,需要4条指令将4个float数值相加,汇编伪代码如下:
ADD c.x, a.x, b.x
ADD c.y, a.y, b.y
ADD c.z, a.z, b.z
ADD c.w, a.w, b.w
但有了SIMD技术,只需一条指令即可处理完(利用可以进行4维向量加减的ALU处理):
SIMD_ADD c, a, b
回归正题:
而GPU 采用不同的方法。 GPU 的大部分芯片区域专用于大量处理器,称为着色器核心(Shader codes),通常有数千个。 GPU 是一个流处理器(Stream Processor),其中轮流处理相似数据的有序集合。 由于这种相似性——例如一组顶点或像素——GPU 可以以大规模并行方式处理这些数据。 另一个重要因素是这些调用尽可能独立,这样它们就不需要来自相邻调用的信息并且不共享可写内存位置。 有时会打破此规则以允许新的和有用的功能,但此类异常是以潜在延迟为代价的,因为一个处理器可能会等待另一个处理器完成其工作。
注:流处理器(SP,Stream Processor)由流处理单元组成。而流处理单元是统一架构GPU内通用标量着色器的称谓。流处理单元(统一架构)既可以完成Vertex功能既可以完成VS(Vertex Shader,顶点着色器)运算,也可以完成PS(Pixel Shader,像素着色器)运算,而且可以根据需要组成任意VS/PS比例,从而给开发者更广阔的发挥空间。它同时是组成渲染管线的一部分,一条完整的渲染管线包括流处理器和纹理贴图处理器。这个流处理单元相当于神经元,流处理单元越多,GPU性能越好。流处理单元。
GPU 针对吞吐量(throughput进行了优化,吞吐量定义为可以处理数据的最大速率。 然而,这种快速处理是有代价的。 由于专用于高速缓存和控制逻辑的芯片区域较少,每个着色器核心的延迟通常比 CPU 处理器遇到的延迟高得多
假设一个网格已光栅化,两千个像素有片段(fragments)需要处理; 像素着色器程序将被调用两千次。 想象一下只有一个着色器处理器,世界上最弱的 GPU。 它开始为 2000 的第一个片段执行着色器程序。 着色器处理器对寄存器中的值执行一些算术运算。 寄存器是本地的并且可以快速访问,因此不会发生停顿。 然后着色器处理器开始执行诸如纹理访问之类的指令; 例如,对于给定的表面位置,程序需要知道应用于网格的图像的像素颜色。 纹理是一个完全独立的资源,而不是像素程序本地内存的一部分,并且可能涉及到纹理访问。 内存提取可能需要数百到数千个时钟周期,在此期间 GPU 处理器什么都不做。 此时着色器处理器会停止,等待返回纹理的颜色值。
注:例如在OpenGL中,纹理(2D纹理)是 uniform sampler2D 类型,是在运行阶段从CPU发送到GPU缓存中的
为了让这个糟糕的 GPU 变得更好,给每个片段一个小的存储空间用于它的本地寄存器。 现在,着色器处理器可以切换并执行另一个片段,即 2000 中的第二个片段,而不是在纹理获取时停止。 这个切换非常快,除了注意到第一个正在执行的指令外,第一个或第二个片段中的任何内容都不会受到影响。 现在执行第二个片段。 与第一个相同,执行一些算术函数,然后再次遇到纹理提取。 着色器核心现在切换到另一个片段,即第三个片段。 最终两千个片段都这样处理。 此时着色器处理器返回到第一个片段。 此时纹理颜色已获取完成并可供使用,因此着色器程序可以继续执行。 处理器以相同的方式继续执行,直到遇到另一条已知会停止执行的指令,或者程序完成。 与着色器处理器(shader processor)专注于单个片段相比,单个片段的执行时间会更长,但片段整体的整体执行时间会大大减少。
在这种架构中,通过切换到另一个片段让 GPU 保持忙碌来隐藏延迟。 GPU 通过将指令执行逻辑与数据分离,使这种设计更进一步。 称为单指令、多数据 (SIMD),这种安排在固定数量的着色器程序上以固定步骤执行相同的命令。 SIMD 的优势在于,与使用单独的逻辑和调度单元来运行每个程序相比,处理数据和切换所需的芯片(和功率)要少得多。 将我们的 2000 个片段示例转换为现代 GPU 术语,片段的每个像素着色器调用称为一个线程。 这种类型的线程不同于 CPU 线程。 它包含一些用于着色器输入值的内存,以及着色器执行所需的任何寄存器空间。 使用相同着色器程序的线程被捆绑成组,NVIDIA 称为 warp,AMD 称为 wavefronts。 一个 warp/wavefront 被安排由一些 GPU 着色器核心执行,从 8 到 64 个,使用 SIMD 处理。 每个线程都映射到一个 SIMD 通道
假设我们有两千个线程要执行。 NVIDIA GPU 上的 线程束(warp) 包含 32 个线程。 这会产生 2000/32 = 62.5 个 warp,这意味着分配了 63 个 warp,其中一个 warp 有一半是空的。 warp 的执行类似于我们的单 GPU 处理器示例。 着色器程序在所有 32 个处理器上以固定步骤执行。 当遇到内存提取时,所有线程都会同时遇到它,因为对所有线程都执行相同的指令。 提取信号表明这个warp将停止,所有线程都在等待它们的(不同的)结果。 warp 不会停止,而是换出另一个 32 线程的 warp,然后由 32 个内核执行。 这种交换与我们的单处理器系统一样快,因为当一个 warp 被换入或换出时,每个线程内的数据都不会被触及。 每个线程都有自己的寄存器,每个 warp 跟踪它正在执行的指令。 换入一个新的 warp 只是将一组核心指向一组不同的线程来执行; 没有其他开销。 warp执行或换出直到全部完成。 见图 3.1。
Image
图 3.1。 简化的着色器执行示例。 三角形片元(称为线程,threads),被捆绑成为warp。 每个 warp 显示为四个线程,但实际上有 32 个线程。 要执行的着色器程序有五个指令长。 四个GPU着色器处理器的集合在第一次 warp 时执行这些指令,直到在“txr”命令上检测到停顿条件,这需要时间来获取其数据。 第二个 warp 被换入,着色器程序的前三个指令被应用到它,直到再次检测到停顿。 在第三个 warp 换入并停止后,执行通过换入第一个 warp 并继续执行来继续。 如果此时尚未返回其“txr”命令的数据,则执行真正停止,直到这些数据可用。 每个线程依次结束。
注:寄存器占据越多 ,warps 越少,tex 操作会导致 warps 切换,如果单个 SM 的warps切换完了 ,tex 还没做完的话就会造成等待,当然这个不一定,在低端机上可能会,中高端可能不会,我们并不清楚这个具体耗时哪个更快,但是我们能从 shader code 上去避免这个。实际操作下来不断的 tex操作,确实会造成性能灾难性降低,所以在中间插入其他计算,相比只有单个 tex 操作, 然后每个 warps 只执行tex 然后就立即切换下一个 warps ,或许能减少造成的延迟。
在我们的简单示例中,纹理获取内存的等待时间可能导致warp掉出。实际上,因为交换成本非常低,所以可以将warp换成较短的延迟。还有其他几种用于优化执行的技术,但 warp交换(warp-swapping)是所有GPU使用的主要延迟隐藏机制。此过程的效率如何涉及多个因素。例如,如果线程很少,那么几乎不会创建任何warp,从而使延迟隐藏成为问题。
着色器程序的结构是影响效率的重要因素。 一个主要因素是每个线程使用的寄存器数量。 在我们的示例中,我们假设 2000 个线程可以同时驻留在 GPU 上。 与每个线程关联的着色器程序所需的寄存器越多,线程就越少,因此可以驻留在 GPU 中的warp也就越少。 warp的短缺可能意味着无法通过交换来缓解延迟。 驻留的warps被称为“in flight”,这个数字称为占用率。 高占用率意味着有许多 warp 可用于处理,因此空闲处理器的可能性较小。 低占用率通常会导致性能不佳。 内存获取的频率也会影响需要多少延迟隐藏。 Lauritzen 概述了占用率如何受寄存器数量和着色器使用的共享内存的影响。 Wronski 讨论了理想的占用率如何根据着色器执行的操作类型而变化。
影响整体效率的另一个因素是由“if”语句和循环引起的动态分支。 假设在着色器程序中遇到“if”语句。 如果所有线程都求值并采用相同的分支,则 warp 可以继续而无需担心其他分支。 然而,如果某些线程,甚至一个线程采用备用路径,则 warp 必须执行两个分支,丢弃每个特定线程不需要的结果 。 这个问题称为线程发散,其中一些线程可能需要执行循环迭代或执行 warp 中其他线程不需要的“if”路径,从而使它们在这段时间内处于空闲状态。
Image
注:在上图中,一个SM(Stream MultiProcesssor)中,有32个着色器核心,也就是线程,他们被 Warp调度器(Warp Scheduler)控制,如右图中不同Wrap执行不同指令,每次GPU调度一个warp里的32个线程执行同一条指令,其中各个线程对应的数据资源不同。对于左边,只会保留if或是else线程的结果,另一些线程则空闲
所有 GPU 都实现了这些架构思想,从而导致系统具有严格的限制,但每瓦计算能力却很高。 理解这个系统是如何运作的将帮助您作为程序员更有效地利用它提供的功能。 在接下来的部分中,我们将讨论 GPU 如何实现渲染管线、可编程着色器如何运行以及每个 GPU 阶段的演变和功能。
3.2 图形管线概述(GPU Pipeline Overview)
GPU 实现了第 2 章中描述的概念性几何处理、光栅化和像素处理等渲染管线阶段。这些阶段分为几个具有不同程度的可配置性或可编程性的硬件阶段。 下图显示了根据可编程或可配置性进行颜色编码的各个阶段。 请注意,这些物理阶段的划分与第 2 章中介绍的功能阶段有所不同。
Image
注:这些阶段根据用户对其操作的控制程度进行颜色编码。 绿色阶段是完全可编程的。 虚线表示可选阶段。 黄色阶段是可配置的但不是可编程的,例如,可以为合并阶段设置各种混合模式。 蓝色阶段的功能完全固定。
GPU渲染管线概览:
[*]顶点着色器(The Vertex Shader)是完全可编程的阶段,顶点着色器可以对每个顶点进行诸如变换和变形在内的很多操作,提供了修改/创建/忽略顶点相关属性的功能,这些顶点属性包括颜色、法线、纹理坐标和位置。顶点着色器的必须完成的任务是将顶点从模型空间转换到齐次裁剪空间。
[*]几何着色器(The Geometry Shader)位于顶点着色器之后,允许 GPU 高效地创建和销毁几何图元。几何着色器是可选的,完全可编程的阶段,主要对图元(点、线、三角形)的顶点进行操作。几何着色器接收顶点着色器的输出作为输入,通过高效的几何运算,将数据输出,数据随后经过几何阶段和光栅化阶段的其他处理后,会发送给片段着色器。
[*]裁剪(Clipping)属于可配置的功能阶段,在此阶段可选运行的裁剪方式,以及添加自定义的裁剪面。
[*]屏幕映射(Screen Mapping)、三角形设置(Triangle Setup)和三角形遍历(Triangle Traversal)阶段是固定功能阶段。
[*]像素着色器(Pixel Shader,Direct3D 中的叫法)常常又称为片断着色器,片元着色器(FragmentShader,OpenGL 中的叫法),是完全可编程的阶段,主要作用是进行像素的处理,让复杂的着色方程在每一个像素上执行。
[*]合并阶段(The Merger Stage)处于完全可编程和固定功能之间,尽管不能编程,但是高度可配置,可以进行一系列的操作。其除了进行合并操作,还分管颜色修改(Color Modifying),Z 缓冲(Zbuffer),混合(Blend),模板(Stencil)和相关缓存的处理。
我们在这里描述了 GPU 的逻辑模型(logical model),即通过 API 向您(程序员)公开的模型。 正如第 18 章和第 23 章所讨论的,这个逻辑管线(物理模型)的实现取决于硬件供应商。 通过向相邻的可编程阶段添加命令,可以在 GPU 上执行逻辑模型中固定功能的阶段。 流水线中的单个程序可以拆分为由单独的子单元执行的元素,或者完全由单独的通道执行。 逻辑模型可以帮助您推断影响性能的因素,但不应将其误认为是 GPU 实际实现流水线的方式。
顶点着色器(vertex shader)是一个完全可编程的阶段,用于实现几何处理阶段。 几何着色器是一个完全可编程的阶段,它在图元(点、线或三角形)的顶点上运行。 它可用于执行每个图元的着色操作、销毁图元或创建新图元。 曲面细分阶段和几何着色器都是可选的,并非所有 GPU 都支持它们,尤其是在移动设备上。
裁剪、三角形设置和三角形遍历阶段由固定功能硬件实现。 屏幕映射受窗口和视口设置的影响,在内部形成简单的比例和重新定位。 像素着色器阶段是完全可编程的。 虽然合并阶段不可编程,但它是高度可配置的,可以设置为执行各种各样的操作。 它实现了“合并”功能阶段,负责颜色修改、z 缓冲区、混合、模板和任何其他与输出相关的缓冲区。 像素着色器执行与合并阶段一起构成了第 2 章中介绍的概念性像素处理阶段。
随着时间的流逝,GPU管线已从硬编码操作演变为增加灵活性和控制能力。可编程着色器阶段的引入是这一发展过程中最重要的一步。下一节将介绍各个可编程阶段的通用功能。
3.3 可编程的着色阶段(The Programmable Shader Stage)
现代着色器程序使用统一的着色器设计。 这意味着与顶点、像素、几何和曲面细分相关的着色器共享一个通用的编程模型。 在内部,它们具有相同的指令集架构 (ISA,instruction set architecture)。 实现此模型的处理器在 DirectX 中称为“通用着色器核心”(common-shader core),具有此类核心的 GPU 被称为具有统一着色器架构。 这种架构背后的想法是着色器处理器可用于各种角色,GPU 可以根据需要分配它们。 例如,一组具有小三角形的网格比每个由两个三角形组成的大正方形需要更多的顶点着色器处理。 具有独立的顶点和像素着色器核心池的 GPU 意味着保持所有核心忙碌的理想工作分配是严格预先确定的。 使用统一的着色器核心,GPU 可以决定如何平衡这种负载。
注:前面提到的流处理器(通用标量着色器)是统一架构的在这里得到体现
描述整个着色器编程模型远远超出了本书的范围,并且已经有许多文档、书籍和网站了。 着色器使用 C风格的着色语言(shading languages)进行编程,例如 DirectX 的高级着色语言 (HLSL, High-Level Shading Language) 和 OpenGL 的着色语言(GLSL, OpenGL Shading Language)。 它们可以编译为 独立于机器的汇编语言,也称为中间语言(IL,Intermediate Language或 DXIL),以提供硬件独立性。 中间(Intermediate)表示还可以允许离线编译和存储着色器程序。 这种中间语言由驱动程序转换为特定 GPU 的 ISA。 控制台编程通常避免中间语言步骤,因为那时只有一个 ISA 用于系统。
基本数据类型是 32 位单精度浮点标量和向量,尽管向量只是着色器代码的一部分,并且如上所述不受硬件支持。 在现代 GPU 上,还支持 32 位整数和 64 位浮点数。 浮点向量通常包含位置 (xyzw)、法线、矩阵行、颜色 (rgba) 或纹理坐标 (uvwq) 等数据。 整数最常用于表示计数器、索引或位掩码。 还支持聚合数据类型(Aggregate data types),例如结构、数组和矩阵。
Draw Call调用图形 API 来绘制一组图元,从而导致图形管线执行并运行其着色器。 每个可编程着色器阶段都有两种类型的输入:统一输入(uniform inputs),其值在整个 Draw Call 期间保持不变(但可以在Draw Call之间更改),以及可变输入(varying inputs),即来自三角形顶点或光栅化的数据。 例如,像素着色器可以提供光源的颜色作为统一值,三角形表面的位置每个像素都在变化,因此也是可变的。 纹理是一种特殊的 Uniform 输入,曾经总是应用于表面的彩色图像,但现在可以将其视为任何大型数据阵列。
底层虚拟机为不同类型的输入和输出提供了特殊的寄存器。 用于 Uniform 输入的可用常量寄存器的数量远大于可用于 Varying 的输入或输出的寄存器的数量。 发生这种情况是因为需要为每个顶点或像素单独存储不同的输入和输出,因此对于需要多少存在自然限制。 Uniform输入存储一次,并在Draw Call中的所有顶点或像素中重复使用。 虚拟机还有通用临时寄存器,用于暂存空间。 所有类型的寄存器都可以使用临时寄存器中的整数值进行数组索引。 着色器虚拟机的输入和输出如图 3.3 所示。
Image
注:图 3.3。 在Shader Model 4.0下的统一虚拟机架构和寄存器布局。 最大可用数量显示在每个资源旁边。 由斜杠分隔的三个数字指的是顶点、几何和像素着色器的限制(从左到右)。例如:常量寄存器的16 buffers of register表示寄存器数量为4096,分为16个缓冲区)
注:Shader Model 4.0 是语言特性规范(简称SM),规定了这个版本的语言可以使用哪些intrinsic和支持哪些特性。 另外 Shader Model 是用于制约的 HLSL,和 GLSL 没关系。虽然没关系,但是影响制约着GLSL.但同时GLSL可以有超出SM设计的实现.
更专业的解答:
微软的关于SM的文档:
着色模型对比:
Image
图形计算中常见的操作可在现代GPU上高效执行。着色语言通过*和+等运算符公开了这些运算中最常见的运算(例如加法和乘法)。其余的通过内在函数(intrinsic functions)公开,例如atan(),sqrt(),log()以及为GPU优化的许多其他函数。对于更复杂的运算,也存在函数,例如向量归一化(vector normalization)和反射(reflection),叉积(cross product),矩阵转置(matrix transpose)和行列式计算(determinant computations)。
术语“流控制”(flow control)是指使用分支指令来更改代码执行流。与流控制相关的指令用于实现高级语言构造,例如“if”和“ case”语句,以及各种类型的循环。着色器支持两种类型的流控制。静态流控制(Static flflow control)分支基于统一输入的值。这意味着代码流在 Draw Call 中是恒定的。静态流控制的主要好处是允许将相同的着色器用于各种不同的情况(例如,不同数量的灯光)。由于所有调用都采用相同的代码路径,因此没有线程差异。动态流控制(Dynamic flflow control)基于变化的输入的值,这意味着每个片段可以不同地执行代码。这比静态流控制功能强大得多,但会降低性能,尤其是在着色器调用之间代码流发生不规则变化时。
3.4 可编程着色和API的发展(The Evolution of Programmable Shading and APIs)
可编程着色框架的构想可以追溯到1984年,当时库克(Cook)的着色树(shade trees)。一个简单的着色器及其相应的着色树如图3.4所示。RenderMan着色语言(The RenderMan Shading Language) 是在1980年代后期从这个想法发展而来的。如今,它与其他不断发展的规范(例如,开放着色语言(OSL)项目)一起用于电影制作渲染。
Image
图 3.4。 一个简单的铜着色器的着色树,及其相应的着色器语言程序。
消费者级图形硬件最早是 3dfx Interactive 于1996年10月1日成功推出的。今年的时间表请参见图3.5。他们的 Voodoo 图形卡能够以高品质和高性能来渲染《Quake》游戏,因此很快就被采用。该硬件始终实现了固定功能的流水线。在GPU原生支持可编程着色器之前,曾有各种尝试通过多次渲染实时实现可编程着色操作。Quake III:Arena脚本语言是1999年在该领域的首个广泛的商业成功案例。如本章开头所述,NVIDIA的 GeForce256 是第一个被称为GPU的硬件,它不是可编程的(not programmable),但是它是可配置的(configurable)。
Image
图3.5。一些API和图形硬件版本的时间表。
在2001年初,NVIDIA 的 GeForce 3 是第一个支持可编程顶点着色器的GPU,该着色器通过 DirectX 8.0 和 OpenGL 扩展公开。这些着色器以一种类似于汇编的语言进行编程,该语言被驱动程序即时转换为微代码。像素着色器也包含在 DirectX 8.0 中,但是像素着色器没有达到实际的可编程性——驱动程序将受支持的有限“程序”转换为纹理混合状态,然后将其连接到硬件“寄存器组合器”(register combiners)。这些“程序”不仅限于长度(不超过12条指令),而且缺少重要的功能。通过对 RenderMan 的研究,Peercy等人确定了相关的纹理读取和浮点数据 对真正的可编程性是至关重要的。
着色器此时不允许进行流控制(分支,branching),因此必须通过计算两个项以及在结果之间进行选择或内插(interpolating)来模拟条件选择。DirectX 定义了着色器模型(Shader Model,SM)的概念,以区分具有不同着色器功能的硬件。2002年,包括 Shader Model 2.0在内的 DirectX 9.0 发行了,该版本具有真正可编程的顶点和像素着色器。在 OpenGL 下,使用各种扩展功能也公开了类似的功能。添加了对任意依赖的纹理读取的支持以及对16位浮点值的存储的支持,最终完成了 Peercy 等人确定的一组要求。诸如指令,纹理和寄存器之类的着色器资源的限制增加了,因此着色器变得能够产生更复杂的效果。还增加了对流控制(flow control)的支持。着色器的长度和复杂性不断增长,使得汇编编程模型变得越来越麻烦。幸运的是,DirectX 9.0 还包含 HLSL 。此着色语言是由 Microsoft 与 NVIDIA 合作开发的。大约在同一时间,OpenGL ARB(架构审查委员会)发布了 GLSL,一种与 OpenGL 非常相似的语言。这些语言在很大程度上受到C编程语言的语法和设计理念的影响,其中包括来自 RenderMan 着色语言的元素。
Shader Model 3.0 于2004年推出,并添加了动态流控制(Dynamic Flow Control),使着色器功能更加强大。它还将可选功能转变为需求,进一步增加了资源限制,并增加了对顶点着色器中纹理读取的有限支持。当在2005年下半年(Microsoft 的 Xbox 360)和2006年下半年(Sony Computer Entertainment 的 PLAYSTATION 3系统)推出新一代游戏机时,它们配备了Shader Model 3.0级GPU。任天堂的 Wii console 是最后一批著名的固定功能GPU之一,该GPU最初于2006年底交付。纯固定功能产品线在这一点上早已消失了。着色器语言已经发展到可以使用各种工具来创建和管理它们的地步。图3.6显示了使用库克(Cook)的着色树(Shade Tree)概念的一种此类工具的屏幕截图。
可编程性的下一个重大进步也是在2006年底左右。DirectX 10.0 中包含的Shader Model 4.0引入了几个主要功能,例如几何体着色器(geometry shader)和流输出(stream output)。Shader Model 4.0包括适用于所有着色器(顶点,像素和几何图形)的统一编程模型,这是先前描述的统一着色器设计。资源限制进一步增加,并增加了对整数数据类型(包括按位运算)的支持。OpenGL 3.3中GLSL 3.30的引入提供了类似的着色器模型。
Image
注:用于着色器设计的视觉着色器图形系统。 各种操作都封装在功能框里,可以在左边选择。 选中后,每个功能框都有可调整的参数,如右图所示。 每个功能框的输入和输出相互链接以形成最终结果,显示在中间框架的右下方。 (截图来自“mental mill”,mental images inc.)
2009年发布了DirectX 11和Shader Model 5.0,添加了细分阶段着色器(tessellation stage)和计算着色器(compute shader),也称为 DirectCompute。该版本还专注于更有效地支持CPU多元处理(Multiprocessing),这是第18.5节中讨论的主题。OpenGL在4.0版中添加了细分着色器,在4.3版中添加了计算着色器。DirectX和OpenGL的发展不同。两者都设置了特定版本发行所需的一定级别的硬件支持。Microsoft控制DirectX API,因此直接与独立硬件供应商(IHV)(例如AMD,NVIDIA和Intel)以及游戏开发商和计算机辅助设计软件公司合作,以确定要公开的功能。OpenGL由非营利组织Khronos Group管理的硬件和软件供应商联盟开发。由于涉及的公司数量众多,API功能通常在DirectX中引入OpenGL之后的一段时间内就会出现。但是,OpenGL允许特定于供应商的或更广泛的扩展(extensions),这些扩展允许在发行版正式支持之前使用最新的GPU功能。
注:多元处理(英语:Multiprocessing),也译为多处理器处理,当一个系统,能够支援一个以上处理器,以及能够将计算工作分配给这些处理器,也被称为是多元处理器系统(Multiprocessingsystem)。
当系统拥有多个处理器时,在同一时间中,可能有数个程式在执行。有时候,运行并行性程式,也会被称为是多元处理。只是当使用在软件时,通常会称为多元程式(multi-programming),或多任务处理(multitasking)。多元处理主要用于指超过一个以上处理器的电脑硬件架构。
API的下一个重大变化是由AMD在2013年推出了MantleAPI。Mantle与视频游戏开发商DICE合作开发的,其目的是消除大部分图形驱动程序的开销,并将此控件直接交给开发人员。除了这种重构之外,还进一步支持有效的CPU多元处理。这类新的API专注于大大减少CPU在驱动程序中花费的时间,以及更有效的CPU多处理器支持(第18章)。在Mantle中开创的创意被Microsoft采纳,并在2015年以DirectX 12的形式发布。请注意,DirectX 12并不专注于公开新的GPU功能-DirectX 11.3公开了相同的硬件功能。这两个API均可用于将图形发送到虚拟现实系统,例如Oculus Rift和HTC Vive。但是,DirectX 12是对API的彻底重新设计,可以更好地映射到现代GPU架构。低开销的驱动程序对于以下应用程序很有用:CPU驱动程序成本引起瓶颈,或者使用更多CPU处理器进行图形处理可能会提高性能。从较早的API移植可能很困难,并且天真的实现可能会导致性能降低。
苹果于2014年发布了自己的低开销API(称为Metal)。Metal首次在iPhone 5S和iPad Air等移动设备上可用,一年后,可通过OS X El Capitan访问较新的Macin代码。除效率外,减少CPU使用率还可以节省功耗,这是移动设备上的重要因素。该API具有自己的着色语言,适用于图形和GPU计算程序。
AMD将其Mantle工作捐赠给了Khronos Group,后者于2016年初发布了自己的新API,名为Vulkan。与OpenGL一样,Vulkan可在多个操作系统上工作。Vulkan使用一种称为SPIR V的新的高级中间语言,该语言既用于着色器表示又用于常规GPU计算。预编译的着色器是可移植的,因此可以在支持所需功能的任何GPU上使用。Vulkan也可以用于非图形GPU计算,因为它不需要显示窗口。Vulkan与其他低开销驱动程序的显着区别是,它旨在与多种系统一起使用,从工作站到移动设备。
在移动设备上,规范是使用OpenGL ES。“ ES”代表嵌入式系统(Embedded Systems),因为此API是为移动设备而开发的。当时的标准OpenGL在其某些调用结构中相当庞大且缓慢,并且需要支持很少使用的功能。OpenGL ES 1.0于2003年发布,是OpenGL 1.3的简化版本,描述了固定功能的管线。虽然 DirectX 的发布与支持它们的图形硬件的发布是同步的,但是开发针对移动设备的图形支持的方式却并不相同。例如,2010年发布的第一台iPad实施了OpenGL ES 1.1。OpenGL ES 2.0规范于2007年发布,提供了可编程的着色(programmable shading)。它基于OpenGL 2.0,但没有固定功能组件,因此与OpenGL ES 1.1不向后兼容。OpenGL ES 3.0于2012年发布,提供了多个渲染目标,纹理压缩,变换反馈,实例化以及更广泛的纹理格式和模式等功能,并改进了着色器语言。OpenGL ES 3.1添加了计算着色器,而3.2添加了几何和曲面细分着色器,以及其他功能。第23章将更详细地讨论移动设备架构。
OpenGL ES的一个分支是基于浏览器的API WebGL,可通过JavaScript调用。该API的第一版发布于2011年,可在大多数移动设备上使用,因为它的功能等效于OpenGL ES 2.0。与OpenGL一样,扩展允许访问更高级的GPU功能。WebGL 2假定支持OpenGL ES 3.0。
WebGL特别适合在教室中试用功能或使用:
[*]它是跨平台的,可在所有个人计算机和几乎所有移动设备上使用。
[*]驱动程序批准由浏览器处理。即使一个浏览器不支持特定的GPU或扩展,通常另一个浏览器也支持。
[*]代码被解释而不是编译,并且仅需要文本编辑器即可进行开发。
[*]大多数浏览器都内置了调试器,可以检查在任何网站上运行的代码。
[*]可以通过将程序上传到网站或Github来进行部署。
更高级别的场景图形和效果库(例如three.js )使您可以轻松访问代码,以获取各种更复杂的效果,例如着色算法(shadow algorithms),后处理效果(post-processing effects),基于物理的着色(physically based shading)和延迟渲染(deferred rendering)。
本文使用 Zhihu On VSCode 创作并发布
页:
[1]