/* 
 * 
 * Confidential Information of Telekinesys Research Limited (t/a Havok).  Not for disclosure or distribution without Havok's
 * prior written consent.This software contains code, techniques and know-how which is confidential and proprietary to Havok.
 * Level 2 and Level 3 source code contains trade secrets of Havok. Havok Software (C) Copyright 1999-2008 Telekinesys Research Limited t/a Havok. All Rights Reserved. Use of this software is subject to the terms of an end user license agreement.
 * 
 */

#include <Demos/demos.h>
#include <Demos/Animation/Api/Multithreading/SampleOnly/SampleOnlyMultithreadingDemo.h>

#include <Common/Base/Reflection/hkClass.h>
#include <Common/Base/Container/LocalArray/hkLocalArray.h>

#include <Common/Base/Monitor/Spu/hkSpuMonitorCache.h>

// Serialization
#include <Common/Serialize/Util/hkLoader.h>
#include <Common/Serialize/Util/hkRootLevelContainer.h>
#include <Common/Base/Reflection/Registry/hkTypeInfoRegistry.h>

// Asset mgt
#include <Demos/DemoCommon/Utilities/Asset/hkAssetManagementUtil.h>

// Scene data
#include <Common/SceneData/Scene/hkxScene.h>
#include <Animation/Animation/Rig/hkaPose.h>

// Skeletal Animation
#include <Animation/Animation/Rig/hkaSkeleton.h>
#include <Animation/Animation/hkaAnimationContainer.h>
#include <Animation/Animation/Animation/Interleaved/hkaInterleavedSkeletalAnimation.h>
#include <Animation/Animation/Playback/Multithreaded/hkaMultithreadedAnimationUtils.h>
#include <Animation/Animation/Animation/hkaAnimationBinding.h>
#include <Animation/Animation/Playback/Control/Default/hkaDefaultAnimationControl.h>
#include <Animation/Animation/Playback/hkaAnimatedSkeleton.h>

// For debug drawing
#include <Demos/DemoCommon/Utilities/Animation/AnimationUtils.h>

#include <Common/Base/Spu/Util/hkSpuUtil.h>
#include <Common/Base/Thread/JobQueue/hkJobQueue.h>

#include <Animation/Animation/Playback/Multithreaded/hkaAnimationJobs.h>
#include <Animation/Animation/Playback/Multithreaded/hkaAnimationJobQueueUtils.h>

#include <Animation/Animation/Animation/WaveletCompressed/hkaWaveletSkeletalAnimation.h>
#include <Animation/Animation/Animation/DeltaCompressed/hkaDeltaCompressedSkeletalAnimation.h>
#include <Animation/Animation/Animation/SplineCompressed/hkaSplineSkeletalAnimation.h>

#include <Animation/Animation/Animation/Mirrored/hkaMirroredSkeletalAnimation.h>
#include <Animation/Animation/Animation/Mirrored/hkaMirroredSkeleton.h>

#define NUM_ANIMATIONS 50

#if defined HK_PLATFORM_PS3
#define NUM_SPUS 5
#define	SPURS_THREAD_GROUP_PRIORITY 250
#define SPURS_HANDLER_THREAD_PRIORITY 1
#else
#define NUM_SPUS 1
#endif

#define NUM_ANIMATION_TYPES 5	// Change this if you add another type of animation (as two variants, CPU and SPU)

SampleOnlyMultithreadingDemo::SampleOnlyMultithreadingDemo( hkDemoEnvironment* env )
:	hkDefaultPhysicsDemo(env),
	m_jobQueue(&m_jobQueueCpuWaitSemaphore, &m_jobQueueTaskCompletionSemaphore, &m_jobQueueSpuWaitSemaphore),
	m_jobQueueCpuWaitSemaphore(0,1000), m_jobQueueTaskCompletionSemaphore(0, 1000), m_jobQueueSpuWaitSemaphore(0,1000),
	m_time(0.0f),
	m_semaphore(0, 1000 )
{
	hkpWorld::IgnoreForceMultithreadedSimulation ignoreForceMultithreaded;

	//
	// Setup the camera
	//
	{
		hkVector4 from(  -1.0f, -25.0f,  1.0f);
		hkVector4 to  (  0.0f,  0.0f,   0.0f);
		hkVector4 up  (  0.0f,  0.0f,   1.0f);
		setupDefaultCameras( env, from, to, up );
	}

	m_loader = new hkLoader();

	//Init the job queue
	m_jobQueue.m_jobPopFunc    = hkaAnimationJobQueueUtils::popAnimationJob;
	m_jobQueue.m_finishJobFunc = hkaAnimationJobQueueUtils::finishAnimationJob;

	m_spuUtil = HK_NULL;

	hkBool useSpu = (m_variantId < NUM_ANIMATION_TYPES);

	if(useSpu)
	{
		m_numSpus = NUM_SPUS;
		hkArray<hkSpuTaskParams> params;
		params.setSize(NUM_SPUS);
		for ( int i = 0; i < NUM_SPUS; ++i )
		{
			// Note that each spu thread uses 4 parameters. Here we set only 2 parameters, the job queue and an optional monitor cache.
			params[i].m_param1 = &m_jobQueue;

			// This parameter is only needed if you wish to collect timers from the SPU
			params[i].m_param2 = m_monitorCaches[i];

		}
#if defined(HK_PLATFORM_HAS_SPU)
		// Init the spu utility
		m_spuUtil = new hkSpuUtil();

#	ifdef HK_PLATFORM_PS3
		extern char _binary_hkaSpu_elf_start[];
		void* elf = _binary_hkaSpu_elf_start;

		CellSpurs* spurs = (CellSpurs*)hkAlignedAllocate<char> ( CELL_SPURS_ALIGN, CELL_SPURS_SIZE , HK_MEMORY_CLASS_DEMO );
		int ret = cellSpursInitialize (spurs, NUM_SPUS, SPURS_THREAD_GROUP_PRIORITY, SPURS_HANDLER_THREAD_PRIORITY, false);
		if (ret)
		{
			HK_ERROR(0x73e432a2, "cellSpursInitialize failed :" << ret);
		}
#	else
		HK_CELL_SPURS* spurs = HK_NULL;

		void* elf = (void*)(hkUlong)HK_TASK_ANIMATION;
#	endif

		hkSpuTaskSetParams unusedTaskSetParams;
		m_spuUtil->initSpursTaskset( NUM_SPUS, unusedTaskSetParams, spurs );
		m_spuUtil->setDefaultSpursTaskAndParams(params, elf );
		m_spuUtil->setNumActiveSpursTasks(NUM_SPUS);
#else
		m_spuUtil = HK_NULL;
#endif
	}

	// Get the rig
	{
		hkString assetFile = hkAssetManagementUtil::getFilePath("Resources/Animation/HavokGirl/hkRig.hkx");
		hkRootLevelContainer* container = m_loader->load( assetFile.cString() );
		HK_ASSERT2(0x27343437, container != HK_NULL , "Could not load asset");
		hkaAnimationContainer* ac = reinterpret_cast<hkaAnimationContainer*>( container->findObjectByType( hkaAnimationContainerClass.getName() ));

		HK_ASSERT2(0x27343435, ac && (ac->m_numSkeletons > 0), "No skeleton loaded");
		m_skeleton = ac->m_skeletons[0];
	}

	hkaInterleavedSkeletalAnimation* uncompressedAnimation;
	// Get the animation and the binding
	{
		hkString assetFile = hkAssetManagementUtil::getFilePath("Resources/Animation/HavokGirl/hkRunTurnLLoop.hkx");
		hkRootLevelContainer* container = m_loader->load( assetFile.cString() );
		HK_ASSERT2(0x27343437, container != HK_NULL , "Could not load asset");
		hkaAnimationContainer* ac = reinterpret_cast<hkaAnimationContainer*>( container->findObjectByType( hkaAnimationContainerClass.getName() ));

		HK_ASSERT2(0x27343435, ac && (ac->m_numAnimations > 0), "No animation loaded");
		uncompressedAnimation = static_cast<hkaInterleavedSkeletalAnimation*> (ac->m_animations[0]);

		HK_ASSERT2(0x27343435, ac && (ac->m_numBindings > 0), "No binding loaded");
		m_binding = ac->m_bindings[0];
	}


	{
		switch(m_variantId % NUM_ANIMATION_TYPES)
		{
			case 0:
			{
				m_animation = uncompressedAnimation;
				break;
			}
			case 1: // Delta
			{
				hkaDeltaCompressedSkeletalAnimation::CompressionParams p;
				p.m_quantizationBits = 16;
				p.m_blockSize = 8;

				m_animation = new hkaDeltaCompressedSkeletalAnimation(*uncompressedAnimation, p);
				m_binding->m_animation = m_animation;
				break;
			}
			case 2: // Wavelet
			{
				hkaWaveletSkeletalAnimation::CompressionParams p;
				p.m_quantizationBits = 16;
				p.m_blockSize = 8;
				p.m_truncProp = .25;

				m_animation = new hkaWaveletSkeletalAnimation(*uncompressedAnimation, p);
				m_binding->m_animation = m_animation;
				break;
			}
			case 3: // Mirrored
			{
				hkaWaveletSkeletalAnimation* w = HK_NULL;
				{
					hkaWaveletSkeletalAnimation::CompressionParams p;
					p.m_quantizationBits = 16;
					p.m_blockSize = 8;
					p.m_truncProp = .25;

					w = new hkaWaveletSkeletalAnimation(*uncompressedAnimation, p);
				}
				makeMirroredAnimation( w );	
				w->removeReference();
				break;
			}
			case 4: // Spline
			{
				hkaSplineSkeletalAnimation::TrackCompressionParams p;
				hkaSplineSkeletalAnimation::AnimationCompressionParams a;

				m_animation = new hkaSplineSkeletalAnimation( *uncompressedAnimation, p, a );
				m_binding->m_animation = m_animation;

				break;
			}

		}
	}

	// Create controls
	for (int i=0; i< NUM_ANIMATIONS; i++)
	{
		hkaDefaultAnimationControl* control = new hkaDefaultAnimationControl(m_binding);
		control->setLocalTime(i * 0.05f);
		m_controls.pushBack( control );
	}

	setupGraphics( );
}

void SampleOnlyMultithreadingDemo::makeMirroredAnimation(hkaSkeletalAnimation* original)
{
	hkObjectArray< hkString > ltag;
	hkObjectArray< hkString > rtag;

	ltag.pushBack( " L " );
	ltag.pushBack( "EyeL" );

	rtag.pushBack( " R " );
	rtag.pushBack( "EyeR" );

	hkaMirroredSkeleton *mirroredSkeleton = new hkaMirroredSkeleton( m_skeleton );

	mirroredSkeleton->computeBonePairingFromNames( ltag, rtag );

	hkaMirroredSkeletalAnimation *mirroredAnimation = new hkaMirroredSkeletalAnimation( original, m_binding, mirroredSkeleton );
	m_binding = mirroredAnimation->createMirroredBinding();
	m_animation= mirroredAnimation;

	mirroredSkeleton->removeReference();

	hkVector4 v_mir;

	hkQsTransform Q_root( hkQsTransform::IDENTITY );
	hkQsTransform Q_child( hkQsTransform::IDENTITY );
	hkQsTransform Q_nubs( hkQsTransform::IDENTITY );

	v_mir.set( 0, 0, 1 );

	const hkReal s2o2 = hkMath::sqrt( 2.0f ) / 2.0f;

	Q_root.m_rotation.set( 0, 0, s2o2, -s2o2 );
	Q_child.m_rotation.set( 0.5f, 0.5f, 0.5f, -0.5f );
	Q_nubs.m_rotation.set( -s2o2, 0, -s2o2, 0 );

	mirroredSkeleton->setMirrorAxis( v_mir );
	mirroredSkeleton->setRootOrientation( Q_root );
	mirroredSkeleton->setChildOrientation( Q_child );
	mirroredSkeleton->setNubOrientation( Q_nubs );
	mirroredSkeleton->setMixedMode( false );
}

SampleOnlyMultithreadingDemo::~SampleOnlyMultithreadingDemo()
{
	delete m_loader;

	// If we created a mirrored animation, clean up binding
	if ((m_variantId % NUM_ANIMATION_TYPES) == 3)
	{
		hkaMirroredSkeletalAnimation::destroyMirroredBinding( m_binding );
	}

	// If we created a compressed animation, clean it up.
	if ((m_variantId % NUM_ANIMATION_TYPES) != 0)
	{
		m_animation->removeReference();
	}

	for (int i=0; i< NUM_ANIMATIONS; i++)
	{
		m_controls[i]->removeReference();
	}

	if ( m_variantId >= NUM_ANIMATION_TYPES)	// If we're on CPU, clean up jobqueue
	{
#if defined(HK_SIMULATE_SPU_DMA_ON_CPU)	
		delete m_jobQueue.m_data;
#endif
	}
}

hkDemo::Result SampleOnlyMultithreadingDemo::stepDemo()
{
	HK_TIMER_BEGIN("StepAnimationsAndSampleSetup", HK_NULL);

	for (int i=0; i< NUM_ANIMATIONS; i++)
	{
		// Advance the animation - this happens in single threaded mode
		m_controls[i]->update( m_timestep );
	}

	// Create an output buffer - it must persist while the job is executing
	hkLocalArray<hkaPose*> pose( NUM_ANIMATIONS );
	pose.setSize( NUM_ANIMATIONS );
	for (int p=0; p < NUM_ANIMATIONS; p++)
	{
		pose[p] = new hkaPose(m_skeleton);
	}

	// This data must persist while the spu job is executing
	hkaSampleAnimationJob::AnimationData* animData = new hkaSampleAnimationJob::AnimationData[ NUM_ANIMATIONS ];

	int maxBufferSize = 0;
	for (int i=0 ; i < NUM_ANIMATIONS; i++)
	{
		hkReal localTime = m_controls[i]->getLocalTime();
		m_animation->getFrameAndDelta( localTime, animData[i].m_frameIndex, animData[i].m_frameDelta );
		animData[i].m_maxTransformTrack = m_animation->m_numberOfTransformTracks;
		animData[i].m_maxFloatTrack = m_animation->m_numberOfFloatTracks;
		animData[i].m_animationType = m_animation->getType();
		animData[i].m_numValidChunks = m_animation->getNumDataChunks( localTime );
		m_animation->getDataChunks( localTime, animData[i].m_chunks, animData[i].m_numValidChunks);
		animData[i].m_poseOut = pose[i]->writeAccessPoseLocalSpace().begin();
		animData[i].m_floatSlotsOut = pose[i]->writeAccessFloatSlotValues().begin();

		HK_ASSERT2(0x19e27e5b, animData[i].isValid(), "Data is invalid!");	// Sanity check before we hand off the jobs

		// Here we can (optionally) try and minimise the buffer size to avoid stack overflow for large animations.
		int chunkSize = m_animation->getMaxSizeOfCombinedDataChunks();
		if ( chunkSize > maxBufferSize )
		{
			maxBufferSize = chunkSize;
		}
	}

	HK_TIMER_END();

	// We'll distribute the work evenly over the spus
	hkObjectArray<hkaSampleAnimationJob> sampleJobs( NUM_SPUS );
	sampleJobs.setSize( NUM_SPUS );

	// Do the sampling and blending
	hkStopwatch timer;
	timer.start();

	hkBool useSpu = (m_variantId < NUM_ANIMATION_TYPES);

	if(useSpu)
	{
		for (int s=0; s < NUM_SPUS; s++)
		{
			const int ANIMS_PER_SPU = NUM_ANIMATIONS / NUM_SPUS;

			// Initialize the job to sample all the animations
			sampleJobs[s].m_animData = &animData[ s * ANIMS_PER_SPU ];
			sampleJobs[s].m_numAnims = ANIMS_PER_SPU;
			sampleJobs[s].m_jobDone = &m_semaphore;

			// Here we can (optionally) try and minimise the buffer size to avoid stack overflow for large animations.
			sampleJobs[s].m_bufferSize = maxBufferSize;

			// Add the job - execution begins immediately on the SPU
			// Since however this uses a critical section, and the job set up code above is realtively fast, 
			// we defer adding until all jobs are set up, and then use an addJobBatch - this willbe faster.
			//HK_TIMER_BEGIN("AddJob", HK_NULL);
			//m_jobQueue.addJob( *reinterpret_cast<hkJobQueue::JobQueueEntry*>(&job[s]), hkJobQueue::JOB_HIGH_PRIORITY, hkJobQueue::JOB_TYPE_HINT_SPU );
			//HK_TIMER_END();
		}

		// Add jobs as a batch
		{
			HK_TIMER_BEGIN("AddJobBatch", HK_NULL);
			hkArray<hkJobQueue::JobQueueEntry*> jobQueueEntries;
			jobQueueEntries.setSize(NUM_SPUS);
			for (int i = 0; i < NUM_SPUS; ++i )
			{
				jobQueueEntries[i] = reinterpret_cast<hkJobQueue::JobQueueEntry*>(&sampleJobs[i]);
			}

			// Add the job - execution begins immediately on the SPU
			m_jobQueue.addJobBatch( jobQueueEntries, hkJobQueue::JOB_HIGH_PRIORITY, hkJobQueue::JOB_TYPE_HINT_SPU );
			HK_TIMER_END();
		}


#if defined(HK_PLATFORM_HAS_SPU)
		m_spuUtil->startDefaultSpursTasks();
#endif
	}
	else
	{
		// Sample animation on the PPU is single threaded mode
		for (int i = 0; i < NUM_ANIMATIONS; ++i )
		{
			// N.B. We are putting track data directly into a pose, which necessarily assumes that the binding is the identity.
			// In general of course this will not be true, but for the purpose of this demo it is simplest to do this rather
			// than sample into a temporary array and track data and copy over into the bone (pose) data.
			m_animation->sampleTracks( m_controls[i]->getLocalTime(), pose[i]->writeAccessPoseLocalSpace().begin(), HK_NULL, HK_NULL );
		}
	}

	// Wait for all threads to finish
	if (useSpu)
	{

		// Wait for all jobs to complete
		for (int s=0; s < NUM_SPUS; s++)
		{
			m_semaphore.acquire();
		}

		// Wait for the actual tasks to finish. This makes sure all timer information will have finished dma-ing to main memory
#if defined(HK_PLATFORM_HAS_SPU)
		for (int i = 0; i < m_spuUtil->getNumActiveSpursTasks(); ++i)
		{
			m_jobQueue.m_taskCompletionSemaphore->acquire();
		}
#endif

	}

	timer.stop();

	delete [] animData;

	// Render the pose buffers
	{

		HK_TIMER_BEGIN("syncModelSpace", HK_NULL);
		for (int i = 0; i < NUM_ANIMATIONS; ++i )
		{
			pose[i]->syncModelSpace();
		}
		HK_TIMER_END();

		hkQsTransform worldFromModel (hkQsTransform::IDENTITY);
		for (int i = 0; i < NUM_ANIMATIONS; ++i )
		{
			worldFromModel.m_translation.set( hkReal(i - 1 - (NUM_ANIMATIONS>>2)), 0, 0);
			AnimationUtils::drawPose( *pose[i], worldFromModel );
			delete pose[i];
		}

		char buf[255];
		hkString::sprintf(buf, "Total Sample Time for %d animations (uSecs): %d", NUM_ANIMATIONS, (int)( timer.getSplitSeconds() * 1000000 ) );
		const int h = m_env->m_window->getHeight();
		m_env->m_textDisplay->outputText( buf, 100, h - 40 );
		m_time += m_timestep;
	}

	return DEMO_OK;
}




void SampleOnlyMultithreadingDemo::setLoadedElfsForTimerProcessing( hkUlong elfs[5] )
{
#if defined(HK_PLATFORM_PS3)
	extern char _binary_hkaSpu_elf_start[];
	elfs[HK_ELF_TYPE_ANIMATION] = (hkUlong)_binary_hkaSpu_elf_start;
#endif
}


#if defined(HK_COMPILER_MWERKS)
#	pragma force_active on
#	pragma fullpath_file on
#endif

static const char helpString[] = \
"Shows animation decompression and blending on the SPU" ;

#ifdef HK_PLATFORM_HAS_SPU
HK_DECLARE_DEMO_VARIANT(SampleOnlyMultithreadingDemo, OTHER, "SPU - Uncompressed", 0,       "Sampling on SPU",              "Sampling on SPU");
HK_DECLARE_DEMO_VARIANT(SampleOnlyMultithreadingDemo, OTHER, "SPU - Delta Compressed", 1,   "Delta decompression on SPU",   "Delta decompression on SPU");
HK_DECLARE_DEMO_VARIANT(SampleOnlyMultithreadingDemo, OTHER, "SPU - Wavelet Compressed", 2, "Wavelet decompression on SPU", "Wavelet decompression on SPU");
HK_DECLARE_DEMO_VARIANT(SampleOnlyMultithreadingDemo, OTHER, "SPU - Mirrored (Wavelet)", 3, "Mirrored on SPU", "Mirrored on SPU");
HK_DECLARE_DEMO_VARIANT(SampleOnlyMultithreadingDemo, OTHER, "SPU - Spline ", 4, "Spline on SPU", "Spline on SPU");
#endif
HK_DECLARE_DEMO_VARIANT(SampleOnlyMultithreadingDemo, ANIMATION_SERIALIZE, "CPU - Uncompressed", 5,       "Sampling on CPU",              "Sampling on CPU");
HK_DECLARE_DEMO_VARIANT(SampleOnlyMultithreadingDemo, ANIMATION_SERIALIZE, "CPU - Delta Compressed", 6,   "Delta decompression on CPU",   "Delta decompression on CPU");
HK_DECLARE_DEMO_VARIANT(SampleOnlyMultithreadingDemo, ANIMATION_SERIALIZE, "CPU - Wavelet Compressed", 7, "Wavelet decompression on CPU", "Wavelet decompression on CPU");
HK_DECLARE_DEMO_VARIANT(SampleOnlyMultithreadingDemo, ANIMATION_SERIALIZE, "CPU - Mirrored (Wavelet)", 8, "Mirrored on CPU", "Mirrored on CPU");
HK_DECLARE_DEMO_VARIANT(SampleOnlyMultithreadingDemo, ANIMATION_SERIALIZE, "CPU - Spline", 9, "Spline on CPU", "Spline on CPU");


/*
* Havok SDK - NO SOURCE PC DOWNLOAD, BUILD(#20080529)
*
* Confidential Information of Havok.  (C) Copyright 1999-2008 
* Telekinesys Research Limited t/a Havok. All Rights Reserved. The Havok
* Logo, and the Havok buzzsaw logo are trademarks of Havok.  Title, ownership
* rights, and intellectual property rights in the Havok software remain in
* Havok and/or its suppliers.
*
* Use of this software for evaluation purposes is subject to and indicates 
* acceptance of the End User licence Agreement for this product. A copy of 
* the license is included with this software and is also available at
* www.havok.com/tryhavok
*
*/
