/******************************************************************************
 * Arachnoid Graphics Plugin for Mupen64Plus
 * https://github.com/mupen64plus/mupen64plus-video-arachnoid/
 *
 * Copyright (C) 2007 Kristofer Karlsson, Rickard Niklasson
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *****************************************************************************/

#include <algorithm>

#include "AdvancedCombinerManager.h"
#include "CachedTexture.h"
#include "DisplayListParser.h"
#include "FogManager.h"
#include "GBI.h"
#include "GBIDefs.h"
#include "Logger.h"
#include "MathLib.h"
#include "Memory.h"
#include "N64Games.h"
#include "OpenGL.h"
#include "OpenGL2DRenderer.h"
#include "OpenGLManager.h"
#include "OpenGLRenderer.h"
#include "RDP.h"
#include "RSP.h"
#include "RomDetector.h"
#include "TextureCache.h"
#include "VI.h"
#include "assembler.h"
#include "m64p.h"
#include "m64p_types.h"

using std::max;

//-----------------------------------------------------------------------------
//! Defines
//-----------------------------------------------------------------------------
#define MI_INTR_DP          0x00000020  //!< RDP Interrupt signal

//-----------------------------------------------------------------------------
//! Static Variables
//-----------------------------------------------------------------------------
Memory* RDP::m_memory  = 0;

//-----------------------------------------------------------------------------
//! Constructor
//-----------------------------------------------------------------------------
RDP::RDP()
{
    m_graphicsInfo       = 0;    
    m_changedTiles       = false;
    m_textureMode        = TM_NORMAL;
    m_loadType           = LOADTYPE_BLOCK;
    m_tmemChanged        = false;  
    m_textureLUT         = 0;   
    m_texRectWidth       = 1; 
    m_texRectHeight      = 1;
    m_displayListParser  = 0;
    m_primitiveZ         = 0;
    m_combinerMgr        = 0;
    m_textureLoader      = 0;
    m_openGL2DRenderer   = 0;
    m_screenUpdatePending= false;
}

//-----------------------------------------------------------------------------
//! Destructor
//-----------------------------------------------------------------------------
RDP::~RDP()
{
}

//-----------------------------------------------------------------------------
//* Initialize
//! Sets pointers to managers and initializes objects
//-----------------------------------------------------------------------------
bool RDP::initialize(GFX_INFO* graphicsInfo, RSP* rsp, Memory* memory, GBI* gbi, TextureCache* textureCache, VI* vi, DisplayListParser* displayListParser, FogManager* fogMgr)
{
    //Set pointers
    m_graphicsInfo      = graphicsInfo;
    m_rsp               = rsp;
    m_vi                = vi;
    m_memory            = memory;
    m_displayListParser = displayListParser;
    m_textureCache      = textureCache;
    m_fogMgr            = fogMgr;

    //Create combiner manager
    m_combinerMgr = new AdvancedCombinerManager();
    m_combinerMgr->initialize();

    //Create texture loader
    m_textureLoader = new TextureLoader();
    m_textureLoader->initialize(this, m_memory);

    //Create OpenGL 2D Renderer
    m_openGL2DRenderer = new OpenGL2DRenderer();
    m_openGL2DRenderer->initialize(vi);

    return true;
}

//-----------------------------------------------------------------------------
//* Dispose
//! Delets all allocated memory
//-----------------------------------------------------------------------------
void RDP::dispose()
{
    if ( m_combinerMgr      ) { delete m_combinerMgr;      m_combinerMgr      = 0; }
    if ( m_textureLoader    ) { delete m_textureLoader;    m_textureLoader    = 0; }
    if ( m_openGL2DRenderer ) { delete m_openGL2DRenderer; m_openGL2DRenderer = 0; }
}

//-----------------------------------------------------------------------------
//* Update States
//! Sets OpenGL states, updates combiner, activates textures, sets blender
//-----------------------------------------------------------------------------
void RDP::updateStates()
{
    //Depth Compare
    if (m_otherMode.depthCompare)
        glDepthFunc( GL_LEQUAL );
    else
        glDepthFunc( GL_ALWAYS );

    //Depth Update
    if (m_otherMode.depthUpdate)
        glDepthMask( true );
    else
        glDepthMask( false );

    // Depth Mode
    if (m_otherMode.depthMode == ZMODE_DEC)
    {
        glEnable( GL_POLYGON_OFFSET_FILL );
        glPolygonOffset( -3.0f, -3.0f );
    }
    else
    {
        glDisable( GL_POLYGON_OFFSET_FILL );
    }

    // Alpha Compare
    if ((m_otherMode.alphaCompare == G_AC_THRESHOLD) && !(m_otherMode.alphaCvgSel))
    {
        glEnable( GL_ALPHA_TEST );
        glAlphaFunc( (m_combinerMgr->getBlendColor()[3] > 0.0f) ? GL_GEQUAL : GL_GREATER, m_combinerMgr->getBlendColor()[3] );
    }
    // Used in TEX_EDGE and similar render modes
    else if (m_otherMode.cvgXAlpha)
    {
        glEnable( GL_ALPHA_TEST );
        glAlphaFunc( GL_GEQUAL, 0.5f );  // Arbitrary number -- gives nice results though
    }
    else
        glDisable( GL_ALPHA_TEST );

    //Combiner
    if ( m_updateCombiner )
    {
        if ( m_otherMode.cycleType == G_CYC_COPY)
        {
            m_combinerMgr->setMux(72057594037727865LL, m_otherMode.cycleType); //Only normal texturing                                                                    
            m_combinerMgr->selectCombine(m_otherMode.cycleType);
        }
        else if  ( m_otherMode.cycleType == G_CYC_FILL )
        {
            m_combinerMgr->setMux( 72057594037828926LL, m_otherMode.cycleType );
            m_combinerMgr->selectCombine(m_otherMode.cycleType);
        }
        else
        {
            m_combinerMgr->selectCombine(m_otherMode.cycleType);
        }
        m_updateCombiner = false;
        m_updateCombineColors = true;
    }

    if ( m_updateCombineColors )
    {
        m_combinerMgr->updateCombineColors();
        m_updateCombineColors = false;
    }

    //Texturing
    if ( m_changedTiles || m_tmemChanged || m_rsp->getTexturesChanged() ) 
    {
        m_combinerMgr->beginTextureUpdate();

        //Update Texture channel 0
        if ( m_combinerMgr->getUsesTexture0() )
        {
            //Enable texture 0
            m_textureCache->update(0);
            m_rsp->setTexturesChanged(false);
            m_changedTiles = false;
            m_tmemChanged = false;
        }
        else
        {
            //Disable texture 0
            glActiveTextureARB( GL_TEXTURE0_ARB + 0 );
            glDisable(GL_TEXTURE_2D);  
        }

        //Update Texture channel 1
        if (  m_combinerMgr->getUsesTexture1() )
        {
            //Enable texture 1
            m_textureCache->update(1);
            m_rsp->setTexturesChanged(false);
            m_changedTiles = false;
            m_tmemChanged = false;
        }
        else
        {
            //Disable textureing 1
            glActiveTextureARB( GL_TEXTURE0_ARB + 1 );
            glDisable(GL_TEXTURE_2D); 
        }

        m_combinerMgr->endTextureUpdate();
    }

    // Blending
    if (    (m_otherMode.forceBlender) &&    
            (m_otherMode.cycleType != G_CYC_COPY) &&
            (m_otherMode.cycleType != G_CYC_FILL) &&
            !(m_otherMode.alphaCvgSel))
    {
        glEnable( GL_BLEND );
        switch (m_otherMode.l >> 16)
        {
            case 0x0448: // Add
            case 0x055A:
                glBlendFunc( GL_ONE, GL_ONE );
                break;
            case 0x0C08: // 1080 Sky
            case 0x0F0A: // Used LOTS of places
                glBlendFunc( GL_ONE, GL_ZERO );
                break;
            case 0xC810: // Blends fog
            case 0xC811: // Blends fog
            case 0x0C18: // Standard interpolated blend
            case 0x0C19: // Used for antialiasing
            case 0x0050: // Standard interpolated blend
            case 0x0055: // Used for antialiasing
                glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
                break;
            case 0x0FA5: // Seems to be doing just blend color - maybe combiner can be used for this?
            case 0x5055: // Used in Paper Mario intro, I'm not sure if this is right...
                glBlendFunc( GL_ZERO, GL_ONE );
                break;
            default:
                glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
                break;
        }
    }
    else
        glDisable( GL_BLEND );

    if (m_otherMode.cycleType == G_CYC_FILL)
    {
        glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
        glEnable( GL_BLEND );
    }
}

//-----------------------------------------------------------------------------
//* Reset
//! Resets all states on RDP
//-----------------------------------------------------------------------------
void RDP::reset()
{
    setTextureFiltering( G_TF_POINT );
    setRenderMode(0);
}

//-----------------------------------------------------------------------------
//* Trigger Interrupt
//! Tell emulator that the RDP is idle
//-----------------------------------------------------------------------------
void RDP::triggerInterrupt()
{
    *(m_graphicsInfo->MI_INTR_REG) |= MI_INTR_DP;
    m_graphicsInfo->CheckInterrupts();
}

//-----------------------------------------------------------------------------
//* Set Alpha Compare Mode
//-----------------------------------------------------------------------------
void RDP::setAlphaCompareMode(unsigned int mode)
{
    m_otherMode.alphaCompare = mode;
    OpenGLManager::getSingleton().setAlphaTest( m_otherMode.alphaCompare != 0 );
}

//-----------------------------------------------------------------------------
//* Set Render Mode
//-----------------------------------------------------------------------------
void RDP::setRenderMode(unsigned int w1)
{
    unsigned int mode1 = (w1 & 0xCCCCFFFF);
    unsigned int mode2 = (w1 & 0x3333FFFF);

    m_otherMode.l &= 0x00000007;
    m_otherMode.l |= mode1 | mode2;
}

//#############################################################################
//  UCODE FUNCTIONS:
//#############################################################################

//*****************************************************************************
// Combiner Mode
//*****************************************************************************

//-----------------------------------------------------------------------------
//* Set Combine
//! Sets all combiner variables on combiner that tells it how to combine
//! the diffrent colors and textures.
//-----------------------------------------------------------------------------
void RDP::RDP_SetCombine(MicrocodeArgument* ucode)
{ 
    int mux0 = _SHIFTR( ucode->w0, 0, 24 );
    int mux1 = ucode->w1;
    m_combinerMgr->setMux(mux0, mux1, (G_CYCLE_TYPE)m_otherMode.cycleType);

    m_updateCombiner = true;    
}

//*****************************************************************************
// Colors
//*****************************************************************************

//-----------------------------------------------------------------------------
//* Set Environment Color
//! Sets Environment Color on combiner
//! @param r Red component of color (0.0 - 1.0)
//! @param g Green component of color (0.0 - 1.0)
//! @param b Blue component of color (0.0 - 1.0)
//! @param a Alpha component of color (0.0 - 1.0)
//-----------------------------------------------------------------------------
void RDP::RDP_SetEnvColor(float r, float g, float b, float a)
{ 
    m_combinerMgr->setEnvColor(r, g, b, a);
    m_updateCombineColors = true;
}

//-----------------------------------------------------------------------------
//* Set Prim Color
//! Sets Prim Color on combiner
//! @param r Red component of color (0.0 - 1.0)
//! @param g Green component of color (0.0 - 1.0)
//! @param b Blue component of color (0.0 - 1.0)
//! @param a Alpha component of color (0.0 - 1.0)
//! @param primLodMin
//! @param primLevel
//-----------------------------------------------------------------------------
void RDP::RDP_SetPrimColor(float r, float g, float b, float a, unsigned int primLodMin, unsigned int primLevel)
{ 
    int primLodFrac = max(primLevel, primLodMin);
    m_combinerMgr->setPrimLodMin(primLodMin);
    m_combinerMgr->setPrimLodFrac(primLodFrac / 255.0f);
    m_combinerMgr->setPrimColor(r, g, b, a);
    m_updateCombineColors = true;
}

//-----------------------------------------------------------------------------
//* Set Blend Color
//! Sets Blend Color on combiner
//! @param r Red component of color (0.0 - 1.0)
//! @param g Green component of color (0.0 - 1.0)
//! @param b Blue component of color (0.0 - 1.0)
//! @param a Alpha component of color (0.0 - 1.0)
//-----------------------------------------------------------------------------
void RDP::RDP_SetBlendColor(float r, float g, float b, float a)
{
    m_combinerMgr->setBlendColor(r, g, b, a);
    m_updateCombineColors = true;
}

//-----------------------------------------------------------------------------
//* Set Fill Color
//! Sets Fill Color on combiner
//! @param r Red component of color (0.0 - 1.0)
//! @param g Green component of color (0.0 - 1.0)
//! @param b Blue component of color (0.0 - 1.0)
//! @param a Alpha component of color (0.0 - 1.0)
//-----------------------------------------------------------------------------
void RDP::RDP_SetFillColor(float r, float g, float b, float a)
{ 
    m_combinerMgr->setFillColor(r, g, b, a);
    m_updateCombineColors = true;
}

//-----------------------------------------------------------------------------
//* Set Fog Color
//! Sets fog color used when rendering fog
//! @param r Red component of color (0.0 - 1.0)
//! @param g Green component of color (0.0 - 1.0)
//! @param b Blue component of color (0.0 - 1.0)
//! @param a Alpha component of color (0.0 - 1.0)
//-----------------------------------------------------------------------------
void RDP::RDP_SetFogColor(float r, float g, float b, float a)
{ 
    m_fogMgr->setFogColor(r, g, b, a);
}

//*****************************************************************************
// Misc
//*****************************************************************************

//-----------------------------------------------------------------------------
//! Set Other Mode
//-----------------------------------------------------------------------------
void RDP::RDP_SetOtherMode(MicrocodeArgument* ucode)
{          
    unsigned int mode0 = _SHIFTR( ucode->w0, 0, 24 );    
    unsigned int mode1 = ucode->w1;                    

    m_otherMode.h = mode0;
    m_otherMode.l = mode1;
}

//-----------------------------------------------------------------------------
//! Set Prim Depth
//-----------------------------------------------------------------------------
void RDP::RDP_SetPrimDepth(unsigned int dwZ, unsigned int dwDZ)
{  
    unsigned int primitiveDepth = dwZ & 0x7FFF;

    //Convert to float
    m_primitiveZ = (float)(primitiveDepth)/(float)0x8000;
 }

//-----------------------------------------------------------------------------
//! Set Scissor
//-----------------------------------------------------------------------------
void RDP::RDP_SetScissor(int x0, int y0, int x1, int y1, int mode)
{  
    //Get Scale
    float vsx = OpenGLManager::getSingleton().getViewScaleX();
    float vsy = OpenGLManager::getSingleton().getViewScaleY();
    
    //Get Offset
    int offset = 0; //TODO: height offset?

    //Set Scissor
    OpenGLManager::getSingleton().setScissor( 
               x0 * vsx, 
               (m_vi->getHeight() - y1) * vsy + offset,
               (x1 - x0) * vsx, 
               (y1 - y0) * vsy 
    );
}

//-----------------------------------------------------------------------------
//* Set Full Sync
//! Called when RDP is finished 
//-----------------------------------------------------------------------------
void RDP::RDP_FullSync()
{ 
    this->triggerInterrupt();
}

//*****************************************************************************
// Rendering
//*****************************************************************************

//-----------------------------------------------------------------------------
//* Fill Rect
//! Renders a rectangle
//-----------------------------------------------------------------------------
void RDP::RDP_FillRect(unsigned int x0, unsigned int y0, unsigned int x1, unsigned int y1)
{ 
    //Logger::getSingleton() << "RDP_FillRect: " << (int)x0 << " " << (int)y0 << " " << (int)x1 << " " << (int)y1 << "\n";

    //Increase rectangle size?
    if( m_otherMode.cycleType >= G_CYC_COPY )
    {
        x1++;
        y1++;
    }

    //Clear Depth Buffer?
    if ( m_depthImageInfo.rdramAddress == m_colorImageInfo.rdramAddress )
    {
        //Clear the Z Buffer
        updateStates();
        glDepthMask( true );
        glClear(GL_DEPTH_BUFFER_BIT);

        // Depth update
        if (m_otherMode.depthUpdate)
        {
            glDepthMask(GL_TRUE);
        }
        else
        {
            glDepthMask(GL_FALSE);
        }

        return;
    }

    //Clear Color Buffer?
    if ( m_otherMode.cycleType == G_CYC_FILL)
    {
        if ( x0 == 0 && y0 == 0 && x1 == m_vi->getWidth() && y1 == m_vi->getHeight() )
        {
            const float* fillColor = m_combinerMgr->getFillColor();
            glClearColor(fillColor[0], fillColor[1], fillColor[2], fillColor[3]);
            bool scissor = OpenGLManager::getSingleton().getScissorEnabled();
            OpenGLManager::getSingleton().setScissorEnabled(false);
            glClear(GL_COLOR_BUFFER_BIT);
            OpenGLManager::getSingleton().setScissorEnabled(scissor);
            return;
        }
    }

    //Update States
    this->updateStates();   

    //Ignore fill rects?
    if ( ROMDetector::getSingleton().getIgnoreFillRects() ) 
    {
        return;
    }

    //Disable Scissor
    glDisable( GL_SCISSOR_TEST );

    //Set Viewport
    //int oldViewport[4];
    //glGetIntegerv(GL_VIEWPORT, oldViewport);
    //glViewport(0, 0, OpenGLManager::getSingleton().getWidth(), OpenGLManager::getSingleton().getHeight() ); 
    glDepthRange(0.0f, 1.0f);

    //Get depth and color
    float depth = m_otherMode.depthSource == 1 ? m_primitiveZ : 0;  //TODO: Use RSP viewport nearz?
    float* color = m_otherMode.cycleType == G_CYC_FILL ? m_combinerMgr->getFillColor() : m_combinerMgr->getPrimColor();

    //Render rectangle
    m_openGL2DRenderer->renderQuad(color, x0, y0, x1, y1, depth);

    //Reset viewport
    //glViewport(oldViewport[0], oldViewport[1], oldViewport[2], oldViewport[3]);     

    //Reset Scissor
    glEnable( GL_SCISSOR_TEST );
}

//-----------------------------------------------------------------------------
// Texture Rectangle Flip
//-----------------------------------------------------------------------------
void RDP::RDP_TexRectFlip(unsigned int dwXH, unsigned int dwYH, unsigned int dwXL, unsigned int dwYL, 
                          unsigned int tileno, unsigned int dwS, unsigned int dwT, int nDSDX, int nDTDY)
{   
    Logger::getSingleton().printMsg("RDP_TexRect");

    float fS0 = (float)dwS / 32.0f;
    float fT0 = (float)dwT / 32.0f;

    float fDSDX = (float)nDSDX / 1024.0f;
    float fDTDY = (float)nDTDY / 1024.0f;

    if (m_otherMode.cycleType == G_CYC_COPY)
    {
        fDSDX /= 4.0f;    // In copy mode 4 pixels are copied at once.
        dwXH++;
        dwYH++;
    }
    else if (m_otherMode.cycleType == G_CYC_FILL )
    {
        dwXH++;
        dwYH++;
    }

    float fS1 = fS0 + (fDSDX * (dwYH - dwYL));
    float fT1 = fT0 + (fDTDY * (dwXH - dwXL));

    //Set Current Texture tiles
    m_rsp->setTile( m_textureLoader->getTile(tileno), 0);
    if ( tileno < 7 ) 
    {
        m_rsp->setTile( m_textureLoader->getTile(tileno + 1), 1);
    }
    else {
        m_rsp->setTile( m_textureLoader->getTile(tileno), 1);
    }

    m_texRectWidth = (unsigned int)fS1;
    m_texRectHeight = (unsigned int)fT1;

    //Update States
    this->updateStates();

    float t0u0 = 0, t0v0 = 0, t0u1 =0, t0v1 = 0;
    if ( m_textureCache->getCurrentTexture(0) )
    {
        t0u0 = (fS0) * m_textureCache->getCurrentTexture(0)->shiftScaleS - m_textureLoader->getTile(tileno)->uls;
        t0v0 = (fT0) * m_textureCache->getCurrentTexture(0)->shiftScaleT - m_textureLoader->getTile(tileno)->ult;
        t0u1 = t0u0 + (fDSDX * (dwYH - dwYL))*m_textureCache->getCurrentTexture(0)->shiftScaleS;
        t0v1 = t0v0 + (fDTDY * (dwXH - dwXL))*m_textureCache->getCurrentTexture(0)->shiftScaleT;
    }

    _textureRectangleFlip(dwXL, dwYL, dwXH, dwYH, t0u0, t0v0, t0u1, t0v1, tileno);
        
    //Restore RSP Tile
    int rspTile = m_rsp->getTexture().tile;
    m_rsp->setTile( m_textureLoader->getTile(tileno), 0);
    m_rsp->setTile( m_textureLoader->getTile(rspTile < 7 ? rspTile + 1 : rspTile), 1);
}

//-----------------------------------------------------------------------------
//* Texture Rect
//! Not this command use 128bits and not 64 bits wich could cause some
//! problems with the program counter.
//-----------------------------------------------------------------------------
void RDP::RDP_TexRect(unsigned int dwXH, unsigned int dwYH, unsigned int dwXL, unsigned int dwYL, 
                      unsigned int tileno, unsigned short dwS, unsigned short dwT, unsigned short nDSDX, unsigned short nDTDY)
{ 
    Logger::getSingleton().printMsg("RDP_TexRect");    

    glEnable(GL_TEXTURE_2D);

    //Convert to signed
    short s16S = *(short*)(&dwS);
    short s16T = *(short*)(&dwT);
    short s16DSDX  = *(short*)(&nDSDX);
    short s16DTDY    = *(short*)(&nDTDY);

    //Convert to float
    float s = s16S / 32.0f;
    float t = s16T / 32.0f;
    float dsdx = s16DSDX / 1024.0f;
    float dtdy = s16DTDY / 1024.0f;


    float ulx = (float)dwXH;
    float uly = (float)dwYH;
    float lrx = (float)dwXL;
    float lry = (float)dwYL;

    int tile = tileno;

    _textureRectangle(ulx, uly, lrx, lry, tile, s, t, dsdx, dtdy, false);
}

//*****************************************************************************
// Texturing
//*****************************************************************************

//-----------------------------------------------------------------------------
//* Set Color Image
//! Sets information about color image
//-----------------------------------------------------------------------------
void RDP::RDP_SetCImg(unsigned int format, unsigned int size, unsigned int width, unsigned int segmentAddress)
{ 
    m_colorImageInfo.rdramAddress = m_memory->getRDRAMAddress( segmentAddress );
    m_colorImageInfo.format       = format;
    m_colorImageInfo.size         = size;
    m_colorImageInfo.width        = width + 1; //Note: add plus one
    m_colorImageInfo.bpl          = m_colorImageInfo.width << m_colorImageInfo.size >> 1;
    
    if (m_screenUpdatePending)
    {
        OpenGLManager::getSingleton().endRendering();
        m_screenUpdatePending = false;
    }
}

//-----------------------------------------------------------------------------
//* Set Z Image
//! Sets information about depth image
//-----------------------------------------------------------------------------
void RDP::RDP_SetZImg(unsigned int format, unsigned int size, unsigned int width, unsigned int segmentAddress)
{ 
    m_depthImageInfo.rdramAddress = m_memory->getRDRAMAddress( segmentAddress );
    m_depthImageInfo.format       = format;
    m_depthImageInfo.size         = size;
    m_depthImageInfo.width        = width + 1; //Note: add plus one
    m_depthImageInfo.bpl          = m_colorImageInfo.width << m_colorImageInfo.size >> 1;
}

//-----------------------------------------------------------------------------
//* Set Texture Image
//! Sets information about texture image
//-----------------------------------------------------------------------------
void RDP::RDP_SetTImg(unsigned int format, unsigned int size, unsigned int width, unsigned int segmentAddress)
{ 
    m_textureLoader->setTextureImage(format, size, width, segmentAddress);
}

//-----------------------------------------------------------------------------
// Set Tile
//-----------------------------------------------------------------------------
void RDP::RDP_SetTile(int format, int size, int line, int tmem, int tile, 
                      int palette, int clampS, int clampT, int mirrorS, 
                      int mirrorT, int maskS, int maskT, int shiftS, int shiftT )
{ 
    //Set Tile
    m_textureLoader->setTile( format, size, line, tmem, tile, palette, 
                              clampS, clampT, mirrorS, mirrorT, maskS, maskT,
                              shiftS, shiftT );

    //m_changedTiles = true;    ??? Not needed?
}

//-----------------------------------------------------------------------------
//* Load Tile
//-----------------------------------------------------------------------------
void RDP::RDP_LoadTile(int tile, int s0, int t0, int s1, int t1)
{ 
    //Load Tile
    m_textureLoader->loadTile(tile, s0, t0, s1, t1);

    m_textureMode = TM_NORMAL;
    m_loadType    = LOADTYPE_TILE;
    m_tmemChanged = true;    
}    

//-----------------------------------------------------------------------------
// Load Block
//-----------------------------------------------------------------------------
void RDP::RDP_LoadBlock(int tile, int s0, int t0, int s1, int t1)
{  
    //Load Block
    m_textureLoader->loadBlock(tile, s0, t0, s1, t1);

    m_textureMode = TM_NORMAL;
    m_loadType    = LOADTYPE_BLOCK;
    m_tmemChanged = true;
}

//-----------------------------------------------------------------------------
//! Sets the size of tile
//! @Param tile Index of the tile to set size on
//! @param s0 Texture Coordinats for first vertex coordinate
//! @param t0 Texture Coordinats for first vertex coordinate
//! @param s1 Texture Coordinats for second vertex coordinate
//! @param t1 Texture Coordinats for second vertex coordinate
//-----------------------------------------------------------------------------
void RDP::RDP_SetTileSize(int tile, unsigned int s0, unsigned int t0, unsigned int s1, unsigned int t1)
{  
    m_textureLoader->setTileSize( tile, s0, t0, s1, t1);

    m_changedTiles = true;    
}

//-----------------------------------------------------------------------------
// Load Texture Look Up Table
//-----------------------------------------------------------------------------
void RDP::RDP_LoadTLUT(int tile, int s0, int t0, int s1, int t1)
{   
    m_textureLoader->loadTLUT(tile, s0, t0, s1, t1);

    m_tmemChanged = true;
}

//*****************************************************************************
// Private Functions
//*****************************************************************************

//-----------------------------------------------------------------------------
// Texture Rectangle
//-----------------------------------------------------------------------------
void RDP::_textureRectangle(float ulx, float uly, float lrx, float lry, int tile, float s, float t, float dsdx, float dtdy,bool flip)
{
    bool zEnabled = OpenGLManager::getSingleton().getZBufferEnabled();
    OpenGLManager::getSingleton().setZBufferEnabled(false);

    //Copy Mode
    if (  m_otherMode.cycleType == G_CYC_COPY )
    {
        dsdx = 1.0;
        lrx += 1.0f;
        lry += 1.0f;
    }
    else if (m_otherMode.cycleType == G_CYC_FILL )
    {
        lrx++;
        lry++;
    }

    //Set Current Texture tiles
    m_rsp->setTile( m_textureLoader->getTile(tile), 0);
    m_rsp->setTile( m_textureLoader->getTile(tile < 7 ? tile + 1 : tile), 1);


    float lrs = s + (lrx - ulx - 1) * dsdx;
    float lrt = t + (lry - uly - 1) * dtdy;

    //Change mode to texture rectangle
    if ( m_textureMode == TM_NORMAL )
        m_textureMode = TM_TEXRECT;

    m_texRectWidth  = (unsigned int)(max( lrs, s ) + dsdx);
    m_texRectHeight = (unsigned int)(max( lrt, t ) + dtdy);

    //Update States
    this->updateStates();

    //glViewport( 0, 0, OpenGLManager::getSingleton().getWidth(), OpenGLManager::getSingleton().getHeight() );

    glDisable(GL_SCISSOR_TEST);

    if (lrs > s)
    {
        if (lrt > t)
            OpenGLRenderer::getSingleton().renderTexRect( ulx, uly, lrx, lry, s, t, lrs, lrt, flip );
        else
            OpenGLRenderer::getSingleton().renderTexRect( ulx, lry, lrx, uly, s, lrt, lrs, t, flip );
    }
    else
    {
        if (lrt > t)
            OpenGLRenderer::getSingleton().renderTexRect( lrx, uly, ulx, lry, lrs, t, s, lrt, flip );
        else
            OpenGLRenderer::getSingleton().renderTexRect( lrx, lry, ulx, uly, lrs, lrt, s, t, flip );
    }

    //Restore RSP Tile
    int rspTile = m_rsp->getTexture().tile;
    m_rsp->setTile( m_textureLoader->getTile( rspTile ), 0);
    m_rsp->setTile( m_textureLoader->getTile(rspTile < 7 ? rspTile + 1 : rspTile), 1);

    //glViewport( 0, m_windowMgr->getHeightOffset(), OpenGLManager::getSingleton().getWidth(), OpenGLManager::getSingleton().getHeight() );

    glEnable(GL_SCISSOR_TEST);
    OpenGLManager::getSingleton().setZBufferEnabled(zEnabled);
}

//-----------------------------------------------------------------------------
//* Texture Rectangle Flip
//! @todo: Clamp Tile
//-----------------------------------------------------------------------------
void RDP::_textureRectangleFlip(int nX0, int nY0, int nX1, int nY1, float fS0, float fT0, float fS1, float fT1, int tile)
{
    //Disable z buffer
    bool zEnabled = OpenGLManager::getSingleton().getZBufferEnabled();
    OpenGLManager::getSingleton().setZBufferEnabled(false);

    float widthDiv = (float)m_textureLoader->getTile( m_rsp->getTexture().tile )->getWidth();
    float heightDiv = (float)m_textureLoader->getTile( m_rsp->getTexture().tile )->getHeight();

    float t0u0 = fS0 / widthDiv;
    float t0v0 = fT0 / heightDiv;
    float t0u1 = (fS1 - fS0)/ widthDiv + t0u0;
    float t0v1 = (fT1 - fT0)/ heightDiv + t0v0;

    float depth = m_otherMode.depthSource == 1 ? m_primitiveZ : 0;  //TODO: Use RSP viewport nearz?

    static bool warned = false;
    if( t0u0 >= 0 && t0u1 <= 1 && t0u1 >= t0u0 ) 
    {
        //TODO: Clamp Tile
        if (!warned)
        {
            warned = true;
            Logger::getSingleton().printMsg("_textureRectangleFlip - unimplemented", M64MSG_WARNING);
        }
    }
    if( t0v0 >= 0 && t0v1 <= 1 && t0v1 >= t0v0 ) 
    {
        //TODO: Clamp tile
        if (!warned)
        {
            warned = true;
            Logger::getSingleton().printMsg("_textureRectangleFlip - unimplemented", M64MSG_WARNING);
        }
    }

    //HACK 
    if ( ROMDetector::getSingleton().getRomID() == SUPER_MARIO_64 )
    {
        t0u0 *= 0.5f;
        t0v0 *= 0.5f;    
        t0u1 *= 0.5f;    
        t0v1 *= 0.5f;
    }

    //glViewport( 0, m_windowMgr->getHeightOffset(), OpenGLManager::getSingleton().getWidth(), OpenGLManager::getSingleton().getHeight() );

    //Get Color
    float color[4] = { 1,1,1,0 }; 
    this->getCombinerMgr()->getCombinerColor(    &color[0] );
    float secondaryColor[4] = { 1,1,1,1 };

    if (  m_otherMode.cycleType == G_CYC_COPY )
    {
        glActiveTextureARB( GL_TEXTURE0_ARB );
        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
    }

    //Disable Scissor
    glDisable( GL_SCISSOR_TEST );

    //Render Quad
    m_openGL2DRenderer->renderFlippedTexturedQuad( color, secondaryColor,
                                                   (float)nX0, (float)nY0,
                                                   (float)nX1, (float)nY1,
                                                   depth,
                                                   t0u0, t0v0,
                                                   t0u1, t0v1, 
                                                   t0u0, t0v0,
                                                   t0u1, t0v1 );

    //Restore states
    glEnable(GL_SCISSOR_TEST);
    OpenGLManager::getSingleton().setZBufferEnabled(zEnabled);
}
