/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *
 *   This file is part of
 *       _______   ______________  ______     _____
 *      / ____/ | / /  _/ ____/  |/  /   |   |__  /
 *     / __/ /  |/ // // / __/ /|_/ / /| |    /_ <
 *    / /___/ /|  // // /_/ / /  / / ___ |  ___/ /
 *   /_____/_/ |_/___/\____/_/  /_/_/  |_| /____/.
 *
 *   Copyright © 2003-2010 Brain Control, all rights reserved.
 *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include <d3d11.h>
#include <d3dx11.h>

#include "../../eshared.hpp"

// Initialize static members.
const eChar eGraphicsApiDx11::WINDOW_TITLE[] = "Enigma Player 3";

eGraphicsApiDx11::eGraphicsApiDx11() :
    m_dxgiFactory(eNULL),
    m_adapter(eNULL),
    m_adapterOutput(eNULL),
    m_swapChain(eNULL),
    m_device(eNULL),
    m_deviceContext(eNULL),
    m_renderTargetView(eNULL),
    m_depthStencilBuffer(eNULL),
    m_depthStencilView(eNULL),
    m_deviceLost(eFALSE),
    m_hwnd(eNULL),
    m_ownWindow(eFALSE),
    m_fullScreen(eFALSE),
    m_vsync(eFALSE),
    m_wndWidth(800),
    m_wndHeight(600)
{
    eStateManager::setGraphics(this);
    eStateManager::reset();

    m_activeDepthStencilView = eNULL;
    for(eU32 i=0;i<MAX_TARGETS;i++)
        m_activeRenderTargetView[i] = eNULL;

    m_startPull = 0;
    m_frame = 0;
}

#ifdef eEDITOR

eGraphicsApiDx11::~eGraphicsApiDx11()
{
    shutdown();
}

#endif

eBool eGraphicsApiDx11::initialize()
{
    HRESULT result;

	eDEBUG_WRITE("Creating DXGI Factory");
    result = CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&m_dxgiFactory);
    eASSERT(!FAILED(result));
    
    // Use the factory to create an adapter for the primary graphics interface (video card).
	eDEBUG_WRITE("Enum Adapters");
    result = m_dxgiFactory->EnumAdapters1(0, &m_adapter);
    if (!FAILED(result))
	{
		// Enumerate the primary adapter output (monitor).
		result = m_adapter->EnumOutputs(0, &m_adapterOutput);
		eASSERT(!FAILED(result));

		// Get the number of modes that fit the DXGI_FORMAT_R8G8B8A8_UNORM display format for the adapter output (monitor).
		eU32 numModes = 0;
		result = m_adapterOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &numModes, NULL);
		eASSERT(!FAILED(result));

		if (!numModes)
		{
			eShowError("Your graphics card doesn't support the X8R8G8B8 format!");
			return eFALSE;
		}

		// Create a list to hold all the possible display modes for this monitor/video card combination.
		DXGI_MODE_DESC *displayModeList = new DXGI_MODE_DESC[numModes];
		eASSERT(displayModeList);

		// Now fill the display mode list structures.
		result = m_adapterOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &numModes, displayModeList);
		eASSERT(!FAILED(result));

		for (eU32 i=0; i<numModes; i++)
		{
			DXGI_MODE_DESC &mode = displayModeList[i];

			// Only list resolutions > 640x480.
			if (mode.Width >= 640 && mode.Height >= 480)
			{
				eBool found = eFALSE;

				for (eU32 i=0; i<m_resolutions.size(); i++)
				{
					const eSize &entry = m_resolutions[i];

					// Resolution was already added?
					if (entry.width == mode.Width && entry.height == mode.Height)
					{
						found = eTRUE;
						break;
					}
				}

				// There are many resolutions with same
				// width/height, but different HZ values,
				// so just add one auf them.
				if (!found)
				{
					eSize entry;

					entry.width = mode.Width;
					entry.height = mode.Height;

					m_resolutions.append(entry);
				}
			}
		}

		eSAFE_DELETE_ARRAY(displayModeList);

		// Get the adapter (video card) description.
		DXGI_ADAPTER_DESC adapterDesc;
		eMemSet(&adapterDesc, 0, sizeof(DXGI_ADAPTER_DESC));
		result = m_adapter->GetDesc(&adapterDesc);
		eASSERT(!FAILED(result));

		// Store the dedicated video card memory in megabytes.
		m_videoCardMemory = (eU32)(adapterDesc.DedicatedVideoMemory / 1024 / 1024);
	}
	else
	{
		eSize entry;

		entry.width = 640;
		entry.height = 480;

		m_resolutions.append(entry);
	}
    
    return eTRUE;
}

#ifdef eEDITOR
void eGraphicsApiDx11::shutdown()
{
	eDEBUG_WRITE("Shutdown");

    eSAFE_RELEASE_COM(m_adapterOutput);
    eSAFE_RELEASE_COM(m_adapter);
    eSAFE_RELEASE_COM(m_dxgiFactory);

    if (m_ownWindow)
    {
        DestroyWindow((HWND)m_hwnd);
        m_hwnd = eNULL;
    }
}
#endif

eBool eGraphicsApiDx11::getInitialized() const
{
    return (m_dxgiFactory ? eTRUE : eFALSE);
}

eU32 eGraphicsApiDx11::getResolutionCount() const
{
    return m_resolutions.size();
}

const eSize & eGraphicsApiDx11::getResolution(eU32 index) const
{
    return m_resolutions[index];
}

eBool eGraphicsApiDx11::openWindow(eU32 width, eU32 height, eBool fullScreen, eBool vsync, ePtr hwnd)
{
    m_wndWidth   = width;
    m_wndHeight  = height;
    m_fullScreen = fullScreen;
    m_vsync      = vsync;

    if (hwnd)
    {
        m_hwnd = (HWND)hwnd;
        m_ownWindow = eFALSE;
    }
    else
    {
		eDEBUG_WRITE("Creating window");
        m_hwnd = _createWindow(width, height, fullScreen);
        eASSERT(m_hwnd != eNULL);
        m_ownWindow = eTRUE;
    }

	eDEBUG_WRITE("Creating device and swap chain");
    if (!_createDeviceAndSwapChain())
        return eFALSE;

	eDEBUG_WRITE("Creating render and target view");
    if (!_createRenderTargetView())
        return eFALSE;

	eDEBUG_WRITE("Creating depth stencil view");
    if (!_createDepthStencilView())
        return eFALSE;
     
    if (fullScreen)
	{
		eDEBUG_WRITE("Hiding cursor");
        ShowCursor(FALSE);
	}

	eDEBUG_WRITE("Creating constant buffers");
    eConstantBufferDx11 *vsConstants = new eConstantBufferDx11(m_device, m_deviceContext);
    eConstantBufferDx11 *psConstants = new eConstantBufferDx11(m_device, m_deviceContext);

    eStateManager::setConstantBuffers(vsConstants, psConstants);
    eStateManager::setViewport(0, 0, width, height);
    eStateManager::forceApply();

    return eTRUE;
}

eBool eGraphicsApiDx11::_createDeviceAndSwapChain()
{
    // Initialize the swap chain description.
    DXGI_SWAP_CHAIN_DESC swapChainDesc;
    eMemSet(&swapChainDesc, 0, sizeof(swapChainDesc));

    // Set to a single back buffer.
    swapChainDesc.BufferCount = 1;

    // Set the width and height of the back buffer.
    swapChainDesc.BufferDesc.Width = m_wndWidth;
    swapChainDesc.BufferDesc.Height = m_wndHeight;

    // Set regular 32-bit surface for the back buffer.
    swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;

    // Set the refresh rate of the back buffer. 
    // TODO: This is vsync on. Implement non-vsync option
    swapChainDesc.BufferDesc.RefreshRate.Numerator = 0;
    swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;

    // Set the usage of the back buffer.
    swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;

    // Set the handle for the window to render to.
    swapChainDesc.OutputWindow = (HWND)m_hwnd;

    // Turn multisampling off.
    swapChainDesc.SampleDesc.Count = 1;
    swapChainDesc.SampleDesc.Quality = 0;

    // Set to full screen or windowed mode.
    swapChainDesc.Windowed = !m_fullScreen;

    // Set the scan line ordering and scaling to unspecified.
    swapChainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
    swapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;

    // Discard the back buffer contents after presenting.
    swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;

    // Don't set the advanced flags.
    swapChainDesc.Flags = 0;
	
	eU32 flags = 0;
#ifdef eDEBUG
	flags = D3D11_CREATE_DEVICE_DEBUG;
#endif

	D3D_DRIVER_TYPE driverType[] = {
		D3D_DRIVER_TYPE_HARDWARE,
		D3D_DRIVER_TYPE_HARDWARE,
		D3D_DRIVER_TYPE_HARDWARE,
		D3D_DRIVER_TYPE_WARP,
		D3D_DRIVER_TYPE_UNKNOWN
	};

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

	eU32 trial = 0;
	while(true)
	{
		if (!FAILED(D3D11CreateDeviceAndSwapChain(
			NULL, driverType[trial], NULL, flags, &featureLevel[trial], 1, 
			D3D11_SDK_VERSION, &swapChainDesc, &m_swapChain, &m_device, NULL, &m_deviceContext)))
		{
			return eTRUE;
		}

		trial++;
		if (driverType[trial] == D3D_DRIVER_TYPE_UNKNOWN)
			break;
	}
    
	eShowError("Couldn't create Direct3D 11 device!");
	closeWindow();
	return eFALSE;
}

eBool eGraphicsApiDx11::_createRenderTargetView()
{
    // Get the pointer to the back buffer.
    ID3D11Texture2D* backBufferPtr;
    if(FAILED(m_swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&backBufferPtr)))
    {
        eShowError("Couldn't get back buffer pointer!");
        closeWindow();
        return eFALSE;
    }

    // Create the render target view with the back buffer pointer.
    if(FAILED(m_device->CreateRenderTargetView(backBufferPtr, NULL, &m_renderTargetView)))
    {
        eShowError("Couldn't get render target view!");
        closeWindow();
        return eFALSE;
    }

    // Release pointer to the back buffer as we no longer need it.
    eSAFE_RELEASE_COM(backBufferPtr);

    return eTRUE;
}

eBool eGraphicsApiDx11::_createDepthStencilView()
{
    // Initialize the description of the depth buffer.
    D3D11_TEXTURE2D_DESC depthBufferDesc;
    eMemSet(&depthBufferDesc, 0, sizeof(depthBufferDesc));

    // Set up the description of the depth buffer.
    depthBufferDesc.Width = m_wndWidth;
    depthBufferDesc.Height = m_wndHeight;
    depthBufferDesc.MipLevels = 1;
    depthBufferDesc.ArraySize = 1;
    depthBufferDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
    depthBufferDesc.SampleDesc.Count = 1;
    depthBufferDesc.SampleDesc.Quality = 0;
    depthBufferDesc.Usage = D3D11_USAGE_DEFAULT;
    depthBufferDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
    depthBufferDesc.CPUAccessFlags = 0;
    depthBufferDesc.MiscFlags = 0;

    // Create the texture for the depth buffer using the filled out description.
    if(FAILED(m_device->CreateTexture2D(&depthBufferDesc, NULL, &m_depthStencilBuffer)))
    {
        eShowError("Couldn't create depth stencil buffer!");
        closeWindow();
        return eFALSE;
    }

    // Initailze the depth stencil view.
    D3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc;
    eMemSet(&depthStencilViewDesc, 0, sizeof(depthStencilViewDesc));

    // Set up the depth stencil view description.
    depthStencilViewDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
    depthStencilViewDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
    depthStencilViewDesc.Texture2D.MipSlice = 0;

    // Create the depth stencil view.
    if(FAILED(m_device->CreateDepthStencilView(m_depthStencilBuffer, &depthStencilViewDesc, &m_depthStencilView)))
    {
        eShowError("Couldn't create depth stencil view!");
        closeWindow();
        return eFALSE;
    }

    return eTRUE;
}

void eGraphicsApiDx11::setWindowTitle(const eString &title)
{
    SetWindowText((HWND)m_hwnd, title);
}

void eGraphicsApiDx11::closeWindow()
{
	eDEBUG_WRITE("Closing window");

    if (m_hwnd)
    {
        if(m_swapChain)
        {
            m_swapChain->SetFullscreenState(false, NULL);
        }

        eSAFE_RELEASE_COM(m_depthStencilView);
        eSAFE_RELEASE_COM(m_depthStencilBuffer);
        eSAFE_RELEASE_COM(m_renderTargetView);
        eSAFE_RELEASE_COM(m_deviceContext);
        eSAFE_RELEASE_COM(m_device);
        eSAFE_RELEASE_COM(m_swapChain);

        DestroyWindow((HWND)m_hwnd);
        m_hwnd = eNULL;
    }

    if (m_fullScreen)
    {
        ShowCursor(TRUE);
    }
}

void eGraphicsApiDx11::handleMessages(eMessage &msg)
{
    MSG winMsg;

    if (PeekMessage(&winMsg, eNULL, 0, 0, PM_REMOVE))
    {
        TranslateMessage(&winMsg);
        DispatchMessage(&winMsg);

        msg = (winMsg.message == WM_QUIT ? eMSG_QUIT : eMSG_BUSY);
    }
    else
    {          
        msg = eMSG_IDLE;
    }
}

void eGraphicsApiDx11::clear(eClearMode mode, const eColor &color) const
{
    if (mode & eCLEAR_COLORBUFFER)
    {
        eF32 rgba[4];
        color.toFloatRgba(rgba);

        for(eU32 i=0;i<MAX_TARGETS;i++)
        {
            if (m_activeRenderTargetView[i])
                m_deviceContext->ClearRenderTargetView(m_activeRenderTargetView[i], rgba);
        }
    }

    if (mode & eCLEAR_DEPTHBUFFER ||
        mode & eCLEAR_STENCILBUFFER)
    {
        eU32 clear = 0;

        if (mode & eCLEAR_DEPTHBUFFER)
        {
            clear |= D3D11_CLEAR_DEPTH;
        }

        if (mode & eCLEAR_STENCILBUFFER)
        {
            clear |= D3D11_CLEAR_STENCIL;
        }

        m_deviceContext->ClearDepthStencilView(m_activeDepthStencilView, clear, 1.0f, 0);
    }
}

eRenderStats eGraphicsApiDx11::getRenderStats() const
{
    return m_renderStats;
}

eBool eGraphicsApiDx11::getFullScreen() const
{
    return m_fullScreen;
}

eU32 eGraphicsApiDx11::getWindowWidth() const
{
    return m_wndWidth;
}

eU32 eGraphicsApiDx11::getWindowHeight() const
{
    return m_wndHeight;
}

eSize eGraphicsApiDx11::getWindowSize() const
{
    return eSize(m_wndWidth, m_wndHeight);
}

void eGraphicsApiDx11::setViewport(eU32 x, eU32 y, eU32 width, eU32 height)
{
    if (width == 0)
    {
        width = m_wndWidth;
    }

    if (height == 0)
    {
        height = m_wndHeight;
    }

    // Setup the viewport for rendering.
    D3D11_VIEWPORT viewport;
    viewport.Width = (float)width;
    viewport.Height = (float)height;
    viewport.MinDepth = 0.0f;
    viewport.MaxDepth = 1.0f;
    viewport.TopLeftX = 0.0f;
    viewport.TopLeftY = 0.0f;

    // Create the viewport.
    m_deviceContext->RSSetViewports(1, &viewport);
}

void eGraphicsApiDx11::setScissorRect(eRect rect)
{
	D3D11_RECT d3drect;
	
	d3drect.left = rect.left;
	d3drect.top = rect.top;
	d3drect.bottom = rect.bottom;
	d3drect.right = rect.right;

	m_deviceContext->RSSetScissorRects(1, &d3drect);
}

void eGraphicsApiDx11::setShader(eShader *s)
{
    if (s != eNULL)
		s->bind();   
}

void eGraphicsApiDx11::setRenderTargets(eU32 numRenderTargets, eITexture **renderTargets, eCubeMapFace *cubeFaces, eTexture2d *depthStencilTarget)
{
	if (!numRenderTargets)
	{
		numRenderTargets = 1;
		m_activeRenderTargetView[0] = m_renderTargetView;
	}
	else
	{
		if (renderTargets)
		{
			for(eU32 i=0;i<numRenderTargets;i++)
			{
                if (!renderTargets[i])
                    m_activeRenderTargetView[i] = eNULL;
				else if (renderTargets[i] == TARGET_SCREEN)
					m_activeRenderTargetView[i] = m_renderTargetView;
				else
				{
					if (renderTargets[i]->isCube()) 
						m_activeRenderTargetView[i] = renderTargets[i]->getRenderTargetViewCube(cubeFaces[i]);
					else
						m_activeRenderTargetView[i] = renderTargets[i]->getRenderTargetView();
				}
			}
		}
		else
		{
			numRenderTargets = 1;
			m_activeRenderTargetView[0] = m_renderTargetView;
		}
	}
	

	if (depthStencilTarget == eNULL || depthStencilTarget == TARGET_SCREEN)
		m_activeDepthStencilView = m_depthStencilView;
	else
		m_activeDepthStencilView = depthStencilTarget->getDepthStencilView();

    m_deviceContext->OMSetRenderTargets(numRenderTargets, m_activeRenderTargetView, m_activeDepthStencilView);
}

void eGraphicsApiDx11::setVertexBuffers(eU32 count, eVertexBuffer **buffers, eU32 *offsets, eInputLayout &inputLayout)
{
	eASSERT(count <= MAX_VERTEX_BUFFERS);
	eASSERT(buffers != eNULL);

	ID3D11Buffer *d3dbuffers[MAX_VERTEX_BUFFERS];
    eU32 strides[MAX_VERTEX_BUFFERS];

	for (eU32 i=0;i<count;i++)
	{
		if (buffers[i])
        {
			d3dbuffers[i] = (ID3D11Buffer*)buffers[i]->getResource();
            strides[i] = buffers[i]->getVertexSize();
        }
		else
        {
			d3dbuffers[i] = eNULL;
            strides[i] = 0;
        }
	}

    m_deviceContext->IASetVertexBuffers(0, count, d3dbuffers, strides, offsets);
	m_deviceContext->IASetInputLayout(inputLayout.getInputLayout());
}

void eGraphicsApiDx11::setIndexBuffer(eIndexBuffer *ib)
{
    if (ib != eNULL)
    {
        m_deviceContext->IASetIndexBuffer((ID3D11Buffer*)ib->getResource(), DXGI_FORMAT_R32_UINT, 0);
    }
}

void eGraphicsApiDx11::setPSConstantBuffer(eConstantBufferDx11 *buffer)
{
	ID3D11Buffer *buffers[1];
	buffers[0] = (ID3D11Buffer *)buffer->getResource();
	m_deviceContext->PSSetConstantBuffers(0, 1, buffers);
}

void eGraphicsApiDx11::setVSConstantBuffer(eConstantBufferDx11 *buffer)
{
	ID3D11Buffer *buffers[1];
	buffers[0] = (ID3D11Buffer *)buffer->getResource();
	m_deviceContext->VSSetConstantBuffers(0, 1, buffers);
}

void eGraphicsApiDx11::setTextures(eU32 count, eITexture **tex)
{
    eASSERT(count <= MAX_TEX_UNITS);
	eASSERT(tex != eNULL);

	ID3D11ShaderResourceView *rviews[MAX_TEX_UNITS];

	for(eU32 i=0;i<count;i++)
	{
        if (tex[i])
		    rviews[i] = tex[i]->getShaderResourceView();
        else
            rviews[i] = eNULL;
	}

	m_deviceContext->PSSetShaderResources(0, count, rviews);
}

void eGraphicsApiDx11::drawPrimitives(ePrimitiveType type, eU32 startVertex, eU32 primitiveCount)
{
    eASSERT(primitiveCount > 0);

    eDEBUG_WRITE_VERBOSE("Execute");

    D3D11_PRIMITIVE_TOPOLOGY topology;
    eU32 vertexCount = 0;
    eU32 triangleCount = 0;
    eU32 lineCount = 0;

    switch (type)
    {
        case ePRIMTYPE_TRIANGLELIST:
        {
            topology = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
            triangleCount = primitiveCount;
            vertexCount = primitiveCount*3;
            break;
        }

        case ePRIMTYPE_TRIANGLESTRIPS:
        {
            topology = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP;
            triangleCount = primitiveCount;
            vertexCount = primitiveCount+2;
            break;
        }

        case ePRIMTYPE_LINESTRIPS:
        {
            topology = D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP;
            vertexCount = primitiveCount+1;
            lineCount = primitiveCount;
            break;
        }

        case ePRIMTYPE_LINELIST:
        {
            topology = D3D11_PRIMITIVE_TOPOLOGY_LINELIST;
            vertexCount = primitiveCount*2;
            lineCount = primitiveCount;
            break;
        }
    }

    m_deviceContext->IASetPrimitiveTopology(topology);
    m_deviceContext->Draw(vertexCount, startVertex);
    
    m_renderStats.triangles += triangleCount;
    m_renderStats.vertices += vertexCount;
    m_renderStats.lines += lineCount;
    m_renderStats.batches++;
}

void eGraphicsApiDx11::drawIndexedPrimitives(ePrimitiveType type, eU32 startVertex, eU32 vertexCount, eU32 startIndex, eU32 primitiveCount, eU32 instanceCount)
{
    eASSERT(vertexCount > 0);
    eASSERT(primitiveCount > 0);

    eDEBUG_WRITE_VERBOSE("Execute");

    D3D11_PRIMITIVE_TOPOLOGY topology;
    eU32 triangleCount = 0;
    eU32 lineCount = 0;
    eU32 indexCount = 0;

    const eU32 realInsts = (instanceCount == 0 ? 1 : instanceCount);

    switch (type)
    {
    case ePRIMTYPE_TRIANGLELIST:
        {
            topology = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
            triangleCount = primitiveCount*realInsts;
            indexCount = primitiveCount*3;
            break;
        }

    case ePRIMTYPE_TRIANGLESTRIPS:
        {
            topology = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP;
            triangleCount = primitiveCount*realInsts;
            indexCount = primitiveCount+2;
            break;
        }

    case ePRIMTYPE_LINESTRIPS:
        {
            topology = D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP;
            indexCount = primitiveCount+1;
            lineCount = primitiveCount*realInsts;
            break;
        }

    case ePRIMTYPE_LINELIST:
        {
            topology = D3D11_PRIMITIVE_TOPOLOGY_LINELIST;
            indexCount = primitiveCount*2;
            lineCount = primitiveCount*realInsts;
            break;
        }
    }

    m_deviceContext->IASetPrimitiveTopology(topology);

    if (realInsts == 1)
        m_deviceContext->DrawIndexed(indexCount, startIndex, startVertex);
    else
        m_deviceContext->DrawIndexedInstanced(indexCount, realInsts, startIndex, startVertex, 0);

    m_renderStats.triangles += triangleCount;
    m_renderStats.lines += lineCount;
    m_renderStats.vertices += vertexCount*realInsts;
    m_renderStats.batches++;
}

void eGraphicsApiDx11::renderStart()
{
    eDEBUG_WRITE_VERBOSE("Execute");
    eMemSet(&m_renderStats, 0, sizeof(m_renderStats));
}

void eGraphicsApiDx11::renderEnd()
{
    eDEBUG_WRITE_VERBOSE("Execute");
    HRESULT result = m_swapChain->Present(m_vsync ? 1 : 0, 0);
    eASSERT(!FAILED(result));
    m_renderStats.fps = _getFpsRate();
}

void eGraphicsApiDx11::setState(eU32 index, eState &state) 
{
	state.activate(index, m_device, m_deviceContext);
}

ePixelShader * eGraphicsApiDx11::createPixelShader(eShaderSource data) const
{
	eDEBUG_WRITE("Creating pixel shader");
    return new ePixelShaderDx11(m_device, m_deviceContext, data);
}

eVertexShader * eGraphicsApiDx11::createVertexShader(eShaderSource data) const
{
	eDEBUG_WRITE("Creating vertex shader");
    return new eVertexShaderDx11(m_device, m_deviceContext, data);
}

eGeometryShader * eGraphicsApiDx11::createGeometryShader(eShaderSource data) const
{
	eDEBUG_WRITE("Creating geometry shader");
	return new eGeometryShaderDx11(m_device, m_deviceContext, data);
}

eHullShader * eGraphicsApiDx11::createHullShader(eShaderSource data) const
{
	eDEBUG_WRITE("Creating hull shader");
	return new eHullShaderDx11(m_device, m_deviceContext, data);
}

eDomainShader * eGraphicsApiDx11::createDomainShader(eShaderSource data) const
{
	eDEBUG_WRITE("Creating domain shader");
	return new eDomainShaderDx11(m_device, m_deviceContext, data);
}

eComputeShader * eGraphicsApiDx11::createComputeShader(eShaderSource data) const
{
	eDEBUG_WRITE("Creating compute shader");
	return new eComputeShaderDx11(m_device, m_deviceContext, data);
}

eInputLayout * eGraphicsApiDx11::createInputLayout(eVertexType type, eShader *shader) const
{
	eDEBUG_WRITE("Creating input layout");
    return new eInputLayoutDx11(m_device, type, shader);
}

eVertexBuffer * eGraphicsApiDx11::createVertexBuffer(eU32 count, eU32 vertexSize, eBool dynamic) const
{
	eDEBUG_WRITE("Creating vertex buffer");
    return new eVertexBufferDx11(m_device, m_deviceContext, count, vertexSize, dynamic);
}

eIndexBuffer *  eGraphicsApiDx11::createIndexBuffer(eU32 indexCount, eBool dynamic) const
{
	eDEBUG_WRITE("Creating index buffer");
    return new eIndexBufferDx11(m_device, m_deviceContext, indexCount, dynamic);
}

eTexture2d * eGraphicsApiDx11::createTexture2d(eU32 width, eU32 height, const ePtr data, eBool renderTarget, eBool mipMapped, eBool dynamic, eFormat format) const
{
	eDEBUG_WRITE("Creating texture 2d");
    return new eTexture2dDx11(m_device, m_deviceContext, width, height, data, renderTarget, mipMapped, dynamic, format);
}

eTexture3d * eGraphicsApiDx11::createTexture3d(eU32 width, eU32 height, eU32 depth, eBool mipMapped, eBool dynamic, eFormat format)
{
	eDEBUG_WRITE("Creating texture 3d");
    return new eTexture3dDx11(m_device, m_deviceContext, width, height, depth, mipMapped, dynamic, format);
}

eTextureCube * eGraphicsApiDx11::createTextureCube(eU32 width, eBool renderTarget, eBool mipMapped, eBool dynamic, eFormat format) const
{
	eDEBUG_WRITE("Creating texture cube");
    return new eTextureCubeDx11(m_device, m_deviceContext, width, renderTarget, mipMapped, dynamic, format);
}

#ifndef eINTRO
eBool eGraphicsApiDx11::loadImage(const eByteArray &fileData, eColor *&image, eU32 &width, eU32 &height) const
{
    //TODO: implement this
    /*
    IDirect3DTexture9 *newTex = eNULL;

    if (FAILED(D3DXCreateTextureFromFileInMemoryEx(m_d3dDevice, &fileData[0], fileData.size(),
                                                   D3DX_DEFAULT_NONPOW2, D3DX_DEFAULT_NONPOW2,
                                                   D3DX_DEFAULT, 0, D3DFMT_UNKNOWN, D3DPOOL_MANAGED,
                                                   D3DX_DEFAULT, D3DX_DEFAULT, 0, eNULL, eNULL, &newTex)))
    {
        return eFALSE;
    }

    D3DSURFACE_DESC desc;
    newTex->GetLevelDesc(0, &desc);
    width = desc.Width;
    height = desc.Height;
    image = new eColor[width*height];
    eASSERT(image != eNULL);

    D3DLOCKED_RECT lr;

    newTex->LockRect(0, &lr, eNULL, D3DLOCK_READONLY);
    const eU8 *src = (eU8*)lr.pBits;

    for (eU32 i=0; i<width*height; i++)
    {
        const eU8 r = *src++;
        const eU8 g = *src++;
        const eU8 b = *src++;
        const eU8 a = *src++;

        image[i].set(b, g, r, a);
    }

    newTex->UnlockRect(0);
    eSAFE_RELEASE_COM(newTex);
    */
    return eTRUE;
}
#endif

ID3D11Device * eGraphicsApiDx11::getDevice() const
{
    return m_device;
}

eF32 eGraphicsApiDx11::_getFpsRate() const
{
    static const eU32 UPDATE_INTERVAL = 333;

    static eF32 fpsHolder = 0;
    static eU32 oldTime = 0;
    static eU32 frameCounter = 0;

    eU32 curTime = GetTickCount();
    frameCounter++;

    if (curTime-oldTime >= UPDATE_INTERVAL)
    {
        fpsHolder = (eF32)frameCounter*(1000.0f/(eF32)UPDATE_INTERVAL);
        frameCounter = 0;
        oldTime = curTime;
    }

    return fpsHolder;
}

// Callback function for Direct3D window.
static LRESULT CALLBACK wndProc(HWND hwnd, eU32 msg, WPARAM wparam, LPARAM lparam)
{
    static eGraphicsApiDx11 *gfx = eNULL;

    switch (msg)
    {
        case WM_CREATE:
        {
            CREATESTRUCT *cs = (CREATESTRUCT*)lparam;
            eASSERT(cs != eNULL);
            gfx = (eGraphicsApiDx11 *)cs->lpCreateParams;
            eASSERT(gfx != eNULL);
            return 0;
        }

        case WM_DESTROY:
        {
            PostQuitMessage(0);
            return 0;
        }

        case WM_SIZE:
        {
            const eU16 width = LOWORD(lparam);
            const eU16 height = HIWORD(lparam);

            gfx->resizeBackbuffer(width, height);
            return 0;
        }

        case WM_KEYDOWN:
        {
            switch(wparam)
            {
                case VK_ESCAPE:
                {
                    DestroyWindow(hwnd);
                    return 0;
                }
            }

            break;
        }
    }

    return DefWindowProc(hwnd, msg, wparam, lparam);
}

ePtr eGraphicsApiDx11::_createWindow(eU32 width, eU32 height, eBool fullScreen)
{
    // Register class and create window if needed.
    WNDCLASS wc;

    eMemSet(&wc, 0, sizeof(wc));
    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.hCursor       = LoadCursor(eNULL, IDC_ARROW);
    wc.lpfnWndProc   = wndProc;
    wc.lpszClassName = WINDOW_TITLE;
//    wc.hIcon    = LoadIcon(0 (LPCTSTR)IDI_ESTUDIO);
//    wc.hIconSm    = LoadIcon(wcex.hInstance, (LPCTSTR)IDI_SMALL);

    const ATOM res = RegisterClass(&wc);
    DWORD l = GetLastError();
    eASSERT(res != eNULL);

    // Create the window.
    if (m_fullScreen)
    {
        return CreateWindow(WINDOW_TITLE, WINDOW_TITLE, WS_VISIBLE | WS_POPUP, 0, 0,
                            m_wndWidth, m_wndHeight, eNULL, eNULL, eNULL, this);
    }
    else
    {
        // In windowed mode adjust the window rect,
        // so that the client area of the window has
        // the size of the desired resolution.
        const eU32 style = WS_OVERLAPPEDWINDOW | WS_VISIBLE;

        RECT r;

        r.left = 0;
        r.top = 0;
        r.right = m_wndWidth;
        r.bottom = m_wndHeight;

        AdjustWindowRect(&r, style, FALSE);    

        return CreateWindow(WINDOW_TITLE, WINDOW_TITLE, style, CW_USEDEFAULT, CW_USEDEFAULT,
                            r.right-r.left, r.bottom-r.top, eNULL, eNULL, eNULL, this);
    }
}

void eGraphicsApiDx11::resizeBackbuffer(eU32 width, eU32 height)
{
    // Only resize back-buffer if size has changed.
    if (m_wndWidth != width || m_wndHeight != height)
    {
        m_wndWidth = width;
        m_wndHeight = height;
        m_deviceContext->ClearState();

        eSAFE_RELEASE_COM(m_depthStencilView);
        eSAFE_RELEASE_COM(m_depthStencilBuffer);
        eSAFE_RELEASE_COM(m_renderTargetView);

        HRESULT result = m_swapChain->ResizeBuffers(1, width, height, DXGI_FORMAT_R8G8B8A8_UNORM, 0);
        eASSERT(!FAILED(result));

        _createRenderTargetView();
        _createDepthStencilView();

        eStateManager::reset(); // Important, because old D3D resource handles are invalid now.
        eStateManager::setViewport(0, 0, width, height);
        eStateManager::forceApply();
    }
}

eTexture2d * eGraphicsApiDx11::TARGET_SCREEN = (eTexture2d *)0xdeadbeef;

eTexture2d * eGraphicsApiDx11::createChessTexture(eU32 width, eU32 height, eU32 step, const eColor &col0, const eColor &col1) const
{
    eASSERT(width > 2);
    eASSERT(height > 2);
    eASSERT(step < width);
    eASSERT(step < height);
    eASSERT(width%step == 0);
    eASSERT(height%step == 0);

    eColor *data = new eColor[width * height];

    const eColor colors[2] =
    {
        col0,
        col1
    };

    for (eU32 y=0, index=0; y<height; y++)
    {
        const eU32 yds = y/step;

        for (eU32 x=0; x<width; x++)
        {
            const eU32 col = ((x/step)+yds)%2;
            data[index++] = colors[col];
        }
    }

    eTexture2d *tex = createTexture2d(width, height, data, eFALSE, eTRUE, eTRUE, eFORMAT_ARGB8);
    
    delete data;

    return tex;
}


