/*
 * This source file is part of KEOS (Free 3D Engine)
 * For the latest info, see http://www.keosengine.org/
 * E-mails : thierry.vouriot@keosengine.org, yeri@keosengine.org
 * 
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place - Suite 330, Boston, MA 02111-1307, USA, or go to
 * http://www.gnu.org/copyleft/lesser.txt.
 *
 */
#include "KeosD3D9RenderSystem.h"
#include "KeosD3D9Window.h"
#include "KeosD3D9Enum.h"
#include "KeosD3D9Exception.h"
#include "KeosD3D9Buffer.h"
#include "KeosD3D9Declaration.h"
#include "KeosD3D9Texture.h"
#include "KeosLogger.h"
#include "KeosMatrix4.h"
#include "KeosColor.h"
#include "KeosRenderSystemCapabilities.h"
#include "KeosPixelUtils.h"
#include "KeosString.h"
#include "KeosD3D9Shader.h"
#include "Cg/cgD3D9.h"

namespace Keos
{
    //=======================================================================
    // CD3D9RenderSystem implementation
    //=======================================================================
    
    //-----------------------------------------------------------------------
    CD3D9RenderSystem::CD3D9RenderSystem() 
    { 
		m_pD3D9 = new CD3D9();
   
        m_nMinVertex = 0;
        m_nVertexCount = 0;
		m_bDeviceLost = false;

        // Create our Direct3D object
		if( NULL == (m_pD3D9->pD3D = Direct3DCreate9(D3D_SDK_VERSION)) )
			D3D9_EXCEPT("Failed to create Direct3D9 object", "CD3D9RenderSystem::CD3D9RenderSystem");

		// Create Matrices
		for (int i = 0; i < 6; ++i)
			m_Matrices[i].push_back(CMatrix4());
    
    }

    //-----------------------------------------------------------------------
    CD3D9RenderSystem::~CD3D9RenderSystem()
    { 
		Shutdown();
        cgD3D9SetDevice(NULL);
		delete m_pD3D9;
    }

	//-----------------------------------------------------------------------
	const String& CD3D9RenderSystem::GetName(void) const
    {
        static String strName("Direct3D9");
        return strName;
    }

    //-----------------------------------------------------------------------
    IRenderWindow* CD3D9RenderSystem::Initialise(bool bAutoCreateWindow, const String& strWindowTitle)
    {
        ILogger::Log("--> Starting Direct3D9 Subsystem");

        ConfigOptionMap::iterator opt = m_Options.find("Video Mode");
        if (opt == m_Options.end())
            D3D9_EXCEPT("Can't find \"Video Mode\" option", "CD3D9RenderSystem::Initialise");
        String val = opt->second.currentValue;
        String::size_type pos = val.find('x');
        if (pos == String::npos)
            D3D9_EXCEPT("Invalid Video Mode provided", "COGLRenderSystem::Initialise");
	    uint w = CStringConverter::ParseUnsignedInt(val.substr(0, pos));
        uint h = CStringConverter::ParseUnsignedInt(val.substr(pos + 1));

        opt = m_Options.find("Color Depth");
        if (opt == m_Options.end())
            D3D9_EXCEPT("Can't find \"Color Depth\" option", "CD3D9RenderSystem::Initialise");
        val = opt->second.currentValue;
        uint d = CStringConverter::ParseUnsignedInt(val);
        if (d == 0)
            D3D9_EXCEPT("Invalid Color Depth provided", "CD3D9RenderSystem::Initialise");

        opt = m_Options.find("Fullscreen");
        if (opt == m_Options.end())
            D3D9_EXCEPT("Can't find \"Fullscreen\" option", "CD3D9RenderSystem::Initialise");
        val = opt->second.currentValue;
        bool f = CStringConverter::ParseBool(val);
  
	    IRenderWindow* pAutoWindow = CreateRenderWindow(strWindowTitle, w, h, d, f);
        
        return pAutoWindow;
    }

    //-----------------------------------------------------------------------
    void CD3D9RenderSystem::Reinitialise(void)
    {
		this->Shutdown();
		this->Initialise( true );
	}
       
	//-----------------------------------------------------------------------
    void CD3D9RenderSystem::Shutdown(void)
    { 
        IRenderSystem::Shutdown();
		m_pD3D9->ReleaseDevice();
    }

	//-----------------------------------------------------------------------
    IRenderWindow* CD3D9RenderSystem::CreateRenderWindow(const String &strName, uint nWidth, uint nHeight, uint nColourDepth, bool bFullScreen)
    {
        ILogger::Log("--> Create Win32 render window");

        // Create the window
        IRenderWindow* win = new CD3D9Window(m_pD3D9);
        win->Create(strName, nWidth, nHeight, nColourDepth, bFullScreen);

        AttachRenderTarget( *win );

        InitCapabilities();

        // Direct3D default states
		m_pD3D9->pD3DDevice->SetRenderState(D3DRS_DITHERENABLE,     true);
		m_pD3D9->pD3DDevice->SetRenderState(D3DRS_LIGHTING,         false);
		m_pD3D9->pD3DDevice->SetRenderState(D3DRS_ZENABLE,          true);
		m_pD3D9->pD3DDevice->SetRenderState(D3DRS_FOGENABLE,        false);
		m_pD3D9->pD3DDevice->SetRenderState(D3DRS_ALPHATESTENABLE,  false);
		m_pD3D9->pD3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, false);
		m_pD3D9->pD3DDevice->SetRenderState(D3DRS_CULLMODE,         D3DCULL_CCW);
		m_pD3D9->pD3DDevice->SetRenderState(D3DRS_STENCILMASK,      0xFF);
		m_pD3D9->pD3DDevice->SetRenderState(D3DRS_STENCILWRITEMASK, 0xFF);
		m_pD3D9->pD3DDevice->SetSamplerState(0, D3DSAMP_ADDRESSU,  D3DTADDRESS_WRAP);
		m_pD3D9->pD3DDevice->SetSamplerState(0, D3DSAMP_ADDRESSV,  D3DTADDRESS_WRAP);
		m_pD3D9->pD3DDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
		m_pD3D9->pD3DDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
		m_pD3D9->pD3DDevice->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);

		SetClearColor(CColor::Black);

        return win;
    }

	//-----------------------------------------------------------------------
    void CD3D9RenderSystem::InitCapabilities()
    {
		// Get caps
        D3DCAPS9 Caps;
        m_pD3D9->pD3DDevice->GetDeviceCaps(&Caps);

        // Check for hardware mipmapping support
        if ((Caps.Caps2 & D3DCAPS2_CANAUTOGENMIPMAP) != 0)
			m_pCapabilities->SetCapability(CAP_HW_MIPMAPPING);
       
        // Check for texture dxt compression
        if (CheckFormat(D3DFMT_DXT1, D3DRTYPE_TEXTURE) &&
                                            CheckFormat(D3DFMT_DXT3, D3DRTYPE_TEXTURE) &&
                                            CheckFormat(D3DFMT_DXT5, D3DRTYPE_TEXTURE))
            m_pCapabilities->SetCapability(CAP_TEXTURE_COMPRESSION_DXT);
        
        // Check for non-power-of-2 texture support
        if (!(Caps.TextureCaps & D3DPTEXTURECAPS_POW2) &&
                                            !(Caps.TextureCaps & D3DPTEXTURECAPS_NONPOW2CONDITIONAL))
            m_pCapabilities->SetCapability(CAP_NON_POWER_OF_2_TEXTURES); 

		// Set number of texture units
		m_pCapabilities->SetNumTextureUnits(Caps.MaxSimultaneousTextures);

        // Check for Vertex Shaders support and set version number
        SearchVertexShaderCaps(Caps);
        // Check for Pixel Shaders support and set version number
        SearchPixelShaderCaps(Caps);
        
        m_pCapabilities->Log();

        // Setup Cg D3D Runtime
        cgD3D9SetDevice(m_pD3D9->pD3DDevice);
		if (m_pCapabilities->HasCapability(CAP_VERTEX_PROGRAM))
		{
			m_VSProfile    = cgD3D9GetLatestVertexProfile();
			m_VSOptions[0] = cgD3D9GetOptimalOptions(m_VSProfile);
			m_VSOptions[1] = NULL;
		}
		if (m_pCapabilities->HasCapability(CAP_FRAGMENT_PROGRAM))
		{
			m_PSProfile    = cgD3D9GetLatestPixelProfile();
			m_PSOptions[0] = cgD3D9GetOptimalOptions(m_PSProfile);
			m_PSOptions[1] = NULL;
		}
    }

    //---------------------------------------------------------------------
    void CD3D9RenderSystem::SearchVertexShaderCaps(D3DCAPS9& Caps)
    {
        ushort nMajor, nMinor;
        nMajor = static_cast<ushort>((Caps.VertexShaderVersion & 0x0000FF00) >> 8);
        nMinor = static_cast<ushort>(Caps.VertexShaderVersion & 0x000000FF);

        // Populate max version & params
        switch (nMajor)
        {
        case 1:
            m_pCapabilities->SetCapability(CAP_VERTEX_PROGRAM);
            m_pCapabilities->SetMaxVertexProgramVersion("vs_1_1");
            // No boolean params allowed
            //m_pCapabilities->SetVertexProgramConstantBoolCount(0);
            // No integer params allowed
            //m_pCapabilities->SetVertexProgramConstantIntCount(0);
            // float params, always 4D
            //m_pCapabilities->SetVertexProgramConstantFloatCount(mCaps.MaxVertexShaderConst);
            break;
        case 2:
            m_pCapabilities->SetCapability(CAP_VERTEX_PROGRAM);
            if (nMinor > 0)
            {
                m_pCapabilities->SetMaxVertexProgramVersion("vs_2_x");
            }
            else
            {
                m_pCapabilities->SetMaxVertexProgramVersion("vs_2_0");
            }
            // 16 boolean params allowed
            //m_pCapabilities->SetVertexProgramConstantBoolCount(16);
            // 16 integer params allowed, 4D
            //m_pCapabilities->SetVertexProgramConstantIntCount(16);
            // float params, always 4D
            //m_pCapabilities->SetVertexProgramConstantFloatCount(Caps.MaxVertexShaderConst);
            break;
        case 3:
            m_pCapabilities->SetCapability(CAP_VERTEX_PROGRAM);
            m_pCapabilities->SetMaxVertexProgramVersion("vs_3_0");
            // 16 boolean params allowed
            //m_pCapabilities->SetVertexProgramConstantBoolCount(16);
            // 16 integer params allowed, 4D
            //m_pCapabilities->SetVertexProgramConstantIntCount(16);
            // float params, always 4D
            //m_pCapabilities->SetVertexProgramConstantFloatCount(mCaps.MaxVertexShaderConst);
            break;
        default:
            m_pCapabilities->SetMaxVertexProgramVersion("");
            break;
        }  
    }

    //---------------------------------------------------------------------
    void CD3D9RenderSystem::SearchPixelShaderCaps(D3DCAPS9& Caps)
    {
        ushort nMajor, nMinor;
        nMajor = static_cast<ushort>((Caps.PixelShaderVersion & 0x0000FF00) >> 8);
        nMinor = static_cast<ushort>(Caps.PixelShaderVersion & 0x000000FF);

        switch (nMajor)
        {
        case 1:
            m_pCapabilities->SetCapability(CAP_FRAGMENT_PROGRAM);
            switch(nMinor)
            {
            case 1:
                m_pCapabilities->SetMaxFragmentProgramVersion("ps_1_1");
                break;
            case 2:
                m_pCapabilities->SetMaxFragmentProgramVersion("ps_1_2");
                break;
            case 3:
                m_pCapabilities->SetMaxFragmentProgramVersion("ps_1_3");
                break;
            case 4:
                m_pCapabilities->SetMaxFragmentProgramVersion("ps_1_4");
                break;
            }
            // no boolean params allowed
            //m_pCapabilities->SetFragmentProgramConstantBoolCount(0);
            // no integer params allowed
            //m_pCapabilities->SetFragmentProgramConstantIntCount(0);
            // float params, always 4D
            //m_pCapabilities->SetFragmentProgramConstantFloatCount(8);
            break;
        case 2:
            m_pCapabilities->SetCapability(CAP_FRAGMENT_PROGRAM);
			if ((Caps.PS20Caps.Caps & D3DPS20CAPS_NODEPENDENTREADLIMIT) &&
				(Caps.PS20Caps.Caps & D3DPS20CAPS_NOTEXINSTRUCTIONLIMIT))
			{
	            if ((Caps.PS20Caps.Caps & D3DPS20CAPS_ARBITRARYSWIZZLE) &&
					(Caps.PS20Caps.Caps & D3DPS20CAPS_GRADIENTINSTRUCTIONS) &&
					(Caps.PS20Caps.Caps & D3DPS20CAPS_PREDICATION))
	            {
					// ps_2_a is more powerful than ps_2_b? odd
		            m_pCapabilities->SetMaxFragmentProgramVersion("ps_2_a");
				}
				else
				{
					m_pCapabilities->SetMaxFragmentProgramVersion("ps_2_b");
				}

                // 16 boolean params allowed
                //m_pCapabilities->SetFragmentProgramConstantBoolCount(16);
                // 16 integer params allowed, 4D
                //m_pCapabilities->SetFragmentProgramConstantIntCount(16);
                // float params, always 4D
                //m_pCapabilities->SetFragmentProgramConstantFloatCount(224);
            }
            else
            {
                m_pCapabilities->SetMaxFragmentProgramVersion("ps_2_0");
                // no boolean params allowed
                //m_pCapabilities->SetFragmentProgramConstantBoolCount(0);
                // no integer params allowed
                //m_pCapabilities->SetFragmentProgramConstantIntCount(0);
                // float params, always 4D
                //m_pCapabilities->SetFragmentProgramConstantFloatCount(32);
            }
            break;
        case 3:
            m_pCapabilities->SetCapability(CAP_FRAGMENT_PROGRAM);
            if (nMinor > 0)
            {
                m_pCapabilities->SetMaxFragmentProgramVersion("ps_3_x");
            }
            else
            {
                m_pCapabilities->SetMaxFragmentProgramVersion("ps_3_0");
            }
            // 16 boolean params allowed
            //m_pCapabilities->SetFragmentProgramConstantBoolCount(16);
            // 16 integer params allowed, 4D
            //m_pCapabilities->SetFragmentProgramConstantIntCount(16);
            // float params, always 4D
            //m_pCapabilities->SetFragmentProgramConstantFloatCount(224);
            break;
        default:
            m_pCapabilities->SetMaxFragmentProgramVersion("");
            break;
        }
    }

	//-----------------------------------------------------------------------
    bool CD3D9RenderSystem::CheckFormat(D3DFORMAT Format, D3DRESOURCETYPE ResourceType, ulong nUsage)
	{
		Assert(m_pD3D9->pD3D != NULL);

		return SUCCEEDED(m_pD3D9->pD3D->CheckDeviceFormat(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, D3DFMT_X8R8G8B8,
													nUsage, ResourceType, Format));
	}

    //-----------------------------------------------------------------------
    void CD3D9RenderSystem::BeginScene() const
    { 
        m_pD3D9->pD3DDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL, m_ClearColor.ToRGBA(), 1.0f, 0x00);
        m_pD3D9->pD3DDevice->BeginScene();
       
    }

    //-----------------------------------------------------------------------
    void CD3D9RenderSystem::EndScene() const
    {
        m_pD3D9->pD3DDevice->EndScene();
    }

    //-----------------------------------------------------------------------
    void CD3D9RenderSystem::PrintInfo() const
    {
        printf("Direct3D9\n");
    }

    //-----------------------------------------------------------------------
    IBufferBase* CD3D9RenderSystem::_CreateVB(ulong nSize, ulong nStride, ulong nFlags) const
    {
        // Create buffer
        LPDIRECT3DVERTEXBUFFER9 VertexBuffer;
        if (FAILED(m_pD3D9->pD3DDevice->CreateVertexBuffer(nSize * nStride, CD3D9Enum::BufferFlags(nFlags), 0, D3DPOOL_DEFAULT, &VertexBuffer, NULL)))
            D3D9_EXCEPT("Error in fonction CreateVertexBuffer", "CD3D9RenderSystem::_CreateVB");

        return new CD3D9VertexBuffer(nSize, VertexBuffer);
    }

    //-----------------------------------------------------------------------
    IBufferBase* CD3D9RenderSystem::_CreateIB(ulong nSize, ulong nStride, ulong nFlags) const
    {
        // Create buffer
        LPDIRECT3DINDEXBUFFER9 IndexBuffer;
        if (FAILED(m_pD3D9->pD3DDevice->CreateIndexBuffer(nSize * nStride, CD3D9Enum::BufferFlags(nFlags), nStride == 2 ? D3DFMT_INDEX16 : D3DFMT_INDEX32, D3DPOOL_DEFAULT, &IndexBuffer, NULL)))
            D3D9_EXCEPT("Error in fonction CreateIndexBuffer", "CD3D9RenderSystem::_CreateIB");

        return new CD3D9IndexBuffer(nSize, IndexBuffer);
    }

    //-----------------------------------------------------------------------
    IDeclaration* CD3D9RenderSystem::CreateDeclaration(const TDeclarationElement* pElements, size_t nCount) const
    {
        std::vector<D3DVERTEXELEMENT9> DxElements;
        std::vector<int> Offset(nCount, 0);

		// Convert declaration elements in Dx9 elements
        for (const TDeclarationElement* Elt = pElements; Elt < pElements + nCount; ++Elt)
        {
            // Build temp element
            D3DVERTEXELEMENT9 CurrentElement;

            // Stream, offset and method
		    CurrentElement.Stream = Elt->Stream;
            CurrentElement.Offset = Offset[Elt->Stream];
            CurrentElement.Method = D3DDECLMETHOD_DEFAULT;

            // Usage
            switch (Elt->Usage)
            {
                case ELT_USAGE_POSITION :  CurrentElement.Usage = D3DDECLUSAGE_POSITION; CurrentElement.UsageIndex = 0; break;
                case ELT_USAGE_NORMAL :    CurrentElement.Usage = D3DDECLUSAGE_NORMAL;   CurrentElement.UsageIndex = 0; break;
                case ELT_USAGE_DIFFUSE :   CurrentElement.Usage = D3DDECLUSAGE_COLOR;    CurrentElement.UsageIndex = 0; break;
                case ELT_USAGE_TEXCOORD0 : CurrentElement.Usage = D3DDECLUSAGE_TEXCOORD; CurrentElement.UsageIndex = 0; break;
                case ELT_USAGE_TEXCOORD1 : CurrentElement.Usage = D3DDECLUSAGE_TEXCOORD; CurrentElement.UsageIndex = 1; break;
			    case ELT_USAGE_TEXCOORD2 : CurrentElement.Usage = D3DDECLUSAGE_TEXCOORD; CurrentElement.UsageIndex = 2; break;
			    case ELT_USAGE_TEXCOORD3 : CurrentElement.Usage = D3DDECLUSAGE_TEXCOORD; CurrentElement.UsageIndex = 3; break;
            }

            // Offset
            switch (Elt->DataType)
            {
                case ELT_TYPE_FLOAT1 : CurrentElement.Type = D3DDECLTYPE_FLOAT1;   Offset[Elt->Stream] += 4;  break;
                case ELT_TYPE_FLOAT2 : CurrentElement.Type = D3DDECLTYPE_FLOAT2;   Offset[Elt->Stream] += 8;  break;
                case ELT_TYPE_FLOAT3 : CurrentElement.Type = D3DDECLTYPE_FLOAT3;   Offset[Elt->Stream] += 12; break;
                case ELT_TYPE_FLOAT4 : CurrentElement.Type = D3DDECLTYPE_FLOAT4;   Offset[Elt->Stream] += 16; break;
                case ELT_TYPE_COLOR :  CurrentElement.Type = D3DDECLTYPE_D3DCOLOR; Offset[Elt->Stream] += 4;  break;
            }

            // Add the current element
	        DxElements.push_back(CurrentElement);
        }

		// Add the element which is the end of the array
        D3DVERTEXELEMENT9 EndElement = D3DDECL_END();
	    DxElements.push_back(EndElement);

        // Create declaration
        LPDIRECT3DVERTEXDECLARATION9 Declaration = NULL;
        if (FAILED(m_pD3D9->pD3DDevice->CreateVertexDeclaration(&DxElements[0], &Declaration)))
            D3D9_EXCEPT("Error in fonction CreateVertexDeclaration", "CD3D9RenderSystem::CreateDeclaration");

        return new CD3D9Declaration(Declaration);
    }

    //-----------------------------------------------------------------------
    void CD3D9RenderSystem::_SetVB(uint nStream, const IBufferBase* pBuffer, ulong nStride, ulong nMinVertex, ulong nMaxVertex)
    {
		// Get the Dx buffer
        const CD3D9VertexBuffer* VertexBuffer = static_cast<const CD3D9VertexBuffer*>(pBuffer);

        // Send to the API
        m_pD3D9->pD3DDevice->SetStreamSource(nStream, VertexBuffer ? VertexBuffer->GetBuffer() : NULL, 0, nStride);

        // Save MinVertex and VertexCount
        m_nMinVertex   = nMinVertex;
        m_nVertexCount = nMaxVertex - nMinVertex + 1;
    }

    //-----------------------------------------------------------------------
    void CD3D9RenderSystem::_SetIB(const IBufferBase* pBuffer, ulong nStride)
    {
        // Get the Dx buffer
        const CD3D9IndexBuffer* IndexBuffer = static_cast<const CD3D9IndexBuffer*>(pBuffer);

        // Send to the API
        m_pD3D9->pD3DDevice->SetIndices(IndexBuffer ? IndexBuffer->GetBuffer() : NULL);
    }

    //-----------------------------------------------------------------------
    void CD3D9RenderSystem::SetDeclaration(const IDeclaration* pDeclaration)
    {
        // Get the Dx buffer
        const CD3D9Declaration* DxDeclaration = static_cast<const CD3D9Declaration*>(pDeclaration);

        // Send to the API
        m_pD3D9->pD3DDevice->SetVertexDeclaration(DxDeclaration ? DxDeclaration->GetDeclaration() : NULL);
    }

    //-----------------------------------------------------------------------
    void CD3D9RenderSystem::DrawPrimitives(TPrimitiveType Type, ulong nFirstVertex, ulong nCount) const
    {
        m_pD3D9->pD3DDevice->DrawPrimitive(CD3D9Enum::Get(Type), nFirstVertex, nCount);
    }

    //-----------------------------------------------------------------------
    void CD3D9RenderSystem::DrawIndexedPrimitives(TPrimitiveType Type, ulong nFirstIndex, ulong nCount) const
    {
        m_pD3D9->pD3DDevice->DrawIndexedPrimitive(CD3D9Enum::Get(Type), 0, m_nMinVertex, m_nVertexCount, nFirstIndex, nCount);
    }

    //-----------------------------------------------------------------------
    void CD3D9RenderSystem::PushMatrix(TMatrixType Type)
    {
        m_Matrices[Type].push_back(m_Matrices[Type].back());
    }

    //-----------------------------------------------------------------------
    void CD3D9RenderSystem::PopMatrix(TMatrixType Type)
    {
        m_Matrices[Type].pop_back();

        m_pD3D9->pD3DDevice->SetTransform(CD3D9Enum::Get(Type), (D3DMATRIX*)&m_Matrices[Type].back());
    }

    //-----------------------------------------------------------------------
    void CD3D9RenderSystem::LoadMatrix(TMatrixType Type, const CMatrix4& Matrix)
    {
        m_Matrices[Type].back() = Matrix;

        m_pD3D9->pD3DDevice->SetTransform(CD3D9Enum::Get(Type), (D3DMATRIX*)&Matrix);
    }

    //-----------------------------------------------------------------------
    void CD3D9RenderSystem::LoadMatrixMult(TMatrixType Type, const CMatrix4& Matrix)
    {
        m_Matrices[Type].back() = Matrix * m_Matrices[Type].back();

        m_pD3D9->pD3DDevice->SetTransform(CD3D9Enum::Get(Type), (D3DMATRIX*)&m_Matrices[Type].back());
    }

    //-----------------------------------------------------------------------
    void CD3D9RenderSystem::GetMatrix(TMatrixType Type, CMatrix4& Matrix) const
    {
        Matrix = m_Matrices[Type].back();
    }

    //-----------------------------------------------------------------------
    ulong CD3D9RenderSystem::ConvertColor(const CColor& Color) const
    { 
        return Color.ToARGB();
    }

    //-----------------------------------------------------------------------
    ITextureBase* CD3D9RenderSystem::CreateTexture(const TVector2I& Size, TPixelFormat Format, ulong nFlags) const
    {
		// Detect mipmapping options
        bool bHasMipmaps  = (nFlags & TEX_NOMIPMAP) != 0;
        bool bAutoMipmaps = GetCapabilities()->HasCapability(CAP_HW_MIPMAPPING) &&
                        m_pD3D9->pD3D->CheckDeviceFormat(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, D3DFMT_X8R8G8B8,
                                                        D3DUSAGE_AUTOGENMIPMAP, D3DRTYPE_TEXTURE,
                                                        CD3D9Enum::Get(Format)) == D3D_OK;

        // Adjust Usage
        ulong nUsage = bAutoMipmaps ? D3DUSAGE_AUTOGENMIPMAP : 0;

        // Create Dx9 texture
        LPDIRECT3DTEXTURE9 Texture = NULL;
        if (FAILED(D3DXCreateTexture(m_pD3D9->pD3DDevice, Size.x, Size.y, 0, nUsage, CD3D9Enum::Get(Format), D3DPOOL_MANAGED, &Texture)))
            D3D9_EXCEPT("Error in fonction D3DXCreateTexture", "CD3D9RenderSystem::CreateTexture");

        return new CD3D9Texture(Size, Format, bHasMipmaps, bAutoMipmaps, Texture);
    }
    
	//-----------------------------------------------------------------------
    void CD3D9RenderSystem::SetTexture(uint nUnit, const ITextureBase* pTexture) const
    {
        const CD3D9Texture* DxTexture = static_cast<const CD3D9Texture*>(pTexture);
        m_pD3D9->pD3DDevice->SetTexture(nUnit, DxTexture ? DxTexture->GetD3D9Texture() : NULL);
    }
    
	//-----------------------------------------------------------------------
    void CD3D9RenderSystem::SetupAlphaBlending(TBlend Src, TBlend Dest) const
    {
        m_pD3D9->pD3DDevice->SetRenderState(D3DRS_SRCBLEND,  CD3D9Enum::Get(Src));
        m_pD3D9->pD3DDevice->SetRenderState(D3DRS_DESTBLEND, CD3D9Enum::Get(Dest));
    }
    
	//-----------------------------------------------------------------------
    void CD3D9RenderSystem::SetupTextureUnit(uint nUnit, TTextureOp Op, TTextureArg Arg1, TTextureArg Arg2, const CColor& Constant) const
    {
        // Operator and arguments
        if (Op < TXO_ALPHA_FIRSTARG)
        {
            m_pD3D9->pD3DDevice->SetTextureStageState(nUnit, D3DTSS_COLOROP,   CD3D9Enum::Get(Op));
            m_pD3D9->pD3DDevice->SetTextureStageState(nUnit, D3DTSS_COLORARG1, CD3D9Enum::Get(Arg1));
            m_pD3D9->pD3DDevice->SetTextureStageState(nUnit, D3DTSS_COLORARG2, CD3D9Enum::Get(Arg2));
        }
        else
        {
            m_pD3D9->pD3DDevice->SetTextureStageState(nUnit, D3DTSS_ALPHAOP,   CD3D9Enum::Get(Op));
            m_pD3D9->pD3DDevice->SetTextureStageState(nUnit, D3DTSS_ALPHAARG1, CD3D9Enum::Get(Arg1));
            m_pD3D9->pD3DDevice->SetTextureStageState(nUnit, D3DTSS_ALPHAARG2, CD3D9Enum::Get(Arg2));
        }

        // Constant
        if ((Arg1 == TXA_CONSTANT) || (Arg2 == TXA_CONSTANT))
        {
            m_pD3D9->pD3DDevice->SetRenderState(D3DRS_TEXTUREFACTOR, ConvertColor(Constant));
        }
    }
    
	//-----------------------------------------------------------------------
    void CD3D9RenderSystem::Enable(TRenderParameter Param, bool bValue) const
    {
        m_pD3D9->pD3DDevice->SetRenderState(CD3D9Enum::Get(Param), bValue);
    }

	//---------------------------------------------------------------------
	IShaderBase* CD3D9RenderSystem::CreateShader(CGprogram Program, TShaderType Type) const
	{
		return new CD3D9Shader(Program, Type);
	}

	//---------------------------------------------------------------------
	void CD3D9RenderSystem::SetVertexShader(const IShaderBase* pShader)
	{
		const CD3D9Shader* pDxShader = static_cast<const CD3D9Shader*>(pShader);

		if (pDxShader)
		{
			if (pDxShader->GetType() != SHADER_VERTEX)
				D3D9_EXCEPT("Incorrect shader type (Pixel Shader)", "CD3D9RenderSystem::SetVertexShader");
				
			cgD3D9BindProgram(pDxShader->GetProgram());
		}
		else
		{
			m_pD3D9->pD3DDevice->SetVertexShader(NULL);
		}
	}

	//---------------------------------------------------------------------
	void CD3D9RenderSystem::SetPixelShader(const IShaderBase* pShader)
	{
		const CD3D9Shader* pDxShader = static_cast<const CD3D9Shader*>(pShader);

		if (pDxShader)
		{
			if (pDxShader->GetType() != SHADER_PIXEL)
				D3D9_EXCEPT("Incorrect shader type (Vertex Shader)", "CD3D9RenderSystem::SetPixelShader");

			cgD3D9BindProgram(pDxShader->GetProgram());
		}
		else
		{
			m_pD3D9->pD3DDevice->SetPixelShader(NULL);
		}
	}

	//---------------------------------------------------------------------
	void CD3D9RenderSystem::RestoreLostDevice(void)
	{
		// Reset the device, using the primary window presentation params
		HRESULT hr = m_pD3D9->pD3DDevice->Reset(
			((CD3D9Window*)m_pRenderWindow)->GetPresentationParameters());

		if (hr == D3DERR_DEVICELOST)
		{
			// Don't continue
			return;
		}
		else if (FAILED(hr))
		{
			D3D9_EXCEPT("Cannot reset device !", "CD3D9RenderSystem::RestoreLostDevice");
		}

		m_bDeviceLost = false;
	}
	
	//---------------------------------------------------------------------
	bool CD3D9RenderSystem::IsDeviceLost(void)
	{
		return m_bDeviceLost;
	}
	
	//---------------------------------------------------------------------
	void CD3D9RenderSystem::_NotifyDeviceLost(void)
	{
		m_bDeviceLost = true;
	}

	//-----------------------------------------------------------------------
	void CD3D9RenderSystem::SetClearColor(const CColor& Color)
	{
		m_ClearColor = Color;
	}
}
