Wednesday, April 30, 2008

Tutorial 2 : Initializing DirectX

After creating the window, we need to give it DirectX capabilities. To get access to DirectX graphics, we need to include the header file d3d9.h. We also need to add the header file d3dx9.h, as it contains a lot of useful functions and macros.

Following the OOP methodology, I created a class which deals with the creation and release of the devices, handling lost devices and the rendering.

//  DxBase.cpp

void cDXBase::Init( const HWND hWnd )
{
m_Hwnd = hWnd;

DirectxInit() ;
#ifdef WINDOWED
SetParameters(false) ;
#else
SetParameters(true) ;
#endif
CreateDirectxDevice() ;
}

void cDXBase::DirectxInit()
{
//create the Direct3d Object
m_pD3D = Direct3DCreate9(D3D_SDK_VERSION) ;

if(m_pD3D == NULL)
{
MessageBox(NULL, _T("Direct3d object creation failed!"), _T("Error!"), MB_ICONEXCLAMATION | MB_OK) ;
}

// get the display mode
m_pD3D->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &m_displayMode );

// get the device caps
m_pD3D->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &m_Caps) ;
}

To initialize DirectX graphics we first create the DirectX object with a call to Direct3DCreate9. The object lets configure the application based on the video card capabilities. This is achieved with a call to GetDeviceCaps. We also get the current display mode with a call to GetAdapterDisplay mode.


//  DxBase.cpp

void cDXBase::SetParameters(const BOOL bFullScreen)
{
ZeroMemory(&m_d3dpp, sizeof(m_d3dpp)) ;

m_d3dpp.BackBufferCount = 1 ;
m_d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE ;
m_d3dpp.MultiSampleQuality = 0 ;
m_d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD ;
m_d3dpp.hDeviceWindow = m_Hwnd ;
m_d3dpp.Flags = 0 ;
m_d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT ;
m_d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE ;
m_d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8 ; //pixel format
m_d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8 ; // depth format
m_d3dpp.EnableAutoDepthStencil = true ;

if(bFullScreen)
{
// if its a full screen app
m_d3dpp.BackBufferWidth = m_displayMode.Width ;
m_d3dpp.BackBufferHeight = m_displayMode.Height ;
m_d3dpp.Windowed = false; // fullscreen
m_d3dpp.FullScreen_RefreshRateInHz = m_displayMode.RefreshRate;
}
else
{
// if its a windowed app
m_d3dpp.Windowed = true ;
m_d3dpp.EnableAutoDepthStencil = TRUE ;
m_d3dpp.AutoDepthStencilFormat = D3DFMT_D16 ;
}
}

Next, we need to fill up a D3DPRESENT_PARAMETERS structure. This structure is used to specify how DirectX is going to behave. If the application is full screen, then the BackBufferWidth, BackBufferHeight and FullScreen_RefreshRateInHZ members need to be set.

//DxBase.cpp

void cDXBase::CreateDirectxDevice()
{
int vp = 0 ; // the typeof vertex processing

if(m_Caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT )
{
// hardware vertex processing is supported.
vp = D3DCREATE_HARDWARE_VERTEXPROCESSING ;
}
else
{
// use software vertex processing.
vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING ;
}

// Create the D3DDevice
if(FAILED(m_pD3D->CreateDevice(D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
m_Hwnd,
vp,
&m_d3dpp,
&m_pd3dDevice)))
{
MessageBox(NULL, _T("Direct3d m_pd3dDevice creation failed!"), _T("Error!"),MB_ICONEXCLAMATION | MB_OK) ;
PostQuitMessage(0) ;
DestroyWindow(m_Hwnd) ;
}
}

Now, we create the device. First, we check if Hardware processing is supported or not. Then, we create the device with a call to CreateDevice. If device creation fails, we post an error and quit.

// Dxbase.cpp

HRESULT cDXBase::BeginRender()
{
HRESULT hr;

// check if the device is available
hr = IsAvailable() ;

if(hr == D3DERR_DEVICELOST || hr == D3DERR_DEVICENOTRESET)
{
HandleLostDevice(hr) ;
}
else
{
if(FAILED(hr))
{
PostQuitMessage(0) ;
}
}


if(SUCCEEDED(hr))
{
// clear the frame
m_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER,m_BkColor, 1.0f, 0) ;

hr = m_pd3dDevice->BeginScene() ;
}

return hr;
}


//DxBase.inl

inline HRESULT cDXBase::IsAvailable()
{
return(m_pd3dDevice->TestCooperativeLevel()) ;
}

inline void cDXBase::EndRender( const HRESULT hr )
{
if(SUCCEEDED(hr))
{
m_pd3dDevice->EndScene() ;
}
m_pd3dDevice->Present(NULL, NULL, NULL, NULL) ;
}

With the device initialized, we need to update the message loop to render geometry. First, we check if the device is available. This is done with a call to TestCooperativeLevel. Before we can render any geometry we need to call BeginScene. Then, we need to clear the surface we are drawing on with a call to Clear. This clears the back buffer with the specified color. When we are done rendering, we need to call EndScene. At this point, we still can't see the geometry as we cleared the back buffer. To switch between the front and back buffer we need to call Present, which displays the what we just rendered to the screen.

// DxBase.cpp

void cDXBase::Release()
{
// release the Direct3d device
SAFE_RELEASE(m_pd3dDevice) ;

// release the Direct3d object
SAFE_RELEASE(m_pD3D) ;
}

When we quit the message loop, we need to give resources back to Windows by releasing the COM interfaces. The Direct3D object and the Direct3D Device are both COM objects. So, to destroy the COM instances, we’ll release them in the reverse order that they are created by calling Release.

//  DxBase.cpp

void cDXBase::HandleLostDevice(HRESULT hr)
{
if(hr == D3DERR_DEVICELOST)
{
Sleep(500) ;
}
else
{
if(hr == D3DERR_DEVICENOTRESET)
{
//The m_pd3dDevice is ready to be Reset
hr = ResetDevice() ;
}
}
}

HRESULT cDXBase::ResetDevice()
{
if (m_pd3dDevice)
{
HRESULT hr ;

hr = m_pd3dDevice->Reset(&m_d3dpp) ;

return hr ;
}

return 0;
}

One more thing we need to handle is lost DirectX devices. The device can be lost when the window is minimized or when we switch among windows etc. This is done by a simple call to Reset.

Now we just need to make a few changes to our MainWindow to integrate DirectX.


//  MainWindow.cpp
GRAPHIC_API HWND cMainWindow::Init( const HINSTANCE &hInstance, const int &nCmdShow, LPCTSTR lpWindowTitle,const int iFullScreenWidth, const int iFullScreenHeight, cBaseApp* const pGameApp )
{
// earlier stuff
m_iFullScreenWidth = iFullScreenWidth ;
m_iFullScreenHeight = iFullScreenHeight ;

// earlier stuff

// initialize DirectX
cDXBase::GetInstance().Init(hWnd);

return hWnd;
}

HWND cMainWindow::CreateMyWindow( const int &nCmdShow, LPCTSTR lpWindowTitle )
{
// earlier stuff
#else
// create the window in full screen mode
m_Hwnd = CreateWindowEx(
WS_EX_CLIENTEDGE,
_T("Window"),
lpWindowTitle,
WS_EX_TOPMOST | WS_POPUP | WS_VISIBLE,
0, 0,
m_iFullScreenWidth,m_iFullScreenHeight,
NULL,
NULL,
m_hInstance,
this) ;
#endif

// earlier stuff
}

LRESULT CALLBACK cMainWindow::WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
// earlier stuff

case WM_SIZE:
case WM_MOVE:
OnReset();
return 0 ;

case WM_KEYDOWN:

switch(wParam)
{
case VK_ESCAPE:
DestroyWindow(hwnd) ;
break ;

}
return 0 ;

// earlier stuff

case WM_DESTROY:
OnDestroy();
return 0 ;

// earlier stuff
}

}

void cMainWindow::Run()
{
// earlier stuff

//No message to process?
// Then do your game stuff here

OnRender();
}
}
}

void cMainWindow::OnRender()
{
HRESULT hr;

hr = cDXBase::GetInstance().BeginRender();
if (SUCCEEDED(hr))
{
cDXBase::GetInstance().EndRender(hr);
}
}

void cMainWindow::OnDestroy()
{
// release the graphic object
cDXBase::GetInstance().Release();

ReleaseCapture() ;
PostQuitMessage(0) ;
}

void cMainWindow::OnReset()
{
GetWinRect() ;
cDXBase::GetInstance().ResetDevice();
}

Now that we have added DirectX, on running the code we should see a blue screen

Code
Binaries

No comments :