/*
 * 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 "KeosOGLRenderSystem.h"
#include "KeosWin32Window.h"
#include "KeosOGLException.h"
#include "KeosLogger.h"
#include "KeosOGLEnum.h"
#include "KeosOGLBuffer.h"
#include "KeosOGLDeclaration.h"
#include "KeosMatrix4.h"
#include "KeosColor.h"
#include "KeosRenderSystemCapabilities.h"
#include "KeosOGLTexture.h"
#include "KeosPixelUtils.h"
#include "KeosString.h"
#include "KeosOGLShader.h"
#include "Cg/cgGL.h"


namespace Keos
{
    //=======================================================================
    // COGLRenderSystem implementation
    //=======================================================================
    
    // Functions ant macros for extensions loading
    template <class T> inline void LoadExtension(T& Proc, const char* strName)
    {
        if (!(Proc = reinterpret_cast<T>(wglGetProcAddress(strName))))
            OGL_EXCEPT("wglGetProcAddress", "LoadExtension");
    }
    #define LOAD_EXTENSION(Ext) LoadExtension(Ext, #Ext)

    // OpenGL Extensions
    PFNGLBINDBUFFERARBPROC              COGLRenderSystem::glBindBufferARB;
    PFNGLDELETEBUFFERSARBPROC           COGLRenderSystem::glDeleteBuffersARB;
    PFNGLGENBUFFERSARBPROC              COGLRenderSystem::glGenBuffersARB;
    PFNGLBUFFERDATAARBPROC              COGLRenderSystem::glBufferDataARB;
    PFNGLBUFFERSUBDATAARBPROC           COGLRenderSystem::glBufferSubDataARB;
    PFNGLGETBUFFERSUBDATAARBPROC        COGLRenderSystem::glGetBufferSubDataARB;
    PFNGLMAPBUFFERARBPROC               COGLRenderSystem::glMapBufferARB;
    PFNGLUNMAPBUFFERARBPROC             COGLRenderSystem::glUnmapBufferARB;
    PFNGLACTIVETEXTUREARBPROC           COGLRenderSystem::glActiveTextureARB;
    PFNGLCLIENTACTIVETEXTUREARBPROC     COGLRenderSystem::glClientActiveTextureARB;
    PFNGLCOMPRESSEDTEXIMAGE2DARBPROC    COGLRenderSystem::glCompressedTexImage2DARB;
    PFNGLCOMPRESSEDTEXSUBIMAGE2DARBPROC COGLRenderSystem::glCompressedTexSubImage2DARB;

    //-----------------------------------------------------------------------
    COGLRenderSystem::COGLRenderSystem() 
    { 
        m_bGLInitialized = false;
    }

    //-----------------------------------------------------------------------
    COGLRenderSystem::~COGLRenderSystem()
    { 
        Shutdown();
    }

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

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

        ConfigOptionMap::iterator opt = m_Options.find("Video Mode");
        if (opt == m_Options.end())
            OGL_EXCEPT("Can't find \"Video Mode\" option", "COGLRenderSystem::Initialise");
        String val = opt->second.currentValue;
        String::size_type pos = val.find('x');
        if (pos == String::npos)
            OGL_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())
            OGL_EXCEPT("Can't find \"Color Depth\" option", "COGLRenderSystem::Initialise");
        val = opt->second.currentValue;
        uint d = CStringConverter::ParseUnsignedInt(val);
        if (d == 0)
            OGL_EXCEPT("Invalid Color Depth provided", "COGLRenderSystem::Initialise");

        opt = m_Options.find("Fullscreen");
        if (opt == m_Options.end())
            OGL_EXCEPT("Can't find \"Fullscreen\" option", "COGLRenderSystem::Initialise");
        val = opt->second.currentValue;
        bool f = CStringConverter::ParseBool(val);
  

	    IRenderWindow* pAutoWindow = CreateRenderWindow(strWindowTitle, w, h, d, f);

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

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

        AttachRenderTarget( *win );

        if (!m_bGLInitialized) 
        {
            // Initialise GL after the first window has been created
            InitGL();
        }

        return win;
    }

    //-----------------------------------------------------------------------
    void COGLRenderSystem::InitGL(void)
    {
        ILogger::Log("--> Initialise OpenGL");

        // Retrieve extensions
        m_strExtensions = reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS));

        // Load OpenGL extensions
        LoadExtensions();

		// Setup Cg OGL Runtime
		if (m_pCapabilities->HasCapability(CAP_VERTEX_PROGRAM))
		{
			m_VSProfile = cgGLGetLatestProfile(CG_GL_VERTEX);
			cgGLSetOptimalOptions(m_VSProfile);
			m_VSOptions[0] = NULL;
		}
		if (m_pCapabilities->HasCapability(CAP_FRAGMENT_PROGRAM))
		{
			m_PSProfile = cgGLGetLatestProfile(CG_GL_FRAGMENT);
			cgGLSetOptimalOptions(m_PSProfile);
			m_PSOptions[0] = NULL;
		}

        // Default states
        SetClearColor(CColor::Black);
        glClearDepth(1.0f);
        glClearStencil(0x00);
        glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
        glShadeModel(GL_SMOOTH);
        glEnable(GL_DEPTH_TEST);

        m_bGLInitialized = true;
    }

    //-----------------------------------------------------------------------
    void COGLRenderSystem::Reinitialise(void)
    {
        this->Shutdown();
        this->Initialise(true);
    }

    //-----------------------------------------------------------------------
    void COGLRenderSystem::Shutdown(void)
    {
        IRenderSystem::Shutdown();
    }

    //-----------------------------------------------------------------------
    bool COGLRenderSystem::CheckExtension(const String& strExtension) const
    {
        return m_strExtensions.find(strExtension) != String::npos;
    }

    //-----------------------------------------------------------------------
    void COGLRenderSystem::LoadExtensions()
    {
        // Load extensions
        LOAD_EXTENSION(glBindBufferARB);
        LOAD_EXTENSION(glDeleteBuffersARB);
        LOAD_EXTENSION(glGenBuffersARB);
        LOAD_EXTENSION(glBufferDataARB);
        LOAD_EXTENSION(glBufferSubDataARB);
        LOAD_EXTENSION(glGetBufferSubDataARB);
        LOAD_EXTENSION(glMapBufferARB);
        LOAD_EXTENSION(glUnmapBufferARB);
        LOAD_EXTENSION(glActiveTextureARB);
        LOAD_EXTENSION(glClientActiveTextureARB);
        LOAD_EXTENSION(glCompressedTexImage2DARB);
        LOAD_EXTENSION(glCompressedTexSubImage2DARB);

        // Check for hardware mipmapping support
        if (CheckExtension("GL_SGIS_generate_mipmap"))
        {
            m_pCapabilities->SetCapability(CAP_HW_MIPMAPPING);
        }
        // Check for texture dxt compression
        if (CheckExtension("GL_ARB_texture_compression") && CheckExtension("GL_EXT_texture_compression_s3tc"))
        {
            m_pCapabilities->SetCapability(CAP_TEXTURE_COMPRESSION_DXT);
        }
        // Check for non-power-of-2 texture support
        if (CheckExtension("GL_ARB_texture_non_power_of_two"))
        {
            m_pCapabilities->SetCapability(CAP_NON_POWER_OF_2_TEXTURES);
        }
        // Check for Multitexturing support and set number of texture units
        if( CheckExtension("GL_ARB_multitexture"))
        {
            GLint units;
            glGetIntegerv( GL_MAX_TEXTURE_UNITS, &units );
            m_pCapabilities->SetNumTextureUnits(units);
        }
        else
        {
            // If no multitexture support then set one texture unit
            m_pCapabilities->SetNumTextureUnits(1);
        }

        // Check for Vertex Program support and set version number
        if(CheckExtension("GL_ARB_vertex_program"))
        {
            m_pCapabilities->SetCapability(CAP_VERTEX_PROGRAM);

            // Vertex Program Properties
            m_pCapabilities->SetMaxVertexProgramVersion("arbvp1");
            //mCapabilities->setVertexProgramConstantBoolCount(0);
            //mCapabilities->setVertexProgramConstantIntCount(0);
            //mCapabilities->setVertexProgramConstantFloatCount(GL_MAX_PROGRAM_LOCAL_PARAMETERS_ARB);

			if (CheckExtension("GL_NV_vertex_program2_option"))
			{
				m_pCapabilities->SetMaxVertexProgramVersion("vp30");
			}

			if (CheckExtension("GL_NV_vertex_program3"))
			{
				m_pCapabilities->SetMaxVertexProgramVersion("vp40");
			}
		}

        // Check for Fragment Program support and set version number
        if (CheckExtension("GL_NV_register_combiners2") &&
            CheckExtension("GL_NV_texture_shader"))
        {
            m_pCapabilities->SetCapability(CAP_FRAGMENT_PROGRAM);
            m_pCapabilities->SetMaxFragmentProgramVersion("fp20");
        }

		if (CheckExtension("GL_ATI_fragment_shader"))
		{
            // ATI fragment shader not supported by Cg Compiler
            ILogger::Log("ATI fragment shader not supported by Keos Engine (Cg Compiler)");
            //m_pCapabilities->SetCapability(RSC_FRAGMENT_PROGRAM);
            //m_pCapabilities->SetMaxFragmentProgramVersion("ps_1_4");
            // no boolean params allowed
            //m_pCapabilities->SetFragmentProgramConstantBoolCount(0);
            // no integer params allowed
            //m_pCapabilities->SetFragmentProgramConstantIntCount(0);
			// only 8 Vector4 constant floats supported
            //m_pCapabilities->SetFragmentProgramConstantFloatCount(8);
		}

        if (CheckExtension("GL_ARB_fragment_program"))
        {
            m_pCapabilities->SetCapability(CAP_FRAGMENT_PROGRAM);
            // Fragment Program Properties
            m_pCapabilities->SetMaxFragmentProgramVersion("arbfp1");
            //m_pCapabilities->SetFragmentProgramConstantBoolCount(0);
            //m_pCapabilities->SetFragmentProgramConstantIntCount(0);
            //m_pCapabilities->SetFragmentProgramConstantFloatCount(GL_MAX_PROGRAM_LOCAL_PARAMETERS_ARB);

			if (CheckExtension("GL_NV_fragment_program_option"))
			{
				m_pCapabilities->SetMaxFragmentProgramVersion("fp30");
			}

			if (CheckExtension("GL_NV_fragment_program2"))
			{
				m_pCapabilities->SetMaxFragmentProgramVersion("fp40");
			}        
		}

        m_pCapabilities->Log();
    }

    //-----------------------------------------------------------------------
    void COGLRenderSystem::BeginScene() const
    { 
       glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    }

    //-----------------------------------------------------------------------
    void COGLRenderSystem::EndScene() const
    {  }

    //-----------------------------------------------------------------------
    void COGLRenderSystem::PrintInfo() const
    {
        printf("OpenGL informations :\n");
        // Print informations about OpenGL
	    char *exten;
	    exten = (char *) glGetString(GL_VENDOR);
	    printf("%s\n",exten);
	    exten = (char *) glGetString(GL_RENDERER);
	    printf("%s\n",exten);
	    exten = (char *) glGetString(GL_VERSION);
	    printf("%s\n",exten);
        //printf("Extensions: %s\n",m_strExtensions.data());
    }

    //-----------------------------------------------------------------------
    IBufferBase* COGLRenderSystem::_CreateVB(ulong nSize, ulong nStride, ulong nFlags) const
    {
        // Buffer creation
        uint nVertexBuffer = 0;
        glGenBuffersARB(1, &nVertexBuffer);

        // Fill buffer
        glBindBufferARB(GL_ARRAY_BUFFER_ARB, nVertexBuffer);
        glBufferDataARB(GL_ARRAY_BUFFER_ARB, nSize * nStride, NULL, COGLEnum::BufferFlags(nFlags));

        return new COGLVertexBuffer(nSize, nVertexBuffer);
    }

    
    //-----------------------------------------------------------------------
    IBufferBase* COGLRenderSystem::_CreateIB(ulong nSize, ulong nStride, ulong nFlags) const
    {
        // Buffer creation
        uint nIndexBuffer = 0;
        glGenBuffersARB(1, &nIndexBuffer);

        // Fill buffer
        glBindBufferARB(GL_ARRAY_BUFFER_ARB, nIndexBuffer);
        glBufferDataARB(GL_ARRAY_BUFFER_ARB, nSize * nStride, NULL, COGLEnum::BufferFlags(nFlags));

        return new COGLIndexBuffer(nSize, nIndexBuffer);
    }

    //-----------------------------------------------------------------------
    IDeclaration* COGLRenderSystem::CreateDeclaration(const TDeclarationElement* Elements, size_t nCount) const
    {
        // OpenGL declaration creation
        COGLDeclaration* Declaration = new COGLDeclaration;

        // For each elements of the declaration, conversion in OGL element
        std::vector<int> Offset(nCount, 0);
        for (const TDeclarationElement* Elt = Elements; Elt < Elements + nCount; ++Elt)
        {
            // Build OGL element
            COGLDeclaration::TElement CurrentElement;
            CurrentElement.Usage  = Elt->Usage;
            CurrentElement.Type   = Elt->DataType;
            CurrentElement.Offset = Offset[Elt->Stream];

            // Addition of the element
	        Declaration->AddElement(Elt->Stream, CurrentElement);

            // Increment offset
            static const uint Size[] = {4, 8, 12, 16, 4};
            Offset[Elt->Stream] += Size[Elt->DataType];
        }

        return Declaration;
    }


    //-----------------------------------------------------------------------
    void COGLRenderSystem::_SetVB(uint nStream, const IBufferBase* pBuffer, ulong nStride, ulong nMinVertex, ulong nMaxVertex)
    {
        // Sending of the buffer OGL in the API
        const COGLVertexBuffer* pVertexBuffer = static_cast<const COGLVertexBuffer*>(pBuffer);
	    glBindBufferARB(GL_ARRAY_BUFFER_ARB, pVertexBuffer->GetBuffer());

        // Tables of correspondence
        static const uint Size[] = {1, 2, 3, 4, 4};
        static const uint Type[] = {GL_FLOAT, GL_FLOAT, GL_FLOAT, GL_FLOAT, GL_UNSIGNED_BYTE};

        // glPointer setup
        const COGLDeclaration::TElementArray& StreamDesc = m_CurrentDeclaration->GetStreamElements(nStream);
        for (COGLDeclaration::TElementArray::const_iterator i = StreamDesc.begin(); i != StreamDesc.end(); ++i)
        {
            switch (i->Usage)
            {
                // Position
                case ELT_USAGE_POSITION :
                    glEnableClientState(GL_VERTEX_ARRAY);
                    glVertexPointer(Size[i->Type], Type[i->Type], nStride, BUFFER_OFFSET(i->Offset + nMinVertex * nStride));
                    break;

                // Normal
                case ELT_USAGE_NORMAL :
                    glEnableClientState(GL_NORMAL_ARRAY);
                    glNormalPointer(Type[i->Type], nStride, BUFFER_OFFSET(i->Offset + nMinVertex * nStride));
                    break;

                // Diffuse color
                case ELT_USAGE_DIFFUSE :
                    glEnableClientState(GL_COLOR_ARRAY);
                    glColorPointer(Size[i->Type], Type[i->Type], nStride, BUFFER_OFFSET(i->Offset + nMinVertex * nStride));
                    break;

                // Texture coordinates 0
                case ELT_USAGE_TEXCOORD0 :
				    glActiveTextureARB(GL_TEXTURE0_ARB);
				    glEnable(GL_TEXTURE_2D);
				    glClientActiveTextureARB(GL_TEXTURE0_ARB);
				    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
                    glTexCoordPointer(Size[i->Type], Type[i->Type], nStride, BUFFER_OFFSET(i->Offset + nMinVertex * nStride));
                    break;

                // Texture coordinates 1
                case ELT_USAGE_TEXCOORD1 :
				    glActiveTextureARB(GL_TEXTURE0_ARB);
				    glEnable(GL_TEXTURE_2D);
				    glClientActiveTextureARB(GL_TEXTURE0_ARB);
				    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
                    glTexCoordPointer(Size[i->Type], Type[i->Type], nStride, BUFFER_OFFSET(i->Offset + nMinVertex * nStride));
                    break;

                // Texture coordinates 2
                case ELT_USAGE_TEXCOORD2 :
				    glActiveTextureARB(GL_TEXTURE0_ARB);
				    glEnable(GL_TEXTURE_2D);
				    glClientActiveTextureARB(GL_TEXTURE0_ARB);
				    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
                    glTexCoordPointer(Size[i->Type], Type[i->Type], nStride, BUFFER_OFFSET(i->Offset + nMinVertex * nStride));
                    break;

                // Texture coordinates 3
                case ELT_USAGE_TEXCOORD3 :
				    glActiveTextureARB(GL_TEXTURE0_ARB);
				    glEnable(GL_TEXTURE_2D);
				    glClientActiveTextureARB(GL_TEXTURE0_ARB);
				    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
                    glTexCoordPointer(Size[i->Type], Type[i->Type], nStride, BUFFER_OFFSET(i->Offset + nMinVertex * nStride));
                    break;
            }
        }
    }

    //-----------------------------------------------------------------------
    void COGLRenderSystem::_SetIB(const IBufferBase* pBuffer, ulong nStride)
    {
        const COGLIndexBuffer* pIndexBuffer = static_cast<const COGLIndexBuffer*>(pBuffer);

        // Sending of the buffer in the API
	    glEnableClientState(GL_INDEX_ARRAY);
	    glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, pIndexBuffer->GetBuffer());

        // Save the indices size
        m_nIndexStride = nStride;
    }

    //-----------------------------------------------------------------------
    void COGLRenderSystem::SetDeclaration(const IDeclaration* pDeclaration)
    {
        // Deactivation of the previous states
        glColor4f(1, 1, 1, 1);
	    glDisableClientState(GL_VERTEX_ARRAY);
	    glDisableClientState(GL_NORMAL_ARRAY);
	    glDisableClientState(GL_COLOR_ARRAY);
        for (int i = 0; i < 4; ++i)
        {
	        glActiveTextureARB(GL_TEXTURE0_ARB + i);
	        glDisable(GL_TEXTURE_2D);
        }

        // Save the declaration
        m_CurrentDeclaration = static_cast<const COGLDeclaration*>(pDeclaration);
    }

    //-----------------------------------------------------------------------
    void COGLRenderSystem::DrawPrimitives(TPrimitiveType Type, ulong nFirstVertex, ulong nCount) const
    {
        // Display of the primitives
        switch (Type)
        {
            case PT_TRIANGLELIST :  glDrawArrays(GL_TRIANGLES,      nFirstVertex, nCount * 3); break;
            case PT_TRIANGLESTRIP : glDrawArrays(GL_TRIANGLE_STRIP, nFirstVertex, nCount + 2); break;
            case PT_TRIANGLEFAN :   glDrawArrays(GL_TRIANGLE_FAN,   nFirstVertex, nCount + 1); break;
            case PT_LINELIST :      glDrawArrays(GL_LINES,          nFirstVertex, nCount * 2); break; 
            case PT_LINESTRIP :     glDrawArrays(GL_LINE_STRIP,     nFirstVertex, nCount + 1); break;
            case PT_POINTLIST :     glDrawArrays(GL_POINTS,         nFirstVertex, nCount);     break;
        }
    }

    //-----------------------------------------------------------------------
    void COGLRenderSystem::DrawIndexedPrimitives(TPrimitiveType Type, ulong nFirstIndex, ulong nCount) const
    {
        ulong nIndicesType = (m_nIndexStride == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT);
        const void*   Offset      = BUFFER_OFFSET(nFirstIndex * m_nIndexStride);

        // Display of the primitives
        switch (Type)
        {
            case PT_TRIANGLELIST :  glDrawElements(GL_TRIANGLES,      nCount * 3, nIndicesType, Offset); break;
            case PT_TRIANGLESTRIP : glDrawElements(GL_TRIANGLE_STRIP, nCount + 2, nIndicesType, Offset); break;
            case PT_TRIANGLEFAN :   glDrawElements(GL_TRIANGLE_FAN,   nCount + 1, nIndicesType, Offset); break;
            case PT_LINELIST :      glDrawElements(GL_LINES,          nCount * 2, nIndicesType, Offset); break; 
            case PT_LINESTRIP :     glDrawElements(GL_LINE_STRIP,     nCount + 1, nIndicesType, Offset); break;
            case PT_POINTLIST :     glDrawElements(GL_POINTS,         nCount,     nIndicesType, Offset); break;
        }
    }

    //-----------------------------------------------------------------------
    void COGLRenderSystem::PushMatrix(TMatrixType Type)
    {
        if (Type >= MAT_TEXTURE_0)
            glActiveTextureARB(GL_TEXTURE0_ARB + Type - MAT_TEXTURE_0);

        glMatrixMode(COGLEnum::Get(Type));
        glPushMatrix();
    }

    //-----------------------------------------------------------------------
    void COGLRenderSystem::PopMatrix(TMatrixType Type)
    {
        if (Type >= MAT_TEXTURE_0)
            glActiveTextureARB(GL_TEXTURE0_ARB + Type - MAT_TEXTURE_0);

        glMatrixMode(COGLEnum::Get(Type));
        glPopMatrix();
    }

    //-----------------------------------------------------------------------
    void COGLRenderSystem::LoadMatrix(TMatrixType Type, const CMatrix4& Matrix)
    {
        if (Type >= MAT_TEXTURE_0)
            glActiveTextureARB(GL_TEXTURE0_ARB + Type - MAT_TEXTURE_0);

        glMatrixMode(COGLEnum::Get(Type));
        glLoadMatrixf(Matrix);
    }

    //-----------------------------------------------------------------------
    void COGLRenderSystem::LoadMatrixMult(TMatrixType Type, const CMatrix4& Matrix)
    {
        if (Type >= MAT_TEXTURE_0)
            glActiveTextureARB(GL_TEXTURE0_ARB + Type - MAT_TEXTURE_0);

        glMatrixMode(COGLEnum::Get(Type));
        glMultMatrixf(Matrix);
    }

    //-----------------------------------------------------------------------
    void COGLRenderSystem::GetMatrix(TMatrixType Type, CMatrix4& Matrix) const
    {
        if (Type >= MAT_TEXTURE_0)
            glActiveTextureARB(GL_TEXTURE0_ARB + Type - MAT_TEXTURE_0);
		
        glGetFloatv(COGLEnum::GetMatrix(Type), Matrix);
    }

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

    //-----------------------------------------------------------------------
    ITextureBase* COGLRenderSystem::CreateTexture(const TVector2I& Size, TPixelFormat Format, ulong nFlags) const
    {
        uint nTexture;
        glGenTextures(1, &nTexture);

        glBindTexture(GL_TEXTURE_2D, nTexture);

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);

        // @todo ya pas un prob la 
        //int NbMipmaps = nFlags & TEX_NOMIPMAP ? GetNbMipLevels(Size.x, Size.y) : 0;
        int NbMipmaps = nFlags & TEX_NOMIPMAP ? 0 : GetNbMipLevels(Size.x, Size.y);

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, NbMipmaps);

        if ((NbMipmaps > 0) && (GetCapabilities()->HasCapability(CAP_HW_MIPMAPPING)))
            glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_TRUE);

        int Width  = Size.x;
        int Height = Size.y;
        for (int i = 0; i <= NbMipmaps; ++i)
        {
            glTexImage2D(GL_TEXTURE_2D, i, COGLEnum::Get(Format).Internal, Width, Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);

            if (Width > 1)  Width  /= 2;
            if (Height > 1) Height /= 2;
        }

        glBindTexture(GL_TEXTURE_2D, 0);

        return new COGLTexture(Size, Format, NbMipmaps > 0, GetCapabilities()->HasCapability(CAP_HW_MIPMAPPING), nTexture);
    }

    //-----------------------------------------------------------------------
    void COGLRenderSystem::SetTexture(uint nUnit, const ITextureBase* Texture) const
    {
        glActiveTextureARB(GL_TEXTURE0_ARB + nUnit);
        const COGLTexture* OGLTexture = static_cast<const COGLTexture*>(Texture);

        if (Texture)
        {
            glEnable(GL_TEXTURE_2D);
            glBindTexture(GL_TEXTURE_2D, OGLTexture->GetOGLTexture());
        }
        else
        {
            glDisable(GL_TEXTURE_2D);
            glBindTexture(GL_TEXTURE_2D, 0);
        }
    }

    //-----------------------------------------------------------------------
    void COGLRenderSystem::SetupAlphaBlending(TBlend Src, TBlend Dest) const
    {
        glBlendFunc(COGLEnum::Get(Src), COGLEnum::Get(Dest));
    }

    //-----------------------------------------------------------------------
    void COGLRenderSystem::SetupTextureUnit(uint nUnit, TTextureOp Op, TTextureArg Arg1, TTextureArg Arg2, const CColor& Constant) const
    {
        // Activate the unit texture
	    glEnable(GL_TEXTURE_2D);
        glActiveTextureARB(GL_TEXTURE0_ARB + nUnit);
        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);

        // Operators and arguments
        if (Op < TXO_ALPHA_FIRSTARG)
        {
            glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, COGLEnum::Get(Op));
            glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, COGLEnum::Get(Arg1));
            glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, COGLEnum::Get(Arg2));
            glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_EXT, GL_SRC_COLOR);
            glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_EXT, GL_SRC_COLOR);
        }
        else
        {
            glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_EXT, COGLEnum::Get(Op));
            glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_EXT, COGLEnum::Get(Arg1));
            glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_EXT, COGLEnum::Get(Arg2));
            glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_EXT, GL_SRC_ALPHA);
            glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA_EXT, GL_SRC_ALPHA);
        }

        // Constants
        if ((Arg1 == TXA_CONSTANT) || (Arg2 == TXA_CONSTANT))
        {
            float Color[4];
            Constant.ToFloat(Color);
            glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, Color);
        }
    }

    //-----------------------------------------------------------------------
    void COGLRenderSystem::Enable(TRenderParameter Param, bool bValue) const
    {
        switch (Param)
        {
            case RENDER_ZWRITE :
            {
                glDepthMask(bValue);
                break;
            }

            default :
            {
                if (bValue)
                    glEnable(COGLEnum::Get(Param));
                else
                    glDisable(COGLEnum::Get(Param));
            }
        }
    }

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

	//-----------------------------------------------------------------------
	void COGLRenderSystem::SetVertexShader(const IShaderBase* pShader)
	{
		const COGLShader* pOGLShader = static_cast<const COGLShader*>(pShader);

		if (pOGLShader)
		{
			if (pOGLShader->GetType() != SHADER_VERTEX)
				OGL_EXCEPT("Incorrect shader type (Pixel Shader)", "COGLRenderSystem::SetVertexShader");

			cgGLBindProgram(pOGLShader->GetProgram());
			cgGLEnableProfile(m_VSProfile);
		}
		else
		{
			cgGLDisableProfile(m_VSProfile);
			cgGLUnbindProgram(m_VSProfile);
		}
	}

	//-----------------------------------------------------------------------
	void COGLRenderSystem::SetPixelShader(const IShaderBase* pShader)
	{
		const COGLShader* pOGLShader = static_cast<const COGLShader*>(pShader);

		if (pOGLShader)
		{
			if (pOGLShader->GetType() != SHADER_PIXEL)
				OGL_EXCEPT("Incorrect shader type (Vertex Shader)", "COGLRenderSystem::SetPixelShader");

			cgGLBindProgram(pOGLShader->GetProgram());
			cgGLEnableProfile(m_PSProfile);
		}
		else
		{
			cgGLDisableProfile(m_PSProfile);
			cgGLUnbindProgram(m_PSProfile);
		}
	}

	//-----------------------------------------------------------------------
	void COGLRenderSystem::SetClearColor(const CColor& Color)
	{
		float rgba[4];
		Color.ToFloat(rgba);

		m_ClearColor = Color;

		glClearColor(rgba[0], rgba[1], rgba[2], rgba[3]);
	}

} // namespace Keos
