DirectX10 Rendering Engine – Engine.cpp

 

/*
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);
}