Direct3D的Runtime/Driver运行时机制整理

整理自:http://www.cnblogs.com/effulgent/archive/2009/02/10/1387438.html

一、Runtime/Driver中的命令缓存

Command buffer和Driver Buffer 缓存翻译指令:

 

当应用程序调用一个D3D API时,RUNTIME将调用转换成设备无关的命令,然后将命令缓冲到这个COMMANDBUFFER中。

这个BUFFER的大小是根据任务负载动态改变的,当这个BUFFER满员之后,RUNTIME会让所有命令FLUSH到KERNEL模式下的驱动中。

 

Command buffer/Driver buffer优化指令和阻塞渲染:

   而驱动中也是有一个BUFFER的,用来存储已被转换成的硬件相关的命令,D3D一般只允许其缓冲最多3个帧的图形指令。

而且RUNTIME和DRIVER都会被BUFFER中的命令做适当优化,比如我们在程序中连续设置同一个RENDER STATE,

我们就会在调试信息中看到如下信息“Ignoring redundantSetRenderState - X”,这便是RUNTIME自动丢弃无用的状态设置命令。

 

   现在我们知道D3DAPI绝大部分都是同步函数,应用程序调用后,RUNTIME只是简单的将其加入到COMMAND BUFFER。

可能有人会疑惑我们如何测定帧率?又如何分析GPU时间呢?对于第一个问题我们要看当一帧完毕,也就是PRESENT()函数调用是否被阻塞,

答案是可能被阻塞也可能不被阻塞,要看RUNTIME允许缓冲中存在的指令数量,如果超过额度,则PRESENT函数会被阻塞下来,

如何PRESENT完全不被阻塞,当GPU执行繁重的绘制任务时,CPU工作进度会大大超过GPU,导致游戏逻辑快于图形显示,这显然是不行的。

 

 

QUERY机制和GPU异步工作:

D3D9中可以使用QUERY机制来与GPU进行异步工作,所谓QUERY就是查询命令,用来查询RUNTIME、DRIVER或者GPU的状态,

D3D9中的QUERY对象有三种状态,SIGNALED STATE、BUILDING STATE和ISSUED STATE,

 

SIGNALED STATE:

   当他们处于空闲状态后会将查询状态置于SIGNALED STATE。

   查询分开始和结束,查询开始表示对象开始记录应用程序所需数据,当应用程序指定查询结束后,

如果被查询的对象处于空闲状态,则被查询对象会将查询对象置于SIGNALED状态。

 

GetData则是用来取得查询结果,如果返回的是D3D_OK则结果可用,如果使用D3DGETDATA_FLUSH标志,

表示将COMMAND BUFFER中的所有命令都发送到DRIVER。

 

1)理解D3D调试的状态信息,传递刷新命令,命令丢弃:

Ignoring redundantSetRenderState - X

 

2)QUERY的异步应用:

测定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的COMMANDBUFFER中。

5.GetData会将BUFFER中的所有命令清空到DRIVER中去,注意这是GETDATA不会等待GPU完成所有命令的执行才返回。这里会有一个从用户模式到核心模式的切换。

6.等待DRIVER将所有命令都转换为硬件相关指令,并填充到DRIVER BUFFER中后,调用从核心模式返回到用户模式。

7.GetData循环查询 查询对象 状态。当GPU完成所有DRIVER BUFFER中的指令后会改变查询对象的状态。

 

3)理解清空缓存命令和模式切换:

 

如下情况可能清空RUNTIME COMMAND BUFFER,并引起一个模式切换:

1.Lock method(某些条件下和某些LOCK标志)

 

2.创建设备、顶点缓冲、索引缓冲和纹理

3.完全释放设备、顶点缓冲、索引缓冲和纹理资源

4.调用ValidateDevice

5.调用Present

6.COMMAND BUFFER已满

7.用D3DGETDATA_FLUSH调用GetData函数

GPU处理完D3DQUERYTYPE_EVENT类型查询在CB中加入的D3DISSUE_END标记后,会将查询对象状态置SIGNALED状态,

所以CPU等待查询一定是异步的。

 

二、Runtime管理资源存取

D3DPOOL_MANAGED表示让D3D RUNTIME来管理资源,被创建的资源会有2份拷贝,一份在SM中,一份在VM/AM中,创建的时候被放置L在SM,

在GPU需要使用资源时D3DRUNTIME自动将数据拷贝到VM中去,当资源被GPU修改后,RUNTIME在必要时自动将其更新到SM中来,

而在SM中修改后也会被UPDATE到VM去中。所以被CPU或者GPU频发修改的数据,一定不要使用托管类型,这样会产生非常昂贵的同步负担。

当LOST DEVICE发生后,RESET时RUNTIME会自动利用SM中的COPY来恢复VM中的数据,因为备份在SM中的数据并不是全部都会提交到VM中,

所以实际备份数据可以远多于VM容量,随着资源的不断增多,备份数据很可能被交换到硬盘上,这是RESET的过程可能变得异常缓慢。

 

D3DRUNTIME会优化D3DUSAGE_DYNAMIC 资源,一般将其放置于AM中,但不敢完全保证。另外为什么静态纹理不能被LOCK,动态纹理却可以,

都关系到D3D RUNTIME的设计。

 

RUNTIME给每个MANAGED资源都保留了一个时间戳,当RUNTIME需要把备份数据拷贝到VM中时,RUNTIME会在VM中分配显存空间,如果分配失败,

表示VM已经没有可用空间,这样RUNTIME会使用LRU算法根据时间戳释放相关资源,SetPriority通过时间戳来设置资源的优先级,

最近常用的资源将拥有高的优先级,这样RUNTIME通过优先级就能合理的释放资源,发生释放后马上又要使用这种情况的几率会比较小,

应用程序还可以调用EvictManagedResources强制清空VM中的所有MANAGED资源,这样如果下一帧有用到MANAGED资源,RUNTIME需要重新载入,

这样对性能有很大影响,平时一般不要使用,但在关卡转换的时候,这个函数是非常有用的,可以消除VM中的内存碎片。

 

1)理解D3DPOOL_MANAGED会用比较大的存取空间,设备丢失后不用重新拷贝资源到VM中。

 

2)用EvictManagedResources函数强制清空VM中的所有MANAGED资源,一般情况下不要使用,但是在关卡切换时候非常有用。

 

3)尽量减少BeginScene/EndScene对的使用

为了效率所以尽量少在PRESENT之前使用BEGINSCENE ENDSCENE对,为什么会影响效率?原因只能猜测,可能EndScene会引发Command buffer flush这样会有一个执行的模式切换,

也可能会引发D3D RUNTIME对MANAGED资源的一些操作。而且ENDSCENE不是一个同步方法,它不会等待DRIVER把所有命令执行完才返回。

 

4)合理的规划BeginScene/EndScene对

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算法处理后续DrawCall能很好的解决性能波动问题,

但资源是否被使用是按FRAME为单位来检测的,并不是每个DRAW CALL都被记录,每个FRAME的标志就是BEGINSCENE/ENDSCENE对,

所以在这种情况下合理使用BEGINSCENE/ENDSCENE对可以很好的提高VM不够情况下的性能。

 

5)CPU写AM,合理使用D3DUSAGE_DYNAMIC,D3DPOOL_MANAGED

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。

     第二请尽量一次写满一个CACHELINE,否则会有额外延迟,因为CPU每次必须FLUSH整个CACHE LINE到目标,但如果我们只写了LINE中部分字节,

CPU必须先从AM中读取整个LINE长数据COMBINE后重新FLUSH。

     第三尽可能顺序写,随机写会让WRITE COMBINING反而变成累赘,如果是随机写资源,不要使用D3DUSAGE_DYNAMIC创建,请使用D3DPOOL_MANAGED,

这样写会完全在SM中完成。

 

6)合理的使用AM, 高频修改的纹理不用用D3DUSAGE_TEXTURE创建,RENDERTARGET不要用D3DPOOL_MANAGED创建。

 

普通纹理(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 属性创建,这样效率极低,原因自己分析。

 

7)关闭和开启Driver的资源管理

不光RUNTEIME会MANAGERESOURCE,DRIVER也很可能也实现了这些功能,我们可以通过D3DCAPS2_CANMANAGERESOURCE标志取得DRIVER是否实现资源管理功能的信息,

而且也可以在CreateDevice的时候指定D3DCREATE_DISABLE_DRIVER_MANAGEMENT来关闭DRIVER资源管理功能。

 

8)QUERY机制获取更多资源管理的信息:

根据DX文档的提示我们还可以使用QUERY机制来获得更多关于RUNTIME MANAGED RESOURCE信息,但好像只在RUNTIMEDEBUG模式下有用,理解RUNTIME如何MANAGERESOURCE很重要,

但编写程序的时候不要将这些细节暴露出来,因为这些东西都是经常会变的。

 

 

三、Runtime管理Lock

1)Lock D3DPOOL_DEFAULT资源时Runtime会来回拷贝资源:

如果LOCK DEFAULT资源会发生什么情况呢?DEFAULT资源可能在VM或AM中,如果在VM中,必须在系统内容中开辟一个临时缓冲返回给数据,

当应用程序将数据填充到临时缓冲后,UNLOCK的时候,RUNTIME会将临时缓冲的数据传回到VM中去。

 

2)D3DLOCK_DISCARD能够有效的提高CPU Lock的性能,使得CPU和GPU可以并行工作

不合理的LOCK会严重影响程序性能,因为一般LOCK需要等待COMMANDBUFFER前面的绘制指令全部执行完毕才能返回,

否则很可能修改正在使用的资源,从LOCK返回到修改完毕UNLOCK这段时间GPU全部处于空闲状态,没有合理使用GPU和CPU的并行性,

DX8.0引进了一个新的LOCK标志D3DLOCK_DISCARD,表示不会读取资源,只会全写资源,这样驱动和RUNTIME配合来了个瞒天过海,

立即返回给应用程序另外块VM地址指针,而原指针在本次UNLOCK之后被丢弃不再使用,这样CPU LOCK无需等待GPU使用资源完毕,

能继续操作图形资源(顶点缓冲和索引缓冲),这技术叫VB IB换名(renaming)。

 

四、Runtime管理D3DUSAGE

1)D3DUSAGE_WRITEONLY的性能优势

如果资源D3DUSAGE属性不是WRITEONLY的,则系统还需要先从VM里拷贝一份原始数据到临时缓冲区,这就是为什么不指定WRITEONLY会降低程序性能的原因。

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值