//	Darkstar Engine  (c) UnderWorld Interactive Studio (all coding by Svarog    2007-2008)
//
//      Данный файл является частью Darkstar Engine. Любое использование кода файла
//      без согласия компании, является нарушением закона об авторском праве (в слу-
//      чае если не оговорено обратное).
//
//      Имя файла: d3d10_graphicDriver.cpp
//      Краткое описание: графический драйвер Direc3D
//

#ifdef GD_D3D10

#include <Darkstar/Graphic System/Direct3D/d3d10_graphicDriver.hpp>
#include <Darkstar/Kernel/logh.hpp>

using namespace gs;
using rs::DEViewport;
using ker::Log;

GraphicDriver::GraphicDriver()
{
	device = NULL;
	swap_chain = NULL;
	dxgi_factory = NULL;
	change_display_mode_on = false;
	current_primitive_topology = rs::DEPT_UNDEFINED;
	cur_color.reset();
	cur_depth.reset();
};

GraphicDriver::~GraphicDriver()
{
	if(device)
	{
		device->ClearState();
		backBuffer.reset();
		depthBuffer.reset();
		if(FAILED(device->Release()))
			lwar << "Cannot release d3d device";
		device = NULL;
	};
	if(swap_chain)
	{
		swap_chain->SetFullscreenState(false, NULL);
		if(FAILED(swap_chain->Release()))
			lwar << "Cannot release swap chain";
		dxgi_factory = NULL;
	};
	if(dxgi_factory)
	{
		if(FAILED(dxgi_factory->Release()))
			lwar << "Cannot release dxgi factory";
		dxgi_factory = NULL;
	};
};

ID3D10Device* GraphicDriver::getDevice() const
{
	return device;
};

bool GraphicDriver::_setBackAndDepthBuffers()
{
	backBuffer.reset(new RenderTarget());
	depthBuffer.reset(new RenderTarget());
	ID3D10Texture2D* back_buf;
	if(FAILED(swap_chain->GetBuffer(0, __uuidof(ID3D10Texture2D), (LPVOID*)&back_buf)))
	{
		lerr << "Cannot get back buffer";
		return false;
	};
	D3D10_TEXTURE2D_DESC desc;
	back_buf->GetDesc(&desc);
	shared_ptr<DisplayMode> new_display_mode(new DisplayMode(desc.Width, desc.Height, current_display_mode->getPixelFormat(), current_display_mode->getRefreshRate()));
	current_display_mode = new_display_mode;
	if(!backBuffer->create2D(back_buf))
	{
		lerr << "Cannot create render target";
		return false;
	};
	if(!depthBuffer->create2D(current_display_mode->getWidth(), current_display_mode->getHeight(), DRTFMT_D32_FLOAT, current_aa_mode, false))
	{
		lerr << "Cannot create depth buffer";
		return false;
	};
	if(FAILED(back_buf->Release()))
	{
		lerr << "Cannot release back buffer of swap chain";
		return false;
	};
	return true;
};

DXGI_SWAP_CHAIN_DESC GraphicDriver::_fillSwapChainDesc(const shared_ptr<DisplayMode>& dm, const DRTAATYPE& aa, const bool is_w, HWND hwnd)
{
	DXGI_SWAP_CHAIN_DESC swap_chain_desc;
	swap_chain_desc.BufferCount = back_buffer_count;
	swap_chain_desc.BufferDesc.Width = dm->getWidth();
	swap_chain_desc.BufferDesc.Height = dm->getHeight();
	swap_chain_desc.BufferDesc.RefreshRate.Denominator = 1;
	swap_chain_desc.BufferDesc.RefreshRate.Numerator = dm->getRefreshRate();
	swap_chain_desc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
	swap_chain_desc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
	swap_chain_desc.BufferDesc.Format = (DXGI_FORMAT)dm->getPixelFormat();
	swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
	swap_chain_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
	swap_chain_desc.OutputWindow = hwnd;
	swap_chain_desc.SampleDesc.Count = aa.count;
	swap_chain_desc.SampleDesc.Quality = aa.quality;
	swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
	swap_chain_desc.Windowed = is_w;
	return swap_chain_desc;
};

bool GraphicDriver::changeDisplayMode(const shared_ptr<DisplayMode>& new_mode, const DRTAATYPE& new_aa_type, const DISPLAY_MODE& display_mode)
{
	if(((is_fullscreen && (display_mode == DDM_FULLSCREEN)) || 
		(!is_fullscreen && (display_mode == DDM_WINDOWED)) || display_mode == DDM_WITHOUT_CHANGE)
		&& *current_display_mode == *new_mode && current_aa_mode.count == new_aa_type.count && current_aa_mode.quality == new_aa_type.quality)
		return true;
	change_display_mode_on = true;
	if(display_mode == DDM_WINDOWED)
		is_fullscreen = false;
	else
		if(display_mode == DDM_FULLSCREEN)
			is_fullscreen = true;
		else
			if(display_mode == DDM_MAKE_CHANGE)
				is_fullscreen = !is_fullscreen;
	DXGI_SWAP_CHAIN_DESC swap_chain_desc = _fillSwapChainDesc(new_mode, new_aa_type, !is_fullscreen, output_window);
	this->current_aa_mode = new_aa_type;
	if(swap_chain)
	{
		swap_chain->SetFullscreenState(0, NULL);
		if(FAILED(swap_chain->Release()))
		{
			lwar << "Cannot release swap chain during changing display mode";
			change_display_mode_on = false;
			return false;
		};
	};
	if(FAILED(dxgi_factory->CreateSwapChain(device, &swap_chain_desc, &swap_chain)))
	{
		lerr << "Cannot create new swap chain. Fatal error";
		change_display_mode_on = false;
		return false;
	};
	current_display_mode = new_mode;
	if(!_setBackAndDepthBuffers())
	{
		lerr << "Cannot set back or depth buffer during changing display mode";
		change_display_mode_on = false;
		return false;
	};
	change_display_mode_on = false;
	return true;
};

bool GraphicDriver::changeDisplayMode(const DRTAATYPE& new_aa_type, const DISPLAY_MODE& display_mode)
{
	return changeDisplayMode(current_display_mode, new_aa_type, display_mode);
};

bool GraphicDriver::changeDisplayMode(const shared_ptr<DisplayMode>& new_mode, const DISPLAY_MODE& display_mode)
{
	return changeDisplayMode(new_mode, current_aa_mode, display_mode);
};

bool GraphicDriver::changeDisplayMode(const DISPLAY_MODE& display_mode)
{
	return changeDisplayMode(current_display_mode, current_aa_mode, display_mode);
};

bool GraphicDriver::init(HWND hwnd, const shared_ptr<DisplayMode>& display_mode, 
						 const bool is_windowed /* = false */, 
						 const DRTAATYPE& aa_type /* = WITHOUT_AA */, 
						 const unsigned int back_buffer_count /* = 2 */)
{
	this->back_buffer_count = back_buffer_count;
	DXGI_SWAP_CHAIN_DESC swap_chain_desc = _fillSwapChainDesc(display_mode, aa_type, is_windowed, hwnd);
	current_aa_mode = aa_type;
	output_window = hwnd;
	is_fullscreen = !is_windowed;
	unsigned int flag = 0;

	if(FAILED(CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)(&dxgi_factory))))
		lwar << "Cannot create dxgi factory. Alt+enter can work not correct";

	IDXGIAdapter* selected_adapter = NULL;
	D3D10_DRIVER_TYPE driver_type = D3D10_DRIVER_TYPE_HARDWARE;

	UINT number_of_adapters = 0;
	IDXGIAdapter* adapter = NULL;

	while(dxgi_factory->EnumAdapters(number_of_adapters, &adapter) != DXGI_ERROR_NOT_FOUND)
	{
		if(adapter)
		{
			DXGI_ADAPTER_DESC adapt_desc;
			if(SUCCEEDED(adapter->GetDesc(&adapt_desc)))
			{
				const bool isPerfHUD = wcscmp(adapt_desc.Description, L"NVIDIA PerfHUD") == 0;
				if(number_of_adapters == 0 || isPerfHUD)
					selected_adapter = adapter;
				if(isPerfHUD)
					driver_type = D3D10_DRIVER_TYPE_REFERENCE;
			};
		};
		++number_of_adapters;
	};

#ifdef _DEBUG
		flag |= D3D10_CREATE_DEVICE_DEBUG;
#endif
	if(FAILED(D3D10CreateDeviceAndSwapChain(selected_adapter, driver_type, NULL, flag, 
		D3D10_SDK_VERSION, &swap_chain_desc, &swap_chain, &device)))
	{
		lerr << "Cannot create device and swap chain";
		return false;
	};

	current_display_mode = display_mode;

	if(FAILED(dxgi_factory->MakeWindowAssociation(hwnd, 0)))
		lwar << "Cannot make window association. Alt+enter can work not correct";

	IDXGIOutput* dxgi_output;
	if(SUCCEEDED(swap_chain->GetContainingOutput(&dxgi_output)))
	{
		UINT num = 0;
		DXGI_FORMAT dfmt = DXGI_FORMAT_R8G8B8A8_UNORM;
		UINT modes = DXGI_ENUM_MODES_INTERLACED;
		if(SUCCEEDED(dxgi_output->GetDisplayModeList(dfmt, modes, &num, NULL)))
		{
			shared_array<DXGI_MODE_DESC> disp_modes(new DXGI_MODE_DESC[num]);
			if(SUCCEEDED(dxgi_output->GetDisplayModeList(dfmt, modes, &num, disp_modes.get())))
			{
				for(unsigned int i = 0; i < num; ++i)
					supported_modes_32bit.push_back(shared_ptr<DisplayMode>(new DisplayMode(disp_modes[i].Width, 
					disp_modes[i].Height, (DISPLAY_PIXEL_FORMAT)disp_modes[i].Format, 
					disp_modes[i].RefreshRate.Numerator/disp_modes[i].RefreshRate.Denominator)));
			}
			else
				lwar << "Cannot get display mode list. Supported modes of display are not available";
		}
		else
			lwar << "Cannot get display mode list. Supported modes of display are not available";
		if(FAILED(dxgi_output->Release()))
			lwar << "Cannot release dxgi output";
	}
	else
		lwar << "Cannot get containing output (DXGI). Supported modes of display are not available";

	if(!_setBackAndDepthBuffers())
		return false;

	ID3D10RenderTargetView* rtv = backBuffer->getRenderTargetView();
	device->OMSetRenderTargets(1, &rtv, depthBuffer->getDepthStencilView());
	cur_color = backBuffer;
	cur_depth = depthBuffer;

	setViewport(display_mode->getWidth(), display_mode->getHeight());

	vsConstBuffer.reset(new ConstantBuffer);
	psConstBuffer.reset(new ConstantBuffer);

	setPrimitiveTopology(rs::DEPT_TRIANGLELIST);

	return true;
};

void GraphicDriver::begin(const shared_ptr<RenderTarget>& color, const shared_ptr<RenderTarget>& depth, const unsigned int color_rt_layer)
{
	unsigned int cwidth = color->getWidth();
	unsigned int cheight = color->getHeight();
	unsigned int dwidth = depth->getWidth();
	unsigned int dheight = depth->getHeight();
	if(cwidth != dwidth || cheight != dheight || depth->getAAType().count != color->getAAType().count || depth->getAAType().quality != color->getAAType().quality)
	{
		lwar << cwidth << " " << dwidth << " " << cheight << " " << dheight;
		lwar << "Try to begin scene with non-compatible render target";
		return;
	};
	DRTFORMAT form = color->getFormat();
	if(form == DRTFMT_D16_UNORM || form == DRTFMT_D24_UNORM_S8_UINT || form == DRTFMT_D32_FLOAT)
	{
		lwar << "Color render target has depth stencil surface format";
		return;
	}
	form = depth->getFormat();
	if(form != DRTFMT_D16_UNORM && form != DRTFMT_D24_UNORM_S8_UINT && form != DRTFMT_D32_FLOAT)
	{
		lwar << "Depth stencil surface has unsupported format";
		return;
	};
	ID3D10RenderTargetView* rtv = color->getRenderTargetView(color_rt_layer);
	device->OMSetRenderTargets(1, &rtv, depth->getDepthStencilView());
	cur_color = color;
	cur_depth = depth;
	cur_color_rt_layer = color_rt_layer;

	psConstBuffer->clear();
	vsConstBuffer->clear();
};

void GraphicDriver::begin(const shared_ptr<RenderTarget>& color, const shared_ptr<RenderTarget>& depth)
{
	begin(color, depth, 0);
};

void GraphicDriver::beginWithColor(const shared_ptr<RenderTarget>& color, const unsigned int color_rt_layer)
{
	DRTFORMAT form = color->getFormat();
	if(form == DRTFMT_D16_UNORM || form == DRTFMT_D24_UNORM_S8_UINT || form == DRTFMT_D32_FLOAT)
	{
		lwar << "Color render target has depth stencil surface format";
		return;
	};

	ID3D10RenderTargetView* rtv = color->getRenderTargetView(color_rt_layer);
	device->OMSetRenderTargets(1, &rtv, NULL);
	cur_color = color;
	cur_depth = shared_ptr<RenderTarget>();
	cur_color_rt_layer = color_rt_layer;

	psConstBuffer->clear();
	vsConstBuffer->clear();
};

void GraphicDriver::beginWithColor(const shared_ptr<RenderTarget>& color)
{
	beginWithColor(color, 0);
};

void GraphicDriver::present()
{
	swap_chain->Present(0, 0);
};

void GraphicDriver::end()
{
#ifdef _NOTFINAL
	if(!cur_color && !cur_depth)
	{
		lwar << "Cannot end scene";
		return;
	};
#endif
	if(cur_color->getIsNeedTexture() && cur_color->getAAType().count != 1)
		device->ResolveSubresource(cur_color->getDataWithoutAATexture(), 0, cur_color->getDataTexture(), 0, getSpecRTFormat(cur_color->getFormat()));
	cur_color_rt_layer = 0;
	cur_color.reset();
	cur_depth.reset();
};

const shared_ptr<RenderTarget>& GraphicDriver::getBackBuffer() const
{
	return backBuffer;
};

const shared_ptr<RenderTarget>& GraphicDriver::getDepthStencilSurface() const
{
	return depthBuffer;
};

const shared_ptr<ConstantBuffer>& GraphicDriver::getPSConstantBuffer() const
{
	return psConstBuffer;
};

const shared_ptr<ConstantBuffer>& GraphicDriver::getVSConstantBuffer() const
{
	return vsConstBuffer;
};

const shared_ptr<RenderTarget>& GraphicDriver::getCurrentColorRenderTarget() const
{
	return cur_color;
};

const shared_ptr<RenderTarget>& GraphicDriver::getCurrentDepthStencilRenderTarget() const
{
	return cur_depth;
};

unsigned int GraphicDriver::getCurrentColorRTLayer() const
{
	return cur_color_rt_layer;
};

void GraphicDriver::setConstantBuffers() 
{
	vsConstBuffer->synchronize();
	psConstBuffer->synchronize();
	
	ID3D10Buffer* vsbuf = vsConstBuffer->getBuffer();
	ID3D10Buffer* psbuf = psConstBuffer->getBuffer();
	device->VSSetConstantBuffers(0, 1, &vsbuf);
	device->PSSetConstantBuffers(0, 1, &psbuf);
};

void GraphicDriver::setViewport(const unsigned int width, const unsigned int height, const unsigned int topx /* = 0 */, const unsigned int topy /* = 0 */, const float min_depth /* = 0.0f */, const float max_depth /* = 1.0f */)
{
	unsigned int w = width;
	unsigned int h = height;
	if(width == 0 && height == 0)
	{
		w = current_display_mode->getWidth();
		h = current_display_mode->getHeight();
	};
	D3D10_VIEWPORT view;
	view.Height = h;
	view.Width = w;
	view.MaxDepth = max_depth;
	view.MinDepth = min_depth;
	view.TopLeftX = topx;
	view.TopLeftY = topy;

	cur_viewport.width = w;
	cur_viewport.height = h;
	cur_viewport.topx = topx;
	cur_viewport.topy = topy;
	cur_viewport.min_depth = min_depth;
	cur_viewport.max_depth = max_depth;

	device->RSSetViewports(1, &view);
};

void GraphicDriver::setViewport(const DEViewport& new_viewport)
{
	setViewport(new_viewport.width, new_viewport.height, new_viewport.topx, new_viewport.topy, new_viewport.min_depth, new_viewport.max_depth);
};

const DEViewport& GraphicDriver::getCurViewport() const
{
	return cur_viewport;
};

void GraphicDriver::resizeBuffers(unsigned int new_width, unsigned int new_height)
{
	if(change_display_mode_on)
		return;
	if(!swap_chain)
		return;
	ID3D10RenderTargetView* const rt[1] = {NULL};
	device->OMSetRenderTargets(1, rt, NULL);
	backBuffer.reset();
	depthBuffer.reset();
	if(FAILED(swap_chain->ResizeBuffers(back_buffer_count, new_width, new_height, (DXGI_FORMAT)current_display_mode->getPixelFormat(), DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH)))
	{	
		lwar << "Cannot resize buffers with new width = " << new_width << " and new height = " << new_height;
		return;
	};
	IDXGIOutput* output;
	BOOL is_full;
	if(FAILED(swap_chain->GetFullscreenState(&is_full, &output)))
		lwar << "Cannot get full screen state";
	if(is_full)
		is_fullscreen = true;
	else
		is_fullscreen = false;
	if(output)
	{
		if(FAILED(output->Release()))
			lwar << "Cannot release dxgi output";
	};	
	if(!_setBackAndDepthBuffers())
		return;
	setViewport(0, 0);
	onResizeBuffers();
};

void GraphicDriver::addMessageListenerOnResize(const boost::function2<void, const unsigned int, const unsigned int>& new_listener)
{
	resize_message_listeners.push_back(new_listener);
};

void GraphicDriver::onResizeBuffers()
{
	for(unsigned int i = 0; i < resize_message_listeners.size(); ++i)
		resize_message_listeners[i](current_display_mode->getWidth(), current_display_mode->getHeight());
};

const shared_ptr<DisplayMode>& GraphicDriver::getCurrentDisplayMode() const
{
	return current_display_mode;
};

const std::vector<shared_ptr<DisplayMode> >& GraphicDriver::getSupported32BitDisplayMode() const
{
	return supported_modes_32bit;
};

void GraphicDriver::unsetAll()
{
	ID3D10ShaderResourceView* pSRV[128];
	for(unsigned int i = 0; i < 128; ++i)
		pSRV[i] = NULL;
	device->PSSetShaderResources(0, 128, pSRV);
	device->VSSetShaderResources(0, 128, pSRV);
	device->GSSetShaderResources(0, 128, pSRV);
	device->IASetIndexBuffer(0, DXGI_FORMAT_R32_UINT, 0);
	device->VSSetShader(NULL);
	device->PSSetShader(NULL);
	device->GSSetShader(NULL);
	device->IASetInputLayout(NULL);
	ID3D10Buffer* vb[16];
	unsigned int p[16];
	for(unsigned int i = 0; i < 16; ++i)
	{
		vb[i] = NULL;
		p[i] = 0;
	};
	device->IASetVertexBuffers(0, 5, vb, &p[0], &p[0]);
};

void GraphicDriver::setPrimitiveTopology(const rs::DE_PRIMITIVE_TOPOLOGY& prim_topl)
{
	if(current_primitive_topology == prim_topl)
		return;
	device->IASetPrimitiveTopology((D3D10_PRIMITIVE_TOPOLOGY)prim_topl);
	current_primitive_topology = prim_topl;
};

void GraphicDriver::clear(const math::Vect4& color /* = math::vect4_zero */, const float depth_value /* = 1 */, const unsigned int stencil_value/* = 0 */)
{
	if(cur_depth)
		device->ClearDepthStencilView(cur_depth->getDepthStencilView(), D3D10_CLEAR_DEPTH | D3D10_CLEAR_STENCIL, depth_value, stencil_value);
	if(cur_color)
		device->ClearRenderTargetView(cur_color->getRenderTargetView(cur_color_rt_layer), color.mas);
};

#endif