/*
Portions of this class derived from Programming a
Multiplayer First Person Shooter in DirectX by Vaughan Young
and Beginning DirectX 10 Game Programming by Wendy Jones
*/
#include "engine.h"
#include "dx_utils.h"
// much prettier color to clear with
#define COLOR_CORNFLOWERBLUE D3DXCOLOR( 100.0f / 255.0f, 149.0f / 255.0f, 237.0f / 255.0f, 255.0f / 255.0f )
#define COLOR_GREY D3DXCOLOR( 0.1f, 0.1f, 0.1f, 1.0f )
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
// Rendering Engine
CEngine* Engine( void )
{
static CEngine g_pEngine;
return &g_pEngine;
}
CEngine::CEngine()
{
m_pD3DDevice = NULL;
m_pSwapChain = NULL;
m_pRenderTargetView = NULL;
m_pDepthStencil = NULL;
m_pDepthStencilView = NULL;
m_pRasterState = NULL;
m_pAlphaBlendState = NULL;
m_pSetup = NULL;
m_pMaterialManager = NULL;
m_pTextureManager = NULL;
m_pEffectManager = NULL;
m_pSoundResManager = NULL;
m_pInputSystem = NULL;
m_pSoundManager = NULL;
m_nFps = 0;
m_bLoaded = false;
}
CEngine::~CEngine( void )
{
if (m_bLoaded)
ShutdownDirect3D();
SAFE_DELETE(m_pSetup);
SAFE_DELETE(m_pMaterialManager);
SAFE_DELETE(m_pTextureManager);
SAFE_DELETE(m_pEffectManager);
SAFE_DELETE(m_pSoundResManager);
SAFE_DELETE(m_pInputSystem);
SAFE_DELETE(m_pSoundManager);
}
/*******************************************************************
* Init
* Initialize the engine
* Inputs - void
* Outputs - True on success, false otherwise
*******************************************************************/
bool CEngine::Init( EngineSetup* pSetup )
{
// If no setup structure was passed in, then create a default one.
// Otherwise, make a copy of the passed in structure.
m_pSetup = new EngineSetup;
if( pSetup != NULL )
memcpy( m_pSetup, pSetup, sizeof( EngineSetup ) );
m_nWindowWidth = m_pSetup->nWindowWidth;
m_nWindowHeight = m_pSetup->nWindowHeight;
if ( !InitWindow( m_pSetup->hInstance, m_nWindowWidth, m_nWindowHeight) )
return false;
if ( !InitDirect3D( m_hWindow, m_nWindowWidth, m_nWindowHeight) )
return false;
// initialize sub systems
m_pInputSystem = new CInputSystem();
if ( !m_pInputSystem->Init() )
return false;
m_pSoundManager = new CSoundManager();
if ( FAILED( m_pSoundManager->Initialize(m_hWindow, DSSCL_PRIORITY) ) )
return false;
HRESULT hr;
// set up our alpha blend state
D3D10_BLEND_DESC blendDesc;
ZeroMemory( &blendDesc, sizeof(D3D10_BLEND_DESC) );
blendDesc.AlphaToCoverageEnable = false;
blendDesc.BlendEnable[0] = true;
blendDesc.SrcBlend = D3D10_BLEND_SRC_ALPHA;
blendDesc.DestBlend = D3D10_BLEND_INV_SRC_ALPHA;
blendDesc.BlendOp = D3D10_BLEND_OP_ADD;
blendDesc.SrcBlendAlpha = D3D10_BLEND_ZERO;
blendDesc.DestBlendAlpha = D3D10_BLEND_ZERO;
blendDesc.BlendOpAlpha = D3D10_BLEND_OP_ADD;
blendDesc.RenderTargetWriteMask[0] = D3D10_COLOR_WRITE_ENABLE_ALL;
hr = m_pD3DDevice->CreateBlendState( &blendDesc, &m_pAlphaBlendState );
// failed
if ( hr != S_OK )
{
m_pAlphaBlendState = NULL;
return false;
}
// create resource managers
m_pMaterialManager = new CResourceManager<CMaterial>( CMaterial::CreateMaterialResource, "LoadMaterial", CMaterial::Lua_SetMaterial );
m_pTextureManager = new CResourceManager<CTexture>( CTexture::CreateTextureResource );
m_pEffectManager = new CResourceManager<CEffect>( CEffect::CreateEffectResource );
m_pSoundResManager = new CResourceManager<CSoundRes>( CSoundRes::CreateSoundResource );
// Allow the application to perform any state setup now.
if( m_pSetup->StateSetup != NULL )
m_pSetup->StateSetup();
// The engine is fully loaded and ready to go.
m_bLoaded = true;
return true;
}
void CEngine::Run( void )
{
if (!m_bLoaded)
return;
// timing vars
LARGE_INTEGER nTimeStart;
LARGE_INTEGER nTimeEnd;
LARGE_INTEGER nTimingFreq;
float timeDelta = 0.0f;
int nFrameCount = 0;
float fTimeCount = 0.0f;
QueryPerformanceFrequency( &nTimingFreq );
// Main message loop
MSG msg = {0};
while (WM_QUIT != msg.message)
{
QueryPerformanceCounter( &nTimeStart );
// Process Windows messages first
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) == TRUE)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// Perform any needed updates
Update( timeDelta );
// Render
Render( timeDelta );
QueryPerformanceCounter( &nTimeEnd );
timeDelta = ( (float)nTimeEnd.QuadPart - (float)nTimeStart.QuadPart ) / nTimingFreq.QuadPart;
// keep track of FPS
fTimeCount += timeDelta;
if (fTimeCount > 1.0f)
{
m_nFps = nFrameCount;
nFrameCount = 0;
fTimeCount = 0.0f;
// set window title to include fps
char newTitle[256];
memset(newTitle,NULL,256);
sprintf_s(newTitle, 256, "%s - FPS: %i", m_pSetup->szName, m_nFps);
SetWindowText(m_hWindow, newTitle);
}
else
{
nFrameCount++;
}
}
// ensure all states are closed
for ( std::list<CState*>::iterator it = m_States.begin(); it != m_States.end(); it++ )
{
delete *it;
}
// Clean up the resources we allocated
ShutdownDirect3D();
m_bLoaded = false;
}
void CEngine::Update( const float fdTime )
{
// Used to retrieve details about the viewer from the application.
ViewerSetup viewer;
m_pInputSystem->Update(fdTime);
// Request the viewer from the current state, if there is one.
if( m_pCurrentState != NULL )
m_pCurrentState->RequestViewer( &viewer );
// TODO: Account for state changes
m_bStateChanged = false;
if( m_pCurrentState != NULL )
m_pCurrentState->Update( fdTime );
}
/*******************************************************************
* Render
* All drawing happens in the Render function
* Inputs - Time delta
* Outputs - void
*******************************************************************/
void CEngine::Render( const float fdTime )
{
if ( m_pD3DDevice == NULL || !m_bLoaded )
return;
// clear render target
m_pD3DDevice->ClearRenderTargetView( m_pRenderTargetView, COLOR_GREY );
m_pD3DDevice->ClearDepthStencilView( m_pDepthStencilView, D3D10_CLEAR_DEPTH, 1.0f, 0 );
// render our state
if ( m_pCurrentState != NULL )
m_pCurrentState->Render();
// display the next item in the swap chain
m_pSwapChain->Present(0, 0);
}
//-----------------------------------------------------------------------------
// Adds a state to the engine.
//-----------------------------------------------------------------------------
void CEngine::AddState( CState* pState, bool change )
{
m_States.push_back(pState);
if( change == false )
return;
if( m_pCurrentState != NULL )
m_pCurrentState->Close();
m_pCurrentState = m_States.back();
m_pCurrentState->Load();
}
//-----------------------------------------------------------------------------
// Removes a state from the engine
//-----------------------------------------------------------------------------
void CEngine::RemoveState( CState* pState )
{
m_States.remove(pState);
}
//-----------------------------------------------------------------------------
// Changes processing to the state with the specified ID.
//-----------------------------------------------------------------------------
void CEngine::ChangeState( unsigned long id )
{
CState* pState = NULL;
// Iterate through the list of states and find the new state to change to.
for (std::list<CState*>::iterator it = m_States.begin(); it != m_States.end(); it++)
{
pState = static_cast<CState*>(*it);
if( pState->GetID() == id )
{
// Close the old state.
if( m_pCurrentState != NULL )
m_pCurrentState->Close();
// Set the new current state and load it.
m_pCurrentState = pState;
m_pCurrentState->Load();
/*
Not entirely sure if this needs to be done in DX10, will require a bit of research
// Swap the back buffers until the first one is in the front.
while( m_nCurrentBackBuffer != 0 )
{
m_pD3DDevice->Present( NULL, NULL, NULL, NULL );
if( ++m_currentBackBuffer == m_setup->totalBackBuffers + 1 )
m_currentBackBuffer = 0;
}
*/
// Indicate that the state has changed.
m_bStateChanged = true;
break;
}
}
}
/*******************************************************************
* InitWindow
* Inits and creates and main app window
* Inputs - application instance - HINSTANCE
Window width - int
Window height - int
* Outputs - true if successful, false if failed - bool
*******************************************************************/
bool CEngine::InitWindow(HINSTANCE hInstance, int width, int height)
{
// Register class
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = 0;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = TEXT("DirectXEngine");
wcex.hIconSm = 0;
if(!RegisterClassEx(&wcex))
{
return false;
}
// Create window
RECT rect = { 0, 0, width, height };
AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, false);
// create the window from the class above
m_hWindow = CreateWindow(TEXT("DirectXEngine"),
m_pSetup->szName,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
rect.right - rect.left,
rect.bottom - rect.top,
NULL,
NULL,
hInstance,
NULL);
if(!m_hWindow)
{
return false;
}
RECT rc;
int screenWidth = GetSystemMetrics(SM_CXSCREEN);
int screenHeight = GetSystemMetrics(SM_CYSCREEN);
GetWindowRect(m_hWindow, &rc);
SetWindowPos(m_hWindow, 0, (screenWidth - (rc.right - rc.left) )/2,
(screenHeight - (rc.bottom - rc.top) )/2, 0, 0, SWP_NOZORDER|SWP_NOSIZE);
ShowWindow(m_hWindow, SW_SHOW);
UpdateWindow(m_hWindow);
return true;
}
/*******************************************************************
* InitDirect3D
* Initializes Direct3D
* Inputs - Parent window handle - HWND,
Window width - int
Window height - int
* Outputs - true if successful, false if failed - bool
*******************************************************************/
bool CEngine::InitDirect3D(HWND hWnd, int width, int height)
{
// Create the clear the DXGI_SWAP_CHAIN_DESC structure
DXGI_SWAP_CHAIN_DESC swapChainDesc;
ZeroMemory(&swapChainDesc, sizeof(swapChainDesc));
// Fill in the needed values
swapChainDesc.BufferCount = 2;
swapChainDesc.BufferDesc.Width = width;
swapChainDesc.BufferDesc.Height = height;
swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.BufferDesc.RefreshRate.Numerator = 60;
swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.OutputWindow = hWnd;
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.SampleDesc.Quality = 0;
swapChainDesc.Windowed = TRUE;
// Create the D3D device and the swap chain
HRESULT hr = D3D10CreateDeviceAndSwapChain(NULL,
D3D10_DRIVER_TYPE_HARDWARE,
NULL,
0,
D3D10_SDK_VERSION,
&swapChainDesc,
&m_pSwapChain,
&m_pD3DDevice);
// Error checking. Make sure the device was created
if (FAILED(hr))
{
MessageBox(hWnd, TEXT("A DX10 Compliant Video Card is Required"), TEXT("ERROR"), MB_OK);
return false;
}
// Get the back buffer from the swapchain
ID3D10Texture2D *pBackBuffer;
hr = m_pSwapChain->GetBuffer(0, __uuidof(ID3D10Texture2D), (LPVOID*)&pBackBuffer);
if (FAILED(hr))
{
return false;
}
// create the render target view
hr = m_pD3DDevice->CreateRenderTargetView(pBackBuffer, NULL, &m_pRenderTargetView);
// release the back buffer
SAFE_RELEASE(pBackBuffer);
// Make sure the render target view was created successfully
if (FAILED(hr))
{
return false;
}
//create depth stencil texture
D3D10_TEXTURE2D_DESC descDepth;
descDepth.Width = width;
descDepth.Height = height;
descDepth.MipLevels = 1;
descDepth.ArraySize = 1;
descDepth.Format = DXGI_FORMAT_D32_FLOAT;
descDepth.SampleDesc.Count = 1;
descDepth.SampleDesc.Quality = 0;
descDepth.Usage = D3D10_USAGE_DEFAULT;
descDepth.BindFlags = D3D10_BIND_DEPTH_STENCIL;
descDepth.CPUAccessFlags = 0;
descDepth.MiscFlags = 0;
if( FAILED( m_pD3DDevice->CreateTexture2D( &descDepth, NULL, &m_pDepthStencil ) ) )
return false;
D3D10_DEPTH_STENCIL_DESC dsDesc;
ZeroMemory( &dsDesc, sizeof(D3D10_DEPTH_STENCIL_DESC) );
// Depth test parameters
dsDesc.DepthEnable = true;
dsDesc.DepthWriteMask = D3D10_DEPTH_WRITE_MASK_ALL;
dsDesc.DepthFunc = D3D10_COMPARISON_LESS;
// Stencil test parameters
dsDesc.StencilEnable = true;
dsDesc.StencilReadMask = D3D10_DEFAULT_STENCIL_READ_MASK;
dsDesc.StencilWriteMask = D3D10_DEFAULT_STENCIL_WRITE_MASK;
// Stencil operations if pixel is front-facing
dsDesc.FrontFace.StencilFailOp = D3D10_STENCIL_OP_KEEP;
dsDesc.FrontFace.StencilDepthFailOp = D3D10_STENCIL_OP_INCR;
dsDesc.FrontFace.StencilPassOp = D3D10_STENCIL_OP_KEEP;
dsDesc.FrontFace.StencilFunc = D3D10_COMPARISON_ALWAYS;
// Stencil operations if pixel is back-facing
dsDesc.BackFace.StencilFailOp = D3D10_STENCIL_OP_KEEP;
dsDesc.BackFace.StencilDepthFailOp = D3D10_STENCIL_OP_DECR;
dsDesc.BackFace.StencilPassOp = D3D10_STENCIL_OP_KEEP;
dsDesc.BackFace.StencilFunc = D3D10_COMPARISON_ALWAYS;
// Create depth stencil state
ID3D10DepthStencilState * pDSState;
m_pD3DDevice->CreateDepthStencilState(&dsDesc, &pDSState);
m_pD3DDevice->OMSetDepthStencilState(pDSState, 1);
// Create the depth stencil view
D3D10_DEPTH_STENCIL_VIEW_DESC descDSV;
descDSV.Format = descDepth.Format;
descDSV.ViewDimension = D3D10_DSV_DIMENSION_TEXTURE2D;
descDSV.Texture2D.MipSlice = 0;
if( FAILED( m_pD3DDevice->CreateDepthStencilView( m_pDepthStencil, &descDSV, &m_pDepthStencilView ) ) )
return false;
// set the render target
m_pD3DDevice->OMSetRenderTargets(1, &m_pRenderTargetView, m_pDepthStencilView);
// Setup the raster description which will determine how and what polygons will be drawn.
D3D10_RASTERIZER_DESC rasterDesc;
rasterDesc.AntialiasedLineEnable = false;
rasterDesc.CullMode = D3D10_CULL_BACK;
rasterDesc.DepthBias = 0;
rasterDesc.DepthBiasClamp = 0.0f;
rasterDesc.DepthClipEnable = true;
rasterDesc.FillMode = D3D10_FILL_SOLID;
rasterDesc.FrontCounterClockwise = false;
rasterDesc.MultisampleEnable = false;
rasterDesc.ScissorEnable = false;
rasterDesc.SlopeScaledDepthBias = 0.0f;
// Create the rasterizer state from the description we just filled out.
if( FAILED( m_pD3DDevice->CreateRasterizerState(&rasterDesc, &m_pRasterState) ) )
return false;
// Now set the rasterizer state.
m_pD3DDevice->RSSetState(m_pRasterState);
// create and set the viewport
D3D10_VIEWPORT viewPort;
viewPort.Width = width;
viewPort.Height = height;
viewPort.MinDepth = 0.0f;
viewPort.MaxDepth = 1.0f;
viewPort.TopLeftX = 0;
viewPort.TopLeftY = 0;
m_pD3DDevice->RSSetViewports(1, &viewPort);
// Set up the projection matrix
D3DXMatrixPerspectiveFovLH( &m_matProjection,
(float)D3DX_PI * 0.25f,
(float)width/(float)height,
0.1f / m_pSetup->fScale,
1000.0f / m_pSetup->fScale );
return true;
}
/*******************************************************************
* ShutdownDirect3D
* Closes down and releases the resources for Direct3D
* Inputs - void
* Outputs - void
*******************************************************************/
void CEngine::ShutdownDirect3D()
{
SAFE_RELEASE(m_pAlphaBlendState);
SAFE_RELEASE(m_pRasterState);
SAFE_RELEASE(m_pDepthStencilView);
SAFE_RELEASE(m_pDepthStencil);
SAFE_RELEASE(m_pRenderTargetView);
SAFE_RELEASE(m_pSwapChain);
SAFE_RELEASE(m_pD3DDevice);
}
/*******************************************************************
* WndProc
* The main window procedure for the application
* Inputs - application window handle - HWND
message sent to the window - UINT
wParam of the message being sent - WPARAM
lParam of the message being sent - LPARAM
* Outputs - LRESULT
*******************************************************************/
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
// Allow the user to press the escape key to end the application
case WM_KEYDOWN:
switch(wParam)
{
// Check if the user hit the escape key
case VK_ESCAPE:
PostQuitMessage(0);
break;
}
break;
// The user hit the close button, close the application
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}