一 pool
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中的数据
二 lock
如果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方式存储的,并不是明文enRGBA格式。动态纹理因为表明
这个纹理需要经常修改,所以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)。