D3DFrame 入门
作者:Philip Taylor
发布日期:2000 年 5 月 15 日 将于 2000 年 6 月 19 日存档
欢迎关注“Driving DirectX”的第二个问题,MSDN Online Voices 的 DirectX® 专栏。在下面几个专栏中,我想着重讨论 Direct3d(R) (D3D) for DirectX 7.0。对于 D3D 示例和 D3DFrame,好的切入点是“DirectX 软件开发工具包 (SDK)”应用程序框架。所有的 D3D 示例都使用 D3DFrame。
使用公用的框架 D3DFrame,为我们提供了公共平台,在此平台上我们可以展开各种功能的讨论。
一些图形芯片厂商使用 D3DFrame 的变体作为自己的示例;例如 ATI SDK 使用具有某些 ATI 专用附件的 D3DFrame 版本。使用公共框架提供了公共平台,在此平台上可以进行如何启用各种功能的讨论。使用公共框架也使我能够避开 D3D 的一般“基本知识”。这并不意味着 D3DFrame 可以作为产品代码;相反,它只是编写短小精悍示例的“方便”工具而已。
开卷语
在开始说明 D3DFrame 之前,让我们回顾一下 DirectX SDK 基础知识,并提供关于 SDK 开发的共同语言。请确保在您建立的环境中,查找头文件和库文件目录的命令是正确的。常见的问题是这些错误的命令,导致了许许多多编译和链接错误。安装 DirectX SDK 的默认路径是 \mssdk。在安装 SDK 时您可以控制该路径。运行时的头文件和库文件保存在 \mssdk\include 和 \mssdk\lib 中。
SDK HTML Help 是大体了解 DirectX 的主要资源,特别是对 D3D。我在解决了某些问题,以及有了某种想法之后,多次重读了该文档中的段落,于是有了温故知新的感觉,“喔,原来是这么回事。”示例代码是学习 DirectX 另一个最好的工具。“Direct3D 立即模式”示例代码保存在 \mssdk\samples\multimedia\d3dim 中。在 \d3dim 下面为 \bin、\include、\lib、\media 和 \src。以准备建立的形式存在的所有示例,可以在 \src 中找到,这取决于 \include 和 \lib,并且使用 \media 中的艺术内容。SDK 安装程序将预先建立的可执行文件植入 \bin 下。我强烈推荐用示例来进行试验,并检验使用它们列出的功能所需的代码。这是快速使用 D3D 的最快的方法。
调试 DirectX 应用程序可能是个难题。让我来介绍几种使工作更轻松的有用的小工具和技术。DXDiag SDK 实用工具提供有关 SDK 安装的信息。一定要对您的系统了如指掌。如果发现任何缺陷,则需要这些信息来报告缺陷。而且,一定要在 SDK 安装时选择调试运行时选项。如此就能保证在调试运行时进行开发,调试运行时包含错误检查和报告,这能真正帮助您解释代码在哪里出现了偏差。调试运行时允许设置启用运行时内部调试的报告级。“DirectX 控制面板”允许开发人员将报告级设置为 0 到 5。我在调试时一般设置为 3,而对于特别严重的缺陷要设置为 5。任何调试程序可以跟踪调试。Visual C++ GUI 调试程序提供了调试输出窗格,专门用来跟踪调试。Visual C++ GUI 调试程序,只有在使用下列系统时,才能调试全屏独占应用程序:
多监视器系统
远程调试
在这种思想指导下,通常明智的做法是产生代码并先在窗口模式下调试,然后再专注于全屏幕。
高级视图
让我们现在回到 DirectX 7.0 D3DFrame。D3DFrame 是 Direct3D SDK 示例的示例框架。如果您确实想了解示例是如何工作的 — 不是了解示例中列出的功能 — 必须对 D3DFrame 如何工作有基本了解。首先我会带您进入包含所有必须了解的内容的 D3DFrame 高级视图,并且使用 D3DFrame 编写自己的示例。对于象谚语中的猫一样好奇的人,我将就 D3DFrame 的主要部分给出更详细等级的概要介绍,以便解释如何提供应用程序接口。在随后的讨论中,我将在示例应用 D3DFrame 的同时介绍有关 D3DFrame 的更多内容。
图 1. D3DFrame 项目视图
D3DFrame 由 7 个模块组成:
D3DApp 展示了该示例使用的应用程序接口。
D3DFrame 提供了应用程序接口使用的框架,“在该框架下”进行 voodoo 会很顺利。
D3DEnum 包含对驱动程序、设备和模式的枚举支持。
D3DTextr 提供纹理支持。
D3DFile 提供 x-文件支持。
D3DMath 给出数学实用程序函数。
D3DUtil 包括剩下的其他各种有用的函数。
每个模块都是由一个 .cpp 源文件和 .h 头文件组成。源文件位于 d3dim\src。头文件位于 d3dim\include。框架库位于 d3dim\lib。鉴于本文的目的,我主要讨论 D3DApp 和 D3DFrame。
检查用户编写的函数
D3DApp 模块包含类 CD3DApplication。该类在 D3Dapp.h 文件中,为示例程序发布接口,如下所示:
牋牋 // 应用程序创建的可替代的 3D 场景函数
virtual HRESULT OneTimeSceneInit() { return S_OK; }
virtual HRESULT InitDeviceObjects() { return S_OK; }
virtual HRESULT DeleteDeviceObjects() { return S_OK; }
virtual HRESULT Render() { return S_OK; }
virtual HRESULT FrameMove( FLOAT ) { return S_OK; }
virtual HRESULT RestoreSurfaces() { return S_OK; }
virtual HRESULT FinalCleanup() { return S_OK; }
要使用示例框架创建新的 D3D 应用程序,只要创建新的项目和这些函数的新的实现;让我们为 D3Dframe 示例调用“公共接口”。这正是 D3D SDK 示例所做的。让我们看一看这些函数:
OneTimeSceneInit 允许示例完成一次性初始化任务,例如加载纹理和 x-文件,建立计算数值,生成程序的结构和类似的操作。通常,任何一次性的资源分配应该在这里完成。在每个应用程序执行周期中,OneTimeSceneInit 被调用一次。
InitDeviceObjects 给示例提供了初始化每个设备对象的机会。通常情况下,程序执行诸如将纹理位加载到设备表面、设置矩阵、设置光源以及填充顶点缓冲区等等操作。在初始化第一个设备以及每当设备有变化时,调用 InitDeviceObjects。
DeleteDeviceObjects 包含示例用于删除在 InitDeviceObjects 中创建的对象的代码。这两个函数是成对匹配的;一定要使您的设备专用的资源分配与删除匹配,否则每当设备改变时将泄漏内存。每当设备被取消时调用 DeleteDeviceObjects。
Render 是所有展现发生的地方。在这儿,清除框架缓冲区并调用 BeginScene/EndScene 对之间所有的展现代码。每帧调用 Render 一次。
FrameMove 提供了收集每帧动画任务的简便方法。更新矩阵,纹理调整,对象调整和其他随时间变化的动作都发生在 FrameMove。每帧调用 FrameMove 一次。
RestoreSurfaces 使示例测试丢失的表面并重新恢复它们,使应用程序可以继续执行。在表面因应用程序切换而丢失时,调用 RestoreSurfaces。
FinalCleanup 与 OneTimeSceneInit 配对,并提供一次取消一个应用程序的对象的方法。一定要使清除代码与初始化代码匹配,避免内存泄漏。
D3DFrame 内部
一旦掌握了应用函数的用途后,就该看一看什么东西会使它们准确运行。除了公共接口以外,类 CD3DApplication 有两个值得检查的部分。首先,类 CD3DApplication 包含下列成员函数:
HRESULT Initialize3DEnvironment();
HRESULT Change3DEnvironment();
HRESULT Render3DEnvironment();
VOID Cleanup3DEnvironment();
这些函数使用了公共接口和类 CD3DFramework7,来处理 D3D 应用程序的许多工作。
Initialize3DEnvironment 调用 CD3DFramework7->Initialize 来初始化示例框架,然后调用公共接口 InitDeviceObjects 来初始化设备专用对象。构造该代码用于处理在初始化过程中可能发生的错误。
Change3DEnvironment 为应用程序处理驱动程序、设备和/或模式的改变。它调用公共接口 DeleteDeviceObjects 释放旧设备的对象。然后建立新的设备状态,并调用 Initialize3DEnvironment 以创建新设备的环境。
Render3DEnvironment 完成五个重要的任务。首先,它检查合作状态以保证它可以继续展现。接着,它为随时间变化的动画调用公共接口 FrameMove。然后调用公共接口 Render 以完成绘制。如果启动了统计,则进行绘制。最后使用 CD3DFramework7->ShowFrame 显示帧。如果显示帧导致表面丢失情况,则框架和公共接口 RestoreSurfaces 都将被调用。
Cleanup3DEnvironment 调用 DeleteDeviceObjects 和 FinalCleanup 为应用程序结束做准备。
我们不要忘了 D3D 应用程序实际上是 Windows 应用程序。Win32 应用程序的内容核心隐藏在成员函数中:
virtual HRESULT Create( HINSTANCE, TCHAR* );
virtual INT Run();
virtual LRESULT MsgProc( HWND hWnd, UINT uMsg,
WPARAM wParam, LPARAM lParam );
在往下深入讲解之前,让我们看一看当使用 CD3DApplication 时 WinMain 变为什么。在本实例中,WinMain 是这样结束的:
INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT )
{
CMyD3DApplication d3dApp;
if( FAILED( d3dApp.Create( hInst, strCmdLine ) ) )
return 0;
return d3dApp.Run();
}
这是非常简单的 WinMain。并且隐藏 Create 和 Run 的动机是明确的,因为它们是 Winmain。MsgProc 是 Windows 消息处理程序。在内部,它们都使用 xxx3DEnvironment 函数和类 CD3DFramework7 来管理 D3D 应用程序以及 Windows 应用程序。
Create 使用模块 D3DEnum 提供的对枚举驱动程序、设备的支持,以及当前硬件的模式支持;它还选择一个默认值。然后调用公共接口 OneTimeSceneInit。一旦创建了用户对象,新的 CD3DFramework7 对象将实例化。需要注意一般的 Win32 细节,注册窗口类,然后创建窗口。最后,调用 Initialize3DEvironment 创建新设备的环境。
Run 完成两个重要的任务:Windows 消息循环处理和调用 Render3DEnvironment。Run 也为 WM_QUIT 消息和退出做准备。
MsgProc 是 Windows 消息的消息处理程序。它依赖并使用 xxx3DEvironment 函数和 CD3DFramework7 对象管理 Win32 和 D3D 应用程序。
现在,我已经大体介绍了 CD3DApplication 类的工作方式,以及它如何使用 CD3DFramework7 类,下面再深入研究 CD3DFramework7 和它的内部。
在类 CD3DFramework7 中主要的繁重任务是由下面两组成员函数完成的:
// 创建框架
HRESULT Initialize( HWND hWnd,GUID* pDriverGUID,
GUID* pDeviceGUID, DDSURFACEDESC2* pddsd,
DWORD dwFlags );
HRESULT DestroyObjects();
// 框架类的内部函数
HRESULT CreateZBuffer( GUID* );
HRESULT CreateFullscreenBuffers( DDSURFACEDESC2* );
HRESULT CreateWindowedBuffers();
HRESULT CreateDirectDraw( GUID*, DWORD );
HRESULT CreateDirect3D( GUID* );
HRESULT CreateEnvironment( GUID*, GUID*, DDSURFACEDESC2*,
DWORD );
让我们先看第一组,Initialize 和 DestroyObjects。
Initialize 是创建框架内部对象的公共函数。它在内部建立一些状态,然后依赖 CreateEnvironment 去完成大部分艰难的工作。
DestroyObjects 仅仅释放 Initialize 中创建的所有 DirectX COM 对象。
CreateEnvironment 创建框架的内部对象。它决定设备的内存类型,然后调用 CreateDirectDraw。一旦它们不正常,CreateFullscreenBuffers 或 CreateWindowedBuffers 就会创建前台和后端缓冲区。最后,CreateDirect3D 创建 3-D 设备,并且 CreateZBuffer 创建并连接 Z-缓冲区。
鉴于理解 D3DFrame 的目的,我们不需要了解 CreateDirectDraw、CreateFullscreenBuffers、CreateWindowedBuffers、CreateDirect3D 或 CreateZBuffer 内部的详细实现。它们是直接相关的 — 这一句就够了。而对于那些想了解如何精确实现的人,源代码正等待他们去研究。
类 CD3Dframework7 中值得一提的最后几组函数提供了另外一些对展现的支持。它们是:
// 帮助展现的函数
HRESULT RestoreSurfaces();
HRESULT ShowFrame();
HRESULT FlipToGDISurface( BOOL bDrawFrame = FALSE );
VOID Move( INT x, INT y );
对于这些内容,我只是粗略带过,因为它们在先前检验的函数中使用,并且它们的用法可以轻松地从上下文导出。
要创建新的示例,现在只须实现一个类,诸如,下面所示的 CMyD3Dapplication:
class CMyD3DApplication : public CD3DApplication
{
protected:
HRESULT OneTimeSceneInit();
HRESULT InitDeviceObjects();
HRESULT DeleteDeviceObjects();
HRESULT Render();
HRESULT FrameMove( FLOAT fTimeKey );
HRESULT FinalCleanup();
static HRESULT ConfirmDevice( DDCAPS*, D3DDEVICEDESC7* );
public:
CMyD3DApplication();
}
完成之后,您就拥有一个有效的 D3DFrame 示例了。可以看见它在 D3D SDK 示例中反复出现。或者等下一个专栏。仅提供空实现功能将导致空的 D3D 应用程序。我已经完成它,将作为以后专栏的基础,这里我也提供了源代码。运行在窗口中的空 D3D 应用程序,如图 2 所示。
图 2. D3DFrameApp 示例
结束语
作为下面几部分的难题,请参阅关于下列内容的四篇 DirectX 7.0 Direct3D 文章:
虚拟光
两篇关于 Alpha 混合物的文章:
顶点中的 Alpha
纹理中的 Alpha
DotProduct3 展现
“虚拟光”一文将说明如何操纵 Direct3D 光,以避开大多数硬件施加的八光限制。Alpha 混合物文章将说明两种不同的透明技术:顶点中的透明和纹理中的透明。DotProduct3 文章将讨论非常好的 DOTPRODUCT3 操作符及其用途(提示,可将 DOTPRODUCT3 视为广义的从矢量到像素的查找函数)。每篇文章都包含一个基于 D3DFrame 的示例。
欢迎您提出反馈!欢迎您通过下面的地址,将您的评论、问题、主题思想或者您对本专栏涉及的主题的不同意见告诉我。不过,请不要期待单独回复或发给我请求支持的问题。请记住,Microsoft 有一个活动邮件的列表,DirectXDev,供志趣相投的开发者作为论坛来共享信息。Web 界面位于 http://DISCUSS.MICROSOFT.COM/archives/DIRECTXDEV.html(英文)。提问之前,请阅读 FAQ,它位于 http://msdn.microsoft.com/library/techart/dxfaq2.htm(英文)。