
//////////////////////////////////////////////////////////////////////////////
//
// Asura_Platform_VideoToTexture.cpp
// Author    : Jason Turner
// Copyright (c) 2008 Rebellion. All rights reserved.
//
// It uses BinkCopyToBuffer() to grab converted RGB frames.
//		The YUV->RGB colour conversion is done in software, which is slightly slower than doing it ourselves using shaders. 
//		See binktextures.h for converting using shaders OR use BinkCopyToBuffer(,,,BINKSURFACEYV12).
//////////////////////////////////////////////////////////////////////////////

#include "Core/Asura.h"
#ifdef ASURA_USE_VIDEO_TO_TEXTURE_SYSTEM

//
// Local defines
//

#define ASURA_BINK_NON_MULTITHREADING
//#define ENABLE_DEBUG_LOG_RENDER_TIME
//#define ENABLE_DEBUG_LOG_MEM_ALLOCS
//#define ASURA_BINK_SET_PAN_AND_VOLUME

// Bink additional defines
#define GETTRACKFREQ(val) ((val)&0xffff)
#define GETTRACKBITS(val) (((((val)>>30)&1)<<3)+8)
#define GETTRACKCHANS(val) ((((val)>>29)&1)+1)

//
// Asura independent includes
//
#include "Core/Asura_RingBuffer.h"
#include "Core/Asura_String.h"
#include "Core/Asura_Timers.h"
#include "Core/Memory/Asura_Mem.h"
#include "Debugging/Asura_Assert.h"
#include "Debugging/Asura_Logfile.h"
#include "FMV/Asura_VideoToTexture.h"
#include "Rendering/Asura_Render.h"
#include "Rendering/Asura_Viewport.h"
//
// Asura platform includes
//

//
// PC includes
//

// Asura_Direct3D.h needs to be included before Bink.h
#include "Rendering/Asura_Direct3D.h"
#include "Rendering/Asura_Direct3D_Shaders.h"
#include "BinkPC/bink.h"
#include "Sound/Asura_XAudio2.h"
#include "Sound/Asura_XAudio2_Buffer.h"

extern Direct3D_BaseTexture* evilg_apxTextureStorage[];

#if defined( _WIN64)
	#undef BinkSoundUseDirectSound
	#define BinkSoundUseDirectSound( _A)

	#undef BinkGoto
	#define BinkGoto( _A, _B, _C)

	#undef BinkWait
	#define BinkWait( _A) (false)

	#undef BinkDoFrame
	#define BinkDoFrame( _A)

	#undef BinkNextFrame
	#define BinkNextFrame( _A)

	#undef BinkOpen
	#define BinkOpen( _A, _B) (NULL)

	#undef BinkClose
	#define BinkClose( _A)

	#undef BinkPause
	#define BinkPause( _A, _B)

	#undef BinkSetVolume
	#define BinkSetVolume( _A, _B, _C)

	#undef BinkShouldSkip
	#define BinkShouldSkip( _A) (false)

	#undef BinkSetSoundTrack
	#define BinkSetSoundTrack( _A, _B)

	#undef BinkCopyToBuffer
	#define BinkCopyToBuffer( _1, _2, _3, _4, _5, _6, _7)
#endif

////////////////////////////////////////////////////////////////////////////////
//
// Asura_VideoToTexture_PCData
//
////////////////////////////////////////////////////////////////////////////////

class Asura_VideoToTexture_PCData
{
public:
	friend class Asura_VideoToTexture;

	Asura_VideoToTexture_PCData();
	~Asura_VideoToTexture_PCData();

	char					m_szFilename[MAX_PATH];

private:
	static void PTR4* RADLINK OnMemAlloc(U32 num_bytes);
	static void RADLINK OnMemFree(void PTR4* ptr);
	static void VoiceBufferCallback( void* pContext);

	static const u_int		s_uPCMBufferSize = 512 * 1024;
	static u_int			s_uDropSize;	// size to drop from the back of the ringbuffer (when XAudio's finished with it).

	bool InitialiseRenderTarget(const int iTextureRTIndex);
	bool SetupVertexBuffer();
	void Render();
	void Reset();
	void SwapInPlaceholderTexture();
	void SwapOutPlaceholderTexture();

	#ifndef ASURA_BINK_NON_MULTITHREADING
	static const U32		s_uDecompressThreadIndex = 0;
	#endif

	volatile bool			m_bPlayerInited;
	HBINK					m_pxBink;
	U32						m_uStartFrameRateDiv;

	bool					m_bUseXAudio;	// if we're only using full volume sound then we don't need to - in which case it defaults to DirectSound
	void PTR4*				m_pPCMData;
	HBINKTRACK				m_pxBinkTrack;
	Asura_RingBuffer<char>	m_xPCMRingBuffer;
	bool					m_bAudioPlaying;

	Direct3D_BaseTexture*	m_pPlaceholderTexture;
	bool					m_bRenderingPlaceholderTexture;
	Direct3D_Texture*		m_pxTexture;
	Direct3D_VertexBuffer*	m_pxVertexBuffer;
	Asura_VideoToTexture*	m_pxParentObject;		// pointer to parent Asura_VideoToTexture object
};

u_int Asura_VideoToTexture_PCData::s_uDropSize = 0;

////////////////////////////////////////////////////////////////////////////////
//
// Asura_VideoToTexture_PCData::Asura_VideoToTexture_PCData()
//
////////////////////////////////////////////////////////////////////////////////

Asura_VideoToTexture_PCData::Asura_VideoToTexture_PCData()
{
	m_pxTexture = NULL;
	m_bPlayerInited = false;
	m_pxBink = NULL;
	m_pPlaceholderTexture = NULL;
	m_pxVertexBuffer = NULL;
	m_pxParentObject = NULL;
	
	m_pPCMData = NULL;
	m_pxBinkTrack = NULL;
	m_bAudioPlaying = false;
	m_bUseXAudio = false;
	m_bRenderingPlaceholderTexture = true;
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_VideoToTexture_PCData::~Asura_VideoToTexture_PCData()
//
////////////////////////////////////////////////////////////////////////////////

Asura_VideoToTexture_PCData::~Asura_VideoToTexture_PCData()
{
	Reset();
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_VideoToTexture_PCData::Reset()
//
////////////////////////////////////////////////////////////////////////////////

void Asura_VideoToTexture_PCData::Reset()
{
	if (!m_bPlayerInited)
	{
		return;
	}

	m_bPlayerInited = false;

	if (m_pPCMData)
	{
		Asura_DeallocateAlign(m_pPCMData);
		m_pPCMData = NULL;
	}

	m_xPCMRingBuffer.Destroy();

	if (m_pxBinkTrack)
	{
		BinkCloseTrack(m_pxBinkTrack);
		m_pxBinkTrack = NULL;
	}

	if (m_pxBink)
	{
	#ifndef ASURA_BINK_NON_MULTITHREADING
		// Stop the decompress thread
		BinkRequestStopAsyncThread(s_uDecompressThreadIndex);
		BinkWaitStopAsyncThread(s_uDecompressThreadIndex);
	#endif

		BinkClose(m_pxBink);
		m_pxBink = NULL;
	}

	if( m_pxVertexBuffer )
	{
		m_pxVertexBuffer->Release();
		m_pxVertexBuffer = NULL;
	}

	if (m_pxTexture)
	{
		m_pxTexture->Release();
	}

	if (!m_bRenderingPlaceholderTexture)
	{
		SwapInPlaceholderTexture();
	}
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_VideoToTexture::Platform_Initialise()
//
////////////////////////////////////////////////////////////////////////////////

bool Asura_VideoToTexture::Platform_Initialise()
{
	// create and initialise PC data
	Asura_VideoToTexture_PCData* pxPCData = new Asura_VideoToTexture_PCData;
	Asura_Assert(pxPCData, "Failed to allocate Asura_VideoToTexture_PCData. Out of memory?");
	pxPCData->m_pxParentObject = this;

	m_pPlatformData = pxPCData;

	pxPCData->m_bPlayerInited = true;

	// Use our memory alloc functions
	BinkSetMemory(Asura_VideoToTexture_PCData::OnMemAlloc, Asura_VideoToTexture_PCData::OnMemFree);

#ifndef ASURA_BINK_NON_MULTITHREADING
	// Start the decompressing thread
	S32 iError = BinkStartAsyncThread(pxPCData->s_uDecompressThreadIndex, NULL);
	Asura_Assertf(iError == 1, ("BinkStartAsyncThread() failed: 0x%X\n", iError));
#endif

	m_ePlayerState = PLAYER_STATE_INITIALISED;

	return true;
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_VideoToTexture::Platform_Initialise()
//
////////////////////////////////////////////////////////////////////////////////

void Asura_VideoToTexture::Platform_Shutdown()
{
	Asura_VideoToTexture_PCData *pxPCData = reinterpret_cast<Asura_VideoToTexture_PCData*>(m_pPlatformData);
	delete pxPCData;
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_VideoToTexture::Platform_Open()
//
////////////////////////////////////////////////////////////////////////////////

bool Asura_VideoToTexture::Platform_Open(const char* szFilename, const int iTextureRTIndex, u_int uSoundTrackIndex)
{
	Asura_VideoToTexture_PCData *pxPCData = reinterpret_cast<Asura_VideoToTexture_PCData*>(m_pPlatformData);

	bool bError = false;

	if (m_eFlags & ASURA_FMV_FLAG_NO_SOUND)
	{
		BinkSetSoundTrack(0, 0);
	}
	else
	{
		// if sounds not switched off and we need spacialised sounds... then use XAudio
		if (m_xSpeakerPropertyList.Size() > 0)
		{
			BinkSetSoundTrack(0, 0);
			pxPCData->m_bUseXAudio = true;
		}
		else
		{
			// else use DirectSound inside Bink
			BinkSoundUseDirectSound(0);
			BinkSetSoundTrack(1, &uSoundTrackIndex);
		}
	}

	Asura_PrintfToBfr(pxPCData->m_szFilename, "FMV/%s.bik", MAX_PATH, 0, szFilename);
	m_xFileName = pxPCData->m_szFilename;

	pxPCData->m_pxBink = BinkOpen( pxPCData->m_szFilename, BINKSNDTRACK | BINKFRAMERATE);
	if( pxPCData->m_pxBink )
	{
		u_int uTrackID = BinkGetTrackID(pxPCData->m_pxBink, uSoundTrackIndex);
		u_int uTrackType = BinkGetTrackType(pxPCData->m_pxBink, uSoundTrackIndex);

		u_int uFreq = GETTRACKFREQ(uTrackType);
		u_int uBitsPerSample = GETTRACKBITS(uTrackType);
		u_int uChannels = GETTRACKCHANS(uTrackType);

		// trying to spacialise a non-mono track
		if ((m_xSpeakerPropertyList.Size() > 0) && (uChannels > 1) && !(m_eFlags & ASURA_FMV_FLAG_NO_SOUND))
		{
			Asura_Assertf (false, ("Trying to spacialise an FMV with %d sound channels. Defaulting to full volume", uChannels));

			// we need to close and reopen the file before we can change soundtrack
			BinkClose(pxPCData->m_pxBink);
			pxPCData->m_pxBink = NULL;
			pxPCData->m_bUseXAudio = false;
			BinkSoundUseDirectSound(0);
			BinkSetSoundTrack(1, &uSoundTrackIndex);

			pxPCData->m_pxBink = BinkOpen( pxPCData->m_szFilename, BINKSNDTRACK );
			if( !pxPCData->m_pxBink )
			{
				Asura_Assertf(false, ("Unable to open FMV \"%s\"\n", pxPCData->m_szFilename));
				m_ePlayerState = PLAYER_STATE_ERROR;
				return false;
			}
		}

		pxPCData->m_uStartFrameRateDiv = pxPCData->m_pxBink->FrameRateDiv;

		// If we're using XAudio, we need to:
		//    Create a source voice,
		//    Open Bink raw sound track data
		//
		if (pxPCData->m_bUseXAudio)
		{
			Asura_Assertf (pxPCData->m_pxBink->NumTracks == 1, ("Only support for one audio track. \"%s\" contains %d tracks", pxPCData->m_szFilename, pxPCData->m_pxBink->NumTracks));

			u_int uTrackSize = BinkGetTrackMaxSize(pxPCData->m_pxBink, uTrackID);
			Asura_Assert (uTrackSize, "Zero audio track size");
			pxPCData->m_xPCMRingBuffer.Create(pxPCData->s_uPCMBufferSize, ASURA_ALIGN_16_BYTE);
//			Asura_OutputToDebugger_Retail(("m_xPCMRingBuffer start: 0x%x, end: 0x%x\n", pxPCData->m_xPCMRingBuffer.BeginAlloc(), ((int) pxPCData->m_xPCMRingBuffer.BeginAlloc()) + pxPCData->s_uPCMBufferSize));
			s_uTotalMemUsed += pxPCData->s_uPCMBufferSize;

			// intermediate buffer for grabbing PCM data out of Bink
			pxPCData->m_pPCMData = Asura_AllocateAlign(uTrackSize, ASURA_ALIGN_32_BYTE);
			Asura_Assert (pxPCData->m_pPCMData, "Unable to allocate PCM buffer");
			s_uTotalMemUsed += uTrackSize;

			pxPCData->m_pxBinkTrack = BinkOpenTrack(pxPCData->m_pxBink, uSoundTrackIndex);
			Asura_Assertf (pxPCData->m_pxBinkTrack, ("Unable to open sound track %d", uSoundTrackIndex));

			WAVEFORMATEX xWaveFormat;
			xWaveFormat.wFormatTag = (WORD) WAVE_FORMAT_PCM;
			xWaveFormat.nChannels = (WORD) uChannels;
			xWaveFormat.nSamplesPerSec = uFreq;
			xWaveFormat.nBlockAlign = (WORD) ((uChannels * uBitsPerSample) / 8);
			xWaveFormat.nAvgBytesPerSec = (DWORD) (xWaveFormat.nSamplesPerSec * xWaveFormat.nBlockAlign);
			xWaveFormat.wBitsPerSample = (WORD) uBitsPerSample;
			xWaveFormat.cbSize = 0;
			
			m_iSourceVoiceHandle = Asura_XAudio2::CreateVoice(xWaveFormat, false, false );
			Asura_Assert(m_iSourceVoiceHandle != ASURA_INVALID_HANDLE, "Can't create source voice");

			Asura_XAudio2_Voice* pxVoice = Asura_XAudio2::GetVoice(m_iSourceVoiceHandle);
			pxVoice->SetBufferEndCallback(Asura_VideoToTexture_PCData::VoiceBufferCallback);
		}

		// Setup our buffers, etc.
		bError = pxPCData->InitialiseRenderTarget(iTextureRTIndex);

		if (!bError)
		{
			m_ePlayerState = PLAYER_STATE_ERROR;
		}
		else
		{
			m_ePlayerState = PLAYER_STATE_FILE_OPENED;
		}

		m_xVideoInfo.m_uWidth = pxPCData->m_pxBink->Width;
		m_xVideoInfo.m_uHeight = pxPCData->m_pxBink->Height;

		float fFPS = static_cast<float>(pxPCData->m_pxBink->FrameRate) / static_cast<float>(pxPCData->m_pxBink->FrameRateDiv);
		m_xVideoInfo.m_fDuration = static_cast<float>(pxPCData->m_pxBink->Frames) / fFPS;
	}
	else
	{
		Asura_Assertf(false, ("Unable to open FMV \"%s\"\n", pxPCData->m_szFilename));
		m_ePlayerState = PLAYER_STATE_ERROR;
		bError = false;
	}

	return bError;
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_VideoToTexture::Platform_Update()
//
////////////////////////////////////////////////////////////////////////////////

void Asura_VideoToTexture::Platform_Update()
{
	Asura_VideoToTexture_PCData *pxPCData = reinterpret_cast<Asura_VideoToTexture_PCData*>(m_pPlatformData);

	// Has XAudio finished with our data?
	if (pxPCData->m_bUseXAudio && (Asura_VideoToTexture_PCData::s_uDropSize > 0))
	{
		pxPCData->m_xPCMRingBuffer.Drop(Asura_VideoToTexture_PCData::s_uDropSize);
		Asura_VideoToTexture_PCData::s_uDropSize = 0;
	}

	if  (m_ePlayerState == PLAYER_STATE_PLAYING)
	{

#ifdef ENABLE_DEBUG_LOG_RENDER_TIME
		u_int uRenderStartTime = Asura_Timers::GetMSecsSinceStart_Continuous();
#endif

		// Ready to render next frame?
		if( BinkWait(pxPCData->m_pxBink) )
		{
			return;
		}

		//
		// Grab PCM data out of Bink and feed it to our XAudio source voice
		//
		if (pxPCData->m_bUseXAudio)
		{
			Asura_XAudio2_Voice* pxVoice = Asura_XAudio2::GetVoice(m_iSourceVoiceHandle);
			Asura_Assert (pxVoice, "Unable to find voice");

			// This gives you the PCM audio data for *the current video frame*
			// Don't call this more than once for this frame or you'll get the same data twice.
			u_int uSize = BinkGetTrackData(pxPCData->m_pxBinkTrack, pxPCData->m_pPCMData);

			if (uSize > 0)
			{
				//
				// We need to right the data in two halves, as XAudio doesn't know we're using a ring buffer
				//

				// get a pointer to the data we're about to write
				void* pData = pxPCData->m_xPCMRingBuffer.BeginAlloc();
				u_int uWrittenSize = pxPCData->m_xPCMRingBuffer.Write((char *) pxPCData->m_pPCMData, uSize, false);

				// Queue data with XAudio
				pxVoice->EnqueueData(pData, uWrittenSize, false, (void*) uWrittenSize);

				// we didn't manage to write all the data, so write the rest
				if (uWrittenSize < uSize)
				{
					u_int uSizeLeft = uSize - uWrittenSize;

					// get a pointer to the data we're about to write
					void* pData = pxPCData->m_xPCMRingBuffer.BeginAlloc();
					uWrittenSize = pxPCData->m_xPCMRingBuffer.Write(((char *) pxPCData->m_pPCMData) + uWrittenSize, uSizeLeft, false);

					// Queue data with XAudio
					pxVoice->EnqueueData(pData, uWrittenSize, false, (void*) uWrittenSize);
				}


				if (!pxPCData->m_bAudioPlaying)
				{
					pxVoice->Start();
					pxPCData->m_bAudioPlaying = true;
				}
			}
		}

#ifdef ASURA_BINK_NON_MULTITHREADING
		// This blocks until the next video frame has been decompressed,
		// but is fast enough.
		BinkDoFrame( pxPCData->m_pxBink );
#endif

#ifndef ASURA_BINK_NON_MULTITHREADING
		// is the previous frame done yet?
		if (!BinkDoFrameAsyncWait( pxPCData->m_pxBink, 1000 ))	// wait 1 ms
		{
			return;
		}

#endif
      // skip frames if we need to...
		while ( BinkShouldSkip( pxPCData->m_pxBink ))
		{
			BinkNextFrame( pxPCData->m_pxBink );

#ifdef ASURA_BINK_NON_MULTITHREADING
			BinkDoFrame( pxPCData->m_pxBink );
#else
			BinkDoFrameAsync( pxPCData->m_pxBink, pxPCData->s_uDecompressThreadIndex, pxPCData->s_uDecompressThreadIndex );
			BinkDoFrameAsyncWait( pxPCData->m_pxBink, -1 );
#endif
		} 

#ifdef ASURA_BINK_NON_MULTITHREADING
		pxPCData->Render();
#endif

		// if we're not looping and we're at the end of the video... then stop
		if (!(m_eFlags & ASURA_FMV_FLAG_LOOP))
		{
			// Finished?
			if( pxPCData->m_pxBink->FrameNum == pxPCData->m_pxBink->Frames )
			{
				m_ePlayerState = PLAYER_STATE_COMPLETE;
				return;
			}
		}

		// internally bump to next frame
		BinkNextFrame( pxPCData->m_pxBink );

#ifndef ASURA_BINK_NON_MULTITHREADING
		BinkDoFrameAsync( pxPCData->m_pxBink, pxPCData->s_uDecompressThreadIndex, pxPCData->s_uDecompressThreadIndex );
#endif
#ifndef ASURA_BINK_NON_MULTITHREADING
		pxPCData->Render();
#endif

#ifdef ENABLE_DEBUG_LOG_RENDER_TIME
		u_int uRenderTime = Asura_Timers::GetMSecsSinceStart_Continuous() - uRenderStartTime;
		Asura_OutputToDebugger_Retail(("FMV Render time: %dms\n", uRenderTime));
#endif

#ifdef ASURA_BINK_SET_PAN_AND_VOLUME
		//
		// Calculate Left/Right Pan and Volume
		//
		// for ( Asura_LinkList_ConstIt< SpeakerProperty > xIt( m_xSpeakerPropertyList ); !xIt.Done(); xIt.Next() )
		Asura_LinkList_ConstIt< SpeakerProperty > xIt( m_xSpeakerPropertyList );
		if (!xIt.Done())
		{
			SpeakerProperty xProp = *xIt.GetCurrent();
			const Asura_Vector_3*	const pxCamPos		= Asura_Camera::GetPosition();
			const Asura_Matrix_3x3*	const pxCamOrient	= Asura_Camera::GetOrientation();
			Asura_Vector_3 xCamFacing = Asura_Vector_3(pxCamOrient->e11, pxCamOrient->e21, pxCamOrient->e31);

			xCamFacing.Normalise();
			Asura_Vector_3 xRelativePos = xProp.m_xPosition - *pxCamPos;
			float fSoundDist = xRelativePos.Normalise();
			float fDotProd = xCamFacing * xRelativePos;
			float fVolumeAttenuation = 0.0;

			if (fSoundDist <= xProp.m_fInnerRange)
			{
				fVolumeAttenuation = 1.0f;
			}
			else if (fSoundDist <= xProp.m_fOuterRange)
			{
				fSoundDist /= xProp.m_fOuterRange;
				fVolumeAttenuation = 1.0f - fSoundDist;
			}

			// range map from -1 -> +1 to 0 -> 65536
			fDotProd = ((fDotProd + 1.0f) * 65536.0f) / 2.0f;
			unsigned int uPan = (unsigned int) fDotProd;
			BinkSetPan(pxPCData->m_pxBink, 0, uPan);

			BinkSetVolume(pxPCData->m_pxBink, 0, (S32) (fVolumeAttenuation * 65536.0f));
		}
#endif	// ASURA_BINK_SET_PAN_AND_VOLUME
	}
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_VideoToTexture_PCData::InitialiseRenderTarget
//
// iTextureRTIndex = texture index of render target
////////////////////////////////////////////////////////////////////////////////

bool Asura_VideoToTexture_PCData::InitialiseRenderTarget(const int iTextureRTIndex)
{
	HRESULT iResult;

	Asura_Assertf (iTextureRTIndex != ASURA_TEXTURE_INVALID, ("Placeholder texture \"%s\" not found", m_pxParentObject->GetTextureRTName()->GetString()));

	if (iTextureRTIndex == ASURA_TEXTURE_INVALID)
	{
		return false;
	}

	// Create a new texture surface the size of the video frame
	Direct3D_Texture* pxTexture = NULL;

	// Create temporary texture for copying each frame into.
	iResult = Asura_Direct3D::D3DDevice->CreateTexture(
		m_pxBink->Width,
		m_pxBink->Height,
		1,
		0,
		D3DFMT_X8R8G8B8,
		D3DPOOL_MANAGED,
		&pxTexture,
		NULL );

	Asura_Assert (pxTexture, "Asura_VideoToTexture: Can't create render target");
	// D3DPOOL_MANAGED takes up sys ram as well as vid ram
	m_pxParentObject->s_uTotalMemUsed += m_pxBink->Width * m_pxBink->Height * 4;

	if( FAILED(iResult) || !pxTexture)
	{
		return false;
	}

	// if we're doing full screen rendering...
	if (iTextureRTIndex == ASURA_TEXTURE_NONE)
	{
		if (!SetupVertexBuffer())
		{
			return false;
		}
	}
	else
	{
		// save current texture interface
		m_pPlaceholderTexture = evilg_apxTextureStorage[iTextureRTIndex];
	}

	m_pxTexture = pxTexture;

	return true;
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_VideoToTexture_PCData::SetupVertexBuffer()
//
////////////////////////////////////////////////////////////////////////////////

bool Asura_VideoToTexture_PCData::SetupVertexBuffer()
{
	HRESULT iResult;

	//
	// Setup vertex buffer
	//
	iResult = Asura_Direct3D::D3DDevice->CreateVertexBuffer( 4 * sizeof(D3DQuadVertex),
		D3DUSAGE_WRITEONLY, D3DFVF_QUADVERTEX, D3DPOOL_MANAGED,
		&m_pxVertexBuffer, NULL );

	if( FAILED(iResult) )
	{
		return false;
	}

	// Fill the vertex buffer.
	{
		D3DQuadVertex* pxVertices;

		iResult = m_pxVertexBuffer->Lock( 0, 0, reinterpret_cast<void**>(&pxVertices), 0 );

		if( FAILED(iResult) )
		{
			return false;
		}

		// Calculate and write vertex data.
		{
			// Set common values.
			for( u_int u = 0; u < 4; ++u )
			{
				pxVertices[u].Z       = 0.0f;
				pxVertices[u].RHW     = 1000.0f;
				pxVertices[u].Diffuse = 0xFFFFFFFFu;
			}

			float fViewportWidth  = static_cast<float>( Asura_Viewport::GetWidth()  );
			float fViewportHeight = static_cast<float>( Asura_Viewport::GetHeight() );

			float fInsetX;
			float fInsetY;

			// Scale the movie to account for different screen / movie aspect
			// ratios. May want some options for this but for now assume 
			// that we want to display the movie at the correct aspect ratio.
			{
				float fMovieAspectRatio  = static_cast<float>(m_pxBink->Width) / static_cast<float>(m_pxBink->Height);
				float fScreenAspectRatio = ( Asura_Camera::IsWidescreen() ? 16.0f / 9.0f : 4.0f / 3.0f );

				float fQuadWidth, fQuadHeight;

				if( fMovieAspectRatio > fScreenAspectRatio )
				{
					fQuadWidth  = fViewportWidth;
					fQuadHeight = fViewportHeight * (fScreenAspectRatio / fMovieAspectRatio);
				}
				else
				{
					fQuadWidth  = fViewportWidth * (fScreenAspectRatio / fMovieAspectRatio);
					fQuadHeight = fViewportHeight;
				}

				fInsetX = (fViewportWidth - fQuadWidth) * 0.5f;
				fInsetY = (fViewportHeight - fQuadHeight) * 0.5f;
			}

			// Bottom left.
			pxVertices[0].X = fInsetX;
			pxVertices[0].Y = fViewportHeight - fInsetY;
			pxVertices[0].U = 0.0f;
			pxVertices[0].V = 1.0f;

			// Top left.
			pxVertices[1].X = fInsetX;
			pxVertices[1].Y = fInsetY;
			pxVertices[1].U = 0.0f;
			pxVertices[1].V = 0.0f;

			// Top right.
			pxVertices[2].X = fViewportWidth - fInsetX;
			pxVertices[2].Y = fInsetY;
			pxVertices[2].U = 1.0f;
			pxVertices[2].V = 0.0f;

			// Bottom right.
			pxVertices[3].X = fViewportWidth - fInsetX;
			pxVertices[3].Y = fViewportHeight - fInsetY;
			pxVertices[3].U = 1.0f;
			pxVertices[3].V = 1.0f;
		}

		iResult = m_pxVertexBuffer->Unlock();
		if( FAILED(iResult) )
		{
			return false;
		}
	}

	return true;
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_VideoToTexture::Platform_Play()
//
////////////////////////////////////////////////////////////////////////////////

bool Asura_VideoToTexture::Platform_Play()
{
	Asura_VideoToTexture_PCData *pxPCData = reinterpret_cast<Asura_VideoToTexture_PCData*>(m_pPlatformData);

	if (pxPCData->m_pxBink)
	{
		// unpause
		BinkPause(pxPCData->m_pxBink, 0);
	}

	m_ePlayerState = PLAYER_STATE_PLAYING;
	return true;
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_VideoToTexture::Platform_Pause()
//
////////////////////////////////////////////////////////////////////////////////

bool Asura_VideoToTexture::Platform_Pause()
{
	Asura_VideoToTexture_PCData *pxPCData = reinterpret_cast<Asura_VideoToTexture_PCData*>(m_pPlatformData);
	bool bPaused = false;
	
	if (pxPCData->m_pxBink)
	{
		BinkPause(pxPCData->m_pxBink, 1);
		m_ePlayerState = PLAYER_STATE_PAUSED;
		bPaused = true;
	}

	return bPaused;
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_VideoToTexture::Platform_SetPlaySpeed()
//
////////////////////////////////////////////////////////////////////////////////

bool Asura_VideoToTexture::Platform_SetPlaySpeed(float fSpeed)
{
	Asura_VideoToTexture_PCData *pxPCData = reinterpret_cast<Asura_VideoToTexture_PCData*>(m_pPlatformData);

	if (pxPCData->m_pxBink)
	{
		// the Bink docs say we shouldn't change these but.... seems to work ok
		pxPCData->m_pxBink->FrameRateDiv = pxPCData->m_uStartFrameRateDiv * ((int) (1.0/fSpeed));
	}

	return true;
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_VideoToTexture::Platform_Render()
//
////////////////////////////////////////////////////////////////////////////////

void Asura_VideoToTexture::Platform_Render()
{
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_VideoToTexture_PCData::Render
//
// Grabs the current video frame from the player and copies it to our render target
////////////////////////////////////////////////////////////////////////////////

void Asura_VideoToTexture_PCData::Render()
{
	SCOPEDPIXEVENT((0xffeebbcc, L"VideoToTexture Render"));

	// Make sure we're rendering the texture containing the video frames
	if (m_bRenderingPlaceholderTexture)
	{
		SwapOutPlaceholderTexture();
	}

	// Can't LockRect() a BaseTexture
	Direct3D_Texture* pxTexture2D;
	void** const ppTexture2D = reinterpret_cast< void** >( &pxTexture2D );

	if( SUCCEEDED( m_pxTexture->QueryInterface( IID_IDirect3DTexture9, ppTexture2D ) ) )
	{
		D3DLOCKED_RECT xRect;

		if(pxTexture2D->LockRect(0, &xRect, 0, 0) == D3D_OK)
		{		
//			use BINKCOPYALL flag to force copy the whole frame (not just the differences).
			S32 iError = BinkCopyToBuffer(m_pxBink, xRect.pBits, xRect.Pitch, m_pxBink->Height, 0, 0, BINKSURFACE32);
			iError = iError;
			pxTexture2D->UnlockRect(0);
		}
		else
		{
			Asura_Assert(false, "Lock failed");
		}

		// render full screen
		if (m_pxParentObject->GetTextureRTIndex() == ASURA_TEXTURE_NONE)
		{
			// Clear back buffer as well to deal with any bits the movie doesn't
			// cover.
			Asura_Direct3D::D3DDevice->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_ARGB(0,0,0,0), 1.0f, 0 );

			Asura_Render::CurrentStates.RequestAlphaTest( false );
			Asura_Render::CurrentStates.RequestCullMode( ASURA_CULLMODE_NONE );
			Asura_Render::CurrentStates.RequestTextureOpMode( ASURA_TEXTUREOP_OPAQUE );
			Asura_Render::CurrentStates.RequestTranslucencyMode( ASURA_TRANSLUCENCY_OFF );
			Asura_Render::CurrentStates.RequestZBufferEnabled( false );
			Asura_Render::CurrentStates.RequestZBufferWriteEnabled( false );

			DWORD ulOldAddressU, ulOldAddressV;
			Asura_Direct3D::D3DDevice->GetSamplerState( 0, D3DSAMP_ADDRESSU, &ulOldAddressU );
			Asura_Direct3D::D3DDevice->GetSamplerState( 0, D3DSAMP_ADDRESSV, &ulOldAddressV );

			Asura_Direct3D::D3DDevice->SetSamplerState( 0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP );
			Asura_Direct3D::D3DDevice->SetSamplerState( 0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP );

			Asura_Direct3D::D3DDevice->SetTexture( 0, m_pxTexture );

			Asura_Direct3D_Shaders::RequestFixedFunctionVertexShader( D3DFVF_QUADVERTEX );
			Asura_Direct3D::D3DDevice->SetStreamSource( 0, m_pxVertexBuffer, 0, sizeof(D3DQuadVertex) );
			Asura_Direct3D::D3DDevice->DrawPrimitive( D3DPT_TRIANGLEFAN, 0, 2 );

			Asura_Direct3D::D3DDevice->SetSamplerState( 0, D3DSAMP_ADDRESSU, ulOldAddressU );
			Asura_Direct3D::D3DDevice->SetSamplerState( 0, D3DSAMP_ADDRESSV, ulOldAddressV );

			// Ensure any texture required for further rendering gets uploaded, since
			// we've bypassed the renderstate stuff above for that.
			Asura_Render::CurrentStates.RequestTextureIndex( 0, ASURA_TEXTURE_INVALID );
		}

		pxTexture2D->Release();

		// Render the subtitles over the frame
		if (m_pxParentObject->GetSubtitleCallback())
		{
			m_pxParentObject->GetSubtitleCallback()(m_pxParentObject->GetPlayedTimeInSeconds());
		}
	}
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_VideoToTexture::Platform_GetPlayedTimeInSeconds
//
////////////////////////////////////////////////////////////////////////////////

float Asura_VideoToTexture::Platform_GetPlayedTimeInSeconds()
{
	Asura_VideoToTexture_PCData *pxPCData = reinterpret_cast<Asura_VideoToTexture_PCData*>(m_pPlatformData);

	float fFPS = static_cast<float>(pxPCData->m_pxBink->FrameRate) / static_cast<float>(pxPCData->m_pxBink->FrameRateDiv);
	return( static_cast<float>(pxPCData->m_pxBink->FrameNum) / fFPS );
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_VideoToTexture_PCData::OnMemAlloc
//
// Called when bink needs to alloc main memory.
// Must return 32-byte aligned memory pointer.
////////////////////////////////////////////////////////////////////////////////

void PTR4* RADLINK Asura_VideoToTexture_PCData::OnMemAlloc(U32 uNumBytes)
{
	Asura_VideoToTexture::s_uTotalMemUsed += uNumBytes;
	void *pNewMem = Asura_AllocateAlign(uNumBytes, ASURA_ALIGN_32_BYTE);

#ifdef ENABLE_DEBUG_LOG_MEM_ALLOCS
	Asura_OutputToDebugger_Retail(("Asura_VideoToTexture_PCData:Alloc of %dB [Total:%dB, 0x%X]\n", uNumBytes, Asura_VideoToTexture::s_uTotalMemUsed, pNewMem));
#endif

	return pNewMem;
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_VideoToTexture_PCData::OnMemFree
//
// Called when bink needs to free main memory.
////////////////////////////////////////////////////////////////////////////////

void RADLINK Asura_VideoToTexture_PCData::OnMemFree(void PTR4* pPtr)
{
#ifdef ENABLE_DEBUG_LOG_MEM_ALLOCS
	Asura_OutputToDebugger_Retail(("Asura_VideoToTexture_PCData:Free 0x%X\n", pPtr));
#endif

	return Asura_DeallocateAlign(pPtr);
}

////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////

void Asura_VideoToTexture_PCData::VoiceBufferCallback( void* pContext)
{
	u_int uDropSize = (u_int) pContext;
	s_uDropSize += uDropSize;
}

////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////

void Asura_VideoToTexture_PCData::SwapInPlaceholderTexture()
{
	//
	// Put the old texture data back
	//
	int iTex = m_pxParentObject->GetTextureRTIndex();
	if (iTex != ASURA_TEXTURE_NONE)
	{
		evilg_apxTextureStorage[iTex] = m_pPlaceholderTexture;
		m_bRenderingPlaceholderTexture = true;
	}
}

////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////

void Asura_VideoToTexture_PCData::SwapOutPlaceholderTexture()
{
	int iTex = m_pxParentObject->GetTextureRTIndex();
	if (iTex != ASURA_TEXTURE_NONE)
	{
		// replace placeholder texture data
		evilg_apxTextureStorage[iTex] = m_pxTexture;
		m_bRenderingPlaceholderTexture = false;
	}
}

#endif // ASURA_USE_VIDEO_TO_TEXTURE_SYSTEM
