//////////////////////////////////////////////////////////////////////////////
//
// main.cpp
//
// Copyright (c) 2005 Rebellion. All rights reserved.
//
// Description:
//
//     main function for PFX update on SPU
//
//////////////////////////////////////////////////////////////////////////////

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

#include "Core/Asura.h"

// Asura independent includes
#include "Core/Memory/Asura_Mem_ResetOnlyAllocator.h"
#include "Core/Asura_Hash.h"
#include "Core/Asura_Graph.h"
#include "Core/Asura_Maths.h"
#include "Jobs/Asura_Job_PFX.h"
#include "Rendering/Asura_Colour.h"	// for RGBA
#include "Rendering/Asura_RenderStates.h"	// for translucency mode
#include "SpecialFX/Asura_PerlinNoise_System.h"
#include "SpecialFX/PFX/Asura_PFX_Classes.h"
#include "SpecialFX/PFX/Asura_PFX_Effect.h"
#include "SpecialFX/PFX/Asura_PFX_Templates.h"

#include "../PS3/PPU_SPU_UTILS/Asura_PS3_SPU_HelperStructs.h"

// Asura PS3 non-SPU includes (relative to PS3/SPU which is sort of a platform)
#include "../PS3/SpecialFX/Asura_PS3_PFXRender.h"

// Library includes
#include <cell/atomic.h>
#include <cell/dma.h>
#include <cell/sync.h>
#include <spu_printf.h>
 
#include <cell/spurs/barrier.h>
#include <cell/spurs/task.h>			// cellSpursTaskMain
#include <cell/spurs/lfqueue.h>

#include <sys/spu_thread.h>

#if 0
    #define Asura_PFX_DebugTrace( args ) \
        { spu_printf args ; }
#else
    #define Asura_PFX_DebugTrace( args ) \
        { }
#endif

extern u_int _end; // End of executable (plus static data, etc.)

static u_int g_auAtomicBuffer[32] __attribute__(( aligned(128) ));

static const u_int uSTACK_SIZE_TO_ALLOW = 4 * 1024;

static const u_int uSOURCE_TEMPLATE_BUFFER_SIZE = 1536;
static char g_acSourceTemplateBuffer[uSOURCE_TEMPLATE_BUFFER_SIZE] __attribute__(( aligned(16) ));

static const u_int uPARTICLE_TEMPLATE_BUFFER_SIZE = 2048;
static char g_acParticleTemplateBuffer[uPARTICLE_TEMPLATE_BUFFER_SIZE] __attribute__(( aligned(16) ));

static const u_int uJOB_BFR_ALIGNED_SIZE = (sizeof(Asura_Job_PFX_Update) + 15) & ~15;
static char g_acJobBuffer[uJOB_BFR_ALIGNED_SIZE] __attribute__(( aligned(16) ));
typedef Asura_Job_PFX_Update Asura_Job_PFX_Update_ALIASED __attribute__(( __may_alias__ ));

// This is essentially just going to be allocating a copy of the data in the
// two templates' data blocks so account for that plus some alignment, etc.
static const u_int uBYTES_TO_RESERVE_FOR_ALLOCATOR = uSOURCE_TEMPLATE_BUFFER_SIZE + uPARTICLE_TEMPLATE_BUFFER_SIZE + 512;
static Asura_Mem_ResetOnlyAllocatorWithInternalBfr<uBYTES_TO_RESERVE_FOR_ALLOCATOR> g_xAllocator;

// Replacements for stdlib allocation functions to use the above allocator
// instead.
namespace std
{
    void* malloc( size_t uSize ) { return g_xAllocator.Allocate( uSize ); }

    void free( void* pvMemory ) { g_xAllocator.Deallocate( pvMemory ); }

    // Unsupported (since not currently required).
    void* memalign( size_t, size_t ) { Asura_Assert( false, "CALLED MEMALIGN!" ); return NULL; }
};

int cellSpursTaskMain( qword argTask, uint64_t argTaskset )    
{
	u_int uSpuNum = 0;								
	Asura_PFX_DebugTrace(( "HELLO from SPU PFX thread ID %u\n", uSpuNum ));

	PFX_TaskParameters*	pTaskParams = (PFX_TaskParameters*)(&argTask);

	uint64_t uNumUnfinishedAddr = pTaskParams->uNumUnfinishedAddr;	// TODO: coming in from argTask

	Asura_PFX_DebugTrace(( "uNumUnfinishedAddr 0x%X 0x%X\n", uNumUnfinishedAddr, static_cast<uint32_t>(uNumUnfinishedAddr)));

	// minimal init of Asura Core
	Asura_Random::Initialise();

  	// first, initialise perlin noise
  	Asura_PerlinNoise_System::Initialise();

    enum
    {
        DMATAG_GET_JOB,
        DMATAG_GET_TEMPLATES,
        DMATAG_GET_UPDATE,

        DMATAG_PUT_UPDATE,
        DMATAG_PUT_RENDER,
    };

    // These have to be 0 under the current SDK.
	const u_int uTID = 0;
	const u_int uRID = 0;

	Asura_PFX_DebugTrace(( "SPU PFX thread ID %u entering main loop\n", uSpuNum ));

    // Calculate available memory.
    const u_int uEndOfExecutable = (reinterpret_cast<u_int>(&_end) + 15) & ~15;
    const u_int uTotalMemoryAvailable = 256 * 1024 - (uEndOfExecutable + uSTACK_SIZE_TO_ALLOW);
    void* const pvStartOfAvailableMemory = reinterpret_cast<void*>( uEndOfExecutable );

    // Calculate how many particles we can therefore support within a single
    // job.
    const u_int uMaxParticles = (uTotalMemoryAvailable - 32) // Account for 16-byte alignment of the buffers
                                /
                                (sizeof(Asura_PFX_Particle) + NUM_VERTICES_PER_PARTICLE * sizeof(Asura_PS3_ParticleRender_Vertex));

    // And setup buffers accordingly.
    Asura_PFX_Particle* const pxParticleUpdateDataBfr = reinterpret_cast<Asura_PFX_Particle*>( uEndOfExecutable );
    const u_int uSecondBfrAddr = (uEndOfExecutable + uMaxParticles * sizeof(Asura_PFX_Particle) + 15) & ~15;
    Asura_PS3_ParticleRender_Vertex* const pxOutputVertexBfr = reinterpret_cast<Asura_PS3_ParticleRender_Vertex*>( uSecondBfrAddr );

    if( !uSpuNum )
    {
        Asura_PFX_DebugTrace(( "end of exe: %u\nmem available: %u bytes\n", uEndOfExecutable, uTotalMemoryAvailable ));

        Asura_PFX_DebugTrace(( "addresses:\n\tjob bfr = 0x%08X\n\tparticle update bfr = 0x%08X\n\trender bfr = 0x%08X\n", (u_int)g_acJobBuffer, (u_int)pxParticleUpdateDataBfr, (u_int)pxOutputVertexBfr ));

        Asura_PFX_DebugTrace(( "max particles: %u\n", uMaxParticles ));
    }

    CellSpursLFQueuePopContainer xQueuePopContainer;
    cellSpursLFQueuePopContainerInitialize( &xQueuePopContainer, g_acJobBuffer, DMATAG_GET_JOB );

	while( true )
	{
        // All allocations take place within this loop and everything should
        // be deallocated by the end (or at least once everything's out of
        // scope).
        g_xAllocator.Wipe();

		// Get next job from queue and block until it finishes copying.
		//cellSyncQueuePop( argTaskset, g_acJobBuffer, DMATAG_GET_JOB );
		int iRes = 0;
		while ( (iRes = cellSpursLFQueueTryPopBegin(argTaskset, &xQueuePopContainer)) != 0 )
		{
			Asura_PFX_DebugTrace(( "Waiting for job! %d\n", iRes ));
		}
		cellSpursLFQueuePopEnd( argTaskset, &xQueuePopContainer );

		cellDmaWaitTagStatusAll( 1 << DMATAG_GET_JOB );

		Asura_PFX_DebugTrace(( "Got a job!\n" ));

	    const Asura_Job_PFX_Update* pxJob = reinterpret_cast<const Asura_Job_PFX_Update_ALIASED*>( g_acJobBuffer );

        const u_int uNumParticles = Asura_Maths::Min( pxJob->GetNumParticles(), uMaxParticles );
		const u_int uNumParticlesToUpdate = Asura_Maths::Min( pxJob->GetNumParticlesToUpdate(), uNumParticles );

        if( uNumParticles < pxJob->GetNumParticles() )
        {
            spu_printf( "ERROR: Clamped num particles in effect (was %u, max is %u)\n", pxJob->GetNumParticles(), uMaxParticles );
        }

		uint64_t uRenderData = reinterpret_cast<uint64_t>(pxJob->GetPlatformRenderData());

		u_int uAlignedParticlesTransferSize        = (uNumParticles * sizeof(Asura_PFX_Particle) + 15) & ~15;
		u_int uAlignedSourceTemplateTransferSize   = (pxJob->GetSourceTemplateDataBlockSize()    + 15) & ~15;
		u_int uAlignedParticleTemplateTransferSize = (pxJob->GetParticleTemplateDataBlockSize()  + 15) & ~15;

		Asura_PFX_DebugTrace(( "SPU PFX thread %u got job (%u particle%s, PPU addr = 0x%llX), transfer bytes = %u)\n", 
            uSpuNum, uNumParticles, (uNumParticles == 1 ? "" : "s"), pxJob->GetParticles(), uAlignedParticlesTransferSize ));

        // Check we can proceeed.
        {
            bool bOK = true;

            if( uAlignedSourceTemplateTransferSize > uSOURCE_TEMPLATE_BUFFER_SIZE )
            {
                spu_printf( "ERROR: source template too large (%u bytes; max is %u)\n", uAlignedSourceTemplateTransferSize, uSOURCE_TEMPLATE_BUFFER_SIZE );
                bOK = false;
            }

			if( uAlignedParticleTemplateTransferSize > uPARTICLE_TEMPLATE_BUFFER_SIZE )
            {
                spu_printf( "ERROR: particle template too large (%u bytes; max is %u)\n", uAlignedParticleTemplateTransferSize, uPARTICLE_TEMPLATE_BUFFER_SIZE );
                bOK = false;
            }

            if( !bOK )
            {
			    cellAtomicDecr32( g_auAtomicBuffer, uNumUnfinishedAddr );
                continue;
            }
		}

        // Start fetches from main memory.
		cellDmaGet( g_acSourceTemplateBuffer, reinterpret_cast<uint64_t>(pxJob->GetSourceTemplateDataBlock()), uAlignedSourceTemplateTransferSize, DMATAG_GET_TEMPLATES, uTID, uRID );
        cellDmaGet( g_acParticleTemplateBuffer, reinterpret_cast<uint64_t>(pxJob->GetParticleTemplateDataBlock()), uAlignedParticleTemplateTransferSize, DMATAG_GET_TEMPLATES, uTID, uRID );

        // Before fetching new input particle data, ensure our previous
        // output into the same buffer has finished copying to the PPU.
        cellDmaWaitTagStatusAll( 1 << DMATAG_PUT_UPDATE );
		cellDmaLargeGet( pxParticleUpdateDataBfr, reinterpret_cast<uint64_t>(pxJob->GetParticles()), uAlignedParticlesTransferSize, DMATAG_GET_UPDATE, uTID, uRID );

		Asura_PFX_Source_Template xSourceTemplate; 
		Asura_PFX_Particle_Template xPcleTemp;
		const Asura_PFX_Particle_Template* pxPcleTempToUse;

        // Wait for input template data to finishing copying from PPU then
        // unpack it.
        {
            cellDmaWaitTagStatusAll( 1 << DMATAG_GET_TEMPLATES );

		    xSourceTemplate.FillInFromDataBlock( g_acSourceTemplateBuffer, pxJob->GetSourceTemplateDataBlockSize() );

		    // only need particle template if using air resistance or if rendering
	  	    if( uRenderData || xSourceTemplate.m_bARPTS )
		    {
			    xPcleTemp.FillInFromDataBlock( g_acParticleTemplateBuffer, pxJob->GetParticleTemplateDataBlockSize() );
			    pxPcleTempToUse = &xPcleTemp;

			    Asura_PFX_DebugTrace(( "particle template fetched\n" ));
		    }
		    else
		    {
                pxPcleTempToUse = NULL;

			    Asura_PFX_DebugTrace(( "particle template not needed\n" ));
		    }
        }

        // Wait for input particle update data to finish copying, process
        // it, then start it copying back.
        {
		    cellDmaWaitTagStatusAll( 1 << DMATAG_GET_UPDATE );

		    Asura_PFX_Effect::UpdateParticles( pxParticleUpdateDataBfr,
                                               uNumParticlesToUpdate,
                                               &xSourceTemplate, 
			                                   pxJob->GetUseCollisionPlane(),
			                                   pxJob->GetCollisionPosition(),
			                                   pxJob->GetCollisionDirection(),
			                                   pxJob->GetUsePointGravity(),
			                                   pxJob->GetGravityPosition(),
			                                   pxJob->GetSizeScale(),
			                                   pxJob->GetTimeSlice(),
			                                   pxJob->GetTurbulencePhase(),
			                                   pxPcleTempToUse );

		    cellDmaLargePut( pxParticleUpdateDataBfr, reinterpret_cast<uint64_t>(pxJob->GetParticles()), uNumParticlesToUpdate * sizeof(Asura_PFX_Particle), DMATAG_PUT_UPDATE, uTID, uRID );
        }

		Asura_PFX_DebugTrace(( "particles written\n" ));

		// do the rendering
		if( uRenderData )
		{
            Asura_PFX_DebugTrace(( "render data ptr before processing this job: %llX\n", uRenderData ));

			u_int uNumVerts = NUM_VERTICES_PER_PARTICLE * uNumParticles;
			u_int uBufferBytesUsed = uNumVerts * sizeof(Asura_PS3_ParticleRender_Vertex);

			bool bPaused = (pxJob->GetTimeSlice() == 0.0f);
			for (int iCamera = 0; iCamera < pxJob->GetNumberOfCameras(); ++iCamera)
			{
				// check visibility flags
				if ( !(pxJob->GetCameraFlags() & (1 << iCamera)) )
				{
					continue;
				}

                // Wait for previous render data output to finish copying
                // first. NB: We lose some parallelism here with multiple
                // cameras since we have to immediately wait before rendering
                // for the next one.
                cellDmaWaitTagStatusAll( 1 << DMATAG_PUT_RENDER );

				Asura_PS3_RenderPFXForCurrentCamera( pxOutputVertexBfr, pxJob->GetSizeScale(), 
					&pxJob->GetEffectOrientation(), pxParticleUpdateDataBfr, uNumParticles, xPcleTemp, 
					pxJob->GetLightColour(), pxJob->GetCameraDeltaX(iCamera), 
					pxJob->GetCameraDeltaY(iCamera), pxJob->GetCameraPosition(iCamera), bPaused );

                cellDmaLargePut( pxOutputVertexBfr, uRenderData, uBufferBytesUsed, DMATAG_PUT_RENDER, uTID, uRID );

				// move mapped ptr along for subsequent cameras
				uRenderData += uBufferBytesUsed;
            }

            Asura_PFX_DebugTrace(( "render data ptr after processing this job: %llX\n", uRenderData ));
        }
		else
		{
			Asura_PFX_DebugTrace(( "no render data\n" ));
		}

		// decrement number of unfinished jobs. 
		cellAtomicDecr32( g_auAtomicBuffer, uNumUnfinishedAddr );
	}

  	return 0;
}



