/* -LICENSE-START-
 ** Copyright (c) 2010 Blackmagic Design
 **
 ** Permission is hereby granted, free of charge, to any person or organization
 ** obtaining a copy of the software and accompanying documentation covered by
 ** this license (the "Software") to use, reproduce, display, distribute,
 ** execute, and transmit the Software, and to prepare derivative works of the
 ** Software, and to permit third-parties to whom the Software is furnished to
 ** do so, all subject to the following:
 ** 
 ** The copyright notices in the Software and this entire statement, including
 ** the above license grant, this restriction and the following disclaimer,
 ** must be included in all copies of the Software, in whole or in part, and
 ** all derivative works of the Software, unless such copies or derivative
 ** works are solely in the form of machine-executable object code generated by
 ** a source language processor.
 ** 
 ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 ** FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
 ** SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
 ** FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
 ** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 ** DEALINGS IN THE SOFTWARE.
 ** -LICENSE-END-
 */
//
//  BMDOpenGLOutput.cpp
//  OpenGLOutput
//

#include "BMDOpenGLOutput.h"

BMDOpenGLOutput::BMDOpenGLOutput()
	: pDL(NULL), pDLOutput(NULL), pDLVideoFrame(NULL),
	hGLWnd(NULL), hGLDC(NULL), hGLRC(NULL), hOldGLRC(NULL)	
{
	InitializeCriticalSection(&pMutex);
	pGLScene = new GLScene();
}

BMDOpenGLOutput::~BMDOpenGLOutput()
{
	if (pDLOutput != NULL)
	{
		pDLOutput->SetScreenPreviewCallback(NULL);
		pDLOutput->SetScheduledFrameCompletionCallback(NULL);

		pDLOutput->Release();
		pDLOutput = NULL;
	}
	if (pDL != NULL)
	{
		pDL->Release();
		pDL = NULL;
	}

	delete pRenderDelegate;
	pRenderDelegate = NULL;

	delete pGLScene;
	pGLScene = NULL;

	ReleaseDC (hGLWnd, hGLDC );
	DestroyWindow(hGLWnd);

	DeleteCriticalSection(&pMutex);
}

void BMDOpenGLOutput::ResetFrame()
{
	// Fill frame with black
	void*	pFrame;
	pDLVideoFrame->GetBytes((void**)&pFrame);
	memset(pFrame, 0x00, pDLVideoFrame->GetRowBytes() * uiFrameHeight);
}

void BMDOpenGLOutput::SetPreroll()
{
	// Set 1 second preroll
	for (unsigned __int32 i=0; i < uiFPS; i++)
	{
		if (pDLOutput->ScheduleVideoFrame(pDLVideoFrame, (uiTotalFrames * frameDuration), frameDuration, frameTimescale) != S_OK)
			return;
		uiTotalFrames++;
	}
}

bool BMDOpenGLOutput::InitDeckLink()
{
	bool bSuccess = FALSE;
	IDeckLinkIterator* pDLIterator = NULL;

	HRESULT result; 
	result = CoCreateInstance(CLSID_CDeckLinkIterator, NULL, CLSCTX_ALL, IID_IDeckLinkIterator, (void**)&pDLIterator);
	if (FAILED(result))
	{
		MessageBox(NULL, _T("Please install the Blackmagic DeckLink drivers to use the features of this application."), _T("This application requires the DeckLink drivers installed."), MB_OK);
		return false;
	}

	if (pDLIterator->Next(&pDL) != S_OK)
	{
		MessageBox(NULL, _T("You will not be able to use the features of this application until a DeckLink device is installed."), _T("This application requires a DeckLink device."), MB_OK);
		goto error;
	}
	
	if (pDL->QueryInterface(IID_IDeckLinkOutput, (void**)&pDLOutput) != S_OK)
		goto error;
	
	pRenderDelegate = new RenderDelegate(this);
	if (pRenderDelegate == NULL)
		goto error;
	
	if (pDLOutput->SetScheduledFrameCompletionCallback(pRenderDelegate) != S_OK)
		goto error;
	
	bSuccess = TRUE;

error:
	
	if (!bSuccess)
	{
		if (pDLOutput != NULL)
		{
			pDLOutput->Release();
			pDLOutput = NULL;
		}
		if (pDL != NULL)
		{
			pDL->Release();
			pDL = NULL;
		}
	}

	if (pDLIterator != NULL)
	{
		pDLIterator->Release();
		pDLIterator = NULL;
	}

	return bSuccess;
}

bool BMDOpenGLOutput::InitGUI(IDeckLinkScreenPreviewCallback *previewCallback)
{
	// Set preview
	if (previewCallback != NULL)
	{
		pDLOutput->SetScreenPreviewCallback(previewCallback);
	}
	return true;
}

bool BMDOpenGLOutput::InitOpenGL()
{
	const GLubyte * strExt;
	GLboolean isFBO = 1;
	
	// Create temporary OpenGL context to check extensions support
	PIXELFORMATDESCRIPTOR pfd;
	int iPixelFormat;
	hGLWnd = CreateWindow( 
		_T("OPENGLCTX"), _T("OPENGLCTX"), 
		WS_POPUPWINDOW,
		0, 0, 10, 10,
		NULL, NULL, 0, NULL );
	hGLDC = GetDC(hGLWnd);

	ZeroMemory( &pfd, sizeof( pfd ) );
	pfd.nSize = sizeof( pfd );
	pfd.nVersion = 1;
	pfd.dwFlags = PFD_GENERIC_ACCELERATED | PFD_SUPPORT_OPENGL;
	pfd.iPixelType = PFD_TYPE_RGBA;
	pfd.cColorBits = 32;
	pfd.cDepthBits = 16;
	pfd.cAlphaBits = 8;
	pfd.iLayerType = PFD_MAIN_PLANE;
	iPixelFormat = ChoosePixelFormat( hGLDC, &pfd );
	SetPixelFormat( hGLDC, iPixelFormat, &pfd );
	hGLRC = wglCreateContext( hGLDC );
	wglMakeCurrent( hGLDC, hGLRC );

	strExt = glGetString (GL_EXTENSIONS);
	isFBO = strstr((char*)strExt, "GL_EXT_framebuffer_object") != NULL;

	wglDeleteContext( hGLRC );
	hGLRC = NULL;

	if (!isFBO)
	{
		MessageBox(NULL, _T("OpenGL extention \"GL_EXT_framebuffer_object\" is not supported."), _T("OpenGL initialization error."), MB_OK);
		return false;
	}

	return true;
}

bool BMDOpenGLOutput::Start()
{
	IDeckLinkDisplayModeIterator*		pDLDisplayModeIterator;
	IDeckLinkDisplayMode*				pDLDisplayMode = NULL;
	
	if (pDLOutput->GetDisplayModeIterator(&pDLDisplayModeIterator) == S_OK)
	{
		if (pDLDisplayModeIterator->Next(&pDLDisplayMode) != S_OK)
		{
			MessageBox(NULL, _T("Cannot find video mode."), _T("DeckLink error."), MB_OK);
			pDLDisplayModeIterator->Release();
			return false;
		}
		pDLDisplayModeIterator->Release();
	}
	
	uiFrameWidth = pDLDisplayMode->GetWidth();
	uiFrameHeight = pDLDisplayMode->GetHeight();
	pDLDisplayMode->GetFrameRate(&frameDuration, &frameTimescale);
	
	uiFPS = ((frameTimescale + (frameDuration-1))  /  frameDuration);
	
	if (pDLOutput->EnableVideoOutput(pDLDisplayMode->GetDisplayMode(), bmdVideoOutputFlagDefault) != S_OK)
		return false;
	
	if (pDLOutput->CreateVideoFrame(uiFrameWidth, uiFrameHeight, uiFrameWidth*4, bmdFormat8BitBGRA, bmdFrameFlagFlipVertical, &pDLVideoFrame) != S_OK)
		return false;
	
	uiTotalFrames = 0;
	
	ResetFrame();
	SetPreroll();

	pDLOutput->StartScheduledPlayback(0, 100, 1.0);
	
	return true;
}

bool BMDOpenGLOutput::Stop()
{
	pDLOutput->StopScheduledPlayback(0, NULL, 0);
	pDLOutput->DisableVideoOutput();

	// Let DeckLink finish with context
	EnterCriticalSection(&pMutex);
	if (pDLVideoFrame != NULL)
	{
		pDLVideoFrame->Release();
		pDLVideoFrame = NULL;
	}

	if (hGLRC != NULL)
	{
		// Delete FBO
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
		glDeleteRenderbuffersEXT(1, &idDepthBuf);
		glDeleteRenderbuffersEXT(1, &idColorBuf);
		glDeleteFramebuffersEXT(1, &idFrameBuf);

		wglDeleteContext( hGLRC );
		hGLRC = NULL;
	}
	LeaveCriticalSection(&pMutex);

	return true;
}

void BMDOpenGLOutput::RenderToDevice()
{
	EnterCriticalSection(&pMutex);
	void*	pFrame;

	pDLVideoFrame->GetBytes((void**)&pFrame);	

	// On the first call create OpenGL render context for current thread
	if (hGLRC == NULL)
	{
		hGLRC = wglCreateContext( hGLDC );
		
		hOldGLRC = wglGetCurrentContext();
		wglMakeCurrent( hGLDC, hGLRC );

		getGLExtensions().ResolveExtensions();

		pGLScene->InitScene();

		// Create FBO for off-screen rendering with exact video frame width and height
		glGenFramebuffersEXT(1, &idFrameBuf);
		glGenRenderbuffersEXT(1, &idColorBuf);
		glGenRenderbuffersEXT(1, &idDepthBuf);
		
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, idFrameBuf);
		
		glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, idColorBuf);
		glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_RGBA8, uiFrameWidth, uiFrameHeight);
		glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, idDepthBuf);
		glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, uiFrameWidth, uiFrameHeight);
		
		glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT, idColorBuf);
		glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, idDepthBuf);
		
		glStatus = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
		if (glStatus != GL_FRAMEBUFFER_COMPLETE_EXT)
		{
			MessageBox(NULL, _T("Cannot initialize framebuffer."), _T("OpenGL initialization error."), MB_OK);
		}
	}
	// Otherwise make our context current
	else
	{
		hOldGLRC = wglGetCurrentContext();
		wglMakeCurrent( hGLDC, hGLRC );
	}

	// Draw OpenGL scene and read it to frame
	pGLScene->DrawScene(0, 0, uiFrameWidth, uiFrameHeight);
	glReadPixels(0, 0, uiFrameWidth, uiFrameHeight, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, pFrame);

	if (pDLOutput->ScheduleVideoFrame(pDLVideoFrame, (uiTotalFrames * frameDuration), frameDuration, frameTimescale) == S_OK)
	{
		uiTotalFrames++;
	}

	wglMakeCurrent( hGLDC, hOldGLRC );
	
	LeaveCriticalSection(&pMutex);
}


////////////////////////////////////////////
// Render Delegate Class
////////////////////////////////////////////
RenderDelegate::RenderDelegate (BMDOpenGLOutput* pOwner)
{
	m_pOwner = pOwner;
}

RenderDelegate::~RenderDelegate ()
{
	
}

HRESULT	RenderDelegate::ScheduledFrameCompleted (IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult result)
{
	m_pOwner->RenderToDevice();
	return S_OK;
}

HRESULT	RenderDelegate::ScheduledPlaybackHasStopped ()
{
	return S_OK;
}
