/*
Copyright (C) 2002 Rice1964

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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

#include "osal_opengl.h"
#include "FrameBuffer.h"
#include "Render.h"

extern uObjMtxReal gObjMtxReal;
extern Matrix g_MtxReal;

//========================================================================

void CRender::LoadFrameBuffer(bool useVIreg, uint32_t left, uint32_t top, uint32_t width, uint32_t height)
{
    uint32_t VIwidth = *gfx_info.VI_WIDTH_REG;
    uint32_t *rdram_u32 = (uint32_t*)gfx_info.RDRAM;

    TxtrInfo gti;

    gti.clampS = gti.clampT = 0;
    gti.maskS = gti.maskT = gti.mirrorS = gti.mirrorT = 0;
    gti.TLutFmt = TLUT_FMT_RGBA16;  //RGBA16
    gti.bSwapped    = false;
    gti.Palette = 0;

    if( useVIreg && *gfx_info.VI_ORIGIN_REG > VIwidth*2 )
    {
        gti.Format  = 0;
        gti.Size    = 2;

        gti.Address = (*gfx_info.VI_ORIGIN_REG & (g_dwRamSize-1) ) - VIwidth*2;
        gti.LeftToLoad  = 0;
        gti.TopToLoad   = 0;

        gti.PalAddress = (uint8_t *) &g_wRDPTlut[0];

        gti.WidthToCreate   = windowSetting.uViWidth;;
        gti.HeightToCreate  = windowSetting.uViHeight;

        if( gti.WidthToCreate == 0 || gti.HeightToCreate == 0 )
        {
            TRACE0("Loading frame buffer: size = 0 x 0");
            return;
        }

        gti.Pitch   = VIwidth << gti.Size >> 1;
    }
    else
    {
        gti.Format  = g_CI.dwFormat;
        gti.Size    = g_CI.dwSize;
        gti.PalAddress = (uint8_t *) &g_wRDPTlut[0];

        gti.Address = RSPSegmentAddr(g_CI.dwAddr);

        if( width == 0 || height == 0 )
        {
            gti.LeftToLoad      = 0;
            gti.TopToLoad       = 0;

            gti.WidthToCreate       = g_CI.dwWidth;
            gti.HeightToCreate      = g_CI.dwWidth*3/4;
        }
        else
        {
            gti.LeftToLoad      = left;
            gti.TopToLoad       = top;

            gti.WidthToCreate   = width;
            gti.HeightToCreate  = height;
        }

        if( gti.Size == G_IM_SIZ_4b )
            gti.Pitch = g_CI.dwWidth >> 1;
        else
            gti.Pitch = g_CI.dwWidth << (gti.Size-1);
    }


    if( gti.Address + gti.Pitch*gti.HeightToCreate > g_dwRamSize )
    {
        TRACE0("Skip frame buffer loading, memory out of bound");
        return;
    }

#ifdef DEBUGGER
    if( pauseAtNext )
    {
        DebuggerAppendMsg("Load Frame Buffer Imag at: %08X, (%d, %d) - (%d, %d)", gti.Address,
            gti.LeftToLoad, gti.TopToLoad, gti.WidthToCreate, gti.HeightToCreate );
    }
#endif


    gti.HeightToLoad = gti.HeightToCreate;
    gti.WidthToLoad = gti.WidthToCreate;

    gti.pPhysicalAddress = ((uint8_t*)rdram_u32) + gti.Address;
    gti.tileNo = -1;
    TxtrCacheEntry *pEntry = gTextureManager.GetTexture(&gti, false, true, false);

    if( pEntry )
        SetCurrentTexture( 0, pEntry->pTexture, pEntry->ti.WidthToCreate, pEntry->ti.HeightToCreate, pEntry);
}

void CRender::LoadTextureFromMemory(void *buf, uint32_t left, uint32_t top, uint32_t width, uint32_t height, uint32_t pitch, uint32_t format)
{
    TxtrInfo gti;
    gti.Format  = g_CI.dwFormat;
    gti.Size    = g_CI.dwSize;
    gti.Palette = 0;
    gti.TLutFmt = TLUT_FMT_RGBA16;  //RGBA16
    gti.PalAddress = 0;
    gti.bSwapped = false;
    gti.Address = 0;
    gti.LeftToLoad = 0;
    gti.TopToLoad = 0;
    gti.WidthToCreate = width;
    gti.HeightToCreate = height;

    gti.Pitch   = pitch;

    gti.HeightToLoad = height;
    gti.WidthToLoad = width;
    gti.pPhysicalAddress = (uint8_t*)buf;

    gti.tileNo = -1;
    TxtrCacheEntry *pEntry = gTextureManager.GetTexture(&gti, false, true, false);
    //Upto here, the texture is loaded wrong because the format is wrong

    DrawInfo info;  
    if( pEntry->pTexture->StartUpdate(&info) )
    {
        for( uint32_t i=0; i<height; i++)
        {
            uint32_t *psrc = (uint32_t*)((uint8_t*)buf+pitch*(i+top))+left;
            uint32_t *pdst = (uint32_t*)((uint8_t*)info.lpSurface+i*info.lPitch);
            for( uint32_t j=0; j<width; j++)
            {
                pdst[j]=psrc[j];
            }
        }
        pEntry->pTexture->EndUpdate(&info);
    }
    SetCurrentTexture( 0, pEntry->pTexture, width, height, pEntry);
}

void CRender::LoadObjBGCopy(uObjBg &info)
{
   uint32_t *rdram_u32 = (uint32_t*)gfx_info.RDRAM;
    TxtrInfo gti;
    gti.Format      = info.imageFmt;
    gti.Size        = info.imageSiz;
    gti.Address     = RSPSegmentAddr(info.imagePtr);
    gti.LeftToLoad  = 0;
    gti.TopToLoad   = 0;
    gti.Palette     = info.imagePal;

    gti.PalAddress  = (uint8_t *) &g_wRDPTlut[0];
    gti.bSwapped    = false;
    gti.TLutFmt     = TLUT_FMT_RGBA16;  //RGBA16

    gti.WidthToCreate   = info.imageW/4;
    gti.HeightToCreate  = info.imageH/4;

    if( options.bEnableHacks )
    {
        if( g_CI.dwWidth == 0x200 && gti.Format == g_CI.dwFormat && gti.Size == g_CI.dwSize &&
            gti.WidthToCreate == 0x200 )
        {
            // Hack for RE2
            uint32_t w = *gfx_info.VI_WIDTH_REG & 0xFFF;
            gti.HeightToCreate = (gti.WidthToCreate*gti.HeightToCreate)/w;
            gti.WidthToCreate = w;
        }
    }

    gti.Pitch   = gti.WidthToCreate << gti.Size >> 1;
    gti.Pitch   = (gti.Pitch>>3)<<3;    // Align to 8 bytes

    if( gti.Address + gti.Pitch*gti.HeightToCreate > g_dwRamSize )
    {
        TRACE0("Skip BG copy loading, memory out of bound");
        return;
    }

    gti.HeightToLoad = gti.HeightToCreate;
    gti.WidthToLoad = gti.WidthToCreate;
    gti.pPhysicalAddress = ((uint8_t*)rdram_u32)+gti.Address;
    gti.tileNo = -1;
    TxtrCacheEntry *pEntry = gTextureManager.GetTexture(&gti, false, true, false);
    SetCurrentTexture(0,pEntry);
}

void CRender::LoadTxtrBufIntoTexture(void)
{
    TxtrInfo gti;
   uint32_t *rdram_u32 = (uint32_t*)gfx_info.RDRAM;

    gti.Format  = g_pRenderTextureInfo->CI_Info.dwFormat;
    gti.Size    = g_pRenderTextureInfo->CI_Info.dwSize;

    gti.Address = RSPSegmentAddr(g_pRenderTextureInfo->CI_Info.dwAddr);
    gti.LeftToLoad      = 0;
    gti.TopToLoad       = 0;
    gti.Palette = 0;

    gti.PalAddress = (uint8_t *) &g_wRDPTlut[0];
    gti.bSwapped    = false;

    gti.WidthToCreate       = g_pRenderTextureInfo->N64Width;
    gti.HeightToCreate      = g_pRenderTextureInfo->N64Height;
    gti.TLutFmt = TLUT_FMT_RGBA16;  //RGBA16

    gti.Pitch   = gti.WidthToCreate << (gti.Size-1);

    gti.HeightToLoad = gti.HeightToCreate;
    gti.WidthToLoad = gti.WidthToCreate;
    gti.pPhysicalAddress = ((uint8_t*)rdram_u32)+gti.Address;
    gti.tileNo = -1;
    TxtrCacheEntry *pEntry = gTextureManager.GetTexture(&gti, false, true, false);
    SetCurrentTexture(0,pEntry);
}

void CRender::LoadSprite2D(Sprite2DInfo &info, uint32_t ucode)
{
    TxtrInfo gti;
   uint8_t *rdram_u8 = (uint8_t*)gfx_info.RDRAM;
   uint32_t *rdram_u32 = (uint32_t*)gfx_info.RDRAM;

    gti.Format  = info.spritePtr->SourceImageType;
    gti.Size    = info.spritePtr->SourceImageBitSize;

    gti.Address = RSPSegmentAddr(info.spritePtr->SourceImagePointer);
    gti.Palette = 0;
    gti.PalAddress = (uint8_t *) (rdram_u8 + RSPSegmentAddr(info.spritePtr->TlutPointer));

    if( options.enableHackForGames == HACK_FOR_NITRO )
    {
        gti.WidthToCreate   = (uint32_t)(info.spritePtr->SubImageWidth/info.scaleX);
        gti.HeightToCreate  = (uint32_t)(info.spritePtr->SubImageHeight/info.scaleY);
        gti.LeftToLoad      = (uint32_t)(info.spritePtr->SourceImageOffsetS/info.scaleX);
        gti.TopToLoad       = (uint32_t)(info.spritePtr->SourceImageOffsetT/info.scaleY);
        gti.Pitch   = info.spritePtr->Stride << gti.Size >> 1;
        gti.Pitch   = (uint32_t)(gti.Pitch*info.scaleY);
    }
    else
    {
        gti.WidthToCreate   = info.spritePtr->SubImageWidth;
        gti.HeightToCreate  = info.spritePtr->SubImageHeight;
        gti.LeftToLoad      = info.spritePtr->SourceImageOffsetS;
        gti.TopToLoad       = info.spritePtr->SourceImageOffsetT;
        gti.Pitch   = info.spritePtr->Stride << gti.Size >> 1;
    }

    /* SubImageWidth/Height are signed shorts and, in the HACK_FOR_NITRO
     * path, are divided by a scale factor before being cast to unsigned.
     * A degenerate sprite (e.g. Wipeout 64's exploding-car effect, which
     * emits a SubImageWidth that works out to -2) would wrap to a huge
     * width/height here and then be walked as a buffer extent by
     * CalculateRDRAMCRC/CalculateMaxCI/the decompressor, reading far off
     * the end of RDRAM and crashing. Reject non-positive dimensions. */
    if( (int)gti.WidthToCreate <= 0 || (int)gti.HeightToCreate <= 0 )
    {
        TRACE0("Skip Sprite image, degenerate dimensions");
        return;
    }

    if( gti.Address + gti.Pitch*gti.HeightToCreate > g_dwRamSize )
    {
        TRACE0("Skip Sprite image decompress, memory out of bound");
        return;
    }

    gti.HeightToLoad = gti.HeightToCreate;
    gti.WidthToLoad = gti.WidthToCreate;

    gti.TLutFmt     = TLUT_FMT_RGBA16;  //RGBA16
    gti.bSwapped    = false;

    gti.pPhysicalAddress = ((uint8_t*)rdram_u32) + gti.Address;
    gti.tileNo = -1;
    TxtrCacheEntry *pEntry = gTextureManager.GetTexture(&gti, false, true, false);
    SetCurrentTexture(0,pEntry);

}


void CRender::DrawSprite2D(Sprite2DInfo &info, uint32_t ucode)
{
    if( !status.bCIBufferIsRendered )
        g_pFrameBufferManager->ActiveTextureBuffer();

    if( status.bHandleN64RenderTexture )
    {
        g_pRenderTextureInfo->maxUsedHeight = g_pRenderTextureInfo->N64Height;
        if( !status.bDirectWriteIntoRDRAM ) 
        {
            status.bFrameBufferIsDrawn = true;
            status.bFrameBufferDrawnByTriangles = true;
        }
    }
    LoadSprite2D(info, ucode);

    info.scaleX = 1/info.scaleX;
    info.scaleY = 1/info.scaleY;

    int x0, y0, x1, y1;
    float t0, s0, t1, s1;

    if( info.flipX )
    {
        //x0 = info.px*info.scaleX + info.spritePtr->SubImageWidth*info.scaleX;
        //x1 = info.px*info.scaleX;
        x0 = info.px + int(info.spritePtr->SubImageWidth*info.scaleX);
        x1 = info.px;
    }
    else
    {
        //x0 = info.px*info.scaleX;
        //x1 = info.px*info.scaleX + info.spritePtr->SubImageWidth*info.scaleX;
        x0 = info.px;
        x1 = info.px + int(info.spritePtr->SubImageWidth*info.scaleX);
    }

    if( info.flipY )
    {
        //y0 = info.py*info.scaleY + info.spritePtr->SubImageHeight*info.scaleY;
        //y1 = info.py*info.scaleY;
        y0 = info.py + int(info.spritePtr->SubImageHeight*info.scaleY);
        y1 = info.py;
    }
    else
    {
        //y0 = info.py*info.scaleY;
        //y1 = info.py*info.scaleY + info.spritePtr->SubImageHeight*info.scaleY;
        y0 = info.py;
        y1 = info.py + int(info.spritePtr->SubImageHeight*info.scaleY);
    }

    t0 = s0 = 0;
    if( options.enableHackForGames == HACK_FOR_NITRO )
    {
        t1 = info.spritePtr->SubImageWidth*info.scaleX/g_textures[0].m_fTexWidth;
        s1 = info.spritePtr->SubImageHeight*info.scaleY/g_textures[0].m_fTexHeight;
    }
    else
    {
        t1 = info.spritePtr->SubImageWidth/g_textures[0].m_fTexWidth;
        s1 = info.spritePtr->SubImageHeight/g_textures[0].m_fTexHeight;
    }

    //InitCombinerBlenderForSimpleTextureDraw(0);
    SetCombinerAndBlender();
    SetAddressUAllStages( 0, TEXTURE_UV_FLAG_CLAMP );
    SetAddressVAllStages( 0, TEXTURE_UV_FLAG_CLAMP );

    COLOR speColor = PostProcessSpecularColor();
    COLOR difColor = PostProcessDiffuseColor(0xffffffff);

    float depth = ( gRDP.otherMode.depth_source == 1 ) ? gRDP.fPrimitiveDepth : 0;
    DrawSimple2DTexture((float)x0, (float)y0, (float)x1, (float)y1, t0, s0, t1, s1, speColor, difColor, depth, 1.0f);
}


void CRender::DrawSpriteR(uObjTxSprite &sprite, bool initCombiner, uint32_t tile, uint32_t left, uint32_t top, uint32_t width, uint32_t height)   // With Rotation
{
    if( !status.bCIBufferIsRendered )
        g_pFrameBufferManager->ActiveTextureBuffer();

    if( status.bHandleN64RenderTexture )
    {
        g_pRenderTextureInfo->maxUsedHeight = g_pRenderTextureInfo->N64Height;
        if( !status.bDirectWriteIntoRDRAM ) 
        {
            status.bFrameBufferIsDrawn = true;
            status.bFrameBufferDrawnByTriangles = true;
        }
    }
    SetCombinerAndBlender();

    float scaleX = sprite.sprite.scaleW/1024.0f;
    float scaleY = sprite.sprite.scaleH/1024.0f;

    if( width == 0 || height == 0 )
    {
        width = g_textures[tile].m_dwTileWidth;
        height = g_textures[tile].m_dwTileHeight;
    }

    //RECT src = {left,top,width, height};
    float depth = 0.0;
    if (gRDP.otherMode.depth_source==1) 
        depth = gRDP.fPrimitiveDepth;

    float x0 = sprite.sprite.objX/4.0f;
    float y0 = sprite.sprite.objY/4.0f;
    float x1 = sprite.sprite.imageW / 32.0f / scaleX + x0;
    float y1 = sprite.sprite.imageH / 32.0f / scaleY + y0;

    if( sprite.sprite.imageFlags&1    ) { float temp = x0; x0 = x1; x1 = temp; } // flip X 
    if( sprite.sprite.imageFlags&0x10 ) { float temp = y0; y0 = y1; y1 = temp; } // flip Y

    g_texRectTVtx[0].x = (gObjMtxReal.A*x0 + gObjMtxReal.B*y0 + gObjMtxReal.X)*windowSetting.fMultX;
    g_texRectTVtx[0].y = (gObjMtxReal.C*x0 + gObjMtxReal.D*y0 + gObjMtxReal.Y)*windowSetting.fMultY;
    g_texRectTVtx[0].z = depth;
    g_texRectTVtx[0].rhw = 1;
    g_texRectTVtx[1].x = (gObjMtxReal.A*x1 + gObjMtxReal.B*y0 + gObjMtxReal.X)*windowSetting.fMultX;
    g_texRectTVtx[1].y = (gObjMtxReal.C*x1 + gObjMtxReal.D*y0 + gObjMtxReal.Y)*windowSetting.fMultY;
    g_texRectTVtx[1].z = depth;
    g_texRectTVtx[1].rhw = 1;
    g_texRectTVtx[2].x = (gObjMtxReal.A*x1 + gObjMtxReal.B*y1 + gObjMtxReal.X)*windowSetting.fMultX;
    g_texRectTVtx[2].y = (gObjMtxReal.C*x1 + gObjMtxReal.D*y1 + gObjMtxReal.Y)*windowSetting.fMultY;
    g_texRectTVtx[2].z = depth;
    g_texRectTVtx[2].rhw = 1;
    g_texRectTVtx[3].x = (gObjMtxReal.A*x0 + gObjMtxReal.B*y1 + gObjMtxReal.X)*windowSetting.fMultX;
    g_texRectTVtx[3].y = (gObjMtxReal.C*x0 + gObjMtxReal.D*y1 + gObjMtxReal.Y)*windowSetting.fMultY;
    g_texRectTVtx[3].z = depth;
    g_texRectTVtx[3].rhw = 1;

    g_texRectTVtx[0].tcord[0].u = left/g_textures[tile].m_fTexWidth;
    g_texRectTVtx[0].tcord[0].v = top/g_textures[tile].m_fTexHeight;
    g_texRectTVtx[1].tcord[0].u = (left+width)/g_textures[tile].m_fTexWidth;
    g_texRectTVtx[1].tcord[0].v = top/g_textures[tile].m_fTexHeight;
    g_texRectTVtx[2].tcord[0].u = (left+width)/g_textures[tile].m_fTexWidth;
    g_texRectTVtx[2].tcord[0].v = (top+height)/g_textures[tile].m_fTexHeight;
    g_texRectTVtx[3].tcord[0].u = left/g_textures[tile].m_fTexWidth;
    g_texRectTVtx[3].tcord[0].v = (top+height)/g_textures[tile].m_fTexHeight;

    //COLOR speColor = PostProcessSpecularColor();
    COLOR difColor = PostProcessDiffuseColor(0xffffffff);

    g_texRectTVtx[0].dcDiffuse = g_texRectTVtx[1].dcDiffuse 
        = g_texRectTVtx[2].dcDiffuse = g_texRectTVtx[3].dcDiffuse = difColor;
    g_texRectTVtx[0].dcSpecular = g_texRectTVtx[1].dcSpecular 
        = g_texRectTVtx[2].dcSpecular = g_texRectTVtx[3].dcSpecular = difColor;

    DrawSpriteR_Render();
}

void CRender::DrawFrameBuffer(bool useVIreg, uint32_t left, uint32_t top, uint32_t width, uint32_t height)
{
    BeginRendering();

    LoadFrameBuffer(useVIreg, left, top, width, height);

    m_pColorCombiner->InitCombinerBlenderForSimpleTextureDraw(0);

    ZBufferEnable(false);
    SetZUpdate(false);
    if( left == 0 )
        SetAlphaTestEnable(false);
    else
        SetAlphaTestEnable(true);   // use Alpha Test for partial frame buffer draw, for Dr. Mario 64

    m_pAlphaBlender->Disable();

    CTexture *pTexture = g_textures[0].m_pCTexture;
    if( pTexture )
    {
        if( useVIreg )
        {
            // Draw the whole frame buffer
            DrawSimple2DTexture(0, 0, windowSetting.uViWidth, windowSetting.uViHeight, 
                0, 0, 1/pTexture->m_fXScale, 1/pTexture->m_fYScale, 0xFFFFFFFF, 0xFFFFFFFF, 0, 1);
        }
        else
        {
            // Draw a small texture in frame buffer
            DrawSimple2DTexture((float)left, (float)top, (float)(left+width), (float)(top+height), 
                0, 0, 1/pTexture->m_fXScale, 1/pTexture->m_fYScale, 0xFFFFFFFF, 0xFFFFFFFF, 0, 1);
        }
    }

    TXTRBUF_OR_CI_DUMP(TRACE0("Draw Frame Buffer Img"));
#ifdef DEBUGGER
    if( pauseAtNext && ( eventToPause == NEXT_FRAME || eventToPause == NEXT_FLUSH_TRI ) )
    {
        TRACE0("Draw Frame Buffer Img");
        debuggerPause = true;
        DebuggerPause();
    }
#endif

    EndRendering();
}

void CRender::DrawObjBGCopy(uObjBg &info)
{
    if( !status.bCIBufferIsRendered )
        g_pFrameBufferManager->ActiveTextureBuffer();

    if( status.bHandleN64RenderTexture )
    {
        g_pRenderTextureInfo->maxUsedHeight = g_pRenderTextureInfo->N64Height;
        if( !status.bDirectWriteIntoRDRAM ) 
        {
            status.bFrameBufferIsDrawn = true;
            status.bFrameBufferDrawnByTriangles = true;
        }
    }
    SetCombinerAndBlender();

    uint32_t frameH = info.frameH;
    uint32_t frameW = info.frameW;
    uint32_t imageH = info.imageH;
    uint32_t imageW = info.imageW;

    if( options.bEnableHacks )
    {
        if( g_CI.dwWidth == 0x200 && info.imageFmt == g_CI.dwFormat && info.imageSiz == g_CI.dwSize &&
            frameW == 0x800 )
        {
            // Hack for RE2
            uint32_t width = *gfx_info.VI_WIDTH_REG & 0xFFF;
            imageH = frameH = (frameW/4*frameH/4)/width*4;
            imageW = frameW = width*4;
        }
    }

    float x0 = info.frameX / 4.0f;
    float y0 = info.frameY / 4.0f;
    float x1 = frameW / 4.0f + x0;
    float y1 = frameH / 4.0f + y0;

    float s0 = info.imageX / 32.0f;
    float t0 = info.imageY / 32.0f;

    float texwidth = imageW/4.0f;
    float texheight = imageH/4.0f;

    float u0 = s0/g_textures[0].m_fTexWidth;
    float v0 = t0/g_textures[0].m_fTexHeight;
    float maxu = texwidth/g_textures[0].m_fTexWidth;
    float maxv = texheight/g_textures[0].m_fTexHeight;

    float x2 = x0 + (texwidth-s0);
    float y2 = y0 + (texheight-t0);
    float u1 = (x1-x2)/g_textures[0].m_fTexWidth;
    float v1 = (y1-y2)/g_textures[0].m_fTexHeight;

    float depth = (gRDP.otherMode.depth_source == 1 ? gRDP.fPrimitiveDepth : 0.0f);

    COLOR speColor = PostProcessSpecularColor();
    COLOR difColor = PostProcessDiffuseColor(0xffffffff);

    if( options.enableHackForGames == HACK_FOR_COMMANDCONQUER )
    {
        float s1 = (x1-x0) + s0;
        float t1 = (y1-y0) + t0;
        DrawSimple2DTexture(x0, y0, x1, y1, u0, v0, 
            s1/g_textures[0].m_fTexWidth, t1/g_textures[0].m_fTexHeight, difColor, speColor, depth, 1);
    }
    else if( x2 >= x1 )
    {
        float s1 = (x1-x0) + s0;
        if( y2 >= y1 )
        {
            float t1 = (y1-y0) + t0;
            DrawSimple2DTexture(x0, y0, x1, y1, u0, v0, 
                s1/g_textures[0].m_fTexWidth, t1/g_textures[0].m_fTexHeight, difColor, speColor, depth, 1);
        }
        else
        {
            DrawSimple2DTexture(x0, y0, x1, y2, u0, v0, 
                s1/g_textures[0].m_fTexWidth, maxv, difColor, speColor, depth, 1);
            DrawSimple2DTexture(x0, y2, x1, y1, u0, 0, 
                s1/g_textures[0].m_fTexWidth, v1, difColor, speColor, depth, 1);
        }
    }
    else
    {
        if( y2 >= y1 )
        {
            float t1 = (y1-y0) + t0;
            DrawSimple2DTexture(x0, y0, x2, y1, u0, v0, 
                maxu, t1/g_textures[0].m_fTexHeight, difColor, speColor, depth, 1);
            DrawSimple2DTexture(x2, y0, x1, y1, 0, v0, 
                u1, t1/g_textures[0].m_fTexHeight, difColor, speColor, depth, 1);
        }
        else
        {
            DrawSimple2DTexture(x0, y0, x2, y2, u0, v0, maxu, maxv, difColor, speColor, depth, 1);
            DrawSimple2DTexture(x2, y0, x1, y2, 0, v0, u1, maxv, difColor, speColor, depth, 1);
            DrawSimple2DTexture(x0, y2, x2, y1, u0, 0, maxu, v1, difColor, speColor, depth, 1);
            DrawSimple2DTexture(x2, y2, x1, y1, 0, 0, u1, v1, difColor, speColor, depth, 1);
        }
    }
}

void CRender::DrawObjBG1CYC(uObjScaleBg &bg, bool scaled)   // Without Rotation
{
    if( !status.bCIBufferIsRendered )
        g_pFrameBufferManager->ActiveTextureBuffer();

    if( g_curRomInfo.bDisableObjBG )
        return;

    if( status.bHandleN64RenderTexture )
    {
        g_pRenderTextureInfo->maxUsedHeight = g_pRenderTextureInfo->N64Height;
        if( !status.bDirectWriteIntoRDRAM ) 
        {
            status.bFrameBufferIsDrawn = true;
            status.bFrameBufferDrawnByTriangles = true;
        }
    }

    SetCombinerAndBlender();

    float depth = (gRDP.otherMode.depth_source == 1 ? gRDP.fPrimitiveDepth : 0.0f);

    float x0 = bg.frameX / 4.0f;
    float y0 = bg.frameY / 4.0f;
    float x1 = bg.frameW / 4.0f + x0;
    float y1 = bg.frameH / 4.0f + y0;

    float s0 = bg.imageX / 32.0f;
    float t0 = bg.imageY / 32.0f;

    float scaleX = bg.scaleW/1024.0f;
    float scaleY = bg.scaleH/1024.0f;

    float texwidth = bg.imageW/4.0f;
    float texheight = bg.imageH/4.0f;

    float u0 = s0/g_textures[0].m_fTexWidth;
    float v0 = t0/g_textures[0].m_fTexHeight;
    float maxu = texwidth/g_textures[0].m_fTexWidth;
    float maxv = texheight/g_textures[0].m_fTexHeight;

    float x2 = x0 + (texwidth-s0)/scaleX;
    float y2 = y0 + (texheight-t0)/scaleY;
    float u1 = (x1-x2)*scaleX/g_textures[0].m_fTexWidth;
    float v1 = (y1-y2)*scaleY/g_textures[0].m_fTexHeight;

    COLOR speColor = PostProcessSpecularColor();
    COLOR difColor = PostProcessDiffuseColor(0xffffffff);

    SetAlphaTestEnable(false);

    if( options.enableHackForGames != HACK_FOR_YOSHI )
    {
        float s1 = (x1-x0)*scaleX + s0;
        float t1 = (y1-y0)*scaleY + t0;
        DrawSimple2DTexture(x0, y0, x1, y1, u0, v0, 
            s1/g_textures[0].m_fTexWidth, t1/g_textures[0].m_fTexHeight, difColor, speColor, depth, 1);
    }
    else if( x2 >= x1 )
    {
        float s1 = (x1-x0)*scaleX + s0;
        if( y2 >= y1 )
        {
            float t1 = (y1-y0)*scaleY + t0;
            DrawSimple2DTexture(x0, y0, x1, y1, u0, v0, 
                s1/g_textures[0].m_fTexWidth, t1/g_textures[0].m_fTexHeight, difColor, speColor, depth, 1);
        }
        else
        {
            DrawSimple2DTexture(x0, y0, x1, y2, u0, v0, 
                s1/g_textures[0].m_fTexWidth, maxv, difColor, speColor, depth, 1);
            DrawSimple2DTexture(x0, y2, x1, y1, u0, 0, 
                s1/g_textures[0].m_fTexWidth, v1, difColor, speColor, depth, 1);
        }
    }
    else
    {
        if( y2 >= y1 )
        {
            float t1 = (y1-y0)*scaleY + t0;
            DrawSimple2DTexture(x0, y0, x2, y1, u0, v0, 
                maxu, t1/g_textures[0].m_fTexHeight, difColor, speColor, depth, 1);
            DrawSimple2DTexture(x2, y0, x1, y1, 0, v0, 
                u1, t1/g_textures[0].m_fTexHeight, difColor, speColor, depth, 1);
        }
        else
        {
            DrawSimple2DTexture(x0, y0, x2, y2, u0, v0, maxu, maxv, difColor, speColor, depth, 1);
            DrawSimple2DTexture(x2, y0, x1, y2, 0, v0, u1, maxv, difColor, speColor, depth, 1);
            DrawSimple2DTexture(x0, y2, x2, y1, u0, 0, maxu, v1, difColor, speColor, depth, 1);
            DrawSimple2DTexture(x2, y2, x1, y1, 0, 0, u1, v1, difColor, speColor, depth, 1);
        }
    }
}


void CRender::DrawSprite(uObjTxSprite &sprite, bool rectR)  // Without Rotation
{
    if( !status.bCIBufferIsRendered )
        g_pFrameBufferManager->ActiveTextureBuffer();

    if( status.bHandleN64RenderTexture )
    {
        g_pRenderTextureInfo->maxUsedHeight = g_pRenderTextureInfo->N64Height;
        if( !status.bDirectWriteIntoRDRAM ) 
        {
            status.bFrameBufferIsDrawn = true;
            status.bFrameBufferDrawnByTriangles = true;
        }
    }
    SetCombinerAndBlender();
    COLOR speColor = PostProcessSpecularColor();
    COLOR difColor = PostProcessDiffuseColor(0xffffffff);


    float objX = sprite.sprite.objX/4.0f;
    float objY = sprite.sprite.objY/4.0f;
    float width = sprite.sprite.imageW / 32.0f;
    float high = sprite.sprite.imageH / 32.0f;
    float scaleW = sprite.sprite.scaleW/1024.0f;
    float scaleH = sprite.sprite.scaleH/1024.0f;

    if( g_curRomInfo.bIncTexRectEdge )
    {
        width++;
        high++;
    }

    float x0, y0, x1, y1;
    if( rectR )
    {
        // Upper-left coordinate
        // ( X + objX / BaseScaleX, Y+objY/BaseScaleY )
        // Lower-right coordinate
        // ( X + (objX + imageW / scaleW) / BaseScaleX - 1, Y + (objY + imageH / scaleH) / BaseScaleY - 1 )

        x0 = gObjMtxReal.X + objX/gObjMtxReal.BaseScaleX;
        y0 = gObjMtxReal.Y + objY/gObjMtxReal.BaseScaleY;
        x1 = gObjMtxReal.X + (objX + width / scaleW) / gObjMtxReal.BaseScaleX;
        y1 = gObjMtxReal.Y + (objY + high / scaleH) / gObjMtxReal.BaseScaleY;
    }
    else
    {
        // (objX, objY) - ( objX+imageW/scaleW-1, objY+imageH/scaleH-1)
        x0 = objX;
        y0 = objY;
        x1 = objX + width / scaleW;
        y1 = objY + high / scaleH;

        if( (sprite.sprite.imageFlags&1) ) // flipX
        {
            float temp = x0;
            x0 = x1;
            x1 = temp;
        }

        if( (sprite.sprite.imageFlags&0x10) ) // flipY
        {
            float temp = y0;
            y0 = y1;
            y1 = temp;
        }
    }

    // save the current clamp type
    GLint iClampS, iClampT;
    glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, &iClampS);
    glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, &iClampT);
    // force clamp type to CLAMP_EDGE (experiments show sometimes this is set to hex 0x2901 - invalid value)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    // draw the 2D sprite as 2 triangles
    float depth = (gRDP.otherMode.depth_source == 1 ? gRDP.fPrimitiveDepth : 0.0f);
    CTexture *pTexture = g_textures[0].m_pCTexture;
    DrawSimple2DTexture(x0, y0, x1, y1, 0, 0, 1/pTexture->m_fXScale, 1/pTexture->m_fYScale, 
        difColor, speColor, depth, 1);
    // return clamp type to original setting
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, iClampS);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, iClampT);
}


void CRender::LoadObjBG1CYC(uObjScaleBg &bg)
{
   uint32_t *rdram_u32 = (uint32_t*)gfx_info.RDRAM;
    uint32_t imageWidth = bg.imageW/4;
    uint32_t imageHeight = bg.imageH/4;

    TxtrInfo gti;
    gti.Format = bg.imageFmt;
    gti.Size   = bg.imageSiz;

    uint8_t *palAddr = (uint8_t *) &g_wRDPTlut[0];
    gti.Address = RSPSegmentAddr(bg.imagePtr);

    gti.LeftToLoad     = 0;
    gti.TopToLoad      = 0;

    gti.WidthToCreate  = imageWidth;
    gti.HeightToCreate = imageHeight;

    gti.clampS = gti.clampT = 1;
    gti.maskS = gti.maskT = 0;

    gti.Palette     = bg.imagePal;
    gti.PalAddress  = palAddr;

    gti.Pitch   = imageWidth << gti.Size >> 1;
    gti.Pitch   = (gti.Pitch>>3)<<3;    // Align to 8 bytes

    if( gti.Address + gti.Pitch*gti.HeightToCreate > g_dwRamSize )
    {
        TRACE0("Skip BG 1CYC loading, memory out of bound");
        return;
    }

    gti.TLutFmt = TLUT_FMT_RGBA16;  //RGBA16
    gti.bSwapped = false;

    gti.HeightToLoad = gti.HeightToCreate;
    gti.WidthToLoad = gti.WidthToCreate;
    gti.pPhysicalAddress = ((uint8_t*)rdram_u32) + gti.Address;
    gti.tileNo = -1;
    TxtrCacheEntry *pEntry = gTextureManager.GetTexture(&gti, false, true, false);
    SetCurrentTexture(0,pEntry);
}

void CRender::LoadObjSprite(uObjTxSprite &sprite, bool useTIAddr)
{
    TxtrInfo gti;
    uint32_t *rdram_u32 = (uint32_t*)gfx_info.RDRAM;

    gti.Format  = sprite.sprite.imageFmt;
    gti.Size    = sprite.sprite.imageSiz;

    uint8_t *palAddr = (uint8_t *) &g_wRDPTlut[0];

    gti.Address = RSPSegmentAddr(sprite.txtr.block.image);
    gti.Address += sprite.sprite.imageAdrs<<3;
    gti.LeftToLoad      = 0;
    gti.TopToLoad       = 0;
    gti.Palette = sprite.sprite.imagePal;
    gti.PalAddress = palAddr;

    if( sprite.txtr.block.type == S2DEX_OBJLT_TXTRBLOCK )
    {
        gti.WidthToCreate       = sprite.sprite.imageW/32;
        if( sprite.sprite.imageW >= 0x8000 )
        {
            gti.WidthToCreate = (0x10000-sprite.sprite.imageW)/32;
        }
        gti.HeightToCreate  = sprite.sprite.imageH/32;
        if( sprite.sprite.imageH >= 0x8000 )
        {
            gti.HeightToCreate  = (0x10000-sprite.sprite.imageH)/32;
        }
        gti.Pitch       = (2047/(sprite.txtr.block.tline-1)) << 3;
    }
    else if( sprite.txtr.block.type == S2DEX_OBJLT_TXTRTILE )
    {
//#define   GS_PIX2TMEM(pix, siz)   ((pix)>>(4-(siz)))
//#define   GS_TT_TWIDTH(pix,siz)   ((GS_PIX2TMEM((pix), (siz))<<2)-1)
//#define   GS_TT_THEIGHT(pix,siz)  (((pix)<<2)-1)

        gti.WidthToCreate       = ((sprite.txtr.tile.twidth+1)>>2)<<(4-gti.Size);
        gti.HeightToCreate  = (sprite.txtr.tile.theight+1)>>2;

        if( gti.Size == G_IM_SIZ_4b )
        {
            gti.Pitch = gti.WidthToCreate >> 1;
        }
        else
        {
            //gti.Pitch     = (sprite.txtr.tile.twidth+1) << 3;
            gti.Pitch       = gti.WidthToCreate << (gti.Size-1);
        }
    }

    if( gti.Address + gti.Pitch*gti.HeightToCreate > g_dwRamSize )
    {
        TRACE0("Skip Obj sprite loading, memory out of bound");
        return;
    }

    gti.TLutFmt = TLUT_FMT_RGBA16;  //RGBA16
    gti.bSwapped    = false;

    gti.HeightToLoad = gti.HeightToCreate;
    gti.WidthToLoad = gti.WidthToCreate;
    gti.pPhysicalAddress = ((uint8_t*)rdram_u32) + gti.Address;
    gti.tileNo = -1;
    TxtrCacheEntry *pEntry = gTextureManager.GetTexture(&gti, false, true, false);
    SetCurrentTexture(0,pEntry);
}

