/*---------------------------------------------------------------------------*
  Project:   NITRO-System - build - demos - g2d - samples - Renderer_PerfCheck
  File:     main.c

  Copyright 2004,2005 Nintendo.  All rights reserved.

  These coded instructions, statements, and computer programs contain
  proprietary information of Nintendo of America Inc. and/or Nintendo
  Company Ltd., and are protected by Federal copyright law.  They may
  not be disclosed to third parties or copied or duplicated in any form,
  in whole or in part, without the prior written consent of Nintendo.

  $Log: main.c,v $
  Revision 1.7  06/29/2005 07:46:05  kitani_toshikazu
  (none)

  Revision 1.6  06/29/2005 05:59:56  kitani_toshikazu
  Cleaned up source code and added comments.

  Revision 1.5  05/31/2005 05:27:31  kitani_toshikazu
  Supported build in ProDG environment.

  Revision 1.4  01/25/2005 01:56:02  kitani_toshikazu
  Corrected comments

  Revision 1.3  01/24/2005 09:41:54  kitani_toshikazu
  Changed the multicell initialization function to use the new API

  Revision 1.2  11/15/2004 05:55:32  kitani_toshikazu
  Revised comments, revised debug messages

  Revision 1.1  11/10/2004 08:25:18  kitani_toshikazu
  Registration of the initial version

  $NoKeywords: $
 *---------------------------------------------------------------------------*/

// ============================================================================
//  Explanation of the demo:
//      Compares the performance of the renderer module.
//
//      This demo draws a multi-cell animation using the renderer and displays the time required to draw it.
//         
//
//      The compile switch USE_OPZ_API (specified value 0) is defined.
//       When USE_OPZ_API is defined as 1, use NNS_G2dBeginRenderingEx() which supports optimization hints.
//         
//       (However, with versions not supporting NNS_G2dBeginRenderingEx() a build is not possible.)
//          )
//
//      Pressing the A button switches to rendering with NNS_G2dMakeSimpleMultiCellToOams() for the fastest rendering possible.
//         
//       Software sprites are not drawn during rendering by NNS_G2dMakeSimpleMultiCellToOams().
//         
//
// 
//  Operation Method
//      START button: Switches the cell output method between OBJ and Software Sprite.
//                      A button:
//      X button: Resets scaling, movement, and rotation status.
//      +Control Pad: Moves the cell.
//      A Button        Switches the drawing between using the renderer and the
//                      Switch to rendering using the function NNS_G2dMakeSimpleMultiCellToOams.
//                        
//
// ============================================================================

#include <nitro.h>
#include <nnsys/g2d.h>

#include "g2d_demolib.h"

//------------------------------------------------------------------------------
// Constant definitions
#define SCREEN_WIDTH                256                     // screen width
#define SCREEN_HEIGHT               192                     // screen height

#define CELL_INIT_POS_X             (120 << FX32_SHIFT)     // Initial X position of the cell
#define CELL_INIT_POS_Y             (120 << FX32_SHIFT)     // Initial Y position of the cell

#define INIT_OUTPUT_TYPE            NNS_G2D_SURFACETYPE_MAIN2D   // Initial value for output method

#define NUM_OF_OAM                  128      // Number of OAMs allocated to OAM manager

#define CHARA_BASE                  0x0      // Character image base address
#define PLTT_BASE                   0x0      // Palette base address

//
// If this definition is set to 1, the new optimized API added from the 11/10/2004 version is used.
// However, compiles are not possible with versions where optimized API is not supported.
// Operations for the affine parameters are not reflected.
//
#define USE_OPZ_API 1

//------------------------------------------------------------------------------
// structure definitions

typedef struct CellInfo
{
    u16          cellIdx;   // No. of rendered cell
    u16          rotate;    // Rotation angle
    NNSG2dFVec2  scale;     // Scale
    NNSG2dFVec2  pos;       // Position of cell rendering
} CellInfo;


//------------------------------------------------------------------------------
// Global Variables

static NNSG2dImageProxy             sImageProxy;    // Character/texture proxy for cell
static NNSG2dImagePaletteProxy      sPaletteProxy;  // Palette proxy for cell

static NNSG2dOamManagerInstance*    spCurrentOam;   // Manager of OAM subject to callback processing

static NNSG2dAnimBankData*          spAnimBank;     // Animation Bank
static NNSG2dCellAnimation*         spCellAnim;     // Cell animation


//------------------------------------------------------------------------------
// Prototype declarations

void VBlankIntr(void);
static void ProcessInput(CellInfo* pCellInfo, NNSG2dSurfaceType* pOutputType, u16 numCells);
static void MakeAffineMatrix(MtxFx22* pMtx, MtxFx22* pMtx2D, const CellInfo* pCellInfo);
static BOOL CallBackAddOam( const GXOamAttr* pOam, u16 affineIndex, BOOL bDoubleAffine );
static u16 CallBackAddAffine( const MtxFx22* mtx );
static void InitOamManager(NNSG2dOamManagerInstance*   pObjOamManager);
static void InitRenderer(
                NNSG2dRendererInstance* pRenderer,
                NNSG2dRenderSurface*    pSurface);


/*---------------------------------------------------------------------------*
  Name:         CallBackAddOam

  Description:  Function called internally by NNS_G2dDraw* functions to add an OAM .

  Arguments:    pOam :           Pointer to OAM to be added
                affineIndex :    Affine index used by this OAM.
                                May be a value of 32 or larger.
                                If affine is not used,
                                NNS_G2D_OAM_AFFINE_IDX_NONE is specified.
                bDoubleAffine :  Whether in double affine mode.

  Returns:      Returns TRUE if successful; returns FALSE otherwise.
 *---------------------------------------------------------------------------*/
static BOOL CallBackAddOam( const GXOamAttr* pOam, u16 affineIndex, BOOL bDoubleAffine )
{
#pragma unused( bDoubleAffine )
    SDK_NULL_ASSERT( spCurrentOam );
    SDK_NULL_ASSERT( pOam );

    return NNS_G2dEntryOamManagerOamWithAffineIdx( spCurrentOam, pOam, affineIndex );
}

/*---------------------------------------------------------------------------*
  Name:         CallBackAddAffine

  Description:  This function is called to add affine parameters in the
                NNS_G2dDraw* functions.

  Arguments:    mtx :        Pointer to affine transform matrix to be added.

  Returns:      Returns the AffineIndex for referencing the affine parameters
                added.
 *---------------------------------------------------------------------------*/
static u16 CallBackAddAffine( const MtxFx22* mtx )
{
    SDK_NULL_ASSERT( spCurrentOam );
    SDK_NULL_ASSERT( mtx );
    return NNS_G2dEntryOamManagerAffine( spCurrentOam, mtx );
}

/*---------------------------------------------------------------------------*
  Name:         InitOamManager

  Description:  This function carries out the initialization of the OAM Manager
                System and one OAM Manager.

  Arguments:    pOamManager:    Pointer to the OAM manager to be initialized.

  Returns:      None.
 *---------------------------------------------------------------------------*/
static void InitOamManager(NNSG2dOamManagerInstance*   pOamManager)
{
    BOOL result;
    SDK_NULL_ASSERT( pOamManager );

    NNS_G2dInitOamManagerModule();

    result = NNS_G2dGetNewOamManagerInstanceAsFastTransferMode(
                pOamManager, 0, NUM_OF_OAM, NNS_G2D_OAMTYPE_MAIN );
    SDK_ASSERT( result );
}

/*---------------------------------------------------------------------------*
  Name:         InitRenderer

  Description:  Initializes Renderer  and Surface.

  Arguments:    pRenderer:  Pointer to Renderer to be initialized.
                pSurface:   Pointer to Surface to be initialized.

  Returns:      None.
 *---------------------------------------------------------------------------*/
static void InitRenderer(
                NNSG2dRendererInstance* pRenderer,
                NNSG2dRenderSurface*    pSurface)
{
    NNSG2dViewRect* pRect = &(pSurface->viewRect);
    SDK_NULL_ASSERT( pRenderer );
    SDK_NULL_ASSERT( pSurface );

    NNS_G2dInitRenderer( pRenderer );
    NNS_G2dInitRenderSurface( pSurface );
    

    pRect->posTopLeft.x = FX32_ONE * 0;
    pRect->posTopLeft.y = FX32_ONE * 0;
    pRect->sizeView.x = FX32_ONE * SCREEN_WIDTH;
    pRect->sizeView.y = FX32_ONE * SCREEN_HEIGHT;

    pSurface->pFuncOamRegister       = CallBackAddOam;
    pSurface->pFuncOamAffineRegister = CallBackAddAffine;

    NNS_G2dAddRendererTargetSurface( pRenderer, pSurface );
    NNS_G2dSetRendererImageProxy( pRenderer, &sImageProxy, &sPaletteProxy );
    NNS_G2dSetRendererSpriteZoffset( pRenderer, -FX32_ONE >> 2 );

}


/*---------------------------------------------------------------------------*
  Name:         ProcessInput

  Description:  Changes the cell display status according to the key input.
                Also switches the displayed cell and resets its status.

  Arguments:    pCellInfo:      Pointer to CellInfo reflecting the key input
                pOutputType:    Pointer to a buffer where the output method information is held
                numCells:       Number of cells that can be selected

  Returns:      None.
 *---------------------------------------------------------------------------*/
static void ProcessInput(CellInfo* pCellInfo, NNSG2dSurfaceType* pOutputType, u16 numCells)
{
    SDK_NULL_ASSERT( pCellInfo );
    SDK_NULL_ASSERT( pOutputType );

    G2DDemo_ReadGamePad();

    // Change current Cell
    if( G2DDEMO_IS_TRIGGER(PAD_BUTTON_A) )
    {
       pCellInfo->cellIdx++;
        if( pCellInfo->cellIdx >= numCells )
        {
            pCellInfo->cellIdx = 0;
        }
    }

    // Move Cell
    if( ! G2DDEMO_IS_PRESS(PAD_BUTTON_B) && ! G2DDEMO_IS_PRESS(PAD_BUTTON_Y) )
    {
        if ( G2DDEMO_IS_PRESS(PAD_KEY_UP) )
        {
            pCellInfo->pos.y -= FX32_ONE;
        }
        if ( G2DDEMO_IS_PRESS(PAD_KEY_DOWN) )
        {
            pCellInfo->pos.y += FX32_ONE;
        }
        if ( G2DDEMO_IS_PRESS(PAD_KEY_LEFT) )
        {
            pCellInfo->pos.x -= FX32_ONE;
        }
        if ( G2DDEMO_IS_PRESS(PAD_KEY_RIGHT) )
        {
            pCellInfo->pos.x += FX32_ONE;
        }
    }

    // Reset
    if( G2DDEMO_IS_TRIGGER(PAD_BUTTON_X) )
    {
        pCellInfo->rotate      = 0;
        pCellInfo->scale       = (NNSG2dFVec2){FX32_ONE, FX32_ONE};
        pCellInfo->pos         = (NNSG2dFVec2){CELL_INIT_POS_X, CELL_INIT_POS_Y};
    }

    // Toggle Output Type
    if( G2DDEMO_IS_TRIGGER(PAD_BUTTON_START) )
    {
        *pOutputType = (*pOutputType == NNS_G2D_SURFACETYPE_MAIN2D) ?
                            NNS_G2D_SURFACETYPE_MAIN3D:
                            NNS_G2D_SURFACETYPE_MAIN2D;
    }
}

/*---------------------------------------------------------------------------*
  Name:         LoadResources

  Description:  This function carries out the building of the multi-cell animation
                and the reading of character data and palette data.

  Arguments:    ppMCAnim:   Pointer that receives the pointer to the multi-cell
                            animation built.

  Returns:      None.
 *---------------------------------------------------------------------------*/
static void LoadResources(NNSG2dMultiCellAnimation** ppMCAnim)
{
    void* pBuf;
    
    NNS_G2dInitImageProxy( &sImageProxy );
    NNS_G2dInitImagePaletteProxy( &sPaletteProxy );
    
    //
    // Initialization related to MultiCell
    //
    {
        NNSG2dCellDataBank*             pCellBank = NULL;   // Cell data
        NNSG2dCellAnimBankData*         pAnimBank = NULL;   // Cell animation
        NNSG2dMultiCellDataBank*        pMCBank = NULL;     // Multi cell data
        NNSG2dMultiCellAnimBankData*    pMCABank = NULL;    // Multi-cell animation

        // Loads the cell data, cell animation, multicell data,
        // and multicell animation.
        // They are used on main memory until the end, so pBuf is not released.
        pBuf = G2DDemo_LoadNCER( &pCellBank, "data/MultiCellAnimation.NCER" );
        SDK_NULL_ASSERT( pBuf );
        pBuf = G2DDemo_LoadNANR( &pAnimBank, "data/MultiCellAnimation.NANR" );
        SDK_NULL_ASSERT( pBuf );
        pBuf = G2DDemo_LoadNMCR( &pMCBank, "data/MultiCellAnimation.NMCR" );
        SDK_NULL_ASSERT( pBuf );
        pBuf = G2DDemo_LoadNMAR( &pMCABank, "data/MultiCellAnimation.NMAR" );
        SDK_NULL_ASSERT( pBuf );


        //
        // Initializes the multicell animation entity.
        //
        {
            const NNSG2dMultiCellAnimSequence* pSequence;

            // Obtains the sequence to play back
            pSequence = NNS_G2dGetAnimSequenceByIdx( pMCABank, 0 );
            SDK_ASSERT( pSequence );

            // Constructs the multicell animation
            *ppMCAnim = G2DDemo_GetNewMultiCellAnimation( pAnimBank,
                                                          pCellBank,
                                                          pMCBank,
                                                          NNS_G2D_MCTYPE_SHARE_CELLANIM );
            SDK_NULL_ASSERT( *ppMCAnim );


            // Sets the sequence to play back to the multicell animation.
            NNS_G2dSetAnimSequenceToMCAnimation( *ppMCAnim, pSequence);
        }
    }

    //
    // Initialization related to VRAM
    //
    {
        //------------------------------------------------------------------------------
        // load character data for 2D
        {
            NNSG2dCharacterData* pCharData;

            pBuf = G2DDemo_LoadNCGR( &pCharData, "data/MultiCellAnimation.NCGR" );
            SDK_NULL_ASSERT( pBuf );

            // Loading For 2D Graphics Engine.
            NNS_G2dLoadImage2DMapping(
                pCharData,
                CHARA_BASE,
                NNS_G2D_VRAM_TYPE_2DMAIN,
                &sImageProxy );

            // Character data were copied into VRAM,
            // this pBuf is freed. Same below.
            G2DDemo_Free( pBuf );
        }

        // load character data for 3D (software sprite)
        {
            NNSG2dCharacterData* pCharData;

            pBuf = G2DDemo_LoadNCBR( &pCharData, "data/MultiCellAnimation.NCBR" );
            SDK_NULL_ASSERT( pBuf );

            // Loading For 3D Graphics Engine.
            NNS_G2dLoadImage2DMapping(
                pCharData,
                CHARA_BASE,
                NNS_G2D_VRAM_TYPE_3DMAIN,
                &sImageProxy );

            G2DDemo_Free( pBuf );
        }
        
        //------------------------------------------------------------------------------
        // load palette data
        {
            NNSG2dPaletteData* pPlttData;

            pBuf = G2DDemo_LoadNCLR( &pPlttData, "data/MultiCellAnimation.NCLR" );
            SDK_NULL_ASSERT( pBuf );

            // Loading For 2D Graphics Engine.
            NNS_G2dLoadPalette(
                pPlttData,
                PLTT_BASE,
                NNS_G2D_VRAM_TYPE_2DMAIN,
                &sPaletteProxy);

            // Loading For 3D Graphics Engine.
            NNS_G2dLoadPalette(
                pPlttData,
                PLTT_BASE,
                NNS_G2D_VRAM_TYPE_3DMAIN,
                &sPaletteProxy);

            G2DDemo_Free( pBuf );
        }
    }
}

//------------------------------------------------------------------------------
// Multicell animation is rendered using the Renderer module API.
//   
static void DrawUsingRendererAPI
(
    NNSG2dRendererInstance*         pRender,       // Renderer for rendering
    const NNSG2dFVec2*              pPos,          // Position data
    const NNSG2dMultiCellAnimation* pMCAnim        // MultiCellAnimation
)
{
    //
    // If USE_OPZ_API has been defined,
    // optimization hints will be given. At this point, specify:
    // Do not use Affine transformation. 
    // Do not change parameters.   
    //            
#if USE_OPZ_API
    NNS_G2dBeginRenderingEx( pRender, NNS_G2D_RDR_OPZHINT_NOT_SR |
                                      NNS_G2D_RDR_OPZHINT_LOCK_PARAMS );
#else// USE_OPZ_API
    NNS_G2dBeginRendering( pRender );
#endif//USE_OPZ_API

        NNS_G2dPushMtx();

            NNS_G2dTranslate( pPos->x, pPos->y, 0 );
            NNS_G2dDrawMultiCellAnimation( pMCAnim );

        NNS_G2dPopMtx();
    NNS_G2dEndRendering();
}

//------------------------------------------------------------------------------
// Render multicell animation using NNS_G2dMakeSimpleMultiCellToOams().
//    
static void DrawUsingSimpleAPI
(
    NNSG2dOamManagerInstance*       pOamManager,   // OAM manager for 2D rendering
    const NNSG2dFVec2*              pPos,          // Position data
    const NNSG2dMultiCellAnimation* pMCAnim        // MultiCellAnimation
)
{
    static GXOamAttr    temp[NUM_OF_OAM];
    u16                 numOamDrawn = 0;           // Number of OAMs to be rendered
    //
    // Writes an OBJ list equivalent to MultiCell to temporary.
    //
    numOamDrawn = NNS_G2dMakeSimpleMultiCellToOams(
                        temp,                           // output destination Oam buffer
                        NUM_OF_OAM,                     // output destination buffer length
                        &pMCAnim->multiCellInstance,    // output MultiCell
                        NULL,                           // affine conversion
                        pPos,                           // offset position
                        NULL,                           // Affine Index
                        FALSE );                        // double affine?

    SDK_ASSERT( numOamDrawn < NUM_OF_OAM );

    // Registers OBJ list to OAM Manager
    (void)NNS_G2dEntryOamManagerOam(
            pOamManager,
            temp,
            numOamDrawn );
}

/*---------------------------------------------------------------------------*
  Name:         NitroMain

  Description:  This is the main function.

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void NitroMain(void)
{
    // Renderer for rendering
    NNSG2dRendererInstance      render;
    // Main screen surface
    NNSG2dRenderSurface         surface;
    // OAM manager for 2D rendering
    NNSG2dOamManagerInstance    oamManager;
    // Type of rendering method
    NNSG2dSurfaceType           outputType = INIT_OUTPUT_TYPE;
    // Multi-cell animation entity
    NNSG2dMultiCellAnimation*   pMCAnim = NULL;
    
    //------------------------------------------
    // Initialize animation
    {
        G2DDemo_CommonInit();
        G2DDemo_CameraSetup();
        G2DDemo_MaterialSetup();
        G2DDemo_PrintInit();
        InitOamManager( &oamManager );
        InitRenderer( &render, &surface );
        LoadResources( &pMCAnim );
        
        spCurrentOam = &oamManager;
    }

    // start display
    {
        SVC_WaitVBlankIntr();
        GX_DispOn();
        GXS_DispOn();
    }

    // Main loop
    while( TRUE )
    {
        // Draw using a renderer?
        static BOOL     bUsingRenderer = TRUE;
        // Time required to draw
        static OSTick   timeDraw       = 0;
        // Cell information regarding location, etc.
        static CellInfo cellInfo = {
            0,                                  // No. of rendered cell
            0,                                  // Rotation angle
            {FX32_ONE, FX32_ONE},               // Scale
            {CELL_INIT_POS_X, CELL_INIT_POS_Y}  // Position of cell rendering
        };

        //------------------------------------------
        // Update display information and output method for cell for which input is read
        {
            ProcessInput(&cellInfo, &outputType, 0);
            surface.type = outputType;
            
            if( G2DDEMO_IS_TRIGGER(PAD_BUTTON_A) )
            {
                bUsingRenderer = bUsingRenderer^TRUE;
            }
        }
        

        //------------------------------------------
        // Drawing
        // Calculate the time required for rendering
        {
            timeDraw = OS_GetTick();
            
            // Use the renderer?
            if( bUsingRenderer )
            {
                DrawUsingRendererAPI( &render, &cellInfo.pos, pMCAnim );
            }else{
                //
                // Not processed if software sprite drawing.
                //
                if( outputType != NNS_G2D_SURFACETYPE_MAIN3D )
                {
                    DrawUsingSimpleAPI( &oamManager, &cellInfo.pos, pMCAnim );
                }
            }
                
            timeDraw = OS_GetTick() - timeDraw;
        }
        
        
        //------------------------------------------
        // Output display information
        {
            // Upper screen
            G2DDemo_PrintOutf(0, 0, "pos:   x=%3d     y=%3d",
                cellInfo.pos.x >> FX32_SHIFT, cellInfo.pos.y >> FX32_SHIFT);
            
            // Lower screen
            G2DDemo_PrintOutf( 0, 21, "TIME-draw  :%06ld usec\n", OS_TicksToMicroSeconds(timeDraw) );
            G2DDemo_PrintOut(0, 22, (outputType == NNS_G2D_SURFACETYPE_MAIN2D) ?
                   "OBJ           ": "SoftwareSprite");
            G2DDemo_PrintOut(0, 23, (bUsingRenderer) ?
                   "UsingRenderer           ": "UsingMakeCellToOams");
            
            // Software sprite drawing is not carried out during NNS_G2dMakeSimpleMultiCellToOams() drawing.
            if( outputType != NNS_G2D_SURFACETYPE_MAIN2D && !bUsingRenderer )
            {
                G2DDemo_PrintOutf( 5, 10, "Not supported." );
            }else{
                G2DDemo_PrintOutf( 5, 10, "              " );
            }
        }
        
        //------------------------------------------
        // Wait for V-Blank
        G3_SwapBuffers(GX_SORTMODE_AUTO, GX_BUFFERMODE_Z);
        SVC_WaitVBlankIntr();

        //------------------------------------------
        //
        // Write buffer contents to HW
        //
        {
            // Display information
            G2DDemo_PrintApplyToHW();

            // Cell
            NNS_G2dApplyOamManagerToHW( &oamManager );
            NNS_G2dResetOamManagerBuffer( &oamManager );
        }
        
        //------------------------------------------
        //
        // Update animation
        //
        NNS_G2dTickMCAnimation( pMCAnim, FX32_ONE );
    }
}

/*---------------------------------------------------------------------------*
  Name:         VBlankIntr

  Description:  Handles V-blank interrupts.

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void VBlankIntr(void)
{
    OS_SetIrqCheckFlag( OS_IE_V_BLANK );                   // checking VBlank interrupt
}



