【转载】深入理解Direct3D9

原文:Effulgent的《深入理解Direct3D9》整理版(转)

深入理解Direct3D9

深入理解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 ADAPTERD3D DEVICE

● D3D OBJECT很简单,就是一个使用D3D功能的COM对象,其提供了创建DEVICE和枚举ADAPTER的功能。

● ADAPTER是对计算机图形硬件和软件性能的一个抽象,其包含了DEVICE。

● DEVICE则是D3D的核心,它包装了整个图形流水管线,包括变换、光照和光栅化(着色);

DEVICE

根据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编写自己的软件图形驱动,然后注册进系统,之后便可在程序中使用。

Direct3D System Integration

检查系统软件硬件性能。
在程序的开始我们就要判断目标机的性能,其主要流程是:

  1. 确定要用的缓冲格式
  2. GetAdapterCount()
  3. GetAdapterDisplayMode
  4. GetAdapterIdentifier //得到适配器描述
  5. CheckDeviceType //判断指定适配器上的设备是否支持硬件加速
  6. GetDeviceCaps //指定设备的性能,主要判断是否支持硬件顶点处理(T&L)
  7. GetAdapterModeCount //得到适配器上指定缓冲格式所有可用的显示模式
  8. EnumAdapterModes //枚举所有显示模式
  9. CheckDeviceFormat
  10. 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)

Memory

  • 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。

所有D3D资源都创建在这3种内存之中,在创建资源时,我们可以指定如下存储标志

  • ● D3DPOOL_DEFAULT
  • ● D3DPOOL_MANAGED
  • ● D3DPOOL_SYSTEMMEM
  • ● D3DPOOL_SCRATCH。

如果我们使用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显示开放驱动和硬件架构信息,这些东西就很容易弄明白了。

时间: 2024-12-25 13:37:09

【转载】深入理解Direct3D9的相关文章

转载——便于理解mysql内幕的各种逻辑图组

原文地址:http://blog.sina.com.cn/s/blog_445e807b0101ggtl.html 1,顶顶有名的官方mysql架构图,理解mysql整体机构必不可少 2,更直观性mysql整体逻辑机构图: 3,innodb引擎架构图,对理解innodb内部结构大有裨益 4,mysql sql执行过程示意图,对理解mysql执行计划有很大帮助 5,当 ‘innodb_flush_log_at_trx_commit = x ’x=0,1,2 各值时刷盘时的示意图 6,mysql 内

【转载】理解矩阵(三)

原文:理解矩阵(三) 理解矩阵(一) 理解矩阵(二)        这两篇文章发表于去年的4月.在第二部分结束的时候,我说:       “矩阵不仅可以作为线性变换的描述,而且可以作为一组基的描述.而 作为变换的矩阵,不但可以把线性空间中的一个点给变换到另一个点去,而且也能够把线性空间中的一个坐标系(基)表换到另一个坐标系(基)去.而且,变换点 与变换坐标系,具有异曲同工的效果.线性代数里最有趣的奥妙,就蕴含在其中.理解了这些内容,线性代数里很多定理和规则会变得更加清晰.直觉. 这个留在下一篇再

[转载] 快速理解Kafka分布式消息队列框架

转载自http://blog.csdn.net/xiaolang85/article/details/18048631 ==是什么 == 简单的说,Kafka是由Linkedin开发的一个分布式的消息队列系统(Message Queue) 目标Scope(解决什么问题) kafka开发的主要初衷目标是构建一个用来处理海量日志,用户行为和网站运营统计等的数据处理框架.在结合了数据挖掘,行为分析,运营监控等需求的情况下,需要能够满足各种实时在线和批量离线处理应用场合对低延迟和批量吞吐性能的要求.从需

转载 深入理解JavaScript中的this关键字

转载原地址: http://www.cnblogs.com/rainman/archive/2009/05/03/1448392.html 深入理解JavaScript中的this关键字 1. 一般用处 2. this.x 与 apply().call() 3. 无意义(诡异)的this用处 4. 事件监听函数中的this 5. 总结 在JavaScript中this变量是一个令人难以摸清的关键字,this可谓是非常强大,充分了解this的相关知识有助于我们在编写面向对象的JavaScript程

[转载] 深入理解Android之Java虚拟机Dalvik

本文转载自: http://blog.csdn.net/innost/article/details/50377905 一.背景 这个选题很大,但并不是一开始就有这么高大上的追求.最初之时,只是源于对Xposed的好奇.Xposed几乎是定制ROM的神器软件技术架构或者说方法了.它到底是怎么实现呢?我本意就是想搞明白Xposed的实现原理,但随着代码研究的深入,我发现如果不了解虚拟机的实现,而仅简单停留在Xposed的调用流程之上,那真是对Xposed最大的不敬了.另外,歪果仁为什么能写出Xpo

【转载】理解C语言中的关键字extern

原文:理解C语言中的关键字extern 最近写了一段C程序,编译时出现变量重复定义的错误,自己查看没发现错误.使用Google发现,自己对extern理解不透彻,我搜到了这篇文章,写得不错.我拙劣的翻译了一下.(原文:http://www.geeksforgeeks.org/understanding-extern-keyword-in-c/)   我确定这篇文章对c语言的初学者会有很大的帮助,因为这将使他们更好更熟练的使用c语言.所以就让我先来说说extern关键字在变量和函数上的应用.最基本

【转载】理解矩阵(二)

原文:理解矩阵(二) 接着理解矩阵. 上一篇里说“矩阵是运动的描述”,到现在为止,好像大家都还没什么意见.但是我相信早晚会有数学系出身的网友来拍板转.因为运动这个概念,在数学和物理里是跟微积分联系在一起的.我们学习微积分的时候,总会有人照本宣科地告诉你,初等数学是研究常量的数学,是研究静态的数学,高等数学是变量的数学,是研究运动的数学.大家口口相传,差不多人人都知道这句话.但是真知道这句话说的是什么意思的人,好像也不多.简而言之,在我们人类的经验里,运动是一个连续过程,从A点到B点,就算走得最快

【转载】理解矩阵(一)

原文:理解矩阵(一) 前不久chensh出于不可告人的目的,要充当老师,教别人线性代数.于是我被揪住就线性代数中一些务虚性的问题与他讨论了几次.很明显,chensh觉得,要让自己在讲线性代数的时候不被那位强势的学生认为是神经病,还是比较难的事情. 可怜的chensh,谁让你趟这个地雷阵?!色令智昏啊! 线性代数课程,无论你从行列式入手还是直接从矩阵入手,从一开始就充斥着莫名其妙.比如说,在全国一般工科院系教学中应用最广泛的同济线性代数教材(现在到了第四版),一上来就介绍逆序数这个“前无古人,后无

(转载)Android理解:显式和隐式Intent

Intent分两种:显式(Explicit intent)和隐式(Implicit intent). 一.显式(设置Component) 显式,即直接指定需要打开的activity对应的类. 以下多种方式都是一样的,实际上都是设置Component直接指定Activity类的显式Intent,由MainActivity跳转到SecondActivity: 1.构造方法传入Component,最常用的方式 Intent intent = new Intent(this, SecondActivit