Frank Luna
www.moon-labs.com
- 1 -
Integrating Direct3D 9.0 with MFC Using Visual
Studio .Net (7.0)
By Frank Luna
www.moon-labs.com
Created on March 16, 2003
(Original Direct3D 8.1 Version Created on April 8, 2002 and is Available at
http://www.gamedev.net/reference/articles/article1778.asp)
1.1 Introduction
There is often some difficulty integrating the DirectX APIs with the Microsoft
Foundation Classes (MFC). The DirectX SDK provides several samples using DirectX
with MFC, but none of them use the desired document/view architecture or the
application wizards. This paper shows the reader, step by step, how to set up a simple
Direct3D 9.0 application in the MFC framework using the wizards and the
document/view architecture in Visual Studio .Net. It is assumed that the reader has some
experience with both Direct3D and MFC but not both of them together.
The project we will build is simple; we will load a teapot into a mesh data
structure and rotate it about the y-axis. The final rendering will look something like the
picture below – figure (1) – but will be animated.
Figure 1: Screenshot of the application we will develop in this paper.
Frank Luna
www.moon-labs.com
- 2 -
1.2 Creating the Project
This paper will move at a slow pace and attempt to walk you through it one step at a time.
We will start by creating a new project in Visual Studio .Net (VS7). Select the MFC
Application icon as shown in figure (2) and enter a project name such as
“Direct3D9MFC.” Then press the OK button.
Figure 2: Select an MFC Application template for this project.
The next dialog box that appears allows you to configure your application. Select
the Application Type tab on the left as shown in figure (3). Then select Single
document as the application type and be sure that the Document/View architecture
support checkbox is checked. The rest of the settings can be left to the default so press
the Finish button to create the application.
Frank Luna
www.moon-labs.com
- 3 -
Figure 3: Application creation settings. Specify how you want your application to be created using
these dialogs. For this demonstration we want a Single Document Interface and we want to use the
Document/View Architecture.
After pressing Finish the application template should have been created successfully.
Just to make sure, you may want to compile, build, and execute your project before
continuing.
Now, before we move on to integrating Direct3D with MFC, we should link the
Direct3D library files “d3d9.lib” and “d3dx9.lib” into our project. In addition, we will
link in “winmm.lib” (Windows Multimedia Library) for timer functions. In VS7 you can
specify the library files to link in by going to the menu and selecting Project-
>Properties->Linker->Input Folder and then entering the library names as shown in
figure (4).
Frank Luna
www.moon-labs.com
- 4 -
Figure 4: Link “d3d9.lib”, “d3dx9.lib” and “winmm.lib” into your application.
1.3 Integrating Direct3D with MFC
We will integrate Direct3D with MFC by having the CDirect3D9MFCView (CView)
contain our Direct3D related interfaces. This makes sense because CDirect3D9MFCView
is responsible for drawing data and will need direct access to Direct3D to do that
drawing.
1.3.1 CDirect3D9MFCView Data Additions
We will begin by adding the following variables to the CDirect3D9MFCView class:
#include
class CDirect3D9MFCView : public CView
{
...
private:
IDirect3DDevice9* _device;
D3DPRESENT_PARAMETERS _d3dpp;
ID3DXMesh* _teapot;
};
Frank Luna
www.moon-labs.com
- 5 -
You should already be familiar with the first two variable types, _device and _d3dpp,
and know what they do or what they are used for; as they are fundamental Direct3D types
that you should know before attempting to read this paper. Therefore I will not elaborate
on them. The third variable, _teapot, is simply a mesh data structure that will store the
geometry of the teapot we are going to render in this paper’s sample program.
Remember that you will need to include d3dx9.h at the top of your CDirect3D9MFCView
class.
Note that the constructor and destructor for CDirect3D9MFCView are implemented
as follows:
CDirect3D9MFCView::CDirect3D9MFCView()
: _device(0), _teapot(0)
{
::ZeroMemory(&_d3dpp, sizeof(_d3dpp));
}
CDirect3D9MFCView::~CDirect3D9MFCView()
{
if( _teapot )
_teapot->Release();
if( _device )
_device->Release();
}
1.3.2 CDirect3D9MFCView Method Additions
Now add the following methods to the CDirect3D9MFCView class:
class CDirect3D9MFCView : public CView
{
...
private:
HRESULT initD3D(
HWND hwnd,
int width,
int height,
bool windowed,
D3DDEVTYPE deviceType);
HRESULT setup(int width, int height);
HRESULT cleanup();
public:
HRESULT update(float timeDelta);
HRESULT render();
};
Frank Luna
www.moon-labs.com
- 6 -
• The initD3D method is responsible for initializing Direct3D, that is, obtaining a
pointer to an IDirect3DDevice9 interface based on the arguments passed in. For
simplicity, the sample application code leaves out device enumeration and selects
a “safe configuration” when initializing Direct3D. The corresponding sample
program to this paper implements this method as follows:
HRESULT CDirect3D9MFCView::initD3D(HWND hwnd,
int width,
int height,
bool windowed,
D3DDEVTYPE deviceType)
{
HRESULT hr = 0;
// Step 1: Create the IDirect3D9 object.
IDirect3D9* d3d9 = 0;
d3d9 = Direct3DCreate9(D3D_SDK_VERSION);
if( !d3d9 )
{
::MessageBox(0, "Direct3DCreate9() - FAILED", 0, 0);
return E_FAIL;
}
// Step 2: Check for hardware vp.
D3DCAPS9 caps;
d3d9->GetDeviceCaps(D3DADAPTER_DEFAULT, deviceType, &caps);
int vp = 0;
if( caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT )
vp = D3DCREATE_HARDWARE_VERTEXPROCESSING;
else
vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING;
// Step 3: Fill out the D3DPRESENT_PARAMETERS structure.
_d3dpp.BackBufferWidth = width;
_d3dpp.BackBufferHeight = height;
_d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8;
_d3dpp.BackBufferCount = 1;
_d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE;
_d3dpp.MultiSampleQuality = 0;
_d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
_d3dpp.hDeviceWindow = hwnd;
_d3dpp.Windowed = windowed;
_d3dpp.EnableAutoDepthStencil = true;
_d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8;
_d3dpp.Flags = 0;
_d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
_d3dpp.PresentationInterval =
D3DPRESENT_INTERVAL_IMMEDIATE;
// Step 4: Create the device.
Frank Luna
www.moon-labs.com
- 7 -
hr = d3d9->CreateDevice(
D3DADAPTER_DEFAULT, // primary adapter
deviceType, // device type
hwnd, // window associated with device
vp, // vertex processing
&_d3dpp, // present parameters
&_device); // return created device
if( FAILED(hr) )
{
// try again using a safer configuration.
_d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
hr = d3d9->CreateDevice(
D3DADAPTER_DEFAULT,
deviceType,
hwnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&_d3dpp,
&_device);
if( FAILED(hr) )
{
d3d9->Release(); // done with d3d9 object
::MessageBox(0, "CreateDevice() - FAILED", 0, 0);
return hr;
}
}
d3d9->Release(); // done with d3d9 object
return S_OK;
}
• The setup method is responsible for performing application specific code that is
dependant upon Direct3D. This includes, allocating Direct3D resources, checking
device capabilities, and setting device states. The corresponding sample program
to this paper implements this method as follows:
HRESULT CDirect3D9MFCView::setup(int width, int height)
{
if( _device )
{
// Set view matrix.
D3DXMATRIX V;
D3DXVECTOR3 pos (0.0f, 0.0f, -6.0f);
D3DXVECTOR3 target (0.0f, 0.0f, 0.0f);
D3DXVECTOR3 up (0.0f, 1.0f, 0.0f);
D3DXMatrixLookAtLH(&V, &pos, &target, &up);
_device->SetTransform(D3DTS_VIEW, &V);
Frank Luna
www.moon-labs.com
- 8 -
// Create the teapot.
if( !_teapot)
D3DXCreateTeapot(_device, &_teapot, 0);
// Use wireframe mode and turn off lighting.
_device->SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME);
_device->SetRenderState(D3DRS_LIGHTING, false);
// Size the viewport based on window dimensions.
D3DVIEWPORT9 vp = {0, 0, width, height, 0.0f, 1.0f};
_device->SetViewport( &vp );
// Set the projection matrix based on the
// window dimensions.
D3DXMATRIX P;
D3DXMatrixPerspectiveFovLH(
&P,
D3DX_PI * 0.25f,//45-degree field of view
(float)width / (float)height,
1.0f,
1000.0f);
_device->SetTransform(D3DTS_PROJECTION, &P);
}
return S_OK;
}
• The cleanup method is used to cleanup any resources that need to be freed before
calling IDirect3DDevice9::Reset. In the corresponding sample program to this
paper, this method’s definition is empty since there is nothing we need to cleanup
before calling IDirect3DDevice9::Reset. Recall that only certain resources
need to be released prior to IDirect3DDevice9::Reset, such as those in the
D3DPOOL_DEFAULT memory pool.
HRESULT CDirect3D9MFCView::cleanup()
{
// Nothing to Destroy.
return S_OK;
}
• In the update method you will perform operations that need to be done on a
frame-by-frame basis, such as, animation, collision detection, and testing for user
input. Note that the time between frames is to be passed into this function as an
argument. In this way you can sync operations based on time. The corresponding
sample program to this paper implements this method as follows:
HRESULT CDirect3D9MFCView::update(float timeDelta)
{
if( _device )
{
Frank Luna
www.moon-labs.com
- 9 -
//
// Spin the teapot around the y-axis.
//
static float angle = 0.0f;
D3DXMATRIX yRotationMatrix;
D3DXMatrixRotationY(&yRotationMatrix, angle);
_device->SetTransform(D3DTS_WORLD, &yRotationMatrix);
angle += timeDelta;
if(angle >= D3DX_PI * 2.0f)
angle = 0.0f;
}
return S_OK;
}
• In the render method you perform your drawing commands, which also occur on
a frame-by-frame basis. The corresponding sample program to this paper
implements this method as follows:
HRESULT CDirect3D9MFCView::render()
{
if( _device )
{
//
// Draw the scene.
//
_device->Clear(0, 0,
D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
0x00000000, 1.0f, 0);
_device->BeginScene();
// Draw the teapot.
_teapot->DrawSubset(0);
_device->EndScene();
_device->Present(0, 0, 0, 0);
}
return S_OK;
}
1.3.3 Creating the Direct3D Interfaces
At this point, we need to figure out where to actually create our Direct3D interfaces.
That is, where should we call initD3D? It doesn’t really matter where, except that we
need to do it after we have a valid window handle and that we need to do it before we
attempt to use the interfaces. I have elected to do it by overriding the
Frank Luna
www.moon-labs.com
- 10 -
CView::OnInitialUpdate method. To override this method, select the Overrides
button on the properties tab for CDirect3DMFCView in the lower right corner of VS7
(figure (5)). Find the OnInitialUpdate method from the list, select it, then select the
down arrow, and click Add.
Figure 5: Select the Overrides button and find and select the “OnInitialUpdate” item from the list,
then select the down arrow, and choose “Add”.
In the sample program, OnInitialUpdate is implemented as follows:
void CDirect3D9MFCView::OnInitialUpdate()
{
CView::OnInitialUpdate();
CRect rect;
GetClientRect(&rect);
// Initialize Direct3D (e.g. acquire a IDirect3DDevice9 poniter).
HRESULT hr = initD3D(
GetSafeHwnd(),
rect.right,
rect.bottom,
true,
D3DDEVTYPE_HAL);
if(FAILED(hr))
{
MessageBox("initD3D() - Failed", "Error");
::PostQuitMessage(0);
}
Frank Luna
www.moon-labs.com
- 11 -
// Setup the application.
hr = setup(rect.right, rect.bottom);
if(FAILED(hr))
{
MessageBox("setup() - Failed", "Error");
::PostQuitMessage(0);
}
}
1.3.4 Handling WM_SIZE
We have yet to define what is to occur when the window is resized. Since several
Direct3D components are dependant on the size of the window, namely the backbuffer,
viewport, and projection matrix, we ought to reset these components whenever the size of
the window changes. We do this by handling the WM_SIZE message. Locate the
Messages button on the properties tab for CDirect3DMFCView in the lower right corner of
VS7, which is next to the Overrides button shown in figure (5). Find the WM_SIZE
message and select it, then select the down arrow, and click the Add OnSize item. In the
sample program, OnSize is implemented as follows:
void CDirect3D9MFCView::OnSize(UINT nType, int cx, int cy)
{
CView::OnSize(nType, cx, cy);
if( _device )
{
HRESULT hr = 0;
// On a resize we must change the dimensions of the
// back buffers to match the new window size.
_d3dpp.BackBufferWidth = cx;
_d3dpp.BackBufferHeight = cy;
// We are about to call Reset, free any resources
// that need to be freed prior to a Reset.
hr = cleanup();
if(FAILED(hr))
{
MessageBox("destroy() - Failed", "Error");
::PostQuitMessage(0);
}
// Reset the flipping chain with the new window dimensions.
// Note that all device states are reset to the default
// after this call.
hr = _device->Reset(&_d3dpp);
if(FAILED(hr))
{
MessageBox("Reset() - Failed", "Error");
::PostQuitMessage(0);
}
// Reinitialize resource and device states since we
Frank Luna
www.moon-labs.com
- 12 -
// Reset everything.
hr = setup(cx, cy);
if(FAILED(hr))
{
MessageBox("setup() - Failed", "Error");
::PostQuitMessage(0);
}
}
}
1.3.5 Handling WM_ERASEBKGND
While we are on the topic of message handlers we should write a handler for the
WM_ERASEBKGND message. Basically, we want to handle this function and have it do
nothing, which prevents MFC from erasing the background every time we redraw it.
Stopping MFC from erasing the background is desired since we are using Direct3D for
graphics, that is, we use Direct3D to erase the background and we don’t want MFC and
Direct3D to conflict with each other. Furthermore, a sort of flicker will occur if we let
MFC erase the background. You can override this method the same way we overrode the
WM_SIZE method, this time look for WM_ERASEBKGND.
BOOL CDirect3D9MFCView::OnEraseBkgnd(CDC* pDC)
{
return FALSE;
}
1.3.6 Updating and Rendering
We now need to turn our attention to integrating the update and render methods into the
MFC framework. In a regular Win32 application we normally update and render during
idle time. We take the same approach here by overriding CWinApp::OnIdle. You can
override CWinApp::OnIdle the same way we overrode CView::OnInitialUpdate,
however, be sure that you are selecting methods to override from your application class
(e.g. CDirect3D9MFCApp) and not the view class, since OnIdle is a method of CWinApp.
In the sample, we implement CDirect3D9MFCApp::OnIdle as follows:
BOOL CDirect3D9MFCApp::OnIdle(LONG lCount)
{
CWinApp::OnIdle(lCount);
CWnd* mainFrame = AfxGetMainWnd();
CDirect3D9MFCView* cview =
(CDirect3D9MFCView*)mainFrame->GetWindow(GW_CHILD);
// Save last time.
static float lastTime = (float)timeGetTime();
// Compute time now.
float currentTime = (float)timeGetTime();
Frank Luna
www.moon-labs.com
- 13 -
// Compute the difference: time elapsed in seconds.
float deltaTime = (currentTime - lastTime) * 0.001f;
// Last time is now current time.
lastTime = currentTime;
cview->update(deltaTime);
cview->render();
return TRUE;
}
First we call the parent implementation of CWinApp::OnIdle so that MFC will take care
of what it needs to do with idle time, like updating user interface components. Next we
get a pointer to our main window, which is the main frame. Then from the main frame
we get a pointer to the view class. The next four lines of code are used to compute the
time elapsed between frames.
Note that you will need to include mmsystem.h for the timeGetTime function.
Now we can call our update and render methods using the view class pointer we
obtained. Finally, we return a non-zero value because we want MFC to keep on running
our idle method as long as the message queue is empty. If we did not return zero, it
would indicate to MFC that we don’t want to do any further idle processing until the next
message is processed.
This is the last step, and at this point you should be done. Compile, build, and execute
your program. If it does not work, compare your code to the corresponding sample
source code for this article.
1.4 Summary
1. Integrate Direct3D with MFC by having the CDirect3D9MFCView (CView) class
contain the Direct3D related interfaces. This makes sense because CDirect3D9MFCView
is responsible for drawing data and will need direct access to Direct3D to do that
drawing.
2. To initialize Direct3D we need a valid handle to the window we are going to draw
onto, therefore initialize Direct3D in a place where such a handle is available, such as
CDirect3D9MFCView::OnInitialUpdate.
3. Handle the WM_SIZE message to update Direct3D components that are dependant upon
the window size, such as the backbuffer, viewport, and the projection matrix.
4. Handle the WM_ERASEBKGND message to prevent MFC from updating the background
when your window is redrawn.
Frank Luna
www.moon-labs.com
- 14 -
5. Override CWinApp::OnIdle and implement the code that you want executed during
idle time, such as updating and rendering the 3D scene.
I hope you have found this article useful. As we have learned, integrating Direct3D and
MFC is not difficult; it is simply a matter of knowing where Direct3D fits into the MFC
framework. You can leave feedback and or corrections either on the message forums at
www.moon-labs.com or email me directly at frank@moon-labs.com.
本文档为【D3D和MFC的整合】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑,
图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
该文档来自用户分享,如有侵权行为请发邮件ishare@vip.sina.com联系网站客服,我们会及时删除。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。
本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。
网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。