本文写的较为深入,故转载在此留以备案,呵呵~

原文链接为: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显示开放驱动和硬件架构信息,这些东西就很容易弄明白了。

深入理解D3D9的更多相关文章

  1. 【转载】深入理解Direct3D9

    原文:Effulgent的<深入理解Direct3D9>整理版(转) 深入理解Direct3D9 深入理解D3D9对图形程序员来说意义重大,我把以前的一些学习笔记都汇总起来,希望对朋友们有 ...

  2. 理解CSS视觉格式化

    前面的话   CSS视觉格式化这个词可能比较陌生,但说起盒模型可能就恍然大悟了.实际上,盒模型只是CSS视觉格式化的一部分.视觉格式化分为块级和行内两种处理方式.理解视觉格式化,可以确定得到的效果是应 ...

  3. 彻底理解AC多模式匹配算法

    (本文尤其适合遍览网上的讲解而仍百思不得姐的同学) 一.原理 AC自动机首先将模式组记录为Trie字典树的形式,以节点表示不同状态,边上标以字母表中的字符,表示状态的转移.根节点状态记为0状态,表示起 ...

  4. 理解加密算法(三)——创建CA机构,签发证书并开始TLS通信

    接理解加密算法(一)--加密算法分类.理解加密算法(二)--TLS/SSL 1 不安全的TCP通信 普通的TCP通信数据是明文传输的,所以存在数据泄露和被篡改的风险,我们可以写一段测试代码试验一下. ...

  5. node.js学习(三)简单的node程序&&模块简单使用&&commonJS规范&&深入理解模块原理

    一.一个简单的node程序 1.新建一个txt文件 2.修改后缀 修改之后会弹出这个,点击"是" 3.运行test.js 源文件 使用node.js运行之后的. 如果该路径下没有该 ...

  6. 如何一步一步用DDD设计一个电商网站(一)—— 先理解核心概念

    一.前言     DDD(领域驱动设计)的一些介绍网上资料很多,这里就不继续描述了.自己使用领域驱动设计摸滚打爬也有2年多的时间,出于对知识的总结和分享,也是对自我理解的一个公开检验,介于博客园这个平 ...

  7. 学习AOP之透过Spring的Ioc理解Advisor

    花了几天时间来学习Spring,突然明白一个问题,就是看书不能让人理解Spring,一方面要结合使用场景,另一方面要阅读源代码,这种方式理解起来事半功倍.那看书有什么用呢?主要还是扩展视野,毕竟书是别 ...

  8. ThreadLocal简单理解

    在java开源项目的代码中看到一个类里ThreadLocal的属性: private static ThreadLocal<Boolean> clientMode = new Thread ...

  9. JS核心系列:理解 new 的运行机制

    和其他高级语言一样 javascript 中也有 new 运算符,我们知道 new 运算符是用来实例化一个类,从而在内存中分配一个实例对象. 但在 javascript 中,万物皆对象,为什么还要通过 ...

随机推荐

  1. (二)Spring框架之JDBC的基本使用(p6spy插件的使用)

    案例一: 用Spring IOC方式使用JDBC Test_2.java package jdbc; import java.lang.Thread.State; import java.sql.Co ...

  2. luogu题解P1967货车运输--树链剖分

    题目链接 https://www.luogu.org/problemnew/show/P1967 分析 NOIp的一道裸题,直接在最大生成树上剖分取最小值一下就完事了,非常好写,常数也比较小,然而题解 ...

  3. 安装mysql采坑记录

    安装之前彻底卸载之前的mysql,再次安装,初始化数据库那一步失败. 再次彻底卸载mysql,把原先的安装路径的文件夹删除,文件夹路径:C:\ProgramData,再次安装,成功. 总结:重装mys ...

  4. 记录下js几种常见的数组排序和去重的方法

    冒泡排序 , , , , , , , ]; function test(){ ; i < arr.length - ; i++){ ; j < arr.length; j++){ var ...

  5. 【Redis】分布式Session

    一.问题引出 1.1 Session的原理 1.2 问题概述 二.解决方案 三.代码实现-使用Token代替Session 3.1 Service 3.2 TokenController 一.问题引出 ...

  6. VUE目录

    学前预备知识 ECMAScript简介和ES6的新增语法 Nodejs基础 webpack的介绍 babel简介 vue基础 vue基础

  7. bash基础——grep、基本正则表达式、扩展正则表达式、fgrep

    grep grep全称:Globally search a Regular Expression and Print 全局搜索正则表达式 正规表达式本质上是一种"表示方法", 只要 ...

  8. pythoth 中常用的魔法方法

    Python魔法方法(magic method),顾名思义,魔法总是带有一些神奇色彩,就跟魔术似的.它也是有自己的规律,在这里或者说规则更合适一些. 魔法方法有一个非常鲜明的特征,就是总是被双下划线所 ...

  9. idou老师教你学Istio12 : Istio 实现流量镜像

    微服务为我们带来了快速开发部署的优秀特性,而如何降低开发和变更的风险成为了一个问题.Istio的流量镜像,也称为影子流量,是将生产流量镜像拷贝到测试集群或者新的版本中,在引导实时流量之前进行测试,可以 ...

  10. Windows安全应急响应(一)

    入侵排查思路 检查账号安全 1.查看服务器是否有弱口令,远程管理端口是否对公网开放 2.查看服务器是否存在可以账号.新增账号 3.查看服务器是否存在隐藏账号.克隆账号检查方法:i.打开注册表,查看管理 ...