1 只在必须的时候Clear。
IDirect3DDevice9::Clear函数通常需要花费较多的时间,因此要尽量少调用,而且只清空的确需要清空的缓存。
2
尽量减少状态切换。并且将需要进行的状态切换组合在一起设置。
状态包括RenderState,SamplerState,TextureStageState等
3 纹理尺寸尽可能小
4
从前至后渲染场景中的对象
从前至后渲染可以尽可能早地精选出不需要绘制的对象和象素
5
使用三角条带代替三角列表和三角扇。为了能更有效利用顶点高速缓存(cache),在排列条带时因考虑尽快重用顶点。
6
根所需要据消耗的系统资源来逐步减少特效。
7 经常性地检测程序的性能。
这样可以更容易发现引起性能突变的部分
8
最小化顶点缓存的切换
9 尽可能使用静态顶点缓存
10
对静态对象,对每种FVF使用一个大的静态顶点缓存来保存多个对象的顶点数据,而不是每个对象使用一个顶点缓存。
其目的也是减少顶点缓存的切换
11 如果程序需要随机访问AGP内存中的顶点缓存,顶点格式的大小最好是32bytes的倍数。否则,选择合适的最小的格式。
32bytes
也就是8个float数据或2个vector4。
12 使用顶点索引方式渲染,这样可以更有效利用顶点高速缓存。
13
如果深度缓存格式中包含有模版缓存,总是将两者一起Clear。
14 将计算结果和输出的shader指令合并:
// Rather
than doing a multiply and add, and then output the data with
// two
instructions:
mad r2, r1, v0, c0
mov oD0, r2
// Combine both
in a single instruction, because this eliminates an
// additional
register copy.
mad oD0, r1, v0, c0
建立一个场景对象的数据库,首先使用最低精度的模型,在保证性能的前提下逐步使用更高精度的模型。密切关注渲染的总的三角面数。
http://nvidia.e-works.net.cn/document/200910/artic le9305_1.htm
将使用相同渲染状态和贴图的图元集中在一起绘制,这样能尽量减少顶点缓存和状态的切换。并且将状态切换操作集中成一组设置。
尽量减少光源数量,使用环境光来提高亮度。方向光源比点光源和聚光灯更高效,因为光的方向是固定的。使用光照范围参数来剔除不受光照影响的物体。镜面高光几乎使光照计算量加倍,因此只在需要时使用,
将D3DRS_SPECULARENABLE设为FALSE,将材质的specular power设为0,将材质的specular color 设为0。
尽量减小纹理尺寸,这样可以增加纹理被缓存的可能性。尽量减少纹理的切换,将使用同一纹理的对象集中绘制。尽量使用正方形纹理。最快的纹理是256×256,将4张128×128的纹理拼接成256×256使用。
连接World-View Matrix, 将ViewMatrix设为Identity减少矩阵乘法运算。
动态纹理。首先要检查D3DCAPS2_DYNAMICTEXTURES来判断硬件是否支持。
其二,动态纹理不能放在MANAGED
pool中。动态纹理总是能锁定,甚至是在D3DPOOL_DEFAULT中。D3DLOCK_DISCARD是合法的。
DrawProceduralTexture(pTex)
{
// pTex should not be very small
because overhead of
// calling driver every D3DLOCK_DISCARD will not
// justify the performance gain. Experimentation is encouraged.
pTex->Lock(D3DLOCK_DISCARD);
pTex->Unlock();
pDev->SetTexture();
pDev->DrawPrimitive();
}
当需要在每帧里锁定顶点或索引缓存是,应该使用动态缓存(D3DUSAGE_DYNAMIC)。对动态缓存使用D3DLOCK_DISCARD锁定能减少延迟。D3DLOCK_NOOVERWRITE锁定可以用于在缓存空闲处添加新的数据而不修改
已经写入的数据。
使用Effect时,应该根据Effect,然后根据Technique来安排渲染顺序,也就是使用相同Effect和Technique的物体应该集中绘制。这样可以减少状态切换开销。
一般来说, 定位渲染通道瓶颈的方法就是改变渲染通道每个步骤的工作量, 如果吞吐量也改变了, 那个步骤就是瓶颈.。找到了瓶颈就要想办法消除瓶颈,
可以减少该步骤的工作量, 增加其他步骤的工作量。
一般在光栅化之前的瓶颈称作"transform bound",
三角形设置处理后的瓶颈称作"fill bound"定位瓶颈的办法:
1.改变帧缓冲或者渲染目标(Render Target)的颜色深度(16 到
32 位), 如果帧速改变了, 那么瓶颈应该在帧缓冲(RenderTarget)的填充率上。
2.否则试试改变贴图大小和贴图过滤设置,
如果帧速变了,那么瓶颈应该是在贴图这里。
3.否则改变分辨率.如果帧速改变了, 那么改变一下pixel shader的指令数量, 如果帧速变了,
那么瓶颈应该就是pixel shader. 否则瓶颈就在光栅化过程中。
4.否则, 改变顶点格式的大小, 如果帧速改变了,
那么瓶颈应该在显卡带宽上。
5.如果以上都不是, 那么瓶颈就在CPU这一边。
优化方法36条:
1.尽量减少无用的顶点数据, 比如贴图坐标, 如果有Object使用2组有的使用1组, 那么不 要将他们放在一个vertex buffer中,
这样可以减少传输的数据量。
2.使用多个streamsource, 比如SkinMesh渲染,
可以把顶点坐标和法线这些每一帧都要修改的数据放在一个动态VB中, 其它不需要修改的(如贴图坐标)放到一个静态VB中, 这样就减少了数据传输量。
3.尽量使用16位的索引缓冲,避免32位的. 一方面浪费带宽, 一方面也不是所有的显卡都支持32位的索引缓冲。
4.可以考虑使用vertex shader来计算静态VB中的数据.比如SkinMesh的顶点可以放到vectex shader中计算,
这样就可以避免每一帧都从AGP内存中向显存传送数据. 这样也可以使用静态VB了。
5.坚决避免使用Draw**UP一族的函数来绘制多边形。
6.在设计程序之前好好规划一下显卡内存的使用, 确保framebuffer, 贴图, 静态VB能够正好放入显卡的本地内存中。
7.尽量使顶点格式大小是32字节的倍数.可以考虑使用压缩过的顶点格式然后用vertex shader去解. 或者留下冗余的部分,
使顶点大小刚好使32字节的倍数。
8.顶点在顶点缓冲中的顺序尽量符合绘制的顺序, 考虑使用strips来代替list。
9.如果可能尽量多的使用static vertex buffer代替dynamic vertex buffer。
10.动态VB使用DISCARD参数来lock更新, 使用NOOVERWRITE来添加.尽量不要使用不带参数的lock调用(0)。
11.尽量减少lock的次数, 有些东西并不一定非要每一帧都更新VB, 比如人物动画一般每秒钟更新30次VB基本上就够了。
12.如果是因为需要绘制的顶点数据太多了可以考虑使用LOD, 但是现在的显卡的绘制能力都很强劲, 所以需要权衡一下LOD是否能够带来相应的好处,
如果过分的强化LOD很可能将瓶颈转移到CPU这边。
13.避免过多的顶点计算,比如过多的光源, 过于复杂的光照计算(复杂的光照模型),
纹理自动生成的开启也会增加顶点的计算量. 如果贴图坐标变换矩阵不是单位矩阵, 也会造成顶点计算量的增加,
所以如果纹理变换已经结束,
记得要将纹理变换矩阵设为单位矩阵同时调整贴图坐标。
14.避免Vertex shader指令数量太多或者分支过多, 尽量减少vertex
shader的长度和复杂程度. 尽量使用swizzling代替mov。
15.如果图象质量方面的计算(pixel shader)范围很大,
并且很复杂, 可以考虑试试全屏反走样。说不定更快。
16.尽量按照front back的顺序来绘制。
17.在shader中判断Z值可以避免绘制不可见的象素, 但是nvidia建议简单的shader不要这么做.(Don't do this in a
simple shader)。
18.如果可能, 尽量使用vertex shader来代替pixel shader.将计算从逐象素变成逐顶点。
19.尽量降低贴图的大小.过大的贴图可能造成贴图cache过载, 从而导致贴图cache命中降低.过大的贴图会导致显存过载,
这时候贴图是从系统内存中取的。
20.只要可能就用16位色的贴图, 如环境贴图或者shadow map.它们用32位色的贴图实在是浪费。
21.考虑使用DXT 贴图压缩。
22.如果可能,使用简单的贴图过滤或者mip map,
除非必要否则尽量不要使用三线过滤和各项异性过滤. light map 和 环境贴图基本上都不需要使用它们。
23.只有真正需要修改的贴图才使用Dynamic, 并且使用DISCRAD和WRITEONLY来lock。
24.太多的帧缓冲读写可以考虑关闭Z-Writes如有些多pass的渲染中的后续pass或者粒子系统等半透明几何物体(如果可以)。
25.可能的话尽量使用alpha test代替alpha blending。
26.如果不需要stencil
buffer就尽量使用16位的Z buffer。
27.减小RenderTarget 贴图的大小, 如shadow map 环境贴图.
可能根本不需要那么大效果就很好。
28.Stencil 和 Z buffer 尽量一起clear. 他们本来就是一块缓冲。
29.尽量减少渲染状态的切换, 尽量一次画尽可能多的多边形。(根据显卡性能决定最多画多少, 不过一般再多也不会多到哪里去。
除非你根本不需要贴图和渲染状态的切换)。
30.尽量使用shader来代替Fixed Pipeline。
31.尽量使用shader来实现来取代Multipass渲染效果。 32.尽量优先先建立重要的资源, 如Render target,
shaders, 贴图, VB, IB等等.以免显存过载的时候它们被创建到系统内存中。
33.坚决不要在渲染循环中调用创建资源。
34.按照shader和贴图分组后再渲染.先按照shaders分组再按贴图。
35.Color Stencil Z
buffer尽量在一次Clear调用中清除。
36.一个Vertex buffer 的大小在2M-4M之间最好。
转
深入理解D3D9
文章来源:http://www.cnblogs.com/effulgent/archive/2009/02/1
0/1387438.html
深入理解D3D9对图形程序员来说意义重大,我把以前的一些学习笔记都汇总起来,希望对朋友们有些所帮助,因为是零散笔记,思路很杂,还请包涵。
其实只要你能完美理解D3DLOCK、D3DUSAGE、D3DPOOL、LOST
DEVICE、QUERY、Present()、BeginScene()、EndScene()等概念,就算是理解D3D9了,
不知道大家有没有同感。有如下几个问题,
如果你能圆满回答就算过关:)。
1、
D3DPOOL_DEFAULT、D3DPOOL_MANAGED、D3DPOOL_SYSTEMMEM和D3DPOOL_SCRATCH到底有何本质区别?
2、 D3DUSAGE的具体怎么使用?
3、 什么是Adapter?什么是D3D Device?HAL
Device和Ref Device有何区别?Device的类型又和Vertex Processing类型有什么关系?
4、
APP(CPU)、RUNTIME、DRIVER、GPU是如何协同工作的?D3D API是同步函数还是异步函数?
5、 Lost
Device到底发生了什么?为什么在设备丢失后D3DPOOL_DEFAULT类型资源需要重新创建?
在D3D中有三大对象,他们是D3D
OBJECT、D3D ADAPTER和D3D DEVICE。D3D
OBJECT很简单,就是一个使用D3D功能的COM对象,其提供了创建DEVICE和枚举ADAPTER的功能。ADAPTER是对计算机图形硬件
和软件性能的一个抽象,其包含了DEVICE。DEVICE则是D3D的核心,它包装了整个图形流水管线,包括变换、光照和光栅化(着色),根据D3D版本不同,流水线也有区别,比如最新的D3D10就包含了新的
GS几何处理。图形管线的所有功能由DRIVER提供,而DIRVER分两类,一种是GPU硬件DRIVER,另一种是软件DRIVER,这就是为什么在D3D中主要有两类DEVICE,
REF和HAL,使用REF DEVICE时,图形管线
的光栅化功能由软件DRIVER在CPU上模拟的,REF
DEVICE从名字就可以看出这个给硬件厂商做功能参考用的,所以按常理它应该是全软件实现,具备全部DX标准功能。而使用HAL DEVICE时,RUNTIME则将
使用HAL硬件层控制GPU来完成变换、光照和光栅化,而且只有HAL DEVICE中同时实现了硬件顶点处理和软件顶点处理(REF
DEVICE一般不能使用硬件顶点处理,除非自己在驱动上做手脚,比如PERFHUD)。
另外还有个一个不常用的SOFTWARE
DEVICE,用户可以使用DDI编写自己的软件图形驱动,然后注册进系统,之后便可在程序中使用。
检查系统软件硬件性能。
在程序的开始我们就要判断目标机的性能,其主要流程是:
确定要用的缓冲格式
GetAdapterCount()
GetAdapterDisplayMode
GetAdapterIdentifier //得到适配器描述
CheckDeviceType //判断指定适配器上的设备是否支持硬件加速
GetDeviceCaps
//指定设备的性能,主要判断是否支持硬件顶点处理(T&L)
GetAdapterModeCount
//得到适配器上指定缓冲格式所有可用的显示模式
EnumAdapterModes //枚举所有显示模式
CheckDeviceFormat
CheckDeviceMultiSampleType
详细使用请参考DX文档。
WINDOWS图形系统的主要分为四层:图形应用程序、D3D RUNTIME、SOFTWARE
DRIVER和GPU。此四层是按功能来分的,实际上他们之间界限并不如此明确,比如RUNTIME中其实也包含有USER MODE的
SOFTWARE
DRIVER,详细结构这里不再多说。而在RUNTIME里有一个很重要的结构,叫做command buffer,当应用程序调用一个D3D
API时,RUNTIME将调用转换成设备无关的命令,然后将命令缓冲
到这个COMMAND
BUFFER中,这个BUFFER的大小是根据任务负载动态改变的,当这个BUFFER满员之后,RUNTIME会让所有命令FLUSH到KERNEL模式下的驱动中,而驱动中也是有一个BUFFER的,用来存储
已被转换成的硬件相关的命令,D3D一般只允许其缓冲最多3个帧的图形指令,而且RUNTIME和DRIVER都会被BUFFER中的命令做适当优化,比如我们在程序中连续设置同一个RENDER
STATE,我们就会
在调试信息中看到如下信息"Ignoring redundant SetRenderState -
X",这便是RUNTIME自动丢弃无用的状态设置命令。在D3D9中可以使用QUERY机制来与GPU进行异步工作,所谓QUERY就是查
询命令,用来查询RUNTIME、DRIVER或者GPU的状态,D3D9中的QUERY对象有三种状态,SIGNALED、BUILDING和ISSUED,当他们处于空闲状态后会将查询状态置于SIGNALED
STATE,查询分开始和结束,
查询开始表示对象开始记录应用程序所需数据,当应用程序指定查询结束后,如果被查询的对象处于空闲状态,则被查询对象会将查询对象置于SIGNALED状态。GetData则是用来取得查询结果,
如果返回的是D3D_OK则结果可用,如果使用D3DGETDATA_FLUSH标志,表示将COMMAND
BUFFER中的所有命令都发送到DRIVER。现在我们知道D3D API绝大部分都是同步函数,应用程序调用后,
RUNTIME只是简单的将其加入到COMMAND
BUFFER,可能有人会疑惑我们如何测定帧率?又如何分析GPU时间呢?对于第一个问题我们要看当一帧完毕,也就是PRESENT()函数调用是否被阻塞,
答案是可能被阻塞也可能不被阻塞,要看RUNTIME允许缓冲中存在的指令数量,如果超过额度,则PRESENT函数会被阻塞下来,如何PRESENT完全不被阻塞,当GPU执行繁重的绘制任务时,
CPU工作进度会大大超过GPU,导致游戏逻辑快于图形显示,这显然是不行的。测定GPU工作时间是件很麻烦的事,首先我们要解决同步问题,要测量GPU时间,首先我们必须让CPU与GPU异步工作,
在D3D9中可以使用QUERY机制做到这点,让我们看看Accurately Profiling Driect3D API Calls中的例子:
IDirect3DQuery9* pQueryEvent;
//1.创建事件类型的查询事件
m_pD3DDevice->CreateQuery( D3DQUERYTYPE_EVENT, &pQueryEvent);
//2.在COMMAND BUFFER中加入一个查询结束的标记,此查询默认开始于CreateDevice
pQueryEvent->Issue(D3DISSUE_END);
//3.将COMMAND
BUFFER中的所有命令清空到DRIVER中去,并循环查询事件对象转换到SIGNALED状态,当GPU完成CB中所有命令后会将查询事件状态进行转换。
while(S_FALSE == pQueryEvent->GetData( NULL, 0, D3DGETDATA_FLUSH) )
;
LARGE_INTEGER start, stop;
QueryPerformanceCounter(&start);
SetTexture();
DrawPrimitive();
pQueryEvent->Issue(D3DISSUE_END);
while(S_FALSE == pQueryEvent->GetData( NULL, 0, D3DGETDATA_FLUSH) )
;
QueryPerformanceCounter(&stop);
1.第一个GetData调用使用了D3DGETDATA_FLUSH标志,表示要将COMMAND
BUFFER中的绘制命令都清空到DRIVER中去,当GPU处理完所有命令后会将这个查询对象状态置SIGNALED。
2.将设备无关的SETTEXTURE命令加入到RUNTIME的COMMAND BUFFER中。
3.将设备无关的DrawPrimitive命令加入到RUNTIME的COMMAND BUFFER中。
4.将设备无关的ISSUE命令加入到RUNTIME的COMMAND BUFFER中。
5.GetData会将BUFFER中的所有命令清空到DRIVER中去,注意这是GETDATA不会等待GPU完成所有命令的执行才返回。这里会有一个从用户模式到核心模式的切换。
6.等待DRIVER将所有命令都转换为硬件相关指令,并填充到DRIVER BUFFER中后,调用从核心模式返回到用户模式。
7.GetData循环查询 查询对象 状态。当GPU完成所有DRIVER BUFFER中的指令后会改变查询对象的状态。
如下情况可能清空RUNTIME COMMAND BUFFER,并引起一个模式切换:
1.Lock
method(某些条件下和某些LOCK标志)
2.创建设备、顶点缓冲、索引缓冲和纹理
3.完全释放设备、顶点缓冲、索引缓冲和纹理资源
4.调用ValidateDevice
5.调用Present
6.COMMAND BUFFER已满
7.用D3DGETDATA_FLUSH调用GetData函数
对于D3DQUERYTYPE_EVENT的解释我不能完全理解(Query
for any and all asynchronous events that have been issued from API
calls)明白的朋友一定告诉我,只知道当GPU处理完
D3DQUERYTYPE_EVENT类型查询在CB中加入的D3DISSUE_END标记后,会将查询对象状态置SIGNALED状态,所以CPU等待查询一定是异步的。为了效率所以尽量少在PRESENT之前使用BEGINSCENE
ENDSCENE对,
为什么会影响效率?原因只能猜测,可能EndScene会引发Command buffer
flush这样会有一个执行的模式切换,也可能会引发D3D RUNTIME对MANAGED资源的一些操作。而且ENDSCENE不是一个同步方法,
它不会等待DRIVER把所有命令执行完才返回。
D3D RUTIME的内存类型,分为3种,VIDEO MEMORY(VM)、AGP
MEMORY(AM)和SYSTEM MEMORY(SM),所有D3D资源都创建在这3种内存之中,在创建资源时,我们可以指定如下存储标志,
D3DPOOL_DEFAULT、D3DPOOL_MANAGED、D3DPOOL_SYSTEMMEM和D3DPOOL_SCRATCH。VM就是位于显卡上的显存,CPU只能通过AGP或PCI-E总线访问到,读写速度都是非常慢的,CPU连续写VM稍微快于读,
因为CPU写VM时会在CACHE中分配32或64个字节(取决于CACHE
LINE长度)的写缓冲,当缓冲满后会一次性写入VM;SM就是系统内存,CPU读写都非常快,因为SM是被CACHE到2级缓冲的,
但GPU却不能直接访问到系统缓冲,所以创建在SM中的资源,GPU是不能直接使用的;AM是最麻烦的一个类型,AM实际也存在于系统内存中,但这部分MEM不会被CPU
CACHE,意味着CPU读写AM都会写来个
CACHE
MISSING然后才通过内存总线访问AM,所以CPU读写AM相比SM会比较慢,但连续的写会稍微快于读,原因就是CPU写AM使用了"write
combining",而且GPU可以直接通过AGP或PCI-E总线访问AM。
如果我们使用D3DPOOL_DEFAULT来创建资源,则表示让D3D
RUNTIME根据我们指定的资源使用方法来自动使用存储类型,一般是VM或AM,系统不会在其他地方进行额外备份,当设备丢失后,
这些资源内容也会被丢失掉。但系统并不会在创建的时候使用D3DPOOL_SYSTEMMEM或D3DPOOL_MANAGED来替换它,注意他们是完全不同的POOL类型,创建到D3DPOOL_DEFAULT中的纹理是不能被CPU
LOCK的,
除非是动态纹理。但创建在D3DPOOL_DEFAULT中的VB IB RENDERTARGET BACK
BUFFERS可以被LOCK。当你用D3DPOOL_DEFAULT创建资源时,如果显存已经使用完毕,则托管资源会被换出显存来释放足够的空间。
D3DPOOL_SYSTEMMEM和D3DPOOL_SCRATCH都是位于SM中的,其差别是使用D3DPOOL_SYSTEMMEM时,资源格式受限于Device性能,因为资源很可能会被更新到AM或VM中去供图形系统使用,
但SCRATCH只受RUNTIME限制,所以这种资源无法被图形系统使用。 D3DRUNTIME会优化D3DUSAGE_DYNAMIC
资源,一般将其放置于AM中,但不敢完全保证。另外为什么静态纹理不能被LOCK,动态纹理却可以,
都关系到D3D
RUNTIME的设计,在后面D3DLOCK说明中会叙述。
D3DPOOL_MANAGED表示让D3D
RUNTIME来管理资源,被创建的资源会有2份拷贝,一份在SM中,一份在VM/AM中,创建的时候被放置L在SM,在GPU需要使用资源时D3D
RUNTIME自动将数据拷贝到VM中去,
当资源被GPU修改后,RUNTIME在必要时自动将其更新到SM中来,而在SM中修改后也会被UPDATE到VM去中。所以被CPU或者GPU频发修改的数据,一定不要使用托管类型,这样会产生非常昂贵的同步负担。
当LOST
DEVICE发生后,RESET时RUNTIME会自动利用SM中的COPY来恢复VM中的数据,因为备份在SM中的数据并不是全部都会提交到VM中,所以实际备份数据可以远多于VM容量,随着资源的不断增多,
备份数据很可能被交换到硬盘上,这是RESET的过程可能变得异常缓慢,RUNTIME给每个MANAGED资源都保留了一个时间戳,当RUNTIME需要把备份数据拷贝到VM中时,RUNTIME会在VM中分配显存空间,
如果分配失败,表示VM已经没有可用空间,这样RUNTIME会使用LRU算法根据时间戳释放相关资源,SetPriority通过时间戳来设置资源的优先级,最近常用的资源将拥有高的优先级,这样RUNTIME通
过优先级就能合理的释放资源,发生释放后马上又要使用这种情况的几率会比较小,应用程序还可以调用EvictManagedResources强制清空VM中的所有MANAGED资源,这样如果下一帧有用到MANAGED资源,
RUNTIME需要重新载入,这样对性能有很大影响,平时一般不要使用,但在关卡转换的时候,这个函数是非常有用的,可以消除VM中的内存碎片。LRU算法在某些情况下有性能缺陷,比如绘制一帧所需资源
量无法被VM装下的时候(MANAGED),使用LRU算法会带来严重的性能波动,如下例子:
BeginScene();
Draw(Box0);
Draw(Box1);
Draw(Box2);
Draw(Box3);
Draw(Circle0);
Draw(Circle1);
EndScene();
Present();
假设VM只能装下其中5个几何体的数据,那么根据LRU算法,在绘制Box3之前必须清空部分数据,那清空的必然是Circle0……,很显然清空Box2是最合理的,所以这是RUNTIME使用MRU算法处理后续
Draw Call能很好的解决性能波动问题,但资源是否被使用是按FRAME为单位来检测的,并不是每个DRAW
CALL都被记录,每个FRAME的标志就是BEGINSCENE/ENDSCENE对,所以在这种情况下合理使用
BEGINSCENE/ENDSCENE对可以很好的提高VM不够情况下的性能。根据DX文档的提示我们还可以使用QUERY机制来获得更多关于RUNTIME
MANAGED RESOURCE信息,但好像只在RUNTIME DEBUG模式下有用,
理解RUNTIME如何MANAGE
RESOURCE很重要,但编写程序的时候不要将这些细节暴露出来,因为这些东西都是经常会变的。最后还要提醒的是,不光RUNTEIME会MANAGE
RESOURCE,DRIVER也很可能也实
现了这些功能,我们可以通过D3DCAPS2_CANMANAGERESOURCE标志取得DRIVER是否实现资源管理功能的信息,而且也可以在CreateDevice的时候指定D3DCREATE_DISABLE_DRIVER_MANAGEMENT来关闭
DRIVER资源管理功能。
D3DLOCK探索D3D RUNTIME工作
如果LOCK
DEFAULT资源会发生什么情况呢?DEFAULT资源可能在VM或AM中,如果在VM中,必须在系统内容中开辟一个临时缓冲返回给数据,当应用程序将数据填充到临时缓冲后,UNLOCK的时候,RUNTIME
会将临时缓冲的数据传回到VM中去,如果资源D3DUSAGE属性不是WRITEONLY的,则系统还需要先从VM里拷贝一份原始数据到临时缓冲区,这就是为什么不指定WRITEONLY会降低程序性能的原因。CPU写AM也
有需要注意的地方,因为CPU写AM一般是WRITE COMBINING,也就是说将写缓冲到一个CACHE LINE上,当CACHE
LINE满了之后才FLUSH到AM中去,第一个要注意的就是写数据必须是WEAK ORDER的
(图形数据一般都满足这个要求),据说D3DRUNTIME和NV
DIRVER有点小BUG,就是在CPU没有FLUSH到AM时,GPU就开始绘制相关资源产生的错误,这时请使用SFENCE等指令FLUSH CACHE
LINE。第二请尽量
一次写满一个CACHE LINE,否则会有额外延迟,因为CPU每次必须FLUSH整个CACHE
LINE到目标,但如果我们只写了LINE中部分字节,CPU必须先从AM中读取整个LINE长数据COMBINE后重新FLUSH。第三尽可
能顺序写,随机写会让WRITE
COMBINING反而变成累赘,如果是随机写资源,不要使用D3DUSAGE_DYNAMIC创建,请使用D3DPOOL_MANAGED,这样写会完全在SM中完成。
普通纹理(D3DPOOL_DEFAULT)是不能被锁定的,因为其位于VM中,只能通过UPDATESURFACE和UPDATETEXTURE来访问,为什么D3D不让我们锁定静态纹理,却让我们锁定静态VB
IB呢?我猜测可能有2个
方面的原因,第一就是纹理矩阵一般十分庞大,且纹理在GPU内部已二维方式存储;第二是纹理在GPU内部是以NATIVE
FORMAT方式存储的,并不是明文RGBA格式。动态纹理因为表明这个纹理需要经常修改,
所以D3D会特别存储对待,高频率修改的动态纹理不适合用动态属性创建,在此分两种情况说明,一种是GPU写入的RENDERTARGET,一种是CPU写入的TEXTURE
VIDEO,我们知道动态资源一般是放置在AM中的,
GPU访问AM需要经过AGP/PCI-E总线,速度较VM慢许多,而CPU访问AM又较SM慢很多,如果资源为动态属性,意味着GPU和CPU访问资源会持续的延迟,所以此类资源最好以D3DPOOL_DEFAULT和D3DPOOL_SYSTEMMEM
各创建一份,自己手动进行双向更新更好。千万别 RENDERTARGET以D3DPOOL_MANAGED
属性创建,这样效率极低,原因自己分析。而对于改动不太频繁的资源则推荐使用DEFAULT创建,自己手动更新,
因为一次更新的效率损失远比GPU持续访问AM带来的损失要小。
不合理的LOCK会严重影响程序性能,因为一般LOCK需要等待COMMAND
BUFFER前面的绘制指令全部执行完毕才能返回,否则很可能修改正在使用的资源,从LOCK返回到修改完毕UNLOCK这段时间GPU全部处
于空闲状态,没有合理使用GPU和CPU的并行性,DX8.0引进了一个新的LOCK标志D3DLOCK_DISCARD,表示不会读取资源,只会全写资源,这样驱动和RUNTIME配合来了个瞒天过海,立即返回给应用程序另
外块VM地址指针,而原指针在本次UNLOCK之后被丢弃不再使用,这样CPU
LOCK无需等待GPU使用资源完毕,能继续操作图形资源(顶点缓冲和索引缓冲),这技术叫VB IB换名(renaming)。
很多困惑来源于底层资料的不足,相信要是MS开放D3D源码,开放驱动接口规范,NV / ATI显示开放驱动和硬件架构信息,这些东西就很容易弄明白了。
顺便做个书的广告 《人工智能:一种现代方法》中文版
卓越网已经有货,AI巨作,不过阅读需要相当的基础,对思维非常有启迪,想买的朋友不要错过。后面我会将学习重点从图形转到AI上来,对AI有
兴趣的朋友一起交流。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/udking/archive/2010/12/01/604
8211.aspx