本文由@浅墨_毛星云 出品,首发于知乎专栏,转载请注明出处。
文章链接: https://zhuanlan.zhihu.com/p/32928016
这是一篇很特殊的文章。它将会是这个系列文章主线的最后一篇。
不知不觉中,专栏中【《Real-Time Rendering 3rd》 提炼总结】系列文章已经增加到了十二篇。从2017年写到了2018年,从渲染管线、高级着色、延迟渲染,一路写到全局光照、光线追踪、非真实感渲染,到这篇文章的渲染管线优化。
相信不仅是我自己在阅读和总结“实时渲染的圣经”《Real-Time Rendering 3rd》的过程中受益良多,一路阅读这个系列文章的朋友们,也应该是颇有收获。
感谢大家一直以来的支持与陪伴。
导读
这篇文章约1万8千字,构成主要分为上篇(渲染管线瓶颈定位策略),下篇(渲染管线优化策略),以及常用的性能分析工具的列举三部分,详细目录如下。
文中列举了渲染管线各个阶段中用到的几十种主流的优化策略。其中,个人印象比较深刻的优化方法有使用实例(Instance)结合层次细节和impostors方法来对多人同屏场景的渲染进行优化,以及使用纹理页(Texture Pages)来进行批次的尺寸最大化。
这篇文章会是《Real-Time Rendering 3rd》第十五章“Pipeline Optimization”和《GPU Gem I》第28章“Graphics Pipeline Performance”的一个结合,而不是之前一贯的《Real-Time Rendering 3rd》的单篇章节为主线。
需要吐槽的是,如果你对照阅读《GPU Gem I》的英文原版和中文翻译版,会发现中文翻译版中有一些不合理甚至曲解英文原文意思的地方,在第五部分性能与实这一部分尤其明显。
OK,正文开始。
一、渲染管线的构成
通常,可以将渲染管线的流程分为CPU和GPU两部分。下图显示了图形渲染管线的流程,可以发现,在GPU中存在许多并行运算的功能单元,本质上它们就像独立的专用处理器,其中存在许多可能产生瓶颈的地方。包括顶点和索引的取得、顶点着色(变换和照明,Transform & Lighting,即T&L)、片元着色和光栅操作( Raster Operations ,ROP)。
图1 图形渲染管线
如《Real-Time Rendering 3rd》第二章所述, 图形的渲染过程基于由三个阶段组成的管线架构:
基于这样的管线架构,其中的任意一个阶段,或者他们之间的通信的最慢的部分,都可能成为性能上的瓶颈。瓶颈阶段会限制渲染过程中的整个吞吐量,从而影响总结渲染的性能,所以不难理解,瓶颈的部分便是进行优化的主要对象。
图2 渲染管线架构
若有对渲染管线架构不太熟悉的朋友,具体可以移步回看这个系列的第二篇文章《【《Real-TimeRendering 3rd》 提炼总结】(二) 第二章 · 图形渲染管线 The Graphics Rendering Pipeline》
二、渲染管线的优化概览
准确定位瓶颈是渲染管线优化的关键一步。若没有很好确认瓶颈就进行盲目优化,将造成大量开发的工作的无谓浪费。
根据以往的优化经验,可以把优化的过程归纳为以下基本的确认和优化的循环:
需要注意的是,在经过一次优化步骤后,瓶颈位置可能依然在优化前的位置,也可能不在。比较好的想法是,尽可能对瓶颈阶段进行优化,保证瓶颈位置能够转移到另外一个阶段。在这个阶段再次成为瓶颈之前,必须对其他阶段进行优化处理,这也是为什么不能在一个阶段上进行过多优化的原因。
同一帧画面中,瓶颈位置也有可能改变。由于某个时候要渲染很多细小的三角形,这个时候,几何阶段就可能是瓶颈;在画面后期,由于要覆盖屏幕的大部分三角形单元进行渲染,因此这时光栅阶段就可能成为瓶颈。因此,凡涉及渲染瓶颈问题,即是指画面中花费时间最多的阶段。
在使用管线结构的时候应该意识到,如果不能对最慢的阶段进行进一步优化,就要使其他阶段与最慢阶段的工作负载尽可能一样多(也就是既然都要等瓶颈阶段,不妨给其他阶段分配更多任务来改善最终的表现,反正是要等)。由于没有改变最慢阶段的速度,因此这样做并没有改变最终的整个性能。例如,假定应用程序阶段成为瓶颈,需要花费50ms,而其他阶段仅需要花费25ms。这意味着,在不改变管线渲染速度(50ms,即每秒20帧)的情况下,几何阶段和光栅化阶段可以在50ms内完成各自任务。这时,可以使用一个更高级的光照模型或者使用阴影和反射来提高真实感(在不增加应用程序阶段工作负载的前提下)。
管线优化的一种大致思路是,先将渲染速度最大化,然后使得非瓶颈部分和瓶颈部分消耗同样多的时间(如上文所述,这里的思想是,既然要等,不等白不等,不妨多给速度快的部分分配更多工作量,来达到更好的画面效果)。但这种想法已经不适于不少新架构,如XBOX 360,因其为自动加载平衡计算资源。
因为优化技术对于不同的架构有很大的不同,且不要过早地进行优化。在优化时,请牢记如下三句格言:
OK,下面开始,本文的上篇,渲染管线的瓶颈定位。
三、上篇:渲染管线的瓶颈定位策略
正确定位到了瓶颈,优化工作就已完成了一半,因为可以针对管线上真正需要优化的地方有的放矢 。
提到瓶颈定位,很多人都会想到Profiler工具。Profiler工具可以提供API调用耗时的详细信息,由此可以知道哪些API调用是昂贵费时的,但不一定能准确地确定管道中哪些阶段正在减慢其余部分的速度。(PS:本文文末提供了一系列常用的profiler工具的列表)
确定瓶颈的方法除了用Profiler查看调用耗时的详细信息这种众所周知的方法外,也可以采用基于工作量变化的控制变量法。设置一系列测试,其中每个测试减少特定阶段执行的工作量。如果其中一个测试导致每秒帧数增加,则已经找到瓶颈阶段。
而上述方法的排除法也同样可行,即在不降低测试阶段的工作量的前提下减少其他阶段的工作量。如果性能没有改变,瓶颈就是工作负载没有改变的此阶段。
下图显示了一个确认瓶颈的流程图,描述了在应用程序中精确定位瓶颈所需要的一系列步骤。
图3 确认渲染管线瓶颈流程图 @ 《GPU GEMS I》
整个确认瓶颈的过程从渲染管线的尾端,光栅化阶段开始,经过帧缓冲区的操作(也称光栅操作),终于CPU(应用程序阶段)。虽然根据定义,某个图元(通常是一个三角形)只有一个瓶颈,但在帧的整个流程中瓶颈有可能改变。因此,修改流水线中多个节点的负载常常会影响性能。例如,少数多边形的天空包围盒经常受到片元着色或帧缓冲区存取的限制:只映射为屏幕上几个像素的蒙皮网络时常受到CPU或顶点处理的约束。因此,逐个物体地改变负载,或逐个材质地改变负载时常是有帮助的。
另外,管线的每个阶段都依赖于GPU频率(分为GPU Core Clock ,GPU核心频率,以及GPU Memory Lock,GPU显存频率),这个信息可以配合工具 PowerStrip(EnTech Taiwan 2003),减小相关的时钟速度,并在应用中观察性能的变化。
下文将按照按照优化定位的一般顺序(即上述图中的流程),按光栅化阶段、几何阶段、应用程序阶段的的顺序来依次介绍瓶颈定位的方法与要点。
3.1 光栅化阶段的瓶颈定位
众所周知,光栅化阶段由三个独立的阶段组成: 三角形设置,像素着色器程序,和光栅操作。
其中三角形设置阶段几乎不会是瓶颈,因为它只是将顶点连接成三角形。而测试光栅化操作是否是瓶颈的最简单方法是将颜色输出的位深度从32(或24)位减少到16位。如果帧速率大幅度增加,那么此阶段瓶颈。
一旦光栅化操作被排除,像素着色器程序的是否是瓶颈所在可以通过改变屏幕分辨率来测试。如果较低的屏幕分辨率导致帧速率明显上升,像素着色器则是瓶颈,至少在某些时候会是这样。当然,如果是渲染的是LOD系统,就需斟酌一下是否瓶颈确实是像素着色器了。
另一种方法与顶点着色器程序所采用的方法相同,可以添加更多的指令来查看对执行速度的影响。当然,也要确保这些额外的指示不会被编译器优化。
下文将对光栅化阶段三个常常可能是瓶颈的地方进行进一步论述。
3.1.1 光栅化操作的瓶颈定位
光栅化操作的瓶颈主要与帧缓冲带宽(Frame-Buffer Bandwidth)相关。众所周知,位于管线末端的光栅化操作(Raster
Operations,常被简称为ROP),用于深度缓冲和模板缓冲的读写、深度缓冲和模板缓冲比较,读写颜色,以及进行alpha 混合和测试。而光栅化操作中许多负载都加重了帧缓冲带宽负载。
测试帧缓冲带宽是否是瓶颈所在,比较好的办法是改变颜色缓冲的位深度,或深度缓冲的位深度(也可以同时改变两者)。如果此操作(比如将颜色缓冲或深度缓冲的深度位从32位减少到16位)明显地提高了性能,那么帧缓冲带宽必然是瓶颈所在。
另外,帧缓冲带宽也与GPU显存频率(GPU memory clock)有关,因此,修改该频率也可以帮助识别瓶颈。
3.1.2 纹理带宽的瓶颈定位
在内存中出现纹理读取请求时,就会消耗纹理带宽(Texture Bandwidth)。尽管现代GPU的纹理高速缓存设计旨在减少多余的内存请求,但纹理的存取依然会消耗大量的内存带宽。
在确认光栅化操作阶段是否是瓶颈所在时,修改纹理格式比修改帧缓冲区的格式更麻烦。所以,比较推荐使用大量正等级mipamap细节层次(LOD)的偏差,让纹理获取访问非常粗糙的mipmap金字塔级别,来有效地减小纹理尺寸。同样,如果此修改显著地改善性能,则意味着纹理带宽是瓶颈限制。
纹理带宽也与GPU显存频率相关。
3.1.3 片元着色的瓶颈定位
片元着色关系到产生一个片元的实际开销,与颜色和深度值有关。这就是运行”像素着色器(Pixel Shader )“或”片元着色器(Fragment Shader )“的开销。片元着色(Fragment shading)和帧缓冲带宽(Frame-Buffer Bandwidth)由于填充率(Fill Rate)的关系,经常在一起考虑,因为他们都与屏幕分辨率相关。尽管它们在管线中位于两个截然不同的阶段,区分两者的差别对有效优化至关重要。
在可编程片元处理的高级GPU出现之前,片元着色几乎没有什么局限性,时常是帧缓冲带宽引起的屏幕分辨率和性能之间不可避免的瓶颈。但随着开发者利用新的灵活性制造出一些新奇的像素,片元着色的性能问题也就出现了。
改变分辨率是确定片元着色是否为瓶颈的第一步。因为在上述光栅化操作步骤中,已经通过改换不同的深度缓冲位,排除了帧缓冲区带宽是瓶颈的可能性。所以,如果调整分辨率使得性能改变,片元着色就可能是瓶颈所在。而辅助的鉴别方法可以是修改片元长度,看这样是否会影响性能。但是要注意,不要添加可以被一些“聪明”的设备驱动轻松优化的指令。
片元着色的速度与GPU核心频率有关。
3.2 几何阶段的瓶颈定位
几何阶段是最难进行瓶颈定位的阶段。这是因为如果在这个阶段的工作负载发生了变化,那么其他阶段的一个或两个阶段的工作量也常常发生变化。为了避免这个问题,Cebenoyan [1] 提出了一系列的试验工作从光栅化阶段后的管线。
在几何阶段有两个主要区域可能出现瓶颈:顶点与索引传输( Vertex and
Index Transfer)和顶点变换阶段(Vertex Transformation Stage)。要看瓶颈是否是由于顶点数据传输的原因,可以增加顶点格式的大小。这可以通过每个顶点发送几个额外的纹理坐标来实现,例如。如果性能下降,这个部分就是瓶颈。
顶点变换是由顶点着色器或固定功能管线的转换和照明功能完成的。对于顶点着色器瓶颈, 测试包括使着色器程序更长。为了确保编译器没有优化这些附加指令,必须采取一些注意事项。对于固定功能管线,可以通过打开附加功能(如镜面高光)或将光源转换成更复杂的形式(例如聚光灯)来提高处理负荷。
下文将对几何阶段两个常可能是瓶颈的阶段的定位方法进行进一步论述。
3.2.1 顶点与索引传输的瓶颈定位
GPU渲染管线的第一步,是让GPU获取顶点和索引。而GPU获取顶点和索引的操作性能取决于顶点和索引的实际位置。其位置通常是在系统内存中(通过AGP或PCI Express总线传送到GPU),或在局部帧缓冲内存中。通常,在PC平台上,这取决于设备驱动程序而不是应用程序,而现代图形API允许应用程序提供使用提示,以帮助驱动程序选择正确的内存类型。
可以通过调整顶点格式的大小,来确定得到顶点或索引传输是否是应用程序的瓶颈。
如果数据放在系统内存内,得到顶点或索引的性能与AGP或PCI Express总线传输速率有关;如果数据位于局部缓冲内存,则与内存频率有关。
如果上述测试对性能都没有明显影响,那么顶点与索引传输阶段的瓶颈也可能位于CPU上。我们可以通过对CPU降频来确认这一事实,如果性能按比例进行变化,那么CPU就是瓶颈所在。
3.2.2 顶点变换的瓶颈定位
渲染管线中的顶点变换阶段(Vertex Transformation Stage)负责输入一组顶点属性(如模型空间位置、顶点法线、纹理坐标等等),以及生产一组适合裁剪和光栅化的属性(如齐次裁剪空间位置,顶点光照结果,纹理坐标等等)。当然,这个阶段的性能与每个顶点完成的工作,以及正在处理的顶点数量有关。
对于可编程的顶点变换,只要简单地改变顶点程序的长度,就能确定顶点处理是否是瓶颈。如果此时发生性能的变化,就可以判定顶点处理阶段是瓶颈所在。如上文提到过的,如果要增加指令,在添加富有意义的指令时需要留心,
以防止被编译器或驱动将指令优化掉。例如,因为驱动程序通常不知道程序编译时常量的值,没有被常量寄存器引用的空操作指令(no-ops)不能被优化(如加入一个含有值为零的常量寄存器)。
对于固定功能的顶点变换,判定瓶颈则有点麻烦。试着通过改变顶点的工作,例如修改镜面光照或纹理坐标生成的状态来修改负载。
另外需要注意,顶点处理的速度与GPU核心频率有关。
3.3 应用程序阶段的瓶颈定位
以下是应用程序阶段的瓶颈定位的一些策略的总结:
四、下篇:渲染管线的优化策略
一旦确定了瓶颈位置,就可以对瓶颈所处阶段进行优化,以改善我们游戏的性能。下面根据解决问题的不同阶段,对一些优化策略进行了分类整理,将分为六个部分来进行呈现:
4.1 对CPU的优化策略
许多应用的瓶颈都位于CPU,有的是正当理由(如复杂的物理或AI运算)导致,有的是因为不好的批处理或资源管理导致。如果已经发现应用程序受到CPU限制,可以试行下列建议,以对渲染管线中CPU的性能进行优化。
4.1.1 减少资源锁定
每当执行一个需要访问GPU的同步操作,就可能严重堵塞GPU管线,这将消耗CPU和GPU两者的周期。CPU必须保持在一个循环中,等待GPU管线工作,直到它闲下来并返回所请求的资源,这种等待会造成CPU周期的浪费。然后GPU等待对管线的再填充,这种等待又造成GPU周期的浪费。
上述的锁定发生在以下情况下:
而减少资源锁定的方法,可以尝试避免访问渲染期间GPU正在使用的资源。
4.1.2 批次的尺寸最大化
这个策略也可以称为“将批次的数量减到最小”。
批次(batch)是调用单个API渲染所做的一组基本渲染。用来绘制几何体的每个API函数调用,都有对应的CPU消耗。因此最大限度地增加每次调用所提交的三角形的数量,CPU渲染给定数目三角形的消耗就可以减到最小。也即批次的尺寸乘以批次数量得到的工作总量一定,此消彼长。
使批次最大化的技巧列举如下:
4.2 应用程序阶段的优化策略
对应用程序阶段的优化,可以通过提高代码的执行速度,以及提到程序的存储访问速度(或者减少存储访问的次数)来实现。下面将给出一些通用的优化技术,适用于大多数的CPU。
最基本的代码优化策略包括为编译器打开优化标志。通常有很多不同的标志,一般需要检查哪些标志可以应用于程序代码中,而且对所使用的优化选项一般不做任何假设。例如,可以将编译器的开关设置为“最小代码大小(minimize code size)”而不是“速度优化(optimizing for speed)”,这样可以导致代码执行速度的提高,因为缓冲性能提高了。此外,如果可能的话,可以尝试不同的编译器,因为不同编译器一般是按照不同的方式进行优化的。
对于代码优化来说,定位大部分时间花在哪部分代码是很关键的。一个好的代码profiler是找到大部分运行时间都花费在代码哪里的关键。然后在这些地方进行优化工作。而这些位置通常是内部循环,或是每帧执行多次的代码片段。(PS:本文文末提供了一系列常用的profiler工具的列表)
优化的基本原则是尝试多种策略,包括重新检查算法,假设,以及代码语法等,也就是尽可能多的尝试各种变化情况。
下文将从内存层面和代码层面进一步说明。
4.2.1 内存层面的优化
对于存储层次结构来说,如何在各种不同的CPU体系结构上编写执行速度较快的代码变得越来越重要。在编写程序时,应该注意下列准则:
PS: 上述条准则的思想有点类似《Game Programming Patterns》书中讲到的数据局部性模式(Data Locality pattern),具体可以参考《Game Programming Patterns》这本书的web版关于数据局部性模式的讲解:http://gameprogrammingpatterns.com/data-locality.html
struct Vertex {float x,y,z;}
Vertex myvertices[1000];
或者为:
struct VertexChunk {float x[1000],y[1000],z[1000];} VertexChunk myvertices;
对于给定的体系结构而言,上述第二种结构对于SIMD命令来说要更好一些。但是随着顶点数目的增多,高速缓存的命中失误率也会随之增多。当数组大小增加到一定程度时,下面这种混合方案可能是最好的一种选择:
struct Vertex4 {float x[4],y[4],z[4];} Vertex4 myvertices[250];
4.2.2 代码层面的优化
下面的会列出编写与计算机图形相关的高效代码的一些技术。这些方法随着编译器和不断发展的CPU而有所不同,但大多数已经保存了很多年(主要是针对C/C++而言):
4.3 API调用的优化策略
上文已经提到,批次(batch)是调用单个API渲染所做的一组基本渲染。用来绘制几何体的每个API函数调用,都有对应的CPU消耗。改进批次过小问题的方法有很多种,且它们都有共同的目标——更少的API调用。以下是一些要点。
图4 植被实例(Vegetation instancing)。所有同样颜色的物体在一个Draw Call中进行渲染。
PS: 此思想有点类似设计模式中的享元模式(flyweight pattern)。具体可以参考《Game Programming Patterns》这本书的web版关于享元模式的精彩讲解:http://gameprogrammingpatterns.com/flyweight.html
图5 多人同屏场景(crowd scene)。使用实例(instance)来减少Draw Call,也可以结合LOD技术使用,比如对于远处的模型,使用 impostors进行渲染。
4.4 几何阶段的优化策略
几何阶段主要负责变换、光照、裁剪、投影,以及屏幕映射。其中,变换和光照过程比较容易优化,其他几个部分的优化稍显困难。以下是一些要点:
4.4.1 减少顶点传输的开销
顶点传递是瓶颈的可能性很小,但也偶有发生。假如顶点或索引(索引是瓶颈的可能性更小)的传递是应用瓶颈,可以试着使用下列各项策略:
4.4.2 顶点处理的优化
顶点处理是现代GPU的瓶颈可能性很小,但是也偶有发生,这取决于所使用的模式和目标硬件。如果发现顶点处理是瓶颈所在,可以试用如下列举的各项策略:
4.5 光照计算的优化策略
考虑光照的影响可以每顶点,每像素的进行计算,光照计算可以通过多种方式进行优化:
4.6 光栅化阶段的优化策略
光栅化阶段可以以多种方式进行优化。现将主流的优化策略列举如下:
通过上述方法得到结果后,可以确认:
(1)场景中深度复杂度的平均值、最小值和最大值
(2)每个图元的像素数目(假定已知场景中图元的数目);
(3)通过或没有通过深度测试的像素数目。
而上述这些像素数量对理解实时图形应用程序的行为、确定需要进一步优化处理的位置都非常有用。
通过深度复杂度可以知道每个像素覆盖的表面数量,重复渲染的像素数量与实际绘制的表面的多少是相关的。假设两个多边形覆盖了一个像素,那么深度复杂度就是2。如果开始绘制的是远处的多边形,那么近处的多边形就会重复绘制整个远处的多边形,重绘数量也就为1。如果开始绘制的是近处的多边形,那么远处的多边形就不会通过深度测试,从而也就没有重绘问题。假设有一组不透明的多边形覆盖了一个像素,那么平均绘制数量就是调和级数:
上式背后所包含的逻辑是:第一个绘制的多边形是一次绘制:第2个多边形在第一个多边形之前绘制的概率是1/2:第三个多边形在前两个多边形前绘制的概率是1/3。依次类推,当n取极极限时:
其中,γ=0.57721…是Euler-Mascheroni常量。当深度复杂度很低时,重绘量会急剧增加,但增加速度也会逐渐减少。深度复杂度为4,平均绘制2.08次,深度复杂度为11,平均绘制3.02次,但深度复杂度为12367,平均绘制10次。
通过进行粗排序,并从前向后场景的渲染对性能提升会有帮助。这是因为后面绘制的被遮挡物体无需写入颜色缓冲区或Z缓冲区中。此外,在到达像素着色器程序之前,像素片元也可以被遮挡剔除硬件丢弃掉。
4.6.1 加速片元着色
如果你正在使用长而复杂的片元着色器,那么往往瓶颈就处于片元着色器中。若果真如此,那么可以试试如下这些建议:
4.6.2 减少纹理带宽
如果发现内存带宽是瓶颈,但是大部分结果又要从纹理中取得,那么可以考虑以下方面的优化。
4.6.3 优化帧缓冲带宽
管线的最后阶段,光栅化操作,与帧缓冲存储器直接衔接,是消耗帧缓冲带宽的主要阶段。因此如果带宽出了问题,经常会追踪到光栅化操作。下面几条技巧将讲到如何优化帧缓冲带宽。
(1)最后渲染天空盒,读取深度,但不写入深度,而且允许和一般的深度缓冲一起进行早期early-z优化,以节省带宽。
(2)首先渲染天空盒,而且禁用所有深度读取和写入。
以上两种策略,究竟哪一种会节省更多开端,取决于目标硬件的功能和在最终帧中有多大部分的天空盒可见。如果大部分的天空盒被遮挡,那么策略(1)更好,否则,策略(2)可以节省更多带宽。
综上,现代GPU能力和可编程性的增强,使得改善机器性能变得更复杂。无论是打算加速应用程序的性能,还是希望无成本地改善图像质量,都需要对渲染管线的内部工作原理有深刻理解。而GPU管线优化的基本思路是,通过改变每个单位的负荷或计算能力来识别瓶颈,然后运用每个传递单元工作原理的理解,系统地解决那些瓶颈。
五、主流性能分析工具列举
有很多不错的分析图形加速器和CPU使用的的工具,以及性能优化相关的Profiling工具,在这里,将主流的工具进行列举:
现今主流游戏引擎提供的Profiler有:
图6 Unreal Engine的GPU Visualizer
图7 Unity的Profiler
六、更多性能优化相关资料
Reference
[1] Cebenoyan, Cem, “Graphics Pipeline Performance,” in Randima Fernando, ed.,GPU Gems, Addison-Wesley, pp. 473–486,2004. Cited on p. 681, 699, 701, 716,722
[2] Hecker, Chris, “More Compiler Results, and What To Do About It,” Game Developer, pp. 14–21, August/September 1996. Cited on p. 705
[3] Blinn, Jim, “Optimizing C++ Vector Expressions,” IEEE Computer Graphics &Applications, vol. 20, no. 4, pp. 97–103, 2000. Also collected in [110], Chapter 18.Cited on p. 707
[4] Deering, Michael, “Geometry Compression,” Computer Graphics (SIGGRAPH 95 Proceedings), pp. 13–20, August 1995. Cited on p. 555, 713
[5] Calver, Dean, “Vertex Decompression Using Vertex Shaders,” in Wolfgang Engel, ed., ShaderX, Wordware, pp. 172–187, May 2002. Cited on p. 713
[6] Zarge, Jonathan, and Richard Huddy, “Squeezing Performance out of your Game with ATI Developer Performance Tools and Optimization Techniques,”Game Developers Conference, March 2006. http://ati.amd.com/developer/gdc/2006/GDC06-ATI Session-Zarge-PerfTools.pdf
Cited on p. 270, 699, 700, 701,702, 712, 713, 722, 847
[7] Purnomo, Budirijanto, Jonathan Bilodeau, Jonathan D. Cohen, and Subodh Kumar,“Hardware-Compatible Vertex Compression Using Quantization and Simplification,”Graphics Hardware, pp. 53–61, 2005. Cited on p. 713
[8] NVIDIA Corporation, “NVIDIA GPU Programming Guide,” NVIDIA developer website, 2005. http://developer.nvidia.com/object/gpu programming guide.htmlCited on p. 38, 282, 699, 700, 701, 702, 712, 722
[9] Fog, Agner, Optimizing software in C++, 2007. Cited on p. 706, 722
[10] Isensee, Pete, “C++ Optimization Strategies and Techniques,” 2007. Cited on p.706, 722
The end.
再次,感谢大家一直以来的支持与陪伴。
With best wish.