//////////////////////////////////////////////////////////////////////////////
//
// Asura_PS2_VU1.h
//
// Copyright (c) 2003 Rebellion. All rights reserved.
//
//////////////////////////////////////////////////////////////////////////////

// B E G I N   F I L E   W R A P P E R ///////////////////////////////////////

#ifdef ASURA_USE_PRAGMA_ONCE
    #pragma once
#endif

#ifndef ___ASURA_PS2_VU1_H___
#define ___ASURA_PS2_VU1_H___ 1

// M A C R O S ///////////////////////////////////////////////////////////////

#define ASURA_PS2_CONVERT_VU_EXTERN_TO_JMP_DST(puExtern) (reinterpret_cast<u_int>(puExtern) >> 3)
#define ASURA_PS2_MAKE_MSCAL_FROM_VU_PTR(puExtern) (SCE_VIF1_SET_MSCAL(ASURA_PS2_CONVERT_VU_EXTERN_TO_JMP_DST(puExtern), false))

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

#include <libvu0.h>

#include "Camera/Asura_Camera.h"

// M A C R O S ///////////////////////////////////////////////////////////////

// Some omissions from the Sony headers...
#define SCE_VIF1_SET_STMASK_REG( m0, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15 ) \
    (   ((u_int)m0  <<  0) \
      | ((u_int)m1  <<  2) \
      | ((u_int)m2  <<  4) \
      | ((u_int)m3  <<  6) \
      | ((u_int)m4  <<  8) \
      | ((u_int)m5  << 10) \
      | ((u_int)m6  << 12) \
      | ((u_int)m7  << 14) \
      | ((u_int)m8  << 16) \
      | ((u_int)m9  << 18) \
      | ((u_int)m10 << 20) \
      | ((u_int)m11 << 22) \
      | ((u_int)m12 << 24) \
      | ((u_int)m13 << 26) \
      | ((u_int)m14 << 28) \
      | ((u_int)m15 << 30)   )
#define SCE_VIF1_SET_UNPACKM(   addr, count, cmd, irq ) (SCE_VIF1_SET_UNPACK (addr,          count, cmd | 0x10, irq))
#define SCE_VIF1_SET_UNPACKMR(  addr, count, cmd, irq ) (SCE_VIF1_SET_UNPACKR(addr,          count, cmd | 0x10, irq))
#define SCE_VIF1_SET_UNPACKMRU( addr, count, cmd, irq ) (SCE_VIF1_SET_UNPACKR(addr | 0x4000, count, cmd | 0x10, irq))
#define SCE_VIF1_SET_UNPACKMU(  addr, count, cmd, irq ) (SCE_VIF1_SET_UNPACK (addr | 0x4000, count, cmd | 0x10, irq))
#define SCE_VIF1_SET_UNPACKRU(  addr, count, cmd, irq ) (SCE_VIF1_SET_UNPACKR(addr | 0x4000, count, cmd,        irq))
#define SCE_VIF1_SET_UNPACKU(   addr, count, cmd, irq ) (SCE_VIF1_SET_UNPACK (addr | 0x4000, count, cmd,        irq))

// C L A S S E S /////////////////////////////////////////////////////////////

class Asura_PS2_VU1
{

    public:

        // T Y P E S /////////////////////////////////////////////////////////

        enum RENDERER
        {
			RENDERER_PARTICLES,
#ifdef ASURA_USE_SKYSPHERE
            RENDERER_FRACTALSKY,
#endif
			RENDERER_GSM,
			RENDERER_CORONAS,
//			RENDERER_GLASS,
			RENDERER_DECAL,
			RENDERER_BEAM,
            RENDERER_SMOOTHSKIN,
			RENDERER_BLUE_TO_ALPHA,
#ifdef ASURA_USE_WATER
			RENDERER_WATER,
#endif
#ifdef ASURA_USE_CARPET
			RENDERER_CARPET,
#endif
#ifdef ASURA_USE_BOTANICALS
			RENDERER_BOTANICALS,
#endif
			RENDERER_NUMRENDERERS,
		};

        // See EE User's Manual section 4.3.4 and SCE_VIF1_SET_UNPACK() macro.
        enum VIFPK
        {
            VIFPK_S_32  = 0x60,
            VIFPK_S_16  = 0x61,
            VIFPK_S_8   = 0x62,
            VIFPK_V2_32 = 0x64,
            VIFPK_V2_16 = 0x65,
            VIFPK_V2_8  = 0x66,
            VIFPK_V3_32 = 0x68,
            VIFPK_V3_16 = 0x69,
            VIFPK_V3_8  = 0x6a,
            VIFPK_V4_32 = 0x6c,
            VIFPK_V4_16 = 0x6d,
            VIFPK_V4_8  = 0x6e,
            VIFPK_V4_5  = 0x6f,
        };
        
        // C L A S S   M E T H O D S /////////////////////////////////////////

        static void Initialise( void );
        static void Shutdown( void );

        static void BeginFrame( void );
        static void SetupMatrices( u_int uCameras = UINT_MAX );
        static void EndFrame( void );

        static float GetZMax( void );
        static void  SetZMax( float fZMax );
        static void  SetZMaxToDefault( void );
        static float TransformZBfrValueToViewspaceZ( float fZBufferValue );
		static float TransformViewspaceZToZBfrValue( float fViewspaceZ );
        static float CalculateZMaxReqd( float fViewspaceZ, float fZBufferValue );

        // These methods return true iff an upload was required.
        static bool LoadRenderer( RENDERER eRenderer );
		static bool LoadCustomRenderer( const void* pvRenderer );
        static bool IsRendererLoaded( RENDERER eRenderer );
        static bool IsCustomRendererLoaded( const void* pvRenderer );

		static void InvalidateCachedRenderer();

        static sceVu0FMATRIX& GetWorldToScreenMat( u_int uCam = Asura_Camera::GetCameraIndex() )        { return s_xWorldToScreenMat[uCam]; }
        static sceVu0FMATRIX& GetWorldToViewMat( u_int uCam = Asura_Camera::GetCameraIndex() )          { return s_xWorldToViewMat[uCam]; }
        static sceVu0FMATRIX& GetWorldToClipMatScreen( u_int uCam = Asura_Camera::GetCameraIndex() )    { return s_xWorldToClipMatScreen[uCam]; }
        static sceVu0FMATRIX& GetWorldToClipMatGuardband( u_int uCam = Asura_Camera::GetCameraIndex() ) { return s_xWorldToClipMatGuardband[uCam]; }
        static sceVu0FMATRIX& GetViewToClipMatScreen( u_int uCam = Asura_Camera::GetCameraIndex() )     { return s_xViewToClipMatScreen[uCam]; }
        static sceVu0FMATRIX& GetViewToClipMatGuardband( u_int uCam = Asura_Camera::GetCameraIndex() )  { return s_xViewToClipMatGuardband[uCam]; }
        static sceVu0FMATRIX& GetViewToScreenMat( u_int uCam = Asura_Camera::GetCameraIndex() )         { return s_xViewToScreenMat[uCam]; }
        static sceVu0FMATRIX& GetClipToScreenMat( u_int uCam = Asura_Camera::GetCameraIndex() )         { return s_xClipToScreenMat[uCam]; }
        static sceVu0FVECTOR& GetGBClipToScrScaleVec( u_int uCam = Asura_Camera::GetCameraIndex() )     { return s_xGC2SScale[uCam]; }
        static sceVu0FVECTOR& GetGBClipToScrTransVec( u_int uCam = Asura_Camera::GetCameraIndex() )     { return s_xGC2STrans[uCam]; }
        static sceVu0FVECTOR& GetGBClipToScrClipScaleVec( u_int uCam = Asura_Camera::GetCameraIndex() ) { return s_xGC2SCScale[uCam]; }

        // These methods assume the matrix ptrs are QW-aligned.
        static inline void CalcPS2MatInverseRotOnly( void* pvDstMat, const void* pvSrcMat );
        static inline void CopyPS2Mat( void* pvDstMat, const void* pvSrcMat );
        static void SetupPS2MatFromAsuraData( float* pfOutMat, const Asura_Matrix_3x3& xOri, const Asura_Vector_3& xPos );
	
        static bool IsSphereEntirelyWithinGuardbandFrustum( const Asura_Vector_3& xCentre, float fRadius );

        // Methods for EE-side T&L on single vertices. These are SLOW and
        // should generally only be used for debugging or prototype code. The
        // input vertices are assumed to be in worldspace.
        static bool IsVtxInsideGuardband( u_int uCam, const Asura_Vector_3& xVtx );
        static void TransformVtxToScreenspace( u_int uCam, const Asura_Vector_3& xVtxIn, Asura_Vector_3* pxVtxOut, float* pfOOW = NULL );
        static void TransformVtxToScreenspace( u_int uCam, const Asura_Vector_3& xVtxIn, Asura_PS2_QW* pxQWOut, float* pfOOW = NULL );
        
        // Debugging helper utilities.
        static void FillMem( u_long128 uFillValue );

	private:

		static void SetupViewClipAndViewScreenMatrices( int iCamID, float fScreenZ );

        static void Project_GetVULibVersions( u_int* puRequiredVersion, u_int* puActualVersion );

        // C L A S S   S T A T E /////////////////////////////////////////////

		static sceVu0FMATRIX s_xWorldToScreenMat[ASURA_MAX_NO_OF_CAMERAS];
		static sceVu0FMATRIX s_xWorldToViewMat[ASURA_MAX_NO_OF_CAMERAS];
		static sceVu0FMATRIX s_xWorldToClipMatScreen[ASURA_MAX_NO_OF_CAMERAS];
		static sceVu0FMATRIX s_xWorldToClipMatGuardband[ASURA_MAX_NO_OF_CAMERAS];
		static sceVu0FMATRIX s_xViewToClipMatScreen[ASURA_MAX_NO_OF_CAMERAS];
		static sceVu0FMATRIX s_xViewToClipMatGuardband[ASURA_MAX_NO_OF_CAMERAS];
		static sceVu0FMATRIX s_xViewToScreenMat[ASURA_MAX_NO_OF_CAMERAS];
		static sceVu0FMATRIX s_xClipToScreenMat[ASURA_MAX_NO_OF_CAMERAS];
        static sceVu0FVECTOR s_xGC2SScale[ASURA_MAX_NO_OF_CAMERAS];
        static sceVu0FVECTOR s_xGC2STrans[ASURA_MAX_NO_OF_CAMERAS];
        static sceVu0FVECTOR s_xGC2SCScale[ASURA_MAX_NO_OF_CAMERAS];
};
// End class Asura_PS2_VU1.

// C L A S S   I M P L E M E N T A T I O N S /////////////////////////////////

// Assumes the source matrix contains only rotation and/or translation
// components.
inline void Asura_PS2_VU1::CalcPS2MatInverseRotOnly( void* pvDstMat, const void* pvSrcMat )
{
    // This is based on sceVu0TransposeMatrix().

    u_long128 uTmp0;
    u_long128 uTmp1;
    u_long128 uTmp2;
    u_long128 uTmp3;

    // Load first three rows.
    asm __volatile__( "LQ %0,  0(%1)" : "=r"(uTmp0) : "r"(pvSrcMat) );
    asm __volatile__( "LQ %0, 16(%1)" : "=r"(uTmp1) : "r"(pvSrcMat) );
    asm __volatile__( "LQ %0, 32(%1)" : "=r"(uTmp2) : "r"(pvSrcMat) );

    // Clobber translation by just grabbing VF00 instead.
    asm __volatile__( "QMFC2 %0, $vf00" : "=r"(uTmp3) );

    u_long128 uTmp4;
    u_long128 uTmp5;
    u_long128 uTmp6;
    u_long128 uTmp7;

    // Transpose.
    asm __volatile__( "PEXTLW %0, %1, %2" : "=r"(uTmp4) : "r"(uTmp1), "r"(uTmp0) );
	asm __volatile__( "PEXTUW %0, %1, %2" : "=r"(uTmp5) : "r"(uTmp1), "r"(uTmp0) );
	asm __volatile__( "PEXTLW %0, %1, %2" : "=r"(uTmp6) : "r"(uTmp3), "r"(uTmp2) );
	asm __volatile__( "PEXTUW %0, %1, %2" : "=r"(uTmp7) : "r"(uTmp3), "r"(uTmp2) );
    
    asm __volatile__( "PCPYLD %0, %2, %1" : "=r"(uTmp0) : "r"(uTmp4), "r"(uTmp6) );
	asm __volatile__( "PCPYUD %0, %1, %2" : "=r"(uTmp1) : "r"(uTmp4), "r"(uTmp6) );
	asm __volatile__( "PCPYLD %0, %2, %1" : "=r"(uTmp2) : "r"(uTmp5), "r"(uTmp7) );
//	asm __volatile__( "PCPYUD %0, %1, %2" : "=r"(uTmp3) : "r"(uTmp5), "r"(uTmp7) );

    // Write back.
    asm __volatile__( "SQ %1,  0(%2)" : "=m"((static_cast<u_long128*>(pvDstMat))[0]) : "r"(uTmp0), "r"(pvDstMat) );
    asm __volatile__( "SQ %1, 16(%2)" : "=m"((static_cast<u_long128*>(pvDstMat))[1]) : "r"(uTmp1), "r"(pvDstMat) );
    asm __volatile__( "SQ %1, 32(%2)" : "=m"((static_cast<u_long128*>(pvDstMat))[2]) : "r"(uTmp2), "r"(pvDstMat) );
    asm __volatile__( "SQ %1, 48(%2)" : "=m"((static_cast<u_long128*>(pvDstMat))[3]) : "r"(uTmp3), "r"(pvDstMat) );
}

inline void Asura_PS2_VU1::CopyPS2Mat( void* pvDstMat, const void* pvSrc )
{
    const u_long128* puSrcQWs = static_cast<const u_long128*>( pvSrc );
    const u_long128 uTmp0 = *(puSrcQWs + 0);
    const u_long128 uTmp1 = *(puSrcQWs + 1);
    const u_long128 uTmp2 = *(puSrcQWs + 2);
    const u_long128 uTmp3 = *(puSrcQWs + 3);

    u_long128* puDstQWs = static_cast<u_long128*>( pvDstMat );
    *(puDstQWs + 0) = uTmp0;
    *(puDstQWs + 1) = uTmp1;
    *(puDstQWs + 2) = uTmp2;
    *(puDstQWs + 3) = uTmp3;
}

// E N D   F I L E   W R A P P E R ///////////////////////////////////////////

#endif // #ifndef ___ASURA_PS2_VU1_H___
