//////////////////////////////////////////////////////////////////////////////
//
// Asura_PS2_Eyetoy.cpp
//
// Copyright (c) 2003 Rebellion. All rights reserved.
//
// Description:
//
//     PS2 Eyetoy support
//
//////////////////////////////////////////////////////////////////////////////

// I N C L U D E S ///////////////////////////////////////////////////////////

#include "Core/Asura.h"
#include "Eyetoy/Asura_PS2_Eyetoy.h"

#include "Core/Asura_TextureManagement.h"
#include "Debugging/Asura_Assert.h"

#include "Core/Asura_PlatformBasics.h"
#include "Core/Asura_PS2_DMA.h"
#include "Core/Asura_PS2_OS.h"
#include "Rendering/Asura_PlatformRenderStates.h"
#include "Rendering/Asura_PlatformTextureManagement.h"
#include "Rendering/Asura_PS2_2D_Graphics.h"
#include "Rendering/Asura_PS2_DeferredRendering.h"
#include "Rendering/Asura_PS2_GS.h"
#include "Rendering/Asura_TIM2.h"

#include "libimage.h"
#include "libvideo.h"

const int IMG_Y  = 0;
const int IMG_Cb = 1;
const int IMG_Cr = 2;

bool    Asura_PS2_Eyetoy::s_bCameraAvailable = false;
bool    Asura_PS2_Eyetoy::s_bFreezeFlag = 0;
bool    Asura_PS2_Eyetoy::s_bDeblock = false;
bool    Asura_PS2_Eyetoy::s_bAttemptStreaming = false;

static sceVideo               g_xVideoDriver;
#if(USE_RGB16)
    static sceImageRGB16      g_xRGB16Stream;
#endif

static sceImageRGBA           g_xRGBAData[2];
static sceImageRGBA           g_xRGBABack;
static sceVideo::SceVideoSize g_xResolution = sceVideo::STANDARD_RES;

static int                    g_iCurrentImageBuffer = 0;
static bool                   g_bReferenceSet       = false;
static u_int                  g_uFrameRate          = 60;

#if(USE_RGB16)
    enum
    {
        RGB16_UNPACKED_NONE     = 0,
        RGB16_UNPACKED_PARTIAL,
        RGB16_UNPACKED_FULL,
    };
    static int g_iRGB16Unpacked = RGB16_UNPACKED_NONE;
#endif

// (NRM - 10.09.2003) reference patch stuff
struct ReferenceImage
{
    Asura_PS2_Eyetoy_Rectangle xReferenceImageRectangle;
    sceImageRGBA               xRGBAImage;
};

const  int iMAX_NUMBER_REFERENCE_IMAGES = 4;
static ReferenceImage*        g_apxReferenceImage[iMAX_NUMBER_REFERENCE_IMAGES];
static int                    g_iNumberOfReferenceImages = 0;

static Asura_PS2_QW           g_apxUploadToDisplayBuffers[2][9] __attribute__((aligned(64)));

void UnPackRGB16ToRGBA32(void);
void UnPackRGB16ToRGBA32(const Asura_PS2_Eyetoy_Rectangle&);

void Asura_PS2_Eyetoy::LoadIRXModule(void)
{
    Asura_PS2_OS::LoadIRXModule("\\IRX\\EYETOY.IRX", 0, NULL);
}

pixel_t* Asura_PS2_Eyetoy::GetImageData(void)
{
    return g_xRGBAData[g_iCurrentImageBuffer].data();
}

void InitialiseEyetoy(void)
{
    // Eyetoy is an OV519
    #if(USE_RGB16)
        int iFormat = sceVideo::RGB16;
    #else
        int iFormat = sceVideo::RGBA32;
    #endif
    int iReturnVal = g_xVideoDriver.init(sceVideo::EYETOY, 
                                         static_cast<sceVideo::SceVideoFormat>(iFormat), 
                                         g_xResolution, g_uFrameRate);
    Asura_Assert(iReturnVal >= 0, "Could not intialise Eyetoy");
    // attach frame buffers
    g_xRGBABack.resize(g_xVideoDriver.width(), g_xVideoDriver.height());
    g_xRGBAData[0].resize(g_xVideoDriver.width(), g_xVideoDriver.height());
    g_xRGBAData[1].resize(g_xVideoDriver.width(), g_xVideoDriver.height());
    #if(USE_RGB16)
        g_xRGB16Stream.resize(g_xVideoDriver.width(), g_xVideoDriver.height());
        g_xVideoDriver.attach(&g_xRGB16Stream);
    #else
        g_xVideoDriver.attach(&g_xRGBAData[g_iCurrentImageBuffer]);
    #endif
}


void Asura_PS2_Eyetoy::StartCapture(void)
{
    // (NRM - 27.08.2003) avoid calling twice
    if(!s_bAttemptStreaming)
    {
        s_bAttemptStreaming = true;
        g_iCurrentImageBuffer = 0;
        #if(!USE_RGB16)
            g_xVideoDriver.attach(&g_xRGBAData[g_iCurrentImageBuffer]);
        #endif
    }
}

void Asura_PS2_Eyetoy::StopCapture(void)
{
    // (NRM - 27.08.2003) avoid calling twice
    if(s_bAttemptStreaming)
    {
        ClearBuffer();
        s_bAttemptStreaming = false;
    }
}

void Asura_PS2_Eyetoy::Initialise(void)
{
    InitialiseEyetoy();

    // (NRM - 10.09.2003) initialise reference patches
    for(int i=0; i<iMAX_NUMBER_REFERENCE_IMAGES; ++i)
    {
        g_apxReferenceImage[i] = NULL;
    }
}

void Asura_PS2_Eyetoy::Shutdown(void)
{
    for(int i=0; i<iMAX_NUMBER_REFERENCE_IMAGES; ++i)
    {
        delete g_apxReferenceImage[i];
    }
    g_xVideoDriver.stop();
}

void Asura_PS2_Eyetoy::ClearBuffer(void)
{
    register int      iImageSize      = g_xRGBAData[0].width() * g_xRGBAData[0].height();
    register pixel_t* puImage1Address = g_xRGBAData[0].data();
    register pixel_t* puImage2Address = g_xRGBAData[1].data();

    /* Register usage : temp regs start at $8 
        t0 = $8  = %0      = Address of front image buffer
        t1 = $9  = %1      = Address of back  image buffer
        t2 = $10 = %2      = image size
    */
    asm __volatile__(\
                     /* Load current pixel address */\
                      "ADD       $8,  $0,  %0   \n" \
                     /* Calculate loop counter */\
                      "ADD       $9,  $0,  %2   \n" \
                     /* Make a big register of 0 */ \
                      "ADD       $10, $0,  $0   \n" \
                     /* loop start */ \
          "1:" \
                      "SQ        $10, 0($8)     \n" \
                     /* inc address, dec counter, check for loop */ \
                      "ADDI      $8,  $8,  16   \n" \
                      "ADDI      $9,  $9,  -4   \n" \
                      "BNE       $0,  $9,  1b   \n" \
                      /* Do the other image buffer */\
                      "ADD       $8,  $0,  %1   \n" \
                      "ADD       $9,  $0,  %2   \n" \
          "1:" \
                      "SQ        $10, 0($8)     \n" \
                     /* inc address, dec counter, check for loop */\
                      "ADDI      $8,  $8,  16   \n" \
                      "ADDI      $9,  $9,  -4   \n" \
                      "BNE       $0,  $9,  1b   \n" \
                     :\
                     : "r"(puImage1Address), \
                       "r"(puImage2Address), \
                       "r"(iImageSize)       \
                     : "$8","$9","$10");
}

void Asura_PS2_Eyetoy::SetBackgroundImage(void)
{
    register int      iImageSize = g_xRGBAData[0].width() * g_xRGBAData[0].height();
    register pixel_t* puSourceImageAddress      = g_xRGBAData[g_iCurrentImageBuffer].data();
    register pixel_t* puDestinationImageAddress = g_xRGBABack.data();

    /* Register usage : temp regs start at $8 
        t0 = $8  = %0      = Address of source image
        t1 = $9  = %1      = Address of destination image
        t2 = $10 = %2      = image siz
    */
    #if(USE_RGB16)
        if(!g_iRGB16Unpacked)
        {
            UnPackRGB16ToRGBA32();
        }
    #endif

    asm __volatile__(\
                     /* Load pixel addresses */\
                      "ADD       $8,  $0,  %0   \n" \
                      "ADD       $9,  $0,  %1   \n" \
                     /* Load loop counter */\
                      "ADD       $10, $0,  %2   \n" \
                     /* loop start */ \
          "1:" \
                      "LQ        $11, 0($8)     \n" \
                      "SQ        $11, 0($9)     \n" \
                     /* inc address, dec counter, check for loop */ \
                      "ADDI      $8,  $8,  16   \n" \
                      "ADDI      $9,  $9,  16   \n" \
                      "ADDI      $10, $10, -4   \n" \
                      "BNE       $0,  $10, 1b   \n" \
                     :\
                     : "r"(puSourceImageAddress), \
                       "r"(puDestinationImageAddress), \
                       "r"(iImageSize)       \
                     : "$8","$9","$10");
}

void Asura_PS2_Eyetoy::SubtractBackground(const int iTolerance)
{   
    /* Register usage : temp regs start at $8 
        t0 = $8  = %0      = Address of destination image
        t1 = $9  = %1      = Address of foreground image
        t2 = $10 = %2      = Address of background image
        t3 = $11 = %3 * %4 = Loop counter (starts at size of bitmap)
        t4 = $12 = %5      = Tolerance mask / compare (parallel copy of 8*iTolerance
        t5 = $13           = Alpha or mask
        t9 = $25           = Compare mask for checking if any pixel is zero 

        All other temp registers are trashed 
        All output is handled in ASM
    */
    #if(USE_RGB16)
        if(!g_iRGB16Unpacked)
        {
            UnPackRGB16ToRGBA32();
        }
    #endif

    register pixel_t* puDestination = g_xRGBAData[g_iCurrentImageBuffer].data();
    register pixel_t* puForeground  = g_xRGBAData[g_iCurrentImageBuffer].data();
    register pixel_t* puBackground  = g_xRGBABack.data();
    // (NRM - 09.09.2003) assumes subtraction buffer and image buffer are the same
    register int    iWidth        = g_xRGBAData[g_iCurrentImageBuffer].width();
    register int    iHeight       = g_xRGBAData[g_iCurrentImageBuffer].height();
   
    asm __volatile__( /* Calculate compare value */ \
                      "LBU       $14, %5        \n" \
                      "SLL       $15, $14, 8    \n" \
                      "OR        $15, $14, $15  \n" \
                      "PCPYLD    $15, $15, $15  \n" \
                      "PCPYH     $12, $15       \n" \
                      \
                      /* Calculate alpha OR mask */ \
                      "LUI       $13, 0x8000    \n" \
                      "DSLL      $13, $13, 16   \n" \
                      "ORI       $13, 0x8000    \n" \
                      "DSLL      $13, $13, 16   \n" \
                      "PCPYLD    $13, $13, $13  \n" \
                      \
                      /* Calculate loop counter amount */ \
                      "ADD       $11, $0,  %3   \n" \
                      "MULT      $11, $11, %4   \n" \
                      \
                      /* Set up address locations of pixels */ \
                      "ADD       $8,  $0,  %0   \n" \
                      "ADD       $9,  $0,  %1   \n" \
                      "ADD       $10, $0,  %2   \n" \
                      \
                      /* Calculate compare mask for checking if any pixel is zero */\
                      "LUI       $25, 0x80FF    \n" \
                      "ORI       $25, 0xFFFF    \n" \
                      "DSLL      $25, $25, 16   \n" \
                      "ORI       $25, 0x80FF    \n" \
                      "DSLL      $25, $25, 16   \n" \
                      "ORI       $25, 0xFFFF    \n" \
                      "PCPYLD    $25, $25, $25  \n" \
                      \
                      /* loop start */ \
          "1:" \
                      "LQ        $14, 0($9)     \n" \
                      "LQ        $15, 0($10)    \n" \
                      "PSUBSB    $15, $14, $15  \n" /* c = b - a */ \
                      "PCGTB     $24, $15, $12  \n" /* c > tol */ \
                      "PSUBSB    $15, $0,  $15  \n" /* 0 - c */ \
                      "PCGTB     $15, $15, $12  \n" /* 0 - c > tol */\
                      "POR       $15, $15, $24  \n" \
                      "POR       $15, $15, $13  \n" /* OR the alpha in */\
                      "PCEQW     $15, $15, $25  \n" /* see if any pixel is 0 for any cmpt*/\
                      "PAND      $15, $15, $14  \n" \
                      "PREF      0,   384($10)  \n" \
                      "SQ        $15, 0($8)     \n" \
                      \
                      /* inc address, dec counter, check for loop */ \
                      "ADDI      $8,  $8,  16   \n" \
                      "ADDI      $9,  $9,  16   \n" \
                      "ADDI      $10, $10, 16   \n" \
                      "ADDI      $11, $11, -4   \n" \
                      "BNE       $0,  $11, 1b   \n"\
                      \
                      :\
                      : "r"(puDestination), \
                        "r"(puForeground),  \
                        "r"(puBackground),  \
                        "r"(iWidth),        \
                        "r"(iHeight),       \
                        "m"(iTolerance)     \
                      : "$8","$9","$10","$11","$12","$13","$14","$15","$24","$25");  
}

// calculate an alpha removal mask in the supplied parameter
#define ASM_Alpha_Removal(szRegister)\
     /* Calculate alpha removal mask*/\
      "LUI       "szRegister", 0x00FF                      \n" \
      "ORI       "szRegister", 0xFFFF                      \n" \
      "DSLL      "szRegister", "szRegister", 16            \n" \
      "ORI       "szRegister", 0x00FF                      \n" \
      "DSLL      "szRegister", "szRegister", 16            \n" \
      "ORI       "szRegister", 0xFFFF                      \n" \
      "PCPYLD    "szRegister", "szRegister", "szRegister"  \n" \

#define ASM_Alpha_Calc(szRegister, szParameter) \
     /* Calculate new alpha mask*/ \
      "OR        "szRegister", $0,  "szParameter"          \n"\
      "DSLL32    "szRegister", 0                           \n"\
      "OR        "szRegister", "szRegister", "szParameter" \n"\
      "DSLL      "szRegister", 24                          \n"\
      "PCPYLD    "szRegister", "szRegister", "szRegister"  \n"\

#define ASM_ForceAlphaRGB16(szRegister)\
     /* build the OR mask to force the alphas to 1 */\
      "LI        "szRegister", 0x80008000   \n" \
      "DSLL      "szRegister", $14, 16      \n" \
      "ORI       "szRegister", 0x8000       \n" \
      "DSLL      "szRegister", $14, 16      \n" \
      "ORI       "szRegister", 0x8000       \n" \
      "PCPYLD    "szRegister", "szRegister", "szRegister"     \n" \

// requires two temp regs
#define ASM_CalculateBufferStart(puBuffer, iStartX, iStartY, iBufferWidth,  /* inputs */\
                                 szDestReg, szTempReg1, szTempReg2, iBytesPerPixel) \
\
    "ADD       "szDestReg",  $0,              "puBuffer"    \n" \
    /* Calculate offset to first pixel */\
    "MULT      "szTempReg1", "iBufferWidth",  "iStartY"     \n" /* iImageWidth * xImageArea.m_iStartY */\
    "ADD       "szTempReg1", "szTempReg1",    "iStartX"     \n" /* + xImageArea.m_iStartX */\
    "LI        "szTempReg2", "iBytesPerPixel"               \n" \
    "MULT      "szTempReg1", "szTempReg1",    "szTempReg2"  \n" /* multiply by number of bytes per pixel */\
    "ADD       "szDestReg" , "szDestReg" ,    "szTempReg1"  \n" /* t1 & t2 now free */ \


void Asura_PS2_Eyetoy::AdjustAlpha(u_char uAlphaValue)
{
    /* Register usage : temp regs start at $8 
        t0 = $8  = %1          - current pixel address
        t1 = $9  = %2 * %3     - loop counter
        t2 = $10               - and away alpha mask
        t3 = $11 = %0          - or in new alpha mask
    */
    #if(USE_RGB16)
        if(!g_iRGB16Unpacked)
        {
            UnPackRGB16ToRGBA32();
        }
    #endif

    register pixel_t* puCurrentPixel = g_xRGBAData[g_iCurrentImageBuffer].data();
    register int      iImageWidth    = g_xRGBAData[g_iCurrentImageBuffer].width();
    register int      iImageHeight   = g_xRGBAData[g_iCurrentImageBuffer].height();

    asm __volatile__(\
                     /* Load current pixel address */\
                      "ADD       $8,  $0,  %1   \n" \
                     /* Calculate loop counter */\
                      "MULT      $9,  %3,  %2  \n"  \
                      \
                      ASM_Alpha_Removal("$10") \
                      ASM_Alpha_Calc("$11","%0") \
                      \
                     /* loop start */ \
          "1:" \
                      "LQ        $12, 0($8)     \n" \
                      "PREF      0,   128($10)  \n" \
                      "PAND      $12, $12, $10  \n" /* AND out current alpha*/\
                      "POR       $12, $12, $11  \n" /* OR in new alpha*/\
                      "SQ        $12, 0($8)     \n" \
                     /* inc address, dec counter, check for loop */ \
                      "ADDI      $8,  $8,  16   \n" \
                      "ADDI      $9,  $9,  -4   \n" \
                      "BNE       $0,  $9,  1b   \n"\
                      \
                     :\
                     : "r"(uAlphaValue),    \
                       "r"(puCurrentPixel), \
                       "r"(iImageWidth),  \
                       "r"(iImageHeight)  \
                     : "$8","$9","$10","$11","$12");

}
    
void Asura_PS2_Eyetoy::AdjustAlpha(const Asura_PS2_Eyetoy_Rectangle& xImageArea, u_char uAlphaValue)
{

    /* Register usage : temp regs start at $8 
        === Start ===
        t0  = $8  = %0          - puCurrentPixel address
        t1  = $9  = %1          - iStartX
        t2  = $10 = %2          - iStartY
        t3  = $11 = %3          - iWidth                     must be preserved 
        t4  = $12 = %4          - iHeight                    used as counter
        t5  = $13 = %5          - uAlphaValue
        t6  = $14 = %6          - s_iImageWidth

        === Loop  ===
        t0  = $8                - puCurrentPixel address
        t1  = $9                - line span counter
        t2  = $10               - pixel
        t3  = $11 = %3          - iWidth                     must be preserved 
        t4  = $12               - height span counter
        t5  = $13               - uAlphaValue
        t6  = $14               - alpha removal mask
        t7  = $15               - line offset
    */
    #if(USE_RGB16)
        if(!g_iRGB16Unpacked)
        {
            UnPackRGB16ToRGBA32();
        }
    #endif

    register pixel_t* puCurrentPixel = g_xRGBAData[g_iCurrentImageBuffer].data();
    register int      iImageWidth    = g_xRGBAData[g_iCurrentImageBuffer].width();

    asm __volatile__ (\
                       ASM_CalculateBufferStart("%0", "%1", "%2", "%6", "$8", "$10", "$12", "0x04") \
                      \
                      /* Calculate line offset - added to line start position after each line loop */ \
                       "ADD       $11, $0,  %3   \n" \
                       "SUB       $15, %6,  $11  \n" /* t6 now free */\
                       "MULT      $15, $15, $12  \n" \
                      /* Load height - use as a counter */\
                       "ADD       $12, $0,  %4   \n" \
                      \
                       ASM_Alpha_Removal("$14") \
                       ASM_Alpha_Calc("$13","%5") \
                      /* loop starts */ \
            "1:" \
                      /* set line span counter - width of image */\
                       "ADD       $9,  $0,  $11     \n" \
            "2:" /* do pixel calc*/ \
                       "LQ        $10, 0($8)     \n" \
                       "PAND      $10, $10, $14  \n" /* AND out current alpha*/\
                       "POR       $10, $10, $13  \n" /* OR in new alpha*/\
                       "SQ        $10, 0($8)     \n" \
                      \
                      /* Increment pixel address */  \
                       "ADDI      $8,  $8,  16   \n" \
                      /* decrement line span counter, check for loop */ \
                       "ADDI      $9,  $9,  -4      \n" \
                       "BNE       $0,  $9,  2b      \n" \
                      /* add the line span to the pixel address */\
                       "ADD       $8,  $8,  $15     \n" \
                      /* decrement height span counter, check for loop */ \
                       "ADDI      $12, $12, -1      \n" \
                       "BNE       $0,  $12, 1b      \n" \
                      \
                     :\
                     : "r"(puCurrentPixel),       \
                       "r"(xImageArea.m_iStartX), \
                       "r"(xImageArea.m_iStartY), \
                       "r"(xImageArea.m_iWidth),  \
                       "r"(xImageArea.m_iHeight), \
                       "r"(uAlphaValue),          \
                       "r"(iImageWidth)           \
                      : "$8","$9","$10","$11","$12","$13","$14","$15","$24","$25");
}

void Asura_PS2_Eyetoy::SetReferenceImage(int iNumber)
{
    const Asura_PS2_Eyetoy_Rectangle xImageArea(0, 0, g_xRGBAData[0].width(), g_xRGBAData[1].height());
    SetReferenceImage(iNumber, xImageArea);
}

void Asura_PS2_Eyetoy::SetReferenceImage(int iNumber, const Asura_PS2_Eyetoy_Rectangle& xImageArea)
{
    Asura_Assert(iNumber < iMAX_NUMBER_REFERENCE_IMAGES, "Reference image out of range");

    #if(USE_RGB16)
        if(!g_iRGB16Unpacked)
        {
            UnPackRGB16ToRGBA32(xImageArea);
        }
    #endif

    if(!g_apxReferenceImage[iNumber])
    {
        g_apxReferenceImage[iNumber] = new ReferenceImage;
        g_iNumberOfReferenceImages++;
    }
    g_bReferenceSet = true;

    g_apxReferenceImage[iNumber]->xReferenceImageRectangle = xImageArea;        // store the area for comparisons later
    g_apxReferenceImage[iNumber]->xRGBAImage.resize(xImageArea.m_iWidth, xImageArea.m_iHeight);
    register pixel_t* puSourceImage      = g_xRGBAData[g_iCurrentImageBuffer].data();
    register pixel_t* puDestinationImage = g_apxReferenceImage[iNumber]->xRGBAImage.data();
    register int      iImageWidth        = g_xRGBAData[g_iCurrentImageBuffer].width();

    /* Register usage : temp regs start at $8 
        === Start ===
        t0  = $8  = %0          - puSourceImage address
        t1  = $9  = %1          - puDestinationImage address
                    %2          - xImageArea.m_iStartX
                    %3          - xImageArea.m_iStartY
        t3  = $11 = %4          - xImageArea.m_iWidth - must be preserved across loop
                    %5          - xImageArea.m_iHeight
                    %6          - iImageWidth

        === Loop  ===
        t0  = $8  = %0          - puSourceImage address
        t1  = $9  = %1          - puDestinationImage address
        t2  = $10               - line span counter
        t3  = $11 = %4          - width - must be preserved across loop
        t4  = $12 = %5          - height counter
        t5  = $13               - pixel
    */
    asm __volatile__ (\
                       ASM_CalculateBufferStart("%0", "%2", "%3", "%6", "$8", "$10", "$12", "0x04") \
                      \
                      /* Load destination image address */\
                       "ADD       $9,  $0,  %1   \n" \
                      \
                      /* Calculate line offset - added to line start position after each line loop */ \
                       "ADD       $11, $0,  %4   \n" /* xImageArea.m_iWidth - preserve and use as line span counter */\
                       "SUB       $15, %6,  $11  \n" /* iImageWidth - xImageArea.m_iWidth */\
                       "MULT      $15, $15, $12  \n" /* multiply by number of bytes per pixel */\
                      \
                      /* Load height - use as a counter */\
                       "ADD       $12, $0,  %5   \n" \
            "1:" \
                      /* set line span counter - width of image */\
                       "ADD       $10, $0,  $11  \n" \
            "2:" \
                       "LQ        $13, 0($8)     \n" \
                       "SQ        $13, 0($9)     \n" \
                      \
                      /* Increment pixel addresses */\
                       "ADDI      $8,  $8,  16   \n" \
                       "ADDI      $9,  $9,  16   \n" \
                      \
                      /* decrement line span counter, check for loop */ \
                       "ADDI      $10, $10, -4   \n" \
                       "BNE       $0,  $10, 2b   \n" \
                      /* add the line span to the pixel address */\
                       "ADD       $8,  $8,  $15  \n" \
                      /* decrement height span counter, check for loop */ \
                       "ADDI      $12, $12, -1   \n" \
                       "BNE       $0,  $12, 1b   \n" \
                      \
                     :\
                     : "r"(puSourceImage),        \
                       "r"(puDestinationImage),   \
                       "r"(xImageArea.m_iStartX), \
                       "r"(xImageArea.m_iStartY), \
                       "r"(xImageArea.m_iWidth),  \
                       "r"(xImageArea.m_iHeight), \
                       "r"(iImageWidth)           \
                     : "$8","$9","$10","$11","$12","$13","$14","$15","$24","$25");
}

const int iPIXEL_BLOCK_SIZE = 16;
u_int CalculatePixelBlockSumOfDifferences(register const pixel_t* puImageData, 
                                          register const pixel_t* puReferenceData,
                                          int iImageLineSpan, int iReferenceLineSpan)
{
    u_int uTotal = 0;
    static int s_iCounter = 0;
    for(int iBlockX = 0; iBlockX < iPIXEL_BLOCK_SIZE; ++iBlockX)
    {
        register u_int uResult = 0;
        register pixel_t* puxImageData      = const_cast<pixel_t*>(puImageData);
        register pixel_t* puxReferenceData  = const_cast<pixel_t*>(puReferenceData);

        for(int iBlockY = 0; iBlockY < iPIXEL_BLOCK_SIZE; iBlockY+=4)
        {
            // (NRM - 06.10.2003) TODO - write this as a truly nested loop in asm, i.e. remove the for loops
            asm __volatile__(\
                             /* build and mask */\
                              "LI       $12, 0x00FF    \n" \
                              "PCPYLD   $12, $12       \n" \
                              "PCPYH    $12, $12       \n" \
                             /* load pixels */\
                              "LQ       $8,  0(%1)     \n" \
                              "LQ       $9,  0(%2)     \n" \
                             /* spread the bytes into two sets of half-words */\
                              "PSRLH    $10, $8,  0x8  \n" /* now contains A & G of puImageData as half-words */\
                              "PAND     $8,  $8,  $12  \n" /* now contains R & B of puImageData as half-words */\
                              "PSRLH    $11, $9,  0x8  \n" /* now contains A & G of puReferenceData as half-words */\
                              "PAND     $9,  $9,  $12  \n" /* now contains R & B of puReferenceData as half-words */\
                             /* subtract the pixels from each other*/\
                              "PSUBH    $8,  $9,  $8   \n" /* now R & B - R & B*/\
                              "PSUBH    $9,  $11, $10  \n" /* now A & G - A & G*/\
                             /* calculate abs*/\
                              "PABSH    $8,  $8        \n"\
                              "PABSH    $9,  $9        \n"\
                             /* do first addition*/\
                              "PADDH    $8, $8,  $9    \n" /* can now recycle all registers except for 8 */\
                             /* add all the half-words in the registers */\
                              "MTSAH    $0,  1         \n"\
                              "QFSRV    $9,  $8,  $8   \n"\
                              "PADDH    $8,  $9,  $8   \n" /* X = abs(R2-R1) + abs(B2-B1) + abs(G2-G1) + abs(A2-A1)*/\
                             /* X1 + X2 & X3 + X4 (plus X2 + X3 & X1 + X4 but we're not interested in those)*/\
                              "MTSAH    $0,  2         \n"\
                              "QFSRV    $9,  $8,  $8   \n"\
                              "PADDH    $8,  $9,  $8   \n"\
                             /* X1 + X2 + X3 + X4*/\
                              "MTSAH    $0,  4         \n"\
                              "QFSRV    $9,  $8,  $8   \n"\
                              "PADDH    %0,  $9,  $8   \n"\
                             /* register now contains the half words*/\
                              "ANDI     %0,  %0,  0xFFFF \n"\
                             : "=r"(uResult)           \
                             : "r"(puImageData),          \
                               "r"(puReferenceData)       \
                             : "$8","$9","$10","$11", "$12", "$13", "$14");

            puImageData+=4;
            puReferenceData+=4;
        }
        puImageData        += iImageLineSpan;
        puReferenceData    += iReferenceLineSpan; 
        uTotal             += uResult;
    }
    s_iCounter++;
    return uTotal;   
}

bool Asura_PS2_Eyetoy::ReferenceImageSet(int iReferenceImageNumber)
{
    return (iReferenceImageNumber < iMAX_NUMBER_REFERENCE_IMAGES) &&
            g_apxReferenceImage[iReferenceImageNumber];
}

float Asura_PS2_Eyetoy::CompareWithReferenceImage(int iReferenceImageNumber)
{
    Asura_Assert(iReferenceImageNumber < iMAX_NUMBER_REFERENCE_IMAGES,
                 "ReferenceImageNumber out of range");
    Asura_Assert(g_apxReferenceImage[iReferenceImageNumber],
                 "Trying to compare with reference image not set yet");

    // divide the referenceimage into 8 by 8 pixel blocks, calculate the sum of the squares
    // of the difference for each pixel in the 8 by 8 block then calculate the average error

    // calculate pixel offset
    const ReferenceImage*   pxRefImage = g_apxReferenceImage[iReferenceImageNumber];
    const Asura_PS2_Eyetoy_Rectangle& xImageArea = pxRefImage->xReferenceImageRectangle;

    #if(USE_RGB16)
        if(!g_iRGB16Unpacked)
        {
            UnPackRGB16ToRGBA32(xImageArea);
        }
    #endif


    int iImageBufferWidth = g_xRGBAData[g_iCurrentImageBuffer].width();
    const pixel_t* puImageData = g_xRGBAData[g_iCurrentImageBuffer].data();
    const pixel_t* puReferenceData = g_apxReferenceImage[iReferenceImageNumber]->xRGBAImage.data();

    int iImagePixelOffset        = xImageArea.m_iStartX + xImageArea.m_iStartY * iImageBufferWidth;
    // calculate line span - added to pixels after each 8 * 8 block
    int iImageLineSpan           = iImageBufferWidth - iPIXEL_BLOCK_SIZE;
    int iReferenceLineSpan       = xImageArea.m_iWidth - iPIXEL_BLOCK_SIZE;

    int iNumberOfXBlocks         = xImageArea.m_iWidth  / iPIXEL_BLOCK_SIZE;
    int iNumberOfYBlocks         = xImageArea.m_iHeight / iPIXEL_BLOCK_SIZE;
    int iBlockLineSpan           = (iPIXEL_BLOCK_SIZE  * iImageBufferWidth) - xImageArea.m_iWidth;
    
    puImageData += iImagePixelOffset;
    u_int uTotal = 0;

    for(int y = 0; y < iNumberOfYBlocks; ++y)
    {
        for(int x = 0; x < iNumberOfXBlocks; x++)
        {
            uTotal += CalculatePixelBlockSumOfDifferences(puImageData,    puReferenceData, 
                                                          iImageLineSpan, iReferenceLineSpan);
            puImageData     += iPIXEL_BLOCK_SIZE;
            puReferenceData += iPIXEL_BLOCK_SIZE;
        }
        puImageData += iBlockLineSpan;
    }

    return uTotal / (iNumberOfYBlocks * iNumberOfXBlocks);
}

#if(USE_RGB16)
void UnPackRGB16ToRGBA32(void)
{
    /* Unpack RGB16 to RGBA32
       Does 8 pixels per read! */

    /* Register usage :
        t0  = $8  = %0          - puSourceImage address
        t1  = $9  = %1          - puDestination address
        t2  = $10 = %2          - loop counter
        t3  = $11               - 8 pixels in RGB16 then last four pixels in RGBA32
        t4  = $12               - first four pixels in RGBA32
        t6  = $14               - OR mask to force alpha in RGB16
    */
       
       register const u_int* puDestination = g_xRGBAData[g_iCurrentImageBuffer].data();
       register const u_int* puSource      = reinterpret_cast<const u_int*>(g_xRGB16Stream.data());
       register const u_int  iImageSizeInPixels = g_xRGB16Stream.width() * g_xRGB16Stream.height();

       #if(USE_RGB16)
            g_iRGB16Unpacked = RGB16_UNPACKED_FULL;
       #endif
       asm __volatile__(\
                        /* load loop counter */\
                        "ADD       $10, $0,  %2      \n" \
                        /* load addresses    */\
                        "ADD       $8,  $0,  %0      \n" \
                        "ADD       $9,  $0,  %1      \n" \
                        ASM_ForceAlphaRGB16("$14")\
                "1:"\
                        "LQ        $11, 0($8)        \n" \
                        "POR       $11, $11, $14     \n" \
                        "PEXTLH    $12, $0,  $11     \n" \
                        "PEXTUH    $11, $0,  $11     \n" \
                        "PEXT5     $12, $12          \n" \
                        "SQ        $12, 0($9)        \n" \
                        "PEXT5     $11, $11          \n" \
                        "SQ        $11, 16($9)       \n" \
                        /* Increment addresses, decrement loop counter */\
                        "PREF      0,   128($8)      \n" \
                        "ADDI      $8,  $8,  16      \n" \
                        "ADDI      $9,  $9,  32      \n" /*written two quads above*/\
                        "ADDI      $10, $10, -8      \n" /*8 pixels at once!*/\
                        "BNE       $0,  $10, 1b      \n" \
                        :\
                        : "r"(puSource), \
                          "r"(puDestination), \
                          "r"(iImageSizeInPixels)\
                        : "$8", "$9", "$10", "$11", "$12", "$13");
}

void UnPackRGB16ToRGBA32(const Asura_PS2_Eyetoy_Rectangle& xImageArea)
{
    // this version only unpacks part of the image
    /* Register usage 
        t0  = $8  = %0          - puSourceImage address
        t1  = $9  = %1          - puDestination address
        t2  = $10 = %2          - height loop counter
        t3  = $11               - RGB16 line span
        t4  = $12               - RGBA32 line span

        t5  = $13               - 8 pixels in RGB16 then last four pixels in RGBA32
        t6  = $14               - OR mask to force alpha in RGB16
        t7  = $15 = %3          - line loop counter - starts as width
        t8  = $24               - first four pixels in RGBA32
                    %4          - startx
                    %5          - starty
                    %6          - imageWidth
    */
       register const u_int* puDestination = g_xRGBAData[g_iCurrentImageBuffer].data();
       register const u_int* puSource      = reinterpret_cast<const u_int*>(g_xRGB16Stream.data());
       register const u_int  iImageWidth   = g_xRGB16Stream.width();

       // (NRM - 18.09.2003) warning!  only going to unpack *some* of the frame here
       #if(USE_RGB16)
            g_iRGB16Unpacked = RGB16_UNPACKED_PARTIAL;
       #endif

       asm __volatile__(\
                        /* load addresses */\
                        ASM_CalculateBufferStart("%0", "%4", "%5", "%6", "$8", "$10", "$11", "0x02")\
                        ASM_CalculateBufferStart("%1", "%4", "%5", "%6", "$9", "$10", "$11", "0x04")\
                        /* load height loop counter */\
                        "ADD       $10, $0,  %2      \n" \
                        /* load line loop counter - equal to width and must be preserved across loop */\
                        "ADD       $15, $0,  %3      \n" \
                        /* calculate OR mask to force alpha */\
                        ASM_ForceAlphaRGB16("$14")\
                        /* calculate line offsets */\
                        "SUB       $11, %6,  $15  \n" /* iImageWidth - xImageArea.m_iWidth */\
                        "LI        $13, 0x04      \n"\
                        "MULT      $12, $11, $13  \n" /* multiply by number of bytes per pixel - RGBA32*/\
                        "LI        $13, 0x02      \n"\
                        "MULT      $11, $11, $13  \n" /* multiply by number of bytes per pixel - RGBA16*/\
                        /* load current line span counter */\
                "1:"\
                        "ADD       $15, $0,  %3      \n"\
                "2:"\
                        "LQ        $13, 0($8)        \n" \
                        "POR       $13, $13, $14     \n" \
                        "PEXTLH    $24, $0,  $13     \n" \
                        "PEXTUH    $13, $0,  $13     \n" \
                        "PEXT5     $24, $24          \n" \
                        "SQ        $24, 0($9)        \n" \
                        "PEXT5     $13, $13          \n" \
                        "SQ        $13, 16($9)       \n" \
                        /* add pixels, check for line end */\
                        "ADDI      $8,  $8,  16      \n" \
                        "ADDI      $9,  $9,  32      \n" /*written two quads above*/\
                        "ADDI      $15, $15, -8      \n" /*8 pixels at once!*/\
                        "BNE       $0,  $15, 2b      \n" \
                        /* add line spans */\
                        "ADD       $8,  $8,  $11     \n" \
                        "ADD       $9,  $9,  $12     \n" \
                        /* decrement height span counter, check for loop */ \
                        "ADDI      $10, $10, -1      \n" \
                        "BNE       $0,  $10, 1b      \n" \
                        :\
                        : "r"(puSource), \
                          "r"(puDestination),\
                          "r"(xImageArea.m_iHeight),\
                          "r"(xImageArea.m_iWidth), \
                          "r"(xImageArea.m_iStartX),\
                          "r"(xImageArea.m_iStartY),\
                          "r"(iImageWidth)\
                        : "$8", "$9", "$10", "$11", "$12", "$13", "$14", "$15", "$24");

}
#endif

static int  g_iNoiseTol = 32;
static bool g_bDoDenoise = true;
static int  g_WindowType = 0;

void Denoise(u_int* puDestination, u_int* puSource, int iWidth, int iHeight)
{
    // Denoise using a window plus thresholding between denoised pixel and original pixel

    // Store the window
    typedef int D2IntVector[2];
    const int iMAX_WINDOW_SIZE = 25;
    int iWINDOW_SIZE = 0;
    D2IntVector iWINDOW[iMAX_WINDOW_SIZE];

    switch(g_WindowType)
    {
        case 0:
    /*                X
                     XXX
                    XXXXX
                     XXX
                      X */

            iWINDOW_SIZE = 13;
            // Pixel offsets
            iWINDOW[0][0]  =  0; iWINDOW[0][1]  = -2;   // row 1
            iWINDOW[1][0]  = -1; iWINDOW[1][1]  = -1;   // row 2
            iWINDOW[2][0]  =  0; iWINDOW[2][1]  = -1;
            iWINDOW[3][0]  =  1; iWINDOW[3][1]  = -1;
            iWINDOW[4][0]  = -2; iWINDOW[4][1]  =  0;   // row 3
            iWINDOW[5][0]  = -1; iWINDOW[5][1]  =  0;
            iWINDOW[6][0]  =  0; iWINDOW[6][1]  =  0;
            iWINDOW[7][0]  =  1; iWINDOW[7][1]  =  0;
            iWINDOW[8][0]  =  2; iWINDOW[8][1]  =  0;
            iWINDOW[9][0]  = -1; iWINDOW[9][1]  =  1;   // row 4
            iWINDOW[10][0] =  0; iWINDOW[10][1] =  1;
            iWINDOW[11][0] =  1; iWINDOW[11][1] =  1;
            iWINDOW[12][0] =  0; iWINDOW[12][1] =  2;   // row 5
        break;

        case 1:
    /*               XXX
                     XXX
                     XXX */

            iWINDOW_SIZE = 9;

            iWINDOW[0][0]  = -1; iWINDOW[0][1]  = -1;   // row 1
            iWINDOW[1][0]  =  0; iWINDOW[1][1]  = -1;   // row 2
            iWINDOW[2][0]  =  1; iWINDOW[2][1]  = -1;
            iWINDOW[3][0]  = -1; iWINDOW[3][1]  =  0;
            iWINDOW[4][0]  =  0; iWINDOW[4][1]  =  0;   // row 3
            iWINDOW[5][0]  =  1; iWINDOW[5][1]  =  0;
            iWINDOW[6][0]  = -1; iWINDOW[6][1]  =  1;
            iWINDOW[7][0]  =  0; iWINDOW[7][1]  =  1;
            iWINDOW[8][0]  =  1; iWINDOW[8][1]  =  1;
        break;

        case 2:
    /*                X
                     XXX
                      X  */

            iWINDOW_SIZE = 5;

            iWINDOW[0][0]  =  0; iWINDOW[0][1]  = -1;   // row 1
            iWINDOW[1][0]  = -1; iWINDOW[1][1]  =  0;   // row 2
            iWINDOW[2][0]  =  0; iWINDOW[2][1]  =  0;
            iWINDOW[3][0]  =  1; iWINDOW[3][1]  =  0;
            iWINDOW[4][0]  =  0; iWINDOW[4][1]  =  1;   // row 3
        break;

    }

    // loop through the image applying the filter
    int iImageSize = iWidth * iHeight;
    for(int i=0; i<iImageSize; ++i)
    {
        int iNumberOfPixelsInWindow = 0;
        int iWindowSum = 0;
        for(int j=0; j<iWINDOW_SIZE; ++j)
        {
            int iPixelLocale = i + iWINDOW[j][1] * iWidth + iWINDOW[j][0];
            if(iPixelLocale > 0 && iPixelLocale < iImageSize)
            {
                iNumberOfPixelsInWindow++;
                // (NRM - 04.09.2003) only interested in alpha values
                iWindowSum += ((puSource[iPixelLocale] & 0xFF000000) >> 24);
            }
        }
        int iM = iWindowSum / iNumberOfPixelsInWindow;

        if(abs(((puSource[i] & 0xFF000000) >> 24) - iM) > g_iNoiseTol)
        {
            puDestination[i] = 0;
        }
        else
        {
            puDestination[i] = puSource[i];
        }
    }
}

void Asura_PS2_Eyetoy::UpdateSony(void)
{
    switch(g_xVideoDriver.status())
    {
        case sceVideo::NO_CAMERA:
            if(s_bCameraAvailable)
            {
                ClearBuffer();
                s_bCameraAvailable = false;
                s_bFreezeFlag = false;
            }
        break;
        case sceVideo::UNINITIALIZED:
            InitialiseEyetoy();
            s_bCameraAvailable = false;
        break;
        case sceVideo::STOPPED:
            // restart the streaming
            if(s_bAttemptStreaming)
            {
                g_xVideoDriver.start();
            }
        break;
        case sceVideo::STARTED:
            // (NRM - 26.08.2003) use the code for stabilising light levels etc.
            if (g_xVideoDriver.frame() < 400) 
            {
                s_bFreezeFlag = true;
            }
            else if (s_bFreezeFlag)
            {
                s_bFreezeFlag = false;
                g_xVideoDriver.setAttribute(sceVideo::AGC,0,0);
                g_xVideoDriver.setAttribute(sceVideo::AEC,0,0);
                g_xVideoDriver.setAttribute(sceVideo::AWB,0,0);
            }

            s_bCameraAvailable = true;
            int iAquiredFrame;
            iAquiredFrame = g_xVideoDriver.acquire();
            #if(USE_RGB16)
                g_iRGB16Unpacked = RGB16_UNPACKED_NONE;
            #endif

            if(iAquiredFrame < 0 || !s_bAttemptStreaming)
            {
                // (NRM - 18.09.2003) do some error reporting here? - camera is probably unplugged
            }
            else
            {
                if(s_bDeblock)
                {
                    // (NRM - 05.09.2003) TODO - write a decent smoothing / MPEG deblocking filter for use on RGBA32 images
                }
            }
        break;
    }
}

void Asura_PS2_Eyetoy::Update(void)
{
    UpdateSony();
}

void Asura_PS2_Eyetoy::SetResolutionAndFPS(int iResolution, u_int uFrameRate)
{
    g_xResolution = static_cast<sceVideo::SceVideoSize>(iResolution);
    g_uFrameRate  = uFrameRate;
}

int  Asura_PS2_Eyetoy::GetResolution(void)
{
    switch(g_xResolution)
    {
        case sceVideo::STANDARD_RES: // 320x240
            return 320*240;
        case sceVideo::LOW_RES:      // 160x112
            return 160*112;
        case sceVideo::HIGH_RES:     // 640x480
            return 640*480;
        case sceVideo::HIGH_RES_X:   // 640x240
            return 640*240;
        case sceVideo::HIGH_RES_Y:   // 320x480
            return 320*480;
        default:
            return 320*240;
    }    
}

int  Asura_PS2_Eyetoy::GetFPS(void)
{
    return g_uFrameRate;
}


static void UploadCallback( u_int uDBP, u_int* puDmaChainCallAddr, u_int* puDataSize )
{
    // (NRM - 26.08.2003) get the size in pixels
    int iImageBufferWidth  = g_xRGBAData[0].width();
    int iImageBufferHeight = g_xRGBAData[0].height();

    int iBytesPerPixel = 4;
    int iImageFormat   = SCE_GS_PSMCT32;
    u_int uCurrentImageBufferAddress = reinterpret_cast<u_int>(g_xRGBAData[g_iCurrentImageBuffer].data());

    int iImageBufferSizeInPixels = iImageBufferWidth * iImageBufferHeight;

    Asura_PS2_QW* pxOut = g_apxUploadToDisplayBuffers[g_iCurrentImageBuffer];

    *puDmaChainCallAddr = reinterpret_cast<u_int>( pxOut );
    *puDataSize         = iImageBufferSizeInPixels * iBytesPerPixel;

    pxOut = Asura_PS2_UncachedMem( pxOut );
    Asura_PS2_QW* const pxSectionStart = pxOut;


	// Write DMA tag.
	// PSJE - Added a VIF_DIRECT to the end of the dma tag.. this allows the texture to go:
	//		via path2 - The DIRECT code is used by vif1 
	//		via path3 - When dmaing to GIF we don't send the dma tag (TTE=0)
    Asura_PS2_DmaTag* pxDmaTag = reinterpret_cast<Asura_PS2_DmaTag*>( pxOut++ );
    pxDmaTag->Set( 6,
                   Asura_PS2_DmaTag::PCE_NOCHANGE,
                   Asura_PS2_DmaTag::ID_CNT,
                   false,
                   0,
                   false,
                   SCE_VIF1_SET_NOP( false ),
                   SCE_VIF1_SET_DIRECT( 6, false ) );

    // GIF tag for register settings.
    Asura_PS2_GifTag* const pxRegGifTag = reinterpret_cast<Asura_PS2_GifTag*>( pxOut++ );
    pxRegGifTag->Set( 4,
                      false,
                      false,
                      0,
                      SCE_GIF_PACKED,
                      1,
                      SCE_GIF_PACKED_AD );

	// Set transmission between buffers.
	pxOut->u64s[0] = SCE_GS_SET_BITBLTBUF( 0, 0, 0, uDBP, Tim2CalcBufWidth( iImageFormat, iImageBufferWidth ), iImageFormat );
	pxOut->u64s[1] = SCE_GS_BITBLTBUF;
    ++pxOut;

	// Set transmission area between buffers (source (x, y), dest (x, y)
    // and direction.)
	pxOut->u64s[0] = SCE_GS_SET_TRXPOS( 0, 0, 0, 0, 0 );
	pxOut->u64s[1] = SCE_GS_TRXPOS;
    ++pxOut;

	// Set size of transmission area.
	pxOut->u64s[0] = SCE_GS_SET_TRXREG( iImageBufferWidth, iImageBufferHeight );
	pxOut->u64s[1] = SCE_GS_TRXREG;
    ++pxOut;

	// Set transmission direction to HOST->LOCAL.
	pxOut->u64s[0] = SCE_GS_SET_TRXDIR( 0 );
	pxOut->u64s[1] = SCE_GS_TRXDIR;
    ++pxOut;

    // GIF tag for IMAGE data.
	// Check if we need an EOP yet... (only put EOPs when bNeedEOP and it's the last chunk of a big upload)
	(reinterpret_cast<Asura_PS2_GifTag*>(pxOut++))->Set( iImageBufferSizeInPixels * iBytesPerPixel / 16,
				                                         true,
					                                     false,
						                                 0,
							                             SCE_GIF_IMAGE,
								                         0 );

    pxDmaTag = reinterpret_cast<Asura_PS2_DmaTag*>( pxOut++ );

    pxDmaTag->Set( iImageBufferSizeInPixels * iBytesPerPixel / 16,
                   Asura_PS2_DmaTag::PCE_NOCHANGE,
                   Asura_PS2_DmaTag::ID_REF,
                   false,
                   uCurrentImageBufferAddress,
                   false,
                   SCE_VIF1_SET_NOP( false ),
                   SCE_VIF1_SET_DIRECT( iImageBufferSizeInPixels * iBytesPerPixel / 16, false ) );
    pxDmaTag = reinterpret_cast<Asura_PS2_DmaTag*>( pxOut++ );
    pxDmaTag->Set( 0,
                   Asura_PS2_DmaTag::PCE_NOCHANGE,
                   Asura_PS2_DmaTag::ID_RET,
                   false,
                   0,
                   false,
                   SCE_VIF1_SET_NOP( false ),
                   SCE_VIF1_SET_NOP( false ) );
}

void Asura_PS2_Eyetoy::DisplayFrame(float fAlpha)
{
    #if(USE_RGB16)
        if(g_iRGB16Unpacked != RGB16_UNPACKED_FULL)
        {
            UnPackRGB16ToRGBA32();
        }
    #endif

    int iBytesPerPixel = 4;
    int iImageFormat   = SCE_GS_PSMCT32;

    int iImageBufferWidth = g_xRGBAData[0].width();
    int iImageBufferHeight= g_xRGBAData[0].height();
    int iImageBufferSizeInPixels = iImageBufferWidth * iImageBufferHeight;

    Asura_PS2_TextureManagement::SetBufferLayout( ASURA_PS2_TEXBFRLAYOUT_2_LARGE, false );

    bool bUploaded = Asura_PS2_TextureManagement::UploadTexture( ASURA_TEXTURE_NONE, 0, UploadCallback );
    Asura_Assert( bUploaded, "" );
    Asura_PS2_TextureManagement::SetupTextureEnvironment( bUploaded, ASURA_PS2_TEXREGFLAG_TEXFLUSH );

    const int iNREG   = 8;
    const int iQWC    = Asura_PlatformRenderStates::NUM_RENDERSTATE_PACKET_QWS + 1 + iNREG;
    const int iNumQWs = 1 + iQWC;

    Asura_PS2_QW* const pxBase = Asura_PS2_DeferredRendering::GetVifBuffer( iNumQWs );
    Asura_PS2_QW* pxOut = pxBase;

    reinterpret_cast<Asura_PS2_DmaTag*>(pxOut++)->Set( iQWC,
                                                       Asura_PS2_DmaTag::PCE_NOCHANGE,
                                                       Asura_PS2_DmaTag::ID_CNT,
                                                       false,
                                                       0,
                                                       false,
                                                       SCE_VIF1_SET_NOP(false),
                                                       SCE_VIF1_SET_DIRECT(iQWC, false) );

    Asura_PlatformRenderStates::CreateDefaultRenderStatePacket( Asura_PlatformRenderStates::CONTEXT_1,
                                                                pxOut,
                                                                false,
                                                                true );

    Asura_PlatformRenderStates::SetPixelTest( false, false );
    Asura_PlatformRenderStates::SetTextureClamp( true );
    Asura_PlatformRenderStates::ForgetMyBuffer( pxOut );
    pxOut += Asura_PlatformRenderStates::NUM_RENDERSTATE_PACKET_QWS;

    const u_int uPRIM = SCE_GS_SET_PRIM( SCE_GS_PRIM_SPRITE,                   // PRIM
                                         0,                                    // IIP
                                         1,                                     // TME
                                         false,                                // FGE
                                         true,                                 // ABE
                                         false,                                // AA1
                                         1,                                    // FST
                                         0,                                    // CTXT
                                         0 );                                  // FIX

    reinterpret_cast<Asura_PS2_GifTag*>(pxOut++)->Set( 1, // NLOOP
                                                       true,                       // EOP
                                                       true,                       // PRE
                                                       uPRIM,                      // PRIM
                                                       SCE_GIF_PACKED,             // FLG
                                                       iNREG,                      // NREG
                                                       SCE_GIF_PACKED_AD,          // REG0
                                                       SCE_GIF_PACKED_AD,          // REG1
                                                       SCE_GIF_PACKED_AD,          // REG2
                                                       SCE_GIF_PACKED_AD,          // REG3
                                                       SCE_GS_UV,                  // REG4
                                                       SCE_GS_XYZ2,                // REG5
                                                       SCE_GS_UV,                  // REG6
                                                       SCE_GS_XYZ2 );              // REG7

    Asura_PS2_TextureManagement::GetTEX1Setting( &pxOut->u64s[0], ASURA_TEXTURE_NONE, SCE_GS_LINEAR, SCE_GS_LINEAR_MIPMAP_NEAREST, true );
    pxOut->u64s[1] = SCE_GS_TEX1_1;
    ++pxOut;

    pxOut->u64s[0] = SCE_GS_SET_TEX0(Asura_PS2_TextureManagement::GetBfrTBP(Asura_PS2_TextureManagement::GetBfrUsedForLastTexture()), 
                                     Tim2CalcBufWidth( iImageFormat, iImageBufferWidth ),
                                     iImageFormat,
                                     Asura_Maths::GetCeilLog2(g_xRGBAData[0].width()), 
                                     Asura_Maths::GetCeilLog2(g_xRGBAData[0].height()), 1,
                                     SCE_GS_MODULATE,
                                     0, 0, 0, 0, 0); // Irrelevant - not palettised.
    pxOut->u64s[1] = SCE_GS_TEX0_1;
    ++pxOut;

    int aaiUVs[2][2];
    const float U[2] = {0.0f, 1.0f};
    const float V[2] = {0.0f, 1.0f};
    Asura_PS2_2D_Graphics::SetupClampAndUVs( U, V, 2, 
                                             static_cast<float>(iImageBufferWidth), 
                                             static_cast<float>(iImageBufferHeight), pxOut++, &aaiUVs[0][0], false );

    pxOut->u64s[0] = SCE_GS_SET_RGBAQ( 128,
                                       128,
                                       128,
                                       s_bCameraAvailable ? Asura_Maths::FloatToInt(fAlpha * 128.0f) : 0,
                                       0x3F800000 );
	pxOut->u64s[1] = SCE_GS_RGBAQ;
    ++pxOut;

    const int iScreenWidth  = Asura_PS2_GS::GetScreenWidth();
    const int iScreenHeight = Asura_PS2_GS::GetScreenHeight();

    const int iTransX = ASURA_PS2_SCREEN_CENTRE_X - (iScreenWidth >> 1);
    const int iTransY = ASURA_PS2_SCREEN_CENTRE_Y - (iScreenHeight>> 1);

    for( int l = 0; l < 2; ++l ) 
    { 
        pxOut->u32s[l] = aaiUVs[0][l]; 
    }
    ++pxOut;

    pxOut->u32s[0] = (iTransX + 0) << 4;
    pxOut->u32s[1] = (iTransY + 0) << 4;
    pxOut->u64s[1] = 0;
    ++pxOut;

    for( int l = 0; l < 2; ++l ) 
    { 
        pxOut->u32s[l] = aaiUVs[1][l]; 
    }
    ++pxOut;

    pxOut->u32s[0] = (iTransX + iScreenWidth) << 4;
    pxOut->u32s[1] = (iTransY + iScreenHeight) << 4;
    pxOut->u64s[1] = 0;
    ++pxOut;

    Asura_Assert( pxOut - pxBase == iNumQWs, "" );

    g_iCurrentImageBuffer = 1 - g_iCurrentImageBuffer;
    #if(!USE_RGB16)
        g_xVideoDriver.attach(&g_xRGBAData[g_iCurrentImageBuffer]);
    #endif
}
