Dual API Renderer

The requirements for this project were to create a rendering API that supported both DirectX and OpenGL with the same interface. This resulting engine supports both graphics API’s, multiple dynamic lights and a variety of visual techniques. These include normal mapping, paralax mapping and specular highlights. The engine supports loading levels from the Quake .BSP file format, the .OBJ file format as well as objects constructed programmatically.

This project helped me learn the detailed structure of both API’s. In particular, DirectX and OpenGL require different orderings for certain operations. Creating an interface that added as little overhead as possible but remained convenient for development was challenging.

View Code

#pragma once

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

#include "Singleton.h"
#include "Vector.h"
#include "Exception.h"

#include "GBuffer.h"
#include "Shader.h"
#include "Texture.h"

#include <set>

namespace Lucid
{
	class Renderer : public Singleton< Renderer >, public DeathListener
	{
	public:

		enum TopologyType
		{
			POINTS, LINES, TRIANGLES, QUADS
		};

		Renderer( HWND hWnd );
		virtual ~Renderer();

		GBuffer* buildBuffer();
		Shader* buildShader( const char* szName );
		Texture* buildTexture( const char* szFile );
		Texture* buildTexture( unsigned width, unsigned height, void* pBuffer,
			Texture::TextureFormatType format = Texture::RGBA );

		virtual void clear( bool clearBuff = true, bool clearDepth = true )=0;
		virtual void present()=0;

		virtual void updateBufferSizes()=0;

		void draw( unsigned nIndices, unsigned firstIndex = 0,
			unsigned firstVertex = 0, TopologyType topology = TRIANGLES );

		HWND hWnd() { return m_hWnd; }

		void setClearColor( Vec4f color ) { m_clearColor = color; }
		Vec4f clearColor() const { return m_clearColor; }

		void useShader( const ptr< Shader >& newShader );
		ptr< Shader > curShader() const { return m_selectedShader; }

		void setProjectionMatrix( const Mat4f& newProjection );
		void setViewMatrix( const Mat4f& newView );
		void setModelMatrix( const Mat4f& newModel );

		Mat4f modelInverseMatrix() const { return m_modelInv; }
		Mat4f projectionMatrix() const { return m_projection; }
		Mat4f modelMatrix() const { return m_model; }
		Mat4f viewMatrix() const { return m_view; }
		Mat4f MVP() { updateMVP(); return m_MVP; }

		void setNumLights( unsigned nLights );
		void setLightPos( unsigned lightIndex, const Vec3f& newPos );

		void died( void* object, int message );

	protected:
		virtual GBuffer* v_buildBuffer()=0;
		virtual Shader* v_buildShader( const char* szName )=0;
		virtual Texture* v_buildTexture( const char* szTitle )=0;
		virtual Texture* v_buildTexture( unsigned width, unsigned height, void* pBuffer,
			Texture::TextureFormatType format )=0;

		virtual void v_draw( unsigned nIndices, unsigned firstIndex,
			unsigned firstVertex, TopologyType topology )=0;


	private:
		HWND m_hWnd;
		Vec4f m_clearColor;

		std::set< GBuffer* > m_buffers;
		std::set< Shader* > m_shaders;
		std::set< Texture* > m_textures;

		void updateMVP();
		void activateShader();

		Mat4f m_modelInv;
		Mat4f m_model, m_view, m_projection, m_MVP;
		bool m_isMVPValid;

		ptr< Shader > m_selectedShader;
		bool m_isShaderActive;

		unsigned m_nLights;
		Vec3f m_lightPos[ MAX_DEFAULT_LIGHTS ];

		PREVENT_COPYING( Renderer )
	};

	extern Renderer* BuildDXRenderer( HWND hWnd );
	extern Renderer* BuildGLRenderer( HWND hWnd );
}
#include "Renderer.h"
#include "MatrixUtil.h"

#include <algorithm>

namespace Lucid
{
	enum ResourceType
	{
		NOTYPE, BUFFER, TEXTURE, SHADER
	};

	Renderer::Renderer( HWND hWnd ) : 
#pragma warning( disable : 4355 )
		Singleton< Renderer >( this ),
#pragma warning( default : 4355 )
			m_hWnd( hWnd ),
			m_clearColor( 0.2f, 0.2f, 0.24f, 1.0f ),
			m_isMVPValid( false ),
			m_isShaderActive( false ),
			m_nLights( 0 )
		{
			BuildIdentity( m_model );
			BuildIdentity( m_view );
			BuildIdentity( m_projection );

			for ( unsigned i = 0; i < MAX_DEFAULT_LIGHTS; ++i )
				m_lightPos[ i ].SetAllElements( 0.0f );
		}
	//=========================================================================
	//=========================================================================
	Renderer::~Renderer()
	{
		useShader( nullptr );

		if ( m_buffers.size() || m_shaders.size() /* || m_textures.size()  */ )
		{
			std::stringstream ermsg;
			
			ermsg << "********* Renderer Shutdown Errors *********\n";

			if ( m_buffers.size() )
			{
				ermsg << m_buffers.size() << " GBuffer object(s) active when Renderer was destructed.\n";
				DELETE_CONTENTS( m_buffers );
			}

			if ( m_shaders.size() )
			{
				ermsg << m_shaders.size() << " Shader object(s) active when Renderer was destructed.\n";
				DELETE_CONTENTS( m_shaders );
			}

			ermsg <<  "********************************************\n";

			THROW( ermsg.str() );
		}
	}

	//=========================================================================
	//=========================================================================
	GBuffer* Renderer::buildBuffer()
	{
		GBuffer* result = v_buildBuffer();

		result->addListener( this );
		result->setDeathMessage( BUFFER );

		m_buffers.insert( result );

		return result;
	}

	//=========================================================================
	//=========================================================================
	Shader* Renderer::buildShader( const char* szName )
	{
		Shader* result = v_buildShader( szName );

		result->addListener( this );
		result->setDeathMessage( SHADER );
		
		m_shaders.insert( result );

		return result;
	}

	//=========================================================================
	//=========================================================================
	Texture* Renderer::buildTexture( const char* szFile )
	{
		Texture* result = v_buildTexture( szFile );

		result->addListener( this );
		result->setDeathMessage( TEXTURE );

		m_textures.insert( result );

		return result;
	}

	//=========================================================================
	//=========================================================================
	Texture* Renderer::buildTexture( unsigned width, unsigned height, void* pBuffer,
		Texture::TextureFormatType format )
	{
		Texture* result = v_buildTexture( width, height, pBuffer, format );

		result->addListener( this );
		result->setDeathMessage( TEXTURE );

		m_textures.insert( result );

		return result;
	}

	//=========================================================================
	//=========================================================================
	void Renderer::updateMVP()
	{
		if ( !m_isMVPValid )
		{
			m_MVP = m_model * m_view * m_projection;
			m_isMVPValid = true;
		}
	}

	//=========================================================================
	//=========================================================================
	void Renderer::activateShader()
	{
		if ( m_selectedShader == nullptr ) THROW( "No shader selected to activate." );

		updateMVP();
		m_selectedShader->MVP() = m_MVP;
		m_selectedShader->model() = m_model;

		for ( unsigned i = 0; i < m_nLights; ++i )
			m_selectedShader->lightPos( i ) =
				TransformPoint( m_lightPos[ i ], m_modelInv );

		m_selectedShader->apply();

		m_isShaderActive = true;
	}

	//=========================================================================
	//=========================================================================
	void Renderer::died( void* object, int message )
	{
		switch ( message )
		{
		case BUFFER:
			m_buffers.erase( reinterpret_cast< GBuffer* >( object ) );
			break;
		case SHADER:
			m_shaders.erase( reinterpret_cast< Shader* >( object ) );
			break;
		case TEXTURE:
			m_textures.erase( reinterpret_cast< Texture* >( object ) );
			break;
		default: THROW( "Unable to determine type of destructed resource" );
		}
	}

	//=========================================================================
	//=========================================================================
	void Renderer::useShader( const ptr< Shader >& newShader )
	{
		if ( newShader != m_selectedShader )
		{
			if ( m_isShaderActive && m_selectedShader != nullptr )
				m_selectedShader->unApply();

			m_isShaderActive = false;
			m_selectedShader = newShader;
		}
	}

	//=========================================================================
	//=========================================================================
	void Renderer::setProjectionMatrix( const Mat4f& newProjection )
	{ m_isMVPValid = false; m_projection = newProjection; }

	//=========================================================================
	//=========================================================================
	void Renderer::setViewMatrix( const Mat4f& newView )
	{ m_isMVPValid = false; m_view = newView; }

	//=========================================================================
	//=========================================================================
	void Renderer::setModelMatrix( const Mat4f& newModel )
	{
		m_isMVPValid = false; m_model = newModel;

		if ( !Invert( m_model, m_modelInv ) ) THROW( "Provided model matrix is non-invertable." );
	}

	//=========================================================================
	//=========================================================================
	void Renderer::setNumLights( unsigned nLights )
	{
		if ( nLights > MAX_DEFAULT_LIGHTS ) THROW( "Attempt to activate more lights than allowed." );
		m_nLights = nLights;
	}
		
	//=========================================================================
	//=========================================================================
	void Renderer::setLightPos( unsigned lightIndex, const Vec3f& newPos )
	{
		if ( lightIndex >= m_nLights ) THROW( "Attempt to change position of invalid light." );
		m_lightPos[ lightIndex ] = newPos;
	}

	//=========================================================================
	//=========================================================================
	void Renderer::draw( unsigned nIndices, unsigned firstIndex /* = 0 */,
		unsigned firstVertex /* = 0 */, TopologyType topology /* = TRIANGLES */ )
	{
		activateShader();

		v_draw( nIndices, firstIndex, firstVertex, topology );
	}
}
#include "Renderer.h"
#include "Exception.h"

#include "DXIncludes.h"

#define HV( x ) { if ( ( x ) != S_OK ) THROW( "Fatal error in DirectX Renderer" ); }

namespace Lucid
{
	extern GBuffer* BuildDXBuffer( ID3D11Device* device, ID3D11DeviceContext* context );
	extern Shader* BuildDXShader( const char* szName, ID3D11Device* device, ID3D11DeviceContext* context );

	extern Texture* BuildDXTexture( const char* szFile, ID3D11Device* device,
		ID3D11DeviceContext* context );
	extern Texture* BuildDXTexture( unsigned width, unsigned height,
		const void* pBuffer, Texture::TextureFormatType format,
		ID3D11Device* device, ID3D11DeviceContext* context );

	class DXRenderer : public Renderer
	{
		ID3D11Device* m_device;
		IDXGISwapChain* m_swapchain;
		ID3D11DeviceContext* m_context;

		ID3D11RenderTargetView* m_rtview;
		ID3D11DepthStencilView* m_dsv;

		//=========================================================================
		//=========================================================================
		void buildDepthAndStencilBuffers()
		{
			ID3D11Texture2D *backbuffer;
			HV( m_swapchain->GetBuffer( 0, __uuidof(ID3D11Texture2D), (void **)&backbuffer ) );
			HV( m_device->CreateRenderTargetView( backbuffer, NULL, &m_rtview ) );
			backbuffer->Release();

			DXGI_SWAP_CHAIN_DESC pDesc;
			HV( m_swapchain->GetDesc( &pDesc ) );

			D3D11_TEXTURE2D_DESC desc =
			{
				pDesc.BufferDesc.Width, pDesc.BufferDesc.Height,
				1, 1,
				DXGI_FORMAT_D32_FLOAT,
				{ 1, 0 },
				D3D11_USAGE_DEFAULT,
				D3D11_BIND_DEPTH_STENCIL,
				0, 0
			};

			ID3D11Texture2D *tex = NULL;
			HV(m_device->CreateTexture2D(&desc, NULL, &tex));
			HV(m_device->CreateDepthStencilView(tex, NULL, &m_dsv));
			tex->Release();
			m_context->OMSetRenderTargets(1, &m_rtview, m_dsv);
		}

		//=========================================================================
		//=========================================================================
		void updateViewPort()
		{
			RECT client;
			::GetClientRect( hWnd(), &client );

			D3D11_VIEWPORT port;
			port.TopLeftX = 0.0f;
			port.TopLeftY = 0.0f;
			port.Width = (float)client.right;
			port.Height = (float)client.bottom;
			port.MinDepth = 0.0f;
			port.MaxDepth = 1.0f;
			m_context->RSSetViewports( 1, &port );
		}

	public:
		//=========================================================================
		//=========================================================================
		DXRenderer( HWND hWnd ) : Renderer( hWnd )
		{
			// Build swap chain
			DXGI_SWAP_CHAIN_DESC sd =
			{
				{ 0, 0, { 60, 1 }, DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED, DXGI_MODE_SCALING_UNSPECIFIED },
				{ 1, 0 },
				DXGI_USAGE_RENDER_TARGET_OUTPUT,
				1,
				hWnd,
				true,
				DXGI_SWAP_EFFECT_DISCARD,
				0
			};

			D3D_FEATURE_LEVEL featurelevels[] = 
			{
				D3D_FEATURE_LEVEL_11_0,
				D3D_FEATURE_LEVEL_10_1,
				D3D_FEATURE_LEVEL_10_0,
			};

			D3D_FEATURE_LEVEL featurelevelpicked;

			unsigned createdeviceflags = 0;
#ifdef _DEBUG
			createdeviceflags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

			HV( D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE,
				NULL, createdeviceflags, featurelevels,
				sizeof(featurelevels)/sizeof(D3D_FEATURE_LEVEL),
				D3D11_SDK_VERSION, &sd, &m_swapchain, &m_device, &featurelevelpicked,
				&m_context));

			buildDepthAndStencilBuffers();

			updateViewPort();

			// Set rasterizer options
			ID3D11RasterizerState* rs;

			D3D11_RASTERIZER_DESC rasterizerstate = { D3D11_FILL_SOLID,
				D3D11_CULL_BACK, true, 0, 0, 0, true, false, false, false };
			HV( m_device->CreateRasterizerState(&rasterizerstate, &rs) );
			m_context->RSSetState(rs);
			rs->Release();
		}

		//=========================================================================
		//=========================================================================
		~DXRenderer()
		{
			m_device->Release();
			m_swapchain->Release();
			m_context->Release();
			m_rtview->Release();
			m_dsv->Release();
		}

		//=========================================================================
		//=========================================================================
		GBuffer* v_buildBuffer()
		{
			return BuildDXBuffer( m_device, m_context );
		}

		//=========================================================================
		//=========================================================================
		Shader* v_buildShader( const char* szName )
		{
			return BuildDXShader( szName, m_device, m_context );
		}

		//=========================================================================
		//=========================================================================
		Texture* v_buildTexture( const char* szFile )
		{
			return BuildDXTexture( szFile, m_device, m_context );
		}

		//=========================================================================
		//=========================================================================
		Texture* v_buildTexture( unsigned width, unsigned height, void* pBuffer,
			Texture::TextureFormatType format)
		{
			return BuildDXTexture( width, height, pBuffer, format, m_device, m_context );
		}

		//=========================================================================
		//=========================================================================
		void clear( bool clearBuff = true, bool clearDepth = true )
		{
			if ( clearBuff ) m_context->ClearRenderTargetView( m_rtview, &( clearColor()[ 0 ] ) );
			if ( clearDepth ) m_context->ClearDepthStencilView( m_dsv, D3D10_CLEAR_DEPTH | D3D10_CLEAR_STENCIL, 1.0f, 0 );
		}

		//=========================================================================
		//=========================================================================
		void present()
		{
			HV( m_swapchain->Present( 0, 0 ) );
		}

		//=========================================================================
		//=========================================================================
		void updateBufferSizes()
		{
			if (m_swapchain) 
			{
				m_dsv->Release();
				m_rtview->Release();

				m_context->OMSetRenderTargets( 0, 0, 0 );
				m_swapchain->ResizeBuffers( 0, 0, 0, DXGI_FORMAT_UNKNOWN, DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH );

				buildDepthAndStencilBuffers();

				updateViewPort();
			}
		}

		//=========================================================================
		//=========================================================================
		void v_draw( unsigned nIndices, unsigned firstIndex = 0,
			unsigned firstVertex = 0, TopologyType topology = TRIANGLES )
		{
			D3D11_PRIMITIVE_TOPOLOGY top = D3D_PRIMITIVE_TOPOLOGY_UNDEFINED;
			switch( topology )
			{
			case POINTS: top = D3D11_PRIMITIVE_TOPOLOGY_POINTLIST; break;
			case LINES: top = D3D11_PRIMITIVE_TOPOLOGY_LINELIST; break;
			case TRIANGLES: top = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST; break;
			default: THROW( "Unsupported topology type encountered in DXRenderer.draw()." );
			}

			m_context->IASetPrimitiveTopology( top );
			m_context->DrawIndexed( nIndices, firstIndex, firstVertex );
		}
	};

	//=========================================================================
	//=========================================================================
	Renderer* BuildDXRenderer( HWND hWnd )
	{
		return new DXRenderer( hWnd );
	}
}
#include "Renderer.h"
#include "Vector.h"

#include "GLIncludes.h"

namespace Lucid
{
	extern GBuffer* BuildGLBuffer();
	extern Shader* BuildGLShader( const char* szName );
	extern Texture* BuildGLTexture( unsigned width, unsigned height,
		const void* pBuffer, Texture::TextureFormatType format );
	extern Texture* BuildGLTexture( const char* szFile );

	class GLRenderer : public Renderer
	{
		HDC m_hdc;
		HGLRC m_renderingContext;

		//=========================================================================
		//=========================================================================
		void updateViewPort()
		{
			RECT client;
			::GetClientRect( hWnd(), &client );

			glViewport( 0, 0, client.right, client.bottom );
		}

	public:
		//=========================================================================
		//=========================================================================
		GLRenderer( HWND hWnd ) : Renderer( hWnd )
		{
			m_hdc = GetDC( hWnd );
		
			// Set the window pixel format
			//
			PIXELFORMATDESCRIPTOR pixelFormatDescriptor = { 0 };

			pixelFormatDescriptor.nSize = sizeof( pixelFormatDescriptor );
			pixelFormatDescriptor.nVersion = 1;

			pixelFormatDescriptor.dwFlags = 
				PFD_DRAW_TO_WINDOW | 
				PFD_SUPPORT_OPENGL | 
				PFD_DOUBLEBUFFER;
			pixelFormatDescriptor.dwLayerMask = PFD_MAIN_PLANE;
			pixelFormatDescriptor.iPixelType = PFD_TYPE_RGBA;
			pixelFormatDescriptor.cColorBits = 32;
			pixelFormatDescriptor.cDepthBits = 32;

			int pixelFormat = ChoosePixelFormat( m_hdc, &pixelFormatDescriptor );
			if  ( pixelFormat == 0) THROW( "Error initializing OpenGL" );
			SetPixelFormat( m_hdc, pixelFormat, &pixelFormatDescriptor );

			// Create the OpenGL render context
			//
			m_renderingContext = wglCreateContext( m_hdc );
			wglMakeCurrent ( m_hdc, m_renderingContext );

			glewInit();

			glEnable( GL_DEPTH_TEST );
			glEnable( GL_CULL_FACE );
			glCullFace( GL_BACK );
		}

		//=========================================================================
		//=========================================================================
		~GLRenderer()
		{
			ReleaseDC( hWnd(), m_hdc );
		}

		//=========================================================================
		//=========================================================================
		GBuffer* v_buildBuffer()
		{
			return BuildGLBuffer();
		}

		//=========================================================================
		//=========================================================================
		Shader* v_buildShader( const char* szName )
		{
			// TODO - store a map of file names -> Shader objects to avoid
			// reloading already-loaded shaders
			return BuildGLShader( szName );
		}

		//=========================================================================
		//=========================================================================
		Texture* v_buildTexture( const char* szFile )
		{
			// TODO - Store a map of file names -> Texture objects to avoid
			// reloading already-loaded textures
			return BuildGLTexture( szFile );
		}

		//=========================================================================
		//=========================================================================
		Texture* v_buildTexture( unsigned width, unsigned height, void* pBuffer,
			Texture::TextureFormatType format )
		{
			return BuildGLTexture( width, height, pBuffer, format );
		}

		//=========================================================================
		//=========================================================================
		void clear( bool clearBuff = true, bool clearDepth = true )
		{
			const Vec4f& col = this->clearColor();
			glClearColor( col[ 0 ], col[ 1 ], col[ 2 ], col[ 3 ] );
			glClear( ( ( clearBuff ) ? GL_COLOR_BUFFER_BIT : 0 ) | 
				     ( ( clearDepth ) ? GL_DEPTH_BUFFER_BIT : 0 ) );
		}

		//=========================================================================
		//=========================================================================
		void present()
		{
			::SwapBuffers( m_hdc );
		}

		//=========================================================================
		//=========================================================================
		void updateBufferSizes()
		{
			updateViewPort();
		}

		//=========================================================================
		//=========================================================================
		void v_draw( unsigned nIndices, unsigned firstIndex,
			unsigned firstVertex, TopologyType topology )
		{
			// TODO
			if ( topology != TRIANGLES ) THROW( "Non-triangle topology not implemented for OpenGL." );

			glDrawElementsBaseVertex( GL_TRIANGLES,
				nIndices, GL_UNSIGNED_INT,
				reinterpret_cast< GLvoid* >( firstIndex * sizeof( unsigned ) ),
				firstVertex );
		}
	};

	//=========================================================================
	//=========================================================================
	Renderer* BuildGLRenderer( HWND hWnd )
	{
		return new GLRenderer( hWnd );
	}
}