<如果窗口的视图区大小和SwapChain的大小不一,那么DirectX将通过Stretch Blit来自动处理图像的伸缩变化。尽管这可能并不令人期待,因为这在视图区变大的时候将导致图像的模糊。> 你说的这个问题 要怎么才能解决?
有位博友这样问过,因为最近少有上博客,于是没有回答及时,请见谅,我这里只能说说我们现在的解决办法。
当后台缓冲区的分辨率和视图区不统一的时候,会导致拉伸现象,使画面变得模糊。
首先说个题外话:
正因为变得模糊,因此有人故意将后台缓冲区做得比视区稍大一点,这样来抗锯齿,至于效果如何,没有真正见过,有兴趣的可以试试。
下面说说解决办法。
我们的解决办法也很简单,就分三步
1,窗口改变的时候,告诉设备窗口大小改变。
2,按改变后的窗口重建缓冲区。
3。强制设备丢失,并重新加载需要的资源。
由于在做这个之前,设备丢失已经做好了,于是就偷了个懒,窗口改变的时候就传入窗口大小,并reset
这样设备就强制处于丢失状态。
不知道有没有说清楚,反正主要的就是要重建缓冲区,并处理设备丢失问题。。。
骨骼动画一直是我感兴趣的内容.虽然采用现成的CSkinMesh能够使用XFile的骨骼动画.但对自己来说总感觉缺少了点什么.于是,还是深入理解理解为好!!!
ZDNet软件频道时间:
2008-03-24作者:Skyman | CSDN本文关键词:骨骼动画) Animation Skeletal 游戏 Linux
骨骼动画(Skeletal Animation)又叫Bone Animation,它与关键帧动画(Key-frame Animation)相比,占用空间小,因为它不需要象关键帧动画那样要存储每一帧的各个顶点的数据,而是只需要存储每一帧的骨骼,骨骼与顶点相比,当然要少得多。所以骨骼动画有很多优势,当然其技术难度也很高。我个人觉得动画在计算机图形学中是一个十分重要的内容,不管是在游戏、电影动画还是虚拟现实中,生动逼真的动画(人、动物等)会使之增色不少。所以我决定今后的研究方向就是计算机动画。目前在研究Skeletal Animation,这是目前动画技术中的主流。欢迎同好与我交流,共同提高!
骨骼动画的实现思路是从我们人的身体的运动方式而来的(所以VR就是对现实世界的虚拟嘛 :-))。动画人物的身体(肉、皮肤)是一个网格(Mesh)模型,网格的内部是一个骨架结构。当人物的骨架运动时,身体就会跟着骨架一起运动。骨架是由一定数目的骨骼组成的层次结构,每一个骨骼的排列和连接关系对整个骨架的运动有很重要的影响。每一个骨骼数据都包含其自身的动画数据。和每个骨架相关联的是一个“蒙皮”(Skin)模型,它提供动画绘制所需要的几何模型(Vertex,Normal,etc)和纹理材质信息。每个顶点都有相应的权值(Weight),这些权值定义了骨骼的运动对有关顶点的影响因子。当把动画人物的姿势和全局运动信息作用到骨架上时,这个“蒙皮”模型就会跟随骨架一起运动。如下图所示:
![](http://p.blog.csdn.net/images/p_blog_csdn_net/skyman_2001/bone.jpg)
所以关键是对骨架进行动画生成,生成的方法也是用关键帧。关键帧动画是对人物的网格(Mesh)模型采用关键帧生成动画;而骨骼动画则是对人物的骨架采用关键帧生成动画,然后再让网格(Mesh)模型跟随骨架运动。关键帧动画实现的2个关键点是:关键帧的选取和中间帧的插补。
关键帧的指定有2种基本的方法:前向动力学(FK)和逆向动力学(IK)。前向动力学用一组节点的角度来找到末端受动器的位置;而逆向动力学则是找到将末端受动器置于所要位置所需的一组节点角度。前向动力学的优点是:计算简单,运算速度快,缺点是:需指定每个关节的角度和位置,而由于骨架的各个节点之间有内在的关联性,直接指定各关节的值很容易产生不自然协调的动作;逆向动力学的优点是:只需指定主要关节点的位置,负担轻,缺点是:计算模型比较复杂,开发者需要机械运动和动力学、几何学以及向量数学等方面的相关知识。
中间帧的插值分2步:(1) 根据当前时间,通过插值计算出每个骨骼的旋转、平移等值,形成中间帧的骨架。插值算法一般采用四元数(Quternion)的球面线性插值(Spherical linear interpolation)SLERP,SLERP特别适合在两个方位之间进行插值,不会出现像对欧拉角插值那样出现万象锁的现象,而且这种插值能产生更平滑和连续的旋转,表达方式也很简洁;(2) 根据骨架的变化情况,插值计算出骨架的“蒙皮”模型的各个顶点的位置变化。对于某个特定骨骼,“蒙皮”模型的顶点变换矩阵=初始姿势的变换矩阵的逆×姿势变换后的矩阵。另外还要考虑顶点可能受多个骨骼运动的影响。这时我们对每个与当前顶点相关联的骨骼,将其运动姿势变换矩阵×当前顶点相对于该骨骼的偏移向量×该骨骼对当前顶点的影响因子(即权重Weight),对所有与当前顶点相关联的骨骼都这么处理,然后相加,就得到当前顶点的新位置。
由此看出,如何设置各关键帧的骨架的各节点的位置和骨骼的转向(也就是骨架的POSE)是其中的关键,有2种方法:一种是由动画师手工放置,这个对动画师的技术要求就比较高,要求动画师对现实生活中的人和动物等的动作有细心的观察。否则设置的骨架动作就会不自然、不协调;另外一种是基于运动捕捉(Motion Capture)的方法,就是在人的各个关节处安置运动捕捉传感器,当人做各种动作时,捕捉仪器就将各节点的位置数据记录下来,这样我们就可以根据这些节点数据进行骨架建模。由于这是捕捉的真实的人的动作,所以这种方式得到的动画就很自然、很真实,但捕捉仪器造价昂贵,国内估计只有很少几家有财力的游戏公司才购置了这些设备吧。
目前有好多3D模型格式支持Skeletal Animation,像Microsoft的.X格式、MilkShape的MS3D格式、Half Life的MDL格式、ID Software的MD5格式等。我准备首先研究一下MS3D格式,因为它有公开的格式说明文档,阅读起来比较容易,而且应用很广。当然,首先要深入学习Skeletal Animation的底层技术,打好坚实的基础,呵呵!
将就看吧,有些单词我实在不知道怎么翻译,只可意会!
像素着色器需要依靠寄存器来取得顶点数据,输出像素数据,取得计算时的临时结果和关联纹理采样通道(stage)。有几种类型的寄存器,每一种都有特殊的功能和用途。
像素着色器需要的用到的数据由寄存器保管,下面是寄器存的所有介绍
寄存器类型:描述了四种可用的寄存器和他们各自的用途
读取端口限制:单指针使用多个寄存器时的限制
R/RW: 描述了哪些寄存器可以用来读,写或是读写。
范围:各个分量的范围的详细说明
Register Types
Versions
Name Type 1_1 1_2 1_3 1_4
c# Constant register 8 8 8 8
r# Temporary register 2 2 2 6
t# Texture register 4 4 4 6
v# Color register 2 2 2 2 in phase 2
1,常量寄存器:常量寄存器容纳了常量数据。数据可以用IDirect3DDevice9::SetPixelShaderConstantF函数将一个常量装入常量寄存器中。也可以用def-ps来定义一个常量。 常量寄存器对纹理寻址指令来说是不可用的,唯一例外的是texm3x3spec-ps指令,这个指令使用一个常量寄存器来提供一个视线向量(eye-ray vector)
2,临时寄存器:临时寄存器用来存立即结果。r0用来作为PS的最终输出。shader的最后时刻r0中存放的是最后的像素颜色值
如果任何的着色器试图从一个没有被写入数据的临时寄存器中读取数据时,着色器激IDirect3DDevice9::CreatePixelShader将会失败(shader validation will fail)。假设激活(validation)是可用状态D3DXAssembleShader函数调用也会因为相同的原因而失败。(不要使用D3DXSHADER_SKIPVALIDATION)
纹理寄存器:
在ps 1_1 到1_3中,纹理寄存器容纳纹理数据或是纹理坐标。当一个纹理被采样时,纹理数据便被装载到一个纹理寄存器中。
当纹理通道状态属性被登记的时候纹理采样使用纹理坐标来查询(look up)或采样(sample)一个纹理坐标(u,v,w,q)标记的颜色值。纹理坐标数据会根据顶点纹理坐标数据进行插值,并关联到相关的纹理通道。纹理通道号与纹理坐标声明序列有一个一一对应关系。默认情况下,顶点格式中定义的第一个纹理坐标与纹理通道0关联。
在这些版本的像素着色器中,当纹理寄存器用来做算术运算的时候就和临时寄存器的效果一样了。
在ps_1_4中,纹理寄存器(t#)容纳的是只读纹理坐标信息。这意味着纹理坐标集和纹理通道编号是独立的。纹理通道编号由目的寄存器(r0 to r5)决定。对于texld指令来说,纹理坐标集由源寄存器t0 to t5决定。因此纹理坐标集可以映射到任何的纹理通道上。另外,对于texld的源寄存器(指定纹理坐标信息)也可以是临时寄存器(r#)。在这样的情况下,临时寄存器记录纹理坐标。
颜色寄存器容纳了每个像素的颜色值,这个值通过顶点数据中的漫反射和镜面光颜色值迭代而来。对于ps_1_4。颜色寄存器只有在phase2中可用。如果着色模式设置为D3DSHADE_FLAT,那么顶点颜色中的颜色迭代将不可用。如果雾化开启的话,那么渲染管线还是会忽略着色模式,对雾进行颜色迭代。记住雾化比像素着色器后应用。
通常我们会从v0加载顶点漫反射颜色数据。从v1加载顶点镜面光颜色数据。
输入颜色数据值将会被规范到0和1,因为这是像素着色器中的颜色寄存器的有效范围
像素着色器对颜色寄存器进行只读操作。颜色寄存器中存放的是迭代值,但是迭代可能会造成比纹理坐标低很多精度
用VS2005+DirectX9 SDK(手头测试过的是2004年10月的DirectX SDK和2006年4月的DirectX SDK)编译游戏会出现以下warning:
--------------------------------------------------------------------------------
d:\microsoft directx 9.0 sdk (october 2004)\include\d3d9types.h(1385) : warning C4819: The file contains a character that cannot be represented in the current code page (936). Save the file in Unicode format to prevent data loss
--------------------------------------------------------------------------------
要修正这个问题,不必要存为UTF8的文件,而是搜索_D3DDEVINFO_VCACHE,然后会看到:
typedef struct _D3DDEVINFO_VCACHE ...{
DWORD Pattern; /**//* bit pattern, return value must be FOUR_CC(慍? 慉? 慍? 慔? */
DWORD OptMethod; /**//* optimization method 0 means longest strips, 1 means vertex cache based */
DWORD CacheSize; /**//* cache size to optimize for (only required if type is 1) */
DWORD MagicNumber; /**//* used to determine when to restart strips (only required if type is 1)*/
} D3DDEVINFO_VCACHE, *LPD3DDEVINFO_VCACHE;
那四个乱码的去掉就可以了
参考了http://gamep.mmoh.jp/e39255.html
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/SONIC3D/archive/2007/11/01/1861794.aspx
文章来源:http://www.cnblogs.com/effulgent/archive/2009/02/10/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有兴趣的朋友一起交流。