Microsoft DirectX 8 开发人员常见问题
:<!-- Begin Content -->
摘要:本文对与 Microsoft DirectX 8.0 版有关的常见开发问题进行解答,其中包括有关 Direct3D、DirectSound 和 DirectPlay 的章节。
目录
一般性 DirectX 开发事宜
Direct3D
一般问题
几何(顶点)处理
性能调谐
Direct3DX 实用程序库
DirectSound
DirectPlay
您可能没有将 include 路径设置正确。许多编译器(包括 Microsoft® Visual C++®)包含 SDK 的一个较早版本,因此如果您的 include 路径首先搜索标准的编译器 include 目录,则您会得到不正确版本的头文件。为解决这一问题,请确保 include 路径和库路径被设为搜索 DirectX include 和库路径。另请参见 SDK 中的 dxreadme.txt 文件。如果您安装 DirectX SDK 而您又在使用 Visual C++,则可以选择让安装程序为您设置各个 include 路径。
您使用的各种 GUID 应该得到一次性定义,且只能定义一次。如果您在插入 DirectX 头文件之前用 #define 定义 INITGUID 符号,则会插入 GUID 的定义。因此,你应确保只对一个编译单元进行此类操作。这一方法的一个替代方案就是用 dxguid.lib 库进行连接,其中包含所有 DirectX GUID 的定义。 如果您使用这一方法(建议),则您永远不要通过 #define 定义 INITGUID 符号。
不能。DirectX 接口属于 COM 接口。 这意味着不要求将较高版本号的接口从相应的低版本号导出。因此,获得到 DirectX 对象的一个不同接口的唯一安全方法就是使用接口的 QueryInterface 方法。该方法是标准的 IUnknown 接口的一部分,所有 COM 接口必须从其导出。
您可以随意混用不同版本的“不同组件”;例如,您可以在将 DirectPlay 8 和 DirectDraw 7 用在同一应用程序中。但是,您通常不可以将“同一组件”的不同版本混用在同一应用程序中;例如,您不能混用 DirectDraw 7 和 Direct3D 8 (鉴于这些实际上是同一组件,因为 DirectDraw 已被含入 DirectX 8 的 Direct3D)。
返回值将是对象的当前参照计数。但是,COM 规范声明,您不应依赖该返回值;该值通常仅供用于调试目的。您观察到的值可能并非所期待的,因为各种其它系统对象可能保持着对你所创建的 DirectX 对象的参照。 因此,您不应编写反复调用 Release 的代码,一直到参照计数为零,因为此时可以将对象释放,即使另一组件可能仍旧在对其进行参照。
应当没有问题,因为 COM 接口是参照计数的。 但是,在某些 DirectX 版本中,接口的释放次序有一些已知的缺陷。 安全起见,在可能的情况下,建议您以与创建时相反的次序释放接口。
智能指针是一个 C++ 模板类,旨在封装指针功能。尤其有一些标准智能指针类,用于封装 COM 接口指针。这些指针自动进行 QueryInterface,而不是进行造型,并替您处理 AddRef 和 Release。您是否使用这些指针,大体上是个人偏好。如果您的代码包含大量的接口指针复制操作,即使用多重 AddRefs 和 Releases,则智能指针可能会使您的代码更加简洁和不易出错。否则,不用也罢。Visual C++ 包含一个标准的 Microsoft COM 智能指针,是在 "comdef.h" 头文件中定义的(请在帮助中查找 com_ptr_t)。
调试 DirectX 应用程序时最常见的问题就是试图在 DirectDraw 表面被锁定时进行调试。这一情形会在 Microsoft Windows® 9x 系统上导致 "Win16 Lock" ,因此而无法绘制调试窗口。在锁定表面时指定 D3DLOCK_NOSYSLOCK 旗标,通常会消除该现象。Windows 2000 没有这一问题。在开发一个应用程序时,最好运行调试版本的 DirectX 运行时(在安装 SDK 时进行选择);该版本进行某些参数证实,并将一些有用的消息输出到调试程序的输出窗口。
使用 SUCCEEDED 和 FAILED 宏。 DirectX 方法可以返回多个成功和失败代码,因此一个简单的 "==D3D_OK" 或类似的测试结果不总是够用的。
DirectDraw 的大多数功能现已被含入新的 Direct3D8 接口。编制单纯 2D 应用程序的开发人员可能会希望继续使用旧的 DirectX 7 接口。对于编制带有某些 2D 元素的 3D 应用程序的开发人员,鼓励其使用 Direct3D 替代程序(例如点对象和公告牌纹理),因为这会改善性能和灵活性。
请切勿这样做。
Inside COM (《COM 内幕》),Dale Rogerson 著,Microsoft Press 出版,其中对 COM 进行了很好的介绍。如要详细考察 COM,Essential COM (《COM 精解》),Don Box 著,Longman 出版,这本书也很值得推荐。
很多。但笔者认为值得推荐的书有:
关于这一主题的标准书是 Computer Graphics: Principles and Practice (《计算机图形:原理与实践》),Foley, Van Dam 等著;对于任何想要了解几何、光栅化以及照明技巧的人来讲,这都是一个宝贵的资源。comp.graphics.algorithms 新闻组的 FAQ (“常见问题解答”)也包含有用的资料。
这要依具体情况而定。Direct3D 具有一个特性齐全的顶点处理流水线(包含对定制顶点着色器的支持)。但是,没有为像素级操作提供任何的仿真功能;应用程序必须检查相应的帽位,并使用 ValidateDevice API 来确定是否支持。
没有。Direct3D 现在支持插件式软件光栅器。但是,目前并不默认提供任何软件光栅器。
使用。 Direct3D 几何流水线有多个不同的代码路径(具体取决于处理器类型),并将使用 3DNow! 或 Pentium III SIMD 指令所提供的特殊的浮点操作(如果这些指令可用的话)。其中包括定制顶点着色器的处理。
您可以借助高于或低于某一给定门限的 alpha 值来将像素滤除。您通过描绘状态 ALPHATESTENABLE、ALPHAREF 和 ALPHAFUNC 来控制这一操作。
模板缓冲区是一个记录每个像素信息的附加的缓冲区,很象一个 z 缓冲区。 实际上,该缓冲区就驻在 z 缓冲区的某些位中。常见的模板/z 缓冲区格式为 15 位的 z 和 1 位的模板,或 24 位的 z 和 8 位的模板。在描绘多边形时,可以针对每个像素,对模板的内容进行简单的算术操作。例如,可以增加或减少模板缓冲区,或在模板值没能通过一项简单的比较测试时,拒绝像素。可以将帧缓冲区的一个区域标出,然后只对标出(或未标出)的区域进行描绘,上述操作对于此类效果十分有用。各种体积效果就是很好的例子,比如阴影量。
这一效果以及其它体积模板缓冲区效果的关键在于模板缓冲区和 z 缓冲区之间的交互作用。带有阴影量的场景是分三个阶段描绘的。首先,照常使用 z 缓冲区来描绘没有阴影的场景。然后,在模板缓冲区中将阴影标出,如下所示。使用不可见的多边形绘制阴影量的正面,其中 z 测试被启用,而 z 写入被禁用,且在每有一个像素通过 z 测试时,就将模板缓冲区增加一次。以同样方式描绘阴影量的背面,但是要减少模板值。
现在,请考虑单独一个像素的情形。假设摄像机不在阴影量中,场景中的相应点就有有四种可能性。如果从摄像机到点的光线不与阴影量相交,则不会绘制任何的阴影多边形,而模板缓冲区依旧为零。否则,如果点在阴影量的前面,则阴影多边形会被输出 z 缓冲区,而模板依旧保持不变。如果点位于阴影量下面,则会描绘同样多的正面阴影,而模板为零,即增加的次数与减少的次数一样多。
最后一种可能性就是点位于阴影量中。在这种情况下,阴影量的背面会被输出 z 缓冲区,但正面则不然,因而模板缓冲区会为非零。结果就是,帧缓冲区位于阴影中的一些部分具有非零的模板值。最后,要实际描绘阴影,整个场景会被一个 alpha 混色的多边形集充溢一遍,其中仅模板值非零的像素受到影响。在随 DirectX SDK 一起提供的 "Shadow Volume" 示例中有关于该技巧的一个示例。
这一点在 DirectX 8 文档中得到了全面的解释(在文章标题 Directly Mapping Texels to Pixels(直接将 Texel 映射到像素)下)。 但是总而言之,您应将屏幕坐标偏移一个像素的 –0.5,以便正确地与 texel 对齐。 大多数的插卡正确符合对齐规则,但是有一些较老的插卡或驱动程序则不然。要解决这些问题,建议您最好与相关的硬件厂商取得联系,请求得到更新过的应驱动程序或其所建议的变通办法。
如果在创建设备时指定了 D3DCREATE_PUREDEVICE 旗标,则 Direct3D 将创建一个“纯粹”的设备。这禁用 Get* 族类的方法,并使顶点处理仅限于硬件。这使得 Direct3D 运行时能够进行某些优化,以提供到驱动程序的最佳路径,而不必跟踪那么多的内部状态。也就是说,您使用 PUREDEVICE 时可以见到一定的性能优势,但却牺牲了某些便利条件。
DirectX 8 并不支持颜色键控。您应当换用 alpha 混色/测试,大体上这是一个更加灵活的技巧,没有与颜色键控相关的一些问题。
与其它的枚举功能相同,该功能已从基于回调变为由应用程序借助 IDirect3D8 接口的各种方法来进行简单的反复。调用 GetAdapterCount 来确定系统中显示适配器的数目。调用 GetAdapterMonitor 来确定适配器所连接的物理监视器(该方法返回一个 HMONITOR,您然后就可以在 Win32 API GetMonitorInfo 中使用该值,以确定关于物理监视器的信息)。确定某一具体显示适配器的特征,或在该适配器上创建一个 Direct3D 设备,简单到在调用 GetDeviceCaps、CreateDevice 或其它方法时,通过替代 D3DADAPTER_DEFAULT 来传递相应的适配器编号。
不再显式支持“预罐装”的顶点类型。多重顶点流系统允许对顶点数据进行更加灵活的装配。如果您想使用其中一个“传统”的顶点格式,则您可以建立一套相应的 FVF 代码。
Direct3D 对从一个或多个顶点流馈入流水线的每个顶点进行组装。只有一个顶点流时,就对应于 DirectX 8 以前的老模型,即顶点来自单独一个源。借助 DirectX 8,不同的顶点组件可以来自不同的源;例如,一个顶点缓冲区可能含有位置和法线,而另一个则含有颜色值和纹理坐标。
顶点着色器是一个用于处理单一顶点的过程。这是借助一种类似于汇编的简单语言来进行定义的,由 D3DX 实用程序汇编为一个 Direct3D 接受的令牌流。顶点着色器接受单独一个顶点和一组常量值的输入,并输出一个顶点位置(在剪贴空间),还可能输出一组用于光栅化的颜色和纹理坐标。请注意,在您有一个定制的顶点着色器时,顶点组件就不再有任何由 Direct3D 施加给它们的语义,而顶点就只是由您所创建的顶点着色器进行解释的任意数据。
不。顶点着色器在已变换的顶点位置的剪贴空间输出一个纯系坐标。透视分割和剪裁是由后着色器自动进行的。
顶点着色器无法创建或消灭顶点;其一次只对单一顶点进行操作,即作为输入接收一个未经处理的顶点,而输出单独一个经过处理的顶点。因此可以将其用于操作已有的几何图形(应用变形或进行外观变换操作),但实际上无法生成新的几何图形。
不能。您必须选择其一。如果您正在使用一个定制的顶点着色器,则您负责进行整个顶点变换操作。
可以。Direct3D 软件顶点处理引擎完全支持定制的顶点着色器,且性能指标出奇的高。
能够硬件支持顶点着色器的设备被要求填充 D3DCAPS8::VertexShaderVersion 字段,以指示其所支持的顶点着色器的版本级别。所有声称支持某一级别的顶点着色器的设备,必须支持所有合法的顶点着色器,这些顶点着色器符合针对该级别或较低级别的规范。
要求支持 DX8 顶点着色器的设备至少支持 96 个常量寄存器。设备的支持能力可能会超过这一最低数目,且可以通过 D3DCAPS8::MaxVertexShaderConst 字段进行报告。
该情形的一个通常示例就是一个立方体,其中您想为每个面使用一个不同的纹理。很不幸,答案是不行;目前还还不能独立索引每个顶点组件。即使是多顶点流,也是所有的顶点一起索引。
在使用软件几何流水线时,Direct3D 首先转换您所提交的范围中的所有的顶点,而不是“根据要求”按照索引对其进行转换。这对于密集数据(即其中使用了大多数的顶点)效率更高,尤其是在可以使用 SIMD 指令时。如果您的数据比较松散(即很多顶点未被使用),则您可能需要考虑重新排列您的数据,以避免多余的转换。在使用硬件几何加速时,顶点经常是根据需要进行转换的。
索引缓冲区与顶点缓冲区极其类似,但其包含的是用于 DrawIndexedPrimitive 调用的索引。强烈建议您尽可能使用索引缓冲区,而不要使用原始的由应用程序分配的内存,其道理与顶点缓冲区相同。
不可以。你必须检查 D3DCAPS8::MaxVertexIndex 字段,以确定设备所支持的最大索引值。该值必须大于 216-1 (0xffff) 才能支持 D3DFMT_INDEX32 类型的索引缓冲区。另外请注意,某些设备可能支持 32 位的索引,但其所支持的最大索引值却小于 232-1 (0xffffffff);这样,应用程序必须遵从设备所报告的限制。
固定功能流水线要求每条顶点流水线是一个严格的 FVF 子集,即根据一个完整的 FVF 声明预定的。 另外请注意,您必须遵从 D3DCAPS8::MaxStreams 字段所报告的流水线数目的限制(现在的许多设备和/或驱动程序仅支持单一流水线)。
在优化性能时,需要考察下列几个关键问题:
注意:在 DirectX 8 中更改缓冲区已不在象在以前版本中那样昂贵了,但依旧建议尽量避免更改顶点缓冲区。
状态更改可能依旧很昂贵,但使用状态宏至少会有助于降低部分成本。
在真实数据中遇到的许多网格,都具有多个多边形共享顶点的特性。为将性能最大化,最好将所转换的顶点中的重复率降低,并横跨总线将其发给描绘设备。使用简单的三角列表根本实现不了任何顶点共享,因而这是最不理想的方法。这一点已很清楚。然后所要作的选择就是使用条形和扇形(暗示多边形之间的具体连接关系),还是使用索引列表。在数据自然归入各种条形和扇形的情况下,这些就是最为合适的选择,因为发给驱动程序的数据被降至最低。但是,将网格分解条形和扇形经常会造成大量分离块,暗示有大量的 DrawPrimitive 调用。因此,最富效率的方法通常是使用带三角列表的单独一个 DrawIndexedPrimitive 调用。使用索引列表的另一个优势就是,这在连续三角形仅共享单独一个顶点时也有益处。总而言之,如果您的数据自然归入各种较大的条形和扇形,就使用条形和扇形,否则就使用索引列表。
D3DX 图象文件加载器函数支持 BMP、TGA、PNG、JPG、DIB、PPM 和 DDS 文件。
使用 ID3DXFont::DrawText 功能时的一个常见错误就是为颜色参数指定一个零 alpha 组件,从而导致完全透明(即不可见)的文本。对于完全不透明的文本,请确保色彩参数的 alpha 成分完全饱和 (255)。
ID3DXFont 类能够处理字间距,因为它使用 GDI 来绘制字符串。这可能会有点慢,因为每次均需要调用 GDI。
CD3DFont 设计用于加速和使用纹理化基本类型来绘制字符。它只能处理简单字体,并不支持 ID3DXFont 可用的全套格式选项,但适用于简单而快速的显示,诸如帧速率计数等等。
对于产品代码,您可能需要实施您自己的字形描绘功能,即借助纹理化基本类型和/或基于 GDI 的方案(带避免重新绘制的缓存功能)。
您可能安装了调试用 DirectX 运行时。运行时的调试版本用静噪声填充缓冲区,以便帮助开发人员捕捉未经初始化缓冲区的缺陷。您无法保证 DirectSound 缓冲区在创建后的内容;尤其是您无法将缓冲区清零。
NAT 和 ICS 是较为复杂的主题,在 MSDN 上的另一篇文章中有更详尽的论述。但是,下列提示可以作为很好的一般性指导:
有关点对点游戏、将服务器驻于 NAT 后面的事宜,以及针对各种不同的 Windows 操作系统上的 ICS 的具体建议,请参考更详尽的文档。
DPNSVR 是一种用于枚举请求的转发服务,消除了多个 DirectPlay 应用程序在端口使用上的冲突所导致的各种问题。使用 DPNSVR 使得 DirectPlay 能够自动选择要使用的端口,同时又允许客户端对您的游戏进行枚举。默认为,DirectPlay 会使用 DPNSVR,因为这通常为应用程序提供了最大的灵活性;但是,您可以将其禁用,方法是在创建您的会话时指定 DPNSESSION_NODPNSVR 旗标。如果客户端使用 DPNSVR 端口来枚举主机,而主机又使用自己的端口来作出响应的话,使用 DPNSVR 可能会导致客户端一侧的 NAT 发生问题;NAT 可能会拒绝将数据包转递给客户端,因为数据包不是来自请求被发往的同一端口。
DirectPlay 不允许每个进程有一个以上的前端客户端或应用程序,因而试图创建多个客户端会导致返回这一错误。
<!-- END TOTAL PAGE CONTENT -->