我喜欢从“ 极限 ”的角度来考虑性能。这是一种概念化相当复杂的互连系统的便捷方法。当您遇到性能问题时,会问一个问题:“我达到了什么极限?” (或者:“我绑定了CPU / GPU吗?”)
您可以将其分为多个级别。在最高级别,您拥有CPU和GPU。您可能受CPU限制(GPU处于空闲状态等待CPU),或者受GPU限制(CPU处于GPU等待状态)。这是有关该主题的一篇不错的博客文章。
您可以进一步分解。在CPU端,您可能正在对CPU缓存中已有的数据使用所有周期。否则您可能会受到内存的限制,使CPU处于空闲状态,等待数据从主内存中进入(因此请优化数据布局)。您可以进一步分解它。
(虽然我正在就XNA进行性能的广泛概述,但我会指出,通常便宜的引用类型(class
not struct
)的分配可能会触发垃圾收集器,这将消耗很多周期,尤其是在Xbox 360上(请参阅此处了解详细信息)。
在GPU方面,我将首先向您介绍这篇出色的博客文章,其中包含许多细节。如果您想在管道上获得疯狂的详细信息,请阅读本系列的博客文章。(这是一个简单的)。
简单地说,其中一些比较大的是:“ 填充限制 ”(可以向后缓冲区写入多少像素-通常可以包含多少透支),“ 着色器限制 ”(着色器可以复杂到多少,以及您可以通过它们推送多少数据),“ 纹理获取/纹理带宽限制 ”(您可以访问多少纹理数据)。
而且,现在,我们进入了大问题-这是您真正要问的问题-CPU和GPU必须在何处交互(通过各种API和驱动程序)。松散地有“ 批次限制 ”和“ 带宽 ”。(请注意,第一部分我刚才提到的一系列的进入丰富的细节。)
但是,基本上,每当您调用其中一个函数(或XNA的一部分,如,为您执行此操作)时,就会发生一批(您已经知道)。毫无疑问,您已经读过了,每帧可以得到数千张*。这是CPU限制-因此它可以与您的其他CPU使用率竞争。基本上,驱动程序将您要绘制的内容打包在一起,然后发送给GPU。GraphicsDevice.Draw*
SpriteBatch
然后是GPU 的带宽。这是您可以在那里传输的原始数据量。这包括批处理附带的所有状态信息-从设置渲染状态和着色器常数/参数(包括诸如世界/视图/项目矩阵之类的东西)到使用DrawUser*
函数时的顶点,一应俱全。它还包括任何电话SetData
和GetData
在纹理,顶点缓冲区等。
在这一点上,我应该说,您可以调用的所有SetData
内容(纹理,顶点和索引缓冲区等)以及Effect
s都保留在GPU内存中。它不会经常重新发送到GPU。引用该数据的绘图命令只需带有指向该数据的指针即可发送。
(另外:您只能从主线程发送绘图命令,但可以SetData
在任何线程上。)
XNA复杂的事情有点与它的渲染状态类(BlendState
,DepthStencilState
,等)。该状态数据被每绘图调用发送(每批)。我不是100%肯定,但是给我的印象是它是延迟发送的(它只发送发生变化的状态)。无论哪种方式,相对于批处理成本,状态更改都便宜到免费。
最后,最后要提到的是内部GPU管道。您不想通过写入仍需要读取的数据或读取仍需要写入的数据来强制刷新它。管道刷新意味着它等待操作完成,以便在访问数据时一切都处于一致状态。
需要注意的两个特殊情况是:调用GetData
动态对象-特别RenderTarget2D
是GPU可能正在写入的对象。这对性能非常不利-请勿这样做。
另一种情况是调用SetData
顶点/索引缓冲区。如果您需要经常执行此操作,请使用DynamicVertexBuffer
(也DynamicIndexBuffer
)。这些使GPU知道它们将经常变化,并在内部进行一些缓冲操作以避免管道刷新。
(还请注意,动态缓冲区比DrawUser*
方法快-但必须以所需的最大大小对其进行预分配。)
...这几乎是我所了解的XNA性能的全部信息:)