#include "stdafx.h"
#include "PresenterEngine.h"
#include "MediaTypeBuilders.h"
#include "CustomGUID.h"
#include "CMediaSessionNative.h"
#include "Direct3D9\CDirect3DNative.h"
#include "Direct3D9\DrawTexture.h"

namespace FDK
{
// public:

	PresenterEngine::PresenterEngine( HRESULT& hr )
	{
		this->m_hwnd = NULL;
		this->m_szVideo.cx = 0;
		this->m_szVideo.cy = 0;
		this->pTexture = NULL;
		this->b表示要求 = true;
	}
	PresenterEngine::~PresenterEngine()
	{
		SAFE_COM_RELEASE( this->pTexture );
	}

	HRESULT PresenterEngine::GetService( REFGUID guidService, REFIID riid, void** ppv )
	{
		HRESULT hr = S_OK;

		if( riid == __uuidof(IDirect3DDeviceManager9) )	// 本来は guidService == MR_VIDEO_ACCELERATION_SERVICE でなければこれを受けてはならないが……
		{
			#pragma region [ D3DDeviceManager9 が有効ならそれを返す。]
			//-----------------
			IDirect3DDeviceManager9 *pd3dm = CDirect3DNative::GetDirect3DDeviceManager();
			if( pd3dm == NULL )
			{
				hr = MF_E_UNSUPPORTED_SERVICE;
			}
			else
			{
				*ppv = pd3dm;
			}
			//-----------------
			#pragma endregion
		}
		else
		{
			hr = MF_E_UNSUPPORTED_SERVICE;
		}

		return hr;
	}
	HRESULT PresenterEngine::t指定されたフォーマットが利用可能か調べる( D3DFORMAT format )
	{
		HRESULT hr = S_OK;

		// Direct3D9Ex を取得。
		IDirect3D9Ex *pd3d9 = CDirect3DNative::GetDirect3DEx();
		if( pd3d9 == NULL )
			return D3DERR_INVALIDCALL;

		UINT uAdapter;				// アダプタ
		D3DDEVTYPE type;			// デバイスタイプ
		D3DDISPLAYMODE mode;		// モード
		D3DDEVICE_CREATION_PARAMETERS params;

		// デバイスを排他取得。
		HANDLE hDevice = NULL;
		IDirect3DDevice9Ex *pDev = NULL;

		try
		{
			CDirect3DNative::tデバイスをロックする( true, &hDevice, &pDev );

			// デバイスから、デバイスアダプタ・デバイスタイプを取得。

			if( pDev )
			{
				// (A) D3Dデバイスが生成済みならパラメータを取得する。
				tFAILEDなら帰還( hr = pDev->GetCreationParameters( &params ) );
				uAdapter = params.AdapterOrdinal;
				type = params.DeviceType;
			}
			else
			{
				// (B) D3Dデバイスが未生成なら初期値を設定。
				uAdapter = D3DADAPTER_DEFAULT;
				type = D3DDEVTYPE_HAL;
			}


			// D3D9Exから、デバイスモードを取得。

			tFAILEDなら帰還( hr = pd3d9->GetAdapterDisplayMode( uAdapter, &mode ) );


			// 指定されたフォーマットが、現在のデバイスアダプタ、デバイスタイプ、デバイスモードで利用可能かを返す。

			tFAILEDなら帰還( hr = pd3d9->CheckDeviceType( uAdapter, type, mode.Format, format, TRUE ) );
		}
		finally
		{
			CDirect3DNative::tデバイスのロックを解除する( hDevice );
			SAFE_COM_RELEASE( pd3d9 );
		}

		return hr;	// CheckDeviceType() の戻り値を返す。
	}
	
	HRESULT PresenterEngine::SetVideoWindow( HWND hwnd )
	{
		this->m_hwnd = hwnd;	// エラーチェックなし
		return S_OK;
	}
	HWND    PresenterEngine::GetVideoWindow()
	{
		return this->m_hwnd;
	}
	void	PresenterEngine::SetVideoSize( LONG width, LONG height )
	{
		AutoLock lock( this->csTexture );	// テクスチャ描画中に変更しないようロック

		this->m_szVideo.cx = width;
		this->m_szVideo.cy = height;
	}
	SIZE	PresenterEngine::GetVideoSize()
	{
		return this->m_szVideo;
	}

	HRESULT PresenterEngine::CreateVideoSamples( IMFMediaType *pMediaType, CVideoSampleList& videoSampleQueue )
	{
		if( this->m_hwnd == NULL || this->m_szVideo.cx == 0 || this->m_szVideo.cy == 0 )
			return MF_E_INVALIDREQUEST;

		if( pMediaType == NULL )
			return E_INVALIDARG;


		HRESULT hr = S_OK;
		VideoTypeBuilder *pVideoType = NULL;
		UINT32 width = 0, height = 0;
		DWORD d3dFormat = 0;

		
		// テクスチャをロックする。

		AutoLock( this->csTexture );

		try
		{
			// 以前のリソースとテクスチャを解放する。

			this->ReleaseResources();
			SAFE_COM_RELEASE( this->pTexture );


			// メディアタイプからフォーマットを取得する。

			tFAILEDなら帰還( hr = VideoTypeBuilder::Create( pMediaType, &pVideoType ) );	// VideoTypeBuilder を作成
			tFAILEDなら帰還( hr = pVideoType->GetFourCC( &d3dFormat ) );					// メディアタイプのフォーマットを取得


			// テクスチャサイズを取得する。

			width = this->m_szVideo.cx;
			height = this->m_szVideo.cy;


			// D3Dデバイスをロックする。

			HANDLE hDevice = NULL;
			IDirect3DDevice9Ex *pdev = NULL;

			try
			{
				CDirect3DNative::tデバイスをロックする( true, &hDevice, &pdev );

				IMFSample *pVideoSample = NULL;
				IDirect3DTexture9 *pTexture = NULL;
				IDirect3DSurface9 *pSurface = NULL;
				D3DCOLOR clrBlack = D3DCOLOR_ARGB( 0xFF, 0x00, 0x00, 0x00 );

				// ビデオサンプルを作成する。

				for( int i = 0; i < PRESENTER_BUFFER_COUNT; i++ )
				{
					try
					{
						// プレゼンタ用テクスチャを作成。
						tFAILEDなら帰還( hr = pdev->CreateTexture( width, height, 1, D3DUSAGE_RENDERTARGET, (D3DFORMAT) d3dFormat, D3DPOOL_DEFAULT, &pTexture, NULL ) );

						// プレゼンタ用テクスチャからのサーフェイスを取得。
						tFAILEDなら帰還( hr = pTexture->GetSurfaceLevel( 0, &pSurface ) );

						// プレゼンタ用サーフェイスを塗りつぶす。
						tFAILEDなら帰還( hr = pdev->ColorFill( pSurface, NULL, clrBlack ) );

						// プレゼンタ用サーフェイスからビデオサンプルを作成。
						tFAILEDなら帰還( hr = ::MFCreateVideoSampleFromSurface( pSurface, &pVideoSample ) );

						// ビデオサンプルキューへビデオサンプルを追加。
						tFAILEDなら帰還( hr = videoSampleQueue.InsertBack( pVideoSample ) );

						// ビデオサンプルの属性へプレゼンタ用テクスチャを割り当てる。
						tFAILEDなら帰還( hr = pVideoSample->SetUnknown( MFSamplePresenter_Texture, pTexture ) );
					}
					finally
					{
						SAFE_COM_RELEASE( pVideoSample );
						SAFE_COM_RELEASE( pSurface );
						SAFE_COM_RELEASE( pTexture );
					}
				}
			}
			finally
			{
				if( hDevice != NULL )
					CDirect3DNative::tデバイスのロックを解除する( hDevice );
			}
		}
		finally
		{
			SAFE_COM_RELEASE( pVideoType );
		}

		return S_OK;
	}
	HRESULT PresenterEngine::tサンプルを受け取る( IMFSample* pSample, LONGLONG llTarget )
	{
		#pragma region [ 表示要求がない（＝前のテクスチャがまだ描画されていない）なら何もしないで帰る。]
		//-----------------
		bool b表示要求 = false;

		this->cs表示要求.Lock();
		b表示要求 = this->b表示要求;
		this->cs表示要求.Unlock();

		if( ! b表示要求 )
			return S_OK;
		//-----------------
		#pragma endregion

		HRESULT hr = S_OK;
		HANDLE hDevice = NULL;
		IDirect3DDevice9 *pDevice = NULL;
		IDirect3DTexture9* pTexture = NULL;
		IUnknown* pUnknown = NULL;

		if( pSample )
		{
			// ビデオサンプルからプレゼンタ用テクスチャを IUnknown で取得する。

			hr = pSample->GetUnknown( MFSamplePresenter_Texture, IID_IUnknown, (void**) &pUnknown );

			if( hr == D3DERR_DEVICELOST || hr == D3DERR_DEVICENOTRESET || hr == D3DERR_DEVICEHUNG )
			{
				this->PaintFrameWithGDI();
				return S_OK;
			}

			if( pUnknown )
			{
				// IUnknown から IDirect3DTexture9 を取得する。
				
				hr = pUnknown->QueryInterface( __uuidof(IDirect3DTexture9), (void**) &pTexture );

				if( hr == D3DERR_DEVICELOST || hr == D3DERR_DEVICENOTRESET || hr == D3DERR_DEVICEHUNG )
				{
					this->PaintFrameWithGDI( );
					return S_OK;
				}


				// 取得したテクスチャのポインタを内部に保管。

				this->csTexture.Lock();					// テクスチャが描画されないようロック

				SAFE_COM_RELEASE( this->pTexture );		// 以前のポインタは解放。
				this->pTexture = pTexture;				// 新しいポインタを保管。
				this->pTexture->AddRef();				// 参照カウンタ増加あり。

				this->csTexture.Unlock();


				// 表示要求 OFF 。

				this->cs表示要求.Lock();
				this->b表示要求 = false;
				this->cs表示要求.Unlock();
			}
		}
		else
		{
			// サンプルがないときは何もしない。
		}

		SAFE_COM_RELEASE( pTexture );
		SAFE_COM_RELEASE( pUnknown );

		return S_OK;
	}
	void	PresenterEngine::t次のサンプルを要求する()
	{
		this->cs表示要求.Lock();
		this->b表示要求 = true;
		this->cs表示要求.Unlock();
	}
	void	PresenterEngine::tサンプルを描画する( LPRECT prcSrc, LPRECT prcDest, UINT32 u透明度, UINT32 u加算合成 )
	{
		if( this->pTexture == NULL )
			return;

		AutoLock lock( this->csTexture );		// テクスチャならびにサイズを変更されないようロック

		RECT rcSrc;
		RECT rcDest;


		// 転送元矩形を決定

		if( prcSrc )
		{
			rcSrc = *prcSrc;
		}
		else
		{
			rcSrc.left = 0;
			rcSrc.top = 0;
			rcSrc.right = this->m_szVideo.cx;
			rcSrc.bottom = this->m_szVideo.cy;
		}


		// 転送先矩形 rcDest を決定

		if( prcDest && !::IsRectEmpty( prcDest ) )
			rcDest = *prcDest;
		else
			rcDest = rcSrc;


		// 拡大率を決定

		float f拡大率X = (float)(rcDest.right - rcDest.left) / (float)(rcSrc.right - rcSrc.left);
		float f拡大率Y = (float)(rcDest.bottom - rcDest.top) / (float)(rcSrc.bottom - rcSrc.top);


		// 描画。

		IDirect3DDevice9Ex *pDevice = CDirect3DNative::GetDirect3DDevice9Ex();		// デバイスは MF によってロックされているという前提。
		try
		{
			DrawTexture::t2D描画( pDevice, this->pTexture, (int) u透明度, ( ( u加算合成 != 0 ) ? true : false ), f拡大率X, f拡大率Y, rcDest.left, rcDest.top );
		}
		finally
		{
			SAFE_COM_RELEASE( pDevice );
		}
	}

	UINT	PresenterEngine::RefreshRate()
	{
		return CDirect3DNative::GetDirect3DDisplayMode().RefreshRate;
	}
	void	PresenterEngine::ReleaseResources()
	{
		// 特になし。
	}


// protected:

	void PresenterEngine::PaintFrameWithGDI()
	{
		HDC hdc = ::GetDC( this->m_hwnd );

		if( hdc )
		{
			HBRUSH hBrush = ::CreateSolidBrush( RGB( 0, 0, 0 ) );

			if( hBrush )
			{
				RECT rc;
				::GetClientRect( m_hwnd, &rc );
				::FillRect( hdc, &rc, hBrush );
				::DeleteObject( hBrush );
			}

			::ReleaseDC( m_hwnd, hdc );
		}
	}
}
